From e82170e6afc033bcb6bcb7f19405c1fbda429932 Mon Sep 17 00:00:00 2001 From: Paulo Souza Date: Tue, 16 Jun 2026 17:20:40 -0300 Subject: [PATCH] Added pre-authorization --- CHANGELOG.md | 3 ++ README.md | 33 +++++++++++++++++++ starkbank/merchantpurchase/__init__.py | 2 +- .../merchantpurchase/__merchantpurchase.py | 6 +++- .../merchantsession/__merchantsession.py | 4 ++- starkbank/merchantsession/__purchase.py | 3 +- tests/sdk/test_merchant_purchase.py | 9 +++++ tests/sdk/test_merchant_session.py | 9 +++++ tests/utils/merchantPurchase.py | 1 + tests/utils/merchantSession.py | 19 +++++++++++ 10 files changed, 85 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eba42ea0..b888e493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ Given a version number MAJOR.MINOR.PATCH, increment: ## [Unreleased] +### Added +- confirmation_mode attribute to MerchantSession, MerchantPurchase and MerchantSession.Purchase resources +- delete method to MerchantPurchase resource ## [2.34.0] - 2026-05-07 ### Changed diff --git a/README.md b/README.md index ee8ee0c7..790af3c6 100644 --- a/README.md +++ b/README.md @@ -2577,6 +2577,7 @@ merchant_session = starkbank.merchantsession.create({ ], "expiration": 3600, "challengeMode": "disabled", + "confirmationMode": "automatic", "tags": [ "your-tags" ] @@ -2585,6 +2586,8 @@ merchant_session = starkbank.merchantsession.create({ print(merchant_session) ``` +Set `confirmationMode` to `"manual"` to create a pre-authorization session: the resulting purchase is approved but only captured once you explicitly confirm it (see [Confirm a MerchantPurchase](#confirm-a-merchantpurchase)). Manual confirmation is available only for credit funding types. + You can create a MerchantPurchase through a MerchantSession by passing its UUID. **Note**: This method must be implemented in your front-end to ensure that sensitive card data does not pass through the back-end of the integration. @@ -2683,6 +2686,8 @@ merchant_purchase = starkbank.merchantpurchase.create( ) ``` +You may also pass `confirmation_mode="manual"` here to create a pre-authorization (credit only); the purchase is captured later via [Confirm a MerchantPurchase](#confirm-a-merchantpurchase). It defaults to `"automatic"`. + ## Query MerchantPurchases Get a list of merchant purchases in chunks of at most 100. If you need smaller chunks, use the limit parameter. @@ -2706,6 +2711,34 @@ merchant_purchase = starkbank.merchantpurchase.get('5950134772826112') print(merchant_purchase) ``` +## Confirm a MerchantPurchase + +When a purchase is created in `manual` confirmation mode (pre-authorization), it stays approved but uncaptured until you confirm it. Confirm it by updating its status to `confirmed`: + +```python +import starkbank + +merchant_purchase = starkbank.merchantpurchase.update( + id="5950134772826112", + status="confirmed", + amount=10000, +) +print(merchant_purchase) +``` + +You can also use `update` to reverse a confirmed purchase (`status="reversed"`) or cancel an approved one (`status="canceled"`). + +## Cancel a MerchantPurchase + +Cancel an approved purchase or fully reverse a confirmed one. The operation is inferred from the purchase's current status: + +```python +import starkbank + +merchant_purchase = starkbank.merchantpurchase.delete('5950134772826112') +print(merchant_purchase) +``` + ## Query MerchantInstallments Get a list of merchant installments in chunks of at most 100. If you need smaller chunks, use the limit parameter. diff --git a/starkbank/merchantpurchase/__init__.py b/starkbank/merchantpurchase/__init__.py index bb9e9e82..f5115f80 100644 --- a/starkbank/merchantpurchase/__init__.py +++ b/starkbank/merchantpurchase/__init__.py @@ -1,3 +1,3 @@ -from .__merchantpurchase import get, query, page, create, update +from .__merchantpurchase import get, query, page, create, update, delete from .log.__log import Log from . import log diff --git a/starkbank/merchantpurchase/__merchantpurchase.py b/starkbank/merchantpurchase/__merchantpurchase.py index 30f15e7b..ff116b03 100644 --- a/starkbank/merchantpurchase/__merchantpurchase.py +++ b/starkbank/merchantpurchase/__merchantpurchase.py @@ -13,7 +13,7 @@ def __init__(self, amount, card_id, funding_type, installment_count, id=None, ca billing_country_code=None, billing_city=None,billing_state_code=None, billing_street_line_1=None, billing_street_line_2=None, billing_zip_code=None, metadata=None, card_ending=None, soft_descriptor=None, challenge_mode=None, challenge_url=None, created=None, currency_code=None, end_to_end_id=None, - fee=None, network=None, source=None, status=None, tags=None, updated=None): + fee=None, network=None, source=None, status=None, tags=None, updated=None, confirmation_mode=None): Resource.__init__(self, id=id) self.amount = amount @@ -45,6 +45,7 @@ def __init__(self, amount, card_id, funding_type, installment_count, id=None, ca self.source = source self.status = status self.tags = tags + self.confirmation_mode = confirmation_mode self.created = check_datetime(created) self.updated = check_datetime(updated) @@ -96,3 +97,6 @@ def update(id, status=None, amount=None, user=None): } return rest.patch_id(resource=_resource, id=id, user=user, payload=payload) + +def delete(id, user=None): + return rest.delete_id(resource=_resource, id=id, user=user) diff --git a/starkbank/merchantsession/__merchantsession.py b/starkbank/merchantsession/__merchantsession.py index df0b7bab..0513987c 100644 --- a/starkbank/merchantsession/__merchantsession.py +++ b/starkbank/merchantsession/__merchantsession.py @@ -13,7 +13,8 @@ class MerchantSession(Resource): """ def __init__(self, allowed_funding_types, allowed_installments, expiration, id=None, allowed_ips=None, - challenge_mode=None, created=None, status=None, tags=None, updated=None, uuid=None, holder_id=None, soft_descriptor=None): + challenge_mode=None, created=None, status=None, tags=None, updated=None, uuid=None, holder_id=None, + soft_descriptor=None, confirmation_mode=None): Resource.__init__(self, id=id) self.allowed_funding_types = allowed_funding_types @@ -28,6 +29,7 @@ def __init__(self, allowed_funding_types, allowed_installments, expiration, id=N self.uuid = uuid self.holder_id = holder_id self.soft_descriptor = soft_descriptor + self.confirmation_mode = confirmation_mode _resource = {"class": MerchantSession, "name": "MerchantSession"} diff --git a/starkbank/merchantsession/__purchase.py b/starkbank/merchantsession/__purchase.py index c6490074..12a880be 100644 --- a/starkbank/merchantsession/__purchase.py +++ b/starkbank/merchantsession/__purchase.py @@ -12,7 +12,7 @@ def __init__(self, amount, card_expiration, card_number, card_security_code, hol billing_city=None, billing_state_code=None, billing_street_line_1=None, billing_street_line_2=None, billing_zip_code=None, metadata=None, card_ending=None, card_id=None, challenge_mode=None, challenge_url=None, created=None, currency_code=None, end_to_end_id=None, fee=None, network=None, - soft_descriptor=None, source=None, status=None, tags=None, updated=None): + soft_descriptor=None, source=None, status=None, tags=None, updated=None, confirmation_mode=None): Resource.__init__(self, id=id) self.amount = amount @@ -44,6 +44,7 @@ def __init__(self, amount, card_expiration, card_number, card_security_code, hol self.source = source self.status = status self.tags = tags + self.confirmation_mode = confirmation_mode self.created = check_datetime(created) self.updated = check_datetime(updated) diff --git a/tests/sdk/test_merchant_purchase.py b/tests/sdk/test_merchant_purchase.py index 6c2245ed..104dd557 100644 --- a/tests/sdk/test_merchant_purchase.py +++ b/tests/sdk/test_merchant_purchase.py @@ -61,6 +61,15 @@ def test_success(self): self.assertIsInstance(merchant_purchase.id, str) +class TestMerchantPurchaseDelete(TestCase): + + def test_success(self): + merchant_purchases = starkbank.merchantpurchase.query(limit=1, status="approved") + for merchant_purchase in merchant_purchases: + merchant_purchase = starkbank.merchantpurchase.delete(merchant_purchase.id) + self.assertIsInstance(merchant_purchase.id, str) + + if __name__ == '__main__': main() diff --git a/tests/sdk/test_merchant_session.py b/tests/sdk/test_merchant_session.py index 87cc3efb..ce345b2c 100644 --- a/tests/sdk/test_merchant_session.py +++ b/tests/sdk/test_merchant_session.py @@ -2,6 +2,7 @@ from unittest import TestCase, main from tests.utils.user import exampleProject from tests.utils.merchantSession import generate_example_merchant_session_json, \ + generate_example_manual_confirmation_merchant_session_json, \ generate_example_merchant_session_purchase_challenge_mode_disabled_json, \ generate_example_merchant_session_purchase_challenge_mode_enabled_json @@ -17,6 +18,14 @@ def test_success(self): self.assertIsNotNone(merchant_session.id) +class TestMerchantSessionCreateManualConfirmation(TestCase): + + def test_success(self): + merchant_session_json = generate_example_manual_confirmation_merchant_session_json() + merchant_session = starkbank.merchantsession.create(merchant_session_json) + self.assertIsNotNone(merchant_session.id) + + class TestMerchantSessionQuery(TestCase): def test_success(self): diff --git a/tests/utils/merchantPurchase.py b/tests/utils/merchantPurchase.py index 68e8913d..d39b5991 100644 --- a/tests/utils/merchantPurchase.py +++ b/tests/utils/merchantPurchase.py @@ -58,6 +58,7 @@ def json_to_merchant_purchase(json_data): metadata=json_data.get("metadata"), card_id=json_data.get("cardId"), soft_descriptor=json_data.get("softDescriptor"), + confirmation_mode=json_data.get("confirmationMode"), ) diff --git a/tests/utils/merchantSession.py b/tests/utils/merchantSession.py index 0170763f..38747e7d 100644 --- a/tests/utils/merchantSession.py +++ b/tests/utils/merchantSession.py @@ -28,6 +28,7 @@ def json_to_merchant_session(json_data): allowed_funding_types=json_data.get("allowedFundingTypes"), allowed_installments=json_data.get("allowedInstallments"), challenge_mode=json_data.get("challengeMode"), + confirmation_mode=json_data.get("confirmationMode"), expiration=json_data.get("expiration"), tags=json_data.get("tags"), holder_id=json_data.get("holderId"), @@ -52,6 +53,24 @@ def generate_example_merchant_session_json(challengeMode): return deepcopy(json_to_merchant_session(merchant_session_json)) +def generate_example_manual_confirmation_merchant_session_json(): + merchant_session_json = { + "allowedFundingTypes": ["credit"], + "allowedInstallments": [ + {"totalAmount": 500, "count": 1}, + {"totalAmount": 1000, "count": 2}, + {"totalAmount": 6000, "count": 12}, + ], + "expiration": 3600, + "challengeMode": "disabled", + "confirmationMode": "manual", + "tags": ["yourTags"], + "holderId": "5656565665", + "softDescriptor": "softDescriptor", + } + return deepcopy(json_to_merchant_session(merchant_session_json)) + + def generate_example_merchant_session_purchase_challenge_mode_disabled_json(): merchant_session_purchase_json = { "amount": 6000,