# Advanced Routing
Private Beta
The Advanced Routing (opens new window) feature is in private beta. To request access, contact Chargebee Support (opens new window).
Payment Components supports the Advanced Routing (opens new window) feature in Chargebee Billing. If you have multiple payment gateways (opens new window) configured, Advanced Routing allows you to direct customer payments through specific gateways based on rules defined in Billing (opens new window).
# Sample application
This tutorial walks you through the sample application (opens new window) that demonstrates the use of Advanced Routing using 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 table below lists the routing variables and their corresponding values in Payment Components.
Routing Variable | How Chargebee determines the value |
---|---|
Amount | payment_intent.amount (opens new window) set while creating (opens new window) or updating (opens new window) the payment_intent . |
Currency code | payment_intent.currency_code (opens new window) set while creating or updating the payment_intent . |
Payment method | payment_intent.payment_method_type (opens new window) 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 (opens new window) 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. |
Important
The value for routing variables marked with an asterisk (*) is taken strictly from the options.context
parameter when creating (opens new window) or updating (opens new window) the Payment Component. User inputs in the Payment Component form fields don't affect Advanced Routing.
# Prerequisites
Before you begin, ensure you have the following:
- Set up payment gateways (opens new window) in Chargebee Billing.
- Configure payment methods (opens new window).
- Configure Advanced Routing (opens new window).
- Set up the Product Catalog (opens new window) in Chargebee.
- Install Node v18 or above.
# Running the sample application locally
You can download and run the sample application (opens new window) locally by following the instructions in its Readme (opens new window) on GitHub.
# Code walkthrough
This section explains how the code in the sample app (opens new window) 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 (opens new window). This creates a Chargebee
object used to create components.
const chargebee = window.Chargebee.init({
site: env.site,
publishableKey: env.publishableKey,
})
2
3
4
# Request a payment_intent
(opens new window) (client)
As soon as the customer provides the necessary details, send the information (checkoutData
) to you server to create a new payment_intent
(opens new window). 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, shipping and billing addresses.
What is a `payment_intent`?
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 = "http://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();
2
3
4
5
6
7
8
9
10
11
# 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 (opens new window) a payment_intent
in Billing and send it to the frontend.
Note
The payment_intent
tracks the first three Advanced Routing variables:
- Amount
- Currency code
- 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);
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
✅ Estimates APIs
Use the Estimate APIs (opens new window) to calculate the amount incalculateAmount()
.
# 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 the following parameters:
payment_intent
IDoptions.context
object, 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"
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 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 (opens new window) 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.
Payment Submission Button
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");
2
3
4
5
6
7
8
9
The customer can now interact with the Payment Component and use the submission button to complete the payment.
# Update Advanced Routing variables (client)
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 (opens new window) the payment_intent
from Chargebee and checking its status
.
# Request payment_intent
retrieval (client)
DETAILS
const url = `http://YOUR_DOMAIN/payment-intent/${paymentIntentId}`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content_Type": "application/json"
}
});
if(!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
paymentIntent = await response.json();
2
3
4
5
6
7
8
9
10
11
12
13
# Retrieve payment_intent
from Chargebee (server)
DETAILS
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);
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Check payment_intent.status
(client)
DETAILS
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.
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# If payment_intent.status
is inited
or in_progress
DETAILS
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 = `http://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();
2
3
4
5
6
7
8
9
10
11
12
13
# 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.
✅ Estimates APIs
Use the Estimate APIs (opens new window) to calculate the amount incalculateAmount()
.
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);
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 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": "ON",
"countryCode": "DE", // Advanced Routing variable.
"zip": "28279"
},
shippingAddress: {
"firstName": "Erika",
"lastName": "Mustermann",
"phone": "634-067-4573",
"addressLine1": "Arster Hemm 59",
"city": "Bremen",
"stateCode": "ON",
"countryCode": "DE", // Advanced Routing variable.
"zip": "28279"
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
paymentComponent.update(paymentComponentOptions);
# If payment_intent.status
is expired
DETAILS
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. We must now 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 = "http://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();
2
3
4
5
6
7
8
9
10
11
# Create a new payment_intent
(server)
On your server, use the new frontend data to recalculate the amount and currency, then create (opens new window) the payment_intent
in Billing.
✅ Estimates APIs
Use the Estimate APIs (opens new window) to calculate the amount incalculateAmount()
.
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);
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 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": "ON",
"countryCode": "DE", // Advanced Routing variable.
"zip": "28279"
},
shippingAddress: {
"firstName": "Erika",
"lastName": "Mustermann",
"phone": "634-067-4573",
"addressLine1": "Arster Hemm 59",
"city": "Bremen",
"stateCode": "ON",
"countryCode": "DE", // Advanced Routing variable.
"zip": "28279"
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
paymentComponent.update(paymentComponentOptions);
# If payment_intent.status
is authorized
DETAILS
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_intent
in Billing—for example, to create (opens new window) or update (opens new window) a subscription or charge (opens new window). If you don’t consume thepayment_intent
within 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_intent
in Billing within 30 minutes of its creation, the amount is automatically refunded to the customer.
✅ Estimates APIs
Use the Estimate APIs (opens new window) to calculate the amount incalculateAmount()
.
⛔ Warning
At this stage, because the payment may have been either blocked or collected, we recommend consuming thepayment_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 thepayment_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 = "http://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();
2
3
4
5
6
7
8
9
10
11
# Create a new payment_intent
(server)
On your server, use the new frontend data to recalculate the amount and currency, then create (opens new window) the payment_intent
in Billing.
✅ Estimates APIs
Use the Estimate APIs (opens new window) to calculate the amount incalculateAmount()
.
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);
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 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": "ON",
"countryCode": "DE", // Advanced Routing variable.
"zip": "28279"
},
shippingAddress: {
"firstName": "Erika",
"lastName": "Mustermann",
"phone": "634-067-4573",
"addressLine1": "Arster Hemm 59",
"city": "Bremen",
"stateCode": "ON",
"countryCode": "DE", // Advanced Routing variable.
"zip": "28279"
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
paymentComponent.update(paymentComponentOptions);
# If payment_intent.status
is consumed
DETAILS
When payment_intent.status
is consumed
, it means the payment has been collected from the customer and used in Billing—for example, to create (opens new window) or update (opens new window) a subscription or charge (opens new window).
Inform the customer and provision the product or service. Do not start another payment session at this stage.