DEV Community

Cover image for Implementing Card on File for Your App: A Developer’s Guide

Implementing Card on File for Your App: A Developer’s Guide

Think about the last time you subscribed to a cloud platform, ordered food, or booked a ride. Chances are, you didn’t pull out your wallet to pay with cash or enter your card details each time. Your card was probably already saved in the app, making the experience fast, smooth, and effortless. That’s the power of Card on File in streamlining payment processing.

Card on File is a game-changer for developers and product teams building consumer-facing products and services. It transforms the user experience by reducing checkout time, keeping customers engaged, and securely managing customer payment information for recurring payments with credit and debit cards.

In this guide, you’ll learn what Card on File really means, how it works, and how to implement it in your application using Flutterwave.

What Are Card on File Transactions?

Card on File transactions are payments where a user allows a merchant or service provider to securely store their card details for future use. Instead of entering the customer’s card details every single time, the user gives permission once, and future transactions can happen with just a click or even automatically.

In Card on File transactions, you don’t store the actual card details in your database (that could lead to serious compliance issues). The stored payment information is converted into a secure token that can be used for future payments.

Here are a few everyday use cases where Card on File shines:

  • Ride-sharing and delivery apps like Uber and GIG Logistics save payment information so customers can request rides or order food with a single tap.
  • Subscription services such as Netflix charge users monthly without asking them to re-enter their card details. Customers sign up once, and billing happens automatically in the background.
  • E-commerce platforms like Jumia and Konga offer a “Save my card for future purchases” option that makes checkout faster and smoother.
  • Utility and bill payment apps automatically charge users for recurring services like electricity, internet, or phone bills without requiring manual payment each billing cycle. ## How Does Card on File Work?

At its core, Card on File payments operate through a combination of user authorization, tokenization, and secure storage. These processes can be grouped into two main transaction types:

Customer-Initiated Transactions (CIT)

In a Customer-Initiated Transaction, the payment is triggered manually by the user. This could happen during the initial setup when a user adds their card details for the first time, or later when they choose to make a payment for a service using a saved card.

The workflow involves:

How CIT works

  1. User Authorization: The process starts when a customer visits your checkout page and chooses to save their card payment details for future use. They enter their card details and may need to complete a 3D Secure authentication, depending on their bank’s requirements.
  2. Tokenization: Instead of storing the raw card number, the payment provider generates a token that represents the card. This token is a random string that has no value outside your integration with the payment provider.
  3. Token Storage: You store the token in your database alongside the user’s profile. You can also store non-sensitive metadata like the card’s last four digits, expiry date, and brand (Visa, Mastercard, etc.) to help users identify their saved credit and debit cards.
  4. Future Transactions: When the user makes another purchase with a saved card, you send the token to the payment provider instead of collecting card details again. For high-value transactions, the user might still need to authenticate, but the overall friction is greatly reduced.

Merchant-Initiated Transactions (MIT)

In a Merchant-Initiated Transaction, you charge the customer without them actively triggering that specific payment. This model is common for recurring billing, subscriptions, and installment payments.

The workflow involves:

How MIT works

  1. Initial Authorization: The process begins when a customer explicitly consents to future charges by saving their card during a transaction. This consent is both legally and technically required because you cannot charge a card without prior approval.
  2. Tokenization and Storage: The process is identical to CIT. You receive and store a token representing the card.
  3. Scheduled Charges: When a payment is due (for example, the first of every month for a subscription), your backend automatically sends a charge request to the payment gateway using the stored token that represents the customer’s credit card details. No user interaction is required.
  4. Reduced Authentication: After the initial setup, MIT transactions bypass 3D Secure authentication because the user has already authorized recurring charges. This makes the process smooth but requires strict compliance with card network and payment regulations.

CIT vs. MIT: The Key Difference

In a CIT, the user starts the payment. For example, tapping “Buy Now” in an app using a saved card. In a MIT, the business starts the payment. For example, when a subscription renews automatically.

Both rely on the initial debit or credit Card on File setup but differ in who triggers the transaction. CIT keeps the user in control for each payment, while MIT allows you to handle billing automatically after getting the user’s initial consent.

How to Implement Card on File with Flutterwave

Before you start implementing Card on File, there are a few prerequisites to put in place:

  • Flutterwave Account with Card on File Access: Card on File is not available by default on all Flutterwave accounts. You need to request access because of the security requirements and sensitivity of the feature. Once approved, you will receive an agreement_id you can use to process Card on File transactions.
  • API Credentials: You need your Flutterwave secret key and encryption key for backend operations. Keep your keys secure and never expose them in client-side code.
  • Secure Backend Setup: Since you will store tokens alongside user profiles and manage sensitive payment flows, your backend must be secured with proper authentication and database protections.
  • Clear Customer Consent Flows: You cannot store a card without permission. Provide a clear interface where users explicitly agree to save their card details, for example, a checkbox at checkout with plain language explaining how their card will be stored and used.
  • Webhook Endpoint: Set up an endpoint to receive notifications from Flutterwave about transaction statuses, failed charges, or card updates.

To implement Card on File, follow the steps below.

Note: This implementation uses the Flutterwave v3 API.

Step 1: Collect and Tokenize the Card

When a user chooses to save their card, you’ll start a standard card payment but mark it for tokenization. On the frontend, display a checkout form where users can enter their card details and opt in to save the card.

On the backend, encrypt the payload using the 3DES algorithm before sending it to the charge endpoint:

const payload = {
    card_number: '340614670471041',
    cvv: '564',
    expiry_month: '10',
    expiry_year: '31',
    currency: 'NGN',
    amount: '2000',
    email: 'user@example.com',
    fullname: 'John Doe',
    phone_number: '0167578999',
    card_holder_name: 'John Doe',
    tx_ref: 'SAMPLE_UNIQUE_REFERENCE',
    redirect_url: 'https://webhook.site/88ba45d2-ae79-4a5b-ae07-48303a32a92e',
    is_unscheduled: true,
    agreement_id: 'YOUR_AGREEMENT_ID',
};

const encryptionKey = process.env.ENCRYPTION_KEY;

function encrypt(encryptionKey, payload) {
    const text = JSON.stringify(payload);
    const forge = require('node-forge');
    const cipher = forge.cipher.createCipher(
        '3DES-ECB',
        forge.util.createBuffer(encryptionKey)
    );
    cipher.start({ iv: '' });
    cipher.update(forge.util.createBuffer(text, 'utf-8'));
    cipher.finish();
    const encrypted = cipher.output;
    return forge.util.encode64(encrypted.getBytes());
}
Enter fullscreen mode Exit fullscreen mode

Two parameters to note in the payload are is_unscheduled and agreement_id. Including these tells Flutterwave to process the charge as a Card on File transaction.

Next, wrap the encrypted payload in a client key and send it to the charge card endpoint.

curl --request POST \
  --url "https://api.flutterwave.com/v3/charges?type=card" \
  --header "Authorization: Bearer FLW_SECRET_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "client": "Of8p6iJUVUezgvjUkjjJsP8aPd6CjHR3f9ptHiH5Q0+2h/FzHA/X1zPlDmRmH5v+GoLWWB4TqEojrKhZI38MSjbGm3DC8UPf385zBYEHZdgvQDsacDYZtFEruJqEWXmbvw9sUz+YwUHegTSogQdnXp7OGdUxPngiv6592YoL0YXa4eHcH1fRGjAimdqucGJPurFVu4sE5gJIEmBCXdESVqNPG72PwdRPfAINT9x1bXemI1M3bBdydtWvAx58ZE4fcOtWkD/IDi+o8K7qpmzgUR8YUbgZ71yi0pg5UmrT4YpcY2eq5i46Gg3L+fxFl4tauG9H4WBChF0agXtP4kjfhfYVD48N9Hrt"
  }'
Enter fullscreen mode Exit fullscreen mode

When the charge is successful, Card on File triggers a 3D Secure process. This step redirects the customer to their bank’s authentication page for verification. It adds an extra layer of protection to confirm the customer’s identity and reduce fraud.

After this step, Flutterwave responds with:

  • A card object containing the tokenized card
  • A customer object with user details
  • A meta object that includes a trace_id used for future charges
{
  card: {
      first_6digits: '543889',
      last_4digits: '0229',
      issuer: 'MASTERCARD GT BANK',
      country: 'NG',
      type: 'MASTERCARD',
      token: 'flw-t1nf-1dd5c21361bb85c64deb7ff57ec891b2-m03k',
      expiry: '10/31',
  },
  meta: {
      agreement_id: 'Agreement00w02W1',
      recurring_amount_variability: 'VARIABLE',
      trace_id: '123456789',
      agreement_type: 'UNSCHEDULED',
  },
  customer: {
      id: 370672,
      phone_number: '2349012345678',
      name: 'John Doe',
      email: 'john.doe@example.com',
      created_at: '2020-04-30T20:09:56.000Z',
  },
}
Enter fullscreen mode Exit fullscreen mode

You should store these details securely in your backend and link them to the user’s profile. They can also be used to display card information such as type, last four digits, and expiry date within your app.

Step 2: Charge the Saved Card (Subsequent Charge)

When it's time to charge the saved card, whether the user initiates it or you're processing a scheduled payment, use the stored token and the trace_id alongside other required details to make the request.

curl --request POST \
  --url "https://api.flutterwave.com/v3/tokenized-charges" \
  --header "Content-Type: application/json" \
  --header "Authorization: Bearer FLW_SECRET_KEY" \
  --data '{
    "token": "flw-t1nf-1dd5c21361bb85c64deb7ff57ec891b2-m03k",
    "currency": "NGN",
    "country": "NG",
    "amount": 2000,
    "email": "john.doe@example.com",
    "full_name": "Flutterwave Developers",
    "tx_ref": "UNIQUE_PAYMENT_REFERENCE",
    "redirect_url": "https://example_company.com/success",
    "trace_id": "123456789",
    "is_unscheduled": true
  }'
Enter fullscreen mode Exit fullscreen mode

Note that for subscription billing, you would typically set up a cron job or scheduled task that runs at specific intervals to trigger these charges automatically.

Step 3: Manage the Card Lifecycle

Cards don’t last forever. They expire, get replaced, or users may want to remove them. Your implementation needs to handle these situations gracefully. Below are some scenarios to plan for:

  • Handling card updates: When a user’s card is updated (for example, a new expiry date or CVV), allow them to re-authenticate and generate a new token by following the same steps outlined earlier. You can keep the old token reference but mark it as superseded.
  • Handling card expiry: Before attempting to charge a card, check the expiry date stored in your database to confirm that it’s still valid.
  • Handling user revocation: Users should be able to remove their saved cards at any time. When they do, mark the token as revoked, but don’t delete the record immediately since you might need it for audit purposes.
  • Handling failed charges: When a Merchant-Initiated Transaction fails, have a clear retry and communication strategy. Notify users about the failed attempt and consider a backoff or scheduled retry mechanism. You can learn more about this in the guide on building a fault-tolerant microservice.

Step 4: Set Up Webhooks for Transaction Updates

Webhooks let Flutterwave notify your system about transaction events in real time. This is especially important for Merchant-Initiated Transactions since your system might not be actively polling for results.

Here’s an example implementation:

app.post('/webhooks/flutterwave', async (req, res) => {
  // Verify the webhook signature
  const signature = req.headers['verif-hash'];
  if (signature !== process.env.FLUTTERWAVE_WEBHOOK_SECRET) {
    return res.status(401).send('Invalid signature');
  }

  const event = req.body;

  if (event.event === 'charge.completed') {
    const { status, tx_ref, customer } = event.data;

    // Update your records based on the transaction status
    if (status === 'successful') {
      await handleSuccessfulPayment(tx_ref, event.data);
    } else {
      await handleFailedPayment(tx_ref, event.data);
    }
  }

  res.status(200).send('Webhook received');
});
Enter fullscreen mode Exit fullscreen mode

Once an event occurs on your account, Flutterwave will automatically send a webhook notification to your server so you can handle it accordingly.

Check out the webhook guide to learn more about building a robust and secure webhook service.

Wrap Up

Implementing Card on File is not just about reducing checkout friction. It’s about creating a modern, smooth payment experience that online retailers and customers expect. From recurring subscriptions to one-click checkouts, Card on File makes your app more user-friendly and can significantly improve conversion rates.

With Flutterwave, developers have a secure and reliable way to integrate Card on File without having to rebuild complex payment flows. The tokenization process protects sensitive data, while your backend handles customer relationships and card lifecycles.

If you’re ready to enhance your app’s payment experience, reach out to the Flutterwave team to activate Card on File and start building faster, more seamless payments today.

Top comments (0)