Payment
01. Payment Flow
Flowchart
Polling

Webhook

1. Triggering createPayment API from Mini Dapp
When a user requests a purchase, the Mini Dapp Client sends the item information to the Mini Dapp Server.
The Mini Dapp Server then calls the createPayment
API on the Dapp Portal Payment Server.
The createPayment
API responds with a payment ID in the format: { id: <payment_id> }
.
Request
curl --location 'https://payment.dappportal.io/api/payment-v1/payment/create' \
--header 'X-Client-Id: {your_client_id}' \
--header 'X-Client-Secret: {your_client_secret}' \
--header 'Content-Type: application/json' \
--data '{
"buyerDappPortalAddress": "{user_wallet_address}", // user_wallet_address should be achieved through walletProvider.
"pgType": "{pg_type}",
"currencyCode": "{currency_code}",
"price": "{price}",
"paymentStatusChangeCallbackUrl": "{url_to_get_confirm_callback}",
"lockUrl": "{url_to_get_item_lock_callback}",
"unlockUrl": "{url_to_get_item_unlock_callback}",
"items": [
{
"itemIdentifier": "{your_item_identifier}",
"name": "{your_item_name}",
"imageUrl": "{your_item_image_url}",
"price": "{price}",
"currencyCode": "{currencyCode}"
}
],
"testMode": {true | false}
}'
Response
{
"id": {payment_id}
}
2. Get paymentProvider from sdk instance.
The Mini Dapp Client retrieves the PaymentProvider
instance from sdk with getPaymentProvider method.
const paymentProvider = sdk.getPaymentProvider()
3. Start payment process via paymentProvider.
Then, call the startPayment
method of the paymentProvider
, passing the paymentId
received from step 1 as a parameter.
await paymentProvider.startPayment(paymentId)
4. Check payment status and finalize payment.
There are two ways to check the payment status:
Client-side: Wait for the promise returned by the startPayment
method to resolve. When the promise resolves, the payment status is updated to FINALIZED
Server-side: Handle the payment status via a webhook event on the server.
When using webhooks, the Dapp Portal Payment Server sends an update to the paymentStatusChangeCallbackUrl
specified in the parameters of the createPayment
API whenever the payment status changes.
The update request is sent as an HTTP POST , and the body includes the updated payment status in the following format:
{
"paymentId": "{payment_id}",
"status": "CONFIRMED"
}
If the received status
is CONFIRMED
, the Mini Dapp Server should send a request to finalize the payment by calling the following API:
curl --location --request
POST 'https://payment.dappportal.io/api/payment-v1/payment/finalize' \
--header 'Content-Type: application/json' \
--data '{
"id": "{payment_id}"
}'
5. In case of payment canceled by system.
If an HTTP 200 OK
status is not returned in response to the webhook event, the event will be retried up to four times at exponential intervals: 1, 2, 4, and 8 seconds.
After all retry attempts have failed, if a lockUrl
was specified when the payment was created, the Dapp Portal Payment Server will send an unlock request as an HTTP POST . The body includes the paymentId and itemIdentifiers.
{
"paymentId": "{payment_id}",
"itemIdentifiers": ["{your_item_identifier}"]
}
This ensures that any reserved resources (e.g., NFT items, inventory) are released properly in case the payment status could not be confirmed via webhook.
6. You can open payment history page via paymentProvider. Promise will be completed if payment history page opens successfully.
await paymentProvider.openPaymentHistory()
02. Payment API
baseUrl for Payment API: https://payment.dappportal.io
1. create payment
post /api/payment-v1/payment/create Parameters
X-Client-Id *required string (header)
client id obtained from support team
X-Client-Secret *required string (header)
client secret obtained from support team
Request Body
{
"buyerDappPortalAddress": "{user_wallet_address}",
"pgType": "{pg_type}",
"currencyCode": "{currency_code}",
"price": "{price}",
"paymentStatusChangeCallbackUrl": "{url_to_get_status_change_callback_using_webhook}",
"lockUrl": "{url_to_get_item_lock_callback}",
"unlockUrl": "{url_to_get_item_unlock_callback}",
"items": [
{
"itemIdentifier": "{your_item_identifier}",
"name": "{your_item_name}",
"imageUrl": "{your_item_image_url}",
"price": "{price}",
"currencyCode": "{currencyCode}"
}
],
"testMode": {true | false}
}
{
buyerDappPortalAddress*: String,
pgType*: String(Enum: [STRIPE,CRYPTO]),
currencyCode*: String(Enum: [USD,KRW,JPY,TWD,THB,KAIA]),
price*: String,
paymentStatusChangeCallbackUrl: String,
lockUrl: String,
unlockUrl: String,
items*: [Item {
itemIdentifier: String,
name: String,
imageUrl: String,
price: String,
currencyCode: String(Enum: [USD,KRW,JPY,TWD,THB,KAIA]),
}],
testMode*: Boolean,
}
buyerDappPortalAddress
Max length : 42
pgType
STRIPE
CRYPTO
currencyCode
USD
KRW
JPY
TWD
THB
KAIA
It should be equal to Item's currencyCode
price
STRIPE
Put minimum unit following here
100(cent) should be put if price is $1
Case1. Minumum unit equals to price unit
10,000 KRW = 10000
10,000 JPY = 10000
Case2. Minimum unit no equals to price unit
10,000 USD = 1000000 (10,000 * 100)
10,000 THB = 1000000 (10,000 * 100)
10,000 TWD = 1000000 (10,000 * 100)
CRYPTO
Put price as KAIA unit
1 $KAIA = 1.0
It should be equal to sum of each price of Items. - Decimal Policy STRIPE : no CRYPTO : Up to 4 decimal places
paymentStatusChangeCallbackUrl
Max length : 512 You can receive webhook whenever payment status was changed if you set paymentStatusChangerCallbackUrl.
Please use port 443 for entire connection. Webhooks cannot be received if a port other than 443 is used.
lockUrl
Max length : 512
unlockUrl
Max length : 512
items(*)
Only the purchase of single item is supported under current version
testMode
The payment methods according to testMode are as follows. - testMode : false stripe : realmode crypto : kaia - testMode : true stripe : testmode crypto : kairos
Items(*)
itemIdentifier
Max length : 256
name
Max length : 256
imageUrl
Max length : 512
price
Put minimum unit following here
For example, put 100(cent) if price is 1 Dollar.
currencyCode
USD
KRW
JPY
TWD
THB
KAIA
Responses
200
Example Value
{
"payment_id": "{payment_id}"
}
Schema
{
payment_id*: String
}
400
Example Value
{
"code": 1001,
"detail": "Invalid argument",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
401
Example Value
{
"code": 1007,
"detail": "Invalid X-Client-Id or X-Client-Secret",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
403
Example Value
{
"code": 1007,
"detail": "Access denied due to country restrictions.",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
500
Example Value
{
"code": 500,
"detail": "Internal server error",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
Request Example
curl --location 'https://payment.dappportal.io/api/payment-v1/payment/create' \
--header 'X-Client-Id: {your_client_id}' \
--header 'X-Client-Secret: {your_client_secret}' \
--header 'Content-Type: application/json' \
--data '{
"buyerDappPortalAddress": "{user_wallet_address}",
"pgType": "{pg_type}",
"currencyCode": "{currency_code}",
"price": "{price}",
"paymentStatusChangeCallbackUrl": "{url_to_get_confirm_callback}",
"lockUrl": "{url_to_get_item_lock_callback}",
"unlockUrl": "{url_to_get_item_unlock_callback}",
"items": [
{
"itemIdentifier": "{your_item_identifier}",
"name": "{your_item_name}",
"imageUrl": "{your_item_image_url}",
"price": "{price}",
"currencyCode": "{currencyCode}"
}
],
"testMode": {true | false}
}'
2. get payment information
get /api/payment-v1/payment/info
Parameters
X-Client-Id *required string (header)
client id obtained from support team
X-Client-Secret *required string (header)
client secret obtained from support team
id *required
string (query)
payment id
responses
200
Example Value
{
"id":"{payment id}",
"buyerDappPortalAddress": "{user_wallet_address}",
"pgType": "{pg_type}",
"status": "{status}",
"currencyCode": "{currency_code}",
"price": "{price}",
"usdExchangeRate":"{Fx rate at the completion of payment. It is only returned where pgType is STRIPE and status is CONFIRMED or FINALIZED.}",
"usdExchangePrice":"{USD Price applied fx rate at the completion of payment. It is only returned where pgType is STRIPE and status is CONFIRMED or FINALIZED."}
"items": [
{
"itemIdentifier": "{your_item_identifier}",
"name": "{your_item_name}",
"imageUrl": "{your_item_image_url}",
"price": "{price}",
"currencyCode": "{currencyCode}"
}
],
"testMode": {true | false},
"refund": {
type: "REFUND",
amount: "1000.0000000000000000000000000000", //Price policy follows .
}
}
Schema
{
id*: String,
buyerDappPortalAddress*: string(maxLength: 42),
pgType*: string(Enum: [STRIPE,CRYPTO]),
status*: string(Enum: [CREATED,STARTED,REGISTERED_ON_PG,CAPTURED,CONFIRMED,CONFIRM_FAILED,FINALIZED,CANCELED])
currencyCode*: string(Enum: [USD,KRW,JPY,TWD,THB,KAIA]),
price*: string,
usdExchangeRate: string,
usdExchangePrice: string,
items*: [Item {
itemIdentifier: string(maxLength: 256),
name: string(maxLength: 256),
imageUrl: string(maxLength: 512),
price: string,
currencyCode: string(Enum: [USD,KRW,JPY,TWD,THB,KAIA]),
}]
testMode*: Boolean,
refund: {
type*: string(Enum:[CHARGEBACK, REFUND]),
amount*: string,
chargebackStatus: string(Enum:[NEEDS_RESPONSE, UNDER_REVIEW, WON, LOSE]),
}
}
404
Example Value
{
"code": 1002,
"detail": "Not found payment",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
401
Example Value
{
"code": 1007,
"detail": "Invalid X-Client-Id or X-Client-Secret Header is not included when payment status is CONFIRMED or FINALIZED.",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
403
Example Value
{
"code": 4030,
"detail": "Access denied due to country restrictions.",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
500
Example Value
{
"code": 500,
"detail": "Internal server error",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
id
buyerDappPortalAddress
Max length : 42
pgType
STRIPE
CRYPTO
status
Payment status >
CREATED
STARTED
REGISTERED_ON_PG
CAPTURED
CONFIRMED
CONFIRM_FAILED
FINALIZED
CANCELED
REFUNDED
CHARGEBACK
currencyCode
USD
KRW
JPY
TWD
THB
KAIA
It should be equal to Item's currencyCode
price
Put minimum unit as here. For example, put 100(cent) if price is 1 Dollar. It should be equal to sum of each price of Items. - Decimal Policy STRIPE : no CRYPTO : Up to 4 decimal places
usdExchangeRate
Fx rate at the completion of payment. It is only returned where pgType is STRIPE and status is CONFIRMED or FINALIZED.
usdExchangePrice
USD Price applied fx rate at the completion of payment. It is only returned where pgType is STRIPE and status is CONFIRMED or FINALIZED.
items(*)
Only the purchase of single item is supported under current version
testMode
The payment methos according to testMode are as follows. - testMode : false stripe : realmode crypto : kaia - testMode : true stripe : testmode crypto : kairos
refund
refund.type
CHARGEBACK
REFUND
refund.amount
Price policy follows here.
refund.chargebackStatus
NEEDS_RESPONSE: A CHARGEBACK has occurred, but it is in the state before contesting or challenging it.
UNDER_REVIEW: The dispute has been filed and is currently under review by STRIPE.
WON: If the dispute is accepted.
LOST: If the dispute result is not accepted
Items(*)
itemIdentifier
Max length : 256
name
Max length : 256
imageUrl
Max length : 512
price
For example, put 100(cent) if price is 1 Dollar.
currencyCode
USD
KRW
JPY
TWD
THB
KAIA
Request Example
curl --location 'https://payment.dappportal.io/api/payment-v1/payment/info?id={payment_id}' \
--header 'X-Client-Id: {your_client_id}' \
--header 'X-Client-Secret: {your_client_secret}'
3. get payment status
get /api/payment-v1/payment/status Parameters
id *required
string (query)
payment id
Responses
200
Example Value
{
"status": "{status}"
}
Schema
{
status*: String (Enum:[CREATED, STARTED, REGISTERED_ON_PG, CAPTURED, CONFIRMED_FAILED,FINALIZED,CANCELED]
}
403
Example Value
{
"code": 4030,
"detail": "Access denied due to country restrictions.",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
404
Example Value
{
"code": 1002,
"detail": "Not found payment",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
500
Example Value
{
"code": 500,
"detail": "Internal server error",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
status
Payment status >
CREATED
STARTED
REGISTERED_ON_PG
CAPTURED
CONFIRMED
CONFIRMED_FAILED
FINALIZED
CANCELED
REFUNDED
CHARGEBACK
Request Example
curl --location 'https://payment.dappportal.io/api/payment-v1/payment/status?id={payment_id}'
4. finalized payment
It will be ready to be settlement after requesting finalize payment API.
You can't get settlement for STRIPE transaction if you did not request finalize payment API. For CRYPTO, We recomment to request finalize payment API too. Parameters N/A
Request Body
{
id: "{payment_id}"
}
{
id*: String
}
Responses
200
N/A
403
Example Value
{
"code": 1004,
"detail": "Invalid payment status.",//Current payment status cannot complete payment finalization
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
403
Example Value
{
"code": 4030,
"detail": "Access denied due to country restrictions.",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
404
Example Value
{
"code": 1002,
"detail": "Not found payment",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
}
500
Example Value
{
"code": 500,
"detail": "Internal server error",
"cause": null
}
Schema
{
code*: number,
detail*: String,
cause: String
Request Example
curl --location 'https://payment.dappportal.io/api/payment-v1/payment/finalize
--header 'Content-Type: application/json' \
--data '{
"id": "{payment_id}"
}
03. PaymentProvider
sdk.getPaymentProvider()
Initializes the paymentProvider
, allowing developers to use various payment features.
const walletProvider = sdk.getWalletProvider();
Parameters
N/A
Responses
PaymentProvider
paymentProvider.startPayment()
Calling paymentProvider.startPayment()
displays a transaction window to the user and begins the payment process.
Parameters
payment_id *required · string
Responses
Promise<unknown>
Error
-31001
{
code: -31001,
message:'Payment is canceled by user or timeout'
}
-31002
{
code: -31002,
message:'Payment is failed'
}
paymentProvider.openPaymentHistory()
This method opens a payment history window. In order to complete the operation, a signature from the user is required.
Parameters
N/A
Responses
Promise<void>
04. Payment Webhook
Each Webhook's callback URL should be set differently.
If there are any unused Webhooks, please enter null
.
1. lock event
If the 'lockUrl' parameter is included in the create payment request, a POST method request for the lock webhook event will be made.
When the payment is initiated using the SDK’s startPayment function, the webhook event is requested just before starting the user’s payment flow if it is determined that the payment can proceed normally.
If a request is made with the 'lockUrl' but a 200 response is not received, the payment will be immediately canceled and marked as failed automatically.
{
"paymentId": "{payment_id}",
"itemIdentifiers": [
{
"{your_item_identifier}"
}
]
}
2. unlock event
If the 'unlockUrl' parameter is included in the create payment request, a POST method request for the unlock webhook event will be made.
If the payment is automatically canceled by the system, a webhook event will be requested.
If a request is made with the 'unlockUrl' but a 200 response is not received, it will be retried up to 5 times at intervals of 1, 2, 4 and 8 seconds. If a 200 response is still not received after retries of 5 times, the payment cancellation will proceed.
{
"paymentId": "{payment_id}",
"itemIdentifiers": [
{
"{your_item_identifier}"
}
]
}
3. payment status change event
When creating a payment request, a payment status change webhook event will be sent to the paymentStatusChangeCallbackUrl
you've provided.
An event occurs whenever the payment status changes, except when the status is CREATED.
If the status of the received webhook is CONFIRMED, you can call the finalize payment API.
If a request to the paymentStatusChangeCallbackUrl does not receive a 200 response, a maximum of 5 retries will be made at intervals of 1, 2, 4, and 8 seconds. Even if a 200 response is not received after 5 retries, the payment cancellation will not proceed.
{
"paymentId": "{payment_id}"
"status": "STARTED|REGISTERED_ON_PG|CAPTURED_CONFIRMED_CONFIRM_FAILED|FINALIZED|CANCELED|REFUNDED|CHARGEBACK"
}
05. Payment Status
CREATED
Hosted create payment API but not host startPayment of SDK
STARTED
Hosted startPayment but await payment approval from user (STRIPE/CRYPTO)
REGISTERED_ON_PG
(Only for pgType = CRYPTO) Transaction has been approved but await for enough block confirmation to prevent chain re-organization. - at least after 10 block confirmation from user's request for KAIA
CAPTURED
(Only for pgType = CRYPTO) Checking validity of the transaction after 10 blocks have confirmed from the user's transaction request
CONFIRMED
Payment approval is completed and await payment finalization from Mini Dapp
CONFIRM_FAILED
(Only for pgTye = CRYPTO) Failed to check validity of the transaction after 10 blocks have confirmed from user's transaction request
FINALIZED
Payment process is done with paymet approval and payment finialization from Mini Dapp - In case of pgType is CRYPTO, transactions will be automatically finalized after 5 minutes once it reachs the CONFIRMED status if the finalize payment API is not hosted.
CANCELED
Payment is cancelled as Policy for payment cancellation
REFUNDED
Payment has been refunded
CHARGEBACK
Users has claimed chargeback directly.
This Webhook message will be sent at each stage of the process, including the occurrence of a CHARGEBACK.
(*) This status can only be queried through the get payment information API.
Last updated