DANA Widget Binding
DANA Widget Binding lets you seamlessly integrate DANA payments into your platform, allowing customers to link their DANA accounts for faster, smoother transactions across all your services. Currently there are two available binding methods:
- Normal Binding: Users enter their DANA-registered phone number manually.
- Seamless Binding: The merchant securely passes the user's phone number (already registered on their platform) to DANA, skipping manual input.
You can initiate DANA account binding between mobile apps by using a Deeplink to open the DANA App from your mobile app for a seamless in-app experience. This flexibility helps enhance user experience while ensuring secure account linking across channels.
DANA Widget is also available for payments without account binding. Check our DANA Widget Overview for more details
Before you start
You will need to register your business in our Merchant Portal to obtain your testing credentials. After you have created your test account, make sure you have done the following:
- Finish your company registration and select Integrated Payment as your payment solution.
- Setup your webhooks & redirect URLs to receive payment outcomes & redirect user after payment.
- Obtain your testing credentials from the merchant portal.
User Experience
Below is a sample of the user experience for users paying using DANA Widget Binding. The checkout page is available using web browsers on mobile devices.
- Mobile

Bind with DANA Account
Connect customer’s DANA accounts with your own!

Simple binding process
Just provide the customer phone number and we’ll do the rest!

Provide seamless payment
Linked accounts can enjoy a more seamless payment experience.

Pay with DANA
Customers can pay using payment methods available in their DANA Account.

Instant Payment Result
You & your customers instantly receive payment result.
Process Flow
The general flow of payment using the DANA Widget Binding is as follows:
Visit the DANA Widget API Overview for edge cases and other scenarios.
- Binding
- Payment

- User begins DANA account binding with the merchant platform.
- Merchant calls Deeplink Binding to create redirect URL.
- Redirect customer to DANA App using the returned Deeplink URL.
- If merchant provides
seamlessData
parameter, the Seamless Binding process will be used and DANA will check if the user's phone number inside theseamlessData
matches with then account that is logged into their DANA app. If they match, DANA will direct to agreement page. - If the
seamlessData
user's phone number is different from the logged in account, DANA will require the user to log out of their current phone number and login or register using theseamlessData
phone number. - After successful authorization, DANA redirects the user to the agreement page.
- When merchant does not provide
seamlessData
, the Normal Binding process will be used and DANA will show the phone number input screen. - User enters phone number and do login or register.
- After successful authorization, DANA redirects the user to the agreement page.
- User makes an agreement to continue the process.
- DANA returns the result of binding process to DANA Server.
- DANA redirects the customer to the
redirectUrl
set by merchant along with the authCode. - Merchant exchanges the
authCode
for an accessToken by calling Apply Token API. - DANA returns the
accessToken
andrefreshToken
that are valid for each user. - The merchant stores
accessToken
andrefreshToken
.

- The user browses the merchant's website or app and proceeds to checkout after selecting a product.
- The merchant system generates an order internally, preparing it for payment processing.
- The merchant's backend sends a request to DANA's Direct Debit Payment API, passing the necessary order details.
- After successfully creating the order, DANA responds with a
webRedirectUrl
for the checkout page and order information. - In order to access the DANA Web page, merchant needs the OTT. To obtain the OTT, merchant needs to apply for it by calling DANA's Apply OTT API using the accessToken.
- DANA returns the OTT.
- Merchant appends the OTT from DANA into the
webRedirectUrl
that was obtained from Direct Debit Payment's response. - Merchant redirects the user to DANA Checkout page.
- DANA displays payment details to user and available payment methods.
- The user chooses one of the supported payment methods provided by DANA and follows the instructions on the DANA checkout page to complete the payment.
- DANA Web page receives the payment details and sends them to DANA API.
- DANA processes the payment.
- DANA shows payment result screen to user.
- DANA redirects the user back to the URL that the merchant specified when calling the Direct Debit Payment API. The redirect URL follows this format:
https:xxx?originalReferenceNo=xxx&originalPartnerReferenceNo=xxx&merchantId=xxxx&status=xxx
.- merchant redirect URL: set on
urlParams.url
- originalReferenceNo: Original transaction identifier on partner system
- originalPartnerReferenceNo: Original transaction identifier on DANA system
- merchantId: Merchant identifier that is unique per each merchant
- status: Payment transaction in DANA side
- Example: https://www.xxx.com/result/?originalReferenceNo=20250613111212800100166070954004283&originalPartnerReferenceNo=8562466e47144b5f82c003b47ae3c474&merchantId=216620000020928274717&status=SUCCESS)
- merchant redirect URL: set on
- If merchant adds urlParams.type =
NOTIFICATION
, DANA will send a payment notification to the merchant's system via the Finish Notify API, updating the payment status of the order.
- NodeJS
- Go
- PHP
Step 1 : Library Installation
Visit our Libraries & Plugins guide for detailed information on our SDK.
DANA provides server-side API libraries for several programming languages, available through common package managers, for easier installation and version management. Follow the guide below to install our library:
Requirements
- Node.js version 18 or later
- Your testing credentials from the merchant portal.
Installation
Install using npm or visit our Githubnpm install dana-node@latest --save
Set up the env
PRIVATE_KEY or PRIVATE_KEY_PATH # Your private key
ORIGIN # Your application's origin URL
X_PARTNER_ID # clientId provided during onboarding
ENV # DANA's environment either 'sandbox' or 'production'
Obtaining merchant credentials: Authentication
Step 2 : Initialize the library
Visit our Authentication guide to learn about the authentication process when not using our Library.
Follow the guide below to initialize the library
import { Dana, WidgetApi as WidgetApiClient } from 'dana-node';
const danaClient = new Dana({
partnerId: "YOUR_PARTNER_ID", // process.env.X_PARTNER_ID
privateKey: "YOUR_PRIVATE_KEY", // process.env.X_PRIVATE_KEY
origin: "YOUR_ORIGIN", // process.env.ORIGIN
env: "sandbox", // process.env.DANA_ENV or process.env.ENV or "sandbox" or "production"
});
const { WidgetApi } = danaClient;
Step 3 : Bind to DANA Account
Generate a redirect URL for DANA account binding. Users will be directed to the DANA App where they can complete the binding process. Configure the OAuth URL as shown below:
import { WidgetUtils } from 'dana-node/widget/v1';
// Generate OAuth URL
const oauth2UrlData = {
redirectUrl: 'https://your-redirect-url.com',
externalId: 'your-external-id', // or use uuidv4()
merchantId: process.env.MERCHANT_ID,
seamlessData: {
mobileNumber: '08xxxxxxxxx' // Optional
}
};
const oauthUrl = WidgetUtils.generateOauthUrl(oauth2UrlData);
console.log(oauthUrl);
The above code redirects users to the DANA App authorization page. By providing the user's phone number in seamlessData
, you can streamline the experience so users don't need to enter their number manually.line the experience so users don't need to enter their number manually.
After successful authorization, the user will be redirected to your specified redirectUrl
with an authCode
that expires in 10 minutes. Example:
https://www.merchant.com/oauth/callback?responseCode=2001000&responseMessage=Successful&authCode=xxx&state=2345555
Step 4 : Exchange the authCode into accessToken
After obtaining the authCode, exchange it for an accessToken using the Apply Token API. The returned accessToken
and refreshToken
both have a 3-year validity period. Once expired, users must rebind their DANA account.
import { Dana } from 'dana-node';
const danaClient = new Dana({
// .. initialize client with authentication
});
const { WidgetApi } = danaClient;
const request: ApplyTokenRequest = {
// Fill in required fields here, refer to Apply Token API Detail
};
const response: ApplyTokenResponse = await WidgetApi.applyToken(request);
Step 5 : Use the Direct Debit Payment API to get a hosted checkout URL
Use the Direct Debit Payment API to create new payment requests which will then return the Checkout URL of the hosted payment page.
To create a new order, make a POST request to the Direct Debit Payment API:
import { Dana } from 'dana-node';
// .. initialize client with authentication
const request: WidgetPaymentRequest = {
// Fill in required fields here, refer to Direct Debit Payment API Detail
};
const response: WidgetPaymentResponse = await WidgetApi.widgetPayment(request);
If successful, the response will include the URL for the DANA's payment page. For example:
Content-Type: application/json
X-TIMESTAMP: 2020-12-23T08:31:11+07:00
{
"responseCode": "2005400", // Refer to response code list
"responseMessage": "Successful", // Refer to response code list
"referenceNo": "2020102977770000000009", // Transaction identifier on DANA system
"partnerReferenceNo": "2020102900000000000001", // Transaction identifier on partner system
"webRedirectUrl": "https://pjsp.com/universal?bizNo=REF993883&...",
"additionalInfo":{}
}
Step 6 : Access DANA's page by hitting Apply OTT API
To access DANA's payment page, convert the user's access token to a one-time token (OTT) via the Apply OTT API. This token has a 10-minute expiration period and can be used only once.
import { Dana } from 'dana-node';
const danaClient = new Dana({
// .. initialize client with authentication
});
const { WidgetApi } = danaClient;
const request: ApplyOTTRequest = {
// Fill in required fields here, refer to Apply OTT API Detail
};
const response: ApplyOTTResponse = await WidgetApi.applyOTT(request);
Step 7 : Redirect to DANA's checkout page
Generate a payment URL by combining the webRedirectUrl
from the Direct Debit Payment API with an OTT token from the Apply OTT API.
import { Util } from 'dana-node/widget/v1';
import { WidgetPaymentResponse, ApplyOTTResponse } from 'dana-node/widget/v1/models';
// Example response from createWidgetPayment
const widgetPaymentResponse = new WidgetPaymentResponse({
webRedirectUrl: 'https://example.com/payment?token=abc123'
}); // this should be from createPayment Widget API
// Example response from applyOTT
const applyOTTResponse = new ApplyOTTResponse({
userResources: [
{
value: 'ott_token_value'
}
]
}); // this should be from applyOTT Widget API
// Generate the payment URL
const paymentUrl = Util.generateCompletePaymentUrl(widgetPaymentResponse, applyOTTResponse);
Optional Query Order Status, Cancel Order, Refund Order, and Balance Inquiry
There are additional APIs available to enhance your integration process:
- Query Payment API - Use this API to inquire the latest status of a payment request.
- Cancel Order API - For unpaid orders or those paid within 24 hours, a full refund returns to the customer's original payment source.
- Refund Order API - Process refunds for completed orders. You can trigger a refund request on behalf of the customer using this API, who will then process the refund through DANA.
- Balance Inquiry API - Implement this API to verify the customer's balance before order creation. Disabling payment methods with insufficient funds will increase your order success rate.
import { Dana } from 'dana-node';
const danaClient = new Dana({
// .. initialize client with authentication
});
const { WidgetApi } = danaClient;
const request: QueryPaymentRequest = {
// Fill in required fields here, refer to Query Payment API Detail
};
const response: QueryPaymentResponse = await WidgetApi.queryPayment(request);
import { Dana } from 'dana-node';
const danaClient = new Dana({
// .. initialize client with authentication
});
const { WidgetApi } = danaClient;
const request: CancelOrderRequest = {
// Fill in required fields here, refer to Cancel Order API Detail
};
const response: CancelOrderResponse = await WidgetApi.cancelOrder(request);
import { Dana } from 'dana-node';
const danaClient = new Dana({
// .. initialize client with authentication
});
const { WidgetApi } = danaClient;
const request: RefundOrderRequest = {
// Fill in required fields here, refer to Refund Order API Detail
};
const response: RefundOrderResponse = await WidgetApi.refundOrder(request);
import { Dana } from 'dana-node';
const danaClient = new Dana({
// .. initialize client with authentication
});
const { WidgetApi } = danaClient;
const request: BalanceInquiryRequest = {
// Fill in required fields here, refer to Balance Inquiry API Detail
};
const response: BalanceInquiryResponse = await WidgetApi.balanceInquiry(request);
Step 8 : Receive Payment Outcome
After a successful payment:
-
Notification: The user will be redirected to your specified Redirect URL, which you can configure using the
urlParams
parameter in the Direct Debit Payment API request, the redirection URL has a format like:https:xxx?originalReferenceNo=xxx&originalPartnerReferenceNo=xxx&merchantId=xxxx&status=xxx
- merchant redirect URL: set on
urlParams.url
- originalReferenceNo: Original transaction identifier on partner system
- originalPartnerReferenceNo: Original transaction identifier on DANA system
- merchantId: Merchant identifier that is unique per each merchant
- status: Payment transaction in DANA side
- Example: https://www.xxx.com/result/?originalReferenceNo=20250613111212800100166070954004283&originalPartnerReferenceNo=8562466e47144b5f82c003b47ae3c474&merchantId=216620000020928274717&status=SUCCESS
- merchant redirect URL: set on
-
[Optional] Finish Notify: In case you add
urlParams.type = NOTIFICATION
, DANA will send payment notifications to your Notification URL via the Finish Notify API. Configure your notification endpoint with the ASPI-mandated path format:/v1.0/debit/notify
.
Construction
new WebhookParser(publicKey?: string, publicKeyPath?: string)
Request
Parameter | Type | Remarks |
---|---|---|
publicKey | string | The DANA gateway's public key as a PEM formatted string. This is used if publicKeyPath is not provided or is empty |
publicKeyPath | string | The file path to the DANA gateway's public key PEM file. If provided, this will be prioritized over the publicKey string |
Notes: One of publicKey
or publicKeyPath
must be provided.
Method
parseWebhook(httpMethod: string, relativePathUrl: string, headers: { [key: string]: string }, body: string): FinishNotifyRequest
Request
Parameter | Type | Remarks |
---|---|---|
httpMethod | string | The HTTP method of the incoming webhook request e.g., http.MethodPost |
relative_path_url | string | The relative URL path of the webhook endpoint that received the notification e.g /v1.0/debit/notify |
headers | map[string]string | A map containing the HTTP request headers. This map must include X-SIGNATURE and X-TIMESTAMP headers provided by DANA for signature verification |
body | string | The raw JSON string payload from the webhook request body |
- Returns: A pointer to a
FinishNotifyRequest
struct containing the parsed and verified webhook data, or an error if parsing or signature verification fails. - Raises:
ValueError
if signature verification fails or the payload is invalid.
Security Notes
- Always use the official public key provided by DANA for webhook verification.
- Reject any webhook requests that fail signature verification or have malformed payloads.
- Never trust webhook data unless it passes verification.
import { WebhookParser } from 'dana-node/dist/webhook'; // Adjust import path as needed
async function handleDanaWebhook(req: AnyRequestType, res: AnyResponseType) {
// Retrieve the DANA public key from environment variables or a secure configuration.
// Option 1: Public key as a string
const danaPublicKeyString: string | undefined = process.env.DANA_WEBHOOK_PUBLIC_KEY_STRING;
// Option 2: Path to the public key file (recommended for production)
const danaPublicKeyPath: string | undefined = process.env.DANA_WEBHOOK_PUBLIC_KEY_PATH;
if (!danaPublicKeyString && !danaPublicKeyPath) {
console.error('DANA webhook public key not configured.');
res.status(500).send('Webhook processor configuration error.'); // Or appropriate error handling
return;
}
const httpMethod: string = req.method!; // e.g., "POST"
const relativePathUrl: string = req.path!; // e.g., "/v1.0/debit/notify". Ensure this is the path DANA signs.
const headers: Record<string, string> = req.headers as Record<string, string>;
let requestBodyString: string;
if (typeof req.body === 'string') {
requestBodyString = req.body;
} else if (req.body && typeof req.body === 'object') {
requestBodyString = JSON.stringify(req.body);
} else {
console.error('Request body is not a string or a parseable object.');
res.status(400).send('Invalid request body format.');
return;
}
// Initialize WebhookParser.
const parser = new WebhookParser(danaPublicKeyString, danaPublicKeyPath);
try {
// Verify the signature and parse the webhook payload
const finishNotify = parser.parseWebhook(
httpMethod,
relativePathUrl,
headers,
requestBodyString
);
console.log('Webhook verified successfully:');
console.log('Original Partner Reference No:', finishNotify.originalPartnerReferenceNo);
// TODO: Process the finishNotify object (e.g., update order status in your database)
res.status(200).send('Webhook received and verified.');
} catch (error: any) { // Catching as 'any' to access error.message
console.error('Webhook verification failed:', error.message);
// Respond with an error status. DANA might retry if it receives an error.
res.status(400).send(`Webhook verification failed: ${error.message}`);
}
}
For detailed example, please refer to the following resource: Example Webhook.
Example of a successful payment webhook payload:
Content-Type: application/json
X-TIMESTAMP: 2024-12-23T09:10:11+07:00
{
"responseCode": "2005400", // Refer to response code list
"responseMessage": "Successful", // Refer to response code list
"referenceNo": "2020102977770000000009", // Transaction identifier on DANA system
"partnerReferenceNo": "2020102900000000000001", // Transaction identifier on partner system
"webRedirectUrl": "https://pjsp.com/universal?bizNo=REF993883&..."
...
}
Additional Enum Configuration
The library provides several enums (enumerations) to represent a fixed set of constant values, ensuring consistency and reducing errors during integration.
import { EnvInfoSourcePlatformEnum } from 'dana-node/dist/widget/v1';
const ipg = EnvInfoSourcePlatformEnum.Ipg;
The following enums are available in the Library DANA Widget:
- AcquirementStatusEnum
- ActorTypeEnum
- GrantTypeEnum
- OrderTerminalTypeEnum
- PayMethodEnum
- PayOptionEnum
- PromoTypeEnum
- ResourceTypeEnum
- ResultStatusEnum
- ServiceScenarioEnum
- ServiceTypeEnum
- SourcePlatformEnum
- TerminalTypeEnum
- TypeEnum
Optional Revoke DANA's user account
Use the Account Unbinding API to remove the connection between your platform and a user's DANA account. For specific users, contact DANA directly.
import { Dana } from 'dana-node';
const danaClient = new Dana({
// .. initialize client with authentication
});
const { WidgetApi } = danaClient;
const request: AccountUnbindingRequest = {
// Fill in required fields here, refer to Account Unbinding API Detail
};
const response: AccountUnbindingResponse = await WidgetApi.accountUnbinding(request);
Step 9 : Automated UAT Testing Suite
To verify your integration, run our automated test suite. It takes under 2 minutes to tests your integration with mandated test scenarios. Check out the Github repo for more instructions
Step 10 : Apply for Live Payment
As part of regulatory compliance, merchants are required to submit UAT testing documents to meet Bank Indonesia's requirements. After completing sandbox testing, follow these steps to move to production:
Generate production keys
Create your production private and public keys, follow this instruction: Authentication - Production Credential.Confirm UAT testing logs
Confirm that you have completed all testing scenarios from our Merchant Portal.Fill go-live submission form
Follow the instructions inside our Merchant Portal to apply for production credentials. We will process your application in 1-2 days.Obtain production credentials
Once approved, you will receive your production credentials such as: Merchant ID, Client ID known as X-PARTNER-ID, and Client Secret.
Testing in production environment
Configure production environment
Switch your application settings from sandbox to production environment by updating the API endpoints and credentials.Test using production credentials
Conduct the same testing scenarios as sandbox testing, using your production credentials.UAT production sign-off
Once testing is complete, DANA will prepare the UAT Production Sign Off document in the Merchant Portal. Both merchant and DANA representatives must sign this document to formally approve the integration.Go-live
After receiving all approvals, your DANA integration will be activated and ready for live payments from your customers.
Ready to submit testing documents?
Access our merchant portal for detailed guide to start receiving live payments
Step 1 : Library Installation
Visit our Libraries & Plugins guide for detailed information on our SDK.
DANA provides server-side API libraries for several programming languages, available through common package managers, for easier installation and version management. Follow the guide below to install our library:
Requirements
- go.mod
- go.sum file
- Your testing credentials from the merchant portal.
Installation
Install or visit our Githubgo get github.com/dana-id/dana-go
Set up the env
PRIVATE_KEY or PRIVATE_KEY_PATH # Your private key
ORIGIN # Your application's origin URL
X_PARTNER_ID # clientId provided during onboarding
ENV # DANA's environment either 'sandbox' or 'production'
Obtaining merchant credentials: Authentication
Import Package
import (
widget "github.com/dana-id/dana-go/widget/v1"
)
Step 2 : Initialize the library
Visit our Authentication guide to learn about the authentication process when not using our Library.
Follow the guide below to initialize the library
package main
import (
"context"
"fmt"
"os"
dana "github.com/dana-id/dana-go"
"github.com/dana-id/dana-go/config"
widget "github.com/dana-id/dana-go/widget/v1"
)
func main() {
configuration := config.NewConfiguration()
// Set API keys
configuration.APIKey = &config.APIKey{
ENV: config.ENV_SANDBOX, // environment either 'sandbox' or 'production'
X_PARTNER_ID: os.Getenv("X_PARTNER_ID"), // known as clientId
PRIVATE_KEY: os.Getenv("PRIVATE_KEY"), // obtained from Merchant Portal
ORIGIN: os.Getenv("ORIGIN"), // Origin domain
// PRIVATE_KEY_PATH: os.Getenv("PRIVATE_KEY_PATH"),
}
apiClient := dana.NewAPIClient(configuration)
}
Step 3 : Bind to DANA Account
Generate a redirect URL for DANA account binding. Users will be directed to the DANA App where they can complete the binding process. Configure the OAuth URL as shown below:
package main
import (
"fmt"
"github.com/dana-team/dana-go-api-client/widget/v1"
"github.com/dana-team/dana-go-api-client/widget/v1/model"
)
func main() {
// Set up OAuth2 URL data
oauth2UrlData := &model.Oauth2UrlData{
RedirectUrl: "https://google.com",
MerchantId: merchantId,
SeamlessData: map[string]interface{}{
"mobileNumber": "087875849373",
},
}
// Generate the OAuth URL
oauthUrl := widget.GenerateOauthUrl(oauth2UrlData, privateKey)
fmt.Println("Generated OAuth URL:", oauthUrl)
}
The above code redirects users to the DANA App authorization page. By providing the user's phone number in seamlessData
, you can streamline the experience so users don't need to enter their number manually.line the experience so users don't need to enter their number manually.
After successful authorization, the user will be redirected to your specified redirectUrl
with an authCode
that expires in 10 minutes. Example:
https://www.merchant.com/oauth/callback?responseCode=2001000&responseMessage=Successful&authCode=xxx&state=2345555
Step 4 : Exchange the authCode into accessToken
After obtaining the authCode, exchange it for an accessToken using the Apply Token API. The returned accessToken
and refreshToken
both have a 3-year validity period. Once expired, users must rebind their DANA account.
package main
import (
"context"
"fmt"
"os"
dana "github.com/dana-id/dana-go"
"github.com/dana-id/dana-go/config"
widget "github.com/dana-id/dana-go/widget/v1"
)
func main() {
// ... define authentication
request := widget.ApplyTokenRequest{
// Fill in required fields here, refer to Apply Token API Detail
}
_, r, err := apiClient.WidgetAPI.ApplyToken(context.Background()).ApplyTokenRequest(request).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `WidgetAPI.ApplyToken``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}
// response from `ApplyToken`: ApplyTokenResponse
fmt.Fprintf(os.Stdout, "Response from `WidgetAPI.ApplyToken`: %v\n", r.Body)
}
Step 5 : Use the Direct Debit Payment API to get a hosted checkout URL
Use the Direct Debit Payment API to create new payment requests which will then return the Checkout URL of the hosted payment page.
To create a new order, make a POST request to the Direct Debit Payment API:
package main
import (
"context"
"fmt"
"os"
dana "github.com/dana-id/dana-go"
"github.com/dana-id/dana-go/config"
widget "github.com/dana-id/dana-go/widget/v1"
)
func main() {
// ... define authentication
request := widget.WidgetPaymentRequest{
// Fill in required fields here, refer to Direct Debit Payment API Detail
}
_, r, err := apiClient.WidgetAPI.WidgetPayment(context.Background()).WidgetPaymentRequest(request).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `WidgetAPI.WidgetPayment``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}
// response from `WidgetPayment`: WidgetPaymentResponse
fmt.Fprintf(os.Stdout, "Response from `WidgetAPI.WidgetPayment`: %v\n", r.Body)
}
If successful, the response will include the URL for the DANA's payment page. For example:
Content-Type: application/json
X-TIMESTAMP: 2020-12-23T08:31:11+07:00
{
"responseCode": "2005400", // Refer to response code list
"responseMessage": "Successful", // Refer to response code list
"referenceNo": "2020102977770000000009", // Transaction identifier on DANA system
"partnerReferenceNo": "2020102900000000000001", // Transaction identifier on partner system
"webRedirectUrl": "https://pjsp.com/universal?bizNo=REF993883&...",
"additionalInfo":{}
}
Step 6 : Access DANA's page by hitting Apply OTT API
To access DANA's payment page, convert the user's access token to a one-time token (OTT) via the Apply OTT API. This token has a 10-minute expiration period and can be used only once.
package main
import (
"context"
"fmt"
"os"
dana "github.com/dana-id/dana-go"
"github.com/dana-id/dana-go/config"
widget "github.com/dana-id/dana-go/widget/v1"
)
func main() {
// ... define authentication
request := widget.ApplyOTTRequest{
// Fill in required fields here, refer to Apply OTT API Detail
}
_, r, err := apiClient.WidgetAPI.ApplyOTT(context.Background()).ApplyOTTRequest(request).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `WidgetAPI.ApplyOTT``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}
// response from `ApplyOTT`: ApplyOTTResponse
fmt.Fprintf(os.Stdout, "Response from `WidgetAPI.ApplyOTT`: %v\n", r.Body)
}
Step 7 : Redirect to DANA’s checkout page
Generate a payment URL by combining the webRedirectUrl
from the Direct Debit Payment API with an OTT token from the Apply OTT API.
package main
import (
"fmt"
"github.com/dana-team/dana-go-api-client/widget/v1"
"github.com/dana-team/dana-go-api-client/widget/v1/model"
)
func main() {
// Example response from createWidgetPayment
widgetPaymentResponse := &model.WidgetPaymentResponse{
WebRedirectUrl: "https://example.com/payment?token=abc123",
}
// This should be from createPayment Widget API
// Example response from applyOTT
applyOTTResponse := &model.ApplyOTTResponse{
UserResources: []model.UserResource{
{
Value: "ott_token_value",
},
},
}
// This should be from applyOTT Widget API
// Generate the payment URL
paymentUrl := widget.GenerateCompletePaymentUrl(widgetPaymentResponse, applyOTTResponse)
fmt.Println("Generated Complete Payment URL:", paymentUrl)
}
Optional Query Order Status, Cancel Order, Refund Order, and Balance Inquiry
There are additional APIs available to enhance your integration process:
- Query Payment API - Use this API to inquire the latest status of a payment request.
- Cancel Order API - For unpaid orders or those paid within 24 hours, a full refund returns to the customer's original payment source.
- Refund Order API - Process refunds for completed orders. You can trigger a refund request on behalf of the customer using this API, who will then process the refund through DANA.
- Balance Inquiry API - Implement this API to verify the customer's balance before order creation. Disabling payment methods with insufficient funds will increase your order success rate.
package main
import (
"context"
"fmt"
"os"
dana "github.com/dana-id/dana-go"
"github.com/dana-id/dana-go/config"
widget "github.com/dana-id/dana-go/widget/v1"
)
func main() {
// ... define authentication
request := widget.QueryPaymentRequest{
// Fill in required fields here, refer to Query Payment API Detail,
}
_, r, err := apiClient.WidgetAPI.QueryPayment(context.Background()).QueryPaymentRequest(request).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `WidgetAPI.QueryPayment``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}
// response from `QueryPayment`: QueryPaymentResponse
fmt.Fprintf(os.Stdout, "Response from `WidgetAPI.QueryPayment`: %v\n", r.Body)
}
package main
import (
"context"
"fmt"
"os"
dana "github.com/dana-id/dana-go"
"github.com/dana-id/dana-go/config"
widget "github.com/dana-id/dana-go/widget/v1"
)
func main() {
// ... define authentication
request := widget.CancelOrderRequest{
// Fill in required fields here, refer to Cancel Order API Detail,
}
_, r, err := apiClient.WidgetAPI.CancelOrder(context.Background()).CancelOrderRequest(request).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `WidgetAPI.CancelOrder``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}
// response from `CancelOrder`: CancelOrderResponse
fmt.Fprintf(os.Stdout, "Response from `WidgetAPI.CancelOrder`: %v\n", r.Body)
}
package main
import (
"context"
"fmt"
"os"
dana "github.com/dana-id/dana-go"
"github.com/dana-id/dana-go/config"
widget "github.com/dana-id/dana-go/widget/v1"
)
func main() {
// ... define authentication
request := widget.RefundOrderRequest{
// Fill in required fields here, refer to Refund Order API Detail,
}
_, r, err := apiClient.WidgetAPI.RefundOrder(context.Background()).RefundOrderRequest(request).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `WidgetAPI.RefundOrder``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}
// response from `RefundOrder`: RefundOrderResponse
fmt.Fprintf(os.Stdout, "Response from `WidgetAPI.RefundOrder`: %v\n", r.Body)
}
package main
import (
"context"
"fmt"
"os"
dana "github.com/dana-id/dana-go"
"github.com/dana-id/dana-go/config"
widget "github.com/dana-id/dana-go/widget/v1"
)
func main() {
// ... define authentication
request := widget.BalanceInquiryRequest{
// Fill in required fields here, refer to Balance Inquiry API Detail,
}
_, r, err := apiClient.WidgetAPI.BalanceInquiry(context.Background()).BalanceInquiryRequest(request).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `WidgetAPI.BalanceInquiry``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}
// response from `BalanceInquiry`: BalanceInquiryResponse
fmt.Fprintf(os.Stdout, "Response from `WidgetAPI.BalanceInquiry`: %v\n", r.Body)
}
Step 8 : Receive Payment Outcome
After a successful payment:
-
Notification: The user will be redirected to your specified Redirect URL, which you can configure using the
urlParams
parameter in the Direct Debit Payment API request, the redirection URL has a format like:https:xxx?originalReferenceNo=xxx&originalPartnerReferenceNo=xxx&merchantId=xxxx&status=xxx
- merchant redirect URL: set on
urlParams.url
- originalReferenceNo: Original transaction identifier on partner system
- originalPartnerReferenceNo: Original transaction identifier on DANA system
- merchantId: Merchant identifier that is unique per each merchant
- status: Payment transaction in DANA side
- Example: https://www.xxx.com/result/?originalReferenceNo=20250613111212800100166070954004283&originalPartnerReferenceNo=8562466e47144b5f82c003b47ae3c474&merchantId=216620000020928274717&status=SUCCESS
- merchant redirect URL: set on
-
[Optional] Finish Notify: In case you add
urlParams.type = NOTIFICATION
, DANA will send payment notifications to your Notification URL via the Finish Notify API. Configure your notification endpoint with the ASPI-mandated path format:/v1.0/debit/notify
.
Construction
func NewWebhookParser(publicKey *string, publicKeyPath *string) (*WebhookParser, error)
Request
Parameter | Type | Remarks |
---|---|---|
publicKey | string | The DANA gateway's public key as a PEM formatted string. This is used if publicKeyPath is not provided or is empty |
publicKeyPath | string | The file path to the DANA gateway's public key PEM file. If provided, this will be prioritized over the publicKey string |
- Returns: A pointer to a
WebhookParser
instance and an error if the public key is invalid.
Method
func (p *WebhookParser) ParseWebhook(httpMethod string, relativePathURL string, headers map[string]string, body string) (*model.FinishNotify, error)
Request
Parameter | Type | Remarks |
---|---|---|
httpMethod | string | The HTTP method of the incoming webhook request e.g., http.MethodPost |
relative_path_url | string | The relative URL path of the webhook endpoint that received the notification e.g /v1.0/debit/notify |
headers | map[string]string | A map containing the HTTP request headers. This map must include X-SIGNATURE and X-TIMESTAMP headers provided by DANA for signature verification |
body | string | The raw JSON string payload from the webhook request body |
- Return: A pointer to a
model.FinishNotifyRequest
struct containing the parsed and verified webhook data, or an error if parsing or signature verification fails.
Security Notes
- Always use the official public key provided by DANA for webhook verification. Store and load it securely.
- The
ParseWebhook
smethod handles both JSON parsing and cryptographic signature verification. If it returns an error, the payload should not be trusted.
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
webhook "github.com/dana-id/dana-go/webhook"
widget "github.com/dana-id/dana-go/widget/v1"
)
// This function would be your actual webhook handler in a real application.
func webhookNotificationHandler(req *http.Request) {
// 1. Initialize the WebhookParser
// You can provide the public key directly as a string or via a file path.
// The parser will prioritize publicKeyPath if both are provided.
// Option 1: Provide public key as a string
// danaPublicKeyPEM := os.Getenv("DANA_PUBLIC_KEY")
// parser, err := webhook.NewWebhookParser(&danaPublicKeyPEM, nil)
// Option 2: Provide path to public key file
danaPublicKeyPath := os.Getenv("DANA_PUBLIC_KEY_PATH") // e.g., "/path/to/your/dana_public_key.pem"
parser, err := webhook.NewWebhookParser(nil, &danaPublicKeyPath)
if err != nil {
fmt.Printf("Error creating WebhookParser: %v\n", err)
return
}
// 2. Extract data from the incoming HTTP Request
httpMethod := req.Method
relativePathUrl := "/v1.0/debit/notify"
// relativePathUrl := req.URL.Path // This should match the path DANA sends the webhook to for example: /v1.0/debit/notify
// Read the request body
bodyBytes, err := io.ReadAll(req.Body)
if err != nil {
fmt.Printf("Error reading request body: %v\n", err)
return
}
defer req.Body.Close() // Important to close the body
webhookBodyStr := string(bodyBytes)
// Log received data for debugging (optional)
fmt.Printf("Received webhook: Method=%s, Path=%s, Headers=%v, Body=%s\n",
httpMethod, relativePathUrl, req.Header, webhookBodyStr)
// 3. Parse and verify the webhook
parsedData, err := parser.ParseWebhook(
httpMethod,
relativePathUrl,
req.Header,
webhookBodyStr,
)
if err != nil {
fmt.Printf("Webhook parsing/verification failed: %v\n", err)
// IMPORTANT: If verification fails, do not trust the payload.
return
}
// 4. Use the parsed data
fmt.Printf("Webhook parsed successfully!\n")
fmt.Printf("Original Partner Reference No: %s\n", parsedData.OriginalPartnerReferenceNo)
fmt.Printf("Amount: %s %s\n", parsedData.Amount.Value, parsedData.Amount.Currency)
fmt.Printf("Status: %s\n", parsedData.LatestTransactionStatus)
// Access other fields from parsedData as needed
}
For detailed example, please refer to the following resource: Example Webhook.
Example of a successful payment webhook payload:
Content-Type: application/json
X-TIMESTAMP: 2024-12-23T09:10:11+07:00
{
"responseCode": "2005400", // Refer to response code list
"responseMessage": "Successful", // Refer to response code list
"referenceNo": "2020102977770000000009", // Transaction identifier on DANA system
"partnerReferenceNo": "2020102900000000000001", // Transaction identifier on partner system
"webRedirectUrl": "https://pjsp.com/universal?bizNo=REF993883&..."
...
}
Additional Enum Configuration
The library provides several enums (enumerations) to represent a fixed set of constant values, ensuring consistency and reducing errors during integration.
import widget "github.com/dana-id/dana-go/widget/v1"
ipg := string(widget.SOURCEPLATFORM_IPG_)
The following enums are available in the Library DANA Widget:
- acquirementStatus
- actorType
- orderTerminalType
- payMethod
- payOption
- promoType
- resourceType
- resultStatus
- serviceScenario
- serviceType
- sourcePlatform
- terminalType
- type
Optional Revoke DANA's user account
Use the Account Unbinding API to remove the connection between your platform and a user's DANA account. For specific users, contact DANA directly.
package main
import (
"context"
"fmt"
"os"
dana "github.com/dana-id/dana-go"
"github.com/dana-id/dana-go/config"
widget "github.com/dana-id/dana-go/widget/v1"
)
func main() {
// ... define authentication
request := widget.AccountUnbindingRequest{
// Fill in required fields here, refer to Account Unbinding API Detail,
}
_, r, err := apiClient.WidgetAPI.AccountUnbinding(context.Background()).AccountUnbindingRequest(request).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `WidgetAPI.AccountUnbinding``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}
// response from `AccountUnbinding`: AccountUnbindingResponse
fmt.Fprintf(os.Stdout, "Response from `WidgetAPI.AccountUnbinding`: %v\n", r.Body)
}
Step 9 : Automated UAT Testing Suite
To verify your integration, run our automated test suite. It takes under 2 minutes to tests your integration with mandated test scenarios. Check out the Github repo for more instructions
Step 10 : Apply for Live Payment
As part of regulatory compliance, merchants are required to submit UAT testing documents to meet Bank Indonesia's requirements. After completing sandbox testing, follow these steps to move to production:
Generate production keys
Create your production private and public keys, follow this instruction: Authentication - Production Credential.Confirm UAT testing logs
Confirm that you have completed all testing scenarios from our Merchant Portal.Fill go-live submission form
Follow the instructions inside our Merchant Portal to apply for production credentials. We will process your application in 1-2 days.Obtain production credentials
Once approved, you will receive your production credentials such as: Merchant ID, Client ID known as X-PARTNER-ID, and Client Secret.
Testing in production environment
Configure production environment
Switch your application settings from sandbox to production environment by updating the API endpoints and credentials.Test using production credentials
Conduct the same testing scenarios as sandbox testing, using your production credentials.UAT production sign-off
Once testing is complete, DANA will prepare the UAT Production Sign Off document in the Merchant Portal. Both merchant and DANA representatives must sign this document to formally approve the integration.Go-live
After receiving all approvals, your DANA integration will be activated and ready for live payments from your customers.
Ready to submit testing documents?
Access our merchant portal for detailed guide to start receiving live payments
Step 1 : Library Installation
Visit our Libraries & Plugins guide for detailed information on our SDK.
DANA provides server-side API libraries for several programming languages, available through common package managers, for easier installation and version management. Follow the guide below to install our library:
Requirements
- PHP 7.4+, compatible with PHP 8.0.
- Your testing credentials from the merchant portal.
Installation
Install using composer or visit our Github- Using Composer
- Add the following code to
composer.json
- Add the following code to
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/GIT_USER_ID/GIT_REPO_ID.git"
}
],
"require": {
"GIT_USER_ID/GIT_REPO_ID": "*@dev"
}
}
- Run
composer install
- Manual Installation
<?php
require_once('/path/to/DanaPhp/vendor/autoload.php');
Set up the env
PRIVATE_KEY or PRIVATE_KEY_PATH # Your private key
ORIGIN # Your application's origin URL
X_PARTNER_ID # clientId provided during onboarding
ENV # DANA's environment either 'sandbox' or 'production'
Obtaining merchant credentials: Authentication
Import Package
use Dana\Widget\v1
Step 2 : Initialize the library
Visit our Authentication guide to learn about the authentication process when not using our Library.
Follow the guide below to initialize the library
<?php
use Dana\Configuration;
use Dana\Env;
use Dana\Widget\v1\Api\WidgetApi;
// Set up configuration with authentication settings
$configuration = new Configuration();
// The Configuration constructor automatically loads values from environment variables
// Choose one of PRIVATE_KEY or PRIVATE_KEY_PATH to set, if you set both, PRIVATE_KEY will be ignored
$configuration->setApiKey('PRIVATE_KEY', getenv('PRIVATE_KEY'));
// $configuration->setApiKey('PRIVATE_KEY_PATH', getenv('PRIVATE_KEY_PATH'));
$configuration->setApiKey('ORIGIN', getenv('ORIGIN'));
$configuration->setApiKey('X_PARTNER_ID', getenv('X_PARTNER_ID'));
$configuration->setApiKey('DANA_ENV', Env::SANDBOX);
// Choose one of ENV or DANA_ENV to set, if you set both, ENV will be ignored
// $configuration->setApiKey('ENV', Env::SANDBOX);
Step 3 : Bind to DANA Account
Generate a redirect URL for DANA account binding. Users will be directed to the DANA App where they can complete the binding process. Configure the OAuth URL as shown below:
<?php
use Dana\Widget\v1\Model\Oauth2UrlData;
use Dana\Widget\v1\Util\Util;
// Set up OAuth2 URL data
$oauth2UrlData = new Oauth2UrlData();
$oauth2UrlData->setRedirectUrl('https://google.com');
$oauth2UrlData->setMerchantId($merchantId);
$oauth2UrlData->setSeamlessData([
'mobileNumber' => '0811742234'
]);
// Generate the OAuth URL
$oauthUrl = Util::generateOauthUrl($oauth2UrlData, $privateKey, $privateKeyPath);
echo 'Generated OAuth URL: ' . $oauthUrl;
The above code redirects users to the DANA App authorization page. By providing the user's phone number in seamlessData
, you can streamline the experience so users don't need to enter their number manually.line the experience so users don't need to enter their number manually.
After successful authorization, the user will be redirected to your specified redirectUrl
with an authCode
that expires in 10 minutes. Example:
https://www.merchant.com/oauth/callback?responseCode=2001000&responseMessage=Successful&authCode=xxx&state=2345555
Step 4 : Exchange the authCode into accessToken
After obtaining the authCode, exchange it for an accessToken using the Apply Token API. The returned accessToken
and refreshToken
both have a 3-year validity period. Once expired, users must rebind their DANA account.
<?php
use Dana\Configuration;
use Dana\Env;
use Dana\Widget\v1\Api\WidgetApi;
use Dana\Widget\v1\Model\ApplyTokenRequest;
// ... define authentication
$applyTokenRequest = ApplyTokenRequest();
try {
$result = $apiInstance->applyToken($applyTokenRequest);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling WidgetApi->applyToken: ', $e->getMessage(), PHP_EOL;
}
Step 5 : Use the Direct Debit Payment API to get a hosted checkout URL
Use the Direct Debit Payment API to create new payment requests which will then return the Checkout URL of the hosted payment page.
To create a new order, make a POST request to the Direct Debit Payment API:
<?php
use Dana\Configuration;
use Dana\Env;
use Dana\Widget\v1\Api\WidgetApi;
use Dana\Widget\v1\Model\WidgetPaymentRequest;
// ... define authentication
$widgetPaymentRequest = WidgetPaymentRequest();
try {
$result = $apiInstance->widgetPayment($widgetPaymentRequest);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling WidgetApi->widgetPayment: ', $e->getMessage(), PHP_EOL;
}
If successful, the response will include the URL for the DANA's payment page. For example:
Content-Type: application/json
X-TIMESTAMP: 2020-12-23T08:31:11+07:00
{
"responseCode": "2005400", // Refer to response code list
"responseMessage": "Successful", // Refer to response code list
"referenceNo": "2020102977770000000009", // Transaction identifier on DANA system
"partnerReferenceNo": "2020102900000000000001", // Transaction identifier on partner system
"webRedirectUrl": "https://pjsp.com/universal?bizNo=REF993883&...",
"additionalInfo":{}
}
Step 6 : Access DANA's page by hitting Apply OTT API
To access DANA's payment page, convert the user's access token to a one-time token (OTT) via the Apply OTT API. This token has a 10-minute expiration
period and can be used only once.
<?php
use Dana\Configuration;
use Dana\Env;
use Dana\Widget\v1\Api\WidgetApi;
use Dana\Widget\v1\Model\ApplyOTTRequest;
// ... define authentication
$applyOTTRequest = ApplyOTTRequest();
try {
$result = $apiInstance->applyOTT($applyOTTRequest);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling WidgetApi->applyOTT: ', $e->getMessage(), PHP_EOL;
}
Step 7 : Redirect to DANA’s checkout page
Generate a payment URL by combining the webRedirectUrl
from the Direct Debit Payment API with an OTT token from the Apply OTT API.
<?php
use Dana\Widget\v1\Model\WidgetPaymentResponse;
use Dana\Widget\v1\Model\ApplyOTTResponse;
use Dana\Widget\v1\Util\Util;
// Example response from createWidgetPayment
$widgetPaymentResponse = new WidgetPaymentResponse();
$widgetPaymentResponse->setWebRedirectUrl('https://example.com/payment?token=abc123');
// This should be from createPayment Widget API
// Example response from applyOTT
$applyOTTResponse = new ApplyOTTResponse();
$applyOTTResponse->setUserResources([
[
'value' => 'ott_token_value'
]
]);
// This should be from applyOTT Widget API
// Generate the payment URL
$paymentUrl = Util::generateCompletePaymentUrl($widgetPaymentResponse, $applyOTTResponse);
echo 'Generated Payment URL: ' . $paymentUrl;
Optional Query Order Status, Cancel Order, Refund Order, and Balance Inquiry
There are additional APIs available to enhance your integration process:
- Query Payment API - Use this API to inquire the latest status of a payment request.
- Cancel Order API - For unpaid orders or those paid within 24 hours, a full refund returns to the customer's original payment source.
- Refund Order API - Process refunds for completed orders. You can trigger a refund request on behalf of the customer using this API, who will then process the refund through DANA.
- Balance Inquiry API - Implement this API to verify the customer's balance before order creation. Disabling payment methods with insufficient funds will increase your order success rate.
<?php
use Dana\Configuration;
use Dana\Env;
use Dana\Widget\v1\Api\WidgetApi;
use Dana\Widget\v1\Model\QueryPaymentRequest;
// ... define authentication
$queryPaymentRequest = QueryPaymentRequest();
try {
$result = $apiInstance->queryPayment($queryPaymentRequest);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling WidgetApi->queryPayment: ', $e->getMessage(), PHP_EOL;
}
<?php
use Dana\Configuration;
use Dana\Env;
use Dana\Widget\v1\Api\WidgetApi;
use Dana\Widget\v1\Model\CancelOrderRequest;
// ... define authentication
$cancelOrderRequest = CancelOrderRequest();
try {
$result = $apiInstance->cancelOrder($cancelOrderRequest);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling WidgetApi->cancelOrder: ', $e->getMessage(), PHP_EOL;
}
<?php
use Dana\Configuration;
use Dana\Env;
use Dana\Widget\v1\Api\WidgetApi;
use Dana\Widget\v1\Model\RefundOrderRequest;
// ... define authentication
$refundOrderRequest = RefundOrderRequest();
try {
$result = $apiInstance->refundOrder($refundOrderRequest);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling WidgetApi->refundOrder: ', $e->getMessage(), PHP_EOL;
}
<?php
use Dana\Configuration;
use Dana\Env;
use Dana\Widget\v1\Api\WidgetApi;
use Dana\Widget\v1\Model\BalanceInquiryRequest;
// ... define authentication
$balanceInquiryRequest = BalanceInquiryRequest();
try {
$result = $apiInstance->balanceInquiry($balanceInquiryRequest);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling WidgetApi->balanceInquiry: ', $e->getMessage(), PHP_EOL;
}
Step 8 : Receive Payment Outcome
After a successful payment:
-
Notification: The user will be redirected to your specified Redirect URL, which you can configure using the
urlParams
parameter in the Direct Debit Payment API request, the redirection URL has a format like:https:xxx?originalReferenceNo=xxx&originalPartnerReferenceNo=xxx&merchantId=xxxx&status=xxx
- merchant redirect URL: set on
urlParams.url
- originalReferenceNo: Original transaction identifier on partner system
- originalPartnerReferenceNo: Original transaction identifier on DANA system
- merchantId: Merchant identifier that is unique per each merchant
- status: Payment transaction in DANA side
- Example: https://www.xxx.com/result/?originalReferenceNo=20250613111212800100166070954004283&originalPartnerReferenceNo=8562466e47144b5f82c003b47ae3c474&merchantId=216620000020928274717&status=SUCCESS
- merchant redirect URL: set on
-
[Optional] Finish Notify: In case you add
urlParams.type = NOTIFICATION
, DANA will send payment notifications to your Notification URL via the Finish Notify API. Configure your notification endpoint with the ASPI-mandated path format:/v1.0/debit/notify
.
Construction
public function __construct(?string $publicKey = null, ?string $publicKeyPath = null)
Request
Parameter | Type | Remarks |
---|---|---|
publicKey | string | The DANA gateway's public key as a PEM formatted string. This is used if publicKeyPath is not provided or is empty |
publicKeyPath | string | The file path to the DANA gateway's public key PEM file. If provided, this will be prioritized over the publicKey string |
- Throws:
\InvalidArgumentException
if neither publicKey nor publicKeyPath is provided or if the public key cannot be loaded
Method
public function parseWebhook(string $httpMethod, string $relativePathURL, array $headers, string $body): \Dana\Webhook\v1\Model\FinishNotifyRequest
Request
Parameter | Type | Remarks |
---|---|---|
httpMethod | string | The HTTP method of the incoming webhook request e.g., POST |
relative_path_url | string | The relative URL path of the webhook endpoint that received the notification e.g /v1.0/debit/notify |
headers | array | An array containing the HTTP request headers. This map must include X-SIGNATURE and X-TIMESTAMP headers provided by DANA for signature verification |
body | string | The raw JSON string payload from the webhook request body |
- Returns: An instance of \Dana\Webhook\v1\Model\FinishNotifyRequest containing the parsed and verified webhook data.
- Throws:
\InvalidArgumentException
if required parameters are missing or\RuntimeException
if signature verification fails.
Security Notes
- Always use the official public key provided by DANA for webhook verification. Store and load it securely.
- The
parseWebhook
method handles both JSON parsing and cryptographic signature verification. If it throws an exception, the payload should not be trusted.
<?php
use Dana\Configuration;
use Dana\Webhook\WebhookParser;
// Initialize the WebhookParser
// You can provide the public key directly as a string or via a file path.
// The parser will prioritize publicKeyPath if both are provided.
// Option 1: Provide public key as a string
$danaPublicKey = getenv('DANA_PUBLIC_KEY');
$parser = new WebhookParser($danaPublicKey);
// Option 2: Provide path to public key file
// $danaPublicKeyPath = getenv('DANA_PUBLIC_KEY_PATH'); // e.g., "/path/to/your/dana_public_key.pem"
// $parser = new WebhookParser(null, $danaPublicKeyPath);
// Get the request data
$httpMethod = $_SERVER['REQUEST_METHOD'];
$relativePathUrl = '/v1.0/debit/notify'; // This should match the path DANA sends the webhook to
// Get headers - getallheaders() is the standard way in PHP
$headers = getallheaders();
// For frameworks that don't support getallheaders(), you can use:
// $headers = [];
// foreach ($_SERVER as $name => $value) {
// if (substr($name, 0, 5) === 'HTTP_') {
// $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
// }
// }
// Get the raw request body as a JSON string
$webhookBodyStr = file_get_contents('php://input');
// If you need to access the decoded data before passing to the parser
// (Not required for parseWebhook which expects the raw string)
// $jsonData = json_decode($webhookBodyStr, true);
// if (json_last_error() !== JSON_ERROR_NONE) {
// throw new \RuntimeException('Invalid JSON in webhook payload: ' . json_last_error_msg());
// }
// echo "Request data: " . print_r($jsonData, true);
try {
// Parse and verify the webhook
$parsedData = $parser->parseWebhook(
$httpMethod,
$relativePathUrl,
$headers,
$webhookBodyStr
);
// If we reach here, the webhook was parsed and verified successfully
echo "Webhook verified successfully!\n";
echo "Original Partner Reference No: " . $parsedData->getOriginalPartnerReferenceNo() . "\n";
echo "Amount: " . $parsedData->getAmount()->getValue() . " " . $parsedData->getAmount()->getCurrency() . "\n";
echo "Status: " . $parsedData->getLatestTransactionStatus() . "\n";
// Access additional information if available
if ($parsedData->getAdditionalInfo() && $parsedData->getAdditionalInfo()->getPaymentInfo()) {
$paymentInfo = $parsedData->getAdditionalInfo()->getPaymentInfo();
$payOptions = $paymentInfo->getPayOptionInfos();
foreach ($payOptions as $payOption) {
echo "Payment Method: " . $payOption->getPayMethod() . "\n";
if ($payOption->getPayOption()) {
echo "Payment Option: " . $payOption->getPayOption() . "\n";
}
}
}
} catch (\Exception $e) {
// If verification fails, do not trust the payload
error_log("Webhook verification failed: " . $e->getMessage());
// Respond with an error
header('Content-Type: application/json');
http_response_code(400);
echo json_encode([
'response_code' => '96',
'response_message' => 'System Error'
]);
}
For detailed example, please refer to the following resource: Example Webhook.
Example of a successful payment webhook payload:
Content-Type: application/json
X-TIMESTAMP: 2024-12-23T09:10:11+07:00
{
"responseCode": "2005400", // Refer to response code list
"responseMessage": "Successful", // Refer to response code list
"referenceNo": "2020102977770000000009", // Transaction identifier on DANA system
"partnerReferenceNo": "2020102900000000000001", // Transaction identifier on partner system
"webRedirectUrl": "https://pjsp.com/universal?bizNo=REF993883&..."
...
}
Additional Enum Configuration
The library provides several enums (enumerations) to represent a fixed set of constant values, ensuring consistency and reducing errors during integration.
// Importing an enum class
use Dana\Widget\v1\Enum\TerminalType;
// Using enum constants
$model->setTerminalType(TerminalType::APP);
// Using enum values directly as strings
$model->setTerminalType('APP');
The following enums are available in the Library DANA Widget:
- ActorType
- OrderTerminalType
- PayMethod
- PayOption
- SourcePlatform
- TerminalType
- Type
Optional Revoke DANA's user account
Use the Account Unbinding API to remove the connection between your platform and a user's DANA account. For specific users, contact DANA directly.
<?php
use Dana\Configuration;
use Dana\Env;
use Dana\Widget\v1\Api\WidgetApi;
use Dana\Widget\v1\Model\AccountUnbindingRequest;
// ... define authentication
$accountUnbindingRequest = AccountUnbindingRequest();
try {
$result = $apiInstance->accountUnbinding($accountUnbindingRequest);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling WidgetApi->accountUnbinding: ', $e->getMessage(), PHP_EOL;
}
Step 9 : Automated UAT Testing Suite
To verify your integration, run our automated test suite. It takes under 2 minutes to tests your integration with mandated test scenarios. Check out the Github repo for more instructions
Step 10 : Apply for Live Payment
As part of regulatory compliance, merchants are required to submit UAT testing documents to meet Bank Indonesia's requirements. After completing sandbox testing, follow these steps to move to production:
Generate production keys
Create your production private and public keys, follow this instruction: Authentication - Production Credential.Confirm UAT testing logs
Confirm that you have completed all testing scenarios from our Merchant Portal.Fill go-live submission form
Follow the instructions inside our Merchant Portal to apply for production credentials. We will process your application in 1-2 days.Obtain production credentials
Once approved, you will receive your production credentials such as: Merchant ID, Client ID known as X-PARTNER-ID, and Client Secret.
Testing in production environment
Configure production environment
Switch your application settings from sandbox to production environment by updating the API endpoints and credentials.Test using production credentials
Conduct the same testing scenarios as sandbox testing, using your production credentials.UAT production sign-off
Once testing is complete, DANA will prepare the UAT Production Sign Off document in the Merchant Portal. Both merchant and DANA representatives must sign this document to formally approve the integration.Go-live
After receiving all approvals, your DANA integration will be activated and ready for live payments from your customers.
Ready to submit testing documents?
Access our merchant portal for detailed guide to start receiving live payments