# 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:

# 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>
1

# 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,
})
1
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();
1
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);
    }
});
1
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 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>
1

# 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 ID
  • options.context object, configuring the following Advanced Routing variables as needed:
    • Plan item price
    • Billing country
    • Shipping country
components = chargebee.components({});
1
setPaymentComponentOptions(index);
1
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"
          }
      }
  }
}
1
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");
1

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>
1

# 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");
1
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();
1
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);
    }
});
1
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.
}
1
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();
1
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 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);
    }
});
1
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"
              }
        }
    }
}
1
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);
1

# 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();
1
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 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);
    }
});
1
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"
              }
        }
    }
}
1
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);
1

# 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 the payment_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 in calculateAmount().

Warning
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 = "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();
1
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 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);
    }
});
1
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"
              }
        }
    }
}
1
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);
1

# 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.