Payments
Every charge BillKit makes against a subscription is a payment. You never create one directly; you read them to see what was collected, what failed, and what can still be refunded.
Where payments come from
There is no create endpoint. A payment is written when BillKit asks Mollie to charge a customer,
and its type says why:
| type | Created by |
|---|---|
| initial | A checkout session. The first charge of a subscription. |
| renewal | BillKit, when a licensed subscription’s period ends, including its retries of a failed renewal and a past-due period the customer pays on-session. |
| plan_change | A plan change that charges a proration. |
| metered_cycle | BillKit, when a metered period closes. |
| reauthorization, import_activation, setup | A verification charge that only exists to mint a new mandate. Not listed; see below. |
One-off sales are a separate object, the one_shot_payment, with its own osp_ ids, route and
events. They never appear here. See Charge once, without a subscription.
The object
| Field | Notes |
|---|---|
| amount_cents, currency | What was charged, tax included. amount_cents + discount_cents is the price before any coupon. |
| status | See Statuses. |
| type | See the table above. |
| subscription_id | null on a first payment that never settled, because the subscription is only created when it does. |
| checkout_session_id | Set on the initial payment only. |
| paid_at | Unix seconds when BillKit saw it settle, or null. |
| failure_reason | Mollie’s code for why it failed, such as insufficient_funds for a card or AC04 for a SEPA debit. null when Mollie gave none. |
| payment_method | How the charge was collected, such as creditcard or directdebit, when known. |
| discount_cents, coupon_id | What a coupon took off this charge, and which coupon. |
| amount_refunded_cents | Pending and succeeded refunds against this charge. |
| amount_disputed_cents | Money an open chargeback has already taken back. |
| amount_refundable_cents | What POST /v1/refunds would still accept, ignoring the refund window. 0 for a charge that is not paid or not a refundable type. |
| customer, subscription | null unless you ask for them with expand. customer stays null for a customer you have deleted or purged; customer_id is still set. |
| refund_eligibility | Whether a refund of the remaining balance would succeed now, applying the refund window and the price’s refund policy, which amount_refundable_cents ignores. null unless you retrieve the payment with expand=refund_eligibility. See Expanding responses. |
The three refund and dispute totals are computed when you read the payment. In a webhook event
payload they are null, so fetch the payment when you need them.
Statuses
status is Mollie’s own payment status, recorded as Mollie reports it. The two exceptions are
renewals BillKit settles without calling Mollie, described below the table.
| status | Meaning |
|---|---|
| open / pending | Created and waiting on the payer or the bank. A SEPA debit sits here for days. |
| authorized | Authorised and about to be captured, which Mollie does on its own. Treat it as not yet paid. |
| paid | Settled. paid_at is set. A charge for something sold has a paid invoice; a verification charge has none (see below). |
| failed / expired / canceled | Terminal. Nothing was collected. |
A payment stays paid after it is refunded or charged back. Read the totals above to see what
happened to the money afterwards.
Some renewal and metered usage payments have no record at Mollie. When the subscription has no
payment method BillKit can charge (none on file, or one a decline or chargeback marked as
unusable), or Mollie refuses to create the charge on the stored mandate, BillKit records the
payment as failed with failure_reason: "mandate_refused" and the subscription goes
past_due. When a coupon brings a renewal to zero, it is recorded as paid with nothing
collected.
Listing payments
curl "https://api.billkit.eu/v1/payments?customer_id=cus_Ja7Ye2vMq9xN4TdWpr&limit=10" \
-H "Authorization: Bearer $BILLKIT_API_KEY"{
"object": "list",
"data": [
{
"id": "pay_Zt6Wq1yBn4Xm9RcKvo",
"object": "payment",
"created": 1789344600,
"livemode": false,
"subscription_id": "sub_Pv9Kc3nX8mQ2rTyLbd",
"customer_id": "cus_Ja7Ye2vMq9xN4TdWpr",
"checkout_session_id": "cs_Lr4Nx8qW2vKm7TbPzc",
"amount_cents": 2900,
"currency": "EUR",
"status": "paid",
"type": "initial",
"paid_at": 1789344612,
"failure_reason": null,
"amount_refunded_cents": 0,
"amount_refundable_cents": 2900,
"amount_disputed_cents": 0,
"payment_method": "creditcard",
"discount_cents": 0,
"coupon_id": null,
"customer": null,
"subscription": null,
"refund_eligibility": null
}
],
"has_more": false
}Newest first, failed and pending attempts included, so check status before counting a row as
revenue. customer_id is the only filter. Pages are cursor-based: limit is 1 to 100 and
defaults to 10, and when has_more is true you pass the last id you received as
starting_after to get the next page. expand=customer and expand=subscription inline those
objects. The key needs the payments:read scope.
The verification charges (reauthorization, import_activation, setup) are left out of the
list, because nothing was sold. They are still payments: GET /v1/payments/{id} returns one, and
payment.succeeded and payment.failed fire for them, so check type in your handler. The
checkout charge of a trial or of a metered price also only verifies the payment method. It is
listed, with type initial, but nothing was sold, so it has no invoice.
Retrieving one
GET /v1/payments/{id} returns the same object, and takes the same expand values plus
refund_eligibility. The list does not accept that one, because answering it costs a query per
row: expand=refund_eligibility on GET /v1/payments is a 400. A payment in
another account, or in the other mode, is a 404. To find the invoice a charge produced, call
GET /v1/invoices?payment_id=pay_….
The provider’s own record
BillKit keeps its own copy of a payment, not Mollie’s. When a support question needs Mollie’s detail, such as the card’s country, the iDEAL bank or Mollie’s own status history, fetch it live:
curl https://api.billkit.eu/v1/payments/pay_Zt6Wq1yBn4Xm9RcKvo/provider \
-H "Authorization: Bearer $BILLKIT_API_KEY"The response has object payment.provider_payload, provider (mollie), the
provider_payment_id (Mollie’s tr_… id) and payload, Mollie’s payment object exactly as
Mollie returned it. That shape is Mollie’s and changes when Mollie changes it, so use it for
diagnosis and support, not as an integration contract.
For a customer you have purged, redacted is true and payload keeps only the
amounts, statuses, timestamps and identifiers. Everything else is removed, including the account
holder, bank and card details, description and metadata. Mollie keeps the payment under its own
legal retention, so without this the call would hand back personal data you have erased. For every
other payment redacted is false and payload is verbatim.
It reads from Mollie on every call, so it can fail. If Mollie cannot be reached, the call still
returns 200 with available: false, payload: null and a sentence in unavailable_reason.
The payment itself is unaffected. This route covers subscription payments only, and a renewal
BillKit recorded without a Mollie payment always comes back available: false.
Events
| Event | When it fires |
|---|---|
| payment.succeeded | A payment reached paid. For an initial payment it arrives with checkout.session.completed and subscription.created. |
| payment.failed | A renewal, plan change, metered cycle or verification charge ended failed, expired or canceled. A checkout’s first payment fires it only when it ended failed, with subscription_id: null; an abandoned one fires nothing. A failed renewal on a subscription that is still set to renew also fires subscription.past_due. |
A first payment that fails fires payment.failed with subscription_id: null, because the
subscription is only created when a payment settles. One the buyer abandons (expired or
canceled) fires no payment.failed. Either way a hosted checkout session also fires
checkout.session.expired; an embedded one stays open so the customer can retry, and fires it at
its expires_at. Do not look up the customer’s other subscriptions from a payment.failed with no
subscription_id. Invoices have their own events; see
Invoices and credit notes.
Money going back out has its own objects. See Refunds for what you return and Disputes for what a bank takes back. For what to do when a renewal fails, see Recover failed payments.
BillKit