# Card

Securely save a customer's card for future charges — without handling raw card details yourself.

If you're building a product that charges customers more than once — loan repayments, subscription renewals, marketplace payouts — you need a way to save their card on file. But storing card numbers directly is a security and compliance nightmare. That's where tokenization comes in.

The Card API lets you create a secure checkout session where your customer enters their card details on a hosted page managed by Payment Providers. Once they complete the flow, Tendar stores a reusable token that represents their card. From that point on, you can charge the customer using the token — no card numbers, no PCI scope, no redirects.

Think of it this way: the customer enters their card once, and you can charge it as many times as you need.

### Before you begin

***

Make sure you have:

* A **user** already created through the [Onboarding Service](https://docs.tendar.co/documentation/onboarding). Each beneficiary is linked to a user by their `user_id` (e.g. `usr-cheltgfnqt`).
* Your **secret key** from the [Tendar dashboard](https://app.tendar.co/services?service-level=api-keys-and-webhooks). All requests require a Bearer token:

  ```
  Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx
  ```

### The card object

***

Here's a breakdown of every field on the card object and what it means:

| Field                | Type    | Description                                                                              |
| -------------------- | ------- | ---------------------------------------------------------------------------------------- |
| `id`                 | string  | The unique identifier for this card record.                                              |
| `user`               | string  | The internal user ID (MongoDB ObjectId).                                                 |
| `user_id`            | string  | The Tendar user ID (e.g., `usr-cheltgfnqt`).                                             |
| `default`            | boolean | Whether this is the customer's default card.                                             |
| `payment_service`    | string  | The payment provider used to tokenize the card (`paystack`, `flutterwave`, or `stripe`). |
| `valid`              | boolean | Whether the card has been verified. Only valid cards can be charged.                     |
| `currency`           | string  | The currency associated with the card.                                                   |
| `card.last_4_digits` | string  | The last 4 digits of the card number.                                                    |
| `card.bin`           | string  | The bank identification number (first 6 digits).                                         |
| `card.card_type`     | string  | The card brand — `visa`, `mastercard`, `verve`, etc.                                     |
| `card.bank`          | string  | The issuing bank name.                                                                   |
| `card.country_code`  | string  | The 2-letter country code (e.g., `NG`, `US`).                                            |
| `card.exp_month`     | string  | The card's expiration month.                                                             |
| `card.exp_year`      | string  | The card's expiration year.                                                              |
| `card.email`         | string  | The email address associated with the card (pulled from the user record).                |
| `signature`          | string  | A unique identifier for the physical card, used for deduplication.                       |
| `service_ref`        | string  | The payment provider's reference for the tokenization session.                           |

### How card tokenization works

***

The tokenization flow has three stages: **create**, **authorize**, and **verify**. Here's what happens at each stage, and what your application needs to do.

#### Step 1: Create a card

To start tokenizing a card, send a `POST` request to the create endpoint with the customer's `user_id`, the `currency`, and a `checkout` object containing redirect URLs.

```
POST /api/v1/card/create
```

```json
{
    "user_id": "usr-cheltgfnqt",
    "currency": "NGN",
    "checkout": {
        "success_url": "https://yourapp.com/card/success",
        "cancel_url": "https://yourapp.com/card/cancel"
    },
    "payment_service": ""
}
```

| Field                  | Type   | Required | Description                                                                                                        |
| ---------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `user_id`              | string | Yes      | The Tendar user ID of the customer whose card you want to tokenize.                                                |
| `currency`             | string | Yes      | The currency for the card. Currently supports `NGN` and `USD`.                                                     |
| `checkout.success_url` | string | Yes      | Where to redirect the customer after they successfully enter their card details.                                   |
| `checkout.cancel_url`  | string | No       | Where to redirect the customer if they cancel the checkout flow.                                                   |
| `payment_service`      | string | No       | Which payment provider to use (`paystack`, `flutterwave`, or `stripe`). Leave empty to use your company's default. |

When the request succeeds, Tendar does the following behind the scenes:

1. Validates that the user exists under your company.
2. Initializes a checkout session with a payment provider — this typically involves a small charge (e.g., ₦50) to verify the card is real.
3. Returns a card object with a `checkout.url` — this is the hosted page where your customer enters their card details.

Here's what the response looks like:

```json
{
    "data": {
        "created_at": "2024-07-03T20:15:58.574Z",
        "updated_at": "2024-07-03T20:15:58.574Z",
        "id": "6685b17e80d19519f2b34217",
        "user": "660f7665740ffdf6498d05f5",
        "user_id": "usr-cheltgfnqt",
        "default": false,
        "payment_service": "paystack",
        "valid": false,
        "currency": "NGN",
        "card": {
            "last_4_digits": "",
            "card_holder_name": "",
            "bin": "",
            "card_type": "",
            "bank": "",
            "country_code": "",
            "exp_month": "",
            "exp_year": "",
            "email": "john@example.com"
        },
        "signature": "",
        "service_ref": "ttpxasbpam",
        "checkout": {
            "success_url": "https://yourapp.com/card/success",
            "cancel_url": "https://yourapp.com/card/cancel",
            "url": "https://checkout.paystack.com/91p42szfrrujgn1"
        }
    },
    "error": false,
    "message": "Card created successfully"
}
```

Notice that `valid` is `false` and the `card` details are empty. The card hasn't been authorized yet — the customer still needs to complete the checkout.

**What to do next:** Redirect your customer to the `checkout.url`. This takes them to a secure, provider-hosted page where they enter their card number, expiry date, and CVV.

#### Step 2: Customer authorizes the card

This step happens entirely on the payment provider's hosted page — your application is not involved. The customer enters their card details, and the provider processes a small verification charge to confirm the card is valid and belongs to the customer.

Once the customer completes the flow, two things happen:

1. The customer is redirected to your `success_url` (or `cancel_url` if they abandoned the checkout).
2. The payment provider sends a webhook to Tendar notifying it that the card authorization is complete.

You don't need to call any API during this step. Just make sure your `success_url` shows the customer an appropriate message (e.g., "Card saved successfully" or "Processing your card...").

#### Step 3: Tendar verifies and saves the card

When Tendar receives the webhook from the payment provider, it automatically:

1. **Verifies the charge** — confirms that the verification payment succeeded.
2. **Extracts the card details** — pulls the last 4 digits, card type (Visa, Mastercard, etc.), issuing bank, expiry date, and country code from the provider's response.
3. **Generates a token** — stores the reusable token that allows future charges on this card without the customer re-entering details.
4. **Checks for duplicates** — if the customer already has a card with the same signature (i.e., the same physical card), Tendar updates the existing token instead of creating a duplicate. The new card record is marked as invalid with the message "card already exists."
5. **Sets the default** — if this is the customer's first valid card, it's automatically set as the default card.
6. **Refunds the verification charge** — the small amount charged during checkout is automatically refunded to the customer. This happens asynchronously — you don't need to trigger it.
7. **Sends a webhook** — fires a `card.validation.success` or `card.validation.failed` event to your company's registered webhook URL.

After verification, the card object looks like this:

```json
{
    "id": "66859a3ca53c1ca520a7b26d",
    "user": "660f7665740ffdf6498d05f5",
    "user_id": "usr-cheltgfnqt",
    "default": true,
    "payment_service": "paystack",
    "valid": true,
    "currency": "NGN",
    "card": {
        "last_4_digits": "4081",
        "bin": "408408",
        "card_type": "visa",
        "bank": "TEST BANK",
        "country_code": "NG",
        "exp_month": "12",
        "exp_year": "2030",
        "email": "john@example.com"
    },
    "signature": "SIG_gjjA8XmsnuQUmVKgnopy",
    "service_ref": "qkdc9pu7so"
}
```

The card is now `valid: true` and ready to be charged.

#### What if the card isn't verified in time?

If a customer starts the tokenization flow but never completes the checkout, the card sits in an invalid state. Tendar automatically cleans up these orphaned cards — any card that remains unverified after 24 hours is deleted. You don't need to handle this yourself.

### Managing cards

***

Once cards are tokenized, you have full control over listing, fetching, setting defaults, and deleting them.

#### List a customer's cards

Retrieve all cards belonging to a specific user. The response excludes sensitive fields like the token, checkout details, and payment information.

```
GET /api/v1/card/list/:user_id
```

You can filter results by passing query parameters:

| Parameter         | Type    | Description                                                               |
| ----------------- | ------- | ------------------------------------------------------------------------- |
| `valid`           | boolean | Filter by validation status. Use `valid=true` to get only verified cards. |
| `sort-created_at` | string  | Sort by creation date. Use `asc` or `desc`.                               |

Example response:

```json
{
    "data": [
        {
            "created_at": "2024-07-03T18:36:44.647Z",
            "updated_at": "2024-07-03T18:36:58.538Z",
            "id": "66859a3ca53c1ca520a7b26d",
            "user": "660f7665740ffdf6498d05f5",
            "user_id": "usr-cheltgfnqt",
            "default": true,
            "payment_service": "paystack",
            "valid": true,
            "currency": "NGN",
            "card": {
                "last_4_digits": "4081",
                "bin": "408408",
                "card_type": "visa",
                "bank": "TEST BANK",
                "country_code": "NG",
                "exp_month": "12",
                "exp_year": "2030",
                "email": "john@example.com"
            },
            "signature": "SIG_gjjA8XmsnuQUmVKgnopy",
            "service_ref": "qkdc9pu7so"
        }
    ],
    "error": false,
    "message": "Cards retrieved successfully"
}
```

#### Fetch a single card

Retrieve a specific card by its ID.

```
GET /api/v1/card/fetch/:id
```

#### Set a default card

When a customer has multiple cards, you can designate one as the default. The default card is the one that gets charged when you don't specify a card ID in a charge or recollection request.

```
PUT /api/v1/card/default/set/:id
```

Only verified cards can be set as default. If the card hasn't been validated yet, the request will fail with a `400` error.

When you set a new default, the previous default card is automatically unset — there's always exactly one default card per customer.

#### Delete a card

Remove a card from a customer's account. This deletes the card record from Tendar and deauthorizes the token with the payment provider.

```
DELETE /api/v1/card/delete/:id
```

By default, you cannot delete a customer's default card — this prevents accidentally removing the card your automated charges depend on. If you really need to delete the default card, pass the `delete_default=true` query parameter:

```
DELETE /api/v1/card/delete/:id?delete_default=true
```

When a card is deleted, Tendar also sends a deauthorization request to the payment provider, revoking the token so it can no longer be used.

### Charging a tokenized card

***

Once a card is tokenized and verified, you can charge it in two ways:

#### Through a recollection

The most common use case. When recording a loan repayment, set `payment_channel` to `"card"` and pass the card ID. Tendar charges the card and applies the repayment to the loan in a single step.

```json
POST /recollection/create

{
    "loan": "6685d6600defdf0d7eea6699",
    "amount": 3500,
    "with_provider": true,
    "payment_channel": "card",
    "card": "66859a3ca53c1ca520a7b26d",
    "callback_url": "https://yourapp.com/webhooks/tendar"
}
```

If you omit the `card` field, Tendar charges the customer's default card. See [Recollection](https://docs.tendar.co/documentation/recollection/recollection) guide for the full recollection flow.

#### Through a collect plan

If you're using the [Collect](https://docs.tendar.co/documentation/recollection/broken-reference) feature for installment-based collections, Tendar automatically charges the customer's default card on the scheduled debit dates. You don't need to initiate individual charges — the system handles it.

### Webhooks

***

The Card API sends webhook events at key moments in the card lifecycle. These are delivered to your company's registered webhook URL.

| Event                     | When it fires                                                     |
| ------------------------- | ----------------------------------------------------------------- |
| `card.validation.success` | The card has been verified, tokenized, and is ready for charges.  |
| `card.validation.failed`  | The verification charge failed — the card could not be tokenized. |

The webhook payload contains the full card object (excluding the token and checkout details), so you can update your UI or trigger downstream workflows without making an additional API call.

### Error handling

***

Here are the errors you're most likely to encounter when working with the Card API, and how to resolve them:

| Error                                       | HTTP Status | Cause                                                                                  | Resolution                                                                                           |
| ------------------------------------------- | ----------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `user not found`                            | 404         | The `user_id` doesn't match any user in your company.                                  | Verify the user ID and ensure the user has been created under your company.                          |
| `card not found`                            | 404         | The card ID doesn't exist or doesn't belong to your company.                           | Verify the card ID.                                                                                  |
| `card already verified`                     | 400         | You attempted to verify a card that has already been validated.                        | No action needed — the card is already usable.                                                       |
| `payment still pending`                     | 400         | The verification charge hasn't settled yet.                                            | Wait for the webhook or retry after a short delay.                                                   |
| `card already exists`                       | —           | The customer already has a verified card with the same signature (same physical card). | The existing card's token is updated. The duplicate card record is marked invalid. No action needed. |
| `only validated card can be set as default` | 400         | You tried to set an unverified card as default.                                        | Wait for the card to be verified first, then set it as default.                                      |
| `default card cannot be deleted`            | 400         | You tried to delete the customer's default card without explicitly allowing it.        | Pass `delete_default=true` as a query parameter, or set a different card as default first.           |

## Security and compliance

The Card API is designed so that you never handle raw card details:

* **Card numbers, CVVs, and full expiry dates never pass through your server.** The customer enters them on a hosted checkout page managed by the payment provider.
* **Tokens are stored encrypted** and scoped to your company. A token from one company cannot be used by another.
* **Verification charges are automatically refunded**, so tokenization doesn't cost your customer anything.
* **Orphaned cards are auto-cleaned.** If a customer starts the flow but doesn't complete it, the invalid card record is automatically deleted after 24 hours.

## Putting it all together

Here's a typical integration flow for saving and charging a customer's card:

1. **Create a card** — Call `POST /card/create` with the customer's `user_id`, `currency`, and `checkout` URLs. You'll get back a `checkout.url`.
2. **Redirect the customer** — Send the customer to the `checkout.url`. They enter their card details on the provider's hosted page.
3. **Wait for the webhook** — Tendar receives the provider's webhook, verifies the card, saves the token, and refunds the verification charge. You receive a `card.validation.success` event.
4. **Display the saved card** — Call `GET /card/list/:user_id?valid=true` to show the customer their saved cards. Display the last 4 digits, card type, and bank — never the full card number.
5. **Charge when needed** — Use the card ID in a [recollection](https://docs.tendar.co/documentation/recollection/recollection) (`payment_channel: "card"`) or let a [collect](https://docs.tendar.co/documentation/recollection/broken-reference) plan charge the default card automatically.
6. **Let the customer manage their cards** — Provide UI to set a different default (`PUT /card/default/set/:id`) or remove a card (`DELETE /card/delete/:id`).

## Next steps

* [Recollect Loan](https://docs.tendar.co/documentation/recollection/broken-reference) — Charge a saved card to record a loan repayment.
* [Direct Debit](https://docs.tendar.co/documentation/recollection/broken-reference) — Set up bank account authorizations as an alternative to cards.
* [Accept Payments](https://docs.tendar.co/documentation/recollection/broken-reference) — Initialize one-time payment sessions.
* [Collect](https://docs.tendar.co/documentation/recollection/broken-reference) — Automate installment-based collections with scheduled card charges.
