
How to Create 3D Secure Payments Using Recurly.js
Introduction
In this guide, we’ll walk through setting up 3D Secure payments using Recurly.js, covering both client-side and server-side configuration. We will do this using PHP.
Requirements
Before you begin, make sure you have the following:
- A Recurly account
- A Recurly Public API Key
- A Recurly Private API Key
- A Subscription Plan with a plan code
You can create a Recurly account here.
A Recurly account
A Recurly Public API Key
A Recurly Private API Key
A Subscription Plan with a plan code
Step 1: Install and Configure Recurly.js
Add Recurly.js to Your HTML
<script src="https://js.recurly.com/v4/recurly.js"></script>
Initialize Recurly
recurly.configure({ publicKey: 'your-public-key-here' });
Step 2: Setup the Payment Form with Hosted Fields
Recurly Hosted Fields securely collect card data while ensuring PCI compliance.
<form id="payment-form"> <!-- Billing Details --> <input type="hidden" data-recurly="first_name" value="Ben" name="first_name"> <input type="hidden" data-recurly="last_name" value="du Monde" name="last_name"> <input type="hidden" data-recurly="address1" value="1313 Main St." name="address1"> <input type="hidden" data-recurly="city" value="Hope" name="city"> <input type="hidden" data-recurly="country" value="US" name="country"> <input type="hidden" data-recurly="state" value="WA" name="state"> <input type="hidden" data-recurly="postal_code" value="98552" name="postal_code"> <!-- Recurly Card Element --> <div id="recurly-element-card"></div> <div id="payment-message" class="mt-2"></div> <div class="d-flex justify-content-center align-items-center"> <button type="submit" id="subscribe" class="btn mt-2 btn-success">Subscribe</button> </div> <input type="hidden" id="recurly_token" name="recurly_token"> </form>
Style Hosted Fields (Optional)
const elements = recurly.Elements(); const cardElement = elements.CardElement({ style: { fontFamily: 'Open Sans', fontSize: '1rem', fontWeight: 'bold', fontColor: '#2c0730' } });
Step 3: Tokenize the Card & Initiate 3D Secure
document.getElementById('payment-form').addEventListener('submit', function (event) { event.preventDefault(); recurly.token(document.querySelector('#payment-form'), function (err, token) { if (err) { console.error('Tokenization error:', err); } else { initiate3DS(token); } }); });
Step 4: Handle 3D Secure Challenge
We need the following files for the payment process:
- index.php
- payment.php (AJAX file)
- The Recurly PHP SDK
index.php
<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Recurly.js Example: 3-D Secure</title> <script src="https://js.recurly.com/v4/recurly.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" /> <link href="https://js.recurly.com/v4/recurly.css" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" /> <link href="/style.css" rel="stylesheet" /> <!-- Bootstrap --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js"></script> </head> <body> <div class="container p-5"> <div class="row"> <div class="col-sm-12 col-lg-6 offset-lg-3 col-md-6 offset-md-3"> <form id="payment-form"> <!-- Billing Details --> <input type="hidden" data-recurly="first_name" value="Ben" name="first_name"> <input type="hidden" data-recurly="last_name" value="du Monde" name="last_name"> <input type="hidden" data-recurly="address1" value="1313 Main St." name="address1"> <input type="hidden" data-recurly="city" value="Hope" name="city"> <input type="hidden" data-recurly="country" value="US" name="country"> <input type="hidden" data-recurly="state" value="WA" name="state"> <input type="hidden" data-recurly="postal_code" value="98552" name="postal_code"> <!-- Recurly Card Element --> <div id="recurly-element-card"></div> <div id="payment-message" class="mt-2"></div> <div class="d-flex justify-content-center align-items-center"> <button type="submit" id="subscribe" class="btn mt-2 btn-success">Subscribe</button> </div> <input type="hidden" id="recurly_token" name="recurly_token"> </form> </div> </div> </div> <!-- 3D Secure Modal --> <div class="modal fade" id="recurlyBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="recurlyBackdropLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-body"> <div id="my-auth-container" style="width: 450px;height: 450px;"></div> </div> </div> </div> </div> <script> recurly.configure({ publicKey: 'your-public-key' // Replace with your actual public key }); const elements = recurly.Elements(); const cardElement = elements.CardElement({ style: { fontFamily: 'Open Sans', fontSize: '1rem', fontWeight: 'bold', fontColor: '#2c0730' } }); cardElement.attach('#recurly-element-card'); $('#payment-form').on('submit', function(event) { event.preventDefault(); $('#subscribe').prop('disabled', true).text('Processing...'); $('#payment-message').html(''); const form = this; const formData = new FormData(form); recurly.token(elements, form, function(err, token) { if (err) { $('#payment-message').html('<div class="alert alert-danger">' + err.message + '</div>'); $('#subscribe').prop('disabled', false).text('Subscribe'); } else { formData.append('recurly_token', token.id); $.ajax({ url: 'payment.php', method: 'POST', data: formData, dataType: 'json', processData: false, contentType: false, success: function(response) { if (response.status === true) { $('#payment-message').html('<div class="alert alert-success">Payment successful!</div>'); $('#subscribe').prop('disabled', false).text('Subscribe'); } else if (response.status === false && response.threeDSToken) { $('#recurlyBackdrop').modal('show'); handle3DSecureAuthentication(response.threeDSToken); } else { $('#payment-message').html('<div class="alert alert-danger">Payment failed.</div>'); $('#subscribe').prop('disabled', false).text('Subscribe'); } }, error: function() { $('#payment-message').html('<div class="alert alert-danger">Server error during payment.</div>'); $('#subscribe').prop('disabled', false).text('Subscribe'); } }); } }); }); function handle3DSecureAuthentication(threeDSToken) { const risk = recurly.Risk(); const threeDSecure = risk.ThreeDSecure({ actionTokenId: threeDSToken }); threeDSecure.on('token', function(token) { $('#recurlyBackdrop').modal('hide'); // Send 3DS result token to backend $('#payment-message').html('<div class="alert alert-success">3D Secure authentication complete.</div>'); $('#subscribe').prop('disabled', false).text('Subscribe'); $('#payment-message').html(''); // You can add here success payment code }); threeDSecure.on('error', function(error) { $('#payment-message').html('<div class="alert alert-danger">3DS Error: ' + error.message + '</div>'); $('#subscribe').prop('disabled', false).text('Subscribe'); }); $('#my-auth-container').html(''); threeDSecure.attach(document.querySelector('#my-auth-container')); } </script> </body> </html>
Install the Recurly PHP SDK
composer require recurly/recurly-client
payment.php (AJAX file)
<?php // Autoload the Recurly SDK require 'vendor/autoload.php'; use Recurly\Client; use Recurly\RecurlyError; $apiKey = 'recurly-private-api-key'; // Replace with your actual Recurly API key $client = new Client($apiKey); // Check if the Recurly token is present if (!isset($_POST['recurly_token']) || empty($_POST['recurly_token'])) { die("Error: Missing Recurly token."); } // Retrieve the token and generate a dynamic account code $paymentToken = $_POST['recurly_token']; // Get the token from the form $accountCode = date('YmdHis') . '-test'; // Use the dynamic account code // Prepare the purchase creation array $purchase_create = [ "currency" => 'INR', "account" => [ "code" => $accountCode, "first_name" => $_POST['first_name'], "last_name" => $_POST['last_name'], "email" => 'example@gmail.com', // Replace with dynamic email "billing_info" => [ "token_id" => $paymentToken, // Pass the Recurly token ], ], "subscriptions" => [ [ "plan_code" => "110586", // Replace with your plan code "unit_amount" => 500, // Price in cents (500 INR) "quantity" => "1", // Number of items ] ] ]; try { // Create the purchase $invoice_collection = $client->createPurchase($purchase_create); // Get the transaction error $transaction_error = $invoice_collection->getChargeInvoice()->getTransactions()[0]->getTransactionError(); } catch (\Recurly\Errors\Validation $e) { // Handle validation errors $response = array( 'status' => false, 'message' => $e->getMessage(), ); echo json_encode($response); } catch (\Recurly\RecurlyError $e) { // Handle Recurly API errors (e.g., 3D Secure) $three_d_secure_action_token_id = $e->getApiError()->getTransactionError()->getThreeDSecureActionTokenId(); $response = array( 'status' => false, 'message' => $e->getMessage(), 'threeDSToken' => $three_d_secure_action_token_id, // Send 3DS token if needed ); echo json_encode($response); } ?>
Final Steps
When you follow the steps above, you will see a screen like the one shown.
Enter a test payment card number and use a future expiration date in the form.
Card Number | Behavior |
---|---|
4000000000003220 |
Challenge flow |
4000000000003063 |
Device fingerprint flow |
4222222222222220 |
Approved fraud review (frictionless flow) |
4000008400001629 |
3DS2 challenge for any recurring transaction (e.g., dunning) |
Once submitted, a 3D Secure popup will appear, allowing you to complete the authentication.
This completes the Recurly 3D Secure payment integration process.
Conclusion
You’ve now set up a fully functional 3D Secure payment flow using Recurly.js! This ensures your payment processing is secure, compliant, and optimized for user trust. By leveraging Recurly’s built-in tools, you’ve simplified what would otherwise be a very complex integration.
If you have any questions or need assistance, feel free to contact me.
I’ll be available to help you as soon as possible.
Thank you!