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

| 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 |
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.

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"],
})- 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
The browser sends card data directly to Paradise Gateway. Your server never sees the raw card details — it only receives the token.

<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.
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 |

| 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 |
- 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.