Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a56994f
Refactor builder architecture and modernize core infrastructure for v…
buckaroo-shu Apr 15, 2026
8b10194
feat: add usage examples for common payment methods
buckaroo-shu Apr 15, 2026
acf98cf
refactor: simplify iDEAL payment example flow
buckaroo-shu Apr 15, 2026
ee3c85a
refactor: standardize codebase with Ruff, expand builders, and add co…
ShuCh3n Apr 23, 2026
4119d13
refactor: modernize builder API and streamline transaction execution
ShuCh3n Apr 23, 2026
492600c
refactor: streamline transaction execution and simplify follow-up API
ShuCh3n Apr 28, 2026
b595a3b
feat(BTI-978): add In3 route param for ABN-AMRO Achteraf Betalen
vildanbina Jul 2, 2026
cfb2404
style(BTI-978): ruff format in3 builder test
vildanbina Jul 2, 2026
114cee7
Merge pull request #7 from buckaroo-it/BTI-978-in3-route
vildanbina Jul 2, 2026
e5d6437
feat(BTI-721): add Banking service (PaymentOrder payout)
vildanbina Jul 3, 2026
1b103bb
Merge pull request #8 from buckaroo-it/BTI-721-banking-paymentorder
vildanbina Jul 3, 2026
ec9d0f6
feat(BTI-1077): add In3 authorize/capture flow
vildanbina Jul 6, 2026
dc3040b
style(BTI-1077): apply ruff format to in3 feature test
vildanbina Jul 6, 2026
9ffdab7
Merge pull request #9 from buckaroo-it/BTI-1077-in3-authorize-capture
vildanbina Jul 6, 2026
b11ab8d
test(BTI-715): verify In3 CompanyName/CocNumber flow through pay
vildanbina Jul 7, 2026
9acd435
Merge pull request #10 from buckaroo-it/BTI-715-in3-company-coc
vildanbina Jul 7, 2026
b7d8c2f
feat(BTI-584): update Klarna MOR to DataRequestKey flow
vildanbina Jul 7, 2026
bf500ab
Merge pull request #11 from buckaroo-it/BTI-584-klarna-mor
vildanbina Jul 7, 2026
27ca466
feat(BTI-17): add PayPerEmail example and feature test
vildanbina Jul 13, 2026
f41b4b9
Merge pull request #12 from buckaroo-it/BTI-17-payperemail-parity
vildanbina Jul 13, 2026
6822ed2
feat(BTI-20): add iDIN verification method (identify, verify, login)
vildanbina Jul 13, 2026
e080732
Merge pull request #13 from buckaroo-it/BTI-20-idin-verification
vildanbina Jul 13, 2026
a573373
feat(BTI-21): implement instant refunds for iDEAL and Payconiq
vildanbina Jul 14, 2026
f797b65
style(BTI-21): apply ruff format to instant-refund tests
vildanbina Jul 14, 2026
03008a9
Merge pull request #14 from buckaroo-it/BTI-21-instant-refunds
vildanbina Jul 14, 2026
d78a91c
feat(BTI-23): add eMandate solution (B2C + B2B)
vildanbina Jul 14, 2026
ef1f026
Merge pull request #15 from buckaroo-it/BTI-23-emandate-solution
vildanbina Jul 14, 2026
e2e6a2d
Merge branch 'master' into BTI-27
ShuCh3n Jul 15, 2026
149a5fd
Merge branch 'develop' into BTI-27
ShuCh3n Jul 15, 2026
b1fc420
refactor: clean up imports, format code, and improve API robustness
ShuCh3n Jul 15, 2026
b7e0d19
```
ShuCh3n Jul 15, 2026
89bc353
Merge branch 'BTI-27' into BTI-977
ShuCh3n Jul 15, 2026
0ed5bd4
feat(BTI-16): add Split Payments (Marketplaces) solution
vildanbina Jul 15, 2026
0605180
Merge pull request #17 from buckaroo-it/BTI-16-split-payments
vildanbina Jul 15, 2026
3f3ab9e
feat(BTI-977): add Point of Sale (POS) payment method
ShuCh3n Jul 16, 2026
09bf6d7
Merge branch 'develop' into BTI-977
ShuCh3n Jul 16, 2026
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
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ htmlcov/
.DS_Store
__pycache__/
*.pyc
__pycache__/
*.pyo

# local environment
.env
.venv/
venv/

# logs
*.log

# type checking
.mypy_cache/
225 changes: 225 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
- [Requirements](#requirements)
- [Pip Installation](#pip-installation)
- [Example](#example)
- [iDIN](#idin)
- [Instant Refunds](#instant-refunds)
- [eMandate](#emandate)
- [Split Payments](#split-payments)
- [Point of Sale (POS)](#point-of-sale-pos)
- [Contribute](#contribute)
- [Versioning](#versioning)
- [Additional information](#additional-information)
Expand Down Expand Up @@ -93,6 +98,226 @@ response = (

Find our full documentation online on [docs.buckaroo.io](https://docs.buckaroo.io).

### iDIN

iDIN lets Dutch banks confirm a consumer's identity on your behalf. It carries no amount or currency — only the return URLs plus the `issuerId` (BIC code of the consumer's bank) service parameter. Three actions are available: `identify()`, `verify()` (age 18+), and `login()`.

```python
response = payments.create_payment("idin", {
"return_url": "https://www.buckaroo.nl",
"return_url_cancel": "https://www.buckaroo.nl/cancel",
"return_url_error": "https://www.buckaroo.nl/error",
"return_url_reject": "https://www.buckaroo.nl/reject",
"service_parameters": {"issuerId": "BANKNL2Y"}, # sandbox issuer
}).identify()

print("key:", response.key)
print("redirect:", response.get_redirect_url())
```

See [`examples/idin.py`](examples/idin.py) for a runnable demo of all three actions.

### Instant Refunds

Instant refunds send money back to the shopper immediately instead of via the regular batch refund process. They are processed as an instant payment rather than a standard refund, and are supported for iDEAL and Payconiq via `instantRefund()`. Pass the `original_transaction_key` of a settled payment; `refund_amount` is optional — omit it for a full refund.

```python
response = payments.create_payment("ideal", {
"currency": "EUR",
"description": "ideal instant refund demo",
"invoice": "IDEAL-REFUND-DEMO-001",
"original_transaction_key": "ORIGINAL-TRANSACTION-KEY",
"refund_amount": 12.34, # optional; omit for a full refund
}).instantRefund()

print("key:", response.key)
```

See [`examples/instant_refund.py`](examples/instant_refund.py) for a runnable demo covering both iDEAL and Payconiq.

### eMandate

eMandate is a DataRequest-based solution for managing SEPA direct debit mandates, reached through
`app.solutions` rather than `app.payments`. It comes in two variants that share the same five
actions — only the service name differs:

- `emandate` — retail (B2C)
- `emandateb2b` — business (B2B)

```python
from buckaroo.app import Buckaroo

app = Buckaroo.from_env()

# List available issuers (GetIssuerList — no parameters)
response = app.solutions.create_solution("emandate").issuer_list()

# Create a mandate (CreateMandate — debtorReference is required)
response = app.solutions.create_solution(
"emandate",
{
"service_parameters": {
"debtorReference": "DEBTOR-001",
"debtorBankId": "ABNANL2A",
"sequenceType": "1",
"purchaseId": "PUR-001",
"language": "nl",
}
},
).create_mandate()
mandate_id = response.get_service_parameter("MandateId")

# Look up a mandate's status (GetStatus — mandateId is required)
response = app.solutions.create_solution(
"emandate", {"service_parameters": {"mandateId": mandate_id}}
).status()

# Modify a mandate (ModifyMandate — mandateId is required)
response = app.solutions.create_solution(
"emandate",
{"service_parameters": {"mandateId": mandate_id, "maxAmount": "1000.00"}},
).modify_mandate()

# Cancel a mandate (CancelMandate — mandateId is required)
response = app.solutions.create_solution(
"emandate",
{"service_parameters": {"mandateId": mandate_id, "purchaseId": "PUR-001"}},
).cancel_mandate()
```

The B2B variant exposes the same five methods — swap `"emandate"` for `"emandateb2b"`.

See [`examples/emandate.py`](examples/emandate.py) for a runnable demo of all five actions
against both the B2C and B2B services.

### Split Payments

Split Payments (Buckaroo service `Marketplaces`) lets a platform divide one
customer payment across its own funds account and one or more seller accounts.
Following the other Buckaroo SDKs, `split` and `refund_supplementary` build a
supplementary service that is *combined* into a payment or refund; `transfer` and
`manual_transfer` are standalone.

```python
from buckaroo import BuckarooClient
from buckaroo.services.payment_service import PaymentService
from buckaroo.services.solution_service import SolutionService

client = BuckarooClient("STORE_KEY", "SECRET_KEY", mode="test")
payments = PaymentService(client)
marketplaces = SolutionService(client)
```

**Split** — build the split, then combine it into the funding payment (e.g.
iDEAL). `daysUntilTransfer` is `"0"` for immediate payout, or omit it to hold the
funds until a later Transfer.

```python
split = marketplaces.create_solution("marketplaces").split({
"daysUntilTransfer": "2",
"marketplace": {"Amount": "10.00", "Description": "INV0001 Commission Platform"},
"sellers": [
{"AccountId": "SELLER_ACCOUNT_1", "Amount": "50.00", "Description": "Payout 1"},
{"AccountId": "SELLER_ACCOUNT_2", "Amount": "35.00", "Description": "Payout 2"},
],
})

response = payments.create_payment("ideal", {
"currency": "EUR",
"amount": 95.00,
"invoice": "INV0001",
"description": "Split order INV0001",
"service_parameters": {"issuer": "ABNANL2A"},
"return_url": "https://example.com/return",
"return_url_cancel": "https://example.com/cancel",
"return_url_error": "https://example.com/error",
"return_url_reject": "https://example.com/reject",
}).combine(split).pay()
```

**Transfer** — release held funds of an existing split payment. With no split
data it transfers everything (Transfer I); pass `marketplace`/`sellers` to
transfer a partial or re-specified split (Transfer II).

```python
marketplaces.create_solution("marketplaces").transfer({
"originalTransactionKey": "SPLIT_TRANSACTION_KEY",
})
```

**RefundSupplementary** — refund the consumer and pull the funds back from the
target accounts. Combine it into the refund. Without seller data it reverts all
transfers (I); pass `sellers` to retrieve specific amounts per account (II).

```python
supplementary = marketplaces.create_solution("marketplaces").refund_supplementary()

payments.create_payment("ideal", {
"currency": "EUR",
"amount": 50.00,
"invoice": "INV0001",
"description": "Split refund INV0001",
"original_transaction_key": "SPLIT_TRANSACTION_KEY",
"refund_amount": 50.00,
"return_url": "https://example.com/return",
"return_url_cancel": "https://example.com/cancel",
"return_url_error": "https://example.com/error",
"return_url_reject": "https://example.com/reject",
}).combine(supplementary).refund()
```

**ManualTransfer** — move funds directly between two accounts.

```python
marketplaces.create_solution("marketplaces").manual_transfer({
"fromAccountId": "ACCOUNT_A",
"toAccountId": "ACCOUNT_B",
"fromDescription": "Deduction monthly fee",
"toDescription": "Monthly fee third party ABC",
"amount": 10.00,
"currency": "EUR",
})
```

A runnable demo of all six request types is in
[`examples/marketplaces.py`](examples/marketplaces.py).

### Point of Sale (POS)

POS transactions are PIN-based in-store payments processed through a physical payment terminal.
You initiate the transaction via API with the terminal's unique `TerminalID`; Buckaroo routes the
request to that terminal, which prompts the customer to complete payment there. There's no
redirect flow, every request is sent with a fixed `Channel: "Web"`, set internally by the SDK.

The immediate response carries a pending/awaiting status. The final result, plus the printable
`Ticket` receipt text for the customer, arrives later via push notification.

```python
response = payments.create_payment("pospayment", {
"currency": "EUR",
"amount": 0.01,
"invoice": "TestFactuur01",
}).terminal_id("50000001").pay()

print("key:", response.key)
print("pending:", response.is_pending())
```

Parsing the push notification once the terminal completes the transaction push bodies wrap the
transaction under a `Transaction` key, so unwrap it before handing it to `PaymentResponse`:

```python
from buckaroo.models.payment_response import PaymentResponse

transaction = push_json["Transaction"] # raw body your webhook endpoint received
response = PaymentResponse({"data": transaction})

ticket = response.get_service_parameter("Ticket") # printable receipt text
```

See [`examples/pos_payment.py`](examples/pos_payment.py) for a runnable demo of both the `Pay`
action and push-notification parsing.

### Contribute

We really appreciate it when developers contribute to improve the Buckaroo plugins.
Expand Down
10 changes: 9 additions & 1 deletion buckaroo/_buckaroo_client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

import logging
from typing import Optional
from .exceptions._authentication_error import AuthenticationError
from .config.buckaroo_config import BuckarooConfig, create_config_from_mode
Expand Down Expand Up @@ -118,7 +120,13 @@ def confirm_credential(self) -> bool:
try:
response = self.http_client.get("/json/Transaction/Specification/ideal")
return response.success
except Exception:
except Exception as e:
logging.warning(
"confirm_credential: unexpected error during credential check "
"(network issue or unexpected API response): %s",
e,
exc_info=True,
)
return False

def get_config_info(self) -> dict:
Expand Down
3 changes: 2 additions & 1 deletion buckaroo/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class BuckarooConfig:
retry_attempts: int = 3

@classmethod
def from_env(cls) -> "BuckarooConfig":
def from_env(cls) -> 'BuckarooConfig':
"""Create configuration from environment variables."""
# Get log level from env
log_level_str = os.getenv("BUCKAROO_LOG_LEVEL", "INFO").upper()
Expand Down Expand Up @@ -240,3 +240,4 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self.logger.log_exception(exc_val, context={"context_manager": "exit"})
else:
self.logger.log_debug("Exiting Buckaroo app context successfully")

Loading
Loading