diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a03f6fb..8dac562 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Setup Python - run: sudo add-apt-repository -y ppa:deadsnakes/ppa && sudo apt-get install -y python3.6 python3.7 python3.9 + run: sudo add-apt-repository -y ppa:deadsnakes/ppa && sudo apt-get install -y python3.6 python3.7 python3.9 python3.6-distutils python3.7-distutils python3.9-distutils - name: Install dependencies run: | diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 270b7cd..88b865b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,14 @@ Change Log ---------- +1.5.2 ++++++++++++++++++++++ + +**Features** + +- Add support for new 'Agreements Engine' parameters +- Add support for Apple Pay + 1.5.1 +++++++++++++++++++++ diff --git a/altapay/__init__.py b/altapay/__init__.py index bba1a9a..7ce749d 100644 --- a/altapay/__init__.py +++ b/altapay/__init__.py @@ -1,5 +1,5 @@ __title__ = 'altapay' -__version__ = '1.5.1' +__version__ = '1.5.2' __author__ = 'Coolshop.com' __license__ = 'MIT' __github_url__ = 'https://github.com/coolshop-com/AltaPay' diff --git a/altapay/card_wallet.py b/altapay/card_wallet.py new file mode 100644 index 0000000..f2baf6b --- /dev/null +++ b/altapay/card_wallet.py @@ -0,0 +1,81 @@ +from __future__ import absolute_import, unicode_literals + +import altapay.callback +from altapay.resource import Resource + + +class CardWallet(Resource): + def session(self, terminal, validation_url, domain): + """ + This is the step to receive Apple Pay session. + By invoking this method we will reach Apple Pay with Merchant key and + certificate to retrieve a session + which should be used to proceed with Apple Pay payment. + + Returned session object should be used in + session.completeMerchantValidation(JSON.parse(merchantSession)); + + :arg terminal: The title of your terminal which was configured + with Apple Pay + :arg validation_url: Validation URL which was passed from + requestSession ApplePayValidateMerchantEvent.validationURL + :arg domain: The domain from which you are initializing the request, + which requires to be verified. + Otherwise, the request will use the default domain specified + in the terminal. + """ + parameters = { + 'terminal': terminal, + 'validationURL': validation_url, + 'domain': domain + } + + response = self.api.post( + 'API/cardWallet/session', data=parameters)['APIResponse'] + + return altapay.callback.Callback.from_xml_callback(response) + + def authorize(self, provider_data, terminal, shop_orderid, amount, + currency, **kwargs): + """ + This step is required to process Apple Pay data. + By invoking this method we will decrypt data + with Processing Key and Authorize it against selected acquirer. + + Returned response is similar to callback xml. + + :arg provider_data: The string value of + ApplePayPaymentAuthorizedEvent.payment.token + as produced by JSON.stringify() + :arg terminal: The title of your terminal which was configured + with Apple Pay + :arg shop_orderid: The id of the order in your webshop. + This is what we will post back to you so you know which order + a given payment is associated with. + :arg amount: The amount of the payment in english notation (ex. 89.95) + :arg currency: The currency of the payment in ISO-4217 format. + Either the 3-digit numeric code, or the 3-letter character code + :arg kwargs: used for optional parameters + The optional parameters follow the same logic as + with eCommerce/API/createPaymentRequest. + For details, see the documentation for createPaymentRequest. + Note that you will need to use lists and dictionaries to map the + URL structures from the AltaPay documentation into these kwargs. + + :rtype: :py:class:`altapay.Callback` object. + """ + parameters = { + 'provider_data': provider_data, + 'terminal': terminal, + 'shop_orderid': shop_orderid, + 'amount': amount, + 'currency': currency, + } + + parameters.update(kwargs) + + response = self.api.post( + 'API/cardWallet/authorize', + data=parameters)['APIResponse'] + + return altapay.callback.Callback.from_xml_callback(response) diff --git a/altapay/transaction.py b/altapay/transaction.py index 40706e8..147c8c2 100644 --- a/altapay/transaction.py +++ b/altapay/transaction.py @@ -96,7 +96,9 @@ def charge_subscription(self, **kwargs): :rtype: :py:class:`altapay.Callback` object. """ parameters = { - 'transaction_id': self.transaction_id + 'agreement': { + 'id': self.transaction_id + } } parameters.update(kwargs) @@ -122,7 +124,9 @@ def reserve_subscription_charge(self, **kwargs): :rtype: :py:class:`altapay.Callback` object. """ parameters = { - 'transaction_id': self.transaction_id + 'agreement': { + 'id': self.transaction_id + } } parameters.update(kwargs) diff --git a/tests/integration/test_merchant_api.py b/tests/integration/test_merchant_api.py index f2f5ef4..a1c0f69 100644 --- a/tests/integration/test_merchant_api.py +++ b/tests/integration/test_merchant_api.py @@ -34,6 +34,23 @@ def test_create_payment_request(self): self.assertIn('url', payment) self.assertEqual(len(payment.url) > 0, True) + def test_create_payment_request_with_agreement(self): + payment = Payment(api=self.api) + params = { + 'terminal': altapay_test_terminal_name, + 'shop_orderid': generate_order_id(), + 'amount': 7.00, + 'currency': 'EUR', + 'type': 'subscription', + 'agreement': { + 'type': 'unscheduled', + 'unscheduled_type': 'incremental' + } + } + self.assertEqual(payment.create(**params), True) + self.assertIn('url', payment) + self.assertEqual(len(payment.url) > 0, True) + def test_create_moto_reservation(self): reservation = Reservation(api=self.api) date_today = date.today() @@ -51,6 +68,28 @@ def test_create_moto_reservation(self): self.assertEqual(reservation.create(**params), True) self.assertEqual(reservation.success, True) + def test_create_moto_reservation_with_agreement(self): + reservation = Reservation(api=self.api) + date_today = date.today() + params = { + 'terminal': altapay_test_terminal_name, + 'shop_orderid': generate_order_id(), + 'amount': '77', + 'currency': 'DKK', + 'cardnum': '4111000011110002', + 'emonth': date_today.month, + 'eyear': date_today.year + 1, + 'cvc': '123', + 'type': 'subscription', + 'agreement': { + 'type': 'unscheduled', + 'unscheduled_type': 'incremental' + } + } + + self.assertEqual(reservation.create(**params), True) + self.assertEqual(reservation.success, True) + def test_reservation_capture(self): reservation = Reservation(api=self.api) date_today = date.today() @@ -203,7 +242,10 @@ def test_charge_subscription(self): 'eyear': date_today.year + 1, 'cvc': '123', 'type': 'subscription', - 'agreement_type': 'recurring' + 'agreement': { + 'type': 'unscheduled', + 'unscheduled_type': 'incremental' + } } reservation.create(**params) # parse transaction from reservation response object @@ -212,7 +254,10 @@ def test_charge_subscription(self): transaction = Transaction.find(transaction_id, self.api) transaction_params = { - 'transaction_id': transaction_id, + 'agreement': { + 'id': transaction_id, + 'unscheduled_type': 'incremental' + }, 'reconciliation_identifier': order_id, 'amount': '21', 'currency': 'DKK' @@ -221,20 +266,24 @@ def test_charge_subscription(self): self.assertEqual(capture_result.result, "Success") - def test_reverse_subscription_charge(self): + def test_reserve_subscription_charge_with_agreement_and_capture(self): reservation = Reservation(api=self.api) date_today = date.today() + order_id = generate_order_id() params = { 'terminal': altapay_test_terminal_name, - 'shop_orderid': generate_order_id(), - 'amount': '25', + 'shop_orderid': order_id, + 'amount': '7777', 'currency': 'DKK', 'cardnum': '4111000011110002', 'emonth': date_today.month, 'eyear': date_today.year + 1, 'cvc': '123', 'type': 'subscription', - 'agreement_type': 'recurring' + 'agreement': { + 'type': 'unscheduled', + 'unscheduled_type': 'incremental' + } } reservation.create(**params) # parse transaction from reservation response object @@ -243,14 +292,31 @@ def test_reverse_subscription_charge(self): transaction = Transaction.find(transaction_id, self.api) transaction_params = { - 'transaction_id': transaction_id, - 'amount': '21', + 'agreement': { + 'id': transaction_id, + 'unscheduled_type': 'incremental', + }, + 'amount': '7777', 'currency': 'DKK' } resp = transaction.reserve_subscription_charge(**transaction_params) self.assertEqual(resp.result, "Success") + for t in resp.transactions(): + if t.auth_type == 'subscription_payment': + transaction_id = t.transaction_id + break + transaction = Transaction.find(transaction_id, self.api) + + c_t_params = { + 'transaction_id': transaction_id, + 'amount': 7777 + } + + # capture existing transaction with defined params + self.assertEqual(transaction.capture(**c_t_params).result, 'Success') + def test_funding_list(self): funding_list = FundingList(api=self.api) self.assertIsNotNone(funding_list.fundings)