# Save a payment method with Direct API

This guide shows how to save a customer’s card **entirely via your server** by creating a `SetupIntent` with `payment_method_data` and confirming it in one request. Use this when you can (and are willing to) handle raw card data server-side.

> **PCI-DSS scope notice** 
> Posting PAN/CVC directly to your servers places you in **SAQ D** scope. If you want to minimize PCI burden, use Drop-in or Checkout instead.

1. ## Prerequisites

| Item                  | Notes                                                        |
| --------------------- | ------------------------------------------------------------ |
| **Secret key**        | Use your server key only on the backend.                     |
| **Customer ID**       | Reuse or create with `POST /v1/customers` (email recommended). |
| **Redirect endpoint** | A front-end page to receive the user after 3-D Secure (`return_url`). |
| **Webhooks**          | Expose an HTTPS endpoint. Treat **`setup_intent.succeeded`** as the **only authoritative** success signal. |

1. ## Create & confirm a SetupIntent (with card data)

[**Create a SetupIntent**](api-53878137)

*Request*

```JSON
{
  "confirm": true,
  "customer": "cus_1838767497487056896",
  "payment_method_types": ["card"],
  "payment_method_data": {
    "type": "card",
    "card": {
      "exp_month": "04",
      "exp_year": "2027",
      "number": "4761344136141390",
      "cvc": "022",
      "name": "XX XXX"
    }
  },
  "usage": "on_session",
  "return_url": "https://yourwebsite.com"
}
```

**Key parameters**

| Field                 | Required            | Description                                                  |
| --------------------- | ------------------- | ------------------------------------------------------------ |
| `confirm`             | ✔                   | Set **`true`** to attempt confirmation immediately.          |
| `customer`            | ✔                   | The customer to attach the card to.                          |
| `payment_method_data` | ✔                   | Raw card details (PCI scope).                                |
| `usage`               | ✔                   | `on_session` (user present now). Future charges can still be `off_session`. |
| `return_url`          | ✔ when 3DS possible | Where to send the cardholder back after challenge.           |

**Possible responses**

- **Frictionless success**: `status: "succeeded"` → you’ll still confirm via webhook (see §4).
- **Action required (3-D Secure)**: `status: "requires_action"` with `next_action.type = "challenge_redirect"` .
- **Failure / try another card**: `status: "requires_payment_method"` plus error details.

**Sample “requires_action” response (yours):**

```JSON
{
  "id": "seti_1838873795297804288",
  "object": "setup_intent",
  "status": "requires_action",
  "customer": "cus_1838767497487056896",
  "client_secret": "seti_..._secret_...",
  "next_action": {
    "type": "challenge_redirect",
    "challenge_redirect": {
          "url": "https://XXXXXXXXXXX",
          "return_url": "https://yourwebsite.com"
    }
  },
  "payment_method": "pm_1838873795201335296",
  "payment_method_options": {
    "card": {
         "request_three_d_secure": "auto", 
         "setup_future_usage": "off_session" 
    }
  }
}
```

**Complete 3-D Secure if** **`requires_action`**

When `next_action.type = "challenge_redirect"`:

1. **Redirect the customer** to `next_action.challenge_redirect.url`.
2. Cardholder completes the bank challenge.
3. They are returned to your `return_url`.
4. **Do not** assume success solely from the redirect; rely on the webhook events.

> Optional: You may poll `GET /v1/setup_intents/{id}` on your server, but the **source of truth** is the webhook event.

1. ## Handle webhooks

Enable these events in the Dashboard and point them to your webhook endpoint:

| Event                    | Purpose                 | Typical action                                     |
| :----------------------- | :---------------------- | :------------------------------------------------- |
| `setup_intent.created`   | Session starts          | Optional logging / analytics                       |
| `setup_intent.succeeded` | **Saved successfully.** | Persist `(customer_id, payment_method_id)` mapping |

**`setup_intent.succeeded`**- **Payload** **demo**

```JSON
{
  "id": "evt_1953045921369423872",
  "object": "event",
  "created": 1754477408000,
  "livemode": false,
  "data": {
    "object": {
      "id": "seti_1953034584329289728",
      "object": "setup_intent",
      "created": 1754474705000,
      "livemode": false,
      "status": "succeeded",
      "metadata": {
        "a": "1",
        "b": "2"
      },
      "customer": "cus_1952983283688013824",
      "client_secret": "seti_1953034584329289728_secret_QNWoBNDOZpW4bomjsAgC8DF1",
      "payment_method_types": [
        "card"
      ],
      "payment_method_options": {
        "card": {
          "request_three_d_secure": "auto",
          "setup_future_usage": "off_session"
        }
      },
      "return_url": "https://checkouttest.wooshpay.com/setup/cs_test_1953034583884693504?key=cGtfdGVzdF9OVEUyTWpZeE1ETTNOekExT1RVek16SXdPVFl4T25SRWFEUjRhbWxhVUcxM1RFeG1aMXBGUVdwd2RGVlRaakUyTnpZMU1qZ3pNamswT1RN",
      "payment_method": "pm_1953045864444329984"
    }
  },
  "type": "setup_intent.succeeded"
}
```

1. ## Charge the card later

[**Create a PaymentIntent**](api-42631218)

POST /v1/payment_intents

```JSON
{
  "amount": 2500,
  "currency": "USD",
  "confirm": true,
  "off_session": false,          // true if no user present
  "customer": "cus_1953770230215868416",
  "payment_method": "pm_123123123",
  "return_url": "https://example.com/pay/complete"
}
```

| Key point        | Value                                                        |
| ---------------- | ------------------------------------------------------------ |
| `customer`       | ID from Step 1.                                              |
| `payment_method` | Saved card’s ID from webhook or listing API.                 |
| `off_session`    | `false`to attempt and on-session charge(user present now)`true` to attempt an off-session charge (recurring / unsupervised). |

1. ## Test the flow

| **Action**               | **Test card number** | **Notes**                              |
| ------------------------ | -------------------- | -------------------------------------- |
| Successful save + charge | 4111 4111 41111 4111 | Any future date, any CVC               |
| 3-D Secure required      | 4462030000000000     | Checkout will prompt for 3DS challenge |

Use your **test secret key** and point webhooks to your dev endpoint.

**Quick reference**

| Task             | API                                                          |
| ---------------- | ------------------------------------------------------------ |
| Create session   | `POST /v1/checkout/sessions/setup`                           |
| Listen to events | `setup_intent.created``setup_intent.succeeded`               |
| List cards       | `GET /v1/customers/{id}/payment_methods`                     |
| Charge card      | `POST /v1/payment_intents` (with `customer` + saved `payment_method)` |
