Skip to content

Step 4: Create Payment ​

Create one collection attempt for an unpaid order, using the customer-confirmed total.

Save data.payment_id from the create-payment response (HTTP 201 or 202). Use it to call GET /v2/payments/{payment_id} and track the result. Do not create another payment just to check its status.

Before creating a payment

Call Calculate Order before each new payment attempt and confirm the returned total with the customer. This picks up changes to fees or the selected payment method. Send that total as expected_payment_total.

Endpoint ​

http
POST https://api.iimmpact.com/v2/payments

Request headers ​

FieldTypeRequiredDescription
X-Api-KeystringYesYour API key; keep it on your backend.
X-TimestampstringYesCurrent Unix time in seconds; within five minutes of server time.
X-NoncestringYesFresh identifier for every request, including retries.
X-SignaturestringYesv1= followed by the request's HMAC signature.

Follow API Key Authentication. Sign the exact body, or an empty body for bodyless requests, and the sorted query when present.

FieldTypeRequiredDescription
Content-TypestringYesapplication/json.
Idempotency-KeystringYesUnique key for this payment; reuse it for network retries.

Request fields ​

FieldTypeRequiredDescription
order_idstringYesExisting unpaid order, with no pending or unresolved payment.
customerobjectYesCustomer details. See customer profile.
customer.external_idstringYesYour customer ID. Use the same ID for payment-method lookup.
customer.namestringFor new customersCustomer's name. Omit if already provided.
customer.emailstringNoCustomer's email address, ASCII, up to 320 characters.
payment_method_codestringYesCode from Payment Methods.
return_urlstringYesYour HTTPS destination after checkout or authentication. See restrictions below.
expected_payment_totalstringYesPositive two-place MYR total confirmed by the customer.
customer_fee_percentstringNoPercentage of the fee the customer bears, 0.00–100.00; defaults to 0.00 on each request.
payment_method_detailsobjectConditionalRequired for FPX (option_code) and new CARD (flat card fields). Not required when using a saved card.
save_payment_methodbooleanNoTop-level saving request with customer consent; defaults to false. Applies to new CARD, subject to capability activation.
saved_payment_method_idstringNoTop-level saved-card selection for this customer and account. Do not combine with a new card number or save_payment_method: true.

return_url cannot contain userinfo, fragments, local/private destinations, or existing order_id/payment_id query keys, including encoded or case-insensitive variants. Other safe query parameters are retained. Use a destination you control and authorize order access on your backend.

Customer profile ​

Send customer details once. For later payments, send only:

json
{ "customer": { "external_id": "CUS-1042" } }

Existing customer details are reused, not overwritten. Supplied details fill missing values.

Choose payment input ​

ChoiceSendCustomer action
Personal FPXpayment_method_details.option_code from method optionsChoose a bank in your UI, then follow the bank redirect.
Saved CARDTop-level saved_payment_method_id; omit payment_method_detailsComplete any required authentication.
Direct new CARDFlat card fields inside payment_method_details, only when enabledEnter details in your UI, then complete any required 3DS.

Use a saved card returned for the same customer and method. To save a new card, set save_payment_method: true with customer consent. Do not combine a saved card with new card details or a saving request.

Request example — personal FPX ​

json
{
  "order_id": "ord_example",
  "customer": {
    "external_id": "CUS-1042",
    "name": "Aisha Rahman",
    "email": "aisha@example.com"
  },
  "payment_method_code": "FPX",
  "return_url": "https://shop.example.com/payment-return",
  "customer_fee_percent": "50.00",
  "expected_payment_total": "101.00",
  "payment_method_details": {
    "option_code": "CIMB_FPX"
  }
}

Use the actual calculated total, including customer fees. Personal FPX supports MYR 1.00–30,000.00, inclusive; business FPX is not supported. No bank credentials or bank-account numbers are collected by this API.

Request example — saved CARD ​

Select payment_method_code: "CARD", recalculate its total, and send the saved-method ID at the top level:

json
{
  "order_id": "ord_example",
  "customer": { "external_id": "CUS-1042" },
  "payment_method_code": "CARD",
  "return_url": "https://shop.example.com/payment-return",
  "customer_fee_percent": "50.00",
  "expected_payment_total": "100.85",
  "saved_payment_method_id": "01J9Z6Q2W8X4K7M3N5P1R2S3T4"
}

New card and cardholder details ​

Activation required

Direct full-PAN entry is a gated capability. It requires PCI-DSS Level 1 compliance and account approval. Setting save_payment_method: true requests saving; a saved method becomes usable only after verified success.

Your card form sends input through your backend → Pine Labs. Keep API/HMAC credentials off the browser. Systems handling card input are in PCI scope: never log or durably store card-bearing payment_method_details, including in retry queues or request capture. Never retain CVV after authorization.

These are the card fields directly within payment_method_details, subject to activation validation:

FieldTypeRequiredDescription
card_numberstringYesFull PAN, digits only.
expiry_monthstringYesTwo-digit month, 01–12.
expiry_yearstringYesFour-digit year.
cvvstringYesCard security code, digits only.
cardholder_first_namestringYesCardholder's first name.
cardholder_last_namestringYesCardholder's last name.
cardholder_emailstringYesValid cardholder email.
cardholder_phone_numberstringYesCardholder phone in E.164 format, for example +60123456789.

Collect the cardholder's first name, last name, email and phone when entering a new card, and send them inside payment_method_details. The customer profile does not replace these cardholder fields. Saved-card payments use saved_payment_method_id and do not require the cardholder fields again.

Example using a test card:

json
{
  "order_id": "ord_example",
  "customer": { "external_id": "CUS-1042" },
  "payment_method_code": "CARD",
  "return_url": "https://shop.example.com/payment-return",
  "customer_fee_percent": "50.00",
  "expected_payment_total": "100.85",
  "save_payment_method": false,
  "payment_method_details": {
    "card_number": "4000000000001091",
    "expiry_month": "12",
    "expiry_year": "2029",
    "cvv": "123",
    "cardholder_first_name": "Amira",
    "cardholder_last_name": "Hassan",
    "cardholder_email": "amira@example.com",
    "cardholder_phone_number": "+60123456789"
  }
}

Retry after a lost response: reuse the same key and customer object. For CARD, omit card-bearing payment_method_details; for FPX, retain the same payment_method_details.option_code. Once creation has resolved, its original response is returned. While creation is still in flight, 409 payment_creation_in_progress returns error.order_id and error.payment_id; use the payment ID to read the existing attempt. If fresh card input is required, collect it again securely; do not use stored card data.

Response fields ​

Fields below are inside data.

FieldTypeDescription
payment_idstringSave for status checks.
order_idstringOrder being paid.
external_referencestringYour order reference.
customer_external_idstringYour customer ID for this payment.
transaction_refidsstring[]Fulfillment references for the successful settlement winner. Empty before settlement and for duplicate collections that do not own fulfillment. These references do not imply fulfillment success.
statusstringCurrent collection state.
currencystringMYR.
payment_method_codestringSelected method.
customer_fee_percentstringPercentage of the fee the customer bears, 0.00–100.00.
feeobjectFee rule used for this payment.
totalsobjectFrozen amount breakdown.
actionobjectPresent when customer action is required; otherwise omitted.
action.typestringredirect, when action exists.
action.urlstringHTTPS URL for the customer to complete payment or authentication.
action.methodstringGET.
saved_payment_method_idstringReturned when this request saved a card with save_payment_method; omitted otherwise. Refresh Payment Methods to check availability.
failureobjectReturned only for failed; omitted otherwise.
failure.codestringProvider or system failure code, when failure exists. See failure codes; codes are not translated into a separate enum.
created_atstringUTC payment creation time.
updated_atstringUTC time of the latest public payment change.
expires_atstringUTC deadline for customer action. Check status for the payment outcome.

Card input and return destinations are never echoed. Fulfillment and refunds are on the order. A successful collection remains successful after a refund.

Response example — 201 ​

json
{
  "data": {
    "payment_id": "pay_example",
    "order_id": "ord_example",
    "external_reference": "ORD-00042",
    "customer_external_id": "CUS-1042",
    "transaction_refids": [],
    "status": "processing",
    "currency": "MYR",
    "payment_method_code": "CARD",
    "customer_fee_percent": "50.00",
    "fee": {
      "type": "percentage",
      "value": "1.70"
    },
    "totals": {
      "items_subtotal": "100.00",
      "fee": {
        "total": "1.70",
        "customer": "0.85",
        "merchant": "0.85"
      },
      "payment_total": "100.85"
    },
    "action": {
      "type": "redirect",
      "url": "https://payments.example/checkout/session",
      "method": "GET"
    },
    "created_at": "2026-09-14T02:05:00.000Z",
    "updated_at": "2026-09-14T02:05:00.000Z",
    "expires_at": "2026-09-14T02:35:00.000Z"
  }
}

HTTP 202 with status: "processing" means the outcome is not yet confirmed. Keep the payment ID and check its status; do not create a replacement.

Redirect and browser return ​

Open action.url when present so the customer can complete payment or authentication. Otherwise, follow the returned payment status.

For custom FPX, action.url redirects to the selected bank. Pine Labs binds the order/payment identifiers to your existing return_url and uses that URL for both success and failure returns. A browser return alone never settles a payment. Reusing an idempotency key with the same bank replays the admitted attempt; changing the bank with that key returns idempotency_conflict without another provider submission.

After checkout/authentication, the browser returns to this payment's return_url with order_id and payment_id. Those IDs are not authorization or proof of payment. Authenticate the customer and read current payment/order state.

Errors ​

HTTPCodeAction
400validation_errorFix the fields listed in error.details. Every invalid field is reported in one response. See validation details.
400payment_method_unavailableRefresh methods and choose an available one.
400payment_method_details_requiredSupply the method-specific details required for a new payment; if already supplied, contact support to check the configured field definitions.
400invalid_payment_amountFPX's calculated fee-inclusive total must be MYR 1.00–30,000.00.
400invalid_customerSupply a valid customer identity and the profile information required for this payment.
400customer_profile_incompleteSupply customer.name for this customer.
400invalid_card_detailsCorrect the fields named in error.details; field values are never echoed.
400invalid_payment_sourceDo not send payment_method_details.card_number together with saved_payment_method_id.
400provider_validation_errorThe provider rejected this attempt's details. Keep the returned order/payment IDs. Reusing the same key returns the same rejection; use a new key only for an explicitly corrected attempt.
404order_not_foundCheck the order ID and access.
404saved_payment_method_not_foundRefresh the saved methods for this customer and reseller account.
409payment_already_successfulRead the existing order and the known payment. Do not charge again.
409order_expiredThe order no longer accepts new payments. Continue tracking any existing payment.
409active_payment_existsWait for the existing attempt's outcome. Read it using the payment_id returned at creation; replay the same request/key first if that response was lost.
409payment_creation_in_progressRead GET /v2/payments/{payment_id} using error.payment_id; error.order_id identifies the order. Retry creation only with the same key/request; do not bypass it with a new charge.
409idempotency_conflictThis key was already used with different request data. Check the existing payment before starting a new attempt.
409total_mismatchNo payment created; confirm the fresh calculation below.
503provider_customer_unavailableThe customer payment profile is temporarily unavailable; retry the same request later.

A timeout or unresolved response is not a validation rejection. Retain the existing payment identity and follow its status rather than submitting a replacement charge.

See shared errors.

Total mismatch fields ​

error.details is the fresh calculation object for the order, including order_id and totals.

Show error.details.totals.payment_total and get customer confirmation before submitting the corrected request.

Cancel a payment ​

http
POST https://api.iimmpact.com/v2/payments/{payment_id}/cancel

Use the same authentication headers and sign an empty body. No Idempotency-Key is required. Cancellation does not refund a successful payment.

CARD payments can be cancelled until they succeed. FPX payments cannot be cancelled once the bank page has been opened, because the customer can still complete payment there; wait for the final status instead. An unpaid FPX payment expires at its expires_at.

Path fields ​

FieldTypeRequiredDescription
payment_idstringYesYour pending or processing CARD payment.

Request example ​

bash
curl --request POST "https://api.iimmpact.com/v2/payments/$PAYMENT_ID/cancel" \
  --header "X-Api-Key: $API_KEY" \
  --header "X-Timestamp: $TIMESTAMP" \
  --header "X-Nonce: $NONCE" \
  --header "X-Signature: v1=$SIGNATURE"

Response fields ​

Returns the same payment object inside data, with status: "cancelled" (200) or status: "cancellation_pending" (202).

Response example — 200 ​

json
{
  "data": {
    "payment_id": "pay_example",
    "order_id": "ord_example",
    "external_reference": "ORD-00042",
    "customer_external_id": "CUS-1042",
    "transaction_refids": [],
    "payment_method_code": "CARD",
    "status": "cancelled",
    "currency": "MYR",
    "customer_fee_percent": "50.00",
    "fee": { "type": "percentage", "value": "1.70" },
    "totals": {
      "items_subtotal": "100.00",
      "fee": { "total": "1.70", "customer": "0.85", "merchant": "0.85" },
      "payment_total": "100.85"
    },
    "created_at": "2026-09-14T02:05:00.000Z",
    "updated_at": "2026-09-14T02:06:00.000Z",
    "expires_at": "2026-09-14T02:35:00.000Z"
  }
}

Repeated calls return the existing cancellation result without resubmitting cancellation. Wait for a final outcome before retrying payment; collection can win a cancellation race.

Errors ​

HTTPCodeAction
404payment_not_foundCheck the payment ID; foreign payments also return 404.
409payment_already_successfulThe payment already succeeded and cannot be cancelled. error.order_id and error.payment_id identify it.
409payment_not_cancellableThe payment already failed or expired, or it is an FPX payment already sent to the bank. Read the payment for its final status. error.order_id and error.payment_id identify it.

Any other unresolved payment state returns 202 with cancellation_pending. See shared errors.

Collection lifecycle and conflicts ​

StatusMeaning
pendingWaiting for customer action; blocks another attempt.
processingInitiation or collection is unresolved; blocks another attempt.
cancellation_pendingCancellation is unresolved; blocks another attempt.
successfulCollection verified.
failed, cancelled, expiredConfirmed unsuccessful outcome; a new attempt may be eligible.

See expiry and retry rules. Next: Track Results.

Refunds ​

Pine Labs handles refunds for failed fulfillment and duplicate collections. See Refunds for amounts and progress; refund data is returned on the order.

Pine Labs API Documentation