{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-docs/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["tabs","tab","admonition"]},"type":"markdown"},"seo":{"title":"Reefpay Docs","description":"Reefpay is the groundbreaking payment gateway that lets you integrate with the future of payments.","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"tutorial-pci-compliant-checkout","__idx":0},"children":["Tutorial: PCI-compliant checkout"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"choose-your-integration-approach","__idx":1},"children":["Choose your integration approach"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your PCI scope depends on how your system handles cardholder data. Paradise Gateway supports several approaches."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"img","attributes":{"src":"https://files.modern-mermaid.live/images/1776277862916-mermaid-diagram-1776277863007.png","alt":"image"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Approach"},"children":["Approach"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"PCI scope"},"children":["PCI scope"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Card data touches your server?"},"children":["Card data touches your server?"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Complexity"},"children":["Complexity"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Server-side tokenization"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["SAQ D"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Yes — briefly, during tokenization"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Medium"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Client-side direct post"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["SAQ A-EP"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["No — browser posts directly to API"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Low"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Hosted payment page"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["SAQ A"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["No — card form hosted by gateway"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Lowest"]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"approach-1-server-side-tokenization-saq-d","__idx":2},"children":["Approach 1: Server-side tokenization (SAQ D)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["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."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"img","attributes":{"src":"https://files.modern-mermaid.live/images/1776277891601-mermaid-diagram-1776277891728.png","alt":"image"},"children":[]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"implementation","__idx":3},"children":["Implementation"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python (Flask)","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"from flask import Flask, request, jsonify\nimport requests, os\n\napp = Flask(__name__)\nAPI_KEY = os.environ[\"PARADISE_API_KEY\"]\nBASE = os.environ.get(\"PARADISE_BASE_URL\", \"https://api.dev.paradisegateway.net\")\nHEADERS = {\"Content-Type\": \"application/json\", \"APIKEY\": API_KEY}\n\n@app.route(\"/checkout\", methods=[\"POST\"])\ndef checkout():\n    data = request.json\n\n    token_resp = requests.post(f\"{BASE}/v1/payment_tokens\", headers=HEADERS, json={\n        \"customer_id\": data[\"customer_id\"],\n        \"card\": {\n            \"pan\": data[\"pan\"],\n            \"expiry_month\": data[\"expiry_month\"],\n            \"expiry_year\": data[\"expiry_year\"],\n            \"cvv\": data[\"cvv\"],\n            \"cardholder_name\": data[\"cardholder_name\"],\n        },\n    })\n    if token_resp.status_code != 200:\n        return jsonify({\"error\": \"Tokenization failed\"}), 400\n\n    token = token_resp.json()[\"token\"]\n\n    sale_resp = requests.post(f\"{BASE}/v1/transactions\", headers=HEADERS, json={\n        \"type\": \"card_sale\",\n        \"amount\": data[\"amount\"],\n        \"payment_token\": token,\n    })\n    result = sale_resp.json()\n    return jsonify({\n        \"status\": result[\"response_status\"],\n        \"transaction_id\": result[\"id\"],\n    })\n","lang":"python"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript (Express)","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"const express = require(\"express\");\nconst app = express();\napp.use(express.json());\n\nconst API_KEY = process.env.PARADISE_API_KEY;\nconst BASE = process.env.PARADISE_BASE_URL || \"https://api.dev.paradisegateway.net\";\nconst headers = { \"Content-Type\": \"application/json\", \"APIKEY\": API_KEY };\n\napp.post(\"/checkout\", async (req, res) => {\n  const { customer_id, pan, expiry_month, expiry_year, cvv, cardholder_name, amount } = req.body;\n\n  const tokenResp = await fetch(`${BASE}/v1/payment_tokens`, {\n    method: \"POST\",\n    headers,\n    body: JSON.stringify({\n      customer_id,\n      card: { pan, expiry_month, expiry_year, cvv, cardholder_name },\n    }),\n  });\n  if (!tokenResp.ok) return res.status(400).json({ error: \"Tokenization failed\" });\n  const { token } = await tokenResp.json();\n\n  const saleResp = await fetch(`${BASE}/v1/transactions`, {\n    method: \"POST\",\n    headers,\n    body: JSON.stringify({ type: \"card_sale\", amount, payment_token: token }),\n  });\n  const result = await saleResp.json();\n  res.json({ status: result.response_status, transaction_id: result.id });\n});\n","lang":"javascript"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"saq-d-requirements-checklist","__idx":4},"children":["SAQ D requirements checklist"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Encrypt all cardholder data in transit (TLS 1.2+)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Never store raw PAN, CVV, or track data"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Implement access controls and logging"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Regularly scan for vulnerabilities"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Maintain a written security policy"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"approach-2-client-side-direct-post-saq-a-ep","__idx":5},"children":["Approach 2: Client-side direct post (SAQ A-EP)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The browser sends card data directly to Paradise Gateway. Your server never sees the raw card details — it only receives the token."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"img","attributes":{"src":"https://files.modern-mermaid.live/images/1776277921324-mermaid-diagram-1776277921484.png","alt":"image"},"children":[]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"implementation-javascript","__idx":6},"children":["Implementation (JavaScript)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"html","header":{"controls":{"copy":{}}},"source":"<form id=\"checkout-form\">\n  <input name=\"pan\" placeholder=\"4111 1111 1111 1111\" required />\n  <input name=\"expiry_month\" placeholder=\"MM\" required />\n  <input name=\"expiry_year\" placeholder=\"YYYY\" required />\n  <input name=\"cvv\" placeholder=\"123\" required />\n  <input name=\"cardholder_name\" placeholder=\"John Doe\" required />\n  <button type=\"submit\">Pay $19.99</button>\n</form>\n\n<script>\ndocument.getElementById(\"checkout-form\").addEventListener(\"submit\", async (e) => {\n  e.preventDefault();\n  const form = new FormData(e.target);\n\n  // Step 1: Tokenize directly with Paradise Gateway\n  const tokenResp = await fetch(\"https://api.dev.paradisegateway.net/v1/payment_tokens\", {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/json\", \"APIKEY\": \"PUBLIC_TOKENIZATION_KEY\" },\n    body: JSON.stringify({\n      card: {\n        pan: form.get(\"pan\"),\n        expiry_month: form.get(\"expiry_month\"),\n        expiry_year: form.get(\"expiry_year\"),\n        cvv: form.get(\"cvv\"),\n        cardholder_name: form.get(\"cardholder_name\"),\n      },\n    }),\n  });\n  const { token } = await tokenResp.json();\n\n  // Step 2: Send token to your server\n  const orderResp = await fetch(\"/checkout\", {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/json\" },\n    body: JSON.stringify({ token, amount: 1999 }),\n  });\n  const result = await orderResp.json();\n  alert(result.status === \"approved\" ? \"Payment successful!\" : \"Payment failed.\");\n});\n</script>\n","lang":"html"},"children":[]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"CORS and key scope"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["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."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"security-checklist","__idx":7},"children":["Security checklist"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Regardless of which approach you choose:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Requirement"},"children":["Requirement"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Details"},"children":["Details"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["TLS everywhere"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["All pages that collect or display payment data must use HTTPS"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["No PAN in logs"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Never log full card numbers — mask to last four digits"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["No CVV storage"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Never store CVV anywhere, for any duration"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Content Security Policy"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Restrict script sources to prevent XSS that could steal card data"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Subresource Integrity"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Pin hashes on third-party scripts loaded on payment pages"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Input validation"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Luhn-check card numbers, validate expiry dates client-side before submission"]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"decision-guide","__idx":8},"children":["Decision guide"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"img","attributes":{"src":"https://files.modern-mermaid.live/images/1776277959908-mermaid-diagram-1776277960024.png","alt":"image"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Factor"},"children":["Factor"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Server-side"},"children":["Server-side"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Client-side"},"children":["Client-side"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Hosted"},"children":["Hosted"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["PCI scope"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["SAQ D (full)"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["SAQ A-EP (reduced)"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["SAQ A (minimal)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["UX control"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Full"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Full"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Limited"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Implementation effort"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Medium"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Low"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Lowest"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Card data on your server"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Briefly"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Never"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Never"]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"best-practices","__idx":9},"children":["Best practices"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Tokenize immediately"]}," — if card data touches your server, tokenize in the same request and discard the raw data."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Use separate keys"]}," — if client-side tokenization is available, use a restricted key that can only create tokens."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Test with sandbox"]}," — verify your PCI-compliant flow works end-to-end before going to production."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Audit annually"]}," — complete your SAQ annually and maintain documentation of your data flow."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Review CSP headers"]}," — ensure your Content Security Policy allows connections to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["api.dev.paradisegateway.net"]}," (sandbox) and the production endpoint."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"further-reading","__idx":10},"children":["Further reading"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/concepts/security-compliance/pci-dss-overview"},"children":["PCI-DSS overview"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/concepts/security-compliance/data-protection"},"children":["Data protection"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/tutorials/build-checkout-page"},"children":["Build a checkout page"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/how-tos/tokenization-vaulting/store-card"},"children":["Store a card for future use"]}]}]}]},"headings":[{"value":"Tutorial: PCI-compliant checkout","id":"tutorial-pci-compliant-checkout","depth":1},{"value":"Choose your integration approach","id":"choose-your-integration-approach","depth":2},{"value":"Approach 1: Server-side tokenization (SAQ D)","id":"approach-1-server-side-tokenization-saq-d","depth":2},{"value":"Implementation","id":"implementation","depth":3},{"value":"SAQ D requirements checklist","id":"saq-d-requirements-checklist","depth":3},{"value":"Approach 2: Client-side direct post (SAQ A-EP)","id":"approach-2-client-side-direct-post-saq-a-ep","depth":2},{"value":"Implementation (JavaScript)","id":"implementation-javascript","depth":3},{"value":"Security checklist","id":"security-checklist","depth":2},{"value":"Decision guide","id":"decision-guide","depth":2},{"value":"Best practices","id":"best-practices","depth":2},{"value":"Further reading","id":"further-reading","depth":2}],"frontmatter":{"title":"Tutorial: PCI-compliant checkout","shortTitle":"PCI-compliant checkout","intro":"Design a checkout flow that minimizes your PCI-DSS scope while providing a seamless payment experience. Choose the right integration approach for your compliance level.","type":"tutorial","seo":{"title":""}},"lastModified":"2026-06-05T02:46:44.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/tutorials/integration-patterns/pci-compliant-checkout","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}