HSRCPAY Documentation

Payment Lifecycle and Confirm

Understand payment creation, confirm, requires_action, succeeded, authorized, and charge attempt relationships in HSRC Pay.

This page explains the difference between creating a Payment and confirming a Payment, why Confirm is kept separate, and how one Payment can produce multiple Charge attempts.

Confirm is the controlled transition that moves the payment record into real provider execution.

What is payment lifecycle?

Payment lifecycle is the state model from merchant payment intent creation to terminal outcome. In HSRC Pay, the important point is: Payment is the business goal; Charge is the attempt at provider level.

Public API status matches the Prisma PaymentStatus enum (uppercase, not snake_case):

StatusMeaning
CREATEDPayment record created; confirm has not run yet
REQUIRES_PAYMENT_METHODMethod missing or last confirm failed/declined; can confirm again
REQUIRES_ACTION3DS, redirect, or user action required
PROCESSINGProvider execution or async result pending
AUTHORIZEDAuth-only success (auto_capture: false)
SUCCEEDEDCollection completed (capture included when auto_capture: true)
CANCELEDTransaction canceled
REFUNDEDRefund processed after successful payment
EXPIREDInvalid due to expiry or business rule

Decline and technical error do not produce separate DECLINED / FAILED status on Payment; when orchestrator result is declined or error, Payment is usually REQUIRES_PAYMENT_METHOD. Decline detail lives on the related Charge record (DECLINED, FAILED).

Statuses that can be confirmed again before confirm: CREATED, REQUIRES_ACTION, REQUIRES_PAYMENT_METHOD.

Create vs Confirm separation

Creating a Payment does not mean pulling funds. Create prepares merchant intent, amount, currency, auto_capture, secure_mode, customer context, and metadata; the record opens as CREATED.

Confirm starts routing plan and provider execution (charge attempt) with payment_method, optional routing_strategy, and payer_identity.

StageWhat does it do?Money movement?
Create PaymentPrepares business intent and payment recordNo
Confirm PaymentRouting plan, charge attempt, provider adapter executionYes in production, simulated in sandbox

What does Confirm do?

At confirm time, typically:

  1. Payment is checked for confirmable status.
  2. Authorization (payment:confirm) and account boundary are validated.
  3. Payment Method is normalized (token, saved method, or inline card data).
  4. Candidate pool is resolved from account routing policy or request routing_strategy.
  5. Routing plan is built; for each candidate a Charge is created and provider adapter pay runs.
  6. Result returns as normalized adapter result; Payment status and optional next_action update.
  7. Charge status updates (e.g. CAPTURED, AUTHORIZED, REQUIRES_ACTION, DECLINED).
  8. Domain event and webhook delivery fire.

Confirm HTTP response

Confirm does not return a single kind field. Standard v1 envelope includes data.payment (current Payment DTO) and data.confirmResult (orchestrator summary).

{
  "data": {
    "payment": {
      "object": "payment",
      "id": "pay_123",
      "status": "REQUIRES_ACTION",
      "amount": 12500,
      "currency": "TRY"
    },
    "confirmResult": {
      "ok": true,
      "results": [],
      "latestResult": { "kind": "requires_action" },
      "paymentNextAction": {
        "action": "redirect_user_to_url",
        "url": "https://..."
      }
    }
  }
}
FieldMerchant behavior
payment.statusOrder / workflow state; reconcile with webhook
confirmResult.oktrue when last step is success or requires_action
confirmResult.latestResultProvider adapter summary (raw nextAction may appear here)
confirmResult.paymentNextActionPlatform next_action: currently redirect_user_to_url + url

Successful confirm with auto_capture: false sets Payment to AUTHORIZED; capture via separate endpoint targets SUCCEEDED / charge CAPTURED.

Requires Action / 3DS

When secure mode or provider requirements need extra user action, Confirm may not return SUCCEEDED / AUTHORIZED immediately. Payment becomes REQUIRES_ACTION; use confirmResult.paymentNextAction (and payment.next_action on the record) for redirect on the merchant side.

Hosted Checkout can manage this action inside the user experience. In API-only integration the merchant redirects with paymentNextAction.url; after challenge the gateway callback URL (linked to charge) completes the resume flow.

Provider adapter nextAction (e.g. render_html) and platform paymentNextAction are different layers; use the platform field for user redirect in integration.

Resume / 3DS callback

Resume is not a separate public payment endpoint named resumePayment(paymentId). When 3DS completes, the provider returns to HSRC Pay gateway at three-d-secure-callback-urls/:id; this id is linked to the charge.

const { payment, confirmResult } = await confirmPayment(paymentId, {
  payment_method: { id: "pm_123" },
  payer_identity: { ip_address: clientIp, user_agent: ua },
});

if (payment.status === "REQUIRES_ACTION" && confirmResult.paymentNextAction?.url) {
  redirectUser(confirmResult.paymentNextAction.url);
  // After user challenge, gateway callback is processed automatically;
  // then verify final status with GET payment or webhook.
}

See 3DS Simulation for detailed sandbox scenarios.

Charge attempt relationship

One Payment can produce multiple Charges. A new charge is created for each routing candidate; attempt_no reflects routing plan index.

Example: Merchant creates a 1000 TRY Payment. First provider returns soft decline (Charge DECLINED). Routing moves to next candidate; second charge attempt succeeds and Payment becomes SUCCEEDED or AUTHORIZED. Merchant sees one Payment; ops traces attempts via charge list.

Routing stops on success or requires_action; on declined type error it usually stops; on other errors the next candidate may be tried (engine rules depend on candidate pool).

Idempotency and re-confirm

Confirm does not define a mandatory separate Idempotency-Key; network timeout or double-click still risks unnecessary new execution on the same payment.

Best practice:

  • After timeout, check existing status and charges with GET /payments/:id instead of opening a new payment.
  • Plan controlled re-confirm when REQUIRES_PAYMENT_METHOD or REQUIRES_ACTION; confirm returns 400 on terminal statuses.
  • Process webhook events idempotently for duplicate delivery.

Webhook expectations

Frontend or confirm response should not be treated as final accounting record. Payment state changes must be verified via webhook/event system.

Expected approach:

  • Confirm response guides user experience (paymentNextAction, immediate status).
  • Webhook closes merchant backend state (payment.succeeded, payment.requires_action, payment.authorized, etc.).
  • Server-side status query is used for reconciliation and incident resolution (include_charges=true recommended).

Best practices

  • Use Payment and Charge concepts with the same language inside the team.
  • Treat Confirm as the execution boundary.
  • Accept REQUIRES_ACTION as a normal lifecycle part, not an edge case.
  • Read decline via Charge DECLINED and REQUIRES_PAYMENT_METHOD, not Payment status alone.
  • Decide from payment.status and normalized charge fields, not provider raw response.
  • Test auto_capture and AUTHORIZED vs SUCCEEDED distinction together with capture/cancel flows.
  • Test outcomes beyond success in sandbox as well.

Example confirm request

{
  "payment_method": {
    "id": "pm_123"
  },
  "routing_strategy": {
    "mode": "merchant_direct",
    "payment_provider_config_id": "ppc_123"
  },
  "payer_identity": {
    "ip_address": "203.0.113.10",
    "user_agent": "Mozilla/5.0 ...",
    "ip_address_details": null
  }
}

Endpoint: POST /v1/payments/:id/confirm. Use API Reference: confirmPayment for the full schema.

On this page