Skip to content

feat: Implement remaining active payment types, installment plans and basket v3 - #6

Open
sveneberth wants to merge 2 commits into
mainfrom
feat/payment-methods
Open

feat: Implement remaining active payment types, installment plans and basket v3#6
sveneberth wants to merge 2 commits into
mainfrom
feat/payment-methods

Conversation

@sveneberth

@sveneberth sveneberth commented Aug 5, 2026

Copy link
Copy Markdown
Member

Summary

Adds every payment method Unzer currently lists as active, with full server-side support
for Installment and Direct Bank Transfer.

Payment types (15): Alipay, ClickToPay, DirectBankTransfer, Eps, PayU,
PaylaterDirectDebit, PaylaterInstallment, PostFinanceCard, PostFinanceEfinance,
Prepayment, Przelewy24, SepaDirectDebit, Twint, Wechatpay, Wero.
PaymentTypes gained PAYU/WERO, PaymentMethodTypes gained WERO.

Installment needs two steps around the payment type, both on the client now:

plans = client.getPaylaterInstallmentPlans(amount=190.00, currency="EUR", country="DE")
plan = plans.plans[0]
pt = client.createPaymentType(PaylaterInstallment(
    inquiryId=plans.inquiryId, numberOfRates=plan.numberOfRates,
    iban="DE89370400440532013000", holder="Max Mustermann", country="DE"))
client.riskCheckPaylaterInstallment(payment)          # optional pre-check

New response models InstallmentPlans, InstallmentPlan, InstallmentRate and
RiskCheckResponse. The customer's IP, which the Pay later methods require, is sent as
CLIENTIP header via UnzerClient(client_ip=…).

Basket v3, which Installment requires: Basket gained totalValueGross, BasketItem
gained amountPerUnitGross/amountDiscountPerUnitGross. Setting totalValueGross switches
the basket to /v3/baskets; without it nothing changes. fromDict() of both models no
longer fails on the keys of the other schema.

Motivation

Only a handful of payment methods were available, and the two most requested ones —
Installment and Direct Bank Transfer — were unusable because Installment needs a plans
inquiry, a payment type with fields, and a v3 basket.

Verification

Field names, short codes and resource paths come from the PHP SDK, the Java SDK and the
OpenAPI specification (api.unzer.com/swagger-ui/api-docs), not from the prose
documentation, which contradicts them in eleven places, among them:

Topic Documentation Implemented
Direct Bank Transfer short code opb obp (typeId s-obp-…)
Direct Bank Transfer endpoint /types/open-banking-pis /types/openbanking-pis
SEPA Direct Debit holder field accountHolder holder
Risk check header x-CLIENTIP CLIENTIP
WeChat Pay / Wero endpoint /types/WeChatPay, /types/Wero lowercase
Installment country required optional (spec requires inquiryId, numberOfRates)

PaymentMethodTypes.EPS is corrected from EPS to eps accordingly — the specification
knows only /v1/types/eps and the typeId prefix p-eps-, and uppercase EPS appears there
solely as a documentation tag. To stay safe either way, the keypair lookup in
get_configuration() now compares case-insensitive.

Checked mechanically for all 15 types: short code and resource path exist in the API
reference, request payloads match its request schemas field by field, and all four
response models match their response schemas field by field. Payloads carry only fields
that are set and never null, which constraints such as minLength would reject.
Baskets serialize and parse in both schemas, with participantId sent for v1 only and
note kept for v3. The complete installment flow (plans -> type -> basket -> risk check
-> authorize) runs against the documented example payloads, and pycodestyle stays at
the five pre-existing violations. Not yet run against the sandbox API with a real
keypair.

Where documentation and specification disagree, the docstring at that spot records which
source was followed and why.

Breaking change

UnzerClient.endpoint no longer contains the API version, which moved to
UnzerClient.apiVersion, so resources can be requested in another version. Only affects
callers that override endpoint. Version bumped 1.4.0 → 1.5.0.

Note for review

The first commit is the separate fix PR #5 — please merge that one first.

`createPaymentType()` interpolated the `PaymentTypes` enum member itself,
so the request went to `types/PaymentTypes.CARD` instead of `types/card`:

> `"types/%s" % paymentType.method` -> `types/PaymentTypes.CARD`

`method` holds the short code used in type ids (`crd`), while the types
endpoint expects the full method name (`card`). Both are wrong when the
enum member is stringified instead of its value.
… basket v3

Adds all payment methods Unzer currently lists as active:

- `Alipay`, `ClickToPay`, `PayU`, `PostFinanceCard`, `PostFinanceEfinance`,
  `Prepayment`, `Przelewy24`, `Twint`, `Wechatpay`, `Wero`
- `DirectBankTransfer` (`openbanking-pis`) with `ibanCountry`
- `Eps` with `bic`
- `SepaDirectDebit` and `PaylaterDirectDebit` with `iban`/`holder`
- `PaylaterInstallment` with `inquiryId`, `numberOfRates`, `iban`, `country`
  and `holder`

Installment needs two more steps around the payment type, both added to the
client: `getPaylaterInstallmentPlans()` fetches the plans to present to the
customer (new models `InstallmentPlans`, `InstallmentPlan`, `InstallmentRate`)
and `riskCheckPaylaterInstallment()` pre-checks the customer data. Both, and
the Pay later authorize calls, need the customer's IP, which `UnzerClient`
now sends as `CLIENTIP` header when constructed with `client_ip`.

It also requires a basket in the newer schema, so `Basket` gained
`totalValueGross` and `BasketItem` gained `amountPerUnitGross` and
`amountDiscountPerUnitGross`. Setting `totalValueGross` switches the basket
to `/v3/baskets`; without it nothing changes. `fromDict()` of both models
no longer fails on the keys of the other schema.

Field names, type short codes and resource paths are taken from the PHP and
Java SDK, not from the documentation, which contradicts them in several
places (`obp` vs. `opb`, `holder` vs. `accountHolder`, `CLIENTIP` vs.
`x-CLIENTIP`). `PaymentMethodTypes.EPS` is corrected to `eps` accordingly,
and the keypair lookup in `get_configuration()` now compares case-insensitive.

BREAKING CHANGE: `UnzerClient.endpoint` no longer contains the API version,
which moved to `UnzerClient.apiVersion`, so that resources can be requested
in a different version.
@sveneberth sveneberth added the enhancement New feature or request label Aug 5, 2026
@sveneberth
sveneberth force-pushed the feat/payment-methods branch from b727a8e to fa787e2 Compare August 5, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant