For Payment Aggregators outside India
This integration guide is for payment aggregators located outside India, who want to collect payments from Indian buyers (individuals and businesses) for their merchants located outside India. Please note that currently Xflow supports collections for only INR
invoices.
In this integration guide, we will provide step by step instructions on collecting payments via two payment methods : (i) local bank transfers and (ii) Unified Payments Interface or UPI.
1. Create and activate a connected user
Your merchants/users are called connected users in Xflow. To start collecting payments for your connected users, you must do the following.
- Create an account for the connected user
- Add details about the directors associated with the enity being onboarded as the connected user
- Add connected user’s bank account details
- Initiate account activation
As of now, you can completed the steps above through our Dashboard. We will shortly provide APIs for you to do the same.
Create an account for a connected user
Create a connected user account by passing basic details about your merchants as shown below. Your connected users do not have access to these accounts. The information provided as part of the connected user account is required for due diligence on the ultimate beneficiary of the transaction.
curl -L -X POST 'https://api.xflowpay.com/v1/accounts' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Content-Type: application/json' \
-d '{
"business_details": {
"email": "finance@marvelousw.com",
"ids" : {
"business": "12-3456789"
},
"legal_name": "Marvelous Software Inc",
"merchant_category_code": "5734",
"physical_address": {
"city": "Seattle",
"country": "US",
"line1": "Bldg. 3, Main Street",
"postal_code": "98103",
"state": "WA"
},
"product_category": "software",
"product_description": "We sell productivity softwares.",
"type": "company",
"website": "www.marvelous.com"
},
"nickname": "Marvelous Softwares",
"type": "user"
}'
{
"address": "requested",
"business_details": {
"date_of_incorporation": null,
"dba": null,
"email": "finance@marvelousw.com",
"ids": {
"business": "12-3456789",
"tax": null,
"tax_deduction": null,
"tax_gst": null,
"tax_trade": null
},
"legal_name": "Marvelous Software Inc",
"merchant_category_code": "5734",
"merchant_size": null,
"physical_address": {
"city": "Seattle",
"country": "US",
"line1": "Bldg. 3, Main Street",
"line2": null,
"postal_code": "98103",
"state": "WA"
},
"product_category": "software",
"product_description": "We sell productivity softwares.",
"type": "company",
"website": "www.marvelous.com"
},
"created": 1727424388,
"id": "account_F0A_1727424388583_DeEtl_000",
"link": null,
"livemode": false,
"logo_id": null,
"metadata": null,
"nickname": "Marvelous Softwares",
"object": "account",
"parent_account_id": "account_F0A_1726296734091_G7Zuq_000",
"supporting_documentation": null,
"status": "draft",
"sub_type": null,
"tos_acceptance": null,
"type": "user"
}
Explaining some of the key fields above.
type
: When you create a connected user, you are creating an account of type = user
. Your own account will be of type = platform
and sub_type = aggregator
.
business_details.nickname
: This is your connected user’s brand name. This must be unique among your connected users. Among other things, this field is used to create UPI handles for your merchants. This is covered further in the guide.
business_details.id.business
: This is a government issued identifier for the business (e.g. EIN in the US).
business_details.product_category
: This field is used to classify your merchants into goods or software. If your merchants exports services to India, please drop a note to support@xflowpay.com, and our team will reach out to understand the usecase and provide next steps. It is important to classify your merchants correctly because there are category specific requirements which we will talk about further in the guide.
It is useful to note that you can create an account for a connected user by passing only a subset of the mandatory information required to activate the account. This will allow you to pass information about the connected user in a staged manner. Before initiating account activation, you will have to provide additional mandatory information. You can use the Update endpoint to provide additional information after the account is created. The mandatory information required as part of the account object is provided here.
Xflow does not support nor recommend cross-origin domain requests from the browser. If you make such requests from the browser, Xflow will return the CORS header 'Access-Control-Allow-Origin' missing
error code. If you encounter this error, you're likely calling an API endpoint that is not intended to be called by a web frontend. Or rather, it can't be safely called by your frontend, because it requires a secret API key, and you don't want to expose that key to clients. Please fix this issue by calling this API endpoint from your backend instead.
Add Directors for connected user
After you have created the connected user, you must now provide names of at least 2 directors for the connected user. This is done by creating a Person
object as shown below.
At this stage, we would like to introduce the header Xflow-Account
. The header Xflow-Account
indicates the connected user's account for which the call is being made. You should pass this header with the connected user's account identifier when taking action in the context of that specific connected user. In the example below, we have passed this header because we are creating the person for a specific connected user.
curl -L -X POST 'https://api.xflowpay.com/v1/persons' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"full_name": "Mike McGallan",
"relationship": {
"director": true
}
}'
{
"created": 1666078214,
"full_name": "Mike McGallan",
"id": "person_F0A_1666078214977_3Du5D_000",
"livemode": true,
"object": "person",
"relationship": {
"director": true,
"owner": false,
"representative": false
},
"status": "unverified",
"supporting_documentation": null,
"supportings_ids": null
}
Add Bank Account for connected user
Before you submit the connected user account for activation, you also need to provide connected user's bank account details. This information is used for regulatory reporting purposes only, and not for making any payouts directly to your merchants. To do this, you should create an Address object with bank account number and the SWIFT code as shown below.
If you do not have the SWIFT code, you can also provide the bank name in the field bank_account.bank_name
. For IBANs, instead of bank_account.number
use bank_account.iban
field.
curl -L -X POST 'https://api.xflowpay.com/v1/addresses' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d $'{
"bank_account": {
"global_wire": "USBKUS44XXX",
"number": "102940182401"
},
"billing_details": {
"city": "San Francisco",
"country": "US",
"line1": "786 West 16th Avenue",
"postal_code": "67ZB8",
"state": "WA"
},
"category": "user_payout",
"currency": "USD",
"linked_id": "account_F0A_1727424388583_DeEtl_000",
"linked_object": "account",
"name": "Marvelous Software Inc",
"type": "bank_account"
}'
{
"bank_account": {
"bank_name": null,
"domestic_credit": null,
"domestic_debit": null,
"domestic_fast_credit": null,
"domestic_wire": null,
"global_wire": "USBKUS44XXX",
"iban": null,
"last4": "2401",
"number": "102940182401"
},
"billing_details": {
"city": "San Francisco",
"country": "US",
"line1": "786 West 16th Avenue",
"line2": null,
"postal_code": "67ZB8",
"state": "WA"
},
"category": "user_payout",
"created": 1727424974,
"currency": "USD",
"id": "address_f0A_1727424974114_GMXp5_000",
"is_reusable": false,
"linked_id": "account_F0A_1727424388583_DeEtl_000",
"linked_object": "account",
"livemode": false,
"metadata": null,
"name": "Marvelous Software Inc",
"object": "address",
"status": "deactivated",
"supporting_documentation": null,
"type": "bank_account",
"vpa": null,
"wallet": null
}
Initiate account activation
Once you have completed all the steps above, you will now need to initiate the activation process by hitting the activate endpoint as shown below.
curl -L -X POST 'https://api.xflowpay.com/v1/accounts/account_F0A_1727424388583_DeEtl_000/activate' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json'
Post this, the connected user status will change to verifying
. At this stage, Xflow and its banking partner will evaluate the information and activate (status changes to activated
) or ask for additional information over email. The account is activated within 3 business days. You can subscribe to events related to account status account.status.*
to receive these updates. While a virtual bank account number is allocated immediately after activation, the UPI VPA is provided in 4 business days after activation. You can subscribe to the event address.status.activated
to know when the UPI id/vpa has been created for your connected user.
Understanding Settings and Fees
Before we proceed to the next steps, we will introduce 2 other objects associated with your account.
AccountSettingsFor every account of type = platform
and user
on Xflow there will be a corresponding AccountSettings
object. Key parameters on the AccountSettings
object are
address
hash which configures the default creation of INR VBANs (virtual bank account numbers) at different levels. Your account is configured with defaults such that every connected user will get an INR VBAN upon activation. However if you wish to create INR VBANs for every partner of the connected user then you can set the fieldaddress.connected_user_partner
to true. This helps the connected user with reconciliation provided they share the right VBAN details with the right partner. You will find similar settings on the connected user’sAccountSettings
object which enables you to have different experiences for different connected users. The UPI VPAs (virtual payment address) creation is not controlled viaAccountSettings
. A VPA is created by default for every connected user for whom the additional information stated on the Activate endpoint has been provided. You cannot create UPI VPAs at partner level.payouts
At the moment, we will process all your payouts daily, but in future we will enable you to configure your payout cadence through thepayout
hash. Since your connected users are not getting paid out, thepayout
hash on connected user'sAccountSettings
is set tonull
.
There are 2 types of FeePlan
objects associated with your account.
type = account_fees_passthrough
: This FeePlan object holds the fees that will be applied on every transaction of your connected users. The fees are organised under the hashreceivable_reconcile
. This FeePlan is inherited as-is on your connected user’s account for processing. The FeePlan on your connected user’s account is of typeaccount_fees
. You cannot edit the FeePlans on your account or your connected user’s account.type = account_fees
: This FeePlan object holds the fees that will be applied while making payouts to you. In essence, these fees are charged per payout, and not per connected user transaction. An example of this is the FX mark-up or SWIFT fees charged per payout that we make to your bank account. The fees are organised underpayout
andpayout_fx
hash. The FeePlan cannot be edited.
2. Create a Partner
Your connected user’s customers are called partners in Xflow. A partner is modeled as an account of type = partner
within Xflow. Use the create endpoint to create an account of type = partner
. You need to create the account on behalf of your user, so remember to pass the Xflow-Account header populated with the user’s account identifier. Please note that you can create the partner later just before withdrawing the incoming funds.
curl -L -X POST 'https://api.xflowpay.com/v1/accounts' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"business_details": {
"email": "ops@shoepower.com",
"legal_name": "Shoe Power",
"physical_address": {
"country": "IN",
"line1": "788 Embassy Industrial Area",
"city": "Bangalore",
"state": "Karnataka",
"postal_code": "560065"
},
"type": "business"
},
"nickname": "Shoe Power",
"type": "partner"
}'
{
"address": "deactivated",
"business_details": {
"date_of_incorporation": null,
"dba": null,
"email": "ops@shoepower.com",
"ids": null,
"legal_name": "Shoe Power",
"merchant_category_code": null,
"merchant_size": null,
"physical_address": {
"city": "Bangalore",
"country": "IN",
"line1": "788 Embassy Industrial Area",
"line2": null,
"postal_code": "560065",
"state": "Karnataka"
},
"product_category": null,
"product_description": null,
"type": "business",
"website": null
},
"created": 1727426459,
"id": "account_F0A_1727426459005_jRi3o_000",
"link": null,
"livemode": false,
"logo_id": null,
"metadata": null,
"nickname": "Shoe Power",
"object": "account",
"parent_account_id": "account_F0A_1727424388583_DeEtl_000",
"supporting_documentation": null,
"status": "verifying",
"sub_type": null,
"tos_acceptance": null,
"type": "partner"
}
Unlike the connected user, activation of a partner is near instantaneous post creation and there is no separate call for activation required. However, given that this is still an asynchronous step, you can listen to the account.status.activated
event to determine when the partner account has been activated.
Please ensure that you are classifying your partners correctly as business
or individual
via the business_details.type
field.
If the partner is a business entity, and your connected user sells physical goods, then you must also provide the Importer Exporter Code (IEC) for the partner in
business_details.ids.tax_trade
.If you will be processing transaction size greater than INR 2.5L, then you must provide the PAN of the partner in the field
business_details.ids.tax
3. Create a Receivable
A receivable is Xflow’s wrapper around the connected user’s invoice. You must create the receivable on behalf of your connected user against an activated partner by using the below curl snippet. Again, like partner, you can create the receivable later just before withdrawing the funds.
For creating a receivable, first create a File
object as shown below by using your invoice file which must be a .jpeg, .pdf or a .png file.
curl -L -X POST 'https://api.xflowpay.com/v1/files' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-F file=@"Inv_AB887.pdf" \
-F payload='{
"purpose": "finance_document"
}'
{
"created": 1675153265,
"file_name": "Inv_AB887.pdf",
"id": "file_F0A_1727427038355_ecoFL_000",
"livemode": false,
"metadata": null,
"object": "file",
"purpose": "finance_document",
"size": 493117,
"type": "pdf",
"url": "https://api.xflowpay.com/v1/files/file_F0A_1727427038355_ecoFL_000/contents"
}
Once you have the id of the File
object, you can create the receivable as shown below. The file id is passed in the field invoice.document
.
curl -L -X POST 'https://api.xflowpay.com/v1/receivables' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"account_id": "account_F0A_1727427042466_UKUbp_000",
"amount_maximum_reconcilable": "830000.00",
"currency": "INR",
"invoice": {
"amount": "830000.00",
"creation_date": "2022-10-18",
"currency": "INR",
"document": "file_F0A_1727427038355_ecoFL_000",
"due_date": "2022-11-07",
"reference_number": "Inv_30475"
},
"hsn_code": "85238090",
"purpose_code": "S0802",
"transaction_type": "software"
}'
{
"account_id": "account_F0A_1727427042466_UKUbp_000",
"amount_locked": null,
"amount_maximum_reconcilable": "830000.00",
"amount_reconcilable": null,
"amount_reconciled": null,
"amount_reconciled_not_settled": null,
"amount_settled_payouts": null,
"created": 1727427046,
"currency": "INR",
"deposit_ids_amount_locked": [],
"description": null,
"hsn_code": "85238090",
"id": "receivable_f0A_1727427046725_mjXJy_000",
"invoice": {
"amount": "830000.00",
"creation_date": "2022-10-18",
"currency": "INR",
"document": "file_F0A_1727427038355_ecoFL_000",
"due_date": "2022-11-07",
"reference_number": "Inv_30475"
},
"livemode": false,
"metadata": null,
"object": "receivable",
"purpose_code": "S0802",
"purpose_code_description": "Software consultancy/implementation",
"status": "draft",
"supporting_documentation": {
"documents": [],
"status": "not_applicable"
},
"system_message": [],
"transaction_type": "software"
}
After creating the receivable, you must confirm the receivable which submits the receivable for verification. Xflow will now verify and activate the receivable with an SLA of 2 hours. You can subscribe to the receivable.status.activated
event to know when a receivable has been activated.
For some receivables, Xflow will require additional inputs and will move your receivable to an Input Required status. You will be notified through webhook receivable.status.input_required
.
curl -L -X POST 'https://api.xflowpay.com/v1/receivables/receivable_f0A_1727427046725_mjXJy_000/confirm' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json'
Please note that,
Receivables with value > INR 25L are not supported.
Receivables where an individual SKU is > INR 2.5L are not supported.
For goods receivable, you/your connected user must provide additional supporting documents (shipping and customs related) after the transaction has been settled. This is covered in detail in section 6.
For goods receivable, you can use purpose codes
S0101
orS0102
. For software, you must useS0802
. If you have a usecase for Services, please reach out to support@xflowpay.com.For software transaction, the
hsn_code
must be set to85238090
. For goods transactions, please use thehsn_code
that you as per your business understanding.
4. Collecting Payments via Bank Transfers
Once your connected user has been activated, you can start collecting payments for them. In this section, we will show you how to collect payments via local bank transfer.
4.1 Using the INR VBAN (Virtual Bank Account Number) for collection
Upon activation, an INR VBAN (also known as VA or Virtual Account) is automatically generated for your connected user. Think about this as the collection account that is specifically created for your connected user, i.e. one VBAN per connected user. Your connected users can share this VBAN with their customers in India to receive payments. This VBAN is modeled as an Address (see below) with category = xflow_receive
and type = bank_account
.
{
"bank_account": {
"bank_name": "ICICI Bank",
"domestic_credit": "ICICTMP3440",
"domestic_debit": null,
"domestic_fast_credit": "ICICTMP3440",
"domestic_wire": "ICICTMP3440",
"global_wire": null,
"iban": null,
"last4": "0400",
"number": "51744431540400"
},
"billing_details": null,
"category": "xflow_receive",
"created": 1709474909,
"currency": "INR",
"id": "address_f0A_1709474909321_p7bVk_000",
"is_reusable": false,
"linked_id": "account_F0A_1727424388583_DeEtl_000",
"linked_object": "account",
"livemode": false,
"metadata": null,
"name": null,
"object": "address",
"status": "activated",
"supporting_documentation": null,
"type": "bank_account",
"vpa" : null,
"wallet": null
}
India supports 3 types of bank transfer payment methods: NEFT, RTGS and IMPS. For using these bank transfer payment methods, the partner will need an account number and routing code which is called IFSC (Indian Financial System Code). The account number is provided in bank_account.number
field. The IFSC is provided in the following fields:
- NEFT :
bank_account.domestic_credit
- IMPS :
bank_account.domestic_fast_credit
- RTGS :
bank_account.domestic_wire
Note: As of today, the IFSC is the same across all the payment methods.
Once funds arrive in the VBAN, a deposit object (see below) is created with the amount received. You should subscribe to the deposit.status.completed
event to know when a connected user has received a deposit.
{
"amount": "830000.00",
"created": 1666079759,
"currency": "INR",
"from": {
"account_id": "account_F0A_1727424388583_DeEtl_000",
"address_id": "address_f0A_28094133409111_X7bck_003"
},
"id": "deposit_f0A_1666079759165_P3FtR_000",
"livemode": true,
"metadata": null,
"object": "deposit",
"payment_method": "domestic_credit",
"payment_method_details": null,
"reason_code": null,
"statement_descriptor": null,
"status": "completed",
"to": {
"account_id": "account_F0A_1727424388583_DeEtl_000",
"address_id": "address_f0A_1709474909321_p7bVk_000"
}
}
The from.address_id
has the bank account details of the remitter along with remitter name. This Address is of type = bank_account
and category = partner_payment
. We recommend that you show the remitter details to your connected users to help them identify the source of funds.
The received funds will reflect in your connected user's balance. Please refer to the Balance object in the API reference, and look for balance.pending []
. Every acccount at Xflow, irrespective of the type, will have a Balance
object associated with it which shows the funds against the account. When you receive the funds, they reflect in the balance.pending
compartment. To withdraw funds, you will have to reconcile the funds against an activated receivable. Upon reconciliation, the funds move to balance.available
compartment, and are available to be paid out. We will look at reconciliation further in section 6 of the guide.
For testing, you will need a way to introduce some dummy funds into your integration. For this, you can use the below test-mode only Create Deposit API.
curl -L -X POST 'https://api.xflowpay.com/v1/deposits' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"amount": "830000.00",
"currency": "INR",
"from": {
"account_id": "account_F0A_1727424388583_DeEtl_000"
},
"payment_method": "domestic_credit",
"to": {
"account_id": "account_F0A_1727424388583_DeEtl_000",
"address_id": "address_f0A_1666079171474_6jxOu_000"
}
}'
4.2 Map the incoming deposit to a Partner
At the time of processing payouts, our banking partner will try to match the incoming funds against outgoing transactions. Funds deposited by partner A cannot be withdrawn against an invoice from partner B, i.e. the funds are not fungible. To adhere to this regulatory constraint, you must ask your connected user to map the incoming funds to a partner by showing them the remitter details against each desposit. Here the mapping of funds to partner means, moving the funds from connected user balance to the partner level balance. To do this, you must use the Transfer API as shown below which moves funds instantly:
curl -L -X POST https://api.xflowpay.com/v1/transfers \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"from": {
"account_id": "account_F0A_1727424388583_DeEtl_000"
},
"linked_id": "deposit_f0A_1666079759165_P3FtR_000",
"linked_object" : "deposit",
"to": {
"account_id": "account_F0A_1727427042466_UKUbp_000"
},
"type": "deposit_transfer"
}'
{
"created": 1645104826,
"description": "Transfer for mapping funds to partner",
"from": {
"account_id": "account_F0A_1727424388583_DeEtl_000",
"amount": "830000.00",
"currency": "INR"
},
"id": "transfer_f0A_1912224718366_laE3R_000",
"linked_id": "deposit_f0A_1666079759165_P3FtR_000",
"linked_object" : "deposit",
"livemode": false,
"metadata": null,
"object": "transfer",
"status": "completed",
"to": {
"account_id": "account_F0A_1727427042466_UKUbp_000",
"amount": "830000.00",
"currency": "INR"
},
"type": "deposit_transfer"
}
It is important to note that Xflow and its banking partner will match the remitter name against the partner name at the time of settlement. Any significant deviation could lead to rejections. Hence its important to inform the connected users about mapping the desposits accurately to partners.
If your connected user has made an error in mapping, please drop a note to support@xflowpay.com with the deposit_id
that was incorrectly mapped. We will try to reverse this for you if it is not already submitted for settlement.
If you are not looking to collect via UPI, you can jump to section 6.
5. Collecting Payments via UPI
In this section, we will take a look at the step by step process of collecting payments using UPI in your own native checkout experience. Xflow supports both one-time payments and recurring payments for UPI.
5.1. Get a Virtual Payment Address for your merchants
When your connected user is activated, Xflow creates UPI handles or Virtual Payment Addresses (VPAs) for your merchant. These are modeled as addresses of type = vpa
and category = xflow_receive
. The VPA can be found in the field address.vpa.id
of the address
object. Using the values of the parameters onetime
and recurring
from the vpa
hash you will be able to know which VPA can be used for one-time payments and which VPA can be used for recurring payments such as those in subscriptions
. You will use these VPAs for collecting payment for your merchants.
{
"bank_account": null,
"billing_details": null,
"category": "xflow_receive",
"created": 1666079069,
"currency": "INR",
"id": "address_f0A_1666184962045_5J7h9_000",
"is_reusable": false,
"linked_id": "account_F0A_1666184956871_JsBZ2_000",
"linked_object": "account",
"livemode": true,
"metadata": null,
"name": "Example Business",
"object": "address",
"status": "activated",
"supporting_documentation": null,
"type": "vpa",
"vpa": {
"id": "merchantname-aggregatorname-cb@aicici.com",
"type": "upi",
"upi": {
"onetime": "enabled",
"recurring": "disabled"
},
},
"wallet": null
}
You can subscribe to the event address.status.activated
to know when the UPI ID/VPAs have been created for your connected user.
5.2. One-time payment with TransactionIntent
TransactionIntent
is the central object that enables collection through UPI via checkout. When your user selects UPI and proceeds to payment in your checkout experience, you will create the TransactionIntent
object as below.
curl -L -X POST 'https://api.xflowpay.com/v1/transaction_intents' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d $'{
"amount": "8000.00",
"currency": "INR",
"payment_method": "upi",
"payment_method_details": {
"upi":{
"flow": "intent"
}
},
"to": {
"account_id": "account_f0A_1646922461210_X7q23_000"
},
"type": "payment"
}'
{
"amount": "8000.00",
"created": 1666079293,
"currency": "INR",
"id": "transaction_intent_f0A_1663339292344_qFGAH_000",
"livemode": true,
"metadata": null,
"object": "transaction_intent",
"payment_method": "upi",
"payment_method_details": {
"upi": {
"flow": "intent",
"intent_url": "upi://pay?pa=<merchant VPA>&pn=<merchant name>&tr=<refid>&am=<amount>&cu=INR&mc=<MCC code>"
}
},
"status": "successful",
"subscription_id": "subscription_f0A_1673345732344_oMLAH_000",
"to": {
"account_id": "account_f0A_1646922461210_X7q23_000"
},
"type": "payment",
"validity": 1666084000
}'
The TransactionIntent
object above provides you with the UPI intent URL (transaction_intent.payment_method_details.upi.intent_url
) which can be used to create a QR code or redirect the partner to a UPI payment app of their choice. The process of enabling these experiences for your payers across different operating systems and front-end types is outlined next. Please note that the TransactionIntent
can only be created with currency = INR
5.2.1 QR Code Generation
Users can scan a QR code on your front-end interface to make a payment using their UPI-enabled payment service provider app. You can enable this as follows:
Step 1: Choose a Library for QR Code Generation
- For Web (JavaScript, React): For JavaScript, use a library like
qrcode.js
orqr-code-styling
. For React, use a library likeqrcode.react
to generate QR codes. - For Mobile Apps (React Native, Flutter): For React Native, use a package like
react-native-qrcode-svg
orreact-native-qrcode-generator
. For Flutter, use theqr_flutter
package to generate QR codes
Step 2: Using the UPI intent URL (payment_method_details.intent_url
) and the chosen library, integrate the library as follows:
- For Web (JavaScript):
var qrcode = new QRCode(document.getElementById("qrcode"), {
text: "upi://pay?pa=merchantVPA&pn=merchantName&tr=Refid&am=amount&cu=INR&mc=MCCcode",
width: 256,
height: 256,
});
- For React Native:
import QRCode from 'react-native-qrcode-svg';
<QRCode
value="upi://pay?pa=merchantVPA&pn=merchantName&tr=Refid&am=amount&cu=INR&mc=MCCcode"
size={256}
/>
The library takes the UPI URL string you provided and generates a QR code based on that data.
The QR code is then rendered in the specified location within your application's UI (like a div
in web or a View
in React Native)
- For React: Use the qrcode.react library to generate the QR code. Below is an example of how to integrate it into a React component
import React from 'react';
import QRCode from 'qrcode.react';
const PaymentQRCode = () => {
const upiUrl = "upi://pay?pa=merchantVPA&pn=merchantName&tr=Refid&am=amount&cu=INR&mc=MCCcode";
return (
<div>
<h2>Scan to Pay</h2>
<QRCode
value={upiUrl}
size={256}
level={"H"}
includeMargin={true}
/>
</div>
);
};
export default PaymentQRCode;
value
is the UPI URL string. size
sets the dimensions of the QR code. level
sets the error correction level (L, M, Q, H). includeMargin
adds margin around the QR code.
Step 3 : Displaying the QR Code
- For Web : The QR code will be rendered directly in the HTML element you specified, like in a
div
with an ID ofqrcode
.
<div id="qrcode"></div>
The QR code will be displayed in this div
- For React Native: The QR code will be rendered as a component in your application’s UI.
<QRCode
value="upi://pay?pa=merchantVPA&pn=merchantName&tr=Refid&am=amount&cu=INR&mc=MCCcode"
size={256}
/>
The QR code will appear where you place the <QRCode />
component.
- For React: Render the PaymentQRCode component within your application where you want the QR code to appear
import React from 'react';
import PaymentQRCode from './PaymentQRCode';
const App = () => {
return (
<div className="App">
<h1>Make a Payment</h1>
<PaymentQRCode />
</div>
);
};
export default App;
Step 4 : Consuming the QR Code
- Scanning the QR Code using a payment service provider (PSP) application: Users can scan the QR code directly using their preferred UPI-enabled PSP app’s QR code scanner. This will trigger the UPI payment process within that app, using the details encoded in the QR code
- Scanning the QR Code using the Device Camera: Many modern smartphones (especially on iOS and Android) allow users to scan QR codes directly using their camera app
5.2.2 Redirection to UPI payment applications
You can redirect your users to their preferred payment service provider app (such as Google Pay, PhonePe) when they click a button or similar UI element. You can power this as follows:
Step 1: Handle redirection across platforms
- Web: Redirecting via JavaScript
function redirectToPSP() {
window.location.href = "upi://pay?pa=merchantVPA&pn=merchantName&tr=Refid&am=amount&cu=INR&mc=MCCcode";
}
This will attempt to open the UPI app installed on the device
iOS: iOS will handle the UPI URL schema (
payment_method_details.intent_url
) natively and open the relevant app if its installed. If you need to handle different behaviors for different PSP apps, you might use universal links for apps that support them. However, for standard UPI, thepayment_method_details.intent_url
scheme should work across appsAndroid: Android handles the
payment_method_details.intent_url
schema natively. Most UPI PSP apps (Google Pay, PhonePe) will automatically detect the UPI intent and process it.
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("upi://pay?pa=merchantVPA&pn=merchantName&tr=Refid&am=amount&cu=INR&mc=MCCcode"));
startActivity(intent);
By following these steps, you can create a smooth and robust experience for users across different platforms (iOS, Android) and PSP apps (Google Pay, PhonePe etc.)
When the partner makes the payment, the transaction_intent.status
will change to successful
. You must subscribe to the event transaction_intent.status.successful
to track this change. The partner can make multiple payment attempts against the TransactionIntent
till its status changes to expired
or successful
. The underlying attempts are tracked via the Transaction
object. When an underlying Transaction
succeeds, the TransactionIntent
goes to successful
, else stays in processing
. You can list all Transaction
s associated with the TransactionIntent
as follows.
curl -L -X GET 'https://api.xflowpay.com/v1/transactions?linked_id=transaction_intent_f0A_1716217802597_jZR51_000' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"'
For successful payments, a Deposit
object is created. This deposit will increase the INR balance against the account_id
that was passed during the Create TransactionIntent call. You can retrieve the Balance for an account as shown below. To reconcile the balance you will have to map the incoming deposit amount to the partner as outlined in section 4.2.
curl -L -X GET 'https://api.xflowpay.com/v1/balance?account_id=account_F0A_1666077553446_41Hgq_000' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"'
You can now go to section 6, to see how to withdraw the received funds.
5.3. Recurring payments with Subscription
If you are looking to collect recurring payments for subscriptions, then you must create a Subscription
. The Subscription
object allows you to define the terms of Subscription
like - how much to debit, when to debit, till when to debit and so on. See the create Subscription
request and response below.
curl -L -X POST 'https://api.xflowpay.com/v1/subscriptions' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d $'{
"amount": 500.00,
"currency": "INR",
"execution_anchor": 14,
"interval": "monthly",
"payment_method": "upi",
"payment_method_details": {
"upi": {
"flow": "intent"
}
},
"to": {
"account_id": "account_f0A_1646922461210_X7q23_000"
},
"validity_start_date": "2024-10-08",
"validity_end_date": "2025-10-08",
}'
{
"amount": 500.00,
"created": 1666079293,
"currency": "INR",
"execution_anchor": 14,
"from": {
"address_id": "address_F0A_1666079163481_A7fje_000"
},
"id": "subscription_f0A_1673345732344_oMLAH_000",
"interval": "monthly",
"livemode": true,
"metadata": null,
"object": "subscription",
"payment_method": "upi",
"payment_method_details": {
"upi": {
"flow": "intent"
}
},
"status": "active",
"to": {
"account_id": "account_f0A_1646922461210_X7q23_000"
},
"validity_end_date": "2025-10-08",
"validity_start_date": "2024-10-08"
}
Refer to the API Reference to understand how you can use fields in the Subscription
hash above to create a Subscription
plan of your choice.
Once you create the Subscription
, a corresponding TransactionIntent
of type=authorize_subscription
is automatically created. You can get this TransactionIntent
as follows
curl -L -X GET 'https://api.xflowpay.com/v1/transaction_intents?subscription_id=subscription_f0A_1673345732344_oMLAH_000' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"'
On the TransactionIntent
object, the field transaction_intent.payment_method_details.upi.intent_url
will have the URL which you must use to generate a QR code or to redirect the partner to their UPI apps. The partner must authorize the Subscription
in their app which will update the status
of TransactionIntent
and Subscription
to successful
and activated
respectively.
Once the Subscription
is activated Xflow will automatically execute the Subscription
and auto-debit the partner’s account at the interval and anchor that you have set. For each Subscription
execution, a TransactionIntent
is created with type = payment
. Please note that when the amount to debit is greater than 15000/- INR the payer will have to authenticate the collect request on their UPI application for every debit. This is required as per NPCI guidelines.
Xflow will automatically trigger a notification from the bank informing the partner of an upcoming debit at least 24 to 48 hours prior to the debit time (for interval=daily
it will be triggered 24 hours in advance). This step is mandated regulatorily and Xflow will do this on your behalf.
Similar to one-time payment, a Deposit
object is created once the transaction_intent.status
of the TransactionIntent
that is executing the Subscription
goes to successful
. You must subscribe to the event transaction_intent.status.successful
to track when the transaction_intent.status
goes to successful
.
This deposit will increase the INR balance against the account_id
that was passed during the Create Subscription call. You can retrieve the Balance for an account as shown below. To reconcile the balance you will have to map the incoming deposit amount to the partner as outlined in section 4.2.
curl -L -X GET 'https://api.xflowpay.com/v1/balance?account_id=account_F0A_1666077553446_41Hgq_000' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"'
6. Withdraw the funds
At this stage, you have an activated receivable, and you have moved funds to the right partner level balance. You are now all set to withdraw the funds.
The funds must be withdrawn within 10 days from the deposit day else they will be returned to the partner. If there are genuine reasons for delay, you can drop a note to support@xflowpay.com
6.1. Reconcile the Receivable
All withdrawals at Xflow must be against an activated receivable. This is done by reconciling the receivable against the pending balance. You will do this by hitting the reconcile endpoint on the Receivable object.
curl -L -X POST 'https://api.xflowpay.com/v1/receivables/receivable_f0A_1727427046725_mjXJy_000/reconcile' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"amount": "830000.00"
}'
Please note that the amount passed in the receivable command is in the currency of the receivable. Furthermore, we support partial reconciliation which means you can reconcile the receivable for a lower value than the actual receivable value (e.g. advance payment use-case).
6.2 Tracking Payouts
Once reconciliation is complete, Xflow will initiate a payout to your bank account. All your reconciliations are aggregated into different payouts as per following logic:
- Goods: Purpose Code + Payout Currency
- Software: Purpose Code + Payout Currency + Remitter Type (Business / Individual)
{
"amount": "10000.00",
"arrival_date": 1675143082,
"automatic": true,
"created": 1666160776,
"currency": "USD",
"id": "payout_f0A_1666160776922_Q1hjW_000",
"livemode": true,
"metadata": null,
"object": "payout",
"payment_method": "global_wire",
"payment_method_details": null,
"payout_confirmation": null,
"statement_descriptor": "XFLOW PAYOUT F0A-1666160776922-J2uic-000",
"status": "settled",
"to": {
"account_id": "account_F0A_357987759890_X7lb0_056",
"address_id": "address_f0A_357944459890_fJSRH_080"
}
}
Once the payout goes to settled
status, we will update payout_confirmation
with MT103 or an equivalent document from our bank within 1 business day. To track the status of a payout, please listen to payout.status.settled
event.
The USD or the Foreign currency amount on the Payout
object is populated only after the Payout
goes to settled
state. At this stage, an ExchangeRates object is created which helps you understand how we arrived at the USD amount. We will explain ahead how to fetch this ExchangeRate
object.
You will be able to unpack each Payout
to understand which receivables funded the particular payout. To do this, you will need to look at the Payment object. At Xflow, the underlying real world money movement is modelled via the Payment
objects of different types. As an example, the Payout
object has an underlying Payment
of type = payout
. Let’s retrieve this Payment
of type = payout
.
curl -L -X GET 'https://api.xflowpay.com/v1/payments?linked_id=payout_f0A_1666160776922_Q1hjW_000' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
{
"created": 1666160776,
"fee_plan_id": "fee_plan_F0A_1666077553446_a8sn4_000",
"from": {
"amount": "830000.00",
"currency": “INR”
},
"id": "payment_f0A_1666160776922_DkBou_000",
"is_exchange_rate_applicable": false,
"linked_id": "payout_f0A_1666160776922_Q1hjW_000",
"linked_object": "payout",
"linked_payments": [],
"linked_payments_file_id": "file_F0A_1666078140654_lQm8l_000",
"livemode": true,
"object": "payment",
"payout_eligible_at": null,
"to": {
"amount": "10000.00",
"currency": “USD”
},
"type": "payout"
}
Before we unpack the Payout
further, remember the ExchangeRate
object we mentioned earlier. The payment_id
field on the ExchangeRate
object will be of the Payment
object of type = payout
. This is how you will be able to link an ExchangeRate
to Payout
.
On the Payment
object of type payout
you will find linked_payments_file_id
. This file provides list of transfer
payments associated with the payout
. These transfer
payment moves the funds from your connected user to your account after reconciliation. These transfer
payments are linked backwards to another Payment
of type = funds_debit
which is further backwards linked to Payment
of type = reconcile
. The chain looks something like this
payout
--> transfer
(gives you the connected user) -->funds_debit
(gives Receivable ID) --> reconcile
(gives reconcile events).
Each Payment provides additional information through fields linked_id
and linked_object
.
Let’s focus on the transfer
first. Since the payout made to you is an aggregation of several transaction
s at connected user level, the payout
payment is backwards linked to multiple transfer
payments. This is the reason, we provide this linkage via a File
object. Pick one of the transfer
payment from the file, and let’s fetch this object as shown below.
curl -L -X GET 'https://api.xflowpay.com/v1/payments/payment_f0A_1666160776922_DkBou_000' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
{
"created": 1666160776,
"fee_plan_id": "fee_plan_F0A_1666077553446_a8sn4_000",
"from": {
"amount": "830000.00",
"currency" : "INR"
},
"id": "payment_f0A_1666160776922_DkBou_000",
"is_exchange_rate_applicable": false,
"linked_id": "transfer_f0A_1666261130393_wuH6k_000",
"linked_object": "transfer",
"linked_payments": [
{
"account_id": "account_F0A_1727424388583_DeEtl_000",
"payment_id": "payment_f0A_1666160779999_ABCou_000",
"payment_type": “funds_debit
}
],
"linked_payments_file_id": null,
"livemode": true,
"object": "payment",
"payout_eligible_at": null,
"to": {
"amount": "830000.00",
"currency" : "INR"
},
"type": “transfer”
}
This transfer
has top-level linked_object
of type Transfer. From the Transfer
object, you will the get connected user’s account (field: from.account_id
).
Now let's focus on the linked_payments
hash on the transfer
payment. You will see that the linked_payments.payment_id
is of type funds_debit
. Since there is 1:1 relationship between transfer
and funds_debit
, the linkage is directly provided on the object itself. Let’s fetch this funds_debit
payment
curl -L -X GET 'https://api.xflowpay.com/v1/payments/payment_f0A_1666160779999_ABCou_000' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
{
"created": 1666160776,
"fee_plan_id": "fee_plan_F0A_1666077553446_a8sn4_000",
"from": {
"amount": "830000.00",
"currency": "INR"
},
"id": "payment_f0A_1666160779999_ABCou_000",
"is_exchange_rate_applicable": false,
"linked_id": "receivable_f0A_1727427046725_mjXJy_000",
"linked_object": "receivable",
"linked_payments": [
{
"account_id": "account_F0A_1727424388583_DeEtl_000",
"payment_id": "payment_f0A_1666160779999_PQCou_000",
"payment_type": "reconcile"
}
],
"linked_payments_file_id": null,
"livemode": true,
"object": "payment",
"payout_eligible_at": null,
"to": {
"amount": "830000.00",
"currency": "INR"
},
"type": "funds_debit"
}
The linked_id
on the funds_debit
object will provide you the receivable that was reconciled. This way, you will be able to get all the receivables that contributed to the payout.
If you support partial reconciliation, and are looking to get to a specific reconcile event, then use the field linked_payment.payment_id
which will give you the payment
of type = reconcile
. Thus, you can traverse upstream from Payment
of type = payout
to a Payment
of type = reconcile
.
Though rare, it is possible that some of your connected user's transactions are rejected by the banking partner. The individual transactions that are rejected can be identified through payment of type unfunds_debit
present on the file
object provided in linked_payments_file_id
field on the payout
payment. On the unfunds_debit
object look for the hash linked_payments
. Within this hash look for linked_payment.payment_id
of type = transfer
, and go further upstream from transfer
to get the funds_debit
and reconcile
to know which receivable and reconciliation was impacted. We will work with our banking partner to return the funds to the partner, and provide you and offline confirmation about these reversed transactions.
6.3. Providing supporting documentation (For Goods receivables)
If you have just reconciled a goods receivable, you will notice that the supporting_documentation.status
has turned to pending
status. Within 15 days, you/your connected user must submit the relevant documents to complete the regulatoty requirements. Following documents will be required
For B2B transactions : Shipping document (Airway Bill or Bill of lading) and Customs document (Bill of Entry or BOE). The BOE is available with the importer (your connected user's partner).
For B2C transactions : Shipping document (Airway Bill or Bill of lading).
These supporting documents are modeled as the File
object. For both shipping and customs document, create the File
object with purpose = transactional_document
. In response, you will receive a unique file identifier which must be used on the receivable object which will be used for updating the receivable.
curl -L -X POST 'https://api.xflowpay.com/v1/files' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-F file=@"AirwayBill.pdf" \
-F payload='{
"purpose": "transactional_document"
}'
{
"created": 1675153265,
"file_name": "Form15CA.pdf",
"id": "file_F0A_1675153265850_IREzx_000",
"livemode": false,
"metadata": null,
"object": "file",
"purpose": "transactional_document",
"size": 493117,
"type": "pdf",
"url": "https://api.xflowpay.com/v1/files/file_F0A_1675153265850_IREzx_000/contents"
}
Once you have created the files, you must now update the receivable. For updating the receivable, you must now mention the type of document as shipping
or customs
as shown below.
curl -L -X POST 'https://api.xflowpay.com/v1/receivables/receivable_f0A_1727427046725_mjXJy_000' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Content-Type: application/json' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-d '{
"supporting_documentation": {
"documents": [
{
"file_id": "file_F0A_1666078140654_lQm8l_000",
"reconcile_id": "payment_f0A_1666160779999_PQCou_000",
"type":"shipping"
},
{
"file_id": "file_F0A_1576078140852_lbKll_020",
"reconcile_id": "payment_f0A_1666160779999_PQCou_000",
"type":"customs"
}
]
}
}'
The field reconcile_id
is the identifier for the reconciliation action you performed in step 6.1. This is the id
on the Payment
of type = reconcile
which we introduced in section 6.2.
curl -L -X GET 'https://api.xflowpay.com/v1/payments?type=reconcile&linked_id=receivable_f0A_1727427046725_mjXJy_000' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
The above List API call will return a list of Payment
object of type = reconcile
. If you have multiple reconciliations on a receivable, you will have to pick the appropriate reconcile
object because your documents are submitted against each outgoing payment. This is the final step of the end-to-end flow. Also note that your users can provide the document at the time of reconciliation if it's available as follows.
curl -L -X POST 'https://api.xflowpay.com/v1/receivables/receivable_f0A_1727427046725_mjXJy_000/reconcile' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"amount": "830000.00"
"supporting_documentation": {
"documents": [
{
"file_id":"file_F0A_1675153265850_IREzx_000",
"type": "shipping"
}
]
}
}'
After you have provided the required documents, the supporting_documentation.status
will transition to completed
. Please reach out to support@xflowpay.com if you have challenges in providing the documents within the provided time. Without a legitimate reason, delaying document submission may lead to transactions getting blocked for your connected users. Note that, it is quite likely that the BOE is not available within 15 days due to delays in shipment and custom clearance. We will manage such events with our banking partner upon receipt of communication from your side.
7. Testmode
You can use the testmode to simulate payments to test your integration. In testmode, You will be able to mimic livemode without actually moving real money. The advantage here is that if your integration works in testmode, you can have confidence that livemode will work once you decide to flip the switch. Please note that data from the testmode will not be carried over to the livemode. To make your testmode more productive, we provide additional testmode specific features:
- User activation: You can add dummy data for creating a connected user. There will be no verification checks for fields like business identifiers and your connected user will be activated instantaneously
- Partner activation: All partners created will be activated instantaneously
- Simulating a Deposit: You will be able to simulate payment from a partner to a connected user. You can do this via the API or from the Partner Details page within the context of a connected user. You will see a button Add Receiving Account which will instantaneously add funds to the pending balance of the connected user
- Creating a VPA address: You will be able to create an address of
type=vpa
for your connected user. This will be a dummy address that you cannot pay to. You can do this via the API or from the connected user dashboard. You will see a button Create a Deposit which will instantaneously create a VPA id for the connected user - TransactionIntent success: If a
TransactionIntent
is created for any amount other than INR 116, aTransaction
will instantaneously be created against it and will go tosuccessful
status. Immediately after, theTransactionIntent
's status will also go tosuccessful
- TransactionIntent failure: If a
TransactionIntent
is created for an amount of INR 116, aTransaction
will immediately be generated against it and will go tofailed
status. Immediately after, theTransactionIntent
's status will go toexpired
- Subscription success: If a
Subscription
is created for any amount other than INR 116, Xflow will create aTransactionIntent
oftype=authorize_subscription
, a successfulTransaction
against theTransactionIntent
, transition theTransactionIntent
object tosuccessful
status and transition theSubscription
toactivated
status - Subscription failure: If a
Subscription
is created for an amount of INR 116, Xflow will create aTransactionIntent
oftype=authorize_subscription
, create aTransaction
against theTransactionIntent
which will transition tofailed
status, theTransactionIntent
will go toexpired
status post the expiration of validity and theSubscription
will stay in draft status forever - Subscription execution: In case of a successful
Subscription
creation, based on theinterval
and thevalidity
chosen by you, Xflow will automatically carry out a maximum of 5 executions of theSubscription
2 mins apart and createTransactionIntent
andTransactions
for the same against theSubscription
- Receivable activation: Receivables once confirmed will immediately auto-transition to activated status
- Payouts: The initialized payout will auto-transition to settled status in 5 minutes which will allow you to see aggregation logic.
Conclusion
That concludes our integration guide. We will be happy to get on a call and help you integrate faster. Just drop a note to support@xflowpay.com