{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-docs/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["admonition","tabs","tab"]},"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-build-a-checkout-page","__idx":0},"children":["Tutorial: Build a checkout page"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"what-you-will-learn","__idx":1},"children":["What you will learn"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Collect payment details in a form"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Tokenize the card to reduce PCI scope"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Process a sale using the token"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Display the result to the customer"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"prerequisites","__idx":2},"children":["Prerequisites"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A sandbox API key — see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/tutorials/first-api-call"},"children":["Your first API call"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Basic HTML/JavaScript knowledge"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"architecture-overview","__idx":3},"children":["Architecture overview"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"img","attributes":{"src":"https://files.modern-mermaid.live/images/1776276685362-mermaid-diagram-1776276685566.png","alt":"image"},"children":[]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"PCI scope"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["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 ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/concepts/security-compliance/pci-dss-overview"},"children":["PCI-DSS overview"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-1-build-the-checkout-form","__idx":4},"children":["Step 1: Build the checkout form"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create a simple HTML form that collects the card number, expiry, CVV, and cardholder name."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"html","header":{"controls":{"copy":{}}},"source":"<form id=\"checkout-form\">\n  <label>Cardholder name\n    <input name=\"name\" placeholder=\"John Doe\" required />\n  </label>\n  <label>Card number\n    <input name=\"pan\" placeholder=\"4111 1111 1111 1111\" maxlength=\"19\" required />\n  </label>\n  <label>Expiry\n    <input name=\"expiry_month\" placeholder=\"MM\" maxlength=\"2\" required />\n    <input name=\"expiry_year\" placeholder=\"YYYY\" maxlength=\"4\" required />\n  </label>\n  <label>CVV\n    <input name=\"cvv\" placeholder=\"123\" maxlength=\"4\" required />\n  </label>\n  <label>Amount ($)\n    <input name=\"amount\" type=\"number\" step=\"0.01\" min=\"0.50\" value=\"19.99\" required />\n  </label>\n  <button type=\"submit\">Pay now</button>\n</form>\n","lang":"html"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-2-submit-to-your-server","__idx":5},"children":["Step 2: Submit to your server"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["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."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"document.getElementById(\"checkout-form\").addEventListener(\"submit\", async (e) => {\n  e.preventDefault();\n  const form = new FormData(e.target);\n  const resp = await fetch(\"/api/checkout\", {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/json\" },\n    body: JSON.stringify({\n      name: form.get(\"name\"),\n      pan: form.get(\"pan\").replace(/\\s/g, \"\"),\n      expiry_month: form.get(\"expiry_month\"),\n      expiry_year: form.get(\"expiry_year\"),\n      cvv: form.get(\"cvv\"),\n      amount: Math.round(parseFloat(form.get(\"amount\")) * 100),\n    }),\n  });\n  const result = await resp.json();\n  if (result.response_status === \"approved\") {\n    document.body.innerHTML = \"<h1>Payment successful!</h1><p>Transaction: \" + result.id + \"</p>\";\n  } else {\n    document.body.innerHTML = \"<h1>Payment failed</h1><p>\" + result.response_message + \"</p>\";\n  }\n});\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-3-tokenize-the-card-server-side","__idx":6},"children":["Step 3: Tokenize the card (server-side)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["On your server, first tokenize the card. This stores it securely and returns a reusable ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment_token"]},"."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import os, requests\n\nAPI_KEY = os.environ[\"PARADISE_API_KEY\"]\nBASE = \"https://api.dev.paradisegateway.net\"\n\ndef tokenize_card(name, pan, expiry_month, expiry_year):\n    resp = requests.post(\n        f\"{BASE}/v1/payment_tokens\",\n        headers={\"Content-Type\": \"application/json\", \"APIKEY\": API_KEY},\n        json={\n            \"customer_id\": \"CUSTOMER-01KFDKXMQ637EKEAY410MSQSXB\",\n            \"nickname\": name,\n            \"card\": {\n                \"pan\": pan,\n                \"expiry_month\": expiry_month,\n                \"expiry_year\": expiry_year,\n                \"cardholder_name\": name,\n            },\n        },\n    )\n    resp.raise_for_status()\n    return resp.json()[\"payment_token\"]\n","lang":"python"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"const API_KEY = process.env.PARADISE_API_KEY;\nconst BASE = \"https://api.dev.paradisegateway.net\";\n\nasync function tokenizeCard(name, pan, expiryMonth, expiryYear) {\n  const resp = await fetch(`${BASE}/v1/payment_tokens`, {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/json\", \"APIKEY\": API_KEY },\n    body: JSON.stringify({\n      customer_id: \"CUSTOMER-01KFDKXMQ637EKEAY410MSQSXB\",\n      nickname: name,\n      card: {\n        pan, expiry_month: expiryMonth, expiry_year: expiryYear,\n        cardholder_name: name,\n      },\n    }),\n  });\n  const data = await resp.json();\n  return data.payment_token;\n}\n","lang":"javascript"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-4-process-the-sale-server-side","__idx":7},"children":["Step 4: Process the sale (server-side)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use the token to charge the customer. This keeps raw card data out of the transaction request."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"def process_sale(payment_token, amount):\n    resp = requests.post(\n        f\"{BASE}/v1/transactions\",\n        headers={\"Content-Type\": \"application/json\", \"APIKEY\": API_KEY},\n        json={\n            \"type\": \"sale\",\n            \"amount\": amount,\n            \"payment_method\": {\n                \"type\": \"payment_token\",\n                \"payment_token\": payment_token,\n            },\n        },\n    )\n    resp.raise_for_status()\n    return resp.json()\n","lang":"python"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"async function processSale(paymentToken, amount) {\n  const resp = await fetch(`${BASE}/v1/transactions`, {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/json\", \"APIKEY\": API_KEY },\n    body: JSON.stringify({\n      type: \"sale\",\n      amount,\n      payment_method: {\n        type: \"payment_token\",\n        payment_token: paymentToken,\n      },\n    }),\n  });\n  return resp.json();\n}\n","lang":"javascript"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-5-handle-the-result","__idx":8},"children":["Step 5: Handle the result"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Check ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["response_status"]}," to determine what to show the customer."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"# Flask example\nfrom flask import Flask, request, jsonify\napp = Flask(__name__)\n\n@app.route(\"/api/checkout\", methods=[\"POST\"])\ndef checkout():\n    data = request.json\n    token = tokenize_card(\n        data[\"name\"], data[\"pan\"],\n        data[\"expiry_month\"], data[\"expiry_year\"],\n    )\n    result = process_sale(token, data[\"amount\"])\n\n    if result[\"response_status\"] == \"approved\":\n        return jsonify({\"response_status\": \"approved\", \"id\": result[\"id\"]})\n    else:\n        return jsonify({\n            \"response_status\": result[\"response_status\"],\n            \"response_message\": result.get(\"response_message\", \"Payment failed\"),\n        }), 400\n","lang":"python"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// Express example\napp.post(\"/api/checkout\", async (req, res) => {\n  const { name, pan, expiry_month, expiry_year, amount } = req.body;\n  const token = await tokenizeCard(name, pan, expiry_month, expiry_year);\n  const result = await processSale(token, amount);\n\n  if (result.response_status === \"approved\") {\n    res.json({ response_status: \"approved\", id: result.id });\n  } else {\n    res.status(400).json({\n      response_status: result.response_status,\n      response_message: result.response_message || \"Payment failed\",\n    });\n  }\n});\n","lang":"javascript"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"complete-flow-diagram","__idx":9},"children":["Complete flow diagram"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"img","attributes":{"src":"https://files.modern-mermaid.live/images/1776276735036-mermaid-diagram-1776276735231.png","alt":"image"},"children":[]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"security-checklist","__idx":10},"children":["Security checklist"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," API key stored in environment variable, not in client-side code"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," HTTPS on your checkout page"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Raw PAN never logged or stored on your server"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Token used for payment instead of raw card data"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Error messages do not expose internal details to the customer"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"next-steps","__idx":11},"children":["Next steps"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/tutorials/payment-cookbook/auth-capture-ecommerce"},"children":["Auth/capture for e-commerce"]}," — delay capture until fulfillment"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/tutorials/payment-cookbook/handle-declined-transactions"},"children":["Handle declined transactions"]}]},{"$$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"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/concepts/security-compliance/pci-dss-overview"},"children":["PCI-DSS overview"]}]}]}]},"headings":[{"value":"Tutorial: Build a checkout page","id":"tutorial-build-a-checkout-page","depth":1},{"value":"What you will learn","id":"what-you-will-learn","depth":2},{"value":"Prerequisites","id":"prerequisites","depth":2},{"value":"Architecture overview","id":"architecture-overview","depth":2},{"value":"Step 1: Build the checkout form","id":"step-1-build-the-checkout-form","depth":2},{"value":"Step 2: Submit to your server","id":"step-2-submit-to-your-server","depth":2},{"value":"Step 3: Tokenize the card (server-side)","id":"step-3-tokenize-the-card-server-side","depth":2},{"value":"Step 4: Process the sale (server-side)","id":"step-4-process-the-sale-server-side","depth":2},{"value":"Step 5: Handle the result","id":"step-5-handle-the-result","depth":2},{"value":"Complete flow diagram","id":"complete-flow-diagram","depth":2},{"value":"Security checklist","id":"security-checklist","depth":2},{"value":"Next steps","id":"next-steps","depth":2}],"frontmatter":{"title":"Tutorial: Build a checkout page","shortTitle":"Build a checkout page","intro":"Build an end-to-end checkout flow for a merchant website — collect card details, tokenize them, process the payment, and handle the result.","type":"tutorial","seo":{"title":""}},"lastModified":"2026-06-05T02:46:44.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/tutorials/build-checkout-page","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}