Core Concepts

Status Lifecycle

Understanding payment intent and attempt status transitions

Understanding the status lifecycle is crucial for properly handling payments in your integration. This guide explains how payment intents and attempts transition through various states.

Payment Lifecycle Diagram

The payment lifecycle includes various states representing specific stages in the payment process:

mermaid
stateDiagram-v2
    [*] --> Selection: Create Intent
    Selection --> Pending: Customer Pays
    Pending --> Success: Payment Succeeds
    Pending --> Selection: Payment Fails (alternatives exist)
    Pending --> Failed: Payment Fails (no alternatives)
    Selection --> Canceled: User/Merchant Cancels
    Success --> Chargeback: Chargeback Filed
    Failed --> [*]
    Canceled --> [*]
    Chargeback --> [*]

Intent Statuses

selection

The initial state where the customer selects a payment method.

  • Intent is created and waiting for customer action
  • Customer is on the hosted checkout page
  • No payment attempt has been made yet (or previous attempt failed with alternatives available)

What to do:

  • Wait for customer to select payment method and make the payment

Transitions to:

  • pending - Customer initiates payment
  • canceled - Customer/merchant cancels

pending

The state where the payment is being processed.

  • Customer has selected a payment method and clicked "Pay"
  • Payment attempt is in progress with the PSP
  • May require additional steps (3D Secure, OTP, etc.)

What to do:

  • Wait for webhook notification
  • Do NOT fulfill order yet

Transitions to:

  • success - Payment succeeds
  • failed - Payment fails with no alternatives
  • selection - Payment fails but alternatives exist
  • authorized - Payment authorized (delayed capture)

Typical duration: Seconds to minutes (depending on payment method)


authorized

The payment has been authorized but not yet captured.

Only applicable when capture_mode: "prefer_delay" is used and the payment method supports authorization.

  • Funds are reserved but not yet transferred
  • You must manually capture within the allowed time window
  • Authorization may expire if not captured

What to do:

  • Verify order can be fulfilled
  • Call capture endpoint: GET /api/v2/intents/{intent_id}/capture
  • Or cancel authorization: GET /api/v2/intents/{intent_id}/cancel-authorization

Transitions to:

  • success - Payment captured
  • canceled - Authorization canceled
  • failed - Authorization expires

See Capture Modes Guide for details.


success

The state indicating a successful payment.

  • Payment has been processed successfully
  • Funds will be settled to your account
  • Safe to fulfill the order

What to do:

  • Fulfill the order/deliver the product
  • Send confirmation email to customer
  • Update your database

Transitions to:

  • chargeback - Customer disputes payment (rare)
This is the only status where you should fulfill orders!

failed

The payment failed and no other payment methods are available.

  • All payment attempts have been exhausted
  • No alternative payment methods exist
  • Customer must start over with a new intent

What to do:

  • Log failure reason for analysis
  • Offer customer to try again (create new intent)

Common causes:

  • Card declined
  • Insufficient funds
  • Payment method not supported
  • All retry attempts failed

Does NOT transition further - This is a terminal state


canceled

The payment intent was canceled.

Cancellation can happen in two ways:

1. Customer Cancellation:

  • Customer clicked "Cancel" on checkout page

2. Merchant Cancellation:

  • You called POST /api/intents/{intent_id}/cancel
  • Order was canceled before payment

What to do:

  • Clean up any pending order records
  • Allow customer to restart checkout if appropriate

Restrictions:

  • Can only cancel intents in selection status
  • Cannot cancel pending, success, or failed intents

Does NOT transition further - This is a terminal state


chargeback

The payment was charged back by the customer.

  • Customer disputed the charge with their bank
  • Rare but important to handle
  • Funds may be reversed

What to do:

  • Review the transaction
  • Gather evidence if you want to dispute
  • Contact NjiaPay support for guidance
  • Update your records

Does NOT transition further - This is a terminal state

Webhook Events for Status Changes

You'll receive StatusChange webhook events when intent status changes:

{
  "type": "StatusChange",
  "created": "2024-09-01T00:00:00Z",
  "content": {
    "intent_id": "550e8400-e29b-41d4-a716-446655440000",
    "reference_id": "order_12345",
    "purchaser_id": "customer_abc",
    "amount": 20000,
    "currency": "ZAR",
    "status": "success",
    "partner": "adyen",
    "method": "card",
    "brand": "mastercard",
    "issuer_country": "ZA",
    "event_ts": "2024-09-01T12:34:56Z",
    "failure_reason": null
  }
}

See Webhooks Guide for complete event documentation.

Handling Status in Your Application

example.js
function handlePaymentStatus(intent) {
  switch (intent.status) {
    case "pending":
      // Payment is processing
      displayProcessingMessage();
      break;

    case "authorized":
      // Payment authorized, needs capture
      // Verify order can be fulfilled
      if (canFulfillOrder(intent.reference_id)) {
        capturePayment(intent.id);
      } else {
        cancelAuthorization(intent.id);
      }
      break;

    case "success":
      // Payment successful - fulfill order
      fulfillOrder(intent.reference_id);
      sendConfirmationEmail(intent.purchaser_id);
      displaySuccessPage();
      break;

    case "failed":
      // Payment failed
      logFailure(intent.failure_reason);
      displayErrorPage("Payment failed. Please try again.");
      break;

    case "canceled":
      // Payment canceled
      displayCanceledPage();
      break;

    case "chargeback":
      // Chargeback filed
      alertFinanceTeam(intent);
      suspendOrder(intent.reference_id);
      break;

    default:
      console.error("Unknown status:", intent.status);
  }
}

Instead of polling the API for status:

example.js
// Not recommended
setInterval(async () => {
  const intent = await getIntent(intentId);
  handlePaymentStatus(intent);
}, 5000); // Poll every 5 seconds

Use webhooks for real-time updates:

example.js
// Recommended
app.post("/webhook", (req, res) => {
  const event = req.body;

  if (event.type === "StatusChange") {
    handlePaymentStatus(event.content);
  }

  res.status(200).send("OK");
});

Best Practices

✅ DO

  • Use webhooks for status updates instead of polling
  • Handle all possible statuses in your code
  • Only fulfill orders when status is success
  • Store status history for audit trails
  • Display user-friendly messages for each status

❌ DON'T

  • Rely on return_url query parameters for status
  • Fulfill orders on pending or authorized status
  • Ignore chargeback status
  • Poll the API excessively
  • Assume statuses can only move forward

Payment Intents

Learn about payment intents

Payment Attempts

Understand attempt lifecycle

Webhooks

Set up real-time notifications