2C2P Gateway Guide
Quick start
New to integrating 2C2P with Spreedly? Here's your setup guide:
- Retrieve your 2C2P credentials
- Log into your 2C2P merchant portal (or contact your 2C2P account manager)
- Obtain your merchant ID and secret key
- Download your 2C2P public certificate PEM and generate/upload your merchant private key PEM (and password if applicable)
- Get familiar with the Spreedly API
- Review Spreedly API basics
- Understand Gateways, Payment Methods, and Transactions
- 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_keyonly. - Payment maintenance (capture, void, refund) uses asymmetric JWE + JWS (RSA-OAEP + A256GCM encryption, PS256 signing) with
two_c_two_p_public_certificate_pemandmerchant_private_key_pem. - Customer tokens (store, remove) use the same JWE + JWS scheme and place
merchant_jws_key_idin the JWSkidheader 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:
| Value | Routes to | Use for |
|---|---|---|
standard | 2C2P standard PGW hosts | Thailand, Philippines, Vietnam |
indonesia | 2C2P Indonesia (Alipay) PGW hosts | Indonesia (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
| Credential | What it is | Who generates it | Used by |
|---|---|---|---|
secret_key | 64-char hex HMAC-SHA256 shared secret | 2C2P (portal) | JWT (HS256) signing on PGW JSON APIs |
two_c_two_p_public_certificate_pem | 2C2P's X.509 cert (their RSA public key) | 2C2P (portal download) | JWE encryption + JWS verification for PaymentAction & CustomerToken |
merchant_private_key_pem | Your 2048-bit RSA private key (PKCS#1 or PKCS#8) | You (openssl) | JWS signing + JWE decryption for PaymentAction & CustomerToken |
merchant_jws_key_id | 32-char hex key identifier | 2C2P (on key upload) | JWS kid header — CustomerToken only |
Which API uses which scheme
| Operation | Signing scheme | Credentials used |
|---|---|---|
| Payment token / inquiry / purchase | Symmetric JWT (HS256) | secret_key only |
| Capture / void / refund | Asymmetric JWE(JWS) — no kid | two_c_two_p_public_certificate_pem + merchant_private_key_pem |
| Store / remove (customer token) | Asymmetric JWE(JWS) — with kid | Both 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
CCfor 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 Name | Type | Required | Use Case (Description) | Example Value |
|---|---|---|---|---|
| payment_channel | String | No | 2C2P payment channel identifier (e.g. CC for card, CSTOKEN for network token) | CC |
| channel_code | String | No | Channel code within the selected payment channel | VI |
| locale | String | No | Locale for 2C2P request/response formatting | en |
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 Name | Type | Description |
|---|---|---|
| respCode | String | 2C2P 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) |
| respDesc | String | Human-readable result description |
| tranRef | String | 2C2P transaction reference |
| referenceNo | String | 2C2P reference number |
| invoiceNo | String | Merchant order/invoice ID; used for inquiry and maintenance |
| amount | Decimal | Confirmed amount from payment inquiry (major units) |
| currencyCode | String | Confirmed currency from payment inquiry |
| paymentID | String | Payment ID for stored-credential MIT chaining |
| schemePaymentID | String | Fallback payment ID when paymentID is absent |
| refundReferenceNo | String | Reference 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_initiatorstored_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_channelon the payment method toDPAYto 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.
Updated 10 days ago

