Skip to main content

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
Note

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.

Create Account for a Connected User
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"
}'
Response: Account of 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.

Note

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.

Create a Person to add a Director
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
}
}'
Create Person Response
{
"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.

Add Address of type = user_payout for connected user
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"
}'
Response: Address object
{
"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.

Activate Account
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.

AccountSettings

For 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 field address.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’s AccountSettings object which enables you to have different experiences for different connected users. The UPI VPAs (virtual payment address) creation is not controlled via AccountSettings. 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 the payout hash. Since your connected users are not getting paid out, the payout hash on connected user's AccountSettings is set to null.

FeePlan

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 hash receivable_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 type account_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 under payout and payout_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.

Add Partner
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"
}'
Response: Account of 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.

Create File object from invoice
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"
}'
Response File Object
{
"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.

Create Receivable
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"
}'
Receivable Object
{
"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.

Confirm Receivable
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 or S0102. For software, you must use S0802. If you have a usecase for Services, please reach out to support@xflowpay.com.

  • For software transaction, the hsn_code must be set to 85238090. For goods transactions, please use the hsn_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.

Address of 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.

Deposit Object
{
"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.

(TEST MODE ONLY) POST /v1/deposits
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:

Transfer the deposit to the partner level balance
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"
}'
Transfer Object
{
"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.

Address of type = vpa and category = xflow_receive
{
"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.

Create TransactionIntent
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"
}'
Response TransactionIntent Object
 {
"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 or qr-code-styling. For React, use a library like qrcode.react to generate QR codes.
  • For Mobile Apps (React Native, Flutter): For React Native, use a package like react-native-qrcode-svg or react-native-qrcode-generator. For Flutter, use the qr_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):
Integrating the Library - 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:
Integrating the Library - 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
Integrating the Library - React
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 of qrcode.
Displaying the QR code - Web
<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.
Displaying the QR code - React Native
 <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
Displaying the QR code - React
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
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, the payment_method_details.intent_url scheme should work across apps

  • Android: 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.

Redirecting via Android Native code
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 Transactions associated with the TransactionIntent as follows.

List Transactions
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.

Get Balance
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.

Create Subscription
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",
}'
Subscription Object
{
"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

Get TransactionIntent associated with a Subscription
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.

Get Balance
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.

Note

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.

Reconciling Receivable
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)
Payout Object
{
"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.

Retrieve 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}}"' \
Response:Payment of type = payout
{
"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 Paymentof 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 transactions 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.

Retrieve Payment of type = transfer

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}}"' \
Response:Payment of type = transfer
{
"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

Retrieve Payment of type = funds_debit
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}}"' \
Response: Payment of type = funds_debit
{
"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.

Create a 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=@"AirwayBill.pdf" \
-F payload='{
"purpose": "transactional_document"
}'
Response: File object
{
"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.

Updating Receivable with supporting documents

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.

Providing supporting documents during reconciliation
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, a Transaction will instantaneously be created against it and will go to successful status. Immediately after, the TransactionIntent's status will also go to successful
  • TransactionIntent failure: If a TransactionIntent is created for an amount of INR 116, a Transaction will immediately be generated against it and will go to failed status. Immediately after, the TransactionIntent's status will go to expired
  • Subscription success: If a Subscription is created for any amount other than INR 116, Xflow will create a TransactionIntent of type=authorize_subscription, a successful Transaction against the TransactionIntent, transition the TransactionIntent object to successful status and transition the Subscription to activated status
  • Subscription failure: If a Subscription is created for an amount of INR 116, Xflow will create a TransactionIntent of type=authorize_subscription, create a Transaction against the TransactionIntent which will transition to failed status, the TransactionIntent will go to expired status post the expiration of validity and the Subscription will stay in draft status forever
  • Subscription execution: In case of a successful Subscription creation, based on the interval and the validity chosen by you, Xflow will automatically carry out a maximum of 5 executions of the Subscription 2 mins apart and create TransactionIntent and Transactions for the same against the Subscription
  • 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