{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["steps","tabs","tab"]},"type":"markdown"},"seo":{"title":"Checkout","siteUrl":"https://docs.glomo.one","llmstxt":{"title":"Glomo developer documentation","description":"Cross-border payments APIs, organised by product. Each product section carries both the explanation of its objects and the job-shaped integration guides that set them up.","details":{"content":"How to use these docs:\n\n- Get started is the front door. It has one section per integration\n  (what you're building), each with the API calls in order and the guide that builds it, plus\n  how to get API keys.\n- The product sections - Payin, Payout, Multi-currency accounts,\n  Verification, Request for Information (RFI) - each hold two kinds of page.\n  Pages titled as a noun (\"Subscriptions\", \"Bank Transfer\") explain what\n  an object is. Pages titled as a job (\"Set up recurring subscriptions\",\n  \"Collect LRS remittances from India\") are complete integration flows\n  and live one level under the object they set up. Match a user's goal\n  against the job-shaped titles first.\n- Reports and Identity and Access Management are the two subjects with no\n  API counterpart. There are no endpoints for reports, users, roles or\n  invites; both pages document the merchant dashboard. A dashboard\n  procedure for something the API also does (a refund, a payout) is NOT\n  here - it stays in the product that owns it.\n- API reference is generated from OpenAPI and carries endpoint detail.\n- The OpenAPI description itself is published as a single self-contained\n  document at /openapi.yaml, and the same document as JSON at\n  /openapi.json. Every $ref is resolved into it, so one fetch is the\n  whole contract: 61 paths, 81 operations, 68 schemas - the same\n  endpoints the API reference pages render. Fetch that instead of\n  reading the 129 pages under /api-reference/.\n- Every page below, in full, is one document at /llms-full.txt. Fetch that\n  instead of the pages one by one. It leaves out the API reference;\n  /openapi.yaml is that.\n- Developer resources covers what every integration touches and no single\n  product owns: auth, errors, pagination, rate limits, webhooks,\n  versioning, supported countries, quotes, the MCP server for AI\n  agents, and the client SDKs.\n"},"sections":[{"title":"Get started","description":"The front door. The ordered API calls for each integration and the guide that delivers it, how to obtain API keys, and the platform glossary.","includeFiles":["**/get-started/**"]},{"title":"Payin","description":"Collecting money: customers, orders, checkout, payment links, payment methods (cards, bank transfer, pay via bank), refunds, disputes, purpose codes, fee models, custom fields, subscriptions, and resident India remittance under LRS. Includes the flows that set each up - bank transfers, subscriptions, LRS remittances, and server-to-server card payments.","includeFiles":["**/payin/**"]},{"title":"Payout","description":"Sending money: the payout life cycle, queued payouts, creating and cancelling a payout, rails, beneficiaries, purpose codes, and the action required state. Includes the flow that sends a payout over the API.","includeFiles":["**/payout/**"]},{"title":"Multi-currency accounts","description":"Holding money: balances, balance conversion, adding balance, and withdrawing balance.","includeFiles":["**/multi-currency-account/**"]},{"title":"Verification","description":"Proving who someone is and that an account is theirs: KYC, bank account validation, and third-party verification (TPV) with the flow that verifies a payer's bank account.","includeFiles":["**/verification/**"]},{"title":"Request for Information (RFI)","description":"Responding to a compliance request for information, per object - payment link, order, payment, payout - plus compliance reviews and RFIs raised on already-successful payments.","includeFiles":["**/request-for-information/**"]},{"title":"Nivo (Capital markets)","description":"Nivo, Glomo's capital markets suite (Nivo for AMCs, Nivo for Distributors, and overseas investing for brokers and wealth platforms). Mutual fund investments run as payment sessions: the fund, distributor, scheme, plan, ARN and folio roles; the distributor's relationship with a fund and what it fixes; and the flow that opens a session as a distributor.","includeFiles":["**/capital-markets/**"]},{"title":"Reports","description":"The downloadable and scheduled reports a merchant pulls from the dashboard for reconciliation, compliance and treasury: payment, subscription, settlement breakup, balance statement and KYC reports, plus scheduling one for recurring email delivery and customizing the fields and column order of a report. Dashboard only - there is no reports API.","includeFiles":["**/reports/**"]},{"title":"Identity and Access Management","description":"Administering who can use a merchant account: one set of credentials across multiple MIDs, switching between them without logging out, the Admin / Member / Sales Partner roles and what each may see and do, and inviting a new member. Dashboard only - there is no users, roles or invites API.","includeFiles":["**/access-management/**"]},{"title":"Settlement holidays","description":"The settlement holiday calendar for 2026 - the Indian and US bank and FX holidays on which payin, payout and settlement to the merchant's bank account may be delayed.","includeFiles":["**/settlement-holidays/**"]},{"title":"API reference","description":"Endpoint reference generated from the Glomo OpenAPI description.","includeFiles":["**/api-reference/**"]},{"title":"Developer resources","description":"What every integration touches and no single product owns: auth, errors, pagination, rate limits, webhooks, versioning, API validations, security, supported countries, quotes, connecting an AI agent to the Glomo MCP server, and the client SDKs - React Native, Flutter, Android and the Unified Web SDK, with per-version references and changelogs. Reports and Identity and Access Management used to be listed here and are now their own sections.","includeFiles":["**/platform/**"]}]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"checkout","__idx":0},"children":["Checkout"]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This guide uses the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/unified-sdk"},"children":["Glomo Unified SDK"]}]}," — see it for the full set of journeys and options. To open checkout inside a native mobile app, see ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/experimental"},"children":["Web Checkout in a Mobile App (WebView)"]}]},". If you integrated earlier with the older ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["checkout-sdk"]},", it continues to work — switch the import to the Unified SDK when convenient."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Migrating from the legacy ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["checkout-sdk"]},"?"]}," Two behaviour differences to note:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["When ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callbackUrl"]}," is set, the Unified SDK ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["only redirects"]}," — it no longer also fires your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".on('payment.success')"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".on('payment.failure')"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".on('payment.bank_transfer_submitted')"]}," handlers (the legacy SDK did both). ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["checkout.closed"]}," is unaffected."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["on()"]}," now returns an unsubscribe function (call it to remove the listener); the legacy SDK returned nothing."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Glomo Checkout offers a prebuilt payment form that enables businesses to securely accept payments online. With its"," ","built-in features, you can minimize development time and streamline the payment process. Embed Checkout directly into"," ","your website, or direct customers to a Glomo-hosted payment page to start accepting payments."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Checkout requires minimal coding because of its prebuilt functionalities and customization options. You can integrate"," ","Checkout by creating a Checkout Session and collecting the customer’s payment details."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"img","attributes":{"src":"/assets/checkout-ui.6887719d68bf3490e4f19f6709d7b0af3bdad8399f5e2759b211cddfc6e22544.9c1bb791.png","alt":"Checkout Interface"},"children":[]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"end-to-end-flow-in-accepting-payments","__idx":1},"children":["End-to-End Flow in Accepting Payments"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Below are the steps that are to accept payments :-"]},{"$$mdtype":"Tag","name":"Steps","attributes":{"steps":["Once the **customer initiates payment** on the merchant’s website - Create an order by making server to server call","**Use order ID** to involve the checkout form","User selects a **payment option** on checkout and enters relevant information - Collect Payment details","**Glomo authenticates** the payment information","**Verify Payment status** returned and redirect the user to the payment confirmation page"]},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"visual-representation-of-payment-flow","__idx":2},"children":["Visual Representation of Payment Flow"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A visual representation for the sequence of steps is given below :-"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"img","attributes":{"src":"/assets/checkout-flow.44e1bf4f1b557b36ae6c093796e82f6da4d385aba22cfce7e50343638a528c1e.9c1bb791.png","alt":"Payment Flow Diagram"},"children":[]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"steps-to-integrate-checkout","__idx":3},"children":["Steps to integrate Checkout"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Note:"]}," We recommend using ES module imports for the Glomo Checkout SDK. While UMD format is still supported for legacy compatibility, it is not actively maintained. For optimal performance and future support, please use ES modules. If you absolutely require UMD format, please contact our support team for assistance."]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["1. ",{"$$mdtype":"Tag","name":"b","attributes":{},"children":["Create an Order in Server"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["You will have to create an order from your server using the secret keys."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["You can get the secret key on your glomopay ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://app.glomopay.com/api-keys-and-webhooks/api-keys"},"children":["dashboard"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["You can create order by integrating our ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/api-reference/openapi/orders/createorder"},"children":["Order APIs"]},".It"," ","should be a server side api call. Refer ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/authentication"},"children":["Authentication"]}," for details around"," ","how to authenticate Order's API."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["order_id"]}," received in the response should be passed to the checkout. This ties the order with the payment and"," ","secures the request from being tampered."]}]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["2. ",{"$$mdtype":"Tag","name":"b","attributes":{},"children":["Add payment button on client"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Add a buy button on your website."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["You can integrate the checkout by adding the below code snippet to your website."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The code snippet will create a checkout instance and open the checkout form when the user clicks on the buy button."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["orderId"]}," of the order created via your server in step 1 should be passed to the checkout instance."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["publicKey"]}," refers to the public key of your Glomo account present on your ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://app.glomopay.com/api-keys-and-webhooks/api-keys"},"children":["dashboard"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Handle the payment success and failure events, in the handlers function you will get payment object or error object based on the payment status. Collect these and send them to your server and retry accordingly in case payment failed."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["For bank transfer payments, a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.bank_transfer_submitted"]}," event is emitted when the user submits their payment details on the checkout. ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["This event only indicates that the user has submitted payment details; it does not confirm that the payment has been completed or verified"]}," — please wait for webhooks to determine the final payment status."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Additionally, you can also provide a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callbackUrl"]}," to redirect the user after payment completion. If you provide a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callbackUrl"]},", the user will be redirected to that URL after payment completion with the payment status and other details in the query parameters."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callbackUrl"]}," is provided,failure and success events will not be triggered in the checkout instance."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Checkout opens as an overlay on your page. To detect a customer closing it without paying, listen for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["checkout.closed"]}," (see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/sdk/unified-sdk#handling-abandoned-checkouts"},"children":["Handling Abandoned Checkouts"]},"). It fires whether or not you set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callbackUrl"]},"."]}]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"HTML","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"html","header":{"controls":{"copy":{}}},"source":"  <!-- your-page.html -->\n  <button id=\"buy-button\" class=\"buy-button\">Buy Now</button>\n\n  <!-- Load the integration from a file so a strict CSP does not need 'unsafe-inline'. -->\n  <script type=\"module\" src=\"/glomo-checkout.js\"></script>\n","lang":"html"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"  // glomo-checkout.js\n  import { GlomoCheckoutApi } from 'https://unified-sdk.glomopay.com/index.js';\n\n  const checkout = new GlomoCheckoutApi({\n    orderId: 'order_679a16457aP6K', // Pass the order_id received from the server in step 1\n    publicKey: 'live_687b0151Bid24PAI', // Pass the public key received from the dashboard\n    callbackUrl: 'https://yourwebsite.com/payment-result', // Optional: URL to redirect after payment completion\n  });\n\n  document.getElementById('buy-button').addEventListener('click', () => {\n    checkout.open();\n  });\n\n  checkout.on('payment.success', function (response) {\n    // Handle payment success\n  });\n\ncheckout.on('payment.failure', function (response) {\n    // Handle payment failure\n  });\n\n  // Bank transfer only: emitted when the user submits their payment details.\n  // Does NOT mean the payment is successful — await a webhook for confirmation.\n  checkout.on('payment.bank_transfer_submitted', function (response) {\n    // response.orderId - the order ID\n  });\n\n  // Detect an abandoned checkout. The checkout opens as an overlay on your page and\n  // emits checkout.closed when the user closes it, including after the success or\n  // failure screen. The payment events fire before it, so track whether one arrived.\n  // With callbackUrl set, the success and failure handlers don't run, but the page is\n  // redirected on those outcomes instead, so checkout.closed only fires if the user\n  // left before finishing.\n  let paymentCompleted = false;\n\n  checkout.on('payment.success', function (response) {\n    paymentCompleted = true;\n    // Handle payment success\n  });\n\n  checkout.on('payment.failure', function (response) {\n    paymentCompleted = true;\n    // Handle payment failure\n  });\n\n  checkout.on('payment.bank_transfer_submitted', function (response) {\n    paymentCompleted = true; // Details submitted, not abandoned. Await a webhook for the result.\n  });\n\n  checkout.on('checkout.closed', function () {\n    if (!paymentCompleted) {\n      // User closed the checkout without completing payment.\n      // Use this to restore your UI, log abandonment, or prompt the user to retry.\n      console.log('Checkout closed without payment');\n    }\n    paymentCompleted = false; // Reset for the next checkout attempt\n  });\n","lang":"js"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"React (TS)","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"jsx","header":{"controls":{"copy":{}}},"source":"  import { FC, useState } from \"react\";\n\n  interface PurchaseButtonProps {\n    orderId: string;\n    publicKey: string;\n  }\n\n  const PurchaseButton: FC<PurchaseButtonProps> = ({ orderId, publicKey }) => {\n    const buttonRef = useRef<HTMLButtonElement>(null);\n\n    const loadCheckout = async () => {\n      try {\n        const { GlomoCheckoutApi } = await import('https://unified-sdk.glomopay.com/index.js');\n\n        // With callback URL option - user will be redirected after payment\n        const checkout = new GlomoCheckoutApi({\n          orderId,\n          publicKey,\n          callbackUrl: 'https://yourwebsite.com/payment-result', // Optional\n        });\n\n        // Set up the click handler on the button\n        if (buttonRef.current) {\n          buttonRef.current.addEventListener('click', checkout.open.bind(checkout));\n        }\n\n        // Listen for payment success\n        checkout.on('payment.success', function () {\n          // Handle payment success (e.g., update state or notify the user)\n        });\n\n        // Listen for payment failure\n        checkout.on('payment.failure', function (response: unknown) {\n          console.error('Payment failed:', response);\n          // Handle payment failure (e.g., show an error message)\n        });\n\n        // Bank transfer only: emitted when the user submits their payment details.\n        // Does NOT mean the payment is successful — await a webhook for confirmation.\n        checkout.on('payment.bank_transfer_submitted', function (response: unknown) {\n          // Handle bank transfer submission (e.g., show a pending state to the user)\n        });\n      } catch (error) {\n        console.error('Error loading the checkout SDK:', error);\n      }\n    };\n\n    useEffect(() => {\n      loadCheckout();\n    }, []);\n\n    return (\n      <button id='buy-button' ref={buttonRef}>\n        Pay now\n      </button>\n    )\n  }\n","lang":"jsx"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"about-the-callback-url","__idx":4},"children":["About the Callback URL"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When you provide a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callbackUrl"]},", the user will be automatically redirected to that URL after payment completion with the following parameters:"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["For successful payments:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"https://yourwebsite.com/payment-result?orderId=order_679a16457aP6K&paymentId=payt_679a16457aP6K&status=success&signature=701eab675aca32c14cd1e5e041842d11ee9d04e91a3aaa73c629664a336b2952\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["For failed payments:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"https://yourwebsite.com/payment-result?orderId=order_679a16457aP6K&paymentId=payt_679a16457aP6K&status=failed&errorCode=INVALID_CARD&errorMessage=Payment+failed+due+to+the+card+being+invalid%3B+please+use+a+different+card.\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A failed redirect carries no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["signature"]},", so don't trust its ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status"]}," on its own. Look the payment up on your server."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This allows server-side handling of payment results in addition to the client-side event handlers."]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["3. ",{"$$mdtype":"Tag","name":"b","attributes":{},"children":["Verify checkout response and signature in server"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.success"]}," handler receives an object with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["orderId"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["paymentId"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["signature"]},". The keys are camelCase."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["signature"]}," lets you check that the payment object came from Glomo. Send ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["paymentId"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["signature"]}," to your server and verify the signature there. Never verify in the browser."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["To verify, sign ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["orderId|paymentId|status"]},", using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["paymentId"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status"]}," from the event and the order ID you created in step 1, not the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["orderId"]}," from the browser."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The signature is the hex HMAC-SHA256 of that string. Compute it with each of your active secret keys, and accept the response if any one of them matches the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["signature"]}," you received. Your active secret keys are every secret key on your account that has not expired. Use a constant-time comparison."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The signature may be made with any one of your active secret keys, and it can differ between payments. Don't assume it is your newest key. If you hold more than one active key, checking only one of them can reject a genuine payment."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["When you revoke a key, or it expires, remove it from your list. A stale key left in the list opens no security hole, but keep the list matching the keys in your dashboard."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.failure"]}," event carries no signature. Don't treat it as final. Confirm the payment status on your server (see step 4)."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"cards","__idx":5},"children":["Cards"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For card payments, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.success"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.failure"]}," events are triggered synchronously once the payment is processed."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Success Response"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n    \"status\": \"success\",\n    \"orderId\": \"order_679a16457aP6K\",\n    \"paymentId\": \"payt_679a16457aP6K\",\n    \"signature\": \"701eab675aca32c14cd1e5e041842d11ee9d04e91a3aaa73c629664a336b2952\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Failure Response"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["error.code"]}," is the payment's error code, and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["error.message"]}," is the message shown to the customer. When Glomo has no specific reason, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["error.code"]}," is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"Failed\""]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["error.message"]}," is a generic message. There is no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["signature"]},"."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n    \"orderId\": \"order_679a16457aP6K\",\n    \"paymentId\": \"payt_679a16457aP6K\",\n    \"status\": \"failed\",\n    \"error\": {\n        \"code\": \"INVALID_CARD\",\n        \"message\": \"Payment failed due to the card being invalid; please use a different card.\"\n    }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"bank-transfer","__idx":6},"children":["Bank Transfer"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Bank transfer payments follow an asynchronous flow. The customer sends the transfer from their own bank, and Glomo matches it to the order later, often after the checkout is closed. Checkout events for bank transfers are ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["not signed"]},", so you cannot verify them with the signature recipe below. Confirm the payment on your server with a verified ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/webhooks"},"children":["webhook"]},", or call ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/api-reference/openapi/orders/getorderbyid"},"children":["Retrieve an order"]}," and check for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status: paid"]},". For the full flow, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/payin/payment-methods/bank-transfer/set-up"},"children":["Set up bank transfers"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Bank Transfer Submitted (Intermediate)"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Emitted when the customer submits the details of the account they paid from. It means the customer says they sent the transfer, not that the money arrived. Use it to show a \"pending\" state. It carries no signature."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n    \"status\": \"bank_transfer_submitted\",\n    \"orderId\": \"order_679a16457aP6K\",\n    \"senderAccountNumber\": \"7890\",\n    \"senderName\": \"John Doe\",\n    \"payerRelationship\": \"\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Success Response"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Emitted only if the order becomes paid while the customer is still on the confirmation screen. For a bank transfer, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["paymentId"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["signature"]}," are empty, so this event cannot be verified. Do not mark the order paid from it."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n    \"status\": \"success\",\n    \"orderId\": \"order_679a16457aP6K\",\n    \"paymentId\": \"\",\n    \"signature\": \"\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["There is no bank-transfer failure event. A transfer that doesn't arrive, or can't be matched, shows up in webhooks, not in checkout. See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/payin/action-required"},"children":["Resolving Action Required Payments"]},"."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"em","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Sample Code to verify signature"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each sample loads your active secret keys from a comma-separated ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GLOMO_SECRET_KEYS"]}," setting in your config, and returns true if the signature matches any one of them. It trims each key, drops empty entries, and stops with an error if no key is left. An empty key must never count as a match: anyone can compute the signature for an empty key, so it would let a forged response through."]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["PHP"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"php","header":{"controls":{"copy":{}}},"source":"<?php\nfunction load_secret_keys($value) {\n    $keys = array_values(array_filter(array_map('trim', explode(',', (string) $value))));\n    if (count($keys) === 0) {\n        throw new RuntimeException('GLOMO_SECRET_KEYS is not set');\n    }\n    return $keys;\n}\n\nfunction verify_signature($order_id, $payment_id, $status, $signature, array $secret_keys) {\n    $data = $order_id . \"|\" . $payment_id . \"|\" . $status;\n    foreach ($secret_keys as $secret) {\n        if ((string) $secret === '') {\n            continue; // an empty key never matches\n        }\n        if (hash_equals(hash_hmac('sha256', $data, $secret), (string) $signature)) {\n            return true;\n        }\n    }\n    return false;\n}\n\n// Example Usage\n$order_id = \"order_679a16457aP6K\"; // the order ID you created in step 1\n$payment_id = \"payt_679a16457aP6K\";\n$status = \"success\";\n$signature = \"e91071c1f6226faac21db65e18f45f3c93e8468ff2d67528f4be3e4412ab0cb7\"; // the signature you received\n$secret_keys = load_secret_keys(getenv('GLOMO_SECRET_KEYS')); // every active secret key, e.g. \"your_secret_key_1,your_secret_key_2\"\n\nvar_dump(verify_signature($order_id, $payment_id, $status, $signature, $secret_keys));\n?>\n","lang":"php"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Ruby"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ruby","header":{"controls":{"copy":{}}},"source":"require 'openssl'\n\ndef load_secret_keys(value)\n  keys = value.to_s.split(',').map(&:strip).reject(&:empty?)\n  raise 'GLOMO_SECRET_KEYS is not set' if keys.empty?\n  keys\nend\n\ndef verify_signature(order_id, payment_id, status, signature, secret_keys)\n  data = \"#{order_id}|#{payment_id}|#{status}\"\n  secret_keys.any? do |secret|\n    next false if secret.to_s.empty? # an empty key never matches\n    OpenSSL.secure_compare(OpenSSL::HMAC.hexdigest(\"SHA256\", secret, data), signature.to_s)\n  end\nend\n\n# Example Usage\norder_id = \"order_679a16457aP6K\" # the order ID you created in step 1\npayment_id = \"payt_679a16457aP6K\"\nstatus = \"success\"\nsignature = \"e91071c1f6226faac21db65e18f45f3c93e8468ff2d67528f4be3e4412ab0cb7\" # the signature you received\nsecret_keys = load_secret_keys(ENV['GLOMO_SECRET_KEYS']) # every active secret key, e.g. \"your_secret_key_1,your_secret_key_2\"\n\nputs verify_signature(order_id, payment_id, status, signature, secret_keys)\n","lang":"ruby"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Go"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"go","header":{"controls":{"copy":{}}},"source":"package main\n\nimport (\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"errors\"\n    \"fmt\"\n    \"os\"\n    \"strings\"\n)\n\nfunc loadSecretKeys(value string) ([]string, error) {\n    var keys []string\n    for _, key := range strings.Split(value, \",\") {\n        key = strings.TrimSpace(key)\n        if key != \"\" {\n            keys = append(keys, key)\n        }\n    }\n    if len(keys) == 0 {\n        return nil, errors.New(\"GLOMO_SECRET_KEYS is not set\")\n    }\n    return keys, nil\n}\n\nfunc verifySignature(orderID, paymentID, status, signature string, secretKeys []string) bool {\n    data := orderID + \"|\" + paymentID + \"|\" + status\n    for _, secret := range secretKeys {\n        if secret == \"\" {\n            continue // an empty key never matches\n        }\n        h := hmac.New(sha256.New, []byte(secret))\n        h.Write([]byte(data))\n        expected := hex.EncodeToString(h.Sum(nil))\n        if hmac.Equal([]byte(expected), []byte(signature)) {\n            return true\n        }\n    }\n    return false\n}\n\nfunc main() {\n    orderID := \"order_679a16457aP6K\" // the order ID you created in step 1\n    paymentID := \"payt_679a16457aP6K\"\n    status := \"success\"\n    signature := \"e91071c1f6226faac21db65e18f45f3c93e8468ff2d67528f4be3e4412ab0cb7\" // the signature you received\n    secretKeys, err := loadSecretKeys(os.Getenv(\"GLOMO_SECRET_KEYS\")) // every active secret key, e.g. \"your_secret_key_1,your_secret_key_2\"\n    if err != nil {\n        panic(err)\n    }\n\n    fmt.Println(verifySignature(orderID, paymentID, status, signature, secretKeys))\n}\n","lang":"go"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Javascript(Node.js)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"const crypto = require('crypto');\n\nfunction loadSecretKeys(value) {\n    const keys = (value || '').split(',').map((k) => k.trim()).filter(Boolean);\n    if (keys.length === 0) throw new Error('GLOMO_SECRET_KEYS is not set');\n    return keys;\n}\n\nfunction verifySignature(orderId, paymentId, status, signature, secretKeys) {\n    const data = `${orderId}|${paymentId}|${status}`;\n    const received = Buffer.from(signature || '');\n    return secretKeys.some((secret) => {\n        if (!secret) return false; // an empty key never matches\n        const expected = Buffer.from(crypto.createHmac('sha256', secret).update(data).digest('hex'));\n        return expected.length === received.length && crypto.timingSafeEqual(expected, received);\n    });\n}\n\n// Example Usage\nconst orderId = \"order_679a16457aP6K\"; // the order ID you created in step 1\nconst paymentId = \"payt_679a16457aP6K\";\nconst status = \"success\";\nconst signature = \"e91071c1f6226faac21db65e18f45f3c93e8468ff2d67528f4be3e4412ab0cb7\"; // the signature you received\nconst secretKeys = loadSecretKeys(process.env.GLOMO_SECRET_KEYS); // every active secret key, e.g. \"your_secret_key_1,your_secret_key_2\"\n\nconsole.log(verifySignature(orderId, paymentId, status, signature, secretKeys));\n","lang":"javascript"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Python"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import hmac\nimport hashlib\nimport os\n\ndef load_secret_keys(value):\n    keys = [key.strip() for key in (value or \"\").split(\",\") if key.strip()]\n    if not keys:\n        raise RuntimeError(\"GLOMO_SECRET_KEYS is not set\")\n    return keys\n\ndef verify_signature(order_id, payment_id, status, signature, secret_keys):\n    data = f\"{order_id}|{payment_id}|{status}\".encode()\n    received = (signature or \"\").encode()\n    return any(\n        hmac.compare_digest(hmac.new(secret.encode(), data, hashlib.sha256).hexdigest().encode(), received)\n        for secret in secret_keys\n        if secret  # an empty key never matches\n    )\n\n# Example Usage\norder_id = \"order_679a16457aP6K\"  # the order ID you created in step 1\npayment_id = \"payt_679a16457aP6K\"\nstatus = \"success\"\nsignature = \"e91071c1f6226faac21db65e18f45f3c93e8468ff2d67528f4be3e4412ab0cb7\"  # the signature you received\nsecret_keys = load_secret_keys(os.environ.get(\"GLOMO_SECRET_KEYS\"))  # every active secret key, e.g. \"your_secret_key_1,your_secret_key_2\"\n\nprint(verify_signature(order_id, payment_id, status, signature, secret_keys))\n","lang":"python"},"children":[]}]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["4. ",{"$$mdtype":"Tag","name":"b","attributes":{},"children":["Verify Payment status"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Payment's status can be tracked via following ways"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["You can track the payments linked to your order_id via dashboard"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["You can also track the payment status via polling ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/api-reference/openapi/payment/getpaymentbyid"},"children":["Payment APIs"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["You can also track the payment status via ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/platform/webhooks"},"children":["Webhooks"]}," which will notify you about the payment status."]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"troubleshooting-common-issues","__idx":7},"children":["Troubleshooting Common Issues"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"checkout-popup-blocked-in-production-environment","__idx":8},"children":["Checkout Popup Blocked in Production Environment"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Symptom:"]}," The checkout works perfectly in your local development environment, but when deployed to UAT or production, the payment page fails to open in a new tab/popup after clicking the \"Pay Now\" button."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Cause:"]}," This issue is typically caused by the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Cross-Origin-Opener-Policy"]}," (COOP) security header on your website. If your website sends ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Cross-Origin-Opener-Policy: same-origin"]},", it will block popups to external domains, including the Glomo payment gateway."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"how-to-detect-this-issue","__idx":9},"children":["How to Detect This Issue"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Open your browser's Developer Tools (F12 or Right-click → Inspect)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Go to the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Network"]}," tab"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Reload your page and click on the main document (usually the first entry)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Look at the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Response Headers"]}," section"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Check if you see: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Cross-Origin-Opener-Policy: same-origin"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you see this header with the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["same-origin"]}," value, this is what's blocking the checkout popup."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"solution","__idx":10},"children":["Solution"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Update your server's COOP header configuration from:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"Cross-Origin-Opener-Policy: same-origin\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["to:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"Cross-Origin-Opener-Policy: same-origin-allow-popups\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["How to implement this depends on your setup:"]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Apache (.htaccess)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"apache","header":{"controls":{"copy":{}}},"source":"Header set Cross-Origin-Opener-Policy \"same-origin-allow-popups\"\n","lang":"apache"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Nginx"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"nginx","header":{"controls":{"copy":{}}},"source":"add_header Cross-Origin-Opener-Policy \"same-origin-allow-popups\";\n","lang":"nginx"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Node.js/Express"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"app.use((req, res, next) => {\n  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin-allow-popups');\n  next();\n});\n","lang":"javascript"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Next.js (next.config.js)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"module.exports = {\n  async headers() {\n    return [\n      {\n        source: '/(.*)',\n        headers: [\n          {\n            key: 'Cross-Origin-Opener-Policy',\n            value: 'same-origin-allow-popups',\n          },\n        ],\n      },\n    ];\n  },\n};\n","lang":"javascript"},"children":[]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"verifying-the-fix","__idx":11},"children":["Verifying the Fix"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After deploying the header change:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Clear your browser cache"]}," (or use incognito/private mode)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Reload your website"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Open Developer Tools → Network tab"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Verify the header now shows: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Cross-Origin-Opener-Policy: same-origin-allow-popups"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Test the checkout flow again"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Note:"]}," The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["same-origin-allow-popups"]}," value maintains security for your website while allowing the checkout to open properly in a new window."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"cors-error-on-checkout-load-preferences-api-failure","__idx":12},"children":["CORS Error on Checkout Load (Preferences API Failure)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Symptom:"]}," After successfully opening the checkout, you see a CORS (Cross-Origin Resource Sharing) error in the browser console when the checkout tries to load. The error typically mentions the preferences API call failing with a message like:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"Access to fetch at 'https://...' from origin '...' has been blocked by CORS policy\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Cause:"]}," This issue is often caused by browser extensions that intercept or modify network requests. Common culprits include ad blockers, privacy extensions, VPN extensions, and other security-focused browser add-ons."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"how-to-detect-this-issue-1","__idx":13},"children":["How to Detect This Issue"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Open your browser's Developer Tools (F12)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Go to the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Console"]}," tab"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Load the checkout and look for CORS-related errors"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The error will typically mention \"blocked by CORS policy\" or \"No 'Access-Control-Allow-Origin' header\""]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"solution-1","__idx":14},"children":["Solution"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 1: Test in Incognito/Private Mode"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The quickest way to verify if a browser extension is causing the issue:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Open an incognito/private browsing window (most extensions are disabled by default)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Test the checkout integration again"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If the checkout works in incognito mode, a browser extension is the culprit"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Step 2: Identify and Disable Interfering Extensions"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If the checkout works in incognito mode:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Go to your browser's extensions page:",{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Chrome/Edge:"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["chrome://extensions"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["edge://extensions"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Firefox:"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["about:addons"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Safari:"]}," Preferences → Extensions"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Disable extensions one by one, especially:",{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Ad blockers (AdBlock, uBlock Origin, etc.)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Privacy extensions (Privacy Badger, Ghostery, etc.)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["VPN or proxy extensions"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Security extensions"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Request interceptors or modifiers"]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Test the checkout after disabling each extension"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Once you identify the problematic extension, keep it disabled for your domain or whitelist your website"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"for-developers-communicating-with-users","__idx":15},"children":["For Developers: Communicating with Users"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If your customers report this issue, provide them with these troubleshooting steps:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Try testing in incognito/private browsing mode"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If it works there, ask them to temporarily disable browser extensions"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Common extensions that cause issues: ad blockers, VPN extensions, privacy tools"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Note:"]}," This is not an issue with Glomo's checkout or your integration - it's a side effect of browser extensions modifying network behavior. The checkout will work correctly once interfering extensions are identified and configured properly."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"content-blocked-by-content-security-policy-csp","__idx":16},"children":["Content Blocked by Content Security Policy (CSP)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Symptom:"]}," When attempting to load the Glomo Checkout, you see a message stating \"This content is blocked. Contact the site owner to fix the issue.\" The checkout iframe or SDK scripts fail to load."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Cause:"]}," Your website has Content Security Policy (CSP) headers configured that are blocking the Glomo Checkout SDK from loading its scripts, iframe, and making API calls. CSP is a security feature that restricts which external resources can be loaded on your page."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"how-to-detect-this-issue-2","__idx":17},"children":["How to Detect This Issue"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Open your browser's Developer Tools (F12 or Right-click → Inspect)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Go to the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Console"]}," tab"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Look for errors mentioning \"Content Security Policy\" or \"CSP\", such as:",{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"Refused to load the script 'https://unified-sdk.glomopay.com/...' because it violates the following Content Security Policy directive...\n"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"Refused to frame 'https://unified-sdk.glomopay.com/' because it violates the following Content Security Policy directive...\n"},"children":[]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["You can also check the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Network"]}," tab → click on your page's main document → ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Response Headers"]}," → look for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Content-Security-Policy"]}," header"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"solution-2","__idx":18},"children":["Solution"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Update your CSP configuration to allow the Glomo Checkout SDK domains. You need to add the following domains to your CSP directives:"]},{"$$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":{"data-label":"Domain"},"children":["Domain"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Purpose"},"children":["Purpose"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://unified-sdk.glomopay.com"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["SDK scripts, iframe, and source maps"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://*.glomopay.com"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Checkout modal and API calls"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Add these domains to the following CSP directives:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["script-src"]}]},": Allows loading the SDK JavaScript"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["frame-src"]}]},": Allows embedding the checkout iframe"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["connect-src"]}]},": Allows API calls to Glomo services"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The examples below do ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["not"]}," include ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["'unsafe-inline'"]}," in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["script-src"]},". The SDK is loaded"," ","as an external ES module, so the origin above is sufficient on its own — and keeping"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["'unsafe-inline'"]}," in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["script-src"]}," is commonly raised as a finding in security reviews,"," ","because it permits any inline script on your page to execute."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This means your own integration code must not sit in an inline ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["<script>"]}," block either."," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["type=\"module\""]}," does not exempt it — under CSP, a script is \"inline\" whenever the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["<script>"]}," ","tag has no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["src"]}," attribute. Either of these works:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Load it from a file"]}," (recommended, and what the HTML example above does):"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"html","header":{"controls":{"copy":{}}},"source":"<script type=\"module\" src=\"/glomo-checkout.js\"></script>\n","lang":"html"},"children":[]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Keep it inline and authorise it with a nonce"]},", if your server already generates one"," ","per request. The same random value goes in the header and on the tag:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"html","header":{"controls":{"copy":{}}},"source":"<!-- Content-Security-Policy: script-src 'self' 'nonce-r4nd0m' https://unified-sdk.glomopay.com -->\n<script type=\"module\" nonce=\"r4nd0m\">\n  import { GlomoCheckoutApi } from 'https://unified-sdk.glomopay.com/index.js';\n  // ...\n</script>\n","lang":"html"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The nonce must be regenerated on every response. A hash (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["'sha256-...'"]},") also works, but"," ","breaks the moment you edit the snippet, so it is harder to keep correct."," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["How to implement this depends on your setup:"]}]}]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Apache (.htaccess)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"apache","header":{"controls":{"copy":{}}},"source":"Header set Content-Security-Policy \"default-src 'self'; script-src 'self' https://unified-sdk.glomopay.com; frame-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; connect-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; style-src 'self' 'unsafe-inline';\"\n","lang":"apache"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Nginx"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"nginx","header":{"controls":{"copy":{}}},"source":"add_header Content-Security-Policy \"default-src 'self'; script-src 'self' https://unified-sdk.glomopay.com; frame-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; connect-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; style-src 'self' 'unsafe-inline';\";\n","lang":"nginx"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Node.js/Express"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"app.use((req, res, next) => {\n  res.setHeader(\n    'Content-Security-Policy',\n    \"default-src 'self'; \" +\n      \"script-src 'self' https://unified-sdk.glomopay.com; \" +\n      \"frame-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; \" +\n      \"connect-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; \" +\n      \"style-src 'self' 'unsafe-inline';\",\n  );\n  next();\n});\n","lang":"javascript"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Next.js (next.config.js)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"module.exports = {\n  async headers() {\n    return [\n      {\n        source: '/(.*)',\n        headers: [\n          {\n            key: 'Content-Security-Policy',\n            value:\n              \"default-src 'self'; script-src 'self' https://unified-sdk.glomopay.com; frame-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; connect-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; style-src 'self' 'unsafe-inline';\",\n          },\n        ],\n      },\n    ];\n  },\n};\n","lang":"javascript"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["ASP.NET Core (C#)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Add this middleware in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Program.cs"]}," (or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Startup.cs"]},"):"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"app.Use(async (context, next) =>\n{\n    context.Response.Headers.Append(\"Content-Security-Policy\",\n        \"default-src 'self'; \" +\n        \"script-src 'self' https://unified-sdk.glomopay.com; \" +\n        \"frame-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; \" +\n        \"connect-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; \" +\n        \"style-src 'self' 'unsafe-inline';\");\n    await next();\n});\n","lang":"csharp"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For IIS, you can add these headers in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["web.config"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"xml","header":{"controls":{"copy":{}}},"source":"<configuration>\n  <system.webServer>\n    <httpProtocol>\n      <customHeaders>\n        <add name=\"Content-Security-Policy\" value=\"default-src 'self'; script-src 'self' https://unified-sdk.glomopay.com; frame-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; connect-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; style-src 'self' 'unsafe-inline';\" />\n      </customHeaders>\n    </httpProtocol>\n  </system.webServer>\n</configuration>\n","lang":"xml"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Django (Python)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["django-csp"]}," middleware:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"# settings.py\nCSP_DEFAULT_SRC = (\"'self'\",)\nCSP_SCRIPT_SRC = (\"'self'\", \"https://unified-sdk.glomopay.com\")\nCSP_FRAME_SRC = (\"'self'\", \"https://unified-sdk.glomopay.com\", \"https://*.glomopay.com\")\nCSP_CONNECT_SRC = (\"'self'\", \"https://unified-sdk.glomopay.com\", \"https://*.glomopay.com\")\nCSP_STYLE_SRC = (\"'self'\", \"'unsafe-inline'\")\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Or using custom middleware:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"class CSPMiddleware:\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        response = self.get_response(request)\n        response['Content-Security-Policy'] = (\n            \"default-src 'self'; \"\n            \"script-src 'self' https://unified-sdk.glomopay.com; \"\n            \"frame-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; \"\n            \"connect-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; \"\n            \"style-src 'self' 'unsafe-inline';\"\n        )\n        return response\n","lang":"python"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Ruby on Rails"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["secure_headers"]}," gem:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ruby","header":{"controls":{"copy":{}}},"source":"# config/initializers/secure_headers.rb\nSecureHeaders::Configuration.default do |config|\n  config.csp = {\n    default_src: %w('self'),\n    script_src: %w('self' https://unified-sdk.glomopay.com),\n    frame_src: %w('self' https://unified-sdk.glomopay.com https://*.glomopay.com),\n    connect_src: %w('self' https://unified-sdk.glomopay.com https://*.glomopay.com),\n    style_src: %w('self' 'unsafe-inline')\n  }\nend\n","lang":"ruby"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Or using custom middleware:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ruby","header":{"controls":{"copy":{}}},"source":"# config/application.rb\nconfig.middleware.insert_before 0, Rack::Headers do |headers|\n  headers['Content-Security-Policy'] = \"default-src 'self'; script-src 'self' https://unified-sdk.glomopay.com; frame-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; connect-src 'self' https://unified-sdk.glomopay.com https://*.glomopay.com; style-src 'self' 'unsafe-inline';\"\nend\n","lang":"ruby"},"children":[]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"verifying-the-fix-1","__idx":19},"children":["Verifying the Fix"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After deploying the CSP changes:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Clear your browser cache"]}," (or use incognito/private mode)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Reload your website"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Open Developer Tools → Console tab"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Verify there are no CSP-related errors"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Check Network tab → Response Headers to confirm your CSP now includes the Glomo domains"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Test the checkout flow again"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Note:"]}," If you have an existing CSP policy, you only need to add the Glomo domains to your existing directives rather than replacing your entire policy. Make sure to merge these domains with your current configuration."]}]},"headings":[{"value":"Checkout","id":"checkout","depth":1},{"value":"End-to-End Flow in Accepting Payments","id":"end-to-end-flow-in-accepting-payments","depth":2},{"value":"Visual Representation of Payment Flow","id":"visual-representation-of-payment-flow","depth":2},{"value":"Steps to integrate Checkout","id":"steps-to-integrate-checkout","depth":2},{"value":"About the Callback URL","id":"about-the-callback-url","depth":3},{"value":"Cards","id":"cards","depth":4},{"value":"Bank Transfer","id":"bank-transfer","depth":4},{"value":"Troubleshooting Common Issues","id":"troubleshooting-common-issues","depth":2},{"value":"Checkout Popup Blocked in Production Environment","id":"checkout-popup-blocked-in-production-environment","depth":3},{"value":"How to Detect This Issue","id":"how-to-detect-this-issue","depth":4},{"value":"Solution","id":"solution","depth":4},{"value":"Verifying the Fix","id":"verifying-the-fix","depth":4},{"value":"CORS Error on Checkout Load (Preferences API Failure)","id":"cors-error-on-checkout-load-preferences-api-failure","depth":3},{"value":"How to Detect This Issue","id":"how-to-detect-this-issue-1","depth":4},{"value":"Solution","id":"solution-1","depth":4},{"value":"For Developers: Communicating with Users","id":"for-developers-communicating-with-users","depth":4},{"value":"Content Blocked by Content Security Policy (CSP)","id":"content-blocked-by-content-security-policy-csp","depth":3},{"value":"How to Detect This Issue","id":"how-to-detect-this-issue-2","depth":4},{"value":"Solution","id":"solution-2","depth":4},{"value":"Verifying the Fix","id":"verifying-the-fix-1","depth":4}],"frontmatter":{"title":"Checkout","steps":["Once the **customer initiates payment** on the merchant’s website - Create an order by making server to server call","**Use order ID** to involve the checkout form","User selects a **payment option** on checkout and enters relevant information - Collect Payment details","**Glomo authenticates** the payment information","**Verify Payment status** returned and redirect the user to the payment confirmation page"],"seo":{"title":"Checkout"}},"lastModified":"2026-11-06T11:06:03.692Z","pagePropGetterError":{"message":"","name":""}},"slug":"/payin/checkout","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}