Payment Provider
LINE IAP isn't handled by PaymentProvider
In-app purchase (IAP) for LINE MINI Apps isn't handled by the Unifi Apps SDK PaymentProvider, which only supports CRYPTO and STRIPE payments.
To offer IAP, implement it directly with the LIFF SDK (liff.iap.*). IAP is available only for verified LINE MINI Apps in Japan and requires IAP permission granted by LY within the MINI App channel.
For details, see LINE's [Integrate the in-app purchase feature] guide.
https://developers.line.biz/en/docs/line-mini-app/in-app-purchase/implement-in-app-purchase
01. Payment Flow
Flowchart
Polling
Webhook
1. Triggering createPayment API from Unifi Apps
When a user requests a purchase, the Unifi App Client sends the item information to the Unifi App Server.
The Unifi App Server then calls the createPaymentAPI on the Unifi Payment Server.
The createPayment API responds with a payment ID in the format: { id: <payment_id> }.
Request where pgType: CRYPTO | STRIPE
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}
}'
2. Get paymentProvider from sdk instance.
The Unifi App 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 CONFIRMED
Server-side: Handle the payment status via a webhook event on the server.
When using webhooks, the Unifi 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 Unifi App 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 Unifi 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. Open payment history page
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
| Name | Type | Description |
|---|---|---|
| X-Client-Id | string(header), required | client id obtained from support team |
| X-Client-Secret | string(header), required | client secret obtained from support team |
Request Body
- Example Value
{
"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}
}
- Schema
{
buyerDappPortalAddress*: String,
pgType*: String(Enum: [STRIPE,CRYPTO]),
currencyCode: String(Enum: [USD,KRW,JPY,TWD,THB,KAIA,USDT]),
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,USDT]),
}],
testMode*: Boolean,
}
| field | limitation |
|---|---|
| buyerDappPortalAddress | Max length : 42 |
| pgType |
|
| currencyCode |
It should be equal to Item's currencyCode |
| price |
It should be equal to sum of each price of Items.
|
| paymentStatusChangeCallbackUrl for more detail | 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 for more detail | Max length : 512 |
| lockUrl for more detail | 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.
|
Items(*)
| field | limitation |
|---|---|
| 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 |
|
Responses
| HTTP Status Code | description |
|---|---|
| 200 | Example ValueSchema |
| 400 | Example ValueSchema |
| 401 | Example ValueSchema |
| 403 | Example ValueSchema |
| 500 | Example ValueSchema |
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
| Name | Type | Description |
|---|---|---|
| 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
| HTTP Status Code | Description |
|---|---|
| 200 | Example ValueSchema |
| 404 | Example ValueSchema |
| 401 | Example ValueSchema |
| 403 | Example ValueSchema |
| 500 | Example ValueSchema |
| field | limitation |
|---|---|
| id | |
| buyerDappPortalAddress | Max length : 42 |
| pgType |
|
| status | Payment status
|
| currencyCode |
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 | This value should be interpreted together with price. It represents the number of decimal places for the given currency. For example, it is 18 for KAIA, 6 for USDT, and 0 for KRW (Stripe payments). |
| cryptoGasFee | The gas fee of the blockchain used for crypto payments. |
| capturedAt | The timestamp when the payment was approved, in milliseconds since the Unix epoch.
|
| txHash | The transaction hash of the payment (applicable only for crypto payments). |
| 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 methods according to testMode are as follows.
|
| refund | |
| refund.type |
|
| refund.amount | Price policy follows here. |
| refund.chargebackStatus |
|
Items(*)
| field | limitation |
|---|---|
| itemIdentifier | Max length : 256 |
| name | Max length : 256 |
| imageUrl | Max length : 512 |
| price | For example, put 100(cent) if price is 1 Dollar. |
| currencyCode |
|
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
| Name | Type | Description |
|---|---|---|
| id | string(header), required | payment id |
Responses
| HTTP Status Code | Description |
|---|---|
| 200 | Example ValueSchema |
| 403 | Example ValueSchema |
| 404 | Example ValueSchema |
| 500 | Example ValueSchema |
| field | limitation |
|---|---|
| status | Payment status
|
Request Example
curl --location 'https://payment.dappportal.io/api/payment-v1/payment/status?id={payment_id}'
4. finalize payment
It will be ready to be settled after requesting finalize payment API.
You can't get settlement for STRIPE transaction if you did not request finalize payment API.
For CRYPTO, We recommend to request finalize payment API too.
Parameters
- N/A
Request Body
| Example Value | Schema |
|---|---|
| |
Responses
| HTTP Status Code | Description |
|---|---|
| 200 | N/A |
| 403 | Example ValueSchema |
| 403 | Example ValueSchema |
| 404 | Example ValueSchema |
| 500 | Example ValueSchema |
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 paymentProvider = sdk.getPaymentProvider();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
| Code | Description |
|---|---|
| -31001 | This error is triggered when a STRIPE payment gets canceled. |
| -31002 | |
| -32001 | This error is triggered when a CRYPTO payment gets canceled. |
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 lockUrl and unlockUrl is not needed, please enter null.
1. lock event
lockUrl is a server endpoint that is called to temporarily lock or reserve the item being purchased. This is typically required for items with limited quantity or NFTs, to prevent overselling and ensure that no other user can purchase the item while the current payment is in progress.
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
unlockUrl is a server endpoint that is called when a payment fails, is canceled, or times out, in order to release the previously locked item and make it available for others to purchase again.
If the unlockUrl parameter is included in the create payment request, a POST request will be sent to this endpoint when a payment failure-related event is triggered.
If the payment is successfully completed (status === CONFIRMED), the unlockUrl will not be called. Instead, the item is typically finalized via a separate process (finalize API or internal logic).
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. To avoid that the item remains locked, implementing background job to attempt unlocking again is recommended.
{
"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",
"cryptoPaymentInfo": { // added when "status" is CONFIRMED
"paymentTxHash": "0x.."
}
}
| Status | Description |
|---|---|
| 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 Unifi App |
| CONFIRM_FAILED | (Only for pgType = CRYPTO) Failed to check validity of the transaction after 10 blocks have confirmed from user's transaction request |
| FINALIZED | Payment process is done with payment approval and payment finalization from Unifi App - In case of pgType is CRYPTO, transactions will be automatically finalized after 5 minutes once it reaches 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 have 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.