2C2P Gateway Guide

Quick start

New to integrating 2C2P with Spreedly? Here's your setup guide:

  1. Retrieve your 2C2P credentials
    1. Log into your 2C2P merchant portal (or contact your 2C2P account manager)
    2. Obtain your merchant ID and secret key
    3. Download your 2C2P public certificate PEM and generate/upload your merchant private key PEM (and password if applicable)
  2. Get familiar with the Spreedly API
    1. Review Spreedly API basics
    2. Understand Gateways, Payment Methods, and Transactions
  3. Add a 2C2P gateway to Spreedly in sandbox mode
ℹ️

Review this documentation to understand more about how to test transactions.

Additional Information

2C2P uses two independent request-signing schemes depending on the operation. two_c_two_p_public_certificate_pem, merchant_private_key_pem, and merchant_jws_key_id are all required — the gateway will not initialize without them.

  • Payment token / inquiry / purchase are signed with symmetric JWT (HS256) using secret_key only.
  • Payment maintenance (capture, void, refund) uses asymmetric JWE + JWS (RSA-OAEP + A256GCM encryption, PS256 signing) with two_c_two_p_public_certificate_pem and merchant_private_key_pem.
  • Customer tokens (store, remove) use the same JWE + JWS scheme and place merchant_jws_key_id in the JWS kid header so 2C2P can select your registered public key.

Currency

The optional currency field sets the merchant account's default ISO currency (e.g. SGD, THB, VND, IDR). It is used as the fallback currency when a transaction does not specify currency_code.

Region

The optional region field controls endpoint routing and accepts two values:

ValueRoutes toUse for
standard2C2P standard PGW hostsThailand, Philippines, Vietnam
indonesia2C2P Indonesia (Alipay) PGW hostsIndonesia (IDR) merchants

If region is omitted or blank it defaults to standard. Any value other than standard or indonesia raises an ArgumentError at gateway creation. Indonesia (IDR) merchants must set region to indonesia explicitly — it is no longer inferred from the currency.

Supported onboarding countries: Thailand, Philippines, Vietnam, and Indonesia.

Supported card brands: Visa, Mastercard, American Express, JCB, Diners Club.

For stored credential (MIT) chaining, Spreedly maps 2C2P's paymentID (preferred) or schemePaymentID (fallback) as the network transaction identifier.

Certificates & keys

2C2P uses different credentials depending on the API being called. Understanding which credential signs which request avoids most 401 Unauthorized and gatewayCryptoError failures.

Credentials at a glance

CredentialWhat it isWho generates itUsed by
secret_key64-char hex HMAC-SHA256 shared secret2C2P (portal)JWT (HS256) signing on PGW JSON APIs
two_c_two_p_public_certificate_pem2C2P's X.509 cert (their RSA public key)2C2P (portal download)JWE encryption + JWS verification for PaymentAction & CustomerToken
merchant_private_key_pemYour 2048-bit RSA private key (PKCS#1 or PKCS#8)You (openssl)JWS signing + JWE decryption for PaymentAction & CustomerToken
merchant_jws_key_id32-char hex key identifier2C2P (on key upload)JWS kid header — CustomerToken only

Which API uses which scheme

OperationSigning schemeCredentials used
Payment token / inquiry / purchaseSymmetric JWT (HS256)secret_key only
Capture / void / refundAsymmetric JWE(JWS) — no kidtwo_c_two_p_public_certificate_pem + merchant_private_key_pem
Store / remove (customer token)Asymmetric JWE(JWS) — with kidBoth certs plus merchant_jws_key_id

The asymmetric flow is standard hybrid encrypt-then-sign: the request body is JWE-encrypted with 2C2P's public key (RSA-OAEP + A256GCM) so only 2C2P can read it, then JWS-signed with your private key (PS256) so 2C2P can verify authenticity. Responses invert the direction — 2C2P signs with their key and encrypts with yours; you verify with their cert and decrypt with your private key.

Adding a 2C2P gateway

Required Credentials

merchant_id: Your 2C2P merchant identifier
secret_key: Your 2C2P API secret key
two_c_two_p_public_certificate_pem: 2C2P public certificate PEM for JWE encryption and JWS verification
merchant_private_key_pem: Your merchant private key PEM for JWS signing and JWE decryption
merchant_jws_key_id: Key identifier placed in the JWS kid header so 2C2P can select your registered public key

Optional Credentials

currency (Optional): Default transaction currency (e.g. PHP, IDR, THB)
region (Optional): Regional routing profile (indonesia or standard). Defaults to standard when omitted. Indonesia (IDR) merchants must set this to indonesia explicitly — it is not inferred from the currency. Any value other than standard or indonesia is rejected at gateway creation.
merchant_private_key_pem_password (Optional): Password for the merchant private key PEM

Request

curl https://core.spreedly.com/v1/gateways.xml \
  -u 'C7cRfNJGODKh4Iu5Ox3PToKjniY:4UIuWybmdythfNGPqAqyQnYha6s451ri0fYAo4p3drZUi7q2Jf4b7HKg8etDtoKJ' \
  -H 'Content-Type: application/xml' \
  -d '<gateway>
        <gateway_type>two_c_two_p</gateway_type>
        <merchant_id>your_merchant_id</merchant_id>
        <secret_key>your_secret_key</secret_key>
        <currency>PHP</currency>
        <region>standard</region>
        <merchant_jws_key_id>your_merchant_jws_key_id</merchant_jws_key_id>
        <two_c_two_p_public_certificate_pem>your_2c2p_public_certificate_pem</two_c_two_p_public_certificate_pem>
        <merchant_private_key_pem>your_merchant_private_key_pem</merchant_private_key_pem>
        <merchant_private_key_pem_password>your_merchant_private_key_password</merchant_private_key_pem_password>
      </gateway>'

Response

<gateway>
  <token>SampleTokenHere</token>
  <gateway_type>two_c_two_p</gateway_type>
  <name>2C2P</name>
  <description nil="true"/>
  <merchant_id>your_merchant_id</merchant_id>
  <merchant_jws_key_id>your_merchant_jws_key_id</merchant_jws_key_id>
  <currency>PHP</currency>
  <region>standard</region>
  <characteristics>
    <supports_purchase type="boolean">true</supports_purchase>
    <supports_authorize type="boolean">true</supports_authorize>
    <supports_capture type="boolean">true</supports_capture>
    <supports_credit type="boolean">true</supports_credit>
    <supports_general_credit type="boolean">false</supports_general_credit>
    <supports_void type="boolean">true</supports_void>
    <supports_adjust type="boolean">false</supports_adjust>
    <supports_verify type="boolean">false</supports_verify>
    <supports_reference_purchase type="boolean">false</supports_reference_purchase>
    <supports_offsite_purchase type="boolean">false</supports_offsite_purchase>
    <supports_offsite_authorize type="boolean">false</supports_offsite_authorize>
    <supports_offsite_synchronous_purchase type="boolean">false</supports_offsite_synchronous_purchase>
    <supports_offsite_synchronous_authorize type="boolean">false</supports_offsite_synchronous_authorize>
    <supports_3dsecure_purchase type="boolean">false</supports_3dsecure_purchase>
    <supports_3dsecure_authorize type="boolean">false</supports_3dsecure_authorize>
    <supports_3dsecure_2_purchase type="boolean">true</supports_3dsecure_2_purchase>
    <supports_3dsecure_2_authorize type="boolean">true</supports_3dsecure_2_authorize>
    <supports_3dsecure_2_mpi_purchase type="boolean">true</supports_3dsecure_2_mpi_purchase>
    <supports_3dsecure_2_mpi_authorize type="boolean">true</supports_3dsecure_2_mpi_authorize>
    <supports_purchase_via_preauthorization type="boolean">false</supports_purchase_via_preauthorization>
    <supports_store type="boolean">true</supports_store>
    <supports_remove type="boolean">true</supports_remove>
    <supports_fraud_review type="boolean">false</supports_fraud_review>
    <supports_network_tokenization type="boolean">true</supports_network_tokenization>
    <supports_partial_capture type="boolean">false</supports_partial_capture>
    <supports_partial_credit type="boolean">false</supports_partial_credit>
    <supports_stored_credentials type="boolean">true</supports_stored_credentials>
    <supports_stored_stored_credentials type="boolean">false</supports_stored_stored_credentials>
    <supports_inquire_by_gateway_transaction_id type="boolean">false</supports_inquire_by_gateway_transaction_id>
    <supports_inquire_by_order_id type="boolean">false</supports_inquire_by_order_id>
    <supports_transaction_retry type="boolean">false</supports_transaction_retry>
  </characteristics>
  <credentials>
    <credential>
      <name>merchant_id</name>
      <value>your_merchant_id</value>
    </credential>
    <credential>
      <name>merchant_jws_key_id</name>
      <value>your_merchant_jws_key_id</value>
    </credential>
    <credential>
      <name>currency</name>
      <value>PHP</value>
    </credential>
    <credential>
      <name>region</name>
      <value>standard</value>
    </credential>
  </credentials>
  <gateway_specific_fields>
    <gateway_specific_field>payment_channel</gateway_specific_field>
    <gateway_specific_field>channel_code</gateway_specific_field>
    <gateway_specific_field>locale</gateway_specific_field>
  </gateway_specific_fields>
  <payment_methods>
    <payment_method>credit_card</payment_method>
    <payment_method>third_party_network_token</payment_method>
  </payment_methods>
  <state>retained</state>
  <redacted type="boolean">false</redacted>
  <sandbox type="boolean">true</sandbox>
</gateway>

Purchase

Request:

curl https://core.spreedly.com/v1/gateways/[gateway_token]/purchase.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "payment_method_token": "payment_method_token",
    "amount": 500,
    "currency_code": "SGD",
    "ip": "203.0.113.1",
    "email": "[email protected]",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "en"
      }
    }
  }
}'

Response:

{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "token": "44k22fahmpfxsu6e0ry",
    "transaction_type": "Purchase",
    "amount": 500,
    "currency_code": "SGD",
    "gateway_transaction_id": "14888196|8738291",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "respCode": "0000",
        "respDesc": "Success",
        "tranRef": "14888196",
        "referenceNo": "8738291",
        "invoiceNo": "PURCHASE_TEST_1718012345"
      }
    },
    "gateway_type": "two_c_two_p",
    "message": "Succeeded!"
  }
}

Authorize

Request:

curl https://core.spreedly.com/v1/gateways/[gateway_token]/authorize.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "payment_method_token": "payment_method_token",
    "amount": 200,
    "currency_code": "SGD",
    "ip": "203.0.113.1",
    "email": "[email protected]",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "en"
      }
    }
  }
}'

Response:

{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "token": "7k33gbcnqpfxsu6e1rz",
    "transaction_type": "Authorization",
    "amount": 200,
    "currency_code": "SGD",
    "gateway_transaction_id": "AUTH-TR|AUTH-RN",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "respCode": "0000",
        "respDesc": "Success",
        "tranRef": "AUTH-TR",
        "referenceNo": "AUTH-RN"
      }
    },
    "gateway_type": "two_c_two_p",
    "message": "Succeeded!"
  }
}

Capture

Request (capture a prior authorization token; PHP gateway with PEM credentials):

curl https://core.spreedly.com/v1/transactions/[authorization_token]/capture.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{}'

Response:

{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "Capture",
    "amount": 10000,
    "currency_code": "PHP",
    "gateway_transaction_id": "CAP-TR|CAP-RN",
    "gateway_type": "two_c_two_p",
    "message": "Succeeded!"
  }
}

Void

Request:

curl https://core.spreedly.com/v1/transactions/[authorization_token]/void.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{}'

Response:

{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "Void",
    "amount": 10000,
    "currency_code": "PHP",
    "gateway_transaction_id": "VOID-TR|VOID-RN",
    "gateway_type": "two_c_two_p",
    "message": "Succeeded!"
  }
}

Credit (refund)

Request (partial refund of 50.00 PHP):

curl https://core.spreedly.com/v1/transactions/[purchase_token]/credit.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "amount": 5000
  }
}'

Response:

{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "Credit",
    "amount": 5000,
    "currency_code": "PHP",
    "gateway_transaction_id": "REF-TR|REF-RN",
    "gateway_type": "two_c_two_p",
    "message": "Succeeded!"
  }
}

Customer tokens (store & unstore)

2C2P supports storing a card as a reusable customer token via the CustomerToken API (CustomerToken/add and CustomerToken/delete). This is the same JWE+JWS scheme as payment maintenance, but it additionally requires merchant_jws_key_id — 2C2P uses the kid in the JWS header to select which of your registered public keys to verify with. A store request against a gateway without merchant_jws_key_id is rejected.

Store

curl https://core.spreedly.com/v1/gateways/{gateway_token}/store.json \
  -u 'C7cRfNJGODKh4Iu53PToKjniY:4UIuWybmdythqAqyQnYha6s451ri0fYAo4p3drZUi7q2Jf4b7HKg8etDtoKJ' \
  -H 'Content-Type: application/json' \
  -d '{
        "transaction": {
          "payment_method_token": "{payment_method_token}",
          "currency_code": "IDR",
          "email": "[email protected]"
        }
      }'

Unstore

Remove the stored token (via redact against the storing gateway):

curl https://core.spreedly.com/v1/payment_methods/{stored_payment_method_token}/redact.json \
  -u 'C7cRfNJGODKh4Iu53PToKjniY:4UIuWybmdythqAqyQnYha6s451ri0fYAo4p3drZUi7q2Jf4b7HKg8etDtoKJ' \
  -H 'Content-Type: application/json' \
  -X PUT \
  -d '{
        "transaction": {
          "remove_from_gateway": "{gateway_token}"
        }
      }'

🚧 A 401 Unauthorized on store almost always means the public key registered against merchant_jws_key_id on the portal does not match the merchant_private_key_pem on the gateway. Confirm the kid and key pair using the MD5 check above.

Gateway specific fields

<gateway_specific_fields>
  <two_c_two_p>
    <payment_channel>CC</payment_channel>
    <channel_code>VI</channel_code>
    <locale>en</locale>
  </two_c_two_p>
</gateway_specific_fields>
  • payment_channel: 2C2P payment channel. Use CC for card PAN; network tokens may omit unless explicitly set (avoids MID-specific errors).
  • channel_code: Channel code within the payment channel.
  • locale: Locale for 2C2P API requests.
Field NameTypeRequiredUse Case (Description)Example Value
payment_channelStringNo2C2P payment channel identifier (e.g. CC for card, CSTOKEN for network token)CC
channel_codeStringNoChannel code within the selected payment channelVI
localeStringNoLocale for 2C2P request/response formattingen

Gateway specific response fields

A response from 2C2P may contain additional fields which you can find in the gateway_specific_response_fields. These currently include respCode, respDesc, tranRef, referenceNo, invoiceNo, amount, currencyCode, paymentID, and schemePaymentID.

For example:

<gateway_specific_response_fields>
  <two_c_two_p>
    <respCode>0000</respCode>
    <respDesc>Success</respDesc>
    <tranRef>TRF1</tranRef>
    <referenceNo>REF1</referenceNo>
    <invoiceNo>ORD_P_FLOW_1</invoiceNo>
    <amount>100.0</amount>
    <currencyCode>PHP</currencyCode>
    <paymentID>PAY-12345678</paymentID>
    <schemePaymentID>SCH-9</schemePaymentID>
  </two_c_two_p>
</gateway_specific_response_fields>
Field NameTypeDescription
respCodeString2C2P result code. Success values: 0000 / 00 (approved), 0001 (accepted/pending). Other notable codes: 1001 (3DS required), 1005 (QR scan pending for PromptPay), 2000 (settlement success for maintenance ops), 12 (not refundable), 32 (auto-settlement)
respDescStringHuman-readable result description
tranRefString2C2P transaction reference
referenceNoString2C2P reference number
invoiceNoStringMerchant order/invoice ID; used for inquiry and maintenance
amountDecimalConfirmed amount from payment inquiry (major units)
currencyCodeStringConfirmed currency from payment inquiry
paymentIDStringPayment ID for stored-credential MIT chaining
schemePaymentIDStringFallback payment ID when paymentID is absent
refundReferenceNoStringReference on refund/credit maintenance responses; used as gateway_transaction_id for credit transactions

gateway_transaction_id is derived as invoiceNo|referenceNo (from inquiry) or tranRef|referenceNo (from do-payment). For credit transactions it falls back to refundReferenceNo. paymentID is also persisted on the payment method as stored_credential_usage.two_c_two_p.network_transaction_id.

3DS transactions

2C2P supports 3D Secure transactions via Spreedly's 3DS Global framework.

Request (authenticated 3DS2 purchase):

curl https://core.spreedly.com/v1/gateways/[gateway_token]/purchase.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "payment_method_token": "payment_method_token",
    "amount": 500,
    "currency_code": "SGD",
    "ip": "203.0.113.1",
    "email": "[email protected]",
    "three_ds_version": "2.2.0",
    "three_ds": {
      "ecommerce_indicator": "05",
      "authentication_value": "AAABB3NTYWxBYWFBQUFBQUFBQUE=",
      "directory_server_transaction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    },
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "en"
      }
    }
  }
}'

Response:

{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "Purchase",
    "gateway_transaction_id": "3DS-TR|3DS-RN",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "respCode": "0000",
        "respDesc": "Success"
      }
    },
    "message": "Succeeded!"
  }
}

Third-party 3DS2 auth data

2C2P supports third-party 3DS2 authentication data via Spreedly's MPI framework for 3dsecure_2_mpi_purchase and 3dsecure_2_mpi_authorize transactions.

Stored Credentials

For 2C2P, sending stored credential fields can be done using Spreedly's first class support. For any Authorize or Purchase request, include:

  • stored_credential_initiator
  • stored_credential_reason_type

Spreedly chains subsequent MIT transactions using 2C2P's paymentID (preferred) or schemePaymentID as the network transaction identifier.

CIT (initial) request:

curl https://core.spreedly.com/v1/gateways/[gateway_token]/purchase.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "payment_method_token": "payment_method_token",
    "amount": 500,
    "currency_code": "SGD",
    "stored_credential_initiator": "cardholder",
    "stored_credential_reason_type": "recurring",
    "gateway_specific_fields": {
      "two_c_two_p": { "locale": "en" }
    }
  }
}'

CIT response (note paymentID persisted as network_transaction_id):

{
  "transaction": {
    "succeeded": true,
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "respCode": "0000",
        "paymentID": "PAY-12345678"
      }
    },
    "payment_method": {
      "stored_credential_usage": {
        "two_c_two_p": {
          "network_transaction_id": "PAY-12345678"
        }
      }
    }
  }
}

MIT (subsequent) request:

curl https://core.spreedly.com/v1/gateways/[gateway_token]/purchase.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "payment_method_token": "payment_method_token",
    "amount": 400,
    "currency_code": "SGD",
    "stored_credential_initiator": "merchant",
    "stored_credential_reason_type": "recurring",
    "gateway_specific_fields": {
      "two_c_two_p": { "locale": "en" }
    }
  }
}'

Learn more in our Stored Credentials guide.

Network Tokenization

While our Network tokenization framework provisions network tokens at Spreedly, your 2C2P merchant account must be correctly configured to use network tokens.

Please contact 2C2P support to enable network tokenization on your account.

Request:

curl https://core.spreedly.com/v1/gateways/[gateway_token]/purchase.json \
  -u 'EnvironmentKey:AccessSecret' \
  -H 'Content-Type: application/json' \
  -d '{
  "transaction": {
    "payment_method_token": "network_token_payment_method_token",
    "amount": 500,
    "currency_code": "SGD",
    "attempt_network_token": true,
    "gateway_specific_fields": {
      "two_c_two_p": { "locale": "en" }
    }
  }
}'

Response:

{
  "transaction": {
    "succeeded": true,
    "gateway_transaction_id": "NT-TR|NT-RN",
    "network_tokenization": {
      "attempted": true,
      "errors": []
    },
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "respCode": "0000",
        "respDesc": "Success"
      }
    },
    "message": "Succeeded!"
  }
}

Local Payment Methods

PromptPay

PromptPay is a QR-based bank transfer payment method in Thailand (THB). Through 2C2P's PPQR channel, merchants can initiate offsite purchases where the buyer scans a PromptPay QR code to complete payment. The flow is asynchronous: the transaction returns pending until 2C2P confirms payment via callback and payment inquiry.

Capabilities: Offsite purchase; refunds subject to 2C2P settlement policy
Flow: Offsite async (QR display → buyer scans → callback / status inquiry)

Create the payment method

Create a PromptPay payment method with payment_method_type set to prompt_pay and country set to TH. Optional identity fields are forwarded to 2C2P when present:

POST /v1/payment_methods.json
{
  "payment_method": {
    "payment_method_type": "prompt_pay",
    "first_name": "Somchai",
    "last_name": "Jaidee",
    "email": "[email protected]",
    "phone_number": "0812345678",
    "country": "TH"
  }
}
{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "AddPaymentMethod",
    "payment_method": {
      "token": "2K061GY27B9V3BV029DMH0ZK33",
      "payment_method_type": "prompt_pay",
      "country": "TH",
      "full_name": "Somchai Jaidee",
      "email": "[email protected]",
      "phone_number": "0812345678"
    }
  }
}

Run a purchase

Initiate an offsite purchase on your 2C2P gateway. Include callback_url and redirect_url so Spreedly can receive the final status and return the buyer to your site. You may also include payment_expiry (seconds) if your 2C2P merchant account supports QR expiry configuration.

POST /v1/gateways/{gateway_token}/purchase.json
{
  "transaction": {
    "payment_method_token": "{{payment_method_token}}",
    "amount": 15000,
    "currency_code": "THB",
    "order_id": "PP-1779906849",
    "redirect_url": "https://merchant.example.com/checkout/return",
    "callback_url": "https://merchant.example.com/spreedly/callbacks",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "th"
      }
    }
  }
}

The transaction returns in pending state. Render the PromptPay QR code from gateway_specific_response_fields.two_c_two_p.qrData:

{
  "transaction": {
    "succeeded": false,
    "state": "pending",
    "transaction_type": "OffsitePurchase",
    "order_id": "PP-1779906809",
    "amount": 15000,
    "currency_code": "THB",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "invoiceNo": "PP-1779906809",
        "respCode": "1005",
        "qrData": "00020101021230630016A00000067701011201150107537000017150208114004680308149655745204599953037645406150.005802TH59182C2P Test merchant6007Bangkok6241010811400468050811400468070110808Purchase63049D8D",
        "expiryTimerMs": "600000"
      }
    },
    "response": {
      "success": true,
      "message": "Pending for user scan QR.",
      "pending": true
    },
    "message": "Pending"
  }
}

Render the QR code from qrData for the buyer to scan. After payment, Spreedly receives the callback and runs payment inquiry to transition the transaction to succeeded or failed.

Refunds

After a PromptPay payment is captured and settled, issue a credit (refund) against the original transaction:

POST /v1/transactions/{transaction_token}/credit.json
{
  "transaction": {
    "amount": 10000,
    "currency_code": "THB",
    "callback_url": "https://merchant.example.com/spreedly/callbacks"
  }
}

Refund timing and partial refunds follow your 2C2P merchant agreement.

MoMo

MoMo is a mobile wallet payment method in Vietnam (VND). Through 2C2P's MOMO channel, merchants can initiate offsite purchases where the buyer completes payment in their MoMo app. The transaction returns pending until 2C2P confirms payment via callback.

Capabilities: Offsite purchase; refunds subject to 2C2P settlement policy
Flow: Offsite async (MoMo app redirect → buyer authorises → callback / status inquiry)

MoMo supports two token channels:

  • MOMO (default): Standard MoMo wallet flow
  • DPAY: MoMo D-Pay flow — set payment_channel on the payment method to DPAY to use this channel

Create the payment method

Create a MoMo payment method with payment_method_type set to momo and country set to VN. The phone_number field is forwarded to 2C2P as the MoMo account mobile number:

POST /v1/payment_methods.json
{
  "payment_method": {
    "payment_method_type": "momo",
    "first_name": "Nguyen",
    "last_name": "Van A",
    "email": "[email protected]",
    "phone_number": "+84912345678",
    "country": "VN"
  }
}
{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "AddPaymentMethod",
    "payment_method": {
      "token": "MOMO_PM_TOKEN_EXAMPLE",
      "payment_method_type": "momo",
      "country": "VN",
      "full_name": "Nguyen Van A",
      "email": "[email protected]",
      "phone_number": "+84912345678"
    }
  }
}

Run a purchase

Initiate an offsite purchase on your 2C2P gateway (Vietnam fixture / VND currency):

POST /v1/gateways/{gateway_token}/purchase.json
{
  "transaction": {
    "payment_method_token": "{{payment_method_token}}",
    "amount": 50000,
    "currency_code": "VND",
    "order_id": "MOMO-1779906849",
    "redirect_url": "https://merchant.example.com/checkout/return",
    "callback_url": "https://merchant.example.com/spreedly/callbacks",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "vi"
      }
    }
  }
}

The transaction returns in pending state with a redirect URL in gateway_specific_response_fields.two_c_two_p that the buyer uses to authorise payment in their MoMo app:

{
  "transaction": {
    "succeeded": false,
    "state": "pending",
    "transaction_type": "OffsitePurchase",
    "order_id": "MOMO-1779906849",
    "amount": 50000,
    "currency_code": "VND",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "invoiceNo": "MOMO-1779906849",
        "respCode": "1001",
        "respDesc": "Pending"
      }
    },
    "message": "Pending"
  }
}

After the buyer completes the payment, Spreedly receives the callback and runs payment inquiry to transition the transaction to succeeded or failed.

DANA

DANA is a digital wallet payment method in Indonesia (IDR). Through 2C2P's DANA channel, merchants can initiate offsite purchases where the buyer completes payment in their DANA app. The transaction returns pending until 2C2P confirms payment via callback.

Capabilities: Offsite purchase; refunds subject to 2C2P settlement policy
Flow: Offsite async (DANA app redirect → buyer authorises → callback / status inquiry)

The Indonesia regional endpoint is used automatically when the gateway currency is IDR or the region credential is set to indonesia.

Create the payment method

Create a DANA payment method with payment_method_type set to dana and country set to ID. The phone_number field is forwarded to 2C2P as the DANA account mobile number:

POST /v1/payment_methods.json
{
  "payment_method": {
    "payment_method_type": "dana",
    "first_name": "Budi",
    "last_name": "Santoso",
    "email": "[email protected]",
    "phone_number": "+6281234567890",
    "country": "ID"
  }
}
{
  "transaction": {
    "succeeded": true,
    "state": "succeeded",
    "transaction_type": "AddPaymentMethod",
    "payment_method": {
      "token": "DANA_PM_TOKEN_EXAMPLE",
      "payment_method_type": "dana",
      "country": "ID",
      "full_name": "Budi Santoso",
      "email": "[email protected]",
      "phone_number": "+6281234567890"
    }
  }
}

Run a purchase

Initiate an offsite purchase on your 2C2P gateway (Indonesia fixture / IDR currency):

POST /v1/gateways/{gateway_token}/purchase.json
{
  "transaction": {
    "payment_method_token": "{{payment_method_token}}",
    "amount": 50000,
    "currency_code": "IDR",
    "order_id": "DANA-1779906849",
    "redirect_url": "https://merchant.example.com/checkout/return",
    "callback_url": "https://merchant.example.com/spreedly/callbacks",
    "gateway_specific_fields": {
      "two_c_two_p": {
        "locale": "id"
      }
    }
  }
}

The transaction returns in pending state until the buyer completes payment in their DANA app:

{
  "transaction": {
    "succeeded": false,
    "state": "pending",
    "transaction_type": "OffsitePurchase",
    "order_id": "DANA-1779906849",
    "amount": 50000,
    "currency_code": "IDR",
    "gateway_specific_response_fields": {
      "two_c_two_p": {
        "invoiceNo": "DANA-1779906849",
        "respCode": "1001",
        "respDesc": "Pending"
      }
    },
    "message": "Pending"
  }
}

After the buyer completes the payment, Spreedly receives the callback and runs payment inquiry to transition the transaction to succeeded or failed.


Did this page help you?