# Save a payment method with Wooshpay Checkout

This guide shows how to let Wooshpay’s hosted Checkout page collect and store a customer’s card so you can charge it later—no custom UI required.

## 1 . Prerequisites

| Item            | Notes                                                        |
| --------------- | ------------------------------------------------------------ |
| Secret key      | Use your **server-side** key to create the session.          |
| Customer ID     | Reuse an existing customer or [create one](/api-55528906) with `POST /v1/customers` (email recommended). |

## 2 . Create a Checkout Session (setup mode)

POST： /v1/checkout/sessions/setup

```JSON
{
  "cancel_url":  "https://example.com/billing/cancel",
  "success_url": "https://example.com/billing/success",
  "payment_method_types": ["card"],
  "customer":    "cus_1953770230215868416",
  "metadata": {
    "order_id":"order123",
    "merchant_user_id":"123"
  }
}
```

Request parameters

| Field                  | Required | Description                                                  |
| ---------------------- | -------- | ------------------------------------------------------------ |
| `cancel_url`           | ✔        | Where to bring the shopper if they abort the flow.           |
| `success_url`          | ✔        | Where to bring the shopper after the card is saved.          |
| `payment_method_types` | ✔        | Must be `["card"]` for save-card.                            |
| `customer`             | ✔        | Attach the new PaymentMethod to this customer. *(If omitted, Wooshpay will create a customer and return its ID.)* |
| `metadata`             | –        | Arbitrary key/value pairs copied to webhooks                 |

*response:*

```JSON
{
    "id": "cs_1953770262348431360",
    "object": "checkout.session",
    "created": 1754650104000,
    "livemode": false,
    "customer": "cus_1953770230215868416",
    "metadata": {
        "merchant_user_id": "123",
        "orderID": "order123"
    },
    "mode": "setup",
    "status": "open",
    "url": "https://checkout.XXXXXX",
    "customer_details": {
        "id": "cus_1953770230215868416",
        "object": "customer",
        "created": 1754650097000,
        "livemode": true,
        "address": {
            "country": "CN"
        },
        "email": "test_prod@gmail.com",
        "name": "test_prod"
    },
    "expires_at": 1754736504499,
    "live_mode": true,
    "payment_method_types": [
        "card"
    ],
    "setup_intent": "seti_1953770262453288960",
    "success_url": "http://yourwebsite.com",
    "cancel_url": "http://yourwebsite.com"
}
```

Redirect the Customer

```JavaScript
// Node, Express-style route
app.post("/create-checkout-session", async (req, res) => {
  const session = await wooshpay.checkout.sessions.create({
    mode: "setup",
    success_url: "https://example.com/success",
    cancel_url:  "https://example.com/cancel",
    payment_method_types: ["card"],
    customer: "cus_1953770230215868416"
  });
  res.redirect(303, session.url);
});
```


![下载.png](https://api.apifox.com/api/v1/projects/1296482/resources/553858/image-preview)

## 3 . Customer completes Checkout

- **Success:** Wooshpay attaches the card to the customer and redirects to your `success_url`.
- **Cancel:** Customer lands on `cancel_url`; no card is saved.

## 4 . 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"
}
```
Persist `(customer_id, payment_method_id)` mapping for charge the card later

## 5 . Charge the card later

[**Create a PaymentIntent**](/api-42631218)
 
POST /v1/payment_intents

```JSON
{
  "amount": 2500,
  "currency": "USD",
  "confirm": true,
  "off_session": true,          // 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`    | `true` to attempt an off-session charge (recurring / unsupervised). |

## 6 . 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](/api-112153316)       | `GET /v1/customers/{id}/payment_methods`       |
| [Charge card](/api-42631218)      | `POST /v1/payment_intents`                     |
