Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2577,6 +2577,7 @@ merchant_session = starkbank.merchantsession.create({
],
"expiration": 3600,
"challengeMode": "disabled",
"confirmationMode": "automatic",
"tags": [
"your-tags"
]
Expand All @@ -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.

Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion starkbank/merchantpurchase/__init__.py
Original file line number Diff line number Diff line change
@@ -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
6 changes: 5 additions & 1 deletion starkbank/merchantpurchase/__merchantpurchase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
4 changes: 3 additions & 1 deletion starkbank/merchantsession/__merchantsession.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"}

Expand Down
3 changes: 2 additions & 1 deletion starkbank/merchantsession/__purchase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
9 changes: 9 additions & 0 deletions tests/sdk/test_merchant_purchase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

9 changes: 9 additions & 0 deletions tests/sdk/test_merchant_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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):
Expand Down
1 change: 1 addition & 0 deletions tests/utils/merchantPurchase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
)


Expand Down
19 changes: 19 additions & 0 deletions tests/utils/merchantSession.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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,
Expand Down
Loading