The Advanced Routing feature is in private beta. Request access to enable the feature.
Payment Components support the Advanced Routing feature in Chargebee Billing. If you have multiple payment gateways configured, Advanced Routing allows you to direct customer payments through specific gateways based on rules defined in Billing.
Sample application
This tutorial walks you through the sample application that demonstrates the use of Advanced Routing with Chargebee.js Payment Components.
Routing variables
The rules in Advanced Routing help select the payment gateway based on payment amount, currency code, billing country, and other variables. The following table lists the routing variables and their corresponding values in Payment Components.
| Routing variable | How Chargebee determines the value |
|---|---|
| Amount | payment_intent.amount set while creating or updating the payment_intent. |
| Currency code | payment_intent.currency_code set while creating or updating the payment_intent. |
| Payment method | payment_intent.payment_method_type set automatically by Chargebee when a customer selects a payment method on the Payment Component. |
| Plan item price* | A plan item price is an item_price with item_type set to plan. You can pass the id of the plan item price in options.context.cart.lineItems[].id when creating or updating the Payment Component. |
| Billing country* | The value passed for options.context.billingAddress.countryCode when creating or updating the Payment Component. |
| Shipping country* | The value passed for options.context.shippingAddress.countryCode when creating or updating the Payment Component. |
Prerequisites
Before you begin, ensure you have the following:
- Set up payment gateways in Chargebee Billing.
- Configure payment methods.
- Configure Advanced Routing.
- Set up the Product Catalog in Chargebee.
- Install Node v18 or above.
Running the sample application locally
You can download and run the sample application locally by following the instructions in its Readme on GitHub.
Code walkthrough
This section explains how the code in the sample app works.
Load Chargebee.js (client)
Load Chargebee.js on the checkout page by adding the following script to the <head> element of the page.
<script src="https://js.chargebee.com/v2/chargebee.js"></script>
Initialize Chargebee.js (client)
Once the page loads, initialize Chargebee.js with a publishable key. This creates a Chargebee object used to create components.
const chargebee = window.Chargebee.init({
site: env.site,
publishableKey: env.publishableKey,
})
Request a payment intent (client)
As soon as the customer provides the necessary details, send the information (checkoutData) to your server to create a new payment_intent. This data includes everything that is required to determine the amount and currency of payment. Depending on your implementation, this data typically includes the list of item prices, quantities, and shipping and billing addresses.
A payment_intent resource manages a customer's payment session. It includes details such as the amount, currency code, payment status, and failed payment attempts. It also prevents duplicate charges for the same payment session.
const url = "https://YOUR_DOMAIN/payment-intent";
const response = await fetch(url,{
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(checkoutData[index])
});
if (!response.ok)
throw new Error(`Error: ${response.status}`);
paymentIntent = await response.json();
Create a payment intent (server)
At your server, use the information received from the frontend to calculate the amount to be charged, after coupons, discounts, credits, taxes, or shipping charges. Then, create a payment_intent in Billing and send it to the frontend.
The payment_intent tracks the first three Advanced Routing variables: amount, currency code, and payment method.
app.post('/payment-intent', async (req, res) => {
const url = `https://${env.site}.chargebee.com/api/v2/payment_intents`;
const checkoutData = req.body;
console.log(`POST /payment-intent/ Request:\n${JSON.stringify(checkoutData)}`);
try {
const result = await fetch(url, {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa(`${env.apiKey}:`),
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
amount: calculateAmount(checkoutData), //Implement this function.
currency_code: getCurrencyCode(checkoutData) //Implement this function.
})
})
const response = await result.json();
console.log(`POST /payment-intent/ Response:\n${response}`);
res.status(200);
res.send(response.payment_intent);
} catch (error) {
console.log(`POST /payment-intent/ Error:\n${error}`);
res.status(500);
res.send(error);
}
});
Use the Estimate APIs to calculate the amount in calculateAmount().
Add a div for the Payment Component (client)
Add an empty <div> element to your page as a placeholder for the Payment Component.
<div id="payment-component"></div>
Create the Payment Component (client)
First, create a Components object. Use it to create a PaymentComponent object by passing the following parameters:
payment_intentIDoptions.contextobject, configuring the following Advanced Routing variables as needed:- Plan item price
- Billing country
- Shipping country
components = chargebee.components({});
setPaymentComponentOptions(index);
paymentComponentOptions = {
paymentIntent: paymentIntent,
form: {
customer: {
firstName: {
required: true
},
lastName: "default"
}
},
layout: {
type: 'accordion',
showRadioButtons: true,
},
paymentMethods: {
sortOrder: ["card"]
},
context: {
cart: {
lineItems: [{ id: "plan-a", type: "plan" }] // Advanced Routing variable.
},
customer: {
firstName: "Jane",
lastName: "Doe",
billingAddress: {
firstName: "Jane",
lastName: "Doe",
phone: "555-123-4567",
addressLine1: "123 Main St",
addressLine2: "Apt 4B",
addressLine3: "",
city: "Springfield",
state: "Illinois",
stateCode: "IL",
countryCode: "US", // Advanced Routing variable.
zip: "62701"
},
shippingAddress: {
firstName: "Jane",
lastName: "Doe",
phone: "555-123-4567",
addressLine1: "123 Main St",
addressLine2: "Apt 4B",
addressLine3: "",
city: "Springfield",
state: "Illinois",
stateCode: "IL",
countryCode: "US", // Advanced Routing variable.
zip: "62701"
}
}
}
}
Then create the Payment Component with these options and the callbacks that handle the payment.
paymentComponent = components.create(
'payment',
paymentComponentOptions,
{
onError,
onSuccess,
onPaymentMethodChange,
onButtonClick,
onClose
},
);
Mount the Payment Component (client)
Mount the Payment Component on the placeholder <div>.
paymentComponent.mount("#payment-component");
Chargebee then:
- Uses the Advanced Routing variables set earlier to evaluate the Advanced Routing rules and determine the appropriate payment gateway.
- Renders an iframe with the Payment Component inside the placeholder
<div>.
(Optional) Add a Payment Button Component (client)
The Payment Button Component is a dynamic, prebuilt UI button that submits the payment form when clicked.
Skip this step if you are using a custom payment submission button. However, note that Chargebee always renders submission buttons for wallet-based payment methods within the Payment Component.
Add a div for the Payment Button Component (client)
Add an empty <div> element to your page as a placeholder for the Payment Button Component.
<div id="payment-button-component"></div>
Create and mount the Payment Button Component (client)
Use the Components object to create a PaymentButton object and mount it to the placeholder <div>.
const paymentButtonComponent = components.create(
"payment-button",
{},
{
onError,
onClose,
},
);
paymentButtonComponent.mount("#payment-button-component");
The customer can now interact with the Payment Component and use the submission button to complete the payment.
Update Advanced Routing variables
Customers may want to modify their order after the Payment Component is rendered. Before allowing changes, check how far the payment process has progressed by retrieving the payment_intent from Chargebee and checking its status.
Request payment intent retrieval (client)
const url = `https://YOUR_DOMAIN/payment-intent/${paymentIntentId}`;
const response = await fetch(url, {
method: "GET"
});
if(!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
paymentIntent = await response.json();
Retrieve the payment intent from Chargebee (server)
app.get('/payment-intent/:paymentIntentId', async (req, res) => {
const paymentIntentId = req.params.paymentIntentId;
const url = `https://${env.site}.chargebee.com/api/v2/payment_intents/${paymentIntentId}`;
try {
const result = await fetch(url, {
method: 'GET',
headers: {
'Authorization': 'Basic ' + btoa(`${env.apiKey}:`),
'Content-Type': 'application/x-www-form-urlencoded'
}
})
const response = await result.json();
console.log("GET /payment-intent/ \n",response);
res.status(200);
res.send(response.payment_intent);
} catch (error) {
res.status(500);
console.log("GET /payment-intent/ error:\n ",error);
res.send(error);
}
});
Check the payment intent status (client)
switch(paymentIntent.status){
case 'inited':
case 'in_progress':
await updatePaymentIntent(paymentIntent.id,index);
updatePaymentComponent(index);
break;
case 'expired':
//It has been 30 minutes since the `payment_intent` was created.
//Start over with a new `payment_intent`.
await createPaymentIntent(index);
updatePaymentComponent(index);
break;
case 'authorized':
//Caution! `payment_intent` is authorized.
break;
case 'consumed':
//Caution! `payment_intent` has been consumed and the payment has been collected.
}
The rest of this section covers what to do for each status.
If payment_intent.status is inited or in_progress
If payment_intent.status is inited or in_progress, the payment has not been collected. Allow the customer to modify their order and prepare updated checkoutData to send to your server.
Send new details to your server (client)
Send the updated data to your server to update the payment_intent.
const url = `https://YOUR_DOMAIN/payment-intent/${paymentIntent.id}`;
const response = await fetch(url, {
method: "PUT",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(checkoutData[index])
});
if(!response.ok) {
throw new Error(`Error: ${response.status}`);
}
paymentIntent = await response.json();
Update the payment intent with new amount and currency code (server)
On your server, use the updated frontend data to recalculate the amount and currency, then update the payment_intent in Billing. Use the Estimate APIs to calculate the amount in calculateAmount().
app.put('/payment-intent/:paymentIntentId', async (req, res) => {
const paymentIntentId = req.params.paymentIntentId;
const checkoutData = req.body;
console.log(`PUT /payment-intent/ Request:\n payment_intent.id: ${paymentIntentId}Data: \n${JSON.stringify(checkoutData)}`);
const url = `https://${env.site}.chargebee.com/api/v2/payment_intents/${paymentIntentId}`;
try {
const result = await fetch(url, {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa(`${env.apiKey}:`),
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
amount: calculateAmount(checkoutData), //Implement this function.
currency_code: getCurrencyCode(checkoutData) //Implement this function.
})
})
const response = await result.json();
console.log(`PUT /payment-intent/ Response:\n${response}`);
res.status(200);
res.send(response.payment_intent);
} catch (error) {
res.status(500);
console.log(`PUT /payment-intent/ Error:\n${error}`);
res.send(error);
}
});
Update the Payment Component (client)
Update the Payment Component, updating any Advanced Routing variables as required.
paymentComponentOptions = {
paymentIntent: paymentIntent,
layout: {
type: 'tab',
showRadioButtons: false,
},
paymentMethods: {
sortOrder: ["card"]
},
context: {
cart: {
lineItems: [{ id: "plan-b", type: "plan" }], // Advanced Routing variable.
},
customer: {
firstName: "Erika",
lastName: "Mustermann",
billingAddress: {
"firstName": "Erika",
"lastName": "Mustermann",
"phone": "634-067-4573",
"addressLine1": "Arster Hemm 59",
"city": "Bremen",
"stateCode": "HB",
"countryCode": "DE", // Advanced Routing variable.
"zip": "28279"
},
shippingAddress: {
"firstName": "Erika",
"lastName": "Mustermann",
"phone": "634-067-4573",
"addressLine1": "Arster Hemm 59",
"city": "Bremen",
"stateCode": "HB",
"countryCode": "DE", // Advanced Routing variable.
"zip": "28279"
}
}
}
}
paymentComponent.update(paymentComponentOptions);
If payment_intent.status is expired
If payment_intent.status is expired, then it has been 30 minutes since the payment_intent was created and it cannot be updated any further. Start again with a new payment_intent.
Send new details to your server (client)
Send the updated data to your server to create a new payment_intent.
const url = "https://YOUR_DOMAIN/payment-intent";
const response = await fetch(url,{
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(checkoutData[index])
});
if (!response.ok)
throw new Error(`Error: ${response.status}`);
paymentIntent = await response.json();
Create a new payment intent (server)
On your server, use the new frontend data to recalculate the amount and currency, then create the payment_intent in Billing. Use the Estimate APIs to calculate the amount in calculateAmount().
app.post('/payment-intent', async (req, res) => {
const url = `https://${env.site}.chargebee.com/api/v2/payment_intents`;
const checkoutData = req.body;
console.log(`POST /payment-intent/ Request:\n${JSON.stringify(checkoutData)}`);
try {
const result = await fetch(url, {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa(`${env.apiKey}:`),
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
amount: calculateAmount(checkoutData), //Implement this function.
currency_code: getCurrencyCode(checkoutData) //Implement this function.
})
})
const response = await result.json();
console.log(`POST /payment-intent/ Response:\n${response}`);
res.status(200);
res.send(response.payment_intent);
} catch (error) {
console.log(`POST /payment-intent/ Error:\n${error}`);
res.status(500);
res.send(error);
}
});
Update the Payment Component (client)
Update the Payment Component, updating any Advanced Routing variables as required.
paymentComponentOptions = {
paymentIntent: paymentIntent,
layout: {
type: 'tab',
showRadioButtons: false,
},
paymentMethods: {
sortOrder: ["card"]
},
context: {
cart: {
lineItems: [{ id: "plan-b", type: "plan" }], // Advanced Routing variable.
},
customer: {
firstName: "Erika",
lastName: "Mustermann",
billingAddress: {
"firstName": "Erika",
"lastName": "Mustermann",
"phone": "634-067-4573",
"addressLine1": "Arster Hemm 59",
"city": "Bremen",
"stateCode": "HB",
"countryCode": "DE", // Advanced Routing variable.
"zip": "28279"
},
shippingAddress: {
"firstName": "Erika",
"lastName": "Mustermann",
"phone": "634-067-4573",
"addressLine1": "Arster Hemm 59",
"city": "Bremen",
"stateCode": "HB",
"countryCode": "DE", // Advanced Routing variable.
"zip": "28279"
}
}
}
}
paymentComponent.update(paymentComponentOptions);
If payment_intent.status is authorized
If payment_intent.status is authorized, you can no longer update it. The meaning of an authorized payment_intent depends on the payment method and gateway. It can indicate one of the following:
- The payment amount is blocked on the customer's payment method. The customer can't use those funds unless Chargebee releases the block. The funds are collected only when you consume the
payment_intentin Billing, for example, to create or update a subscription or charge. If you don't consume thepayment_intentwithin 30 minutes of its creation, the blocked amount is automatically released. - The payment has been collected from the customer. However, if you don't consume the
payment_intentin Billing within 30 minutes of its creation, the amount is automatically refunded to the customer.
At this stage, because the payment may have been either blocked or collected, we recommend consuming the payment_intent instead of allowing the customer to change their order. However, if you choose to let the customer modify their order during the current session, make sure not to consume the payment_intent.
Once you confirm that the payment_intent won't be consumed, follow the steps below to start over with a new one.
Send new details to your server (client)
Send the updated data to your server to create a new payment_intent.
const url = "https://YOUR_DOMAIN/payment-intent";
const response = await fetch(url,{
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(checkoutData[index])
});
if (!response.ok)
throw new Error(`Error: ${response.status}`);
paymentIntent = await response.json();
Create a new payment intent (server)
On your server, use the new frontend data to recalculate the amount and currency, then create the payment_intent in Billing. Use the Estimate APIs to calculate the amount in calculateAmount().
app.post('/payment-intent', async (req, res) => {
const url = `https://${env.site}.chargebee.com/api/v2/payment_intents`;
const checkoutData = req.body;
console.log(`POST /payment-intent/ Request:\n${JSON.stringify(checkoutData)}`);
try {
const result = await fetch(url, {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa(`${env.apiKey}:`),
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
amount: calculateAmount(checkoutData), //Implement this function.
currency_code: getCurrencyCode(checkoutData) //Implement this function.
})
})
const response = await result.json();
console.log(`POST /payment-intent/ Response:\n${response}`);
res.status(200);
res.send(response.payment_intent);
} catch (error) {
console.log(`POST /payment-intent/ Error:\n${error}`);
res.status(500);
res.send(error);
}
});
Update the Payment Component (client)
Update the Payment Component, updating any Advanced Routing variables as required.
paymentComponentOptions = {
paymentIntent: paymentIntent,
layout: {
type: 'tab',
showRadioButtons: false,
},
paymentMethods: {
sortOrder: ["card"]
},
context: {
cart: {
lineItems: [{ id: "plan-b", type: "plan" }], // Advanced Routing variable.
},
customer: {
firstName: "Erika",
lastName: "Mustermann",
billingAddress: {
"firstName": "Erika",
"lastName": "Mustermann",
"phone": "634-067-4573",
"addressLine1": "Arster Hemm 59",
"city": "Bremen",
"stateCode": "HB",
"countryCode": "DE", // Advanced Routing variable.
"zip": "28279"
},
shippingAddress: {
"firstName": "Erika",
"lastName": "Mustermann",
"phone": "634-067-4573",
"addressLine1": "Arster Hemm 59",
"city": "Bremen",
"stateCode": "HB",
"countryCode": "DE", // Advanced Routing variable.
"zip": "28279"
}
}
}
}
paymentComponent.update(paymentComponentOptions);
If payment_intent.status is consumed
When payment_intent.status is consumed, the payment has been collected from the customer and used in Billing, for example, to create or update a subscription or charge.
Inform the customer and provision the product or service. Do not start another payment session at this stage.
We're always happy to help you with any questions you might have! Click here to reach out to us.