# 

## Choose your integration approach

Your PCI scope depends on how your system handles cardholder data. Paradise Gateway supports several approaches.

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

| Approach | PCI scope | Card data touches your server? | Complexity |
|  --- | --- | --- | --- |
| **Server-side tokenization** | SAQ D | Yes — briefly, during tokenization | Medium |
| **Client-side direct post** | SAQ A-EP | No — browser posts directly to API | Low |
| **Hosted payment page** | SAQ A | No — card form hosted by gateway | Lowest |


## Approach 1: Server-side tokenization (SAQ D)

Your server receives card data from the browser, immediately tokenizes it via the API, then uses the token for all subsequent operations. The raw card data never persists on your systems.

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

### Implementation

Python (Flask)
```python
from flask import Flask, request, jsonify
import requests, os

app = Flask(__name__)
API_KEY = os.environ["PARADISE_API_KEY"]
BASE = os.environ.get("PARADISE_BASE_URL", "https://api.dev.paradisegateway.net")
HEADERS = {"Content-Type": "application/json", "APIKEY": API_KEY}

@app.route("/checkout", methods=["POST"])
def checkout():
    data = request.json

    token_resp = requests.post(f"{BASE}/v1/payment_tokens", headers=HEADERS, json={
        "customer_id": data["customer_id"],
        "card": {
            "pan": data["pan"],
            "expiry_month": data["expiry_month"],
            "expiry_year": data["expiry_year"],
            "cvv": data["cvv"],
            "cardholder_name": data["cardholder_name"],
        },
    })
    if token_resp.status_code != 200:
        return jsonify({"error": "Tokenization failed"}), 400

    token = token_resp.json()["token"]

    sale_resp = requests.post(f"{BASE}/v1/transactions", headers=HEADERS, json={
        "type": "card_sale",
        "amount": data["amount"],
        "payment_token": token,
    })
    result = sale_resp.json()
    return jsonify({
        "status": result["response_status"],
        "transaction_id": result["id"],
    })
```

JavaScript (Express)
```javascript
const express = require("express");
const app = express();
app.use(express.json());

const API_KEY = process.env.PARADISE_API_KEY;
const BASE = process.env.PARADISE_BASE_URL || "https://api.dev.paradisegateway.net";
const headers = { "Content-Type": "application/json", "APIKEY": API_KEY };

app.post("/checkout", async (req, res) => {
  const { customer_id, pan, expiry_month, expiry_year, cvv, cardholder_name, amount } = req.body;

  const tokenResp = await fetch(`${BASE}/v1/payment_tokens`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      customer_id,
      card: { pan, expiry_month, expiry_year, cvv, cardholder_name },
    }),
  });
  if (!tokenResp.ok) return res.status(400).json({ error: "Tokenization failed" });
  const { token } = await tokenResp.json();

  const saleResp = await fetch(`${BASE}/v1/transactions`, {
    method: "POST",
    headers,
    body: JSON.stringify({ type: "card_sale", amount, payment_token: token }),
  });
  const result = await saleResp.json();
  res.json({ status: result.response_status, transaction_id: result.id });
});
```

### SAQ D requirements checklist

* Encrypt all cardholder data in transit (TLS 1.2+)
* Never store raw PAN, CVV, or track data
* Implement access controls and logging
* Regularly scan for vulnerabilities
* Maintain a written security policy


## Approach 2: Client-side direct post (SAQ A-EP)

The browser sends card data directly to Paradise Gateway. Your server never sees the raw card details — it only receives the token.

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

### Implementation (JavaScript)

```html
<form id="checkout-form">
  <input name="pan" placeholder="4111 1111 1111 1111" required />
  <input name="expiry_month" placeholder="MM" required />
  <input name="expiry_year" placeholder="YYYY" required />
  <input name="cvv" placeholder="123" required />
  <input name="cardholder_name" placeholder="John Doe" required />
  <button type="submit">Pay $19.99</button>
</form>

<script>
document.getElementById("checkout-form").addEventListener("submit", async (e) => {
  e.preventDefault();
  const form = new FormData(e.target);

  // Step 1: Tokenize directly with Paradise Gateway
  const tokenResp = await fetch("https://api.dev.paradisegateway.net/v1/payment_tokens", {
    method: "POST",
    headers: { "Content-Type": "application/json", "APIKEY": "PUBLIC_TOKENIZATION_KEY" },
    body: JSON.stringify({
      card: {
        pan: form.get("pan"),
        expiry_month: form.get("expiry_month"),
        expiry_year: form.get("expiry_year"),
        cvv: form.get("cvv"),
        cardholder_name: form.get("cardholder_name"),
      },
    }),
  });
  const { token } = await tokenResp.json();

  // Step 2: Send token to your server
  const orderResp = await fetch("/checkout", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ token, amount: 1999 }),
  });
  const result = await orderResp.json();
  alert(result.status === "approved" ? "Payment successful!" : "Payment failed.");
});
</script>
```

CORS and key scope
Client-side tokenization requires a tokenization-only API key that can be safely exposed in the browser. Verify that CORS headers allow requests from your domain.

## Security checklist

Regardless of which approach you choose:

| Requirement | Details |
|  --- | --- |
| **TLS everywhere** | All pages that collect or display payment data must use HTTPS |
| **No PAN in logs** | Never log full card numbers — mask to last four digits |
| **No CVV storage** | Never store CVV anywhere, for any duration |
| **Content Security Policy** | Restrict script sources to prevent XSS that could steal card data |
| **Subresource Integrity** | Pin hashes on third-party scripts loaded on payment pages |
| **Input validation** | Luhn-check card numbers, validate expiry dates client-side before submission |


## Decision guide

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

| Factor | Server-side | Client-side | Hosted |
|  --- | --- | --- | --- |
| PCI scope | SAQ D (full) | SAQ A-EP (reduced) | SAQ A (minimal) |
| UX control | Full | Full | Limited |
| Implementation effort | Medium | Low | Lowest |
| Card data on your server | Briefly | Never | Never |


## Best practices

* **Tokenize immediately** — if card data touches your server, tokenize in the same request and discard the raw data.
* **Use separate keys** — if client-side tokenization is available, use a restricted key that can only create tokens.
* **Test with sandbox** — verify your PCI-compliant flow works end-to-end before going to production.
* **Audit annually** — complete your SAQ annually and maintain documentation of your data flow.
* **Review CSP headers** — ensure your Content Security Policy allows connections to `api.dev.paradisegateway.net` (sandbox) and the production endpoint.


## Further reading

* [PCI-DSS overview](/docs/concepts/security-compliance/pci-dss-overview)
* [Data protection](/docs/concepts/security-compliance/data-protection)
* [Build a checkout page](/docs/tutorials/build-checkout-page)
* [Store a card for future use](/docs/how-tos/tokenization-vaulting/store-card)