├── .gitignore
├── .coveralls.yml
├── view
└── frontend
│ ├── web
│ ├── css
│ │ └── source
│ │ │ └── _module.less
│ ├── js
│ │ └── view
│ │ │ └── payment
│ │ │ ├── stripe.js
│ │ │ └── method-renderer
│ │ │ ├── vault.js
│ │ │ └── cc-form.js
│ └── template
│ │ └── payment
│ │ ├── vault.html
│ │ └── cc-form.html
│ └── layout
│ ├── vault_cards_listaction.xml
│ └── checkout_index_index.xml
├── registration.php
├── Block
├── Form.php
├── Info.php
└── Customer
│ └── CardRenderer.php
├── Gateway
├── Http
│ ├── Client
│ │ ├── RefundCreate.php
│ │ ├── PaymentIntentCreate.php
│ │ ├── PaymentIntentCapture.php
│ │ ├── PaymentIntentRetrieve.php
│ │ └── AbstractClient.php
│ └── TransferFactory.php
├── Validator
│ ├── ResponseValidator.php
│ └── GeneralResponseValidator.php
├── Helper
│ ├── AmountProvider.php
│ ├── TokenProvider.php
│ ├── PaymentIntentProvider.php
│ └── SubjectReader.php
├── Config
│ ├── CanVoidHandler.php
│ └── Config.php
├── Request
│ ├── RefundDataBuilder.php
│ ├── CaptureDataBuilder.php
│ └── RetrieveDataBuilder.php
├── Response
│ ├── CardDetailsHandler.php
│ ├── PaymentIntentIdHandler.php
│ ├── RefundHandler.php
│ ├── PaymentDetailsHandler.php
│ └── VaultDetailsHandler.php
└── Command
│ ├── CreatePaymentIntentCommand.php
│ └── CaptureStrategyCommand.php
├── etc
├── frontend
│ ├── routes.xml
│ └── di.xml
├── events.xml
├── module.xml
├── config.xml
└── adminhtml
│ └── system.xml
├── Test
└── Unit
│ ├── bootstrap.php
│ ├── Gateway
│ ├── Http
│ │ ├── TransferFactoryTest.php
│ │ └── Client
│ │ │ ├── RefundCreateTest.php
│ │ │ ├── PaymentIntentCreateTest.php
│ │ │ └── PaymentIntentCaptureTest.php
│ ├── Helper
│ │ ├── SubjectReaderTest.php
│ │ └── TokenProviderTest.php
│ ├── Response
│ │ ├── PaymentIntentIdHandleTest.php
│ │ ├── PaymentDetailsHandlerTest.php
│ │ ├── CardDetailsHandlerTest.php
│ │ ├── RefundHandlerTest.php
│ │ └── VaultDetailsHandlerTest.php
│ ├── Validator
│ │ ├── GeneralResponseValidatorTest.php
│ │ └── ResponseValidatorTest.php
│ ├── Config
│ │ ├── CanVoidHandlerTest.php
│ │ └── ConfigTest.php
│ └── Request
│ │ ├── CaptureDataBuilderTest.php
│ │ └── RefundDataBuilderTest.php
│ ├── Model
│ └── Ui
│ │ ├── ConfigProviderTest.php
│ │ └── TokenUiComponentProviderTest.php
│ ├── Observer
│ └── DataAssignObserverTest.php
│ └── Block
│ └── Customer
│ └── CardRendererTest.php
├── Model
├── Adminhtml
│ └── Source
│ │ ├── PaymentAction.php
│ │ └── FutureUsage.php
├── Ui
│ ├── ConfigProvider.php
│ └── TokenUiComponentProvider.php
└── Adapter
│ └── StripeAdapter.php
├── phpunit.xml.dist
├── Observer
└── DataAssignObserver.php
├── composer.json
├── Setup
├── Uninstall.php
└── UpgradeData.php
├── .travis.yml
├── README.md
├── Controller
└── PaymentIntent
│ └── Generate.php
└── LICENSE.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | /composer.lock
2 | /magento-coding-standard/
3 | /Test/Unit/logs/
4 | /vendor/
5 |
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | coverage_clover: Test/Unit/logs/clover.xml
2 | json_path: Test/Unit/logs/coveralls-upload.json
3 | service_name: travis-ci
4 |
--------------------------------------------------------------------------------
/view/frontend/web/css/source/_module.less:
--------------------------------------------------------------------------------
1 | #co-stripe-form {
2 | .stripe-error {
3 | color: #e02b27;
4 | font-weight: 600;
5 | }
6 | }
--------------------------------------------------------------------------------
/registration.php:
--------------------------------------------------------------------------------
1 | adapter->refundCreate($data);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Gateway/Http/Client/PaymentIntentCreate.php:
--------------------------------------------------------------------------------
1 | adapter->paymentIntentCreate($data);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/etc/frontend/routes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/etc/events.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Block/Info.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/view/frontend/layout/vault_cards_listaction.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Gateway/Http/Client/PaymentIntentCapture.php:
--------------------------------------------------------------------------------
1 | adapter->paymentIntentRetrieve($paymentIntentId);
18 | $paymentIntent->capture($data);
19 |
20 | return $paymentIntent;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Test/Unit/bootstrap.php:
--------------------------------------------------------------------------------
1 | AbstractMethod::ACTION_AUTHORIZE,
23 | 'label' => __('Authorize'),
24 | ],
25 | [
26 | 'value' => AbstractMethod::ACTION_AUTHORIZE_CAPTURE,
27 | 'label' => __('Authorize and Capture'),
28 | ]
29 | ];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Model/Adminhtml/Source/FutureUsage.php:
--------------------------------------------------------------------------------
1 | self::USAGE_ON_SESSION,
25 | 'label' => __('On Session'),
26 | ],
27 | [
28 | 'value' => self::USAGE_OFF_SESSION,
29 | 'label' => __('Off Session'),
30 | ]
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 | Test/Unit
9 |
10 |
11 |
12 | ./Block
13 | ./Gateway
14 | ./Helper
15 | ./Model
16 | ./Observer
17 | ./Setup
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Gateway/Validator/ResponseValidator.php:
--------------------------------------------------------------------------------
1 | status)
24 | && $response->status != self::STATUS_FAILED,
25 | [__('Wrong transaction status')]
26 | ];
27 | }
28 | ]
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Gateway/Http/TransferFactory.php:
--------------------------------------------------------------------------------
1 | transferBuilder = $transferBuilder;
24 | }
25 |
26 | /**
27 | * Builds gateway transfer object
28 | *
29 | * @param array $request
30 | * @return TransferInterface
31 | */
32 | public function create(array $request)
33 | {
34 | return $this->transferBuilder
35 | ->setBody($request)
36 | ->build();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Gateway/Http/Client/PaymentIntentRetrieve.php:
--------------------------------------------------------------------------------
1 | adapter->paymentIntentRetrieve($paymentIntentId);
16 |
17 | // Assign payment method to customer if needed
18 | if (!isset($data[RetrieveDataBuilder::CUSTOMER])) {
19 | return $paymentIntent;
20 | }
21 |
22 | // Skip if already attached
23 | $paymentMethod = $this->adapter->paymentMethodRetrieve($paymentIntent->payment_method);
24 | if (!is_null($paymentMethod->customer)) {
25 | return $paymentIntent;
26 | }
27 |
28 | $paymentMethod->attach([
29 | 'customer' => $data[RetrieveDataBuilder::CUSTOMER],
30 | ]);
31 |
32 | return $paymentIntent;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Gateway/Helper/AmountProvider.php:
--------------------------------------------------------------------------------
1 | zeroDecimal) ? 1 : 100;
45 |
46 | return (int)($multiplier * $amount);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Gateway/Config/CanVoidHandler.php:
--------------------------------------------------------------------------------
1 | subjectReader = $subjectReader;
24 | }
25 |
26 | /**
27 | * Retrieve method configured value
28 | *
29 | * @param array $subject
30 | * @param int|null $storeId
31 | *
32 | * @return mixed
33 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
34 | */
35 | public function handle(array $subject, $storeId = null)
36 | {
37 | $paymentDO = $this->subjectReader->readPayment($subject);
38 |
39 | $payment = $paymentDO->getPayment();
40 | return $payment instanceof Payment && !(bool)$payment->getAmountPaid();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Model/Ui/ConfigProvider.php:
--------------------------------------------------------------------------------
1 | config = $config;
25 | }
26 |
27 | /**
28 | * Retrieve checkout configuration
29 | *
30 | * @return array
31 | */
32 | public function getConfig()
33 | {
34 | return [
35 | 'payment' => [
36 | self::CODE => [
37 | 'isActive' => $this->config->isActive(),
38 | 'publishableKey' => $this->config->getPublishableKey(),
39 | 'sdkUrl' => $this->config->getSdkUrl(),
40 | 'ccVaultCode' => self::VAULT_CODE,
41 | 'paymentIntentUrl' => $this->config->getPaymentIntentUrl(),
42 | ],
43 | ]
44 | ];
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Observer/DataAssignObserver.php:
--------------------------------------------------------------------------------
1 | readDataArgument($observer);
26 |
27 | $additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA);
28 | if (!is_array($additionalData)) {
29 | return;
30 | }
31 |
32 | $paymentInfo = $this->readPaymentModelArgument($observer);
33 |
34 | foreach ($this->additionalInfo as $additionalInfoKey) {
35 | if (isset($additionalData[$additionalInfoKey])) {
36 | $paymentInfo->setAdditionalInformation(
37 | $additionalInfoKey,
38 | $additionalData[$additionalInfoKey]
39 | );
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aune-io/magento2-stripe",
3 | "description": "Stripe payments integration module for Magento 2",
4 | "repositories": [
5 | {
6 | "type": "composer",
7 | "url": "https://repo.magento.com/"
8 | }
9 | ],
10 | "require": {
11 | "php": "~7.1.0|~7.2.0|~7.3.0",
12 | "psr/log": "1.1.0",
13 | "magento/framework": "102.0.*",
14 | "magento/module-config": "101.1.*",
15 | "magento/module-directory": "100.3.*",
16 | "magento/module-payment": "100.3.*",
17 | "magento/module-checkout": "100.3.*",
18 | "magento/module-sales": "102.0.*",
19 | "magento/module-backend": "101.0.*",
20 | "magento/module-vault": "101.1.*",
21 | "magento/module-quote": "101.1.*",
22 | "magento/module-ui": "101.1.*",
23 | "stripe/stripe-php": "~7.14.2"
24 | },
25 | "require-dev": {
26 | "phpunit/phpunit": "~6.5.13",
27 | "squizlabs/php_codesniffer": "~3.3.1",
28 | "phpmd/phpmd": "@stable",
29 | "php-coveralls/php-coveralls": "~2.1.0"
30 | },
31 | "type": "magento2-module",
32 | "license": [
33 | "OSL-3.0"
34 | ],
35 | "autoload": {
36 | "files": [
37 | "registration.php"
38 | ],
39 | "psr-4": {
40 | "Aune\\Stripe\\": ""
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Setup/Uninstall.php:
--------------------------------------------------------------------------------
1 | eavSetupFactory = $eavSetupFactory;
28 | }
29 |
30 | /**
31 | * @inheritdoc
32 | */
33 | public function uninstall(SchemaSetupInterface $setup, ModuleContextInterface $context)
34 | {
35 | $setup->startSetup();
36 | $eavSetup = $this->eavSetupFactory->create();
37 |
38 | $attributes = [
39 | TokenProvider::ATTRIBUTE_CODE,
40 | ];
41 |
42 | foreach($attributes as $attribute) {
43 | $eavSetup->removeAttribute(
44 | \Magento\Customer\Model\Customer::ENTITY,
45 | $attribute
46 | );
47 | }
48 |
49 | $setup->endSetup();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/etc/frontend/di.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | - Aune\Stripe\Model\Ui\ConfigProvider
7 |
8 |
9 |
10 |
11 |
12 |
13 | - Aune\Stripe\Model\Ui\ConfigProvider::CODE
14 |
15 |
16 |
17 |
18 |
19 |
20 | - 1
21 |
22 |
23 |
24 |
25 |
26 |
27 | - Aune\Stripe\Model\Ui\TokenUiComponentProvider
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Http/TransferFactoryTest.php:
--------------------------------------------------------------------------------
1 | transferBuilder = $this->createMock(TransferBuilder::class);
29 | $this->transferMock = $this->getMockForAbstractClass(TransferInterface::class);
30 |
31 | $this->transferFactory = new TransferFactory(
32 | $this->transferBuilder
33 | );
34 | }
35 |
36 | public function testCreate()
37 | {
38 | $request = ['data1', 'data2'];
39 |
40 | $this->transferBuilder->expects($this->once())
41 | ->method('setBody')
42 | ->with($request)
43 | ->willReturnSelf();
44 |
45 | $this->transferBuilder->expects($this->once())
46 | ->method('build')
47 | ->willReturn($this->transferMock);
48 |
49 | $this->assertEquals($this->transferMock, $this->transferFactory->create($request));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Block/Customer/CardRenderer.php:
--------------------------------------------------------------------------------
1 | getPaymentMethodCode() === ConfigProvider::CODE;
22 | }
23 |
24 | /**
25 | * @return string
26 | */
27 | public function getNumberLast4Digits()
28 | {
29 | return $this->getTokenDetails()['maskedCC'];
30 | }
31 |
32 | /**
33 | * @return string
34 | */
35 | public function getExpDate()
36 | {
37 | return $this->getTokenDetails()['expirationDate'];
38 | }
39 |
40 | /**
41 | * @return string
42 | */
43 | public function getIconUrl()
44 | {
45 | return $this->getIconForType($this->getTokenDetails()['type'])['url'];
46 | }
47 |
48 | /**
49 | * @return int
50 | */
51 | public function getIconHeight()
52 | {
53 | return $this->getIconForType($this->getTokenDetails()['type'])['height'];
54 | }
55 |
56 | /**
57 | * @return int
58 | */
59 | public function getIconWidth()
60 | {
61 | return $this->getIconForType($this->getTokenDetails()['type'])['width'];
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Gateway/Request/RefundDataBuilder.php:
--------------------------------------------------------------------------------
1 | subjectReader = $subjectReader;
33 | $this->amountProvider = $amountProvider;
34 | }
35 |
36 | /**
37 | * @inheritdoc
38 | */
39 | public function build(array $buildSubject)
40 | {
41 | $paymentDO = $this->subjectReader->readPayment($buildSubject);
42 | $payment = $paymentDO->getPayment();
43 | $order = $paymentDO->getOrder();
44 |
45 | try {
46 |
47 | $currencyCode = $order->getCurrencyCode();
48 | $amount = $this->amountProvider->convert(
49 | $this->subjectReader->readAmount($buildSubject),
50 | $currencyCode
51 | );
52 |
53 | } catch (\InvalidArgumentException $e) {
54 | $amount = null;
55 | }
56 |
57 | return [
58 | self::PAYMENT_INTENT => $payment->getLastTransId(),
59 | self::AMOUNT => $amount,
60 | ];
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Helper/SubjectReaderTest.php:
--------------------------------------------------------------------------------
1 | subjectReader = new SubjectReader();
17 | }
18 |
19 | /**
20 | * @covers \Aune\Stripe\Gateway\Helper\SubjectReader::readCustomerId
21 | *
22 | * @expectedException InvalidArgumentException
23 | * @expectedExceptionMessage The "customerId" field does not exists
24 | */
25 | public function testReadCustomerIdWithException()
26 | {
27 | $this->subjectReader->readCustomerId([]);
28 | }
29 |
30 | /**
31 | * @covers \Aune\Stripe\Gateway\Helper\SubjectReader::readCustomerId
32 | */
33 | public function testReadCustomerId()
34 | {
35 | $customerId = 1;
36 | static::assertEquals($customerId, $this->subjectReader->readCustomerId(['customer_id' => $customerId]));
37 | }
38 |
39 | /**
40 | * @covers \Aune\Stripe\Gateway\Helper\SubjectReader::readPublicHash
41 | *
42 | * @expectedException \InvalidArgumentException
43 | * @expectedExceptionMessage The "public_hash" field does not exists
44 | */
45 | public function testReadPublicHashWithException()
46 | {
47 | $this->subjectReader->readPublicHash([]);
48 | }
49 |
50 | /**
51 | * @covers \Aune\Stripe\Gateway\Helper\SubjectReader::readPublicHash
52 | */
53 | public function testReadPublicHash()
54 | {
55 | $hash = sha1(rand());
56 | static::assertEquals($hash, $this->subjectReader->readPublicHash(['public_hash' => $hash]));
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Gateway/Request/CaptureDataBuilder.php:
--------------------------------------------------------------------------------
1 | subjectReader = $subjectReader;
34 | $this->amountProvider = $amountProvider;
35 | }
36 |
37 | /**
38 | * @inheritdoc
39 | */
40 | public function build(array $buildSubject)
41 | {
42 | $paymentDO = $this->subjectReader->readPayment($buildSubject);
43 | $payment = $paymentDO->getPayment();
44 | $order = $paymentDO->getOrder();
45 |
46 | try {
47 |
48 | $currencyCode = $order->getCurrencyCode();
49 | $amount = $this->amountProvider->convert(
50 | $this->subjectReader->readAmount($buildSubject),
51 | $currencyCode
52 | );
53 |
54 | } catch (\InvalidArgumentException $e) {
55 | $amount = null;
56 | }
57 |
58 | return [
59 | self::PAYMENT_INTENT => $payment->getAdditionalInformation(DataAssignObserver::PAYMENT_INTENT),
60 | self::AMOUNT_TO_CAPTURE => $amount,
61 | ];
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Model/Ui/TokenUiComponentProvider.php:
--------------------------------------------------------------------------------
1 | componentFactory = $componentFactory;
32 | $this->config = $config;
33 | }
34 |
35 | /**
36 | * Get UI component for token
37 | *
38 | * @param PaymentTokenInterface $paymentToken
39 | * @return TokenUiComponentInterface
40 | */
41 | public function getComponentForToken(PaymentTokenInterface $paymentToken)
42 | {
43 | $jsonDetails = json_decode($paymentToken->getTokenDetails() ?: '{}', true);
44 |
45 | $component = $this->componentFactory->create(
46 | [
47 | 'config' => [
48 | 'code' => ConfigProvider::VAULT_CODE,
49 | 'paymentIntentUrl' => $this->config->getPaymentIntentUrl(),
50 | TokenUiComponentProviderInterface::COMPONENT_DETAILS => $jsonDetails,
51 | TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH => $paymentToken->getPublicHash()
52 | ],
53 | 'name' => 'Aune_Stripe/js/view/payment/method-renderer/vault'
54 | ]
55 | );
56 |
57 | return $component;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Gateway/Validator/GeneralResponseValidator.php:
--------------------------------------------------------------------------------
1 | subjectReader = $subjectReader;
29 | }
30 |
31 | /**
32 | * @inheritdoc
33 | */
34 | public function validate(array $validationSubject)
35 | {
36 | /** @var Successful|Error $response */
37 | $response = $this->subjectReader->readResponseObject($validationSubject);
38 |
39 | $isValid = true;
40 | $errorMessages = [];
41 |
42 | foreach ($this->getResponseValidators() as $validator) {
43 | $validationResult = $validator($response);
44 |
45 | if (!$validationResult[0]) {
46 | $isValid = $validationResult[0];
47 | $errorMessages = array_merge($errorMessages, $validationResult[1]);
48 | }
49 | }
50 |
51 | return $this->createResult($isValid, $errorMessages);
52 | }
53 |
54 | /**
55 | * @return array
56 | */
57 | protected function getResponseValidators()
58 | {
59 | return [
60 | function ($response) {
61 | return [
62 | $response
63 | && ($response instanceof \Stripe\StripeObject),
64 | [__('Stripe error response')]
65 | ];
66 | }
67 | ];
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Gateway/Response/CardDetailsHandler.php:
--------------------------------------------------------------------------------
1 | config = $config;
44 | $this->subjectReader = $subjectReader;
45 | }
46 |
47 | /**
48 | * @inheritdoc
49 | */
50 | public function handle(array $handlingSubject, array $response)
51 | {
52 | $paymentDO = $this->subjectReader->readPayment($handlingSubject);
53 | $paymentIntent = $this->subjectReader->readPaymentIntent($response);
54 |
55 | $payment = $paymentDO->getPayment();
56 | ContextHelper::assertOrderPayment($payment);
57 |
58 | $charge = $paymentIntent->charges->data[0];
59 | $card = $charge->payment_method_details->card;
60 | $payment->setCcLast4($card->last4);
61 | $payment->setCcExpMonth($card->exp_month);
62 | $payment->setCcExpYear($card->exp_year);
63 | $payment->setCcType($card->brand);
64 |
65 | // set card details to additional info
66 | $payment->setAdditionalInformation(self::CARD_NUMBER, 'xxxx-' . $card->last4);
67 | $payment->setAdditionalInformation(OrderPaymentInterface::CC_TYPE, $card->brand);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Gateway/Helper/TokenProvider.php:
--------------------------------------------------------------------------------
1 | customerRepository = $customerRepository;
34 | $this->stripeAdapter = $stripeAdapter;
35 | }
36 |
37 | /**
38 | * Returns the Stripe customer id given the Magento customer id
39 | */
40 | public function getCustomerStripeId(int $magentoCustomerId)
41 | {
42 | try {
43 | $customer = $this->customerRepository->getById($magentoCustomerId);
44 | $attribute = $customer->getCustomAttribute(self::ATTRIBUTE_CODE);
45 | if ($attribute instanceof \Magento\Framework\Api\AttributeValue) {
46 | return $attribute->getValue();
47 | }
48 | } catch (NoSuchEntityException $ex) {
49 | return null;
50 | }
51 | }
52 |
53 | /**
54 | * Saves the Stripe customer id against a Magento customer
55 | */
56 | public function setCustomerStripeId(int $magentoCustomerId, string $stripeCustomerId)
57 | {
58 | try {
59 | $customer = $this->customerRepository->getById($magentoCustomerId);
60 | $customer->setCustomAttribute(self::ATTRIBUTE_CODE, $stripeCustomerId);
61 | return $this->customerRepository->save($customer);
62 | } catch (NoSuchEntityException $ex) {
63 | return false;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Gateway/Http/Client/AbstractClient.php:
--------------------------------------------------------------------------------
1 | logger = $logger;
42 | $this->customLogger = $customLogger;
43 | $this->adapter = $adapter;
44 | }
45 |
46 | /**
47 | * @inheritdoc
48 | */
49 | public function placeRequest(TransferInterface $transferObject)
50 | {
51 | $data = $transferObject->getBody();
52 | $response = [
53 | 'object' => [],
54 | ];
55 |
56 | $log = [
57 | 'request' => $data,
58 | 'client' => static::class
59 | ];
60 |
61 | try {
62 | $response['object'] = $this->process($data);
63 | } catch (\Exception $e) {
64 | $message = __($e->getMessage() ?: 'Sorry, but something went wrong');
65 | $this->logger->critical($message);
66 | throw new ClientException($message);
67 | } finally {
68 | $log['response'] = (array) $response['object'];
69 | $this->customLogger->debug($log);
70 | }
71 |
72 | return $response;
73 | }
74 |
75 | /**
76 | * Process http request
77 | * @param array $data
78 | * @return \Stripe\ApiResource|\Stripe\Error\Base
79 | */
80 | abstract protected function process(array $data);
81 | }
82 |
--------------------------------------------------------------------------------
/view/frontend/web/template/payment/vault.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/Setup/UpgradeData.php:
--------------------------------------------------------------------------------
1 | customerSetupFactory = $customerSetupFactory;
31 | }
32 |
33 | /**
34 | * @inheritdoc
35 | */
36 | public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
37 | {
38 | $setup->startSetup();
39 |
40 | if(version_compare($context->getVersion(), '1.1.0', '<')) {
41 | $this->addStripeCustomerIdAttribute($setup);
42 | }
43 |
44 | $setup->endSetup();
45 | }
46 |
47 | /**
48 | * Add Stripe ID attribute to customer
49 | */
50 | private function addStripeCustomerIdAttribute(ModuleDataSetupInterface $setup)
51 | {
52 | $customerSetup = $this->customerSetupFactory->create(['setup' => $setup]);
53 |
54 | $customerSetup->addAttribute(
55 | \Magento\Customer\Model\Customer::ENTITY,
56 | TokenProvider::ATTRIBUTE_CODE,
57 | [
58 | 'type' => 'varchar',
59 | 'label' => 'Stripe Customer ID',
60 | 'input' => 'text',
61 | 'source' => '',
62 | 'required' => false,
63 | 'default' => '',
64 | 'system' => false,
65 | 'position' => 120,
66 | 'sort_order' => 120,
67 | 'adminhtml_only' => 1,
68 | ]
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: php
4 |
5 | php:
6 | - 7.1
7 | - 7.2
8 | - 7.3
9 |
10 | install:
11 | - mkdir -p ~/.composer/
12 | - echo "{\"http-basic\":{\"repo.magento.com\":{\"username\":\"${MAGENTO_USERNAME}\",\"password\":\"${MAGENTO_PASSWORD}\"}}}" > ~/.composer/auth.json
13 | - composer install --prefer-dist
14 | - composer create-project magento/magento-coding-standard magento-coding-standard
15 |
16 | script:
17 | - php magento-coding-standard/vendor/bin/phpcs Block/ Gateway/ Model/ Observer/ Setup/ Test/ view/ --standard=Magento2 --severity=10
18 | - php vendor/bin/phpmd Block/,Gateway/,Model/,Observer/,Setup/,view/ text cleancode,codesize,controversial,design,naming,unusedcode --ignore-violations-on-exit
19 | - php vendor/phpunit/phpunit/phpunit --coverage-clover Test/Unit/logs/clover.xml Test
20 |
21 | after_script:
22 | - php vendor/bin/coveralls -v
23 |
24 | env:
25 | global:
26 | - secure: "dSQkHA6ybCyS4HyJsghfDtw1bpVlmNxC/URuBkDCJOp+CmrHUXyoo5rDaWWV0ISvvrzh60g8zb0c/ToaOZyDVWUYYBLdv0tZSHsVlILEHAHqRgzu8B2F9FMjYqs5J/vL7Xh7oigTn1E5EH0zJEhnW0y8LuyyenXsKKikBtCkemMiEDOxu2RBNaCtfD6zSmc/Hbcwo0FI4noNOikfEkrAM4BICx2YK8++AIvtmTqOD1qhwqgUMGisEC/QfRN2RuCqoFWGdjXNITug5Cwy3F+kgjeXnotLdXfcyDx9CG60msZHfZx1DR6iSOOxu6UZH3GB7s7OOINSUODJCcvBDQes597TNHPJz/hI2cu732UcWspboDLN1Ucr5D1fM+rv9Oxbbm3eFiAPl3X+TMiyI+lCl+uxtdFRnjUN3WIiEhMXDvAFl5BA/XnWEJ51zuvgbEAcoI5UFde4erzq7Q0GhZeSTiRV6nDW9aoMpcKxch9wJqX/1TlM6pR0yqvHOu5/sXRcoBsla+MWViqdxB43rzhEsXMV655pD1ksU4sHWMwy5GT0mzsjcBeakUxAtYE7FonLH3sOocVbpi+Bp0kbhAPO1H4hTeuN0VLthxmdZiocwbtGgd6+gO6VYoEzdEb/nK1eUHVJBev0Tl5nqJDQBGqYCkvohuzm2b4Zg9z3ge8HJdk="
27 | - secure: "G4gd01rxA6TvzpsmDncI1G7jZgVktcn83sgTlo2MZUt5qqtcdWb19vJYXQH2p1/jea3IG9+prFH2WAJEiuBiLshYB/4J3m9QPRZJv83xN/eGFXa6YOT7qMMYnPa9M80PAGr9VsHovI1WmAq9KEqXLs3w78vnmGfItd+JV6rHZ+kLXVcBUgxIAwkYL23aLUln5Rs+l4OClWEeLLw4t2fLNxhpHk8qRD8ToJVyvvJ7Ko2x1bAajbu91ZrptPZ5XnIWMeXbn4jx91kB0prbG1dzQr+1jHyRIXm7ljueZp02vKWkdBgtxPENgAQ8qiaz8RdPJF/GuhFARZwB7X9yyEuXtSCDvIXLA4BM4amZpIzMWXfKGrmOBlf4bj+zZpUo6sGysFIMQQfpuRwthmrYyB2JS3j2Ol84xX9i65WjXhvAqVJnqrTXLW4LOCSfMBNWhHUV/SCcjFZ3/hJVKZvXXS1JsD0dHTzdtIeudiTo7KG3V7jUhxUnqt2tsIPREePYPLlDMUf6bg0KAvV3XLKQyfh+dDeAqzoOthDDm7XycD2zNZ5bq2j9iKaYXySdpdXZrM2X6HpdNI3xunDzLNOq2Z2Hj70J4bN/uo4LfhrL85MD9DN7hPWKWAMmg2ury/PAQDiJnr1t7kIBfUTqBksggyEj/RSyq5E5MkeG+kZHclLVQPI="
28 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Response/PaymentIntentIdHandleTest.php:
--------------------------------------------------------------------------------
1 | getMockForAbstractClass(PaymentDataObjectInterface::class);
19 | $paymentInfo = $this->getMockBuilder(Payment::class)
20 | ->disableOriginalConstructor()
21 | ->getMock();
22 |
23 | $handlingSubject = [
24 | 'payment' => $paymentDO
25 | ];
26 |
27 | $paymentIntent = \Stripe\Util\Util::convertToStripeObject([
28 | 'object' => 'payment_intent',
29 | 'id' => 1,
30 | ], []);
31 |
32 | $response = [
33 | 'object' => $paymentIntent,
34 | ];
35 |
36 | $subjectReader = $this->getMockBuilder(SubjectReader::class)
37 | ->disableOriginalConstructor()
38 | ->getMock();
39 |
40 | $subjectReader->expects(static::once())
41 | ->method('readPayment')
42 | ->with($handlingSubject)
43 | ->willReturn($paymentDO);
44 | $paymentDO->expects(static::atLeastOnce())
45 | ->method('getPayment')
46 | ->willReturn($paymentInfo);
47 | $subjectReader->expects(static::once())
48 | ->method('readPaymentIntent')
49 | ->with($response)
50 | ->willReturn($paymentIntent);
51 |
52 | $paymentInfo->expects(static::once())
53 | ->method('setTransactionId')
54 | ->with(1);
55 |
56 | $paymentInfo->expects(static::once())
57 | ->method('setIsTransactionClosed')
58 | ->with(false);
59 | $paymentInfo->expects(static::once())
60 | ->method('setShouldCloseParentTransaction')
61 | ->with(false);
62 |
63 | $handler = new PaymentIntentIdHandler($subjectReader);
64 | $handler->handle($handlingSubject, $response);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/etc/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | AuneStripeFacade
7 | Credit Card (Stripe)
8 | authorize
9 | 0
10 | 1
11 | 1
12 | 1
13 | 1
14 | 1
15 | 1
16 | 1
17 | 1
18 | 1
19 | 1
20 | 1
21 | 1
22 | 1
23 | 1
24 | 1
25 | processing
26 |
27 |
28 | source,outcome_seller_message,failure_code,failure_message,outcome_type,outcome_network_status,outcome_risk_level,cc_type,cc_number
29 | source,outcome_seller_message,failure_code,failure_message,outcome_type,outcome_network_status,outcome_risk_level
30 |
31 |
32 | AuneStripeVaultFacade
33 | Stored Cards (Stripe)
34 | source,outcome_seller_message,failure_code,failure_message,outcome_type,outcome_network_status,outcome_risk_level,cc_type,cc_number
35 | source,outcome_seller_message,failure_code,failure_message,outcome_type,outcome_network_status,outcome_risk_level
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/Gateway/Response/PaymentIntentIdHandler.php:
--------------------------------------------------------------------------------
1 | subjectReader = $subjectReader;
23 | }
24 |
25 | /**
26 | * @param array $handlingSubject
27 | * @param array $response
28 | * @return void
29 | */
30 | public function handle(array $handlingSubject, array $response)
31 | {
32 | $paymentDO = $this->subjectReader->readPayment($handlingSubject);
33 |
34 | if ($paymentDO->getPayment() instanceof Payment) {
35 | /** @var \Stripe\PaymentIntent $paymentIntent */
36 | $paymentIntent = $this->subjectReader->readPaymentIntent($response);
37 |
38 | /** @var Payment $orderPayment */
39 | $orderPayment = $paymentDO->getPayment();
40 | $this->setPaymentIntentId(
41 | $orderPayment,
42 | $paymentIntent
43 | );
44 |
45 | $orderPayment->setIsTransactionClosed($this->shouldCloseTransaction());
46 | $orderPayment->setShouldCloseParentTransaction(
47 | $this->shouldCloseParentTransaction($orderPayment)
48 | );
49 | }
50 | }
51 |
52 | /**
53 | * @param Payment $orderPayment
54 | * @param \Stripe\PaymentIntent $paymentIntent
55 | * @return void
56 | */
57 | protected function setPaymentIntentId(
58 | Payment $orderPayment,
59 | \Stripe\PaymentIntent $paymentIntent
60 | ) {
61 | $orderPayment->setTransactionId($paymentIntent->id);
62 | }
63 |
64 | /**
65 | * Whether transaction should be closed
66 | *
67 | * @return bool
68 | */
69 | protected function shouldCloseTransaction()
70 | {
71 | return false;
72 | }
73 |
74 | /**
75 | * Whether parent transaction should be closed
76 | *
77 | * @param Payment $orderPayment
78 | * @return bool
79 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
80 | */
81 | protected function shouldCloseParentTransaction(Payment $orderPayment)
82 | {
83 | return false;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Gateway/Response/RefundHandler.php:
--------------------------------------------------------------------------------
1 | subjectReader = $subjectReader;
23 | }
24 |
25 | /**
26 | * @param array $handlingSubject
27 | * @param array $response
28 | * @return void
29 | */
30 | public function handle(array $handlingSubject, array $response)
31 | {
32 | $paymentDO = $this->subjectReader->readPayment($handlingSubject);
33 |
34 | /** @var Payment $orderPayment */
35 | $orderPayment = $paymentDO->getPayment();
36 |
37 | if (!($orderPayment instanceof Payment)) {
38 | return;
39 | }
40 |
41 | /** @var \Stripe\Refund $refund */
42 | $refund = $this->subjectReader->readRefund($response);
43 |
44 | $this->setRefundId(
45 | $orderPayment,
46 | $refund
47 | );
48 |
49 | $orderPayment->setIsTransactionClosed($this->shouldCloseTransaction());
50 | $orderPayment->setShouldCloseParentTransaction(
51 | $this->shouldCloseParentTransaction($orderPayment)
52 | );
53 | }
54 |
55 | /**
56 | * @param Payment $orderPayment
57 | * @param \Stripe\Refund $refund
58 | * @return void
59 | */
60 | protected function setRefundId(
61 | Payment $orderPayment,
62 | \Stripe\Refund $refund
63 | ) {
64 | $orderPayment->setTransactionId($refund->id);
65 | }
66 |
67 | /**
68 | * Whether transaction should be closed
69 | *
70 | * @return bool
71 | */
72 | protected function shouldCloseTransaction()
73 | {
74 | return true;
75 | }
76 |
77 | /**
78 | * Whether parent transaction should be closed
79 | *
80 | * @param Payment $orderPayment
81 | * @return bool
82 | */
83 | protected function shouldCloseParentTransaction(Payment $orderPayment)
84 | {
85 | $creditmemo = $orderPayment->getCreditmemo();
86 | if (!$creditmemo) {
87 | return true;
88 | }
89 |
90 | $invoice = $creditmemo->getInvoice();
91 | if (!$invoice) {
92 | return true;
93 | }
94 |
95 | return !(bool)$invoice->canRefund();
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Magento 2 Stripe Payments
2 | Stripe payments integration module for Magento 2.
3 |
4 | [](https://travis-ci.org/aune-io/magento2-stripe)
5 | [](https://coveralls.io/github/aune-io/magento2-stripe?branch=master)
6 | [](https://packagist.org/packages/aune-io/magento2-stripe)
7 | [](https://packagist.org/packages/aune-io/magento2-stripe)
8 | [](https://packagist.org/packages/aune-io/magento2-stripe)
9 | [](https://packagist.org/packages/aune-io/magento2-stripe)
10 |
11 | ## System requirements
12 | This extension supports the following versions of Magento:
13 |
14 | * Community Edition (CE) versions 2.1.x, 2.2.x and 2.3.x
15 | * Enterprise Edition (EE) versions 2.1.x, 2.2.x and 2.3.x
16 |
17 | ## Installation
18 | 1. Require the module via Composer
19 | ```bash
20 | $ composer require aune-io/magento2-stripe
21 | ```
22 |
23 | 2. Enable the module
24 | ```bash
25 | $ bin/magento module:enable Aune_Stripe
26 | $ bin/magento setup:upgrade
27 | ```
28 |
29 | 3. Login to the admin
30 | 4. Go to Stores > Configuration > Sales > Payment Methods > Aune - Stripe
31 | 5. Enter your Stripe API Keys and set the payment method as active
32 | 6. (Optional) Enable customer storing in Stripe or Vault to allow customers to reuse their payment methods
33 |
34 | ## SCA
35 | Version 4 of the extension supports SCA with the following warnings:
36 | * vaulting it's not backward compatibile: previous versions of the extension used the source id as gateway token, version 4 uses the payment method id. If you are upgrading, you can either empty the vault or write a script to change the gateway token.
37 | * partial capture will refund the remaining amount: this is due to how capturing a payment intent works, see the official documentation [here](https://stripe.com/docs/api/payment_intents/capture#capture_payment_intent-amount_to_capture).
38 | * payment intent confermation is done on the frontend: it is strongly reccommended to use _Authorize_ as _Payment Action_ and capture payments when generating the Magento invoice, to avoid having captured transaction without orders in case of timeouts or server errors.
39 |
40 | ## Authors, contributors and maintainers
41 |
42 | Author:
43 | - [Renato Cason](https://github.com/renatocason)
44 |
45 | ## License
46 | Licensed under the Open Software License version 3.0
47 |
--------------------------------------------------------------------------------
/Test/Unit/Model/Ui/ConfigProviderTest.php:
--------------------------------------------------------------------------------
1 | config = $this->getMockBuilder(Config::class)
28 | ->disableOriginalConstructor()
29 | ->getMock();
30 |
31 | $this->configProvider = new ConfigProvider(
32 | $this->config
33 | );
34 | }
35 |
36 | /**
37 | * Run test getConfig method
38 | *
39 | * @covers \Aune\Stripe\Model\Ui\ConfigProvider::getConfig
40 | *
41 | * @dataProvider getConfigDataProvider
42 | *
43 | * @param array $config
44 | * @param array $expected
45 | */
46 | public function testGetConfig($config, $expected)
47 | {
48 | foreach ($config as $method => $value) {
49 | $this->config->expects(static::once())
50 | ->method($method)
51 | ->willReturn($value);
52 | }
53 |
54 | static::assertEquals($expected, $this->configProvider->getConfig());
55 | }
56 |
57 | /**
58 | * @return array
59 | */
60 | public function getConfigDataProvider()
61 | {
62 | $isActive = true;
63 | $publishableKey = 'publishable-key';
64 |
65 | return [
66 | [
67 | 'config' => [
68 | 'isActive' => $isActive,
69 | 'getPublishableKey' => $publishableKey,
70 | 'getSdkUrl' => self::SDK_URL,
71 | 'getPaymentIntentUrl' => self::PAYMENT_INTENT_URL,
72 | ],
73 | 'expected' => [
74 | 'payment' => [
75 | ConfigProvider::CODE => [
76 | 'isActive' => $isActive,
77 | 'publishableKey' => $publishableKey,
78 | 'sdkUrl' => self::SDK_URL,
79 | 'ccVaultCode' => ConfigProvider::VAULT_CODE,
80 | 'paymentIntentUrl' => self::PAYMENT_INTENT_URL,
81 | ],
82 | ]
83 | ]
84 | ]
85 | ];
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Gateway/Helper/PaymentIntentProvider.php:
--------------------------------------------------------------------------------
1 | config = $config;
43 | $this->amountProvider = $amountProvider;
44 | $this->stripeAdapter = $stripeAdapter;
45 | }
46 |
47 | /**
48 | * Return a Payment Intent for the quote, taking care of creating or updating it
49 | */
50 | public function getPaymentIntent(Quote $quote, $publicHash = null)
51 | {
52 | $currency = $quote->getBaseCurrencyCode();
53 | $amount = $quote->getBaseGrandTotal();
54 |
55 | // Create new Stripe Payment Intent
56 | //@todo: rewrite as command to re-use for vaulted payment methods
57 | $params = [
58 | 'currency' => $currency,
59 | 'amount' => $this->amountProvider->convert($amount, $currency),
60 | 'capture_method' => $this->getCaptureMethod(),
61 | ];
62 |
63 | if (!is_null($publicHash)) {
64 | // Use vaulted payment method
65 | $params['customer'] = 'cus_GUGes1omIq5nUx';
66 | $params['payment_method'] = 'pm_1FxJq4D7OORb2ZnPbYSMMp7R';
67 | } elseif ($this->config->isStoreCustomerEnabled()) {
68 | // Vault for future usage if not already vaulted, and if enabled
69 | $params['setup_future_usage'] = $this->config->getStoreFutureUsage();
70 | }
71 |
72 | return $this->stripeAdapter->paymentIntentCreate($params);
73 | }
74 |
75 | /**
76 | * Return the capture method, based on configuration
77 | */
78 | private function getCaptureMethod()
79 | {
80 | $paymentAction = $this->config->getPaymentAction();
81 |
82 | return $paymentAction === AbstractMethod::ACTION_AUTHORIZE ?
83 | self::CAPTURE_METHOD_MANUAL : self::CAPTURE_METHOD_AUTOMATIC;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Gateway/Response/PaymentDetailsHandler.php:
--------------------------------------------------------------------------------
1 | [
29 | self::OUTCOME_TYPE,
30 | self::OUTCOME_NETWORK_STATUS,
31 | self::OUTCOME_REASON,
32 | self::OUTCOME_SELLER_MESSAGE,
33 | self::OUTCOME_RISK_LEVEL,
34 | ],
35 | ];
36 |
37 | /**
38 | * @var SubjectReader
39 | */
40 | private $subjectReader;
41 |
42 | /**
43 | * @param SubjectReader $subjectReader
44 | */
45 | public function __construct(SubjectReader $subjectReader)
46 | {
47 | $this->subjectReader = $subjectReader;
48 | }
49 |
50 | /**
51 | * @inheritdoc
52 | */
53 | public function handle(array $handlingSubject, array $response)
54 | {
55 | $paymentDO = $this->subjectReader->readPayment($handlingSubject);
56 |
57 | /** @var \Stripe\PaymentIntent $paymentIntent */
58 | $paymentIntent = $this->subjectReader->readPaymentIntent($response);
59 | $charge = $paymentIntent->charges->data[0];
60 |
61 | /** @var OrderPaymentInterface $payment */
62 | $payment = $paymentDO->getPayment();
63 |
64 | foreach ($this->additionalInfoMap as $key => $value) {
65 | if (is_array($value)) {
66 | foreach ($value as $item) {
67 | // Skip empty values
68 | if (!isset($charge->$key->$item)) {
69 | continue;
70 | }
71 |
72 | // Copy over nested element
73 | $payment->setAdditionalInformation(
74 | $key . '_' . $item,
75 | $charge->$key->$item
76 | );
77 | }
78 | } elseif (isset($charge->$value)) {
79 | // Copy over element on base level
80 | $payment->setAdditionalInformation($value, $charge->$value);
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/view/frontend/layout/checkout_index_index.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | -
8 |
-
9 |
-
10 |
-
11 |
-
12 |
-
13 |
- uiComponent
14 | -
15 |
-
16 |
-
17 |
-
18 |
19 |
-
20 |
-
21 |
- Aune_Stripe/js/view/payment/stripe
22 | -
23 |
-
24 |
- true
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Controller/PaymentIntent/Generate.php:
--------------------------------------------------------------------------------
1 | logger = $logger;
54 | $this->customerSession = $customerSession;
55 | $this->checkoutSession = $checkoutSession;
56 | $this->command = $command;
57 | }
58 |
59 | /**
60 | * @inheritdoc
61 | */
62 | public function execute()
63 | {
64 | $response = $this->resultFactory->create(ResultFactory::TYPE_JSON);
65 |
66 | try {
67 | $publicHash = $this->getRequest()->getParam('public_hash');
68 |
69 | $paymentIntent = $this->command->execute([
70 | 'quote' => $this->checkoutSession->getQuote(),
71 | 'customer_id' => $this->customerSession->getCustomerId(),
72 | 'public_hash' => $publicHash,
73 | ]);
74 |
75 | $response->setData(['paymentIntent' => [
76 | 'id' => $paymentIntent->id,
77 | 'clientSecret' => $paymentIntent->client_secret,
78 | ]]);
79 |
80 | } catch (\Exception $e) {
81 | $this->logger->critical($e);
82 | return $this->processBadRequest($response);
83 | }
84 |
85 | return $response;
86 | }
87 |
88 | /**
89 | * Return response for bad request
90 | * @param ResultInterface $response
91 | * @return ResultInterface
92 | */
93 | private function processBadRequest(ResultInterface $response)
94 | {
95 | $response->setHttpResponseCode(Exception::HTTP_BAD_REQUEST);
96 | $response->setData(['message' => __('Sorry, but something went wrong')]);
97 |
98 | return $response;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Test/Unit/Observer/DataAssignObserverTest.php:
--------------------------------------------------------------------------------
1 | observerContainer = $this->getMockBuilder(Event\Observer::class)
34 | ->disableOriginalConstructor()
35 | ->getMock();
36 |
37 | $this->event = $this->getMockBuilder(Event::class)
38 | ->disableOriginalConstructor()
39 | ->getMock();
40 |
41 | $this->paymentInfoModel = $this->getMockForAbstractClass(InfoInterface::class);
42 | }
43 |
44 | /**
45 | * @covers \Aune\Stripe\Observer\DataAssignObserver::execute
46 | */
47 | public function testExecute()
48 | {
49 | $dataObject = new DataObject(
50 | [
51 | PaymentInterface::KEY_ADDITIONAL_DATA => [
52 | 'payment_intent' => self::PAYMENT_INTENT_ID,
53 | ]
54 | ]
55 | );
56 |
57 | $this->observerContainer->expects(static::atLeastOnce())
58 | ->method('getEvent')
59 | ->willReturn($this->event);
60 |
61 | $this->event->expects(static::exactly(2))
62 | ->method('getDataByKey')
63 | ->willReturnMap(
64 | [
65 | [AbstractDataAssignObserver::MODEL_CODE, $this->paymentInfoModel],
66 | [AbstractDataAssignObserver::DATA_CODE, $dataObject]
67 | ]
68 | );
69 |
70 | $this->paymentInfoModel->expects(static::at(0))
71 | ->method('setAdditionalInformation')
72 | ->with('payment_intent', self::PAYMENT_INTENT_ID);
73 |
74 | $observer = new DataAssignObserver();
75 | $observer->execute($this->observerContainer);
76 | }
77 |
78 | /**
79 | * @covers \Aune\Stripe\Observer\DataAssignObserver::execute
80 | */
81 | public function testExecuteNoAdditionalData()
82 | {
83 | $dataObject = new DataObject(
84 | [
85 | PaymentInterface::KEY_ADDITIONAL_DATA => false
86 | ]
87 | );
88 |
89 | $this->observerContainer->expects(static::atLeastOnce())
90 | ->method('getEvent')
91 | ->willReturn($this->event);
92 |
93 | $this->event->expects(static::once())
94 | ->method('getDataByKey')
95 | ->willReturnMap(
96 | [
97 | [AbstractDataAssignObserver::DATA_CODE, $dataObject]
98 | ]
99 | );
100 |
101 | $observer = new DataAssignObserver();
102 | $observer->execute($this->observerContainer);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Test/Unit/Model/Ui/TokenUiComponentProviderTest.php:
--------------------------------------------------------------------------------
1 | componentFactory = $this->getMockBuilder(TokenUiComponentInterfaceFactory::class)
46 | ->disableOriginalConstructor()
47 | ->setMethods(['create'])
48 | ->getMock();
49 |
50 | $this->config = $this->getMockBuilder(Config::class)
51 | ->disableOriginalConstructor()
52 | ->getMock();
53 |
54 | $this->tokenComponent = $this->getMockForAbstractClass(TokenUiComponentInterface::class);
55 |
56 | $this->paymentToken = $this->getMockForAbstractClass(PaymentTokenInterface::class);
57 |
58 | $this->componentProvider = new TokenUiComponentProvider(
59 | $this->componentFactory,
60 | $this->config
61 | );
62 | }
63 |
64 | /**
65 | * @covers \Aune\Stripe\Model\Ui\TokenUiComponentProvider::getComponentForToken
66 | */
67 | public function testGetComponentForToken()
68 | {
69 | $tokenDetails = [
70 | 'maskedCC' => '1111',
71 | 'expirationDate' => '10/26',
72 | 'type' => 'VI',
73 | ];
74 |
75 | $hash = rand();
76 | $paymentIntentUrl = 'url';
77 |
78 | $params = [
79 | 'config' => [
80 | 'code' => ConfigProvider::VAULT_CODE,
81 | 'paymentIntentUrl' => $paymentIntentUrl,
82 | TokenUiComponentProviderInterface::COMPONENT_DETAILS => $tokenDetails,
83 | TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH => $hash
84 | ],
85 | 'name' => 'Aune_Stripe/js/view/payment/method-renderer/vault'
86 | ];
87 |
88 | $this->paymentToken->expects(static::once())
89 | ->method('getTokenDetails')
90 | ->willReturn(json_encode($tokenDetails));
91 |
92 | $this->componentFactory->expects(static::once())
93 | ->method('create')
94 | ->with($params)
95 | ->willReturn($this->tokenComponent);
96 |
97 | $this->paymentToken->expects(static::once())
98 | ->method('getPublicHash')
99 | ->willReturn($hash);
100 |
101 | $this->config->expects(static::once())
102 | ->method('getPaymentIntentUrl')
103 | ->willReturn($paymentIntentUrl);
104 |
105 | $actual = $this->componentProvider->getComponentForToken($this->paymentToken);
106 |
107 | static::assertEquals($this->tokenComponent, $actual);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/Gateway/Helper/SubjectReader.php:
--------------------------------------------------------------------------------
1 | encryptor = $encryptor;
44 | $this->urlHelper = $urlHelper;
45 | }
46 |
47 | /**
48 | * Get Payment configuration status
49 | *
50 | * @return bool
51 | */
52 | public function isActive()
53 | {
54 | return (bool) $this->getValue(self::KEY_ACTIVE);
55 | }
56 |
57 | /**
58 | * Get payment action
59 | *
60 | * @return string
61 | */
62 | public function getPaymentAction()
63 | {
64 | return $this->getValue(self::KEY_PAYMENT_ACTION);
65 | }
66 |
67 | /**
68 | * Get publishable key
69 | *
70 | * @return string
71 | */
72 | public function getPublishableKey()
73 | {
74 | return $this->getValue(self::KEY_PUBLISHABLE_KEY);
75 | }
76 |
77 | /**
78 | * Get secret key
79 | *
80 | * @return string
81 | */
82 | public function getSecretKey()
83 | {
84 | $value = $this->getValue(self::KEY_SECRET_KEY);
85 | return $value ? $this->encryptor->decrypt($value) : $value;
86 | }
87 |
88 | /**
89 | * Get sdk url
90 | *
91 | * @return string
92 | */
93 | public function getSdkUrl()
94 | {
95 | return $this->getValue(self::KEY_SDK_URL);
96 | }
97 |
98 | /**
99 | * Get payment intent generation url
100 | *
101 | * @return string
102 | */
103 | public function getPaymentIntentUrl()
104 | {
105 | return $this->urlHelper->getUrl(self::PAYMENT_INTENT_PATH);
106 | }
107 |
108 | /**
109 | * Return wether the customer should be stored in Stripe or not
110 | *
111 | * @return bool
112 | */
113 | public function isStoreCustomerEnabled()
114 | {
115 | return (bool) $this->getValue(self::KEY_STORE_CUSTOMER);
116 | }
117 |
118 | /**
119 | * Return the configured future usage setting
120 | *
121 | * @return string
122 | */
123 | public function getStoreFutureUsage()
124 | {
125 | return $this->getValue(self::KEY_STORE_FUTURE_USAGE);
126 | }
127 |
128 | /**
129 | * Retrieve mapper between Magento and Stripe card types
130 | *
131 | * @return array
132 | */
133 | public function getCcTypesMapper()
134 | {
135 | $result = json_decode(
136 | $this->getValue(self::KEY_CC_TYPES_STRIPE_MAPPER),
137 | true
138 | );
139 |
140 | return is_array($result) ? $result : [];
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php:
--------------------------------------------------------------------------------
1 | resultInterfaceFactoryMock = $this->getMockBuilder(
40 | \Magento\Payment\Gateway\Validator\ResultInterfaceFactory::class
41 | )->disableOriginalConstructor()
42 | ->setMethods(['create'])
43 | ->getMock();
44 | $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class)
45 | ->disableOriginalConstructor()
46 | ->getMock();
47 |
48 | $this->responseValidator = new GeneralResponseValidator(
49 | $this->resultInterfaceFactoryMock,
50 | $this->subjectReaderMock
51 | );
52 | }
53 |
54 | /**
55 | * Run test for validate method
56 | *
57 | * @param array $validationSubject
58 | * @param bool $isValid
59 | * @param Phrase[] $messages
60 | * @return void
61 | *
62 | * @dataProvider dataProviderTestValidate
63 | */
64 | public function testValidate(array $validationSubject, $isValid, $messages)
65 | {
66 | /** @var ResultInterface|\PHPUnit_Framework_MockObject_MockObject $resultMock */
67 | $resultMock = $this->getMockForAbstractClass(ResultInterface::class);
68 |
69 | $this->subjectReaderMock->expects(self::once())
70 | ->method('readResponseObject')
71 | ->with($validationSubject)
72 | ->willReturn($validationSubject['response']['object']);
73 |
74 | $this->resultInterfaceFactoryMock->expects(self::once())
75 | ->method('create')
76 | ->with([
77 | 'isValid' => $isValid,
78 | 'failsDescription' => $messages,
79 | 'errorCodes' => [],
80 | ])
81 | ->willReturn($resultMock);
82 |
83 | $actualMock = $this->responseValidator->validate($validationSubject);
84 |
85 | self::assertEquals($resultMock, $actualMock);
86 | }
87 |
88 | /**
89 | * @return array
90 | */
91 | public function dataProviderTestValidate()
92 | {
93 | $successTrue = \Stripe\Util\Util::convertToStripeObject([
94 | 'object' => 'payment_intent',
95 | ], []);
96 |
97 | $errorResult = new \Stripe\Exception\AuthenticationException('');
98 |
99 | return [
100 | [
101 | 'validationSubject' => [
102 | 'response' => [
103 | 'object' => $successTrue
104 | ],
105 | ],
106 | 'isValid' => true,
107 | []
108 | ],
109 | [
110 | 'validationSubject' => [
111 | 'response' => [
112 | 'object' => null,
113 | ]
114 | ],
115 | 'isValid' => false,
116 | [
117 | __('Stripe error response')
118 | ]
119 | ]
120 | ];
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Config/CanVoidHandlerTest.php:
--------------------------------------------------------------------------------
1 | subjectReaderMock = $this->getMockBuilder(SubjectReader::class)
35 | ->disableOriginalConstructor()
36 | ->getMock();
37 | $this->paymentDOMock = $this->getMockForAbstractClass(PaymentDataObjectInterface::class);
38 | $this->paymentMock = $this->getMockBuilder(Payment::class)
39 | ->disableOriginalConstructor()
40 | ->getMock();
41 |
42 | $this->canVoidHandler = new CanVoidHandler(
43 | $this->subjectReaderMock
44 | );
45 | }
46 |
47 | /**
48 | * @covers \Aune\Stripe\Gateway\Config\CanVoidHandler::handle
49 | */
50 | public function testHandleWithoutPayment()
51 | {
52 | $subject = [
53 | 'payment' => $this->paymentDOMock,
54 | ];
55 |
56 | $this->subjectReaderMock->expects(self::once())
57 | ->method('readPayment')
58 | ->with($subject)
59 | ->willReturn($this->paymentDOMock);
60 |
61 | $this->paymentDOMock->expects(self::once())
62 | ->method('getPayment')
63 | ->willReturn(null);
64 |
65 | self::assertEquals(
66 | false,
67 | $this->canVoidHandler->handle($subject)
68 | );
69 | }
70 |
71 | /**
72 | * @covers \Aune\Stripe\Gateway\Config\CanVoidHandler::handle
73 | */
74 | public function testHandleWithAmountPaid()
75 | {
76 | $subject = [
77 | 'payment' => $this->paymentDOMock,
78 | ];
79 |
80 | $this->subjectReaderMock->expects(self::once())
81 | ->method('readPayment')
82 | ->with($subject)
83 | ->willReturn($this->paymentDOMock);
84 |
85 | $this->paymentDOMock->expects(self::once())
86 | ->method('getPayment')
87 | ->willReturn($this->paymentMock);
88 |
89 | $this->paymentMock->expects(self::once())
90 | ->method('getAmountPaid')
91 | ->willReturn(1);
92 |
93 | self::assertEquals(
94 | false,
95 | $this->canVoidHandler->handle($subject)
96 | );
97 | }
98 |
99 | /**
100 | * @covers \Aune\Stripe\Gateway\Config\CanVoidHandler::handle
101 | */
102 | public function testHandle()
103 | {
104 | $subject = [
105 | 'payment' => $this->paymentDOMock,
106 | ];
107 |
108 | $this->subjectReaderMock->expects(self::once())
109 | ->method('readPayment')
110 | ->with($subject)
111 | ->willReturn($this->paymentDOMock);
112 |
113 | $this->paymentDOMock->expects(self::once())
114 | ->method('getPayment')
115 | ->willReturn($this->paymentMock);
116 |
117 | $this->paymentMock->expects(self::once())
118 | ->method('getAmountPaid')
119 | ->willReturn(0);
120 |
121 | self::assertEquals(
122 | true,
123 | $this->canVoidHandler->handle($subject)
124 | );
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Response/PaymentDetailsHandlerTest.php:
--------------------------------------------------------------------------------
1 | subjectReaderMock = $this->getMockBuilder(SubjectReader::class)
37 | ->disableOriginalConstructor()
38 | ->getMock();
39 |
40 | $this->handler = new PaymentDetailsHandler($this->subjectReaderMock);
41 | }
42 |
43 | /**
44 | * @covers \Aune\Stripe\Gateway\Response\PaymentDetailsHandler::handle
45 | */
46 | public function testHandle()
47 | {
48 | $paymentData = $this->getPaymentDataObjectMock();
49 | $paymentIntent = $this->getStripePaymentIntent();
50 |
51 | $subject = ['payment' => $paymentData];
52 | $response = ['object' => $paymentIntent];
53 |
54 | $this->subjectReaderMock->expects(self::once())
55 | ->method('readPayment')
56 | ->with($subject)
57 | ->willReturn($paymentData);
58 | $this->subjectReaderMock->expects(self::once())
59 | ->method('readPaymentIntent')
60 | ->with($response)
61 | ->willReturn($paymentIntent);
62 |
63 | $this->payment->expects(static::exactly(2))
64 | ->method('setAdditionalInformation')
65 | ->withConsecutive(
66 | [PaymentDetailsHandler::FAILURE_CODE, self::FAILURE_CODE],
67 | [PaymentDetailsHandler::OUTCOME . '_' . PaymentDetailsHandler::OUTCOME_REASON, self::OUTCOME_REASON]
68 | );
69 |
70 | $this->handler->handle($subject, $response);
71 | }
72 |
73 | /**
74 | * Create mock for payment data object and order payment
75 | *
76 | * @return \PHPUnit_Framework_MockObject_MockObject
77 | */
78 | private function getPaymentDataObjectMock()
79 | {
80 | $this->payment = $this->getMockBuilder(Payment::class)
81 | ->disableOriginalConstructor()
82 | ->getMock();
83 |
84 | $mock = $this->getMockBuilder(PaymentDataObject::class)
85 | ->setMethods(['getPayment'])
86 | ->disableOriginalConstructor()
87 | ->getMock();
88 |
89 | $mock->expects(static::once())
90 | ->method('getPayment')
91 | ->willReturn($this->payment);
92 |
93 | return $mock;
94 | }
95 |
96 | /**
97 | * Create Stripe Payment Intent
98 | *
99 | * @return \Stripe\PaymentIntent
100 | */
101 | private function getStripePaymentIntent()
102 | {
103 | $attributes = [
104 | 'object' => 'payment_intent',
105 | 'charges' => [
106 | 'object' => 'list',
107 | 'data' => [[
108 | 'object' => 'charge',
109 | PaymentDetailsHandler::FAILURE_CODE => self::FAILURE_CODE,
110 | PaymentDetailsHandler::OUTCOME => [
111 | PaymentDetailsHandler::OUTCOME_REASON => self::OUTCOME_REASON,
112 | ],
113 | ]]
114 | ]
115 | ];
116 |
117 | return \Stripe\Util\Util::convertToStripeObject($attributes, []);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/view/frontend/web/js/view/payment/method-renderer/vault.js:
--------------------------------------------------------------------------------
1 | /*browser:true*/
2 | /*global define*/
3 | define(
4 | [
5 | 'jquery',
6 | 'Magento_Vault/js/view/payment/method-renderer/vault',
7 | 'Magento_Ui/js/model/messageList',
8 | 'mage/translate'
9 | ],
10 | function ($, Component, messageList, $t) {
11 | 'use strict';
12 |
13 | return Component.extend({
14 | defaults: {
15 | paymentIntent: null,
16 | template: 'Aune_Stripe/payment/vault'
17 | },
18 |
19 | /**
20 | * Get last 4 digits of card
21 | * @returns {String}
22 | */
23 | getMaskedCard: function () {
24 | return this.details.maskedCC;
25 | },
26 |
27 | /**
28 | * Get expiration date
29 | * @returns {String}
30 | */
31 | getExpirationDate: function () {
32 | return this.details.expirationDate;
33 | },
34 |
35 | /**
36 | * Get card type
37 | * @returns {String}
38 | */
39 | getCardType: function () {
40 | return this.details.type;
41 | },
42 |
43 | /**
44 | * Get payment method token
45 | * @returns {String}
46 | */
47 | getToken: function () {
48 | return this.publicHash;
49 | },
50 |
51 | /**
52 | * Set payment intent
53 | *
54 | * @param {String} paymentIntent
55 | */
56 | setPaymentIntent: function (paymentIntent) {
57 | this.paymentIntent = paymentIntent;
58 | },
59 |
60 | /**
61 | * @returns {*}
62 | */
63 | getData: function () {
64 | var data = {
65 | method: this.getCode()
66 | };
67 |
68 | data['additional_data'] = {};
69 | data['additional_data']['payment_intent'] = this.paymentIntent;
70 | data['additional_data']['public_hash'] = this.getToken();
71 |
72 | return data;
73 | },
74 |
75 | /**
76 | * Place order click
77 | */
78 | placeOrderClick: function () {
79 | if (!this.isPlaceOrderActionAllowed()) {
80 | return;
81 | }
82 | this.isPlaceOrderActionAllowed(false);
83 |
84 | var self = this;
85 | $.get(this.paymentIntentUrl, { public_hash: this.publicHash }, function(response) {
86 |
87 | if (!response || !response.paymentIntent || !response.paymentIntent.clientSecret) {
88 | messageList.addErrorMessage({
89 | message: $t('An error occurred generating the payment intent.')
90 | });
91 | this.isPlaceOrderActionAllowed(true);
92 | return;
93 | }
94 |
95 | window.stripe.confirmCardPayment(response.paymentIntent.clientSecret)
96 | .then(function (result) {
97 | if (result.error) {
98 | var message = result.error.message;
99 | if (result.error.type == 'validation_error') {
100 | message = $t('Please verify you card information.');
101 | }
102 | messageList.addErrorMessage({
103 | message: message
104 | });
105 | return;
106 | }
107 |
108 | self.setPaymentIntent(result.paymentIntent.id);
109 | self.placeOrder();
110 | });
111 | });
112 | }
113 | });
114 | }
115 | );
116 |
--------------------------------------------------------------------------------
/Gateway/Command/CreatePaymentIntentCommand.php:
--------------------------------------------------------------------------------
1 | config = $config;
68 | $this->amountProvider = $amountProvider;
69 | $this->tokenManagement = $tokenManagement;
70 | $this->tokenProvider = $tokenProvider;
71 | $this->stripeAdapter = $stripeAdapter;
72 | }
73 |
74 | /**
75 | * @inheritdoc
76 | * @throws \Exception
77 | */
78 | public function execute(array $commandSubject)
79 | {
80 | $quote = $commandSubject['quote'];
81 | $currency = $quote->getBaseCurrencyCode();
82 | $amount = $quote->getBaseGrandTotal();
83 |
84 | $params = [
85 | self::CURRENCY => $currency,
86 | self::AMOUNT => $this->amountProvider->convert($amount, $currency),
87 | self::CAPTURE_METHOD => $this->getCaptureMethod(),
88 | ];
89 |
90 | if (!empty($commandSubject['public_hash'])) {
91 |
92 | // Use vaulted payment method
93 | $publicHash = $commandSubject['public_hash'];
94 | $customerId = $commandSubject['customer_id'];
95 |
96 | $paymentToken = $this->tokenManagement->getByPublicHash($publicHash, $customerId);
97 | if (!$paymentToken) {
98 | throw new PaymentException('Invalid payment token');
99 | }
100 |
101 | $params[self::CUSTOMER] = $this->tokenProvider->getCustomerStripeId($customerId);
102 | $params[self::PAYMENT_METHOD] = $paymentToken->getGatewayToken();
103 |
104 | } else if ($this->config->isStoreCustomerEnabled()) {
105 | // Setup for future usage if vaulting is enabled
106 | // and the payment method isn't vaulted already
107 | $params[self::SETUP_FUTURE_USAGE] = $this->config->getStoreFutureUsage();
108 | }
109 |
110 | return $this->stripeAdapter->paymentIntentCreate($params);
111 | }
112 |
113 | /**
114 | * Return the capture method, based on configuration
115 | */
116 | private function getCaptureMethod()
117 | {
118 | $paymentAction = $this->config->getPaymentAction();
119 |
120 | return $paymentAction === AbstractMethod::ACTION_AUTHORIZE ?
121 | self::CAPTURE_METHOD_MANUAL : self::CAPTURE_METHOD_AUTOMATIC;
122 | }
123 | }
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Config/ConfigTest.php:
--------------------------------------------------------------------------------
1 | scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class);
41 | $this->encryptorMock = $this->getMockBuilder(Encryptor::class)
42 | ->disableOriginalConstructor()
43 | ->getMock();
44 | $this->urlHelperMock = $this->getMockForAbstractClass(UrlInterface::class);
45 |
46 | $this->config = new Config(
47 | $this->scopeConfigMock,
48 | $this->encryptorMock,
49 | $this->urlHelperMock,
50 | self::METHOD_CODE
51 | );
52 | }
53 |
54 | /**
55 | * @dataProvider getConfigDataProvider
56 | *
57 | * @param array $config
58 | * @param array $expected
59 | */
60 | public function testGetConfigValue($key, $method , $in, $out, $secret)
61 | {
62 | $this->scopeConfigMock->expects(self::once())
63 | ->method('getValue')
64 | ->with('payment/' . self::METHOD_CODE . '/' . $key)
65 | ->willReturn($in);
66 |
67 | if ($secret) {
68 | $this->encryptorMock->expects(self::once())
69 | ->method('decrypt')
70 | ->with($in)
71 | ->willReturn($in);
72 | }
73 |
74 | self::assertEquals(
75 | $out,
76 | $this->config->$method()
77 | );
78 | }
79 |
80 | /**
81 | * @return array
82 | */
83 | public function getConfigDataProvider()
84 | {
85 | return [
86 | ['key' => Config::KEY_ACTIVE, 'method' => 'isActive', 'in' => '1', 'out' => true, 'secret' => false],
87 | ['key' => Config::KEY_ACTIVE, 'method' => 'isActive', 'in' => '0', 'out' => false, 'secret' => false],
88 | ['key' => Config::KEY_PUBLISHABLE_KEY, 'method' => 'getPublishableKey', 'in' => 'test', 'out' => 'test', 'secret' => false],
89 | ['key' => Config::KEY_SECRET_KEY, 'method' => 'getSecretKey', 'in' => 'test', 'out' => 'test', 'secret' => true],
90 | ['key' => Config::KEY_SDK_URL, 'method' => 'getSdkUrl', 'in' => 'test', 'out' => 'test', 'secret' => false],
91 | ['key' => Config::KEY_STORE_CUSTOMER, 'method' => 'isStoreCustomerEnabled', 'in' => '1', 'out' => true, 'secret' => false],
92 | ['key' => Config::KEY_STORE_CUSTOMER, 'method' => 'isStoreCustomerEnabled', 'in' => '0', 'out' => false, 'secret' => false],
93 | ['key' => Config::KEY_CC_TYPES_STRIPE_MAPPER, 'method' => 'getCcTypesMapper', 'in' => null, 'out' => [], 'secret' => false],
94 | ['key' => Config::KEY_CC_TYPES_STRIPE_MAPPER, 'method' => 'getCcTypesMapper', 'in' => '{"american-express":"AE","discover":"DI"}', 'out' => ['american-express' => 'AE', 'discover' => 'DI'], 'secret' => false],
95 | ];
96 | }
97 |
98 | /**
99 | * Test payment intent generation url getter
100 | */
101 | public function testGetPaymentIntentUrl()
102 | {
103 | $url = 'test_url';
104 |
105 | $this->urlHelperMock->expects(self::once())
106 | ->method('getUrl')
107 | ->with(Config::PAYMENT_INTENT_PATH)
108 | ->willReturn($url);
109 |
110 | self::assertEquals($url, $this->config->getPaymentIntentUrl());
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Http/Client/RefundCreateTest.php:
--------------------------------------------------------------------------------
1 | getMockForAbstractClass(LoggerInterface::class);
36 | $this->loggerMock = $this->getMockBuilder(Logger::class)
37 | ->disableOriginalConstructor()
38 | ->getMock();
39 | $this->adapter = $this->getMockBuilder(StripeAdapter::class)
40 | ->disableOriginalConstructor()
41 | ->getMock();
42 |
43 | $this->model = new RefundCreate($criticalLoggerMock, $this->loggerMock, $this->adapter);
44 | }
45 |
46 | /**
47 | * Run test placeRequest method (exception)
48 | *
49 | * @return void
50 | *
51 | * @expectedException \Magento\Payment\Gateway\Http\ClientException
52 | * @expectedExceptionMessage Test message
53 | */
54 | public function testPlaceRequestException()
55 | {
56 | $this->loggerMock->expects($this->once())
57 | ->method('debug')
58 | ->with(
59 | [
60 | 'request' => $this->getTransferData(),
61 | 'client' => RefundCreate::class,
62 | 'response' => []
63 | ]
64 | );
65 |
66 | $this->adapter->expects($this->once())
67 | ->method('refundCreate')
68 | ->willThrowException(new \Exception('Test message'));
69 |
70 | /** @var TransferInterface|\PHPUnit_Framework_MockObject_MockObject $transferObjectMock */
71 | $transferObjectMock = $this->getTransferObjectMock();
72 |
73 | $this->model->placeRequest($transferObjectMock);
74 | }
75 |
76 | /**
77 | * Run test placeRequest method
78 | *
79 | * @return void
80 | */
81 | public function testPlaceRequestSuccess()
82 | {
83 | $refundMock = $this->getMockBuilder(\Stripe\Refund::class)
84 | ->disableOriginalConstructor()
85 | ->getMock();
86 |
87 | $this->adapter->expects($this->once())
88 | ->method('refundCreate')
89 | ->with($this->getTransferData())
90 | ->willReturn($refundMock);
91 |
92 | $this->loggerMock->expects($this->once())
93 | ->method('debug')
94 | ->with(
95 | [
96 | 'request' => $this->getTransferData(),
97 | 'client' => RefundCreate::class,
98 | 'response' => (array)$refundMock,
99 | ]
100 | );
101 |
102 | $actualResult = $this->model->placeRequest($this->getTransferObjectMock());
103 |
104 | $this->assertTrue(is_object($actualResult['object']));
105 | $this->assertEquals(['object' => $refundMock], $actualResult);
106 | }
107 |
108 | /**
109 | * @return TransferInterface|\PHPUnit_Framework_MockObject_MockObject
110 | */
111 | private function getTransferObjectMock()
112 | {
113 | $transferObjectMock = $this->getMockForAbstractClass(TransferInterface::class);
114 | $transferObjectMock->expects($this->once())
115 | ->method('getBody')
116 | ->willReturn($this->getTransferData());
117 |
118 | return $transferObjectMock;
119 | }
120 |
121 | /**
122 | * @return array
123 | */
124 | private function getTransferData()
125 | {
126 | return [
127 | 'test-data-key' => 'test-data-value'
128 | ];
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Http/Client/PaymentIntentCreateTest.php:
--------------------------------------------------------------------------------
1 | getMockForAbstractClass(LoggerInterface::class);
36 | $this->loggerMock = $this->getMockBuilder(Logger::class)
37 | ->disableOriginalConstructor()
38 | ->getMock();
39 | $this->adapter = $this->getMockBuilder(StripeAdapter::class)
40 | ->disableOriginalConstructor()
41 | ->getMock();
42 |
43 | $this->model = new PaymentIntentCreate($criticalLoggerMock, $this->loggerMock, $this->adapter);
44 | }
45 |
46 | /**
47 | * Run test placeRequest method (exception)
48 | *
49 | * @return void
50 | *
51 | * @expectedException \Magento\Payment\Gateway\Http\ClientException
52 | * @expectedExceptionMessage Test message
53 | */
54 | public function testPlaceRequestException()
55 | {
56 | $this->loggerMock->expects($this->once())
57 | ->method('debug')
58 | ->with(
59 | [
60 | 'request' => $this->getTransferData(),
61 | 'client' => PaymentIntentCreate::class,
62 | 'response' => []
63 | ]
64 | );
65 |
66 | $this->adapter->expects($this->once())
67 | ->method('paymentIntentCreate')
68 | ->willThrowException(new \Exception('Test message'));
69 |
70 | /** @var TransferInterface|\PHPUnit_Framework_MockObject_MockObject $transferObjectMock */
71 | $transferObjectMock = $this->getTransferObjectMock();
72 |
73 | $this->model->placeRequest($transferObjectMock);
74 | }
75 |
76 | /**
77 | * Run test placeRequest method
78 | *
79 | * @return void
80 | */
81 | public function testPlaceRequestSuccess()
82 | {
83 | $paymentIntentMock = $this->getMockBuilder(\Stripe\PaymentIntent::class)
84 | ->disableOriginalConstructor()
85 | ->getMock();
86 |
87 | $this->adapter->expects(self::once())
88 | ->method('paymentIntentCreate')
89 | ->with($this->getTransferData())
90 | ->willReturn($paymentIntentMock);
91 |
92 | $this->loggerMock->expects(self::once())
93 | ->method('debug')
94 | ->with(
95 | [
96 | 'request' => $this->getTransferData(),
97 | 'client' => PaymentIntentCreate::class,
98 | 'response' => (array)$paymentIntentMock,
99 | ]
100 | );
101 |
102 | $actualResult = $this->model->placeRequest($this->getTransferObjectMock());
103 |
104 | $this->assertTrue(is_object($actualResult['object']));
105 | $this->assertEquals(['object' => $paymentIntentMock], $actualResult);
106 | }
107 |
108 | /**
109 | * @return TransferInterface|\PHPUnit_Framework_MockObject_MockObject
110 | */
111 | private function getTransferObjectMock()
112 | {
113 | $transferObjectMock = $this->getMockForAbstractClass(TransferInterface::class);
114 | $transferObjectMock->expects($this->once())
115 | ->method('getBody')
116 | ->willReturn($this->getTransferData());
117 |
118 | return $transferObjectMock;
119 | }
120 |
121 | /**
122 | * @return array
123 | */
124 | private function getTransferData()
125 | {
126 | return [
127 | 'test-data-key' => 'test-data-value'
128 | ];
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Helper/TokenProviderTest.php:
--------------------------------------------------------------------------------
1 | customerRepositoryMock = $this->getMockForAbstractClass(CustomerRepositoryInterface::class);
41 | $this->customerMock = $this->getMockForAbstractClass(CustomerInterface::class);
42 | $this->attributeValueMock = $this->getMockBuilder(AttributeValue::class)
43 | ->getMock();
44 | $this->stripeAdapterMock = $this->getMockBuilder(StripeAdapter::class)
45 | ->disableOriginalConstructor()
46 | ->getMock();
47 |
48 | $this->tokenProvider = new TokenProvider(
49 | $this->customerRepositoryMock,
50 | $this->stripeAdapterMock
51 | );
52 | }
53 |
54 | /**
55 | * @covers \Aune\Stripe\Gateway\Helper\TokenProvider::getCustomerStripeId
56 | */
57 | public function testGetCustomerStripeIdNotAssigned()
58 | {
59 | $customerId = rand();
60 |
61 | $this->customerRepositoryMock->expects(static::once())
62 | ->method('getById')
63 | ->with($customerId)
64 | ->willReturn($this->customerMock);
65 |
66 | self::assertEquals(
67 | null,
68 | $this->tokenProvider->getCustomerStripeId($customerId)
69 | );
70 | }
71 |
72 | /**
73 | * @covers \Aune\Stripe\Gateway\Helper\TokenProvider::getCustomerStripeId
74 | */
75 | public function testGetCustomerStripeIdAssigned()
76 | {
77 | $customerId = rand();
78 | $stripeId = rand();
79 |
80 | $this->customerRepositoryMock->expects(static::once())
81 | ->method('getById')
82 | ->with($customerId)
83 | ->willReturn($this->customerMock);
84 |
85 | $this->customerMock->expects(static::once())
86 | ->method('getCustomAttribute')
87 | ->with(TokenProvider::ATTRIBUTE_CODE)
88 | ->willReturn($this->attributeValueMock);
89 |
90 | $this->attributeValueMock->expects(static::once())
91 | ->method('getValue')
92 | ->willReturn($stripeId);
93 |
94 | self::assertEquals(
95 | $stripeId,
96 | $this->tokenProvider->getCustomerStripeId($customerId)
97 | );
98 | }
99 |
100 | /**
101 | * @covers \Aune\Stripe\Gateway\Helper\TokenProvider::setCustomerStripeId
102 | */
103 | public function testSetCustomerStripeId()
104 | {
105 | $customerId = rand();
106 | $stripeId = rand();
107 |
108 | $this->customerRepositoryMock->expects(static::once())
109 | ->method('getById')
110 | ->with($customerId)
111 | ->willReturn($this->customerMock);
112 |
113 | $this->customerMock->expects(static::once())
114 | ->method('setCustomAttribute')
115 | ->with(TokenProvider::ATTRIBUTE_CODE, $stripeId)
116 | ->willReturn($this->customerMock);
117 |
118 | $this->customerRepositoryMock->expects(static::once())
119 | ->method('save')
120 | ->with($this->customerMock)
121 | ->willReturn(true);
122 |
123 | $this->tokenProvider->setCustomerStripeId($customerId, $stripeId);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Gateway/Request/RetrieveDataBuilder.php:
--------------------------------------------------------------------------------
1 | config = $config;
57 | $this->subjectReader = $subjectReader;
58 | $this->stripeAdapter = $stripeAdapter;
59 | $this->tokenProvider = $tokenProvider;
60 | }
61 |
62 | /**
63 | * @inheritdoc
64 | */
65 | public function build(array $buildSubject)
66 | {
67 | $paymentDO = $this->subjectReader->readPayment($buildSubject);
68 | $payment = $paymentDO->getPayment();
69 | $orderAdapter = $paymentDO->getOrder();
70 |
71 | $paymentIntentId = $payment->getAdditionalInformation(DataAssignObserver::PAYMENT_INTENT);
72 | $data = [
73 | self::PAYMENT_INTENT => $paymentIntentId,
74 | ];
75 |
76 | // If vaulting is enabled, assign the payment intent to the customer
77 | if ($this->canVaultCustomer($orderAdapter, $payment)) {
78 |
79 | // Attach new source to customer
80 | $stripeCustomer = $this->getStripeCustomer($orderAdapter);
81 |
82 | $data[self::CUSTOMER] = $stripeCustomer->id;
83 | }
84 |
85 | return $data;
86 | }
87 |
88 | /**
89 | * Check if the customer can be vaulted for the given order
90 | */
91 | private function canVaultCustomer(
92 | OrderAdapterInterface $order,
93 | PaymentInfoInterface $payment
94 | ) {
95 | if (is_null($order->getCustomerId())) {
96 | return false;
97 | }
98 |
99 | return $this->config->isStoreCustomerEnabled() ||
100 | $payment->getAdditionalInformation(VaultConfigProvider::IS_ACTIVE_CODE);
101 | }
102 |
103 | /**
104 | * Get Stripe customer if it exists, otherwise create a new one and assign
105 | * it to the Magento customer
106 | */
107 | private function getStripeCustomer(OrderAdapterInterface $orderAdapter)
108 | {
109 | // Check if the customer already has a stripe id
110 | $customerId = $orderAdapter->getCustomerId();
111 |
112 | $stripeId = $this->tokenProvider->getCustomerStripeId($customerId);
113 | if ($stripeId) {
114 | return $this->stripeAdapter->customerRetrieve($stripeId);
115 | }
116 |
117 | $addressAdapter = $orderAdapter->getBillingAddress();
118 | $stripeCustomer = $this->stripeAdapter->customerCreate([
119 | 'email' => $addressAdapter->getEmail(),
120 | 'description' => $addressAdapter->getFirstname() . ' ' . $addressAdapter->getLastname(),
121 | ]);
122 |
123 | // Assign the customer Stripe id to the Magento customer
124 | $this->tokenProvider->setCustomerStripeId(
125 | $customerId,
126 | $stripeCustomer->id
127 | );
128 |
129 | return $stripeCustomer;
130 | }
131 | }
--------------------------------------------------------------------------------
/etc/adminhtml/system.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Magento\Config\Model\Config\Source\Yesno
11 |
12 |
13 |
14 |
15 | 1
16 |
17 |
18 |
19 |
20 | Aune\Stripe\Model\Adminhtml\Source\PaymentAction
21 |
22 | 1
23 |
24 |
25 |
26 |
27 |
28 | 1
29 |
30 |
31 |
32 |
33 | Magento\Config\Model\Config\Backend\Encrypted
34 |
35 | 1
36 |
37 |
38 |
39 |
40 | Create customer entity in Stripe (mandatory to enable the vault).
41 | Magento\Config\Model\Config\Source\Yesno
42 |
43 | 1
44 |
45 |
46 |
47 |
48 | Use On Session if you intend to only reuse the payment method when your customer is present in your checkout flow. Use Off Session if your customer may or may not be in your checkout flow.
49 | Aune\Stripe\Model\Adminhtml\Source\FutureUsage
50 |
51 | 1
52 | 1
53 |
54 |
55 |
56 |
57 | Magento\Config\Model\Config\Source\Yesno
58 |
59 |
60 |
61 | Magento\Config\Model\Config\Source\Yesno
62 | payment/aune_stripe_vault/active
63 |
64 | 1
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Http/Client/PaymentIntentCaptureTest.php:
--------------------------------------------------------------------------------
1 | getMockForAbstractClass(LoggerInterface::class);
39 | $this->loggerMock = $this->getMockBuilder(Logger::class)
40 | ->disableOriginalConstructor()
41 | ->getMock();
42 | $this->adapter = $this->getMockBuilder(StripeAdapter::class)
43 | ->disableOriginalConstructor()
44 | ->getMock();
45 |
46 | $this->model = new PaymentIntentCapture($criticalLoggerMock, $this->loggerMock, $this->adapter);
47 | }
48 |
49 | /**
50 | * Run test placeRequest method (exception)
51 | *
52 | * @return void
53 | *
54 | * @expectedException \Magento\Payment\Gateway\Http\ClientException
55 | * @expectedExceptionMessage Test message
56 | */
57 | public function testPlaceRequestException()
58 | {
59 | $this->loggerMock->expects($this->once())
60 | ->method('debug')
61 | ->with(
62 | [
63 | 'request' => $this->getTransferData(),
64 | 'client' => PaymentIntentCapture::class,
65 | 'response' => []
66 | ]
67 | );
68 |
69 | $this->adapter->expects($this->once())
70 | ->method('paymentIntentRetrieve')
71 | ->willThrowException(new \Exception('Test message'));
72 |
73 | /** @var TransferInterface|\PHPUnit_Framework_MockObject_MockObject $transferObjectMock */
74 | $transferObjectMock = $this->getTransferObjectMock();
75 |
76 | $this->model->placeRequest($transferObjectMock);
77 | }
78 |
79 | /**
80 | * Run test placeRequest method
81 | *
82 | * @return void
83 | */
84 | public function testPlaceRequestSuccess()
85 | {
86 | $paymentIntentMock = $this->getMockBuilder(\Stripe\PaymentIntent::class)
87 | ->disableOriginalConstructor()
88 | ->getMock();
89 |
90 | $this->adapter->expects(self::once())
91 | ->method('paymentIntentRetrieve')
92 | ->with(self::PAYMENT_INTENT_ID)
93 | ->willReturn($paymentIntentMock);
94 |
95 | $paymentIntentMock->expects(self::once())
96 | ->method('capture');
97 |
98 | $this->loggerMock->expects($this->once())
99 | ->method('debug')
100 | ->with(
101 | [
102 | 'request' => $this->getTransferData(),
103 | 'client' => PaymentIntentCapture::class,
104 | 'response' => (array)$paymentIntentMock,
105 | ]
106 | );
107 |
108 | $actualResult = $this->model->placeRequest($this->getTransferObjectMock());
109 |
110 | $this->assertTrue(is_object($actualResult['object']));
111 | $this->assertEquals(['object' => $paymentIntentMock], $actualResult);
112 | }
113 |
114 | /**
115 | * @return TransferInterface|\PHPUnit_Framework_MockObject_MockObject
116 | */
117 | private function getTransferObjectMock()
118 | {
119 | $transferObjectMock = $this->getMockForAbstractClass(TransferInterface::class);
120 | $transferObjectMock->expects($this->once())
121 | ->method('getBody')
122 | ->willReturn($this->getTransferData());
123 |
124 | return $transferObjectMock;
125 | }
126 |
127 | /**
128 | * @return array
129 | */
130 | private function getTransferData()
131 | {
132 | return [
133 | CaptureDataBuilder::PAYMENT_INTENT => self::PAYMENT_INTENT_ID,
134 | 'test-data-key' => 'test-data-value'
135 | ];
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/Model/Adapter/StripeAdapter.php:
--------------------------------------------------------------------------------
1 | config = $config;
45 | $this->moduleList = $moduleList;
46 |
47 | $this->initCredentials();
48 | }
49 |
50 | /**
51 | * Initializes credentials.
52 | *
53 | * @return void
54 | */
55 | protected function initCredentials()
56 | {
57 | // Set application version
58 | $module = $this->moduleList->getOne(self::MODULE_NAME);
59 | $this->setAppInfo(
60 | self::APPLICATION_NAME,
61 | $module['setup_version'],
62 | self::APPLICATION_URL
63 | );
64 |
65 | // Set secret key
66 | $this->setApiKey($this->config->getSecretKey());
67 |
68 | // Pinpoint API version
69 | $this->setApiVersion(self::API_VERSION);
70 | }
71 |
72 | /**
73 | * @param string|null $value
74 | * @return mixed
75 | */
76 | public function setApiKey($value = null)
77 | {
78 | return Stripe::setApiKey($value);
79 | }
80 |
81 | /**
82 | * @param string $applicationName
83 | * @param string $applicationVersion
84 | * @param string $applicationUrl
85 | * @return mixed
86 | */
87 | public function setAppInfo($applicationName, $applicationVersion, $applicationUrl)
88 | {
89 | return Stripe::setAppInfo($applicationName, $applicationVersion, $applicationUrl);
90 | }
91 |
92 | /**
93 | * @param string|null $value
94 | * @return mixed
95 | */
96 | public function setApiVersion($value = null)
97 | {
98 | return Stripe::setApiVErsion($value);
99 | }
100 |
101 | /**
102 | * @param array $attributes
103 | * @return \Stripe\Customer|\Stripe\Error\Base
104 | */
105 | public function customerCreate(array $attributes)
106 | {
107 | return Customer::create($attributes);
108 | }
109 |
110 | /**
111 | * @param string $customerId
112 | * @return \Stripe\Customer|\Stripe\Error\Base
113 | */
114 | public function customerRetrieve(string $customerId)
115 | {
116 | return Customer::retrieve($customerId);
117 | }
118 |
119 | /**
120 | * @param \Stripe\Customer $customer
121 | * @param string $sourceId
122 | * @return \Stripe\Source|\Stripe\Error\Base
123 | */
124 | public function customerAttachSource(
125 | \Stripe\Customer $customer,
126 | string $sourceId
127 | ) {
128 | return $customer->sources->create([
129 | 'source' => $sourceId,
130 | ]);
131 | }
132 |
133 | /**
134 | * @param array $params
135 | * @return \Stripe\PaymentIntent|\Stripe\Error\Base
136 | */
137 | public function paymentIntentCreate($params)
138 | {
139 | return PaymentIntent::create($params);
140 | }
141 |
142 | /**
143 | * @param string $paymentIntentId
144 | * @return \Stripe\PaymentIntent|\Stripe\Error\Base
145 | */
146 | public function paymentIntentRetrieve($paymentIntentId)
147 | {
148 | return PaymentIntent::retrieve($paymentIntentId);
149 | }
150 |
151 | /**
152 | * @param string $paymentMethodId
153 | * @return \Stripe\PaymentMethod|\Stripe\Error\Base
154 | */
155 | public function paymentMethodRetrieve($paymentMethodId)
156 | {
157 | return PaymentMethod::retrieve($paymentMethodId);
158 | }
159 |
160 | /**
161 | * @param array $params
162 | * @return \Stripe\Refund|\Stripe\Error\Base
163 | */
164 | public function refundCreate($params)
165 | {
166 | return Refund::create($params);
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Response/CardDetailsHandlerTest.php:
--------------------------------------------------------------------------------
1 | config = $this->getMockBuilder(Config::class)
40 | ->disableOriginalConstructor()
41 | ->getMock();
42 |
43 | $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class)
44 | ->disableOriginalConstructor()
45 | ->getMock();
46 |
47 | $this->cardHandler = new CardDetailsHandler($this->config, $this->subjectReaderMock);
48 | }
49 |
50 | /**
51 | * @covers \Aune\Stripe\Gateway\Response\CardDetailsHandler::handle
52 | */
53 | public function testHandle()
54 | {
55 | $paymentData = $this->getPaymentDataObjectMock();
56 | $paymentIntent = $this->getStripePaymentIntent();
57 |
58 | $subject = ['payment' => $paymentData];
59 | $response = ['object' => $paymentIntent];
60 |
61 | $this->subjectReaderMock->expects(self::once())
62 | ->method('readPayment')
63 | ->with($subject)
64 | ->willReturn($paymentData);
65 | $this->subjectReaderMock->expects(self::once())
66 | ->method('readPaymentIntent')
67 | ->with($response)
68 | ->willReturn($paymentIntent);
69 |
70 | $this->payment->expects(static::once())
71 | ->method('setCcLast4');
72 | $this->payment->expects(static::once())
73 | ->method('setCcExpMonth');
74 | $this->payment->expects(static::once())
75 | ->method('setCcExpYear');
76 | $this->payment->expects(static::once())
77 | ->method('setCcType');
78 | $this->payment->expects(static::exactly(2))
79 | ->method('setAdditionalInformation');
80 |
81 | $this->cardHandler->handle($subject, $response);
82 | }
83 |
84 | /**
85 | * Create mock for payment data object and order payment
86 | *
87 | * @return \PHPUnit_Framework_MockObject_MockObject
88 | */
89 | private function getPaymentDataObjectMock()
90 | {
91 | $this->payment = $this->getMockBuilder(Payment::class)
92 | ->disableOriginalConstructor()
93 | ->setMethods([
94 | 'setCcLast4',
95 | 'setCcExpMonth',
96 | 'setCcExpYear',
97 | 'setCcType',
98 | 'setAdditionalInformation',
99 | ])
100 | ->getMock();
101 |
102 | $mock = $this->getMockBuilder(PaymentDataObject::class)
103 | ->setMethods(['getPayment'])
104 | ->disableOriginalConstructor()
105 | ->getMock();
106 |
107 | $mock->expects(static::once())
108 | ->method('getPayment')
109 | ->willReturn($this->payment);
110 |
111 | return $mock;
112 | }
113 |
114 | /**
115 | * Create Stripe Payment Intent
116 | *
117 | * @return \Stripe\PaymentIntent
118 | */
119 | private function getStripePaymentIntent()
120 | {
121 | $attributes = [
122 | 'object' => 'payment_intent',
123 | 'charges' => [
124 | 'object' => 'list',
125 | 'data' => [[
126 | 'object' => 'charge',
127 | 'payment_method_details' => [
128 | 'card' => [
129 | 'brand' => 'Visa',
130 | 'exp_month' => 07,
131 | 'exp_year' => 29,
132 | 'last4' => 1234,
133 | ]
134 | ]
135 | ]]
136 | ]
137 | ];
138 |
139 | return \Stripe\Util\Util::convertToStripeObject($attributes, []);
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/view/frontend/web/template/payment/cc-form.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
76 |
77 |
78 |
79 |
80 |
81 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Validator/ResponseValidatorTest.php:
--------------------------------------------------------------------------------
1 | resultInterfaceFactory = $this->getMockBuilder(ResultInterfaceFactory::class)
39 | ->disableOriginalConstructor()
40 | ->setMethods(['create'])
41 | ->getMock();
42 | $this->subjectReader = $this->getMockBuilder(SubjectReader::class)
43 | ->disableOriginalConstructor()
44 | ->getMock();
45 |
46 | $this->responseValidator = new ResponseValidator(
47 | $this->resultInterfaceFactory,
48 | $this->subjectReader
49 | );
50 | }
51 |
52 | /**
53 | * @expectedException \InvalidArgumentException
54 | */
55 | public function testValidateReadResponseException()
56 | {
57 | $validationSubject = [
58 | 'response' => null
59 | ];
60 |
61 | $this->subjectReader->expects(self::once())
62 | ->method('readResponseObject')
63 | ->with($validationSubject)
64 | ->willThrowException(new \InvalidArgumentException());
65 |
66 | $this->responseValidator->validate($validationSubject);
67 | }
68 |
69 | /**
70 | * @expectedException \InvalidArgumentException
71 | */
72 | public function testValidateReadResponseObjectException()
73 | {
74 | $validationSubject = [
75 | 'response' => ['object' => null]
76 | ];
77 |
78 | $this->subjectReader->expects(self::once())
79 | ->method('readResponseObject')
80 | ->with($validationSubject)
81 | ->willThrowException(new \InvalidArgumentException());
82 |
83 | $this->responseValidator->validate($validationSubject);
84 | }
85 |
86 | /**
87 | * Run test for validate method
88 | *
89 | * @param array $validationSubject
90 | * @param bool $isValid
91 | * @param Phrase[] $messages
92 | * @return void
93 | *
94 | * @dataProvider dataProviderTestValidate
95 | */
96 | public function testValidate(array $validationSubject, $isValid, $messages)
97 | {
98 | $result = $this->getMockForAbstractClass(ResultInterface::class);
99 |
100 | $this->subjectReader->expects(self::once())
101 | ->method('readResponseObject')
102 | ->with($validationSubject)
103 | ->willReturn($validationSubject['response']['object']);
104 |
105 | $this->resultInterfaceFactory->expects(self::once())
106 | ->method('create')
107 | ->with([
108 | 'isValid' => $isValid,
109 | 'failsDescription' => $messages,
110 | 'errorCodes' => [],
111 | ])
112 | ->willReturn($result);
113 |
114 | $actual = $this->responseValidator->validate($validationSubject);
115 |
116 | self::assertEquals($result, $actual);
117 | }
118 |
119 | /**
120 | * @return array
121 | */
122 | public function dataProviderTestValidate()
123 | {
124 | $successTrue = \Stripe\Util\Util::convertToStripeObject([
125 | 'object' => 'payment_intent',
126 | 'status' => 'succeeded',
127 | ], []);
128 |
129 | $transactionDeclined = \Stripe\Util\Util::convertToStripeObject([
130 | 'object' => 'payment_intent',
131 | 'status' => 'failed',
132 | ], []);
133 |
134 | $errorResult = new \Stripe\Exception\AuthenticationException('');
135 |
136 | return [
137 | [
138 | 'validationSubject' => [
139 | 'response' => [
140 | 'object' => $successTrue
141 | ],
142 | ],
143 | 'isValid' => true,
144 | []
145 | ],
146 | [
147 | 'validationSubject' => [
148 | 'response' => [
149 | 'object' => $transactionDeclined
150 | ]
151 | ],
152 | 'isValid' => false,
153 | [
154 | __('Wrong transaction status')
155 | ]
156 | ],
157 | [
158 | 'validationSubject' => [
159 | 'response' => [
160 | 'object' => $errorResult,
161 | ]
162 | ],
163 | 'isValid' => false,
164 | [
165 | __('Stripe error response'),
166 | __('Wrong transaction status')
167 | ]
168 | ]
169 | ];
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Response/RefundHandlerTest.php:
--------------------------------------------------------------------------------
1 | subjectReaderMock = $this->getMockBuilder(SubjectReader::class)
38 | ->disableOriginalConstructor()
39 | ->getMock();
40 |
41 | $this->handler = new RefundHandler($this->subjectReaderMock);
42 | }
43 |
44 | /**
45 | * @covers \Aune\Stripe\Gateway\Response\RefundHandler::handle
46 | */
47 | public function testHandleCloseParent()
48 | {
49 | $paymentData = $this->getPaymentDataObjectMock();
50 | $refund = $this->getStripeRefund();
51 |
52 | $subject = ['payment' => $paymentData];
53 | $response = ['object' => $refund];
54 |
55 | $this->subjectReaderMock->expects(self::once())
56 | ->method('readPayment')
57 | ->with($subject)
58 | ->willReturn($paymentData);
59 |
60 | $this->subjectReaderMock->expects(self::once())
61 | ->method('readRefund')
62 | ->with($response)
63 | ->willReturn($refund);
64 |
65 | $this->payment->expects(self::once())
66 | ->method('setTransactionId')
67 | ->with(self::REFUND_ID);
68 |
69 | $this->payment->expects(self::once())
70 | ->method('setIsTransactionClosed')
71 | ->with(true);
72 |
73 | $this->payment->expects(self::once())
74 | ->method('setShouldCloseParentTransaction')
75 | ->with(true);
76 |
77 | $this->handler->handle($subject, $response);
78 | }
79 |
80 | /**
81 | * @covers \Aune\Stripe\Gateway\Response\RefundHandler::handle
82 | */
83 | public function testHandle()
84 | {
85 | $paymentData = $this->getPaymentDataObjectMock();
86 | $refund = $this->getStripeRefund();
87 |
88 | $subject = ['payment' => $paymentData];
89 | $response = ['object' => $refund];
90 |
91 | $this->subjectReaderMock->expects(self::once())
92 | ->method('readPayment')
93 | ->with($subject)
94 | ->willReturn($paymentData);
95 |
96 | $this->subjectReaderMock->expects(self::once())
97 | ->method('readRefund')
98 | ->with($response)
99 | ->willReturn($refund);
100 |
101 | $invoice = $this->getMockBuilder(Invoice::class)
102 | ->disableOriginalConstructor()
103 | ->getMock();
104 |
105 | $creditmemo = $this->getMockBuilder(Creditmemo::class)
106 | ->disableOriginalConstructor()
107 | ->getMock();
108 |
109 | $creditmemo->expects(self::once())
110 | ->method('getInvoice')
111 | ->willReturn($invoice);
112 |
113 | $this->payment->expects(self::once())
114 | ->method('getCreditmemo')
115 | ->willReturn($creditmemo);
116 |
117 | $invoice->expects(self::once())
118 | ->method('canRefund')
119 | ->willReturn(true);
120 |
121 | $this->payment->expects(self::once())
122 | ->method('setTransactionId')
123 | ->with(self::REFUND_ID);
124 |
125 | $this->payment->expects(self::once())
126 | ->method('setIsTransactionClosed')
127 | ->with(true);
128 |
129 | $this->payment->expects(self::once())
130 | ->method('setShouldCloseParentTransaction')
131 | ->with(false);
132 |
133 | $this->handler->handle($subject, $response);
134 | }
135 |
136 | /**
137 | * Create mock for payment data object and order payment
138 | *
139 | * @return \PHPUnit_Framework_MockObject_MockObject
140 | */
141 | private function getPaymentDataObjectMock()
142 | {
143 | $this->payment = $this->getMockBuilder(Payment::class)
144 | ->disableOriginalConstructor()
145 | ->getMock();
146 |
147 | $mock = $this->getMockBuilder(PaymentDataObject::class)
148 | ->setMethods(['getPayment'])
149 | ->disableOriginalConstructor()
150 | ->getMock();
151 |
152 | $mock->expects(static::once())
153 | ->method('getPayment')
154 | ->willReturn($this->payment);
155 |
156 | return $mock;
157 | }
158 |
159 | /**
160 | * Create Stripe Refund
161 | *
162 | * @return \Stripe\Refund
163 | */
164 | private function getStripeRefund()
165 | {
166 | $attributes = [
167 | 'object' => 'refund',
168 | 'id' => self::REFUND_ID,
169 | ];
170 |
171 | return \Stripe\Util\Util::convertToStripeObject($attributes, []);
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/Gateway/Command/CaptureStrategyCommand.php:
--------------------------------------------------------------------------------
1 | commandPool = $commandPool;
88 | $this->transactionRepository = $repository;
89 | $this->filterBuilder = $filterBuilder;
90 | $this->searchCriteriaBuilder = $searchCriteriaBuilder;
91 | $this->dateTime = $dateTime;
92 | $this->subjectReader = $subjectReader;
93 | }
94 |
95 | /**
96 | * @inheritdoc
97 | */
98 | public function execute(array $commandSubject)
99 | {
100 | /** @var \Magento\Payment\Gateway\Data\PaymentDataObjectInterface $paymentDO */
101 | $paymentDO = $this->subjectReader->readPayment($commandSubject);
102 |
103 | /** @var \Magento\Sales\Api\Data\OrderPaymentInterface $paymentInfo */
104 | $paymentInfo = $paymentDO->getPayment();
105 | ContextHelper::assertOrderPayment($paymentInfo);
106 |
107 | $command = $this->getCommand($paymentInfo);
108 | $this->commandPool->get($command)->execute($commandSubject);
109 | }
110 |
111 | /**
112 | * Get execution command name
113 | * @param OrderPaymentInterface $payment
114 | * @return string
115 | */
116 | private function getCommand(OrderPaymentInterface $payment)
117 | {
118 | // If no authorization transaction exists execute authorize and capture command
119 | $existsCapture = $this->isExistsCaptureTransaction($payment);
120 | if (!$payment->getAuthorizationTransaction() && !$existsCapture) {
121 | return self::SALE;
122 | }
123 |
124 | // Capture authorized charge
125 | if (!$existsCapture && !$this->isExpiredAuthorization($payment)) {
126 | return self::CAPTURE;
127 | }
128 |
129 | // Process capture for payment via Vault
130 | return self::CAPTURE;
131 | }
132 |
133 | /**
134 | * @param OrderPaymentInterface $payment
135 | * @return boolean
136 | */
137 | private function isExpiredAuthorization(OrderPaymentInterface $payment)
138 | {
139 | $currentTs = $this->dateTime->timestamp();
140 | $txTs = $this->dateTime->timestamp($payment->getOrder()->getCreatedAt());
141 |
142 | return $currentTs - $txTs > self::AUTHORIZATION_TTL;
143 | }
144 |
145 | /**
146 | * Check if capture transaction already exists
147 | *
148 | * @param OrderPaymentInterface $payment
149 | * @return bool
150 | */
151 | private function isExistsCaptureTransaction(OrderPaymentInterface $payment)
152 | {
153 | $this->searchCriteriaBuilder->addFilters(
154 | [
155 | $this->filterBuilder
156 | ->setField('payment_id')
157 | ->setValue($payment->getId())
158 | ->create(),
159 | ]
160 | );
161 |
162 | $this->searchCriteriaBuilder->addFilters(
163 | [
164 | $this->filterBuilder
165 | ->setField('txn_type')
166 | ->setValue(TransactionInterface::TYPE_CAPTURE)
167 | ->create(),
168 | ]
169 | );
170 |
171 | $searchCriteria = $this->searchCriteriaBuilder->create();
172 | $count = $this->transactionRepository->getList($searchCriteria)->getTotalCount();
173 |
174 | return (boolean) $count;
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/Gateway/Response/VaultDetailsHandler.php:
--------------------------------------------------------------------------------
1 | paymentTokenFactory = $paymentTokenFactory;
59 | $this->paymentExtensionFactory = $paymentExtensionFactory;
60 | $this->config = $config;
61 | $this->subjectReader = $subjectReader;
62 | }
63 |
64 | /**
65 | * @inheritdoc
66 | */
67 | public function handle(array $handlingSubject, array $response)
68 | {
69 | $paymentDO = $this->subjectReader->readPayment($handlingSubject);
70 | $paymentIntent = $this->subjectReader->readPaymentIntent($response);
71 | $payment = $paymentDO->getPayment();
72 |
73 | // add vault payment token entity to extension attributes
74 | $paymentToken = $this->getVaultPaymentToken($paymentIntent);
75 | if (null !== $paymentToken) {
76 | $extensionAttributes = $this->getExtensionAttributes($payment);
77 | $extensionAttributes->setVaultPaymentToken($paymentToken);
78 | }
79 | }
80 |
81 | /**
82 | * Get vault payment token entity
83 | *
84 | * @param \Stripe\PaymentIntent $paymentIntent
85 | * @return PaymentTokenInterface|null
86 | */
87 | protected function getVaultPaymentToken(PaymentIntent $paymentIntent)
88 | {
89 | // Extract source id as token
90 | $token = $paymentIntent->payment_method;
91 | if (empty($token)) {
92 | return null;
93 | }
94 |
95 | $charge = $paymentIntent->charges->data[0];
96 | $card = $charge->payment_method_details->card;
97 |
98 | /** @var PaymentTokenInterface $paymentToken */
99 | $paymentToken = $this->paymentTokenFactory->create();
100 | $expirationDate = $this->getExpirationDate($card);
101 |
102 | $paymentToken->setTokenDetails($this->convertDetailsToJSON([
103 | 'tokenType' => TokenProvider::TOKEN_TYPE_SOURCE,
104 | 'type' => $this->getCreditCardType($card->brand),
105 | 'maskedCC' => $card->last4,
106 | 'expirationDate' => $expirationDate->format('m/Y'),
107 | ]));
108 |
109 | $expirationDate->add(new DateInterval('P1M'));
110 |
111 | $paymentToken->setGatewayToken($token);
112 | $paymentToken->setExpiresAt($expirationDate->format('Y-m-d 00:00:00'));
113 |
114 | return $paymentToken;
115 | }
116 |
117 | /**
118 | * @param object $card
119 | * @return \DateTime
120 | */
121 | private function getExpirationDate($card)
122 | {
123 | return new DateTime(
124 | $card->exp_year
125 | . '-'
126 | . $card->exp_month
127 | . '-'
128 | . '01'
129 | . ' '
130 | . '00:00:00',
131 | new DateTimeZone('UTC')
132 | );
133 | }
134 |
135 | /**
136 | * Convert payment token details to JSON
137 | * @param array $details
138 | * @return string
139 | */
140 | private function convertDetailsToJSON($details)
141 | {
142 | $json = json_encode($details);
143 | return $json ? $json : '{}';
144 | }
145 |
146 | /**
147 | * Get type of credit card mapped from Stripe
148 | *
149 | * @param string $type
150 | * @return array
151 | */
152 | private function getCreditCardType($type)
153 | {
154 | $replaced = str_replace(' ', '-', strtolower($type));
155 | $mapper = $this->config->getCctypesMapper();
156 |
157 | return $mapper[$replaced];
158 | }
159 |
160 | /**
161 | * Get payment extension attributes
162 | * @param InfoInterface $payment
163 | * @return OrderPaymentExtensionInterface
164 | */
165 | private function getExtensionAttributes(InfoInterface $payment)
166 | {
167 | $extensionAttributes = $payment->getExtensionAttributes();
168 | if (null === $extensionAttributes) {
169 | $extensionAttributes = $this->paymentExtensionFactory->create();
170 | $payment->setExtensionAttributes($extensionAttributes);
171 | }
172 | return $extensionAttributes;
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Request/CaptureDataBuilderTest.php:
--------------------------------------------------------------------------------
1 | paymentDO = $this->getMockForAbstractClass(PaymentDataObjectInterface::class);
45 | $this->paymentMock = $this->getMockBuilder(Payment::class)
46 | ->disableOriginalConstructor()
47 | ->getMock();
48 | $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class)
49 | ->disableOriginalConstructor()
50 | ->getMock();
51 | $this->orderMock = $this->getMockForAbstractClass(OrderAdapterInterface::class);
52 |
53 | $this->builder = new CaptureDataBuilder(
54 | new AmountProvider(),
55 | $this->subjectReaderMock
56 | );
57 | }
58 |
59 | /**
60 | * @covers \Aune\Stripe\Gateway\Request\CaptureDataBuilder::build
61 | *
62 | * @expectedException \InvalidArgumentException
63 | */
64 | public function testBuildReadPaymentException()
65 | {
66 | $buildSubject = [];
67 |
68 | $this->subjectReaderMock->expects(self::once())
69 | ->method('readPayment')
70 | ->with($buildSubject)
71 | ->willThrowException(new \InvalidArgumentException());
72 |
73 | $this->builder->build($buildSubject);
74 | }
75 |
76 | /**
77 | * @covers \Aune\Stripe\Gateway\Request\CaptureDataBuilder::build
78 | */
79 | public function testBuildFullCapture()
80 | {
81 | $paymentIntent = rand();
82 | $expectedResult = [
83 | CaptureDataBuilder::PAYMENT_INTENT => $paymentIntent,
84 | CaptureDataBuilder::AMOUNT_TO_CAPTURE => null,
85 | ];
86 |
87 | $buildSubject = [
88 | 'payment' => $this->paymentDO,
89 | ];
90 |
91 | $this->paymentDO->expects(static::once())
92 | ->method('getPayment')
93 | ->willReturn($this->paymentMock);
94 |
95 | $this->subjectReaderMock->expects(self::once())
96 | ->method('readPayment')
97 | ->with($buildSubject)
98 | ->willReturn($this->paymentDO);
99 |
100 | $this->subjectReaderMock->expects(self::once())
101 | ->method('readAmount')
102 | ->with($buildSubject)
103 | ->willReturn(null);
104 |
105 | $this->paymentDO->expects(static::once())
106 | ->method('getOrder')
107 | ->willReturn($this->orderMock);
108 |
109 | $this->paymentMock->expects(static::once())
110 | ->method('getAdditionalInformation')
111 | ->with(DataAssignObserver::PAYMENT_INTENT)
112 | ->willReturn($paymentIntent);
113 |
114 | $this->orderMock->expects(static::once())
115 | ->method('getCurrencyCode')
116 | ->willReturn('EUR');
117 |
118 | static::assertEquals(
119 | $expectedResult,
120 | $this->builder->build($buildSubject)
121 | );
122 | }
123 |
124 | /**
125 | * @covers \Aune\Stripe\Gateway\Request\CaptureDataBuilder::build
126 | */
127 | public function testBuildPartialCapture()
128 | {
129 | $paymentIntent = rand();
130 | $expectedResult = [
131 | CaptureDataBuilder::PAYMENT_INTENT => $paymentIntent,
132 | CaptureDataBuilder::AMOUNT_TO_CAPTURE => 1000,
133 | ];
134 |
135 | $buildSubject = [
136 | 'payment' => $this->paymentDO,
137 | ];
138 |
139 | $this->paymentDO->expects(static::once())
140 | ->method('getPayment')
141 | ->willReturn($this->paymentMock);
142 |
143 | $this->subjectReaderMock->expects(self::once())
144 | ->method('readPayment')
145 | ->with($buildSubject)
146 | ->willReturn($this->paymentDO);
147 |
148 | $this->subjectReaderMock->expects(self::once())
149 | ->method('readAmount')
150 | ->with($buildSubject)
151 | ->willReturn(10.00);
152 |
153 | $this->paymentDO->expects(static::once())
154 | ->method('getOrder')
155 | ->willReturn($this->orderMock);
156 |
157 | $this->paymentMock->expects(static::once())
158 | ->method('getAdditionalInformation')
159 | ->with(DataAssignObserver::PAYMENT_INTENT)
160 | ->willReturn($paymentIntent);
161 |
162 | $this->orderMock->expects(static::once())
163 | ->method('getCurrencyCode')
164 | ->willReturn('EUR');
165 |
166 | static::assertEquals(
167 | $expectedResult,
168 | $this->builder->build($buildSubject)
169 | );
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/Test/Unit/Block/Customer/CardRendererTest.php:
--------------------------------------------------------------------------------
1 | objectManager = new ObjectManager($this);
45 |
46 | $this->paymentToken = $this->getMockForAbstractClass(PaymentTokenInterface::class);
47 |
48 | $this->iconsProvider = $this->getMockBuilder(CcConfigProvider::class)
49 | ->disableOriginalConstructor()
50 | ->getMock();
51 |
52 | $this->block = $this->objectManager->getObject(CardRenderer::class, [
53 | 'iconsProvider' => $this->iconsProvider,
54 | ]);
55 | }
56 |
57 | /**
58 | * @covers \Aune\Stripe\Block\Customer\CardRenderer::canRender
59 | */
60 | public function testCanRenderTrue()
61 | {
62 | $this->paymentToken->expects(static::once())
63 | ->method('getPaymentMethodCode')
64 | ->willReturn(ConfigProvider::CODE);
65 |
66 | static::assertEquals(true, $this->block->canRender($this->paymentToken));
67 | }
68 |
69 | /**
70 | * @covers \Aune\Stripe\Block\Customer\CardRenderer::canRender
71 | */
72 | public function testCanRenderFalse()
73 | {
74 | $this->paymentToken->expects(static::once())
75 | ->method('getPaymentMethodCode')
76 | ->willReturn(rand());
77 |
78 | static::assertEquals(false, $this->block->canRender($this->paymentToken));
79 | }
80 |
81 | /**
82 | * @covers \Aune\Stripe\Block\Customer\CardRenderer::getNumberLast4Digits
83 | */
84 | public function testGetNumberLast4Digits()
85 | {
86 | $this->paymentToken->expects(static::once())
87 | ->method('getTokenDetails')
88 | ->willReturn($this->getTokenDetails());
89 |
90 | $this->block->render($this->paymentToken);
91 |
92 | static::assertEquals(self::TOKEN_MASKED_CC, $this->block->getNumberLast4Digits());
93 | }
94 |
95 | /**
96 | * @covers \Aune\Stripe\Block\Customer\CardRenderer::getExpDate
97 | */
98 | public function testGetExpDate()
99 | {
100 | $this->paymentToken->expects(static::once())
101 | ->method('getTokenDetails')
102 | ->willReturn($this->getTokenDetails());
103 |
104 | $this->block->render($this->paymentToken);
105 |
106 | static::assertEquals(self::TOKEN_EXPIRATION_DATE, $this->block->getExpDate());
107 | }
108 |
109 | /**
110 | * @covers \Aune\Stripe\Block\Customer\CardRenderer::getIconUrl
111 | */
112 | public function testGetIconUrl()
113 | {
114 | $this->iconsProvider->expects(static::atLeastOnce())
115 | ->method('getIcons')
116 | ->willReturn($this->getIcons());
117 |
118 | $this->paymentToken->expects(static::once())
119 | ->method('getTokenDetails')
120 | ->willReturn($this->getTokenDetails());
121 |
122 | $this->block->render($this->paymentToken);
123 |
124 | static::assertEquals(self::ICON_URL, $this->block->getIconUrl());
125 | }
126 |
127 | /**
128 | * @covers \Aune\Stripe\Block\Customer\CardRenderer::getIconWidth
129 | */
130 | public function testGetIconWidth()
131 | {
132 | $this->iconsProvider->expects(static::atLeastOnce())
133 | ->method('getIcons')
134 | ->willReturn($this->getIcons());
135 |
136 | $this->paymentToken->expects(static::once())
137 | ->method('getTokenDetails')
138 | ->willReturn($this->getTokenDetails());
139 |
140 | $this->block->render($this->paymentToken);
141 |
142 | static::assertEquals(self::ICON_WIDTH, $this->block->getIconWidth());
143 | }
144 |
145 | /**
146 | * @covers \Aune\Stripe\Block\Customer\CardRenderer::getIconHeight
147 | */
148 | public function testGetIconHeight()
149 | {
150 | $this->iconsProvider->expects(static::atLeastOnce())
151 | ->method('getIcons')
152 | ->willReturn($this->getIcons());
153 |
154 | $this->paymentToken->expects(static::once())
155 | ->method('getTokenDetails')
156 | ->willReturn($this->getTokenDetails());
157 |
158 | $this->block->render($this->paymentToken);
159 |
160 | static::assertEquals(self::ICON_HEIGHT, $this->block->getIconHeight());
161 | }
162 |
163 | /**
164 | * Return json encoded mock token details
165 | */
166 | protected function getTokenDetails()
167 | {
168 | return json_encode([
169 | 'maskedCC' => self::TOKEN_MASKED_CC,
170 | 'expirationDate' => self::TOKEN_EXPIRATION_DATE,
171 | 'type' => self::TOKEN_TYPE,
172 | ]);
173 | }
174 |
175 | /**
176 | * Return json encoded mock token icon
177 | */
178 | protected function getIcons()
179 | {
180 | return [
181 | self::TOKEN_TYPE => [
182 | 'url' => self::ICON_URL,
183 | 'width' => self::ICON_WIDTH,
184 | 'height' => self::ICON_HEIGHT,
185 | ]
186 | ];
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Response/VaultDetailsHandlerTest.php:
--------------------------------------------------------------------------------
1 | paymentTokenFactory = $this->getMockBuilder(PaymentTokenInterfaceFactory::class)
57 | ->disableOriginalConstructor()
58 | ->getMock();
59 |
60 | $this->paymentExtensionFactory = $this->getMockBuilder(OrderPaymentExtensionInterfaceFactory::class)
61 | ->disableOriginalConstructor()
62 | ->setMethods(['create'])
63 | ->getMock();
64 |
65 | $this->config = $this->getMockBuilder(Config::class)
66 | ->disableOriginalConstructor()
67 | ->getMock();
68 |
69 | $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class)
70 | ->disableOriginalConstructor()
71 | ->getMock();
72 |
73 | $this->handler = new VaultDetailsHandler(
74 | $this->paymentTokenFactory,
75 | $this->paymentExtensionFactory,
76 | $this->config,
77 | $this->subjectReaderMock
78 | );
79 | }
80 |
81 | /**
82 | * @covers \Aune\Stripe\Gateway\Response\VaultDetailsHandler::handle
83 | */
84 | public function testHandle()
85 | {
86 | $paymentData = $this->getPaymentDataObjectMock();
87 | $paymentIntent = $this->getStripePaymentIntent();
88 |
89 | $subject = ['payment' => $paymentData];
90 | $response = ['object' => $paymentIntent];
91 |
92 | $this->subjectReaderMock->expects(self::once())
93 | ->method('readPayment')
94 | ->with($subject)
95 | ->willReturn($paymentData);
96 |
97 | $this->subjectReaderMock->expects(self::once())
98 | ->method('readPaymentIntent')
99 | ->with($response)
100 | ->willReturn($paymentIntent);
101 |
102 | $paymentToken = $this->getMockBuilder(PaymentTokenInterface::class)
103 | ->disableOriginalConstructor()
104 | ->getMock();
105 |
106 | $this->paymentTokenFactory->expects(self::once())
107 | ->method('create')
108 | ->willReturn($paymentToken);
109 |
110 | $paymentExtension = $this->getMockBuilder(OrderPaymentExtensionInterface::class)
111 | ->disableOriginalConstructor()
112 | ->setMethods(['setVaultPaymentToken'])
113 | ->getMock();
114 |
115 | $this->paymentExtensionFactory->expects(self::once())
116 | ->method('create')
117 | ->willReturn($paymentExtension);
118 |
119 | $paymentExtension->expects(self::once())
120 | ->method('setVaultPaymentToken')
121 | ->with($paymentToken);
122 |
123 | $this->config->expects(self::once())
124 | ->method('getCctypesMapper')
125 | ->willReturn(['visa' => 'VI']);
126 |
127 | $paymentToken->expects(self::once())
128 | ->method('setTokenDetails')
129 | ->with('{"tokenType":"source","type":"VI","maskedCC":1234,"expirationDate":"07\/2029"}');
130 |
131 | $paymentToken->expects(self::once())
132 | ->method('setGatewayToken')
133 | ->with(self::PAYMENT_METHOD_ID);
134 |
135 | $paymentToken->expects(self::once())
136 | ->method('setExpiresAt')
137 | ->with('2029-08-01 00:00:00');
138 |
139 | $this->handler->handle($subject, $response);
140 | }
141 |
142 | /**
143 | * Create mock for payment data object and order payment
144 | *
145 | * @return \PHPUnit_Framework_MockObject_MockObject
146 | */
147 | private function getPaymentDataObjectMock()
148 | {
149 | $this->payment = $this->getMockBuilder(Payment::class)
150 | ->disableOriginalConstructor()
151 | ->getMock();
152 |
153 | $mock = $this->getMockBuilder(PaymentDataObject::class)
154 | ->setMethods(['getPayment'])
155 | ->disableOriginalConstructor()
156 | ->getMock();
157 |
158 | $mock->expects(static::once())
159 | ->method('getPayment')
160 | ->willReturn($this->payment);
161 |
162 | return $mock;
163 | }
164 |
165 | /**
166 | * Create Stripe Payment Intent
167 | *
168 | * @return \Stripe\PaymentIntent
169 | */
170 | private function getStripePaymentIntent()
171 | {
172 | $attributes = [
173 | 'object' => 'payment_intent',
174 | 'payment_method' => self::PAYMENT_METHOD_ID,
175 | 'charges' => [
176 | 'object' => 'list',
177 | 'data' => [[
178 | 'object' => 'charge',
179 | 'payment_method_details' => [
180 | 'card' => [
181 | 'brand' => 'Visa',
182 | 'exp_month' => 07,
183 | 'exp_year' => 29,
184 | 'last4' => 1234,
185 | ]
186 | ]
187 | ]]
188 | ]
189 | ];
190 |
191 | return \Stripe\Util\Util::convertToStripeObject($attributes, []);
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/Test/Unit/Gateway/Request/RefundDataBuilderTest.php:
--------------------------------------------------------------------------------
1 | paymentDO = $this->getMockForAbstractClass(PaymentDataObjectInterface::class);
49 | $this->paymentMock = $this->getMockBuilder(Payment::class)
50 | ->disableOriginalConstructor()
51 | ->getMock();
52 | $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class)
53 | ->disableOriginalConstructor()
54 | ->getMock();
55 | $this->orderMock = $this->getMockForAbstractClass(OrderAdapterInterface::class);
56 |
57 | $this->builder = new RefundDataBuilder(
58 | new AmountProvider(),
59 | $this->subjectReaderMock
60 | );
61 | }
62 |
63 | /**
64 | * @covers \Aune\Stripe\Gateway\Request\RefundDataBuilder::build
65 | *
66 | * @expectedException \InvalidArgumentException
67 | */
68 | public function testBuildReadPaymentException()
69 | {
70 | $buildSubject = [];
71 |
72 | $this->subjectReaderMock->expects(self::once())
73 | ->method('readPayment')
74 | ->with($buildSubject)
75 | ->willThrowException(new \InvalidArgumentException());
76 |
77 | $this->builder->build($buildSubject);
78 | }
79 |
80 | /**
81 | * @covers \Aune\Stripe\Gateway\Request\RefundDataBuilder::build
82 | */
83 | public function testBuildReadAmountException()
84 | {
85 | $paymentIntent = rand();
86 | $expectedResult = [
87 | RefundDataBuilder::PAYMENT_INTENT => $paymentIntent,
88 | RefundDataBuilder::AMOUNT => null,
89 | ];
90 |
91 | $buildSubject = [
92 | 'payment' => $this->paymentDO,
93 | ];
94 |
95 | $this->subjectReaderMock->expects(self::once())
96 | ->method('readPayment')
97 | ->with($buildSubject)
98 | ->willReturn($this->paymentDO);
99 |
100 | $this->subjectReaderMock->expects(self::once())
101 | ->method('readAmount')
102 | ->with($buildSubject)
103 | ->willThrowException(new \InvalidArgumentException());
104 |
105 | $this->paymentDO->expects(static::once())
106 | ->method('getOrder')
107 | ->willReturn($this->orderMock);
108 |
109 | $this->paymentDO->expects(static::once())
110 | ->method('getPayment')
111 | ->willReturn($this->paymentMock);
112 |
113 | $this->paymentMock->expects(static::once())
114 | ->method('getLastTransId')
115 | ->willReturn($paymentIntent);
116 |
117 | $this->orderMock->expects(static::once())
118 | ->method('getCurrencyCode')
119 | ->willReturn(self::CURRENCY_CODE_DECIMAL);
120 |
121 | static::assertEquals(
122 | $expectedResult,
123 | $this->builder->build($buildSubject)
124 | );
125 | }
126 |
127 | /**
128 | * @covers \Aune\Stripe\Gateway\Request\RefundDataBuilder::build
129 | */
130 | public function testBuildDecimal()
131 | {
132 | $paymentIntent = rand();
133 | $expectedResult = [
134 | RefundDataBuilder::PAYMENT_INTENT => $paymentIntent,
135 | RefundDataBuilder::AMOUNT => 1000,
136 | ];
137 |
138 | $buildSubject = [
139 | 'payment' => $this->paymentDO,
140 | ];
141 |
142 | $this->paymentDO->expects(static::once())
143 | ->method('getPayment')
144 | ->willReturn($this->paymentMock);
145 |
146 | $this->subjectReaderMock->expects(self::once())
147 | ->method('readPayment')
148 | ->with($buildSubject)
149 | ->willReturn($this->paymentDO);
150 |
151 | $this->subjectReaderMock->expects(self::once())
152 | ->method('readAmount')
153 | ->with($buildSubject)
154 | ->willReturn(10.00);
155 |
156 | $this->paymentDO->expects(static::once())
157 | ->method('getOrder')
158 | ->willReturn($this->orderMock);
159 |
160 | $this->paymentMock->expects(static::once())
161 | ->method('getLastTransId')
162 | ->willReturn($paymentIntent);
163 |
164 | $this->orderMock->expects(static::once())
165 | ->method('getCurrencyCode')
166 | ->willReturn(self::CURRENCY_CODE_DECIMAL);
167 |
168 | static::assertEquals(
169 | $expectedResult,
170 | $this->builder->build($buildSubject)
171 | );
172 | }
173 |
174 | /**
175 | * @covers \Aune\Stripe\Gateway\Request\RefundDataBuilder::build
176 | */
177 | public function testBuildZeroDecimal()
178 | {
179 | $paymentIntent = rand();
180 |
181 | $expectedResult = [
182 | RefundDataBuilder::PAYMENT_INTENT => $paymentIntent,
183 | RefundDataBuilder::AMOUNT => 1000,
184 | ];
185 |
186 | $buildSubject = [
187 | 'payment' => $this->paymentDO,
188 | ];
189 |
190 | $this->paymentDO->expects(static::once())
191 | ->method('getPayment')
192 | ->willReturn($this->paymentMock);
193 |
194 | $this->subjectReaderMock->expects(self::once())
195 | ->method('readPayment')
196 | ->with($buildSubject)
197 | ->willReturn($this->paymentDO);
198 |
199 | $this->subjectReaderMock->expects(self::once())
200 | ->method('readAmount')
201 | ->with($buildSubject)
202 | ->willReturn(1000);
203 |
204 | $this->paymentDO->expects(static::once())
205 | ->method('getOrder')
206 | ->willReturn($this->orderMock);
207 |
208 | $this->paymentMock->expects(static::once())
209 | ->method('getLastTransId')
210 | ->willReturn($paymentIntent);
211 |
212 | $this->orderMock->expects(static::once())
213 | ->method('getCurrencyCode')
214 | ->willReturn(self::CURRENCY_CODE_ZERO_DECIMAL);
215 |
216 | static::assertEquals(
217 | $expectedResult,
218 | $this->builder->build($buildSubject)
219 | );
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/view/frontend/web/js/view/payment/method-renderer/cc-form.js:
--------------------------------------------------------------------------------
1 | /*browser:true*/
2 | /*global define*/
3 | /*global Stripe*/
4 | define(
5 | [
6 | 'underscore',
7 | 'jquery',
8 | 'ko',
9 | 'Magento_Checkout/js/view/payment/default',
10 | 'Magento_Checkout/js/model/quote',
11 | 'Magento_Vault/js/view/payment/vault-enabler',
12 | 'Magento_Ui/js/model/messageList',
13 | 'mage/translate'
14 | ],
15 | function (
16 | _,
17 | $,
18 | ko,
19 | Component,
20 | quote,
21 | VaultEnabler,
22 | messageList,
23 | $t
24 | ) {
25 | 'use strict';
26 |
27 | return Component.extend({
28 | defaults: {
29 | template: 'Aune_Stripe/payment/cc-form',
30 | stripe: null,
31 | cardElement: null,
32 | paymentIntent: null,
33 | paymentIntentUrl: null,
34 | fieldErrorMessages: {
35 | card: ko.observable(false),
36 | expiry: ko.observable(false),
37 | cvc: ko.observable(false)
38 | }
39 | },
40 |
41 | /**
42 | * @returns {exports.initialize}
43 | */
44 | initialize: function () {
45 | this._super();
46 | this.vaultEnabler = new VaultEnabler();
47 | this.vaultEnabler.setPaymentCode(this.getVaultCode());
48 |
49 | return this;
50 | },
51 |
52 | /**
53 | * Initialize Stripe element
54 | */
55 | initStripe: function () {
56 | var config = window.checkoutConfig.payment[this.getCode()];
57 | if (!config) {
58 | return;
59 | }
60 |
61 | this.paymentIntentUrl = config.paymentIntentUrl;
62 |
63 | var self = this;
64 | require([config.sdkUrl], function () {
65 | // Initialise Stripe
66 | window.stripe = Stripe(config.publishableKey);
67 |
68 | // Initialise elements
69 | var elements = window.stripe.elements();
70 | self.cardElement = elements.create('cardNumber');
71 | self.cardElement.mount('#' + self.getCode() + '_cc_number');
72 | self.cardElement.on('change', self.onFieldChange('card'));
73 |
74 | var cardExpiry = elements.create('cardExpiry');
75 | cardExpiry.mount('#' + self.getCode() + '_expiry');
76 | cardExpiry.on('change', self.onFieldChange('expiry'));
77 |
78 | var cardCvc = elements.create('cardCvc');
79 | cardCvc.mount('#' + self.getCode() + '_cc_cvc');
80 | cardCvc.on('change', self.onFieldChange('cvc'));
81 | });
82 | },
83 |
84 | /**
85 | * Check if payment is active
86 | *
87 | * @returns {Boolean}
88 | */
89 | isActive: function () {
90 | return this.getCode() === this.isChecked();
91 | },
92 |
93 | /**
94 | * Return field's error message observable
95 | */
96 | getErrorMessageObserver: function (field) {
97 | return this.fieldErrorMessages[field];
98 | },
99 |
100 | /**
101 | * Return field change event handler
102 | */
103 | onFieldChange: function (fieldName) {
104 | var errorMessage = this.fieldErrorMessages[fieldName];
105 | return function (event) {
106 | errorMessage(
107 | event.error ? event.error.message : false
108 | );
109 | };
110 | },
111 |
112 | /**
113 | * Get data
114 | *
115 | * @returns {Object}
116 | */
117 | getData: function () {
118 | var data = {
119 | 'method': this.item.method,
120 | 'additional_data': {
121 | 'payment_intent': this.paymentIntent
122 | }
123 | };
124 |
125 | this.vaultEnabler.visitAdditionalData(data);
126 |
127 | return data;
128 | },
129 |
130 | /**
131 | * Set payment intent
132 | *
133 | * @param {String} paymentIntent
134 | */
135 | setPaymentIntent: function (paymentIntent) {
136 | this.paymentIntent = paymentIntent;
137 | },
138 |
139 | /**
140 | * Place the order
141 | *
142 | * @param {Object} data
143 | */
144 | placeOrderClick: function () {
145 | if (!window.stripe || !this.cardElement) {
146 | console.err('Stripe or CardElement not found');
147 | return;
148 | }
149 |
150 | var data = {
151 | payment_method: {
152 | card: this.cardElement
153 | }
154 | };
155 |
156 | var billingAddress = quote.billingAddress();
157 | if (billingAddress) {
158 | data.payment_method.billing_details = {
159 | name: billingAddress.firstname + ' ' + billingAddress.lastname,
160 | phone: billingAddress.telephone,
161 | address: {
162 | line1: billingAddress.street[0],
163 | line2: billingAddress.street.length > 1 ? billingAddress.street[1] : null,
164 | city: billingAddress.city,
165 | state: billingAddress.region,
166 | postal_code: billingAddress.postcode,
167 | country: billingAddress.countryId,
168 | }
169 | };
170 | }
171 |
172 | var self = this;
173 | $.get(this.paymentIntentUrl, {}, function(response) {
174 |
175 | if (!response || !response.paymentIntent || !response.paymentIntent.clientSecret) {
176 | messageList.addErrorMessage({
177 | message: $t('An error occurred generating the payment intent.')
178 | });
179 | return;
180 | }
181 |
182 | window.stripe.confirmCardPayment(response.paymentIntent.clientSecret, data)
183 | .then(function (result) {
184 | if (result.error) {
185 | var message = result.error.message;
186 | if (result.error.type == 'validation_error') {
187 | message = $t('Please verify you card information.');
188 | }
189 | messageList.addErrorMessage({
190 | message: message
191 | });
192 | return;
193 | }
194 |
195 | self.setPaymentIntent(result.paymentIntent.id);
196 | self.placeOrder();
197 | });
198 | });
199 | },
200 |
201 | /**
202 | * @returns {Bool}
203 | */
204 | isVaultEnabled: function () {
205 | return this.vaultEnabler.isVaultEnabled();
206 | },
207 |
208 | /**
209 | * @returns {String}
210 | */
211 | getVaultCode: function () {
212 | return window.checkoutConfig.payment[this.getCode()].ccVaultCode;
213 | }
214 | });
215 | }
216 | );
217 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Open Software License ("OSL") v. 3.0
3 |
4 | This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work:
5 |
6 | Licensed under the Open Software License version 3.0
7 |
8 | 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following:
9 |
10 | 1. to reproduce the Original Work in copies, either alone or as part of a collective work;
11 |
12 | 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work;
13 |
14 | 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License;
15 |
16 | 4. to perform the Original Work publicly; and
17 |
18 | 5. to display the Original Work publicly.
19 |
20 | 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works.
21 |
22 | 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work.
23 |
24 | 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license.
25 |
26 | 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c).
27 |
28 | 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.
29 |
30 | 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer.
31 |
32 | 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation.
33 |
34 | 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c).
35 |
36 | 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.
37 |
38 | 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License.
39 |
40 | 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.
41 |
42 | 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
43 |
44 | 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
45 |
46 | 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.
47 |
48 | 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.
49 |
--------------------------------------------------------------------------------