> ## Documentation Index
> Fetch the complete documentation index at: https://flutterwaveinc.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Direct Card Charge

> Learn how to collect payments with Cards

Direct card charge allows you to charge both local cards (issued in your country of operation) and international cards. This is useful if your customers are predominantly credit/debit card users and you'd prefer they manage payments via your app.

<Info>
  Your country of operation is the country you selected when you created your
  Flutterwave account. If you accept payments from cards issued in other
  countries, or you charge in currencies apart from your local currency, the
  payment will be considered **international**.
</Info>

## Prerequisite

<Warning>
  **Compliance**

  Using direct card charges involves handling some
  very sensitive customer data, so a PCI DSS compliance certificate is
  required. When you've got one, contact your Relationship Manager or reach
  out to [hi@flutterwavego.com](mailto:hi@flutterwavego.com) to enable this feature on your account.
</Warning>

## Payment Flow

Using a card charge involves four main steps:

1. **Initiating the payment**: You send the transaction details and the customer's payment details to the [charge card endpoint](/api-reference/charges/card).
2. **Authorize the charge**: We tell you the details needed to authorize the charge, you get those from the customer, and you send them to the same [charge card endpoint](/api-reference/charges/card).
3. **Validate the charge**: Think of this as a second layer of authentication. The customer provides some info to validate the charge (such as an OTP), and you send that to us (via the validate charge [endpoint](/api-reference/charges/validate-charge)). This completes the payment.
4. **Verify the payment**: As a failsafe, you'll call our API to verify that the payment was successful before giving value (via the [verify transaction endpoint]().

<img src="https://mintcdn.com/flutterwaveinc/8LJ4mpYLmd_SNb9Z/images/card_charge.png?fit=max&auto=format&n=8LJ4mpYLmd_SNb9Z&q=85&s=c37f43e4c512652c3d192c59504d7baf" alt="card-charge" width="5760" height="10284" data-path="images/card_charge.png" />

In some scenarios, steps 2 and/or 3 might be skipped. For example, cards which use 3DS authentication won't have a validation stage. We'll explain the different scenarios in this guide.

### Recurring Payments

You can also collect recurring payments from customers with direct card charges. See our guide to [card tokenization](/other-features/recurring-payments/tokenization).

### Card Authorization Models

When you make a card charge request, Flutterwave will evaluate the card to determine its authorization model—a card personal identification number (PIN), an address verification system (AVS), a 3DS redirect, or no authorization at all.

If no authorization is required, the charge is completed, and you can skip to [verifying the payment](/payment-api/card#validate-the-charge).

For cards that require authorization, we'll send you a prompt telling you what parameters are needed for that authorization model so the cardholder can authenticate their transaction.

## Collect the Customer's Payment Details

First, you'll need the customer's card details. You can collect these with a simple HTML form like the one below:

<iframe height="300" width="100%" scrolling="no" title="Untitled" src="https://codepen.io/Phenzic/embed/xxojmrL?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
  See the Pen <a href="https://codepen.io/Phenzic/pen/xxojmrL">Untitled</a> by
  Mayowa Julius (<a href="https://codepen.io/Phenzic">@Phenzic</a>) on{' '}
  <a href="https://codepen.io">CodePen</a>.
</iframe>

After collecting the card details, you'll need to add some other details to make up the full payload, including:

* `tx_ref`: A unique reference code that you'll generate for each transaction.
* `amount`: The amount to be charged for the transaction.
  ` `currency\`: The currency to be used for the charge.
* `email`: The customer's email address.
* `redirect_url` (optional): The url we should redirect to after the customer completes payment. For 3DSecure payments, we'll append `data` (the transaction response data) to the URL as query parameters.
* `meta` (optional): Any extra details you wish to attach to the transaction. For instance, you could include the `flight_id` or `product_url`.

See the [charge card endpoint](/api-reference/charges/card) for a full list of available options.

```json Request payload theme={null}
{
	"card_number": "5531886652142950",
	"expiry_month": "09",
	"expiry_year": "32",
	"cvv": "564",
	"currency": "NGN",
	"amount": "7500",
	"email": "developers@flutterwavego.com",
	"fullname": "Flutterwave Developers",
	"phone_number": "+2349012345678",
	"tx_ref": "UNIQUE_TRANSACTION_REFERENCE",
	"redirect_url": "https://example_company.com/success"
}
```

### Preauthorization

Preauthorizing a card is a way to place a "hold" on the amount you plan to charge a customer. This is helpful if you intend to bill the customer after they use your service, but you want to ensure they have enough funds to pay before providing them the service.

For instance, if you run a ride-hailing business or hotel, you'd typically charge the customer after their trip or stay. However, you can estimate how much the trip or stay will cost and preauthorize the charge. This will "lock" the requested amount for a maximum of 7 working days. That way, you can be sure the customer will be able to pay their bill after rendering the service.

<Info>
  **Approval Required**

  Using preauthorization requires approval. Contact us at [compliance@flutterwavego.com](mailto:compliance@flutterwavego.com) so we can enable it on your account.
</Info>

To preauthorize a card, pass a `preauthorize` parameter in your [card charge payload](/api-reference/charges/card).

```json theme={null}
{
* "preauthorize": true,
* "usesecureauth": true,// set to true if you want to charge the card with 3DS, set to false to charge with noauth
  "card_number": "-bank-branches",
  "expiry_month": "09",
  "expiry_year": "31",
  "cvv": "789",
  "currency": "NGN",
  "amount": "7500",
  "email": "developers@flutterwavego.com",
  "fullname": "Flutterwave Developers",
  "phone_number": "+2349012345678",
  "tx_ref": "UNIQUE_TRANSACTION_REFERENCE",
  "redirect_url": "https://example_company.com/success"
}
```

This will place a hold on the amount but won't actually charge the card. When the service has been rendered, and you're ready to charge the customer, you can use the [capture pre-auth charge](/api-reference/preauthorization/capture-a-charge) endpoint. You can also pass in the `amount` you want to charge. Note that the captured amount must be less than or equal to the preauthorized amount.

```javascript theme={null}
const axios = require('axios');

await axios.post(
	`https://api.flutterwave.com/v3/charges/${orderRef}/capture`,
	{ amount: 1200 },
	{
		headers: {
			Authorization: `Bearer ${process.env.FLW_SECRET_KEY}`,
			'Content-Type': 'application/json',
		},
	}
);
```

You can also release the hold on the funds (if the customer does not eventually use the service) or refund part of the held amount. Any holds that you don't capture, void, or refund will automatically be voided after 7 working days.

## Encrypt the Payload Request

<Info>
  If you're using one of our [backend
  SDKs](/sdk-plugins/backend-libraries/nodejs), you should skip this step, as
  they'll automatically encrypt the payload for you when sending the request.
  Head on to the [next step](/payment-api/card#initiate-payment).
</Info>

Next, you'll encrypt the payload you've built up. You'll need your encryption key (from the Settings > API section of your [Dashboard](https://app.flutterwave.com/dashboard/home/)), and you'll use the [3DES algorithm](https://en.wikipedia.org/wiki/Triple_DES) for encryption. You can see examples of this in our [encryption guide](/encryption).

Now, you'll wrap the encrypted payload inside a JSON body like this:

```json theme={null}
{
	"client": "Of8p6iJUVUezgvjUkjjJsP8aPd6CjHR3f9ptHiH5Q0+2h/FzHA/X1zPlDmRmH5v+GoLWWB4TqEojrKhZI38MSjbGm3DC8UPf385zBYEHZdgvQDsacDYZtFEruJqEWXmbvw9sUz+YwUHegTSogQdnXp7OGdUxPngiv6592YoL0YXa4eHcH1fRGjAimdqucGJPurFVu4sE5gJIEmBCXdESVqNPG72PwdRPfAINT9x1bXemI1M3bBdydtWvAx58ZE4fcOtWkD/IDi+o8K7qpmzgUR8YUbgZ71yi0pg5UmrT4YpcY2eq5i46Gg3L+fxFl4tauG9H4WBChF0agXtP4kjfhfYVD48N9Hrt"
}
```

You can then use this payload to initiate payment.

## Initiate Payment

To initiate payment, send your encrypted payload to our charge card [endpoint](/api-reference/charges/card).

If you're using one of our SDKs, you'll pass in the payload directly, and we'll encrypt it for you. Otherwise, you can do the encryption manually like above, then call the endpoint:

<CodeGroup>
  ```javascript Node.js theme={null}
  // Installation: npm i flutterwave-node-v3

  const Flutterwave = require('flutterwave-node-v3');
  const flw = new Flutterwave(process.env.FLW_PUBLIC_KEY, process.env.FLW_SECRET_KEY);

  const payload = {
  card_number: '5531886652142950',
  expiry_month: '09',
  expiry_year: '32',
  cvv: '564',
  currency: 'NGN',
  amount: '7500',
  email: 'developers@flutterwavego.com',
  fullname: 'Flutterwave Developers',
  phone_number: '+2349012345678',
  tx_ref: 'UNIQUE_TRANSACTION_REFERENCE',
  redirect_url: 'https://example_company.com/success',
  enckey: process.env.FLW_ENCRYPTION_KEY
  }

  flw.Charge.card(payload)
  .then(response => console.log(response));

  ```

  ```php PHP theme={null}
  // Installation: composer require flutterwavedev/flutterwave-v3

  use Flutterwave\Util\Currency;

  $data = [
      "amount" => 7500,
      "currency" => Currency::NGN,
      "tx_ref" => "UNIQUE_TRANSACTION_REFERENCE",
      "redirectUrl" => "https://example_company.com/success",
      "additionalData" => [
          "card_details" => [
              "card_number" => "5531886652142950",
              "expiry_month" => "09",
              "expiry_year" => "32",
              "cvv" => "564"
          ]
      ],
  ];

  $cardpayment = \Flutterwave\Flutterwave::create("card");
  $customerObj = $cardpayment->customer->create([
      "email" => "developers@flutterwavego.com",
      "full_name" => "Flutterwave Developers",
      "phone" => "+2349012345678"
  ]);
  $data['customer'] = $customerObj;
  $payload  = $cardpayment->payload->create($data);
  $result = $cardpayment->initiate($payload);
  print_r($result);
  ```

  ```ruby Ruby theme={null}
  # Installation: gem install flutterwave_sdk

  require './flutterwave_sdk'

  flw = Flutterwave.new(ENV["FLW_PUBLIC_KEY"], ENV["FLW_SECRET_KEY"], ENV["FLW_ENCRYPTION_KEY"])
  charge_card = Card.new(flw)
  payload = {
    "card_number": "5531886652142950",
    "expiry_month": "09",
    "expiry_year": "32",
    "cvv": "564",
    "currency": "NGN",
    "amount": "7500",
    "email": "developers@flutterwavego.com",
    "fullname": "Flutterwave Developers",
    "phone_number": "+2349012345678",
    "tx_ref": "UNIQUE_TRANSACTION_REFERENCE",
    "redirect_url": "https://example_company.com/success"
  }
  response = charge_card.initiate_charge(payload)
  print response
  ```

  ```python Python theme={null}
  # Installation: pip install rave_python

  import os
  from rave_python import Rave

  rave = Rave(os.getenv("FLW_PUBLIC_KEY"), os.getenv("FLW_SECRET_KEY"))
  details = {
    "card_number": '5531886652142950',
    "expiry_month": '09',
    "expiry_year": '32',
    "cvv": '564',
    "currency": 'NGN',
    "amount": '7500',
    "email": 'developers@flutterwavego.com',
    "fullname": 'Flutterwave Developers',
    "tx_ref": 'UNIQUE_TRANSACTION_REFERENCE',
    "redirect_url": 'https://example_company.com/success',
  }
  response = rave.Card.charge(payload)
  print(response)
  ```

  ```go Go theme={null}
  // Installation: go get github.com/Flutterwave/Rave-go/rave

  import (
    "fmt"
    "os"
    "github.com/Flutterwave/Rave-go/rave"
  )
  var r = rave.Rave{
    false,
    os.Getenv("FLW_PUBLIC_KEY"),
    os.Getenv("FLW_SECRET_KEY"),
  }
  var card = rave.Card{
    r,
  }
  payload := rave.CardChargeData{
    Cardno: "5531886652142950",
    Expirymonth: "09",
    Expiryyear: "32",
    Cvv:"564",
    Currency: "NGN",
    Amount: 7500,
    Email: "developers@flutterwavego.com",
    FirstName: "Flutterwave",
    LastName: "Developers",
    Txref: "UNIQUE_TRANSACTION_REFERENCE",
    RedirectUrl: "https://example_company.com/success"
  }
  err, response := card.ChargeCard(payload)
  if err != nil {
    panic(err)
  }
  fmt.Println(response)
  ```

  ```json curl (Manually encrypted) theme={null}
  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"}'
  ```
</CodeGroup>

## Authorize the Payment

This next step will vary depending on the type of card you're charging. After initiating a charge, the customer may need to authorize the charge on their card. Card authorization methods are typically one of the following:

* **PIN**: The customer enters the card PIN to authorize the payment.
* **AVS (Address Verification System)**: The customer enters details of the card's billing address. This is often used for international cards.
* **3DS (3D Secure)**: The customer needs to be redirected to a secure webpage to complete the payment.
* **None**: In some cases, the card may not need any authorization steps at all.

The response you get from the previous step (initiating the payment) will tell you the authorization required for the card (if any) in the `meta.authorization` field. The `meta.authorization.mode` key indicates which type of authorization is needed, while the `meta.authorization.fields` key indicates the fields you need to collect from the customer. For instance:

* If the `mode` is `"pin"`, then the card requires PIN authentication. You'll need to collect the customer's PIN.
* If the `mode` is `"avs_noauth"`, the card requires AVS authentication. You'll need to collect the specified `fields` (for example, `city`, `address`, `state`, `country`, and `zipcode`).
* If the `mode` is `"redirect"`, then you won't be collecting any extra fields. Instead, you'll redirect to the specified URL (in `meta.authorization.redirect`) for the customer to authenticate with their bank.
* In some cases, the card doesn't need any additional authentication, so you can skip to [verifying the payment](/payment-api/card#validate-the-charge). In that case, the `data.status` field will be either `"successful"` or `"failed"`.

Here are some sample responses for the different scenarios:

<CodeGroup>
  ```json PIN authorization theme={null}
  {
    "status": "success",
    "message": "Charge authorization data required",
    "meta": {
      "authorization": {
        "mode": "pin",
        "fields": [
          "pin"
        ]
      }
    }
  }
  ```

  ```json AVS authorization theme={null}
  {
  	"status": "success",
  	"message": "Charge authorization data required",
  	"meta": {
  		"authorization": {
  			"mode": "avs_noauth",
  			"fields": ["city", "address", "state", "country", "zipcode"]
  		}
  	}
  }
  ```

  ```json 3DS authorization theme={null}
  {
  	"status": "success",
  	"message": "Charge initiated",
  	"data": {
  		"id": 1254647,
  		"tx_ref": "UNIQUE_TRANSACTION_REFERENCE",
  		"flw_ref": "IUSE9942171639769110812191",
  		"device_fingerprint": "N/A",
  		"amount": 7500,
  		"charged_amount": 7500,
  		"app_fee": 105,
  		"merchant_fee": 0,
  		"processor_response": "Pending redirect to issuer's 3DS authentication page",
  		"auth_model": "VBVSECURECODE",
  		"currency": "NGN",
  		"ip": "N/A",
  		"narration": "CARD Transaction ",
  		"status": "pending",
  		"payment_type": "card",
  		"fraud_status": "ok",
  		"charge_type": "normal",
  		"created_at": "2020-04-30T20:09:56.000Z",
  		"account_id": 27468,
  		"customer": {
  			"id": 370672,
  			"phone_number": "2349012345678",
  			"name": "Flutterwave Developers",
  			"email": "developers@flutterwavego.com",
  			"created_at": "2020-04-30T20:09:56.000Z"
  		},
  		"card": {
  			"first_6digits": "543889",
  			"last_4digits": "0229",
  			"issuer": "MASTERCARD MASHREQ BANK CREDITSTANDARD",
  			"country": "EG",
  			"type": "MASTERCARD",
  			"expiry": "10/31"
  		}
  	},
  	"meta": {
  		"authorization": {
  			"mode": "redirect",
  			"redirect": "https://auth.coreflutterwaveprod.com/transaction?reference=IUSE9942171639769110812191"
  		}
  	}
  }
  ```
</CodeGroup>

### Handling 3DS Authorization

With 3DS authorization, all you'll need to do is redirect your user to the specified redirect URL (the `meta.authorization.redirect` field). The user will then complete the authorization on their bank's page, we'll redirect back to the `redirect_url` you specified initially, appending the `tx_ref` and `status` to the URL as query parameters.

<Info>
  Before redirecting, you should store the transaction ID returned from the
  3DS response ( `data.id` ), as you'll need it to
  [verify](/payment-api/card#validate-the-charge) the payment when we redirect
  back to your app.
</Info>

<CodeGroup>
  ```javascript Node.js theme={null}
  const transactionId = response.data.id;
  const flwRef = response.data.flw_ref;

  // Store the transaction ID
  // so we can look it up later with the tx_ref
  await redis.setAsync(`txref-${txRef}`, transactionId);
  const authUrl = response.meta.authorization.redirect;
  res.redirect(authUrl);

  ```

  ```php PHP theme={null}
  $transactionId = $response['data']['id'];
  $txRef = $response['data']['tx_ref'];

  // Store the transaction ID
  // so we can look it up later with the tx_ref
  Redis::set("txref-$txRef", $transactionId);
  $authUrl = $response['meta']['authorization']['redirect'];
  return redirect($authUrl);
  ```

  ```ruby Ruby theme={null}
  transaction_id = response['data']['id']
  tx_ref = response['data']['tx_ref']

  # Store the transaction ID
  # so we can look it up later with the tx_ref
  redis.set "txref-#{tx_ref}", transaction_id
  auth_url = response['meta']['authorization']['redirect']
  redirect to: auth_url
  ```

  ```python Python theme={null}
  transaction_id = response['data']['id']
  tx_ref = response['data']['tx_ref']

  # Store the transaction ID
  # so we can look it up later with the tx_ref
  redis.set(f'txref-{tx_ref}', transaction_id)
  auth_url = response['meta']['authorization']['redirect']
  redirect(auth_url)
  ```

  ```go Go theme={null}
  transactionId := response["data"]["id"]
  txRef := response["data"]["tx_ref"]

  // Store the transaction ID
  // so we can look it up later with the tx_ref
  redis.Set("txref-" + txRef, transactionId)
  authUrl := response["meta"]["authorization"]["redirect"]
  c.Redirect(authUrl)
  ```
</CodeGroup>

With 3DS authorization, you won't need to validate the charge. In your redirect route, you can then skip to [verifying the payment](/payment-api/card#validate-the-charge).

### Handling PIN and AVS Authorization

With PIN and AVS authorization, you'll need to collect the specified `fields` (i.e. the card's PIN or address details) and add them to your initial payload (in an `authorization` object). Then, you'll encrypt the new payload and send it to the same charge card endpoint.

<Info>
  This means you'll be calling the charge card endpoint twice—the first time
  to find out the authorization mode, and the second to authorize the charge.
</Info>

In this example, that means we'll end up with a payload like this:

<CodeGroup>
  ```json PIN authorization theme={null}
  {
    "card_number": "5531886652142950",
    "expiry_month": "09",
    "expiry_year": "32",
    "cvv": "564",
    "currency": "NGN",
    "amount": "7500",
    "email": "developers@flutterwavego.com",
    "phone_number": "+2349012345678",
    "fullname": "Flutterwave Developers",
    "tx_ref": "UNIQUE_TRANSACTION_REFERENCE",
    "redirect_url":"https://example_company.com/success",
    "authorization": {
      "mode": "pin",
      "pin": "3310"
    }
  }
  ```

  ```json AVS authorization theme={null}
  {
  	"card_number": "4556052704172643",
  	"expiry_month": "09",
  	"expiry_year": "32",
  	"cvv": "899",
  	"currency": "NGN",
  	"amount": "7500",
  	"email": "developers@flutterwavego.com",
  	"fullname": "Flutterwave Developers",
  	"phone_number": "+2349012345678",
  	"tx_ref": "UNIQUE_TRANSACTION_REFERENCE",
  	"redirect_url": "https://example_company.com/success",
  	"authorization": {
  		"mode": "avs_noauth",
  		"city": "San Francisco",
  		"address": "69 Fremont Street",
  		"state": "CA",
  		"country": "US",
  		"zipcode": "94105"
  	}
  }
  ```
</CodeGroup>

Then you encrypt the new payload and send it to the charge card [endpoint](/api-reference/charges/card).

## Validate the Charge

After encrypting and sending the new payload, if the authorization is successful, you'll get a response that looks like one of these:

<CodeGroup>
  ```json OTP required theme={null}
  {
    "status": "success",
    "message": "Charge initiated",
    "data": {
      "id": 288192886,
      "tx_ref": "UNIQUE_TRANSACTION_REFERENCE",
      "flw_ref": "FLEG8147171639979534513071",
      "device_fingerprint": "N/A",
      "amount": 7500,
      "charged_amount": 7500,
      "app_fee": 105,
      "merchant_fee": 0,
      "processor_response": "Kindly enter the OTP sent to *******0328",
      "auth_model": "PIN",
      "currency": "NGN",
      "ip": "N/A",
      "narration": "CARD Transaction ",
      "status": "pending",
      "auth_url": "N/A",
      "payment_type": "card",
      "fraud_status": "ok",
      "charge_type": "normal",
      "created_at": "2021-07-15T14:06:55.000Z",
      "account_id": 17321,
      "customer": {
        "id": 370672,
        "phone_number": "2349012345678",
        "name": "Flutterwave Developers",
        "email": "developers@flutterwavego.com",
        "created_at": "2021-07-15T14:06:55.000Z"
      },
      "card": {
        "first_6digits": "553188",
        "last_4digits": "2950",
        "issuer": "MASTERCARD  CREDIT",
        "country": "NG",
        "type": "MASTERCARD",
        "expiry": "09/32"
      }
    },
    "meta": {
      "authorization": {
        "mode": "otp",
        "endpoint": "/v3/validate-charge"
      }
    }
  }
  ```

  ```json Redirect required theme={null}
  {
  	"status": "success",
  	"message": "Charge initiated",
  	"data": {
  		"id": 1254647,
  		"tx_ref": "UNIQUE_TRANSACTION_REFERENCE",
  		"flw_ref": "IUSE9942171639769110812191",
  		"device_fingerprint": "N/A",
  		"amount": 7500,
  		"charged_amount": 7500,
  		"app_fee": 105,
  		"merchant_fee": 0,
  		"processor_response": "Pending redirect to issuer's 3DS authentication page",
  		"auth_model": "VBVSECURECODE",
  		"currency": "NGN",
  		"ip": "N/A",
  		"narration": "CARD Transaction ",
  		"status": "pending",
  		"payment_type": "card",
  		"fraud_status": "ok",
  		"charge_type": "normal",
  		"created_at": "2021-07-15T14:06:55.000Z",
  		"account_id": 17321,
  		"customer": {
  			"id": 370672,
  			"phone_number": "2349012345678",
  			"name": "Flutterwave Developers",
  			"email": "developers@flutterwavego.com",
  			"created_at": "2021-07-15T14:06:55.000Z"
  		},
  		"card": {
  			"first_6digits": "543889",
  			"last_4digits": "0229",
  			"issuer": "MASTERCARD MASHREQ BANK CREDITSTANDARD",
  			"country": "EG",
  			"type": "MASTERCARD",
  			"expiry": "10/31"
  		}
  	},
  	"meta": {
  		"authorization": {
  			"mode": "redirect",
  			"redirect": "https://auth.coreflutterwaveprod.com/transaction?reference=IUSE9942171639769110812191"
  		}
  	}
  }
  ```
</CodeGroup>

This response tells you how to proceed.

<Note>
  Note that even though the `status` field is `"success"` (the charge has been
  initiated), the `data.status` field is `"pending"` , meaning that the
  transaction hasn't yet been approved by the customer. You'll need to
  validate the transaction to complete it.
</Note>

As you can see, depending on the type of card you're charging, you'll get one of two things in the new `meta.authorization.mode` field.

* `redirect`: in this case, you'll get a `processor_response` message saying `"Pending Validation"`. You'll need to redirect your customers to the returned link where they can complete the payment. Afterwards, we'll redirect the customer back to the `redirect_url` you specified earlier, with a `tx_ref` and `status`. This means you don't need to do anything else, and your redirect handler can skip to [verifying the payment](/payment-api/card#validate-the-charge).
* `otp`: This means an OTP has been sent to your customer's mobile phone. The `processor_response` will contain instructions you can display to the cardholder. When the user enters the OTP, you'll need to validate the transaction by calling our [validate charge endpoint](/api-reference/charges/validate-charge) with the customer's OTP and the `flw_ref` for the transaction (which was returned in the earlier response).

<CodeGroup>
  ```javascript Node.js theme={null}
  const response = await flw.Charge.validate({
      otp: req.body.otp,
      flw_ref: req.session.flw_ref
  });
  ```

  ```php PHP theme={null}
  $response = $cardChargeService->validateTransaction([
      'otp' => $req->body->get('otp'),
      'flw_ref' => session('flw_ref'),
  ]);
  ```

  ```ruby Ruby theme={null}
  response = charge_card.validate_charge(session[:flw_ref], params[:otp])
  ```

  ```python Python theme={null}
  response = charge_card.validate_charge(session[:flw_ref], params[:otp])
  ```

  ```go Go theme={null}
  payload := rave.CardValidateData{
      Reference: sessions.Default(c).Get("flw_ref"),
      Otp: otp,
  }
  err, response := card.ValidateCharge(payload)
  ```

  ```json Sample Request theme={null}
  payload := rave.CardValidateData{
      Reference: sessions.Default(c).Get("flw_ref"),
      Otp: otp,
  }
  err, response := card.ValidateCharge(payload)
  ```
</CodeGroup>

You'll get a response like this:

```json Validation Successfully theme={null}
{
	"status": "success",
	"message": "Charge validated",
	"data": {
		"id": 288192886,
		"tx_ref": "UNIQUE_TRANSACTION_REFERENCE",
		"flw_ref": "FLEG8147171639979534513071",
		"device_fingerprint": "N/A",
		"amount": 7500,
		"charged_amount": 7500,
		"app_fee": 105,
		"merchant_fee": 0,
		"processor_response": "successful",
		"auth_model": "PIN",
		"currency": "NGN",
		"ip": "N/A",
		"narration": "CARD Transaction ",
		"status": "successful",
		"auth_url": "N/A",
		"payment_type": "card",
		"fraud_status": "ok",
		"charge_type": "normal",
		"created_at": "2021-07-15T14:06:55.000Z",
		"account_id": 17321,
		"customer": {
			"id": 370672,
			"phone_number": "2349012345678",
			"name": "Flutterwave Developers",
			"email": "developers@flutterwavego.com",
			"created_at": "2021-07-15T14:06:55.000Z"
		},
		"card": {
			"first_6digits": "553188",
			"last_4digits": "2950",
			"issuer": "MASTERCARD  CREDIT",
			"country": "NG",
			"type": "MASTERCARD",
			"expiry": "09/32"
		}
	}
}
```

The `data.status` field indicates that the charge was successful. However, as a safety measure, always verify the payment.

## Handling Redirects

When redirecting users, the URL structure typically places the query string before the fragment identifier (`#`). This is standard web behavior and may cause errors if your application relies on the hash for routing.

```json theme={null}
https://example.com/path?status=successful&tx_ref=xyz&transaction_id=123#fragment
```

If your application needs the hash to appear before the query string (e.g. for frontend routing), and Direct Charge is stripping the hash, use **#redirect\_as\_post**, to ensure that the hash remains part of the URL during the redirect.

### Redirects as a POST Request

To ensure that a redirect is performed using a POST request instead of the default GET request, append **#redirect\_as\_post** to the `redirect_url`.

```json theme={null}
{
  ...
  "redirect_url": "https://example.com/#/path#redirect_as_post",
  ...
}
```

After completing your payment, Flutterwave appends the transaction information to the body of the request. The **#redirect\_as\_post** helps you retain the fragment identifier needed for correct routing in your application.

* If your `redirect_url` is a frontend application:
  * The POST request body will not be accessible directly.
  * We recommend the use of webhooks or the [verify endpoint](/api-reference/transactions/verify-a-transaction) to retrieve the transaction status after the redirect.
* If the `redirect_url` is a backend system:
  * The backend can process both GET and POST requests, making the request body accessible.

### Redirects using Ignore\_Opener

If you experience issues when redirecting your users to new windows/tabs, e.g. redirect within single-page applications (SPAs), append **#ignore\_opener** to the `redirect_url`.

This prevents the redirected page from accessing the `window.opener` property, ensuring it opens separately and can't immediately redirect to a malicious URL.

```json theme={null}
{
  ...
  "redirect_url": "https://example.com/#/path#ignore_opener",
  ...
}
```

## Verify the Payment

Almost done! The last step is to verify that the payment was successful before giving value to your customer. To do so, call our [verify transaction endpoint](/api-reference/transactions/verify-a-transaction) with your `transaction_id`. See our guide on [transaction verification](/transaction-verification) for more details.

<CodeGroup>
  ```javascript Node.js theme={null}
  // If we came from a redirect, we'll need to
  // fetch the transactionID we stored earlier, using the tx_ref
  const txRef = req.query.tx_ref
  const transactionId = await redis.getAsync(`txref-${txRef}`);

  // Otherwise, if we came from a validate process,
  // we can just get the transaction ID from the response
  const transactionId = response.data.id;
  flw.Transaction.verify({
  id: transactionId
  });

  ```

  ```php PHP theme={null}
  // If we came from a redirect, we'll need to
  // fetch the transactionID we stored earlier, using the tx_ref
  $txRef = $req->query->get('tx_ref');
  $transactionId = Redis::get("txref-$txRef");

  // Otherwise, if we came from a validate process,
  // we can just get the transaction ID from the response
  $transactionId = $response['data']['id'];
  $cardChargeService->verifyTransaction($transactionId);
  ```

  ```ruby Ruby theme={null}
  # If we came from a redirect (Step 4), we'll need to
  # fetch the transactionID we stored earlier, using the tx_ref
  transaction_id = redis.get "txref-#{params[:tx_ref]}"

  # Otherwise, if we came from a validate process (Step 5),
  # we can just get the transaction ID from the response
  transaction_id = response['data']['id']
  charge_card.verify_charge transaction_id
  ```

  ```python Python theme={null}
  # If we came from a redirect (Step 4), we'll need to
  # fetch the transactionID we stored earlier, using the tx_ref
  transaction_id = redis.get(f'txref-#{request.GET["tx_ref"]}')
  # Otherwise, if we came from a validate process (Step 5),
  # we can just get the transaction ID from the response
  transaction_id = response['data']['id']
  rave.Card.verify(transaction_id)
  ```

  ```go Go theme={null}
  payload := rave.CardVerifyData{
      Reference: txRef,
  }
  err, response := card.VerifyCard(payload)
  ```

  ```json Sample Request theme={null}
  curl --request GET \
     --url https://api.flutterwave.com/v3/transactions/288200108/verify \
     --header 'Authorization: Bearer FLW_SECRET_KEY' \
     --header 'content-type: application/json'
  ```
</CodeGroup>

You'll get a response that looks like this, and you can see that the `data.status` field is now `"successful"`.

```json Payment successful theme={null}
{
	"status": "success",
	"message": "Transaction fetched successfully",
	"data": {
		"id": 288192886,
		"tx_ref": "UNIQUE_TRANSACTION_REFERENCE",
		"flw_ref": "FLEG8147171639979534513071",
		"device_fingerprint": "N/A",
		"amount": 7500,
		"currency": "NGN",
		"charged_amount": 7500,
		"app_fee": 105,
		"merchant_fee": 0,
		"processor_response": "successful",
		"auth_model": "PIN",
		"ip": "N/A",
		"narration": "CARD Transaction ",
		"status": "successful",
		"payment_type": "card",
		"created_at": "2021-07-15T14:06:55.000Z",
		"account_id": 17321,
		"card": {
			"first_6digits": "553188",
			"last_4digits": "2950",
			"issuer": "MASTERCARD  CREDIT",
			"country": "NG",
			"type": "MASTERCARD",
			"token": "flw-t1nf-dac504fefbc7fcac6b776c0f33c2f774-m03k",
			"expiry": "09/32"
		},
		"amount_settled": 7395,
		"customer": {
			"id": 370672,
			"phone_number": "2349012345678",
			"name": "Flutterwave Developers",
			"email": "developers@flutterwavego.com",
			"created_at": "2021-07-15T14:06:55.000Z"
		}
	}
}
```

All done

## Putting it All Together

Putting it all together, here's what an implementation of direct card charge might look like:

<CodeGroup>
  ```javascript Node.js theme={null}
  // In an Express-like app:

  // The route where we initiate payment (Steps 1 - 3)
  app.post('/pay', async (req, res) => {
  const payload = {
  card_number: req.body.card_number,
  cvv: req.body.card_cvv,
  expiry_month: req.body.card_expiry_year,
  expiry_year: req.body.card_expiry_year,
  currency: 'NGN',
  amount: product.price,
  email: req.user.email,
  fullname: req.body.card_name,
  phone_number: req.user.phone_number,
  // Generate a unique transaction reference
  tx_ref: generateTransactionReference(),
  redirect_url: process.env.APP_BASE_URL + '/pay/redirect',
  enckey: process.env.FLW_ENCRYPTION_KEY
  }
  const response = await flw.Charge.card(payload);

      switch (response?.meta?.authorization?.mode) {
          case 'pin':
          case 'avs_noauth':
              // Store the current payload
              req.session.charge_payload = payload;
              // Now we'll show the user a form to enter
              // the requested fields (PIN or billing details)
              req.session.auth_fields = response.meta.authorization.fields;
              req.session.auth_mode = response.meta.authorization.mode;
              return res.redirect('/pay/authorize');
          case 'redirect':
              // Store the transaction ID
              // so we can look it up later with the flw_ref
              await redis.setAsync(`txref-${response.data.tx_ref}`, response.data.id);
              // Auth type is redirect,
              // so just redirect to the customer's bank
              const authUrl = response.meta.authorization.redirect;
              return res.redirect(authUrl);
          default:
              // No authorization needed; just verify the payment
              const transactionId = response.data.id;
              const transaction = await flw.Transaction.verify({
                  id: transactionId
              });
              if (transaction.data.status == "successful") {
                  return res.redirect('/payment-successful');
              } else if (transaction.data.status == "pending") {
                  // Schedule a job that polls for the status of the payment every 10 minutes
                  transactionVerificationQueue.add({
                      id: transactionId
                  });
                  return res.redirect('/payment-processing');
              } else {
                  return res.redirect('/payment-failed');
              }
      }

  });

  // The route where we send the user's auth details (Step 4)
  app.post('/pay/authorize', async (req, res) => {
  const payload = req.session.charge_payload;
  // Add the auth mode and requested fields to the payload,
  // then call chargeCard again
  payload.authorization = {
  mode: req.session.auth_mode,
  };
  req.session.auth_fields.forEach(field => {
  payload.authorization.field = req.body[field];
  });
  const response = await flw.Charge.card(payload);

      switch (response?.meta?.authorization?.mode) {
          case 'otp':
              // Show the user a form to enter the OTP
              req.session.flw_ref = response.data.flw_ref;
              return res.redirect('/pay/validate');
          case 'redirect':
              const authUrl = response.meta.authorization.redirect;
              return res.redirect(authUrl);
          default:
              // No validation needed; just verify the payment
              const transactionId = response.data.id;
              const transaction = await flw.Transaction.verify({
                  id: transactionId
              });
              if (transaction.data.status == "successful") {
                  return res.redirect('/payment-successful');
              } else if (transaction.data.status == "pending") {
                  // Schedule a job that polls for the status of the payment every 10 minutes
                  transactionVerificationQueue.add({
                      id: transactionId
                  });
                  return res.redirect('/payment-processing');
              } else {
                  return res.redirect('/payment-failed');
              }
      }

  });

  // The route where we validate and verify the payment (Steps 5 - 6)
  app.post('/pay/validate', async (req, res) => {
  const response = await flw.Charge.validate({
  otp: req.body.otp,
  flw_ref: req.session.flw_ref
  });
  if (response.data.status === 'successful' || response.data.status === 'pending') {
  // Verify the payment
  const transactionId = response.data.id;
  const transaction = flw.Transaction.verify({
  id: transactionId
  });
  if (transaction.data.status == "successful") {
  return res.redirect('/payment-successful');
  } else if (transaction.data.status == "pending") {
  // Schedule a job that polls for the status of the payment every 10 minutes
  transactionVerificationQueue.add({
  id: transactionId
  });
  return res.redirect('/payment-processing');
  }
  }

      return res.redirect('/payment-failed');

  });

  // Our redirect_url. For 3DS payments, Flutterwave will redirect here after authorization,
  // and we can verify the payment (Step 6)
  app.post('/pay/redirect', async (req, res) => {
  if (req.query.status === 'successful' || req.query.status === 'pending') {
  // Verify the payment
  const txRef = req.query.tx_ref;
  const transactionId = await redis.getAsync(`txref-${txRef}`);
  const transaction = flw.Transaction.verify({
  id: transactionId
  });
  if (transaction.data.status == "successful") {
  return res.redirect('/payment-successful');
  } else if (transaction.data.status == "pending") {
  // Schedule a job that polls for the status of the payment every 10 minutes
  transactionVerificationQueue.add({
  id: transactionId
  });
  return res.redirect('/payment-processing');
  }
  }

      return res.redirect('/payment-failed');

  });

  ```

  ```php PHP theme={null}
  // In a Laravel-like app:

  // The route where we initiate payment (Steps 1 - 3)
  Route::post('/pay', function ($req) {
      $payload = [
          'card_number' => $req->input('card_number'),
          'cvv' => $req->input('card_cvv'),
          'expiry_month' => $req->input('card_expiry_month'),
          'expiry_year' => $req->input('card_expiry_year'),
          'currency' => 'NGN',
          'amount' => $product->price,
          'email' => auth()->email,
          'fullname' => $req->input('card_name'),
          'tx_ref' => generateTransactionReference(),
          'redirect_url' => url('/pay/redirect'),
      ];

      $response = $cardChargeService->chargeCard($payload);
      switch ($response['meta']['authorization']['mode'] ?? null) {
          case 'pin':
          case 'avs_noauth':
              // Store the current payload
              Session::put('charge_payload', $payload);
              // Now we'll show the user a form to enter
              // the requested fields (PIN or billing details)
              Session::put('auth_fields', $response['meta']['authorization']['fields']);
              Session::put('auth_mode', $response['meta']['authorization']['mode']);
              return redirect('/pay/authorize');
          case 'redirect':
              // Store the transaction ID
              // so we can look it up later with the flw_ref
              Redis::set("txref-{$response['data']['tx_ref']}", $response['data']['id']);
              // Auth type is redirect,
              // so just redirect to the customer's bank
              $authUrl = $response['meta']['authorization']['redirect'];
              return redirect($authUrl);
          default:
              // No authorization needed; just verify the payment
              $transactionId = $response['data']['id'];
              $transaction = $cardChargeService->verifyTransaction($transactionId);
              if ($transaction['data']['status'] == "successful") {
                  return redirect('/payment-successful');
              } else if ($transaction['data']['status'] == "pending") {
                  // Schedule a job that polls for the status of the payment every 10 minutes
                  dispatch(new CheckTransactionStatus($transactionId));
                  return redirect('/payment-processing');
              } else {
                  return redirect('/payment-failed');
              }
          }
  });

  // The route where we send the user's auth details (Step 4)
  Route::post('/pay/authorize', function ($req) {
      $payload = Session::get('charge_payload');
      // Add the auth mode and requested fields to the payload,
      // then call chargeCard again
      $payload['authorization'] = [
          'mode' => Session::get('auth_mode'),
      ];
      foreach (Session::get('auth_fields') as $field) {
          $payload['authorization'][$field] = $req->input($field);
      }
      $response = $cardChargeService->cardCharge(payload);

      switch ($response['meta']['authorization']['mode'] ?? null) {
          case 'otp':
              // Show the user a form to enter the OTP
              Session::put('flw_ref', $response['data']['flw_ref']);
              return redirect('/pay/validate');
          case 'redirect':
              $authUrl = $response['meta']['authorization']['redirect'];
              return redirect($authUrl);
          default:
              // No validation needed; just verify the payment
              $transactionId = $response['data']['id'];
              $transaction = $cardChargeService->verifyTransaction($transactionId);
              if ($transaction['data']['status'] == "successful") {
                  return redirect('/payment-successful');
              } else if ($transaction['data']['status'] == "pending") {
                  // Schedule a job that polls for the status of the payment every 10 minutes
                  dispatch(new CheckTransactionStatus($transactionId));
                  return redirect('/payment-processing');
              } else {
                  return redirect('/payment-failed');
              }
      }
  });

  // The route where we validate and verify the payment (Steps 5 - 6)
  Route::post('/pay/validate', function ($req) {
      $response = $cardChargeService->validateTransaction([
          'otp' => $req->body->get('otp'),
          'flw_ref' => session('flw_ref'),
      ]);
      if ($response['data']['status'] === 'successful' || $response['data']['status'] === 'pending') {
          // Verify the payment
          $transactionId = $response['data']['id'];
          $transaction = $cardChargeService->verifyTransaction($transactionId);
          if ($transaction['data']['status'] == "pending") {
              return redirect('/payment-successful');
          } else if ($transaction['data']['status'] == "pending") {
              // Schedule a job that polls for the status of the payment every 10 minutes
              dispatch(new CheckTransactionStatus($transactionId));
              return redirect('/payment-processing');
          }
      }

      return redirect('/payment-failed');
  });

  // Our redirect_url. For 3DS payments, Flutterwave will redirect here after authorization,
  // and we can verify the payment (Step 6)
  Route::post('/pay/redirect', function ($req) {
      if ($req->query->get('status') === 'successful') {
          // Verify the payment
          $txRef = $req->query->get('tx_ref');
          $transactionId = Redis::get("txref-$txRef");
          $transaction = $cardChargeService->verifyTransaction($transactionId);
          if ($transaction['data']['status'] == "successful") {
              return redirect('/payment-successful');
          } else if ($transaction['data']['status'] == "pending") {
              // Schedule a job that polls for the status of the payment every 10 minutes
              dispatch(new CheckTransactionStatus($transactionId));
              return redirect('/payment-processing');
          }
      }

      return redirect('/payment-failed');
  });
  ```

  ```ruby Ruby theme={null}
  # In a Rails-like app:

  # Handles POST /pay
  # The route where we initiate payment (Steps 1 - 3)
  def pay
      charge_card = Card.new(flw)
      payload = {
          card_number: params[:card_number],
          cvv: params[:card_cvv],
          expiry_month: params[:card_expiry_month],
          expiry_year: params[:card_expiry_year],
          currency: 'NGN',
          amount: session[:amount],
          email: current_user.email,
          fullname: current_user.name,
          tx_ref: generate_transaction_reference,
          redirect_url: 'https://example_company.com/success',
      }
      response = charge_card.initiate_charge payload

      case response["meta"]["authorization"]["mode"] rescue nil
      when 'pin', 'avs_noauth'
          # Store the current payload
          session[:charge_payload] = payload
          # Now, we'll show the user a form to enter
          # the requested fields (PIN or billing details)
          session[:auth_fields] = response["meta"]["authorization"]["fields"]
          session[:auth_mode] = response["meta"]["authorization"]["mode"]
          redirect to: '/pay/authorize'
      when 'redirect'
          # Store the transaction ID
          # so we can look it up later with the flw_ref
          redis.set("txref-#{response['data']['tx_ref']}", response['data']['id'])
          # Auth type is redirect,
          # so just redirect to the customer's bank
          auth_url = response["meta"]["authorization"]["redirect"]
          redirect to: auth_url
      else
          # No authorization needed; just verify the payment
          transaction = charge_card.verify_charge transaction_id
          if transaction['data']['status'] == "successful"
              return redirect to: '/payment-successful'
          elsif transaction['data']['status'] == "pending"
              # Schedule a job that polls for the status of the payment every 10 minutes
              CheckTransactionStatus.perform_later transaction_id
              return redirect to: '/payment-processing'
          else
              return redirect to: '/payment-failed'
          end
      end
  end

  # Handles POST /pay/authorize
  # The route where we send the user's auth details (Step 4)
  def authorize_payment
      payload = session[:charge_payload]
      # Add the auth mode and requested fields to the payload,
      # then call chargeCard again
      payload['authorization'] = {
          mode: session[:auth_mode],
      }
      session.auth_fields.each do |field|
          payload['authorization'][field] = params[field]
      end
      response = charge_card.initiate_charge payload

      case response["meta"]["authorization"]["mode"] rescue nil
      when 'otp'
          # Show the user a form to enter the OTP
          session[:flw_ref] = response['data']['flw_ref']
          redirect to: '/pay/validate'
      when 'redirect'
          auth_url = response["meta"]["authorization"]["redirect"]
          redirect to: auth_url
      else
          # No validation needed; just verify the payment
          transaction_id = response['data']['id']
          transaction = charge_card.verify_charge transaction_id
          if transaction['data']['status'] == "successful"
              return redirect to: '/payment-successful'
          elsif transaction['data']['status'] == "pending"
              # Schedule a job that polls for the status of the payment every 10 minutes
              CheckTransactionStatus.perform_later transaction_id
              return redirect to: '/payment-processing'
          else
              return redirect to: '/payment-failed'
          end
      end
  end

  # Handles POST /pay/validate
  # The route where we validate and verify the payment (Steps 5 - 6)
  def validate_payment
      response = charge_card.validate_charge(session[:flw_ref], params[:otp])
      if (response['data']['status'] === 'successful' || response['data']['status'] === 'pending')
          # Verify the payment
          transaction = charge_card.verify_charge transaction_id
          if transaction['data']['status'] == "successful"
              return redirect to: '/payment-successful'
          elsif transaction['data']['status'] == "pending"
              # Schedule a job that polls for the status of the payment every 10 minutes
              CheckTransactionStatus.perform_later transaction_id
              return redirect to: '/payment-processing'
          end
      end

      redirect to: '/payment-failed'
  end

  # Handles POST /pay/redirect
  # Our redirect_url. For 3DS payments, Flutterwave will redirect here after authorization,
  # and we can verify the payment (Step 6)
  def payment_redirect
      if (params[:status] === 'successful' || params[:status] === 'pending')
          # Verify the payment
          tx_ref = params[:tx_ref]
          transaction_id = redis.get(`txref-#{tx_ref}`)
          charge_card.verify_charge transaction_id
          redirect to: '/payment-successful'
      elsif transaction['data']['status'] == "pending"
          # Schedule a job that polls for the status of the payment every 10 minutes
          CheckTransactionStatus.perform_later transaction_id
          return redirect to: '/payment-processing'
      else
          redirect to: '/payment-failed'
      end
  end
  ```

  ```python Python theme={null}
  # In a Django-like app:

  # Handles POST /pay
  # The route where we initiate payment (Steps 1 - 3)
  def pay:
      details = {
          "card_number": '4556052704172643',
          "cvv": '899',
          "expiry_month": '01',
          "expiry_year": '23',
          "currency": 'NGN',
          "amount": '7500',
          "email": 'user@example.com',
          "fullname": 'Flutterwave Developers',
          "tx_ref": 'YOUR_PAYMENT_REFERENCE',
          "redirect_url": 'https://example_company.com/success',
      }
      response = rave.Card.charge(payload)
      mode = response.get("meta", {}).get("authorization", {}).get("mode", None)
      if mode == 'pin' or mode == 'avs_noauth':
          # Store the current payload
          request.session["charge_payload"] = payload
          # Now we'll show the user a form to enter
          # the requested fields (PIN or billing details)
          request.session["auth_fields"] = response["meta"]["authorization"]["fields"]
          request.session["auth_mode"] = response["meta"]["authorization"]["mode"]
          return redirect('/pay/authorize')
      elif mode == 'redirect':
          # Store the transaction ID
          # so we can look it up later with the flw_ref
          redis.set(f'txref-{response['data']['tx_ref']}', response['data']['id'])
          # Auth type is a redirect,
          # so just redirect to the customer's bank
          auth_url = response["meta"]["authorization"]["redirect"]
          return redirect(auth_url)
      else:
          # No authorization needed; just verify the payment
          transaction = rave.Card.verify(transaction_id)
          if transaction['data']['status'] == "successful":
              return redirect('/payment-successful')
          elif transaction['data']['status'] == "pending":
              # Schedule a job that polls for the status of the payment every 10 minutes
              check_transaction_status(transaction_id)
              return redirect('/payment-successful')
          else:
               return redirect('/payment-failed')

  # Handles POST /pay/authorize
  # The route where we send the user's auth details (Step 4)
  def authorize_payment:
      payload = request.session["charge_payload"]
      # Add the auth mode and requested fields to the payload,
      # then call chargeCard again
      payload['authorization'] = {
          "mode": request.session["auth_mode"],
      }
      for field in request.session.auth_fields:
          payload['authorization'][field] = request.POST[field]
      response = rave.Card.charge(payload)

      mode = response.get("meta", {}).get("authorization", {}).get("mode", None)
      if mode == 'otp':
          # Show the user a form to enter the OTP
          request.session["flw_ref"] = response['data']['flw_ref']
          return redirect('/pay/validate')
      elif mode == 'redirect':
          auth_url = response["meta"]["authorization"]["redirect"]
          return redirect(auth_url)
      else:
          # No validation needed; just verify the payment
          transaction_id = response["data"]["id"]
          transaction = rave.Card.verify(transaction_id)
          if transaction['data']['status'] == "successful":
              return redirect('/payment-successful')
          elif transaction['data']['status'] == "pending":
              # Schedule a job that polls for the status of the payment every 10 minutes
              check_transaction_status(transaction_id)
              return redirect('/payment-successful')
          else:
               return redirect('/payment-failed')

  # Handles POST /pay/validate
  # The route where we validate and verify the payment (Steps 5 - 6)
  def validate_payment:
      response = charge_card.validate_charge(request.session["flw_ref"], request.POST["otp"])
      if response['data']['status'] == 'successful':
          # Verify the payment
          transaction = rave.Card.verify(transaction_id)
          if transaction['data']['status'] == "successful":
              return redirect('/payment-successful')
          elif transaction['data']['status'] == "pending":
              # Schedule a job that polls for the status of the payment every 10 minutes
              check_transaction_status(transaction_id)
              return redirect('/payment-successful')

      return redirect('/payment-failed')

  # Handles POST /pay/redirect
  # Our redirect_url. For 3DS payments, Flutterwave will redirect here after authorization,
  # and we can verify the payment (Step 6)
  def payment_redirect:
      if request.GET["status"] == 'successful' or request.GET["status"] == 'pending':
          # Verify the payment
          tx_ref = request.POST["tx_ref"]
          transaction_id = redis.get(f"txref-{tx_ref}")
          transaction = rave.Card.verify(transaction_id)
          if transaction['data']['status'] == 'successful':
              return redirect('/payment-successful')
          elif transaction['data']['status'] == "pending":
              # Schedule a job that polls for the status of the payment every 10 minutes
              check_transaction_status(transaction_id)
              return redirect('/payment-successful')
      else:
          return redirect('/payment-failed')
  ```
</CodeGroup>

## Webhooks

You can also verify the payment in a webhook handler. Here's an example of the payload we send to your webhook for successful card charges. The event type is `charge.completed` and the `data` object contains the transaction information.

```json theme={null}
{
	"event": "charge.completed",
	"data": {
		"id": 288192886,
		"tx_ref": "UNIQUE_TRANSACTION_REFERENCE",
		"flw_ref": "FLEG8147171639979534513071",
		"device_fingerprint": "N/A",
		"amount": 7500,
		"currency": "NGN",
		"charged_amount": 7500,
		"app_fee": 105,
		"merchant_fee": 0,
		"processor_response": "successful",
		"auth_model": "PIN",
		"ip": "N/A",
		"narration": "CARD Transaction ",
		"status": "successful",
		"payment_type": "card",
		"created_at": "2021-07-15T14:06:55.000Z",
		"account_id": 17321,
		"customer": {
			"id": 370672,
			"name": "Flutterwave Developers",
			"phone_number": "2349012345678",
			"email": "developers@flutterwavego.com",
			"created_at": "2021-07-15T14:06:55.000Z"
		},
		"card": {
			"first_6digits": "553188",
			"last_4digits": "2950",
			"issuer": "MASTERCARD  CREDIT",
			"country": "NG",
			"type": "MASTERCARD",
			"expiry": "09/32"
		}
	}
}
```
