# 

## What you will learn

1. Collect payment details in a form
2. Tokenize the card to reduce PCI scope
3. Process a sale using the token
4. Display the result to the customer


## Prerequisites

* A sandbox API key — see [Your first API call](/docs/tutorials/first-api-call)
* Basic HTML/JavaScript knowledge


## Architecture overview

![image](https://files.modern-mermaid.live/images/1776276685362-mermaid-diagram-1776276685566.png)

PCI scope
By tokenizing on your server and never logging raw card data, you minimize your PCI-DSS scope. For the lowest scope (SAQ A), consider a hosted payment form. See [PCI-DSS overview](/docs/concepts/security-compliance/pci-dss-overview).

## Step 1: Build the checkout form

Create a simple HTML form that collects the card number, expiry, CVV, and cardholder name.

```html
<form id="checkout-form">
  <label>Cardholder name
    <input name="name" placeholder="John Doe" required />
  </label>
  <label>Card number
    <input name="pan" placeholder="4111 1111 1111 1111" maxlength="19" required />
  </label>
  <label>Expiry
    <input name="expiry_month" placeholder="MM" maxlength="2" required />
    <input name="expiry_year" placeholder="YYYY" maxlength="4" required />
  </label>
  <label>CVV
    <input name="cvv" placeholder="123" maxlength="4" required />
  </label>
  <label>Amount ($)
    <input name="amount" type="number" step="0.01" min="0.50" value="19.99" required />
  </label>
  <button type="submit">Pay now</button>
</form>
```

## Step 2: Submit to your server

When the form submits, send the data to your backend over HTTPS. Never call the Paradise Gateway API directly from the browser — your API key must stay server-side.

```javascript
document.getElementById("checkout-form").addEventListener("submit", async (e) => {
  e.preventDefault();
  const form = new FormData(e.target);
  const resp = await fetch("/api/checkout", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      name: form.get("name"),
      pan: form.get("pan").replace(/\s/g, ""),
      expiry_month: form.get("expiry_month"),
      expiry_year: form.get("expiry_year"),
      cvv: form.get("cvv"),
      amount: Math.round(parseFloat(form.get("amount")) * 100),
    }),
  });
  const result = await resp.json();
  if (result.response_status === "approved") {
    document.body.innerHTML = "<h1>Payment successful!</h1><p>Transaction: " + result.id + "</p>";
  } else {
    document.body.innerHTML = "<h1>Payment failed</h1><p>" + result.response_message + "</p>";
  }
});
```

## Step 3: Tokenize the card (server-side)

On your server, first tokenize the card. This stores it securely and returns a reusable `payment_token`.

Python
```python
import os, requests

API_KEY = os.environ["PARADISE_API_KEY"]
BASE = "https://api.dev.paradisegateway.net"

def tokenize_card(name, pan, expiry_month, expiry_year):
    resp = requests.post(
        f"{BASE}/v1/payment_tokens",
        headers={"Content-Type": "application/json", "APIKEY": API_KEY},
        json={
            "customer_id": "CUSTOMER-01KFDKXMQ637EKEAY410MSQSXB",
            "nickname": name,
            "card": {
                "pan": pan,
                "expiry_month": expiry_month,
                "expiry_year": expiry_year,
                "cardholder_name": name,
            },
        },
    )
    resp.raise_for_status()
    return resp.json()["payment_token"]
```

JavaScript
```javascript
const API_KEY = process.env.PARADISE_API_KEY;
const BASE = "https://api.dev.paradisegateway.net";

async function tokenizeCard(name, pan, expiryMonth, expiryYear) {
  const resp = await fetch(`${BASE}/v1/payment_tokens`, {
    method: "POST",
    headers: { "Content-Type": "application/json", "APIKEY": API_KEY },
    body: JSON.stringify({
      customer_id: "CUSTOMER-01KFDKXMQ637EKEAY410MSQSXB",
      nickname: name,
      card: {
        pan, expiry_month: expiryMonth, expiry_year: expiryYear,
        cardholder_name: name,
      },
    }),
  });
  const data = await resp.json();
  return data.payment_token;
}
```

## Step 4: Process the sale (server-side)

Use the token to charge the customer. This keeps raw card data out of the transaction request.

Python
```python
def process_sale(payment_token, amount):
    resp = requests.post(
        f"{BASE}/v1/transactions",
        headers={"Content-Type": "application/json", "APIKEY": API_KEY},
        json={
            "type": "sale",
            "amount": amount,
            "payment_method": {
                "type": "payment_token",
                "payment_token": payment_token,
            },
        },
    )
    resp.raise_for_status()
    return resp.json()
```

JavaScript
```javascript
async function processSale(paymentToken, amount) {
  const resp = await fetch(`${BASE}/v1/transactions`, {
    method: "POST",
    headers: { "Content-Type": "application/json", "APIKEY": API_KEY },
    body: JSON.stringify({
      type: "sale",
      amount,
      payment_method: {
        type: "payment_token",
        payment_token: paymentToken,
      },
    }),
  });
  return resp.json();
}
```

## Step 5: Handle the result

Check `response_status` to determine what to show the customer.

Python
```python
# Flask example
from flask import Flask, request, jsonify
app = Flask(__name__)

@app.route("/api/checkout", methods=["POST"])
def checkout():
    data = request.json
    token = tokenize_card(
        data["name"], data["pan"],
        data["expiry_month"], data["expiry_year"],
    )
    result = process_sale(token, data["amount"])

    if result["response_status"] == "approved":
        return jsonify({"response_status": "approved", "id": result["id"]})
    else:
        return jsonify({
            "response_status": result["response_status"],
            "response_message": result.get("response_message", "Payment failed"),
        }), 400
```

JavaScript
```javascript
// Express example
app.post("/api/checkout", async (req, res) => {
  const { name, pan, expiry_month, expiry_year, amount } = req.body;
  const token = await tokenizeCard(name, pan, expiry_month, expiry_year);
  const result = await processSale(token, amount);

  if (result.response_status === "approved") {
    res.json({ response_status: "approved", id: result.id });
  } else {
    res.status(400).json({
      response_status: result.response_status,
      response_message: result.response_message || "Payment failed",
    });
  }
});
```

## Complete flow diagram

![image](https://files.modern-mermaid.live/images/1776276735036-mermaid-diagram-1776276735231.png)

## Security checklist

* [ ] API key stored in environment variable, not in client-side code
* [ ] HTTPS on your checkout page
* [ ] Raw PAN never logged or stored on your server
* [ ] Token used for payment instead of raw card data
* [ ] Error messages do not expose internal details to the customer


## Next steps

* [Auth/capture for e-commerce](/docs/tutorials/payment-cookbook/auth-capture-ecommerce) — delay capture until fulfillment
* [Handle declined transactions](/docs/tutorials/payment-cookbook/handle-declined-transactions)
* [Store a card for future use](/docs/how-tos/tokenization-vaulting/store-card)
* [PCI-DSS overview](/docs/concepts/security-compliance/pci-dss-overview)