Pre-capture void via Transaction/Cancel; captured transactions come back
PSP_075 → invalid_request. The required idempotencyKey has no PayZen
channel; a replayed cancel is a PSP_105 state error, never a second effect.
Manual capture = Transaction/Validate: it confirms an
AUTHORISED_TO_VALIDATE transaction for capture on its expected date.
Transaction/Capture is NOT this operation — it is a Brazil-specific batch
WS and must never be used here. Validation releases the full authorized
amount; PayZen has no partial validate, so a differing amount is
rejected. The required idempotencyKey cannot be delegated — PayZen has
no idempotency channel — but replays are naturally safe: validating twice
yields a status error, never a duplicate capture.
Creates a PSP-native subscription via Charge/CreateSubscription: the
gateway itself schedules, charges, and retries every installment against
the vaulted savedPaymentMethodToken (a PayZen paymentMethodToken; for
SEPA direct debits the mandate reference, which PayZen requires to start
= 14 days out). Installment outcomes arrive as ordinary transaction IPNs on the existing webhook path — enable the Back Office rule "Notification URL when creating a recurring payment".
Cadence: interval/intervalCount synthesize an RFC 5545 RRULE
(day/week/month/year are all accepted — PayZen rejects only sub-daily
periods); schedule passes a host RRULE through after validation and is
sent in PayZen's documented RRULE:-prefixed form. startAt becomes
effectDate (normalized to PayZen's 25-character ISO 8601 UTC shape);
when omitted the adapter's clock supplies "now" because effectDate is a
REQUIRED field. merchantRefNum rides the dedicated orderId field.
pspCustomerId is withheld (PayZen keys subscriptions by token, not by
a customer object) and planId is rejected — the platform has no
plan/price model to bill from.
Replay safety: PayZen has no idempotency channel and creation applies
IMMEDIATELY (unlike a formToken, a subscription is a live billing
schedule), so this call is never transport-retried and a replayed create
CAN create a second active subscription. The deterministic orderId
(derived from idempotencyKey unless merchantRefNum is set) and the
payfanout_key metadata stamp make duplicates visible in the Back
Office and on installment IPNs, but there is no API to look a
subscription up by orderId — never blind-retry this call; reconcile
first. RETAIN the returned record's id together with the token: they
form the composite key every later retrieve/cancel needs.
Two session shapes, routed by the requested payment method types:
PayZen has no idempotency mechanism, so the adapter synthesizes traceable replays: orderId derives deterministically from the idempotencyKey and the key is stamped into metadata. A replayed call mints another formToken (or payment order) for the SAME orderId — harmless (no money moves until the shopper completes) and reconcilable via Order/Get.
Throws PayFanoutError (code "invalid_request") on unparseable payloads; maps genuinely unknown-but-valid event types to type "unknown" rather than throwing.
PayZen has NO refund idempotency: a replayed Transaction/Refund creates a
SECOND credit while the total stays within the original, and the refund
request carries no metadata/reference field the adapter could pre-check
against (idempotencyKey cannot be delegated OR reliably synthesized).
Consequences, deliberately encoded here:
Partial refunds go to Transaction/Refund (requires a captured source). Full refunds go through Transaction/CancelOrRefund AUTO, which cancels a not-yet-captured transaction instead — the result is mapped honestly (a cancellation releases the authorization; the shopper was never charged, so the refund "succeeded").
Reads a subscription via Subscription/Get. PayZen keys subscriptions by
subscriptionId + paymentMethodToken (BOTH required — there is no list
API to rediscover either), so savedPaymentMethodToken is mandatory
here. The V4/Subscription answer has no status field; the unified status
is derived — see derivePayZenSubscriptionStatus for the exact rule.
Accepts either a transaction uuid (32-hex, from webhooks/refunds) or a PayFanout pspSessionId (= the derived orderId): the krypton form creates the transaction in-browser, so right after checkout the host may only hold the orderId. The orderId path reads the order and reports its most recent payment (DEBIT) attempt.
A PayZen refund IS a transaction (the CREDIT created by Transaction/ Refund) — polling it is Transaction/Get on the refund uuid. A cancellation outcome (full refund of an uncaptured payment) polls the original DEBIT, whose CANCELLED state still means "funds released".
"Test connection" probe: one single-shot, side-effect-free Charge/SDKTest
call — PayZen's purpose-built connection test, which just echoes the
submitted value back on valid credentials. Transport retries are disabled so
a transient failure surfaces promptly instead of replaying (a "Test
connection" click must not hang for multiples of the timeout). PayZen
selects TEST vs LIVE by the key set, so the probe body carries no mode. The
outcome is classified so a host UI can tell a wrong shopId/password (auth)
from a transient outage (network); it resolves on every path instead of
throwing, and never surfaces the credential.
Webhook handling — same adapter, separate concerns. Both async: future PSPs may need remote key retrieval; the contract stays uniform. MUST operate on the RAW request body bytes/string — re-serializing a parsed body breaks signatures (the conformance suite tests this).
Stops PSP-side billing via Subscription/Cancel (immediate termination — installments already in flight are NOT cancelled; use cancelPayment on the individual transaction for those). The composite key rule of retrieveNativeSubscription applies:
savedPaymentMethodTokenis required.The Cancel answer is a bare Common/ResponseCodeAnswer (0 terminated, 30 token not found, 32 subscription not found, 99 undefined error), so the returned record always comes from a follow-up Subscription/Get. Verified-idempotent: on ANY rejection — nonzero responseCode or an ERROR envelope such as PSP_033/PSP_564 (already cancelled) — the adapter re-reads the subscription and resolves successfully when it is already terminated; only a subscription that is genuinely still billing surfaces the rejection. Replaying this call is therefore always safe, which is also why it is the one mutating PayZen call that keeps automatic transport retries.
idempotencyKeyhas no PayZen channel to ride; replay safety comes from this verification, not from dedupe.