Appearance
Step 4: Create Payment
Create one collection attempt for an unpaid order, using the customer-confirmed total.
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/paymentsRequest headers
| Field | Type | Required | Description |
|---|---|---|---|
X-Api-Key | string | Yes | Your API key; keep it on your backend. |
X-Timestamp | string | Yes | Current Unix time in seconds; within five minutes of server time. |
X-Nonce | string | Yes | Fresh identifier for every request, including retries. |
X-Signature | string | Yes | v1= 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.
| Field | Type | Required | Description |
|---|---|---|---|
Content-Type | string | Yes | application/json. |
Idempotency-Key | string | Yes | Unique attempt key; network retries reuse the admitted key. |
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
order_id | string | Yes | Existing unpaid order, with no pending or unresolved payment. |
customer_external_id | string | Yes | Your own customer ID as a non-empty string, matching the method lookup. No particular ID format is required. |
payment_method_code | string | Yes | Code from Payment Methods. |
return_url | string | Yes | Your HTTPS destination after checkout or authentication. See restrictions below. |
expected_payment_total | string | Yes | Positive two-place MYR total confirmed by the customer. |
customer_fee_percent | string | No | Percentage of the fee the customer bears, 0.00–100.00; defaults to 0.00 on each request. |
saved_payment_method_id | string | No | Saved option for this customer and method within your account. Mutually exclusive with card_details. |
save_payment_method | boolean | No | Request saving with customer consent; defaults to false. Currently supported for hosted new cards. Cannot be true with a saved method ID. |
card_details | object | Conditional | Required for approved direct card entry; omitted for hosted entry and saved methods. |
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.
Choose payment input
| Choice | Send | Customer action |
|---|---|---|
| Hosted checkout | Neither card_details nor saved_payment_method_id | Complete the selected method's checkout. |
| Save a hosted new card | save_payment_method: true, with customer consent | Complete checkout; refresh methods after saving succeeds. |
| Saved payment method | saved_payment_method_id | Complete any required authentication. |
| Direct new card | card_details, only when enabled | Enter details in your UI, then complete any required 3DS. |
Select a saved ID returned for the same customer and method. Unavailable or inaccessible IDs return validation_error. card_details is only for cards; unsupported saving requests are rejected. Requesting saving does not guarantee it has completed.
Request example — hosted 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"
}For a saved payment method, add its ID to the same request:
json
{
"saved_payment_method_id": "spm_example"
}Direct card entry
Activation required
Direct full-PAN entry is a proposed, gated capability. It requires PCI-DSS Level 1 compliance and account approval; Malaysia/MYR availability and the recovery protocol must be confirmed before use. Direct-card saving is not yet supported by this contract.
Your card form sends input through your backend → IIMMPACT. Keep API/HMAC credentials off the browser. Systems handling card input are in PCI scope: never log or durably store card_details, including in retry queues or request capture. Never retain CVV after authorization.
These are the proposed required fields within card_details, subject to activation validation:
| Field | Type | Required | Description |
|---|---|---|---|
card_number | string | Yes | Full PAN, digits only. |
expiry_month | string | Yes | Two-digit month, 01–12. |
expiry_year | string | Yes | Four-digit year; card must be unexpired. |
cvv | string | Yes | Card security code, digits only. |
cardholder_first_name | string | Yes | Cardholder's first name. |
cardholder_last_name | string | Yes | Cardholder's last name. |
cardholder_email | string | Yes | Valid cardholder email. |
Direct-entry request example with a documentation test card, not live card data:
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",
"card_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"
}
}Recovery: retain safe references and any returned payment ID, not the raw card request. Read a known payment after uncertainty; do not start another attempt or blindly resubmit card data. If the response is lost before an ID is received, the creation-key recovery protocol must be agreed during activation. Permanent replay must return the admitted attempt without storing CVV or initiating another collection; this guide does not yet prescribe a runnable recovery request for that case.
Response fields
Fields below are inside data.
| Field | Type | Description |
|---|---|---|
payment_id | string | Save for status checks. |
order_id | string | Order being paid. |
external_reference | string | Your order reference. |
customer_external_id | string | Payer identity for this attempt. |
status | string | Current collection state. |
currency | string | MYR. |
payment_method_code | string | Selected method. |
customer_fee_percent | string | Admitted percentage of the fee the customer bears, 0.00–100.00. |
fee | object | Admitted fee rule. |
totals | object | Frozen amount breakdown. |
action | object or null | Current redirect action; null when none is usable. Omitted from webhook snapshots. |
action.type | string | redirect, when action exists. |
action.url | string | HTTPS checkout or 3DS URL, when action exists. |
failure | object | Returned only for failed; omitted otherwise. |
failure.code | string | Failure code, e.g. collection_failed, when failure exists. |
failure.message | string | Safe display text, when failure exists. |
created_at | string | RFC3339 payment creation time. |
updated_at | string | RFC3339 time of the latest public payment change. |
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",
"status": "pending",
"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"
},
"created_at": "2026-09-14T10:05:00+08:00",
"updated_at": "2026-09-14T10:05:00+08:00"
}
}If initiation is uncertain, HTTP 202 returns the same schema with status: "processing" and action: null. Keep the payment ID and await an event or read; do not create a replacement.
Redirect and browser return
Follow the current action.url when present. It can open hosted checkout or 3DS authentication. When action is null, use the status to decide whether to wait or show the result.
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
| HTTP | Code | Action |
|---|---|---|
| 400 | idempotency_key_required | Supply the attempt key. |
| 404 | order_not_found | Check the order ID and access. |
| 409 | order_already_paid | Read the existing order; latest_payment_id identifies the winning collection. Do not charge again. |
| 409 | payment_outcome_unresolved | Wait for the existing attempt's outcome. Read latest_payment_id from the order, then check that payment's status. |
| 409 | idempotency_conflict | This key was already used with different request data. Check the existing payment before starting a new attempt. |
| 409 | total_mismatch | No payment created; confirm the fresh calculation below. |
| 422 | validation_error | Correct field errors, such as an unavailable saved method or an invalid fee percent. |
| 422 | payment_method_unavailable | Refresh methods and choose an available one. |
See shared errors.
Total mismatch fields
| Field | Type | Description |
|---|---|---|
error.details.expected_payment_total | string | The total you submitted. |
error.details.calculation | object | Full existing-order calculation: quote fields plus order_id. |
Show the new calculation.totals.payment_total and obtain confirmation before submitting a corrected request with a new key.
Cancel a payment
http
POST https://api.iimmpact.com/v2/payments/{payment_id}/cancelUse the same authentication headers, signing an empty body.
Path fields
| Field | Type | Required | Description |
|---|---|---|---|
payment_id | string | Yes | Your eligible pending payment. Successful collections cannot be cancelled. |
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
| Field | Type | Description |
|---|---|---|
data.payment_id | string | Payment being cancelled. |
data.order_id | string | Related order. |
data.external_reference | string | Your order reference. |
data.status | string | cancelled (200) or cancellation_pending (202). |
data.created_at | string | RFC3339 payment creation time. |
data.updated_at | string | RFC3339 latest payment change. |
Response example — 200
json
{
"data": {
"payment_id": "pay_example",
"order_id": "ord_example",
"external_reference": "ORD-00042",
"status": "cancelled",
"created_at": "2026-09-14T10:05:00+08:00",
"updated_at": "2026-09-14T10:06:00+08:00"
}
}Repeated calls return the existing cancellation result without resubmitting cancellation. Wait for a final outcome before retrying payment; collection can win a cancellation race.
| HTTP | Code | Meaning |
|---|---|---|
| 404 | payment_not_found | Missing or inaccessible payment. |
| 409 | payment_already_successful | Collection succeeded; cancellation cannot refund it. |
| 409 | payment_outcome_unresolved | Collection is still uncertain. |
| 409 | payment_already_terminal | Already failed or expired. |
| 409 | cancellation_not_supported | This payment cannot be cancelled. |
| 422 | validation_error | Invalid payment ID. |
Collection lifecycle and conflicts
| Status | Meaning |
|---|---|
pending | Waiting for customer action; blocks another attempt. |
processing | Initiation or collection is unresolved; blocks another attempt. |
cancellation_pending | Cancellation is unresolved; blocks another attempt. |
successful | Collection verified. |
failed, cancelled, expired | Confirmed unsuccessful outcome; a new attempt may be eligible. |
See expiry and retry rules. Next: Track Results.
Refunds
IIMMPACT handles refunds for failed fulfillment and duplicate collections. See Refunds for amounts and progress; refund data is returned on the order.
