Tutorial Scope
This tutorial explains the steps to implement 3DS2-enabled card payments for Chargebee subscriptions using Checkout.com elements. The integration involves client-side implementation using Frames and sequential communication between Checkout.com and Chargebee APIs for the backend payment process. The sample code is provided to help you try out the integration at your end.
Frames Overview
Checkout.com's customizable iFrame-based hosted payment form and its fields can be used to collect sensitive card details without the need to collect it at your end. Frames can be accessed using Checkout.com's JavaScript library.
Learn more about Frames.
Other ways to integrate: If you are looking for any other integration methods that helps you create a checkout form with easier subscription creation workflows, you can choose to integrate using:
- Chargebee.js library - for quick and easy integration
- Chargebee hosted pages API - for custom integration
Integration flow
The entire 3DS checkout flow including subscription creation is outlined as follows:
Collect payment details
- Users enter their card details on a checkout form built using Frames
- Upon submission, the card information is passed to the Checkout.com’s server in exchange for a temporary card token. Pass this to your backend.
Create Payment Request
- Using Chargebee’s Estimate API, the estimated checkout amount is fetched from Chargebee. (Frontend/Backend)
- A 3DS2 payment request with the
amount
set to$0
(or actual amount for non-trial plans), containing the temporary card token, (card details) and other checkout details are submitted to Checkout.com. This is used to verify the card and return a permanent card source Id (examplesrc_nwd3m4in3hkuddfpjsaevunhdy
) which can be used for subsequent payments thereafter.
If immediate payment is requested by the merchant, the amount can be the actual subscription fee. In that case, a separate card verification is not required.
3DS Challenge Flow
- If the card is enrolled for 3DS2 payments, the payment redirects to the 3DS challenge flow authentication window.
- On successful authentication, payment
status
changes from “Pending
” to "Authorized
" or “Card Verified
”. You can verify the paymentstatus
using the Get payment details API using the session ID.
Create Subscription
- Once the payment status has changed to “
Authorized
” or “Card Verified
”, the payment Id, permanent card source Id (src_nwd3m4in3hkuddfpjsaevunhdy
), along with other checkout details are passed to Chargebee using the create subscription API. - Chargebee captures the payment and creates the Subscription.
- Once the payment status has changed to “
Prerequisites
Before trying out this tutorial, you need to setup the following:
- A Chargebee account
- Your Chargebee test site's API key
- Product Catalog configured in Chargebee
- Checkout.com’s client library set up. Download and importthe client library of your choice and configure the client library with Checkout.com's test secret key.
- Configure Checkout.com as a payment gateway in Chargebee.
- Webhooks setup for notifications (Optional)
Setup Client library
Download and import the client library of your choice and configure.
For the tutorials we have configured the credentials in the constructor of the class.
/* Set false for Production Checkout.com account */
this.api = CheckoutApiImpl.create("<checkout.com-secret-key>", true, "<checkout.com-public-key>");
Environment.configure("<chargebee-site-name>", "<chargebee-api-key>");
We have setup the client library in the checkoutdotcom_3ds_controller.rb.
# Configuring the server for Checkout.com API calls.
CheckoutSdk.configure do |config|
config.secret_key = '<checkout.com-secret-key>'
config.public_key = 'checkout.com-public key'
config.base_url = 'https://api.sandbox.checkout.com' # for sandbox
end
ChargeBee.configure(site: '<chargebee-site-name>',
api_key: '<chargebee-api-key>')
When you have downloaded the Checkout.com PHP SDK, add the Secret Key in the checkout-sdk-php/src/config.ini and initialize the SDK.
public function __construct()
{
ChargeBee_Environment::configure("<chargebee-site-name>", "<chargebee-api-key>");
$this->initCheckoutApi();
}
private function initCheckoutApi()
{
// Add the Secret Key in the checkout-sdk-php/src/config.ini file
$this->checkout = new CheckoutApi();
}
Client-side Implementation
The client-side implementation of Checkout Frames and Chargebee API integration for creating a subscription in Chargebee.
Checkout form
The client-side implementation starts by building a payment form for collecting payment details (customer and card details). The sample form we've used here contains fields for customer and card information.
You need to initialize Frames for collecting payment details by authenticating using your Public key as shown below:
Frames.init("<your-gateway-account-public-key>");
Add the Checkout.com Frames JavaScript file in the header tag of the form. The frames script will relay the payment details to Checkout.com’s server for processing the card information and payments.
<script src="https://cdn.checkout.com/js/framesv2.min.js"></script>
Event handler: card validation
Add the card validation event handler to listen to card validation responses from Checkout.com and is triggered as soon as the card details are submitted and the card validation status changes. This is done to verify whether the valid card is used for the 3DS flow.
Frames.addEventHandler(
Frames.Events.CARD_VALIDATION_CHANGED,
function (event) {
console.log("CARD_VALIDATION_CHANGED: %o", event);
payButton.disabled = !Frames.isCardValid();
}
);
Event handler: Card Tokenization
Add the card tokenization event handler which listens to card tokenization responses and retrieves the card token. This event must be triggered as soon as the valid card details are submitted.
Frames.addEventHandler(
Frames.Events.CARD_TOKENIZED,
function (event) {
var el = document.querySelector(".success-payment-message");
el.innerHTML = "Card tokenization completed<br>" +
"Your card token is: <span class=\"token\">" + event.token + "</span>";
}
);
An alpha-numeric card token is returned to the user. Now, the next step is to create a card payment.
Card tokens are single use tokens which have a lifespan of 15 minutes.
Server-side Implementation
Implementation of Chargebee’s Subscription API within the Checkout.com payment collection workflow for creating a subscription in Chargebee.
Step 1: Create a card payment
Prerequisites
Before creating a card payment, ensure that:
- The payment status is either
Authorized
orCard Verified
- A redirect success and failure URL configured for 3DS in your Hub dashboard
For Authorized
payments, the estimated amount for the checkout can be retrieved using the Estimate API.
Using the temporary card token generated at the time of payment creation, initiate a card payment request of $0 or actual amount that you got from the estimate API using card token using the following endpoint:
For the tutorials we have configured the credentials in the constructor of the class.
/* Returns a Processed payment source. The Payment request is created */
public PaymentProcessed CreateProcessedPaymentSource(String token){
TokenSource tokenSource = new TokenSource(token);
PaymentRequest<TokenSource> paymentRequest = PaymentRequest.fromSource(tokenSource, Currency.USD, 10000L);
paymentRequest.setCapture(false);
paymentRequest.setThreeDS(ThreeDSRequest.from(true));
PaymentResponse response;
try {
response = this.api.paymentsClient().requestAsync(paymentRequest).get();
this.paymentSourceID = response.isPending() ? response.getPending().getId() : response.getPayment().getId();
/* Keep checking whether the payment has been approved */
if (response.isPending() && response.getPending().requiresRedirect()) {
redirect(response);
}
else if (response.getPayment().isApproved()){
paymentSuccessful(response.getPayment());
}
else if (response.getPayment().getStatus().equals("Declined")){
paymentDeclined();
}
else System.out.println("Unknown error occurred.");
} catch (CheckoutValidationException e) {
validationError(e.getError());
} catch (CheckoutApiException e) {
System.out.println("Payment request failed with status code " + e.getApiResponseInfo());
throw e;
} catch (ExecutionException e) {
System.out.println("Checkout.com Error: " + e.getCause().toString());
System.exit(0);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
return this.payment;
}
# Create a PaymentRequestSource from a token
def get_token(token_id)
payment_request_source = CheckoutSdk::PaymentRequestSource.new
payment_request_source.type = 'token'
payment_request_source.token = token_id
payment_request_source.amount = 10_000
payment_request_source.currency = 'USD'
payment_request_source.threeds_enabled = true
payment_request_source.capture = false
payment_request_source
end
# Create a Payment Source from a PaymentRequestSource
def create_checkout_payment_source(token_id)
# Send API call to Checkout.com
@api.checkout_connection.reset
response = @api.request_payment(get_token(token_id))
# Check whether there's a valid response.
# If response is invalid then print the response data.
if response.status > 400
puts 'Token might have been already used or expired. Check the response from Checkout.com mentioned below'
puts response.data
exit
end
response_json = JSON.parse(response.data[:body])
# If the payment is declined then exit the program
if response_json['status'].eql? 'Declined'
puts "Reason for Decline: #{response_json['response_summary']}"
abort 'Payment has been declined so exiting this program'
end
response_json
end
When you have downloaded the Checkout.com PHP SDK, add the Secret Key in the checkout-sdk-php/src/config.ini and initialize the SDK.
function getPaymentSource($token)
{
$this->$token = $token;
// Create a payment method instance with card details
$token = new TokenSource($token);
// Prepare the payment parameters
$payment = new Payment($token, 'USD');
$payment->capture = false;
$payment->threeDs = new ThreeDs(true);
$payment->amount = 10000; // = 100.00
// Send the request and retrieve the response
try {
$cdc = $this->getCheckout();
$response = $cdc->payments()->request($payment);
$this->setPaymentSourceID($response->id);
echo "Payment Intent ID: " . $this->getPaymentSourceID() . PHP_EOL;
$this->redirectUrl($response);
} catch (CheckoutHttpException $che) {
echo "Checkout.com Error: " . $che->getErrors()[0] . PHP_EOL;
echo "Possibly the token has expired or it has been used." . PHP_EOL;
exit();
}
$this->setPayment($response);
return $this->payment;
}
Learn more about creating a card payment.
Creating a payment of $0 initiates a card verification request to Checkout.com servers. It creates a permanent source Id (for example,
src_nwd3m4in3hkuddfpjsaevunhdy
) for the card for future transactions and also generates a payment “id
”. You may also choose to pass the actual subscription amount as value for the “amount
” field in which case, the verification happens automatically.
Step 2: 3DS challenge flow - Response
The payment response is a 202 asynchronous payment response for the 3DS challenge flow.
/* Redirect the user to the Authorization Flow URL */ this.redirectURL =
paymentResponse.getPending().getRedirectLink().getHref(); Desktop.getDesktop().browse(new
URI(redirectURL));
redirect_url = payment_source['_links']['redirect']['href'] puts("Complete 3DS Flow Here: #
{redirect_url}")
echo "Complete 3DS Flow here: " . $response->getRedirection() . PHP_EOL;
{
"id": "pay_y3oqhf46pyzuxjbcn2giaqnb44",
"status": "Pending",
"reference": "ORD-5023-4E89",
"customer": {
"id": "cus_y3oqhf46pyzuxjbcn2giaqnb44",
"email": "sarah.mitchell@checkout.com",
"name": "Sarah Mitchell"
},
"3ds": {
"downgraded": false,
"enrolled": "Y"
},
"_links": {
"self": {
"href": "https://api.checkout.com/payments/pay_y3oqhf46pyzuxjbcn2giaqnb44"
},
"redirect": {
"href": "https://api.checkout.com/3ds/pay_y3oqhf46pyzuxjbcn2giaqnb44"
}
}
}
redirect
key contains the URL to which the users are redirected to follow the 3DS challenge flow. Once the user successfully completes the challenge flow, the payment status changes from “Pending
” to Authorized
.
Step 3: Check for Payment Status
You can enable webhooks to listen to payment status from the Get Payment Detail API response.
/* Fetches the current instance of the Payment Source */
public GetPaymentResponse getPayment(){
GetPaymentResponse checkedPayment = null;
try {
checkedPayment = this.api.paymentsClient().getAsync(this.paymentSourceID).get();
System.out.println(String.format("Payment ID: %s Status: %s",checkedPayment.getId(), checkedPayment.getStatus()));
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return checkedPayment;
}
public boolean isPaymentApproved(String payment_id){
GetPaymentResponse getPaymentResponse = getPayment();
if (getPaymentResponse.getStatus().equals("Declined"))
{
paymentDeclined();
}
/* Check whether the payment is Authorized */
else return getPaymentResponse.isApproved();
return false;
}
# Get payment details for a payment id
def get_payment_intent_info(payment_intent_id)
# Reset the Excon::Connection before reusing it.
@api.checkout_connection.reset
response = @api.get_payment_details(payment_intent_id)
JSON.parse(response.data[:body])
end
# Check whether a payment can be used to create a subscription.
def payment_authorized?(payment_id)
check_payment = get_payment_intent_info(payment_id)
status = check_payment['status']
puts "Current Payment Status: #{status}"
case status
when 'Authorized', 'Card Verified'
puts 'Payment has been authorized'
return true
# when 'Declined'
# puts "Reason for Decline: #{check_payment['response_summary']}"
# abort 'Payment has been declined so exiting this program'
end
puts "3DS Flow hasn't been completed"
false
end
function checkIfPaymentIsAuthorized(): bool
{
$this->setCheckPayment($this->getCheckout()->payments()->details($this->paymentSourceID));
$currentPaymentStatus = $this->checkPayment->status;
echo "Current Payment Status: " . $currentPaymentStatus . PHP_EOL;
if (($currentPaymentStatus === "Authorized") || ($currentPaymentStatus === "Card Verified")) {
return true;
}
return false;
}
{
"id": "pay_jkpegpvbjviu5pu6xkewah5r6y",
"requested_on": "2019-01-28T11:12:54Z",
"source": {
"id": "src_v2fgh6u6ijmehgmu5efkbgw4d4",
"type": "card",
"expiry_month": 8,
"expiry_year": 2025,
"scheme": "Dinersclub",
"last4": "9019",
"fingerprint": "792A3B9CCCEE7E940E41CC40E92D08E1DE48BAD0F61BAFAF3809C93270241F83",
"bin": "301234",
"card_type": "Credit",
"card_category": "Commercial",
"issuer": "US Bank",
"issuer_country": "US",
"product_id": "L",
"product_type": "Commercial Credit",
"avs_check": "S",
"cvv_check": ""
},
"amount": 6500,
"currency": "USD",
"payment_type": "Regular",
"reference": "ORD-5023-4E89",
"status": "Authorized",
"approved": true,
"risk": {
"flagged": false
},
"customer": {
"id": "cus_ebxdsbvjgxcuxpkluxpwwychka"
},
"billing_descriptor": {
"name": "Fiona's Fashion",
"city": "New York"
},
"eci": "05",
"scheme_id": "638284745624527",
"_links": {
"self": {
"href": "https://api.sandbox.checkout.com/payments/pay_jkpegpvbjviu5pu6xkewah5r6y"
},
"actions": {
"href": "https://api.sandbox.checkout.com/payments/pay_jkpegpvbjviu5pu6xkewah5r6y/actions"
}
}
}
Check for the following key value:
"status": "Authorized"
orCard Verified
"approved": true
Creating a Subscription
Once the payment status
is Authorized
or Card Verified
pass the payment_id
to create a subscription using the create subscription API via payment_intent[gw_token]
:
Chargebee can create a subscription only when the payment state is
Authorized
orCard Verified
.
public static String createChargebeeSubscription(String paymentMethodID) {
Result result = null;
try {
result = Subscription.create()
.planId("<chargebee-plan-id>")
.autoCollection(AutoCollection.ON)
.customerFirstName("John")
.customerLastName("Doe")
.customerEmail("john@user.com")
.billingAddressFirstName("John")
.billingAddressLastName("Doe")
.billingAddressLine1("PO Box 9999")
.billingAddressCity("Walnut")
.billingAddressState("California")
.billingAddressZip("91789")
.billingAddressCountry("US")
.paymentIntentGatewayAccountId("<checkout.com-gateway-id>")
.paymentIntentGwToken(paymentMethodID)
.request();
} catch (Exception e) {
e.printStackTrace();
}
Subscription subscription = result.subscription();
System.out.println(String.format("Chargebee Subscription \'%s\' created for Checkout.com Payment ID \'%s\'",subscription.id(), paymentMethodID));
return subscription.id();
}
# Create a subscription in Chargebee
def create_subscription(payment_intent_id)
result = ChargeBee::Subscription.create({
plan_id: '<chargebee-plan-id>',
auto_collection: 'on',
payment_intent: {
gateway_account_id: '<checkout.com-gateway-id>',
gw_token: payment_intent_id
}
})
subscription = result.subscription
puts "Chargebee subscription ID: #{subscription.id} created for Checkout.com payment ID: #{payment_intent_id}"
end
function createChargebeeCustomer($paymentIntentID)
{
try {
$result = ChargeBee_Subscription::create(array(
"planId" => "<chargebee-plan-id>",
"autoCollection" => "on",
"billingAddress" => array(
"firstName" => "John",
"lastName" => "Doe",
"line1" => "PO Box 9999",
"city" => "Walnut",
"state" => "California",
"zip" => "91789",
"country" => "US"
),
"customer" => array(
"firstName" => "John",
"lastName" => "Doe",
"email" => "john@user.com"
),
"paymentIntent" => array(
"gatewayAccountId" => "<checkout.com-gateway-id>",
"gwToken" => $paymentIntentID
)
));
$subscription = $result->subscription();
} catch (ChargeBee_PaymentException $cbe) {
echo "Chargebee Payment Error: " . $cbe->getApiErrorCode() . PHP_EOL;
exit();
}
return $subscription->id;
}
- gatewayAccountId: can be found in Payment Gateway’s details page
- gwToken: the Session Id or payment Id returned after redirection
- referenceId: this will be checkout.com’s customer_id/source_id
Method
ChargeBee_Subscription::create(array(<param name> => <value>,<param name> => <value> ...))
Once you've successfully processed the card payment request, you can simply use the source Id (src_v2fgh6u6ijmehgmu5efkbgw4d4
) for subsequent payments.
Test cards
When you're all set, test your integration with some test transactions. Here are some credit card numbers that you can use to test the application:
For more test cards for testing different scenarios click here.
Reference Links
We're always happy to help you with any questions you might have!
support@chargebee.com