Idempotency
Create and confirm are safe to retry. They do not emit outbound webhooks again on replay.
Create checkout
- Prefer the
Idempotency-Keyheader (max 255 characters) - Otherwise StudentPay uses
{provider_code}:{provider_order_id}
POST /v1/provider-checkouts
Idempotency-Key: DEMO-ENROLMENT-1001
Authorization: Bearer <YOUR_API_KEY>
If a checkout already exists for that provider and provider order id (or checkout id), and the canonical enrolment intent is unchanged, the API returns 200 with:
{
"success": true,
"idempotent_replay": true,
"idempotency_key": "DEMO-ENROLMENT-1001"
}
No additional student, checkout, or direct-debit authority records are created.
While the checkout is still open, a replay may remint a fresh direct_debit.setup_url and direct_debit.token. Use the latest URL and token.
checkout.created is sent only on the first create, not on replay.
Same order id, different payload
Replay is keyed on provider + provider_order_id (or Idempotency-Key).
StudentPay then compares canonical enrolment intent, not raw JSON ordering:
- course name
- course price
- upfront amount
- amount to finance
- regular instalment
- number of instalments
- payment frequency
- first payment date
For catalogue-authoritative providers those commercial fields are the server-resolved values, not whatever duplicate money the client tried to send.
If the canonical intent is the same, create returns the original checkout
(idempotent_replay: true).
If the canonical intent is materially different, create returns 409
IDEMPOTENCY_CONFLICT. It does not create a second financial record and it does
not replace the original enrolment.
Supported contract: reuse the original logical enrolment for the same
provider_order_id. Do not use one order id to represent two different enrolments.
Confirm
If the checkout is already confirmed, confirm returns 200 with already_confirmed: true and the existing agreement references.
It does not:
- create a second Payment Plan Agreement
- generate a second PDF
- create a second payment schedule
- emit
checkout.confirmedoragreement.generatedagain
You can safely retry confirm after a network timeout.