Create a Virtual Account via API
In this quickstart guide, you will create and pay into a virtual account created through the Payments API. This quickstart guide assumes a use case where you might generate a single use Virtual Account with a fixed payment amount. This use case is ideal for E-Commerce where you can generate a virtual account for each purchase.
Before you begin
- Register for a Xendit account by visiting our dashboard
- Generate a Secret API Key for development here or by visiting Settings > Developers > API Keys on the dashboard
- Make sure to set "Write" permissions to your API Key for Money-In products. For more details, please visit this guide
1. Create a Payment Request to generate a Virtual Account
Let's start by creating a Virtual Account for your customer to pay to. You can use the Payment Requests API provided by Xendit. This example uses the Axios library to perform a POST request to Xendit in JavaScript by defining a createVirtualAccountPayment
function.
import axios from 'axios';
const authToken = Buffer.from('xnd_development_XXXX:').toString('base64');
async function createVirtualAccountPayment() {
try {
const { data, status } = await axios.post('https://api.xendit.co/payment_requests',
{
currency: 'IDR',
amount: 35000,
reference_id: 'xendit_test_id_1',
payment_method: {
type: 'VIRTUAL_ACCOUNT',
reusability: 'ONE_TIME_USE',
virtual_account: {
channel_code: 'BNI',
channel_properties: {
customer_name: 'Ahmad Gunawan',
expires_at: '2023-09-27 18:00:00.000'
}
}
}
},
{
headers: {
'Authorization': `Basic ${authToken}`
}
}
)
}
The table below gives an explanation of each parameter in the request and the reason for specifying it. Make sure to replace the secret key in the example with your own secret key.
Parameter | Required | Description |
---|---|---|
currency | Yes | The currency of the virtual account. For IDR payment in Indonesia, we specify IDR |
amount | No | Specifies the exact amount the virtual account can accept. For our E-Commerce use case, we would like our VA to accept a specific amount that equals the value of the product our customer is purchasing |
reference_id | No | An ID set by you to uniquely identify each payment method. For example, "123" |
payment_method.type | Yes | The type of payment method. In this case, Virtual Account |
payment_method.reusibility | Yes | Whether the Virtual Account can accept multiple payments or only one. For our E-Commerce use case, we will restrict usage to only a single payment for this virtual account |
payment_method.virtual_account.channel_code | Yes | Specifies the bank with which the Virtual Account is created. This will typically be the bank that the end customer would like to pay with. In this example, we use BNI. |
payment_method.virtual_account.amount | No | Specifies the exact amount the virtual account can accept. For our E-Commerce use case, we would like our VA to accept a specific amount that equals the value of the product our customer is purchasing |
payment_method.virtual_account.channel_properties.customer_name | Yes | The name of the customer paying you. This must exactly match the name the customer has specified on their bank account. In this example, we used Ahmad Gunawan |
payment_method.virtual_account.channel_properties.expires_at | No | Specify the time at which the virtual account will expire. This allows the virtual account number to be reused for other transactions |
For each payment method creation, Xendit responds to you with the status of the payment and additional parameters. To view this, you can log the response returned from the axios POST request.
console.log(`Response returned with a status of ${status}`);
const { payment_method: { id, virtual_account: { channel_properties: { virtual_account_number } } }} = data;
console.log(`Virtual Account created with number ${virtual_account_number} and Payment Method ID ${id}`)
In the above snippet, the virtual_account_number
parameter has been extracted. This is the virtual account which the end user should transfer money to. Along with this, the created payment_method.id
field has also been extracted, which you will need to simulate payment in the next step. For further reading on the response returned from the Payment Requests API, please visit here.
In your checkout page, you should display this number to the end customer so that they can complete their payment.
Putting it all together, your final script should look like this:
import axios from 'axios';
const authToken = Buffer.from('xnd_development_XXXX:').toString('base64');
async function createVirtualAccountPayment() {
try {
const { data, status } = await axios.post('https://api.xendit.co/payment_requests',
{
currency: 'IDR',
amount: 35000,
reference_id: 'xendit_test_id_1',
payment_method: {
type: 'VIRTUAL_ACCOUNT',
reusability: 'ONE_TIME_USE',
virtual_account: {
channel_code: 'BCA',
channel_properties: {
customer_name: 'Ahmad Gunawan',
}
}
}
},
{
headers: {
'Authorization': `Basic ${authToken}`
}
}
)
console.log(`Response returned with a status of ${status}`);
const { payment_method: { id, virtual_account: { channel_properties: { virtual_account_number } } }} = data;
console.log(`Virtual Account created with number ${virtual_account_number} and Payment Method ID ${id}`)
} catch (error) {
console.log("Request failed")
}
}
createVirtualAccountPayment()
After running this code (you may use NodeJS or Express to do this), you should see a successful created virtual account number being logged to the console.
> Response returned with a status of 201
> Virtual Account created with number 381659999480605 and Payment Method ID pm-442e7029-6622-4ef2-9eec-7a55b097ad00
2. Handle the Callback for Successful Payment
Once payment is completed by the customer, Xendit will send you a callback notifying successful payment. In this step, you will define a function to handle this callback sent from Xendit.
In the script below, a simple express server serves a POST endpoint /receive_callback
. This is the endpoint Xendit will send the callback to.
import express, { json } from 'express';
const app = express();
app.use(json())
const PORT = process.env.PORT || 3000;
app.post('/receive_callback', async(req, res) => {
const { body } = req;
if (body.data.status === 'SUCCEEDED') {
console.log(`Payment Successfull with status ${body.data.status} and id ${body.data.id}`)
}
res.sendStatus(200).end()
})
app.listen(PORT, () => console.log(`App listening at port ${PORT}`));
Parameter | Always Present | Description |
---|---|---|
body.data.status | Yes | Status of the payment. Will equal "SUCCEEDED" when payment is successful |
body.data.id | Yes | ID of the payment request. Can be used to perform further actions such as Refunds |
For a detailed list of parameters provided in the payment callback, please visit our API reference page. This page also provides additional details on how to handle failed payment callbacks.
3. Set the Callback URL on the Xendit Dashboard
Now that you have defined your callback handler, it is time to set the callback URL on Xendit's Dashboard. Ideally, this URL would lead to a production deployment of your server. For this example, it is possible to use the free tool ngrok to publicly host a localhost server.
> ngrok http 3000
Session Status online
Account abc@gmail.com (Plan: Free)
Update update available (version 3.3.0-beta, Ctrl-U to update)
Version 3.0.4
Region Asia Pacific (ap)
Latency 22ms
Web Interface http://127.0.0.1:4040
Forwarding https://XXXX-121-6-44-41.ngrok-free.app -> http://localhost:3000
You can then set the callback URL (https://XXXX-121-6-44-41.ngrok-free.app
in this case) on the Xendit Dashboard. Click here and navigate to Payment Request > Payment Succeeded (New Payments API) to Test and Save this URL.
3. Simulate a Virtual Account Payment to your Payment Method
At this point, you would display the created virtual_account_number
to your end user on your E-Commerce checkout page. Your customer would then continue on to payment through the BCA app or website. However, for testing, Xendit's simulation endpoint can be used to simulate payment.
> curl https://api.xendit.co/v2/payment_methods/pm-442e7029-6622-4ef2-9eec-7a55b097ad00/payments/simulate -X POST \
--user xnd_development_XXXXXX: \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": 35000
}' \
Make sure to replace the user
field with your API key and the path parameter with your newly created Payment Method ID from step 1. You should receive the response below
{
"message": "We're processing payment for payment method ID 'pm-442e7029-6622-4ef2-9eec-7a55b097ad00' and will send you the result via callback. Please make sure you've set a callback URL in 'Payment Succeeded' section in Callback settings in Xendit Dashboard. If you don't receive the callback within the next 5 minutes, please contact us.",
"status": "PENDING"
}
4. Verify that Callback was received
Now, if you go back to the server that we've been running (and perhaps hosting through ngrok), you should see a console log indicating a successful payment.
> Payment Successfull with status SUCCEEDED and id 64672654fe87ba392f68cb46
Wrapping up
You've managed to successfully integrate to Xendit by creating a Virtual Account and simulating payment. You'e also managed to verify successful payment by verifying a successful callback sent by Xendit.
Feel free to explore the following for further reading,
Last Updated on 2023-06-15