{
 "site": "PayLater Docs",
 "base_url": "https://docs.paylaterapp.com/",
 "summary": "The PayLater API lets you build Buy Now, Pay Later into any checkout in Qatar: payment links, POS request-to-pay, refunds and real-time webhooks, plus plugins for Magento, WooCommerce, Shopify, Odoo and OpenCart.",
 "pages": [
  {
   "slug": "index",
   "title": "Introduction",
   "section": "Getting Started",
   "description": "The PayLater API lets you build Buy Now, Pay Later into any checkout in Qatar: payment links, POS request-to-pay, refunds and real-time webhooks, plus plugins for Magento, WooCommerce, Shopify, Odoo and OpenCart.",
   "keywords": "overview bnpl buy now pay later split pay getting started intro home gateway checkout",
   "url": "https://docs.paylaterapp.com/index.html",
   "markdown_url": "https://docs.paylaterapp.com/index.md",
   "markdown": "# Build BNPL into any checkout\n\nThe PayLater API is your gateway to custom, end-to-end Buy Now, Pay Later experiences — in your platform, mobile app, or point of sale. The new way to split pay.\n\nPayLater lets shoppers in Qatar split a purchase into interest-free instalments at checkout. This reference covers everything you need to integrate: authenticating with OAuth 2.0, generating payment links, taking payments at the point of sale, tracking status, issuing refunds, and receiving real-time webhooks.\n\n- **OAuth 2.0**: Client-credentials auth\n- **300–25,000**: QAR per order\n- **1–1,440 min**: Configurable link expiry\n- **Real-time**: Signed webhook events\n\n## Start building\n\n- [Quickstart](https://docs.paylaterapp.com/quickstart.html): Go from zero to your first payment link in a sandbox in minutes.\n- [Authentication](https://docs.paylaterapp.com/authentication.html): Exchange your client credentials for an OAuth 2.0 bearer token.\n- [Test environment](https://docs.paylaterapp.com/test-environment.html): Sandbox credentials, test shoppers, and test cards for every scenario.\n\n## Core APIs\n\n- [Generate Payment Link](https://docs.paylaterapp.com/generate-payment-link.html): Create a hosted checkout link and redirect the shopper to pay.\n- [Request to Pay (POS)](https://docs.paylaterapp.com/request-to-pay.html): Push a payment request to a registered shopper’s PayLater app — built for the counter.\n- [Check Payment Status](https://docs.paylaterapp.com/check-payment-status.html): Poll the live status of any order by its merchant order ID.\n- [Shopper & Transactions](https://docs.paylaterapp.com/get-shopper-information.html): Look up a shopper’s spending limit and retrieve transaction details.\n- [Refunds](https://docs.paylaterapp.com/full-refund.html): Issue full or partial refunds within the refund window.\n- [Webhooks](https://docs.paylaterapp.com/webhooks.html): Receive signed, real-time notifications when an order’s status changes.\n\n## No-code plugins\n\nAlready on a major commerce platform? Skip the build and drop PayLater in with an official plugin.\n\n- [Magento](https://docs.paylaterapp.com/magento.html): OAuth2 plugin for Adobe Commerce / Magento 2.\n- [WooCommerce](https://docs.paylaterapp.com/woocommerce.html): WordPress + WooCommerce payment gateway.\n- [Shopify](https://docs.paylaterapp.com/shopify.html): Manual payment method + webhook setup for Shopify.\n- [Odoo](https://docs.paylaterapp.com/odoo.html): Payment provider for Odoo websites.\n- [OpenCart](https://docs.paylaterapp.com/opencart.html): Payment extension for OpenCart storefronts.\n- [Go live](https://docs.paylaterapp.com/go-live.html): The checklist to move from sandbox to production.\n\n> **Tip: Need credentials?**\n>\n> Sandbox access and production API keys are issued by your PayLater account manager. Reach the integrations team any time at [tech@paylaterapp.com](mailto:tech@paylaterapp.com).\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/index.html",
     "markdown": "# Build BNPL into any checkout\n\nThe PayLater API is your gateway to custom, end-to-end Buy Now, Pay Later experiences — in your platform, mobile app, or point of sale. The new way to split pay.\n\nPayLater lets shoppers in Qatar split a purchase into interest-free instalments at checkout. This reference covers everything you need to integrate: authenticating with OAuth 2.0, generating payment links, taking payments at the point of sale, tracking status, issuing refunds, and receiving real-time webhooks.\n\n- **OAuth 2.0**: Client-credentials auth\n- **300–25,000**: QAR per order\n- **1–1,440 min**: Configurable link expiry\n- **Real-time**: Signed webhook events"
    },
    {
     "heading": "Start building",
     "anchor": "start-building",
     "url": "https://docs.paylaterapp.com/index.html#start-building",
     "markdown": "## Start building\n\n- [Quickstart](https://docs.paylaterapp.com/quickstart.html): Go from zero to your first payment link in a sandbox in minutes.\n- [Authentication](https://docs.paylaterapp.com/authentication.html): Exchange your client credentials for an OAuth 2.0 bearer token.\n- [Test environment](https://docs.paylaterapp.com/test-environment.html): Sandbox credentials, test shoppers, and test cards for every scenario."
    },
    {
     "heading": "Core APIs",
     "anchor": "core-apis",
     "url": "https://docs.paylaterapp.com/index.html#core-apis",
     "markdown": "## Core APIs\n\n- [Generate Payment Link](https://docs.paylaterapp.com/generate-payment-link.html): Create a hosted checkout link and redirect the shopper to pay.\n- [Request to Pay (POS)](https://docs.paylaterapp.com/request-to-pay.html): Push a payment request to a registered shopper’s PayLater app — built for the counter.\n- [Check Payment Status](https://docs.paylaterapp.com/check-payment-status.html): Poll the live status of any order by its merchant order ID.\n- [Shopper & Transactions](https://docs.paylaterapp.com/get-shopper-information.html): Look up a shopper’s spending limit and retrieve transaction details.\n- [Refunds](https://docs.paylaterapp.com/full-refund.html): Issue full or partial refunds within the refund window.\n- [Webhooks](https://docs.paylaterapp.com/webhooks.html): Receive signed, real-time notifications when an order’s status changes."
    },
    {
     "heading": "No-code plugins",
     "anchor": "no-code-plugins",
     "url": "https://docs.paylaterapp.com/index.html#no-code-plugins",
     "markdown": "## No-code plugins\n\nAlready on a major commerce platform? Skip the build and drop PayLater in with an official plugin.\n\n- [Magento](https://docs.paylaterapp.com/magento.html): OAuth2 plugin for Adobe Commerce / Magento 2.\n- [WooCommerce](https://docs.paylaterapp.com/woocommerce.html): WordPress + WooCommerce payment gateway.\n- [Shopify](https://docs.paylaterapp.com/shopify.html): Manual payment method + webhook setup for Shopify.\n- [Odoo](https://docs.paylaterapp.com/odoo.html): Payment provider for Odoo websites.\n- [OpenCart](https://docs.paylaterapp.com/opencart.html): Payment extension for OpenCart storefronts.\n- [Go live](https://docs.paylaterapp.com/go-live.html): The checklist to move from sandbox to production.\n\n> **Tip: Need credentials?**\n>\n> Sandbox access and production API keys are issued by your PayLater account manager. Reach the integrations team any time at [tech@paylaterapp.com](mailto:tech@paylaterapp.com)."
    }
   ],
   "endpoints": []
  },
  {
   "slug": "quickstart",
   "title": "Quickstart",
   "section": "Getting Started",
   "description": "From zero to your first PayLater payment link in the sandbox.",
   "keywords": "quickstart start first steps sandbox tutorial integrate begin four steps",
   "url": "https://docs.paylaterapp.com/quickstart.html",
   "markdown_url": "https://docs.paylaterapp.com/quickstart.md",
   "markdown": "# Quickstart\n\nIntegrate PayLater in four steps — authenticate, generate a payment link in the sandbox, redirect a test shopper to pay, and confirm the result.\n\n> **Tip: Prefer Postman?**\n>\n> Skip the copy-paste — [download our Postman collection](https://docs.paylaterapp.com/postman.html) with a ready-to-run sandbox environment and try every endpoint in seconds.\n\n## 1. Get your credentials\n\nThere's no separate sandbox to provision. PayLater publishes a shared set of **sandbox test credentials** — Client ID, Client Secret, and Outlet ID — that you can build and test against straight away. You'll find them, along with test shoppers and cards, on the [Test environment](https://docs.paylaterapp.com/test-environment.html) page.\n\nWhen you're ready to go live, get your **production** credentials from your Merchant Dashboard:\n\n1. Log in to your **PayLater Merchant Dashboard** and open **Settings → Integration** to view your integration credentials.\n2. Make sure your account has permission to see them. If you don't, your user role needs the *API credentials* permission.\n3. To grant it: go to **User Management → Roles**, click **Edit** on the role, open the **Admin** section, and toggle **API Credentials** on. Log out and back in — the credentials then appear under **Settings → Integration**.\n\n## 2. Get an access token\n\nExchange your client credentials for an OAuth 2.0 bearer token — see [Authentication](https://docs.paylaterapp.com/authentication.html). Send it as `Authorization: Bearer <ACCESS_TOKEN>` on every call.\n\n## 3. Generate a payment link\n\nCall [Generate Payment Link](https://docs.paylaterapp.com/generate-payment-link.html) with the order amount and your redirect URLs, then send the shopper to the returned `paymentLinkUrl`.\n\n## 4. Confirm the result\n\nListen for the order [webhook](https://docs.paylaterapp.com/webhooks.html), or poll [Check Payment Status](https://docs.paylaterapp.com/check-payment-status.html).\n\n> **Tip: Need help with credentials?**\n>\n> Stuck on production credentials or role permissions? Reach the integrations team at [tech@paylaterapp.com](mailto:tech@paylaterapp.com).\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/quickstart.html",
     "markdown": "# Quickstart\n\nIntegrate PayLater in four steps — authenticate, generate a payment link in the sandbox, redirect a test shopper to pay, and confirm the result.\n\n> **Tip: Prefer Postman?**\n>\n> Skip the copy-paste — [download our Postman collection](https://docs.paylaterapp.com/postman.html) with a ready-to-run sandbox environment and try every endpoint in seconds."
    },
    {
     "heading": "1. Get your credentials",
     "anchor": "1-get-your-credentials",
     "url": "https://docs.paylaterapp.com/quickstart.html#1-get-your-credentials",
     "markdown": "## 1. Get your credentials\n\nThere's no separate sandbox to provision. PayLater publishes a shared set of **sandbox test credentials** — Client ID, Client Secret, and Outlet ID — that you can build and test against straight away. You'll find them, along with test shoppers and cards, on the [Test environment](https://docs.paylaterapp.com/test-environment.html) page.\n\nWhen you're ready to go live, get your **production** credentials from your Merchant Dashboard:\n\n1. Log in to your **PayLater Merchant Dashboard** and open **Settings → Integration** to view your integration credentials.\n2. Make sure your account has permission to see them. If you don't, your user role needs the *API credentials* permission.\n3. To grant it: go to **User Management → Roles**, click **Edit** on the role, open the **Admin** section, and toggle **API Credentials** on. Log out and back in — the credentials then appear under **Settings → Integration**."
    },
    {
     "heading": "2. Get an access token",
     "anchor": "2-get-an-access-token",
     "url": "https://docs.paylaterapp.com/quickstart.html#2-get-an-access-token",
     "markdown": "## 2. Get an access token\n\nExchange your client credentials for an OAuth 2.0 bearer token — see [Authentication](https://docs.paylaterapp.com/authentication.html). Send it as `Authorization: Bearer <ACCESS_TOKEN>` on every call."
    },
    {
     "heading": "3. Generate a payment link",
     "anchor": "3-generate-a-payment-link",
     "url": "https://docs.paylaterapp.com/quickstart.html#3-generate-a-payment-link",
     "markdown": "## 3. Generate a payment link\n\nCall [Generate Payment Link](https://docs.paylaterapp.com/generate-payment-link.html) with the order amount and your redirect URLs, then send the shopper to the returned `paymentLinkUrl`."
    },
    {
     "heading": "4. Confirm the result",
     "anchor": "4-confirm-the-result",
     "url": "https://docs.paylaterapp.com/quickstart.html#4-confirm-the-result",
     "markdown": "## 4. Confirm the result\n\nListen for the order [webhook](https://docs.paylaterapp.com/webhooks.html), or poll [Check Payment Status](https://docs.paylaterapp.com/check-payment-status.html).\n\n> **Tip: Need help with credentials?**\n>\n> Stuck on production credentials or role permissions? Reach the integrations team at [tech@paylaterapp.com](mailto:tech@paylaterapp.com)."
    }
   ],
   "endpoints": []
  },
  {
   "slug": "authentication",
   "title": "Authentication",
   "section": "Getting Started",
   "description": "Exchange your client credentials for an OAuth 2.0 bearer token.",
   "keywords": "auth oauth token bearer client credentials client_id client_secret grant_type access_token login keycloak realms",
   "url": "https://docs.paylaterapp.com/authentication.html",
   "markdown_url": "https://docs.paylaterapp.com/authentication.md",
   "markdown": "# Authentication\n\nPayLater uses OAuth 2.0 client credentials. Exchange your `client_id` and `client_secret` for a short-lived bearer token, then send it as `Authorization: Bearer <ACCESS_TOKEN>` on every API call.\n\n> **Tip: Token lifecycle**\n>\n> The access token lives for `expires_in` seconds (`300` = 5 minutes). Cache it, reuse it until just before expiry, then re-authenticate. A single token covers all your outlets — pass `outlet_id` per request.\n\n**POST** `/auth/realms/api/protocol/openid-connect/token`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/auth/realms/api/protocol/openid-connect/token`\n- Production: `https://connect.paylaterapp.com/auth/realms/api/protocol/openid-connect/token`\n\n## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Content-Type` | `application/x-www-form-urlencoded` |\n\n**Body parameters (form-urlencoded)**\n\n- `grant_type` (String, required): Must be `client_credentials`.\n- `client_id` (String, required): Your merchant client ID.\n- `client_secret` (String, required): Your merchant client secret.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/auth/realms/api/protocol/openid-connect/token' \\\n  --header 'Content-Type: application/x-www-form-urlencoded' \\\n  --data-urlencode 'grant_type=client_credentials' \\\n  --data-urlencode 'client_id=<CLIENT_ID>' \\\n  --data-urlencode 'client_secret=<CLIENT_SECRET>'\n```\n\n**Node.js**\n\n```javascript\nconst body = new URLSearchParams({\n  grant_type: 'client_credentials',\n  client_id: '<CLIENT_ID>',\n  client_secret: '<CLIENT_SECRET>',\n});\n\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/auth/realms/api/protocol/openid-connect/token',\n  {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/x-www-form-urlencoded',\n    },\n    body,\n  },\n);\n\nconst { access_token } = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.post(\n    \"https://connect.uat.paylaterapp.com/auth/realms/api/protocol/openid-connect/token\",\n    headers={\n        \"Content-Type\": \"application/x-www-form-urlencoded\",\n    },\n    data={\n        \"grant_type\": \"client_credentials\",\n        \"client_id\": \"<CLIENT_ID>\",\n        \"client_secret\": \"<CLIENT_SECRET>\",\n    },\n)\n\naccess_token = resp.json()[\"access_token\"]\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/auth/realms/api/protocol/openid-connect/token');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        'Content-Type: application/x-www-form-urlencoded',\n    ],\n    CURLOPT_POSTFIELDS => http_build_query([\n        'grant_type' => 'client_credentials',\n        'client_id' => '<CLIENT_ID>',\n        'client_secret' => '<CLIENT_SECRET>',\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$accessToken = $response['access_token'];\n```\n\n## Response\n\nA `200 OK` returns the bearer token and its lifetimes.\n\n**200 OK**\n\n```json\n{\n  \"access_token\": \"ey***********Dw\",\n  \"expires_in\": 300,\n  \"refresh_expires_in\": 1800,\n  \"refresh_token\": \"ey**********kQ\",\n  \"token_type\": \"Bearer\",\n  \"scope\": \"profile email\"\n}\n```\n\n## Errors\n\n| Example response | Cause |\n| --- | --- |\n| `{ \"error\": \"invalid_client\", \"error_description\": \"Invalid client or Invalid client credentials\" }` | Wrong `client_id` / `client_secret`. |\n| `{ \"error\": \"unsupported_grant_type\", \"error_description\": \"Unsupported grant_type\" }` | `grant_type` must be `client_credentials`. |\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/authentication.html",
     "markdown": "# Authentication\n\nPayLater uses OAuth 2.0 client credentials. Exchange your `client_id` and `client_secret` for a short-lived bearer token, then send it as `Authorization: Bearer <ACCESS_TOKEN>` on every API call.\n\n> **Tip: Token lifecycle**\n>\n> The access token lives for `expires_in` seconds (`300` = 5 minutes). Cache it, reuse it until just before expiry, then re-authenticate. A single token covers all your outlets — pass `outlet_id` per request.\n\n**POST** `/auth/realms/api/protocol/openid-connect/token`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/auth/realms/api/protocol/openid-connect/token`\n- Production: `https://connect.paylaterapp.com/auth/realms/api/protocol/openid-connect/token`"
    },
    {
     "heading": "Request",
     "anchor": "request",
     "url": "https://docs.paylaterapp.com/authentication.html#request",
     "markdown": "## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Content-Type` | `application/x-www-form-urlencoded` |\n\n**Body parameters (form-urlencoded)**\n\n- `grant_type` (String, required): Must be `client_credentials`.\n- `client_id` (String, required): Your merchant client ID.\n- `client_secret` (String, required): Your merchant client secret.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/auth/realms/api/protocol/openid-connect/token' \\\n  --header 'Content-Type: application/x-www-form-urlencoded' \\\n  --data-urlencode 'grant_type=client_credentials' \\\n  --data-urlencode 'client_id=<CLIENT_ID>' \\\n  --data-urlencode 'client_secret=<CLIENT_SECRET>'\n```\n\n**Node.js**\n\n```javascript\nconst body = new URLSearchParams({\n  grant_type: 'client_credentials',\n  client_id: '<CLIENT_ID>',\n  client_secret: '<CLIENT_SECRET>',\n});\n\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/auth/realms/api/protocol/openid-connect/token',\n  {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/x-www-form-urlencoded',\n    },\n    body,\n  },\n);\n\nconst { access_token } = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.post(\n    \"https://connect.uat.paylaterapp.com/auth/realms/api/protocol/openid-connect/token\",\n    headers={\n        \"Content-Type\": \"application/x-www-form-urlencoded\",\n    },\n    data={\n        \"grant_type\": \"client_credentials\",\n        \"client_id\": \"<CLIENT_ID>\",\n        \"client_secret\": \"<CLIENT_SECRET>\",\n    },\n)\n\naccess_token = resp.json()[\"access_token\"]\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/auth/realms/api/protocol/openid-connect/token');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        'Content-Type: application/x-www-form-urlencoded',\n    ],\n    CURLOPT_POSTFIELDS => http_build_query([\n        'grant_type' => 'client_credentials',\n        'client_id' => '<CLIENT_ID>',\n        'client_secret' => '<CLIENT_SECRET>',\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$accessToken = $response['access_token'];\n```"
    },
    {
     "heading": "Response",
     "anchor": "response",
     "url": "https://docs.paylaterapp.com/authentication.html#response",
     "markdown": "## Response\n\nA `200 OK` returns the bearer token and its lifetimes.\n\n**200 OK**\n\n```json\n{\n  \"access_token\": \"ey***********Dw\",\n  \"expires_in\": 300,\n  \"refresh_expires_in\": 1800,\n  \"refresh_token\": \"ey**********kQ\",\n  \"token_type\": \"Bearer\",\n  \"scope\": \"profile email\"\n}\n```"
    },
    {
     "heading": "Errors",
     "anchor": "errors",
     "url": "https://docs.paylaterapp.com/authentication.html#errors",
     "markdown": "## Errors\n\n| Example response | Cause |\n| --- | --- |\n| `{ \"error\": \"invalid_client\", \"error_description\": \"Invalid client or Invalid client credentials\" }` | Wrong `client_id` / `client_secret`. |\n| `{ \"error\": \"unsupported_grant_type\", \"error_description\": \"Unsupported grant_type\" }` | `grant_type` must be `client_credentials`. |"
    }
   ],
   "endpoints": [
    {
     "method": "POST",
     "path": "/auth/realms/api/protocol/openid-connect/token",
     "urls": {
      "sandbox": "https://connect.uat.paylaterapp.com/auth/realms/api/protocol/openid-connect/token",
      "production": "https://connect.paylaterapp.com/auth/realms/api/protocol/openid-connect/token"
     },
     "title": "Authentication",
     "heading": "",
     "params": [
      {
       "name": "grant_type",
       "type": "String",
       "required": true,
       "description": "Must be `client_credentials`.",
       "group": "Body parameters (form-urlencoded)"
      },
      {
       "name": "client_id",
       "type": "String",
       "required": true,
       "description": "Your merchant client ID.",
       "group": "Body parameters (form-urlencoded)"
      },
      {
       "name": "client_secret",
       "type": "String",
       "required": true,
       "description": "Your merchant client secret.",
       "group": "Body parameters (form-urlencoded)"
      }
     ],
     "kind": "api"
    }
   ]
  },
  {
   "slug": "test-environment",
   "title": "Test environment",
   "section": "Getting Started",
   "description": "Sandbox credentials, test shoppers, and test cards for the PayLater sandbox.",
   "keywords": "sandbox uat test cards test shoppers credentials otp visa mastercard naps debit outlet merchant-138",
   "url": "https://docs.paylaterapp.com/test-environment.html",
   "markdown_url": "https://docs.paylaterapp.com/test-environment.md",
   "markdown": "# Test environment\n\nEverything you need to exercise the full flow in the sandbox (UAT). These are shared, sandbox-only values for development.\n\n> **Warning: Sandbox only**\n>\n> These credentials and cards work only against `connect.uat.paylaterapp.com`. Never use them — or commit real production secrets — in a live integration.\n\n## Sandbox credentials\n\n| Field | Value |\n| --- | --- |\n| Base URL | `https://connect.uat.paylaterapp.com` |\n| Client ID | `merchant-138` |\n| Client Secret | `M6Xjszdtd8X2XivLmUvS9Pa7Hm0JeA6g` |\n| Outlet ID | `1000000061` |\n\n## Test shoppers\n\nThe OTP for every test shopper is `1234`.\n\n| Mobile | Email |\n| --- | --- |\n| `51118021` | `qid8021@mailinator.com` |\n| `51118022` | `qid8022@mailinator.com` |\n| `51118023` | `qid8023@mailinator.com` |\n| `51118024` | `qid8024@mailinator.com` |\n| `51118025` | `qid8025@mailinator.com` |\n\n## Test cards\n\nThe expiry dates below are examples: any future expiry date works with these test cards.\n\n#### Success\n\n| Scheme | Number | Expiry | CVV |\n| --- | --- | --- | --- |\n| Mastercard | `5200 0000 0000 2151` | `10/2028` | `237` |\n| Visa | `4000 0000 0000 2503` | `10/2028` | `442` |\n| Mastercard (non-3DS) | `5200 0000 0000 0007` | `04/2030` | `256` |\n\n#### Failure\n\n| Scheme | Number | Expiry | CVV |\n| --- | --- | --- | --- |\n| Mastercard | `5200 0000 0000 2490` | `04/2028` | `256` |\n| Visa | `4000 0000 0000 2370` | `06/2028` | `256` |\n\n#### Debit (NAPS)\n\n| Number | Expiry | CVV | OTP |\n| --- | --- | --- | --- |\n| `4215 3755 0088 3243` | `06/2030` | `123` | `123456` |\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/test-environment.html",
     "markdown": "# Test environment\n\nEverything you need to exercise the full flow in the sandbox (UAT). These are shared, sandbox-only values for development.\n\n> **Warning: Sandbox only**\n>\n> These credentials and cards work only against `connect.uat.paylaterapp.com`. Never use them — or commit real production secrets — in a live integration."
    },
    {
     "heading": "Sandbox credentials",
     "anchor": "sandbox-credentials",
     "url": "https://docs.paylaterapp.com/test-environment.html#sandbox-credentials",
     "markdown": "## Sandbox credentials\n\n| Field | Value |\n| --- | --- |\n| Base URL | `https://connect.uat.paylaterapp.com` |\n| Client ID | `merchant-138` |\n| Client Secret | `M6Xjszdtd8X2XivLmUvS9Pa7Hm0JeA6g` |\n| Outlet ID | `1000000061` |"
    },
    {
     "heading": "Test shoppers",
     "anchor": "test-shoppers",
     "url": "https://docs.paylaterapp.com/test-environment.html#test-shoppers",
     "markdown": "## Test shoppers\n\nThe OTP for every test shopper is `1234`.\n\n| Mobile | Email |\n| --- | --- |\n| `51118021` | `qid8021@mailinator.com` |\n| `51118022` | `qid8022@mailinator.com` |\n| `51118023` | `qid8023@mailinator.com` |\n| `51118024` | `qid8024@mailinator.com` |\n| `51118025` | `qid8025@mailinator.com` |"
    },
    {
     "heading": "Test cards",
     "anchor": "test-cards",
     "url": "https://docs.paylaterapp.com/test-environment.html#test-cards",
     "markdown": "## Test cards\n\nThe expiry dates below are examples: any future expiry date works with these test cards.\n\n#### Success\n\n| Scheme | Number | Expiry | CVV |\n| --- | --- | --- | --- |\n| Mastercard | `5200 0000 0000 2151` | `10/2028` | `237` |\n| Visa | `4000 0000 0000 2503` | `10/2028` | `442` |\n| Mastercard (non-3DS) | `5200 0000 0000 0007` | `04/2030` | `256` |\n\n#### Failure\n\n| Scheme | Number | Expiry | CVV |\n| --- | --- | --- | --- |\n| Mastercard | `5200 0000 0000 2490` | `04/2028` | `256` |\n| Visa | `4000 0000 0000 2370` | `06/2028` | `256` |\n\n#### Debit (NAPS)\n\n| Number | Expiry | CVV | OTP |\n| --- | --- | --- | --- |\n| `4215 3755 0088 3243` | `06/2030` | `123` | `123456` |"
    }
   ],
   "endpoints": []
  },
  {
   "slug": "postman",
   "title": "Postman collection",
   "section": "Getting Started",
   "description": "Download the official PayLater API Postman collection and environments — every endpoint, with the OAuth token saved automatically and ready-to-run sandbox variables.",
   "keywords": "postman collection environment import api requests download try it run http oauth token bearer sandbox production json variables",
   "url": "https://docs.paylaterapp.com/postman.html",
   "markdown_url": "https://docs.paylaterapp.com/postman.md",
   "markdown": "# Postman collection\n\nTry the PayLater API in seconds. Import our Postman collection and a ready-to-run environment — authenticate once and every request just works, using `{{base_url}}` and an auto-saved `{{access_token}}`.\n\n## Download\n\n- [Collection](https://docs.paylaterapp.com/downloads/paylater-api.postman_collection.json): PayLater API — all endpoints (Postman Collection v2.1)\n- [Sandbox env](https://docs.paylaterapp.com/downloads/paylater-api-sandbox.postman_environment.json): Pre-filled with the published sandbox test credentials\n- [Production env](https://docs.paylaterapp.com/downloads/paylater-api-production.postman_environment.json): Blank template — add your own production credentials\n\n## Import & run\n\n1. In Postman, click **Import** and drop in the **collection** plus the **environment** file(s) above.\n2. Top-right, select the **PayLater Sandbox** environment.\n3. Open **Authentication → Get Access Token** and click **Send**. The token is saved to the environment automatically.\n4. Run any other request — they all inherit `Authorization: Bearer {{access_token}}` and use your environment variables.\n\n> **Tip: How auth works**\n>\n> The **Get Access Token** request has a test script that saves `access_token` to the active environment, and the collection sends it as a Bearer token on every request. To test production, switch to the **PayLater Production** environment and fill in your own `client_id`, `client_secret`, and `outlet_id`.\n\n## Environment variables\n\n| Variable | Purpose |\n| --- | --- |\n| `base_url` | API host — sandbox (`connect.uat.paylaterapp.com`) or production (`connect.paylaterapp.com`) |\n| `client_id` | Your OAuth 2.0 client ID |\n| `client_secret` | Your OAuth 2.0 client secret |\n| `access_token` | Bearer token — set automatically after Get Access Token |\n| `outlet_id` | Your outlet identifier |\n| `order_id` | Your unique order reference |\n| `merchant_order_reference` | Order reference for Request to Pay |\n| `amount` | Order amount, in QAR |\n| `expiry_duration` | Payment-link expiry, in minutes |\n| `mobile_number` | Shopper mobile number (lookup / Request to Pay) |\n| `email` | Shopper email (shopper lookup) |\n| `size` | Transactions per page |\n| `page` | Transaction list page number |\n\n> **Note: Sandbox credentials**\n>\n> The Sandbox environment ships with PayLater's published, **sandbox-only** test values (see [Test environment](https://docs.paylaterapp.com/test-environment.html)). Never use them in production.\n\n> **Info: One-click \"Run in Postman\" button**\n>\n> Prefer the orange *Run in Postman* button? That requires publishing this collection to a public Postman workspace — ask the docs team and we'll wire it up.\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/postman.html",
     "markdown": "# Postman collection\n\nTry the PayLater API in seconds. Import our Postman collection and a ready-to-run environment — authenticate once and every request just works, using `{{base_url}}` and an auto-saved `{{access_token}}`."
    },
    {
     "heading": "Download",
     "anchor": "download",
     "url": "https://docs.paylaterapp.com/postman.html#download",
     "markdown": "## Download\n\n- [Collection](https://docs.paylaterapp.com/downloads/paylater-api.postman_collection.json): PayLater API — all endpoints (Postman Collection v2.1)\n- [Sandbox env](https://docs.paylaterapp.com/downloads/paylater-api-sandbox.postman_environment.json): Pre-filled with the published sandbox test credentials\n- [Production env](https://docs.paylaterapp.com/downloads/paylater-api-production.postman_environment.json): Blank template — add your own production credentials"
    },
    {
     "heading": "Import & run",
     "anchor": "import-run",
     "url": "https://docs.paylaterapp.com/postman.html#import-run",
     "markdown": "## Import & run\n\n1. In Postman, click **Import** and drop in the **collection** plus the **environment** file(s) above.\n2. Top-right, select the **PayLater Sandbox** environment.\n3. Open **Authentication → Get Access Token** and click **Send**. The token is saved to the environment automatically.\n4. Run any other request — they all inherit `Authorization: Bearer {{access_token}}` and use your environment variables.\n\n> **Tip: How auth works**\n>\n> The **Get Access Token** request has a test script that saves `access_token` to the active environment, and the collection sends it as a Bearer token on every request. To test production, switch to the **PayLater Production** environment and fill in your own `client_id`, `client_secret`, and `outlet_id`."
    },
    {
     "heading": "Environment variables",
     "anchor": "environment-variables",
     "url": "https://docs.paylaterapp.com/postman.html#environment-variables",
     "markdown": "## Environment variables\n\n| Variable | Purpose |\n| --- | --- |\n| `base_url` | API host — sandbox (`connect.uat.paylaterapp.com`) or production (`connect.paylaterapp.com`) |\n| `client_id` | Your OAuth 2.0 client ID |\n| `client_secret` | Your OAuth 2.0 client secret |\n| `access_token` | Bearer token — set automatically after Get Access Token |\n| `outlet_id` | Your outlet identifier |\n| `order_id` | Your unique order reference |\n| `merchant_order_reference` | Order reference for Request to Pay |\n| `amount` | Order amount, in QAR |\n| `expiry_duration` | Payment-link expiry, in minutes |\n| `mobile_number` | Shopper mobile number (lookup / Request to Pay) |\n| `email` | Shopper email (shopper lookup) |\n| `size` | Transactions per page |\n| `page` | Transaction list page number |\n\n> **Note: Sandbox credentials**\n>\n> The Sandbox environment ships with PayLater's published, **sandbox-only** test values (see [Test environment](https://docs.paylaterapp.com/test-environment.html)). Never use them in production.\n\n> **Info: One-click \"Run in Postman\" button**\n>\n> Prefer the orange *Run in Postman* button? That requires publishing this collection to a public Postman workspace — ask the docs team and we'll wire it up."
    }
   ],
   "endpoints": []
  },
  {
   "slug": "ai-assistants",
   "title": "Use with AI assistants",
   "section": "Getting Started",
   "description": "Point Claude Code, Codex, Cursor and chat assistants at the PayLater docs with llms.txt, Markdown copies of every page and paste-ready instructions.",
   "keywords": "ai assistant llm llms.txt llms-full markdown claude code codex cursor chatgpt gemini agents.md claude.md mcp docs.json copilot prompt",
   "url": "https://docs.paylaterapp.com/ai-assistants.html",
   "markdown_url": "https://docs.paylaterapp.com/ai-assistants.md",
   "markdown": "# Use with AI assistants\n\nAI coding assistants write better PayLater integrations when they read these docs instead of guessing. Every page is published as plain Markdown, with an `llms.txt` index, so you can point your assistant at the docs in a few lines.\n\n## What is published\n\nThese files are rebuilt from the docs on every update, so they always match this site.\n\n| File | What it is |\n| --- | --- |\n| [`/llms.txt`](https://docs.paylaterapp.com/llms.txt) | An index of every page with a one-line summary, in the [llms.txt](https://llmstxt.org) format. Start here. |\n| [`/llms-full.txt`](https://docs.paylaterapp.com/llms-full.txt) | The whole site as one Markdown file (about 110 KB), for loading everything at once. |\n| `/<page>.md` | A Markdown copy of each page, for example [`/generate-payment-link.md`](https://docs.paylaterapp.com/generate-payment-link.md). Every page also links to its own copy under **View as Markdown**. |\n| [`/docs.json`](https://docs.paylaterapp.com/docs.json) | The same content as structured data, including every API endpoint with its method, URLs and parameters, for your own tools. |\n\n## Claude Code and Codex\n\nBoth read a project instructions file at the start of each session: Claude Code reads `CLAUDE.md` (and can read `AGENTS.md` instead), Codex reads `AGENTS.md`. Add this section to that file in the repository that holds your PayLater integration:\n\n**CLAUDE.md or AGENTS.md**\n\n```markdown\n## PayLater integration\n\nPayLater is a Buy Now, Pay Later payment provider. When working on the PayLater\nintegration, use the official developer docs, not memory:\n\n- Index of all pages: https://docs.paylaterapp.com/llms.txt\n- Whole site in one file: https://docs.paylaterapp.com/llms-full.txt\n\nRead the relevant page before writing or changing API calls, webhook handling or\nrefunds. Use the sandbox (https://connect.uat.paylaterapp.com) while developing.\nNever put live client secrets or real shopper data in code, prompts or logs.\n```\n\nThe assistant fetches the pages it needs while it works. To load everything up front instead, ask it to read `https://docs.paylaterapp.com/llms-full.txt` at the start of the session.\n\n## Cursor\n\n- Type **@Docs** in the chat, choose **Add new doc** and enter `https://docs.paylaterapp.com/`. Name it *PayLater* and refer to it as **@PayLater** in your prompts.\n- For one topic, attach that page's Markdown copy instead, for example `https://docs.paylaterapp.com/webhooks.md`.\n\n## Chat assistants\n\nIn ChatGPT, Claude, Gemini and similar assistants, either:\n\n- download [`llms-full.txt`](https://docs.paylaterapp.com/llms-full.txt) and attach it to the conversation or project, or\n- paste the link to a single page's Markdown copy if the assistant can browse the web.\n\n## Good practice\n\n- **Keep secrets and shopper data out of AI tools.** Do not paste live client secrets, webhook secrets, or shopper names, phone numbers or emails into prompts. Use the sandbox credentials from the [Test environment](https://docs.paylaterapp.com/test-environment.html) page while developing.\n- **Check what the assistant writes.** Assistants can still get details wrong. Compare amounts, status values and webhook verification against the relevant page, and test the full flow in the sandbox before you [go live](https://docs.paylaterapp.com/go-live.html).\n- **Refresh after updates.** The files change when the docs change. If your tool keeps its own copy, as Cursor's indexed docs do, re-index after a new [release](https://docs.paylaterapp.com/release-notes.html).\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/ai-assistants.html",
     "markdown": "# Use with AI assistants\n\nAI coding assistants write better PayLater integrations when they read these docs instead of guessing. Every page is published as plain Markdown, with an `llms.txt` index, so you can point your assistant at the docs in a few lines."
    },
    {
     "heading": "What is published",
     "anchor": "what-is-published",
     "url": "https://docs.paylaterapp.com/ai-assistants.html#what-is-published",
     "markdown": "## What is published\n\nThese files are rebuilt from the docs on every update, so they always match this site.\n\n| File | What it is |\n| --- | --- |\n| [`/llms.txt`](https://docs.paylaterapp.com/llms.txt) | An index of every page with a one-line summary, in the [llms.txt](https://llmstxt.org) format. Start here. |\n| [`/llms-full.txt`](https://docs.paylaterapp.com/llms-full.txt) | The whole site as one Markdown file (about 110 KB), for loading everything at once. |\n| `/<page>.md` | A Markdown copy of each page, for example [`/generate-payment-link.md`](https://docs.paylaterapp.com/generate-payment-link.md). Every page also links to its own copy under **View as Markdown**. |\n| [`/docs.json`](https://docs.paylaterapp.com/docs.json) | The same content as structured data, including every API endpoint with its method, URLs and parameters, for your own tools. |"
    },
    {
     "heading": "Claude Code and Codex",
     "anchor": "claude-code-and-codex",
     "url": "https://docs.paylaterapp.com/ai-assistants.html#claude-code-and-codex",
     "markdown": "## Claude Code and Codex\n\nBoth read a project instructions file at the start of each session: Claude Code reads `CLAUDE.md` (and can read `AGENTS.md` instead), Codex reads `AGENTS.md`. Add this section to that file in the repository that holds your PayLater integration:\n\n**CLAUDE.md or AGENTS.md**\n\n```markdown\n## PayLater integration\n\nPayLater is a Buy Now, Pay Later payment provider. When working on the PayLater\nintegration, use the official developer docs, not memory:\n\n- Index of all pages: https://docs.paylaterapp.com/llms.txt\n- Whole site in one file: https://docs.paylaterapp.com/llms-full.txt\n\nRead the relevant page before writing or changing API calls, webhook handling or\nrefunds. Use the sandbox (https://connect.uat.paylaterapp.com) while developing.\nNever put live client secrets or real shopper data in code, prompts or logs.\n```\n\nThe assistant fetches the pages it needs while it works. To load everything up front instead, ask it to read `https://docs.paylaterapp.com/llms-full.txt` at the start of the session."
    },
    {
     "heading": "Cursor",
     "anchor": "cursor",
     "url": "https://docs.paylaterapp.com/ai-assistants.html#cursor",
     "markdown": "## Cursor\n\n- Type **@Docs** in the chat, choose **Add new doc** and enter `https://docs.paylaterapp.com/`. Name it *PayLater* and refer to it as **@PayLater** in your prompts.\n- For one topic, attach that page's Markdown copy instead, for example `https://docs.paylaterapp.com/webhooks.md`."
    },
    {
     "heading": "Chat assistants",
     "anchor": "chat-assistants",
     "url": "https://docs.paylaterapp.com/ai-assistants.html#chat-assistants",
     "markdown": "## Chat assistants\n\nIn ChatGPT, Claude, Gemini and similar assistants, either:\n\n- download [`llms-full.txt`](https://docs.paylaterapp.com/llms-full.txt) and attach it to the conversation or project, or\n- paste the link to a single page's Markdown copy if the assistant can browse the web."
    },
    {
     "heading": "Good practice",
     "anchor": "good-practice",
     "url": "https://docs.paylaterapp.com/ai-assistants.html#good-practice",
     "markdown": "## Good practice\n\n- **Keep secrets and shopper data out of AI tools.** Do not paste live client secrets, webhook secrets, or shopper names, phone numbers or emails into prompts. Use the sandbox credentials from the [Test environment](https://docs.paylaterapp.com/test-environment.html) page while developing.\n- **Check what the assistant writes.** Assistants can still get details wrong. Compare amounts, status values and webhook verification against the relevant page, and test the full flow in the sandbox before you [go live](https://docs.paylaterapp.com/go-live.html).\n- **Refresh after updates.** The files change when the docs change. If your tool keeps its own copy, as Cursor's indexed docs do, re-index after a new [release](https://docs.paylaterapp.com/release-notes.html)."
    }
   ],
   "endpoints": []
  },
  {
   "slug": "generate-payment-link",
   "title": "Generate Payment Link",
   "section": "API Reference",
   "description": "Create a hosted PayLater checkout link and redirect the shopper to complete a Buy Now, Pay Later payment.",
   "keywords": "payment link web-checkout post create checkout hosted paymentLinkUrl outlet_id amount order_id currency qar expiry_duration redirect",
   "url": "https://docs.paylaterapp.com/generate-payment-link.html",
   "markdown_url": "https://docs.paylaterapp.com/generate-payment-link.md",
   "markdown": "# Generate Payment Link\n\nCreate a **hosted checkout link** for an order. Redirect the shopper to the returned URL, they complete the PayLater flow (identity, instalment plan, payment), and we redirect them back to your success or failure URL.\n\n**POST** `/api/paylater/merchant-portal/v2/web-checkout`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout`\n\n> **Info: Authentication**\n>\n> All requests require a bearer token from the [OAuth 2.0 token endpoint](https://docs.paylaterapp.com/authentication.html). Pass it as `Authorization: Bearer <ACCESS_TOKEN>`.\n\n## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n| `Content-Type` | `application/json` |\n\n**Body parameters**\n\n- `outlet_id` (Long, required): The storefront / outlet identifier issued to your merchant account.\n- `currency` (String, required): The order currency. Must be `QAR`.\n- `amount` (Double, required): Order amount. Accepted range is `300`–`25,000` QAR (your outlet may be configured with a narrower range).\n- `order_id` (String, required): Your unique reference for this transaction. Must be unique per merchant.\n- `success_redirect_url` (String, required): URL the shopper is redirected to after a successful payment.\n- `fail_redirect_url` (String, required): URL the shopper is redirected to if the payment fails or is abandoned.\n- `expiry_duration` (Integer, required): How long the link stays valid, in minutes. Range `1`–`1440`.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>' \\\n  --header 'Content-Type: application/json' \\\n  --data '{\n    \"outlet_id\": 1000000061,\n    \"currency\": \"QAR\",\n    \"amount\": 350.00,\n    \"order_id\": \"O-3445\",\n    \"success_redirect_url\": \"https://test.com/success\",\n    \"fail_redirect_url\": \"https://test.com/fail\",\n    \"expiry_duration\": 10\n  }'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout',\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Content-Type': 'application/json',\n    },\n    body: JSON.stringify({\n      outlet_id: 1000000061,\n      currency: 'QAR',\n      amount: 350.0,\n      order_id: 'O-3445',\n      success_redirect_url: 'https://test.com/success',\n      fail_redirect_url: 'https://test.com/fail',\n      expiry_duration: 10,\n    }),\n  },\n);\n\nconst { paymentLinkUrl } = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.post(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"outlet_id\": 1000000061,\n        \"currency\": \"QAR\",\n        \"amount\": 350.00,\n        \"order_id\": \"O-3445\",\n        \"success_redirect_url\": \"https://test.com/success\",\n        \"fail_redirect_url\": \"https://test.com/fail\",\n        \"expiry_duration\": 10,\n    },\n)\n\npayment_link_url = resp.json()[\"paymentLinkUrl\"]\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n        'Content-Type: application/json',\n    ],\n    CURLOPT_POSTFIELDS => json_encode([\n        'outlet_id' => 1000000061,\n        'currency' => 'QAR',\n        'amount' => 350.00,\n        'order_id' => 'O-3445',\n        'success_redirect_url' => 'https://test.com/success',\n        'fail_redirect_url' => 'https://test.com/fail',\n        'expiry_duration' => 10,\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n```\n\n## Response\n\nA `200 OK` returns the hosted checkout URL. Redirect the shopper there to complete payment.\n\n**200 OK**\n\n```json\n{\n  \"paymentLinkUrl\": \"https://payments.uat.paylaterapp.com/paylink/uuid?token=xyz&channel=web\"\n}\n```\n\n> **Warning: Payment links are single-use**\n>\n> Each link is valid for one payment attempt and expires after `expiry_duration` minutes. Generate a fresh link per checkout attempt.\n\n## Errors\n\n| Example response | Cause |\n| --- | --- |\n| `{ \"error\": \"Merchant ID cannot be null\" }` | `outlet_id` / merchant context missing or invalid. |\n| `{ \"error\": \"Order ID must be unique\" }` | An order with this `order_id` already exists. |\n| `{ \"error\": \"Amount must be between 300 and 25000\" }` | `amount` is outside the accepted range for your outlet. |\n\nSee [Error handling & response codes](https://docs.paylaterapp.com/errors.html) for the full catalogue.\n\n## Next steps\n\n- [Check Payment Status](https://docs.paylaterapp.com/check-payment-status.html): Poll the order status, or let webhooks tell you when it changes.\n- [Webhooks](https://docs.paylaterapp.com/webhooks.html): Receive a signed notification the moment the payment succeeds or fails.\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/generate-payment-link.html",
     "markdown": "# Generate Payment Link\n\nCreate a **hosted checkout link** for an order. Redirect the shopper to the returned URL, they complete the PayLater flow (identity, instalment plan, payment), and we redirect them back to your success or failure URL.\n\n**POST** `/api/paylater/merchant-portal/v2/web-checkout`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout`\n\n> **Info: Authentication**\n>\n> All requests require a bearer token from the [OAuth 2.0 token endpoint](https://docs.paylaterapp.com/authentication.html). Pass it as `Authorization: Bearer <ACCESS_TOKEN>`."
    },
    {
     "heading": "Request",
     "anchor": "request",
     "url": "https://docs.paylaterapp.com/generate-payment-link.html#request",
     "markdown": "## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n| `Content-Type` | `application/json` |\n\n**Body parameters**\n\n- `outlet_id` (Long, required): The storefront / outlet identifier issued to your merchant account.\n- `currency` (String, required): The order currency. Must be `QAR`.\n- `amount` (Double, required): Order amount. Accepted range is `300`–`25,000` QAR (your outlet may be configured with a narrower range).\n- `order_id` (String, required): Your unique reference for this transaction. Must be unique per merchant.\n- `success_redirect_url` (String, required): URL the shopper is redirected to after a successful payment.\n- `fail_redirect_url` (String, required): URL the shopper is redirected to if the payment fails or is abandoned.\n- `expiry_duration` (Integer, required): How long the link stays valid, in minutes. Range `1`–`1440`.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>' \\\n  --header 'Content-Type: application/json' \\\n  --data '{\n    \"outlet_id\": 1000000061,\n    \"currency\": \"QAR\",\n    \"amount\": 350.00,\n    \"order_id\": \"O-3445\",\n    \"success_redirect_url\": \"https://test.com/success\",\n    \"fail_redirect_url\": \"https://test.com/fail\",\n    \"expiry_duration\": 10\n  }'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout',\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Content-Type': 'application/json',\n    },\n    body: JSON.stringify({\n      outlet_id: 1000000061,\n      currency: 'QAR',\n      amount: 350.0,\n      order_id: 'O-3445',\n      success_redirect_url: 'https://test.com/success',\n      fail_redirect_url: 'https://test.com/fail',\n      expiry_duration: 10,\n    }),\n  },\n);\n\nconst { paymentLinkUrl } = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.post(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"outlet_id\": 1000000061,\n        \"currency\": \"QAR\",\n        \"amount\": 350.00,\n        \"order_id\": \"O-3445\",\n        \"success_redirect_url\": \"https://test.com/success\",\n        \"fail_redirect_url\": \"https://test.com/fail\",\n        \"expiry_duration\": 10,\n    },\n)\n\npayment_link_url = resp.json()[\"paymentLinkUrl\"]\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n        'Content-Type: application/json',\n    ],\n    CURLOPT_POSTFIELDS => json_encode([\n        'outlet_id' => 1000000061,\n        'currency' => 'QAR',\n        'amount' => 350.00,\n        'order_id' => 'O-3445',\n        'success_redirect_url' => 'https://test.com/success',\n        'fail_redirect_url' => 'https://test.com/fail',\n        'expiry_duration' => 10,\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n```"
    },
    {
     "heading": "Response",
     "anchor": "response",
     "url": "https://docs.paylaterapp.com/generate-payment-link.html#response",
     "markdown": "## Response\n\nA `200 OK` returns the hosted checkout URL. Redirect the shopper there to complete payment.\n\n**200 OK**\n\n```json\n{\n  \"paymentLinkUrl\": \"https://payments.uat.paylaterapp.com/paylink/uuid?token=xyz&channel=web\"\n}\n```\n\n> **Warning: Payment links are single-use**\n>\n> Each link is valid for one payment attempt and expires after `expiry_duration` minutes. Generate a fresh link per checkout attempt."
    },
    {
     "heading": "Errors",
     "anchor": "errors",
     "url": "https://docs.paylaterapp.com/generate-payment-link.html#errors",
     "markdown": "## Errors\n\n| Example response | Cause |\n| --- | --- |\n| `{ \"error\": \"Merchant ID cannot be null\" }` | `outlet_id` / merchant context missing or invalid. |\n| `{ \"error\": \"Order ID must be unique\" }` | An order with this `order_id` already exists. |\n| `{ \"error\": \"Amount must be between 300 and 25000\" }` | `amount` is outside the accepted range for your outlet. |\n\nSee [Error handling & response codes](https://docs.paylaterapp.com/errors.html) for the full catalogue."
    },
    {
     "heading": "Next steps",
     "anchor": "next-steps",
     "url": "https://docs.paylaterapp.com/generate-payment-link.html#next-steps",
     "markdown": "## Next steps\n\n- [Check Payment Status](https://docs.paylaterapp.com/check-payment-status.html): Poll the order status, or let webhooks tell you when it changes.\n- [Webhooks](https://docs.paylaterapp.com/webhooks.html): Receive a signed notification the moment the payment succeeds or fails."
    }
   ],
   "endpoints": [
    {
     "method": "POST",
     "path": "/api/paylater/merchant-portal/v2/web-checkout",
     "urls": {
      "sandbox": "https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout",
      "production": "https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout"
     },
     "title": "Generate Payment Link",
     "heading": "",
     "params": [
      {
       "name": "outlet_id",
       "type": "Long",
       "required": true,
       "description": "The storefront / outlet identifier issued to your merchant account.",
       "group": "Body parameters"
      },
      {
       "name": "currency",
       "type": "String",
       "required": true,
       "description": "The order currency. Must be `QAR`.",
       "group": "Body parameters"
      },
      {
       "name": "amount",
       "type": "Double",
       "required": true,
       "description": "Order amount. Accepted range is `300`–`25,000` QAR (your outlet may be configured with a narrower range).",
       "group": "Body parameters"
      },
      {
       "name": "order_id",
       "type": "String",
       "required": true,
       "description": "Your unique reference for this transaction. Must be unique per merchant.",
       "group": "Body parameters"
      },
      {
       "name": "success_redirect_url",
       "type": "String",
       "required": true,
       "description": "URL the shopper is redirected to after a successful payment.",
       "group": "Body parameters"
      },
      {
       "name": "fail_redirect_url",
       "type": "String",
       "required": true,
       "description": "URL the shopper is redirected to if the payment fails or is abandoned.",
       "group": "Body parameters"
      },
      {
       "name": "expiry_duration",
       "type": "Integer",
       "required": true,
       "description": "How long the link stays valid, in minutes. Range `1`–`1440`.",
       "group": "Body parameters"
      }
     ],
     "kind": "api"
    }
   ]
  },
  {
   "slug": "request-to-pay",
   "title": "Request to Pay (POS)",
   "section": "API Reference",
   "description": "Push a payment request to a registered shopper’s PayLater app — built for the point of sale.",
   "keywords": "request to pay pos point of sale in-store mobile_number order_amount merchant_order_reference expiry_minutes push app",
   "url": "https://docs.paylaterapp.com/request-to-pay.html",
   "markdown_url": "https://docs.paylaterapp.com/request-to-pay.md",
   "markdown": "# Request to Pay (POS)\n\nPush a payment request straight to a registered shopper’s PayLater app by mobile number — ideal for in-store / point of sale. The shopper approves and pays in-app.\n\n**POST** `/api/paylater/merchant-portal/v2/web-checkout/request-to-pay`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay`\n\n> **Info: Authentication**\n>\n> Requires a bearer token from the [OAuth 2.0 token endpoint](https://docs.paylaterapp.com/authentication.html). Pass it as `Authorization: Bearer <ACCESS_TOKEN>`.\n\n## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n| `Content-Type` | `application/json` |\n\n**Body parameters**\n\n- `outlet_id` (Number, required): Storefront / outlet identifier.\n- `mobile_number` (String, required): Registered customer mobile number.\n- `order_amount` (Decimal, required): Requested payment amount.\n- `merchant_order_reference` (String, required): Your unique transaction reference.\n- `expiry_minutes` (Integer, required): Expiry time in minutes.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>' \\\n  --header 'Content-Type: application/json' \\\n  --data '{ \"outlet_id\": 1000000061, \"mobile_number\": \"97451118021\", \"order_amount\": 3015, \"merchant_order_reference\": \"ORD-5\", \"expiry_minutes\": 20 }'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay',\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Content-Type': 'application/json',\n    },\n    body: JSON.stringify({\n      outlet_id: 1000000061,\n      mobile_number: '97451118021',\n      order_amount: 3015,\n      merchant_order_reference: 'ORD-5',\n      expiry_minutes: 20,\n    }),\n  },\n);\n\nconst data = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.post(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"outlet_id\": 1000000061,\n        \"mobile_number\": \"97451118021\",\n        \"order_amount\": 3015,\n        \"merchant_order_reference\": \"ORD-5\",\n        \"expiry_minutes\": 20,\n    },\n)\n\ndata = resp.json()\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n        'Content-Type: application/json',\n    ],\n    CURLOPT_POSTFIELDS => json_encode([\n        'outlet_id' => 1000000061,\n        'mobile_number' => '97451118021',\n        'order_amount' => 3015,\n        'merchant_order_reference' => 'ORD-5',\n        'expiry_minutes' => 20,\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n```\n\n## Response\n\nA `200 OK` confirms the request was pushed to the shopper’s PayLater app.\n\n**200 OK**\n\n```json\n{\n  \"merchant_order_reference\": \"ORD-5\",\n  \"status\": \"SUCCESS\",\n  \"expiry_date\": \"2026-04-28T10:15:41.477013291\"\n}\n```\n\n## Errors\n\nA duplicate `merchant_order_reference` returns `409`:\n\n```json\n{\n  \"status\": \"FAILED\",\n  \"message\": \"A Request-to-Pay already exists for this merchant order reference.\",\n  \"http_code\": 409,\n  \"error_reference\": \"RTP-409-DUPLICATE-REFERENCE\"\n}\n```\n\nAn upstream failure returns `404`:\n\n```json\n{\n  \"status\": \"FAILED\",\n  \"message\": \"Unable to process Request-to-Pay right now. Please try again later.\",\n  \"http_code\": 404,\n  \"error_reference\": \"RTP-UPSTREAM-ERROR\"\n}\n```\n\nSee [Error handling & response codes](https://docs.paylaterapp.com/errors.html) for the full catalogue.\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/request-to-pay.html",
     "markdown": "# Request to Pay (POS)\n\nPush a payment request straight to a registered shopper’s PayLater app by mobile number — ideal for in-store / point of sale. The shopper approves and pays in-app.\n\n**POST** `/api/paylater/merchant-portal/v2/web-checkout/request-to-pay`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay`\n\n> **Info: Authentication**\n>\n> Requires a bearer token from the [OAuth 2.0 token endpoint](https://docs.paylaterapp.com/authentication.html). Pass it as `Authorization: Bearer <ACCESS_TOKEN>`."
    },
    {
     "heading": "Request",
     "anchor": "request",
     "url": "https://docs.paylaterapp.com/request-to-pay.html#request",
     "markdown": "## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n| `Content-Type` | `application/json` |\n\n**Body parameters**\n\n- `outlet_id` (Number, required): Storefront / outlet identifier.\n- `mobile_number` (String, required): Registered customer mobile number.\n- `order_amount` (Decimal, required): Requested payment amount.\n- `merchant_order_reference` (String, required): Your unique transaction reference.\n- `expiry_minutes` (Integer, required): Expiry time in minutes.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>' \\\n  --header 'Content-Type: application/json' \\\n  --data '{ \"outlet_id\": 1000000061, \"mobile_number\": \"97451118021\", \"order_amount\": 3015, \"merchant_order_reference\": \"ORD-5\", \"expiry_minutes\": 20 }'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay',\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Content-Type': 'application/json',\n    },\n    body: JSON.stringify({\n      outlet_id: 1000000061,\n      mobile_number: '97451118021',\n      order_amount: 3015,\n      merchant_order_reference: 'ORD-5',\n      expiry_minutes: 20,\n    }),\n  },\n);\n\nconst data = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.post(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"outlet_id\": 1000000061,\n        \"mobile_number\": \"97451118021\",\n        \"order_amount\": 3015,\n        \"merchant_order_reference\": \"ORD-5\",\n        \"expiry_minutes\": 20,\n    },\n)\n\ndata = resp.json()\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n        'Content-Type: application/json',\n    ],\n    CURLOPT_POSTFIELDS => json_encode([\n        'outlet_id' => 1000000061,\n        'mobile_number' => '97451118021',\n        'order_amount' => 3015,\n        'merchant_order_reference' => 'ORD-5',\n        'expiry_minutes' => 20,\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n```"
    },
    {
     "heading": "Response",
     "anchor": "response",
     "url": "https://docs.paylaterapp.com/request-to-pay.html#response",
     "markdown": "## Response\n\nA `200 OK` confirms the request was pushed to the shopper’s PayLater app.\n\n**200 OK**\n\n```json\n{\n  \"merchant_order_reference\": \"ORD-5\",\n  \"status\": \"SUCCESS\",\n  \"expiry_date\": \"2026-04-28T10:15:41.477013291\"\n}\n```"
    },
    {
     "heading": "Errors",
     "anchor": "errors",
     "url": "https://docs.paylaterapp.com/request-to-pay.html#errors",
     "markdown": "## Errors\n\nA duplicate `merchant_order_reference` returns `409`:\n\n```json\n{\n  \"status\": \"FAILED\",\n  \"message\": \"A Request-to-Pay already exists for this merchant order reference.\",\n  \"http_code\": 409,\n  \"error_reference\": \"RTP-409-DUPLICATE-REFERENCE\"\n}\n```\n\nAn upstream failure returns `404`:\n\n```json\n{\n  \"status\": \"FAILED\",\n  \"message\": \"Unable to process Request-to-Pay right now. Please try again later.\",\n  \"http_code\": 404,\n  \"error_reference\": \"RTP-UPSTREAM-ERROR\"\n}\n```\n\nSee [Error handling & response codes](https://docs.paylaterapp.com/errors.html) for the full catalogue."
    }
   ],
   "endpoints": [
    {
     "method": "POST",
     "path": "/api/paylater/merchant-portal/v2/web-checkout/request-to-pay",
     "urls": {
      "sandbox": "https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay",
      "production": "https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/request-to-pay"
     },
     "title": "Request to Pay (POS)",
     "heading": "",
     "params": [
      {
       "name": "outlet_id",
       "type": "Number",
       "required": true,
       "description": "Storefront / outlet identifier.",
       "group": "Body parameters"
      },
      {
       "name": "mobile_number",
       "type": "String",
       "required": true,
       "description": "Registered customer mobile number.",
       "group": "Body parameters"
      },
      {
       "name": "order_amount",
       "type": "Decimal",
       "required": true,
       "description": "Requested payment amount.",
       "group": "Body parameters"
      },
      {
       "name": "merchant_order_reference",
       "type": "String",
       "required": true,
       "description": "Your unique transaction reference.",
       "group": "Body parameters"
      },
      {
       "name": "expiry_minutes",
       "type": "Integer",
       "required": true,
       "description": "Expiry time in minutes.",
       "group": "Body parameters"
      }
     ],
     "kind": "api"
    }
   ]
  },
  {
   "slug": "check-payment-status",
   "title": "Check Payment Status",
   "section": "API Reference",
   "description": "Poll the live status of any PayLater order by its merchant order ID.",
   "keywords": "status poll order check state pending success failed get order_id 0 1 2 3",
   "url": "https://docs.paylaterapp.com/check-payment-status.html",
   "markdown_url": "https://docs.paylaterapp.com/check-payment-status.md",
   "markdown": "# Check Payment Status\n\nPoll the live status of an order by its `order_id`.\n\n**GET** `/api/paylater/merchant-portal/v2/web-checkout/status`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status`\n\n> **Info: Authentication**\n>\n> All requests require a bearer token from the [OAuth 2.0 token endpoint](https://docs.paylaterapp.com/authentication.html). Pass it as `Authorization: Bearer <ACCESS_TOKEN>`.\n\n## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n\n**Query parameters**\n\n- `order_id` (String, required): Unique order ID.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status?order_id=000072152' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status?order_id=000072152',\n  {\n    method: 'GET',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n    },\n  },\n);\n\nconst status = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.get(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n    },\n    params={\n        \"order_id\": \"000072152\",\n    },\n)\n\nstatus = resp.json()\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status?order_id=000072152');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n    ],\n]);\n\n$status = json_decode(curl_exec($ch), true);\n```\n\n## Response\n\nThe response reflects the current state of the order. The `status` field is the canonical indicator — see [Status codes](https://docs.paylaterapp.com/check-payment-status.html#status-codes) below.\n\n**Not initiated**\n\n```json\n{\n  \"message\": \"Order not initiated\",\n  \"status\": 0\n}\n```\n\n**Pending**\n\n```json\n{\n  \"payLaterOrderId\": \"PL1744792493935483\",\n  \"message\": \"pending\",\n  \"merchantReference\": \"000072152\",\n  \"status\": 1\n}\n```\n\n**Success**\n\n```json\n{\n  \"payLaterOrderId\": \"PL1744792493935483\",\n  \"message\": \"success\",\n  \"merchantReference\": \"000072152\",\n  \"status\": 2\n}\n```\n\n**Failed**\n\n```json\n{\n  \"payLaterOrderId\": \"PL1744792493935483\",\n  \"message\": \"failed\",\n  \"merchantReference\": \"000072152\",\n  \"status\": 3\n}\n```\n\n## Status codes\n\n| status | Meaning |\n| --- | --- |\n| `0` | Customer did not proceed |\n| `1` | Pending |\n| `2` | Success |\n| `3` | Failed |\n\n## Errors\n\n```json\n{\n  \"error\": \"Order ID is required\"\n}\n```\n\n> **Tip: Prefer webhooks**\n>\n> For production, subscribe to [webhooks](https://docs.paylaterapp.com/webhooks.html) instead of tight polling.\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/check-payment-status.html",
     "markdown": "# Check Payment Status\n\nPoll the live status of an order by its `order_id`.\n\n**GET** `/api/paylater/merchant-portal/v2/web-checkout/status`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status`\n\n> **Info: Authentication**\n>\n> All requests require a bearer token from the [OAuth 2.0 token endpoint](https://docs.paylaterapp.com/authentication.html). Pass it as `Authorization: Bearer <ACCESS_TOKEN>`."
    },
    {
     "heading": "Request",
     "anchor": "request",
     "url": "https://docs.paylaterapp.com/check-payment-status.html#request",
     "markdown": "## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n\n**Query parameters**\n\n- `order_id` (String, required): Unique order ID.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status?order_id=000072152' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status?order_id=000072152',\n  {\n    method: 'GET',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n    },\n  },\n);\n\nconst status = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.get(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n    },\n    params={\n        \"order_id\": \"000072152\",\n    },\n)\n\nstatus = resp.json()\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status?order_id=000072152');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n    ],\n]);\n\n$status = json_decode(curl_exec($ch), true);\n```"
    },
    {
     "heading": "Response",
     "anchor": "response",
     "url": "https://docs.paylaterapp.com/check-payment-status.html#response",
     "markdown": "## Response\n\nThe response reflects the current state of the order. The `status` field is the canonical indicator — see [Status codes](https://docs.paylaterapp.com/check-payment-status.html#status-codes) below.\n\n**Not initiated**\n\n```json\n{\n  \"message\": \"Order not initiated\",\n  \"status\": 0\n}\n```\n\n**Pending**\n\n```json\n{\n  \"payLaterOrderId\": \"PL1744792493935483\",\n  \"message\": \"pending\",\n  \"merchantReference\": \"000072152\",\n  \"status\": 1\n}\n```\n\n**Success**\n\n```json\n{\n  \"payLaterOrderId\": \"PL1744792493935483\",\n  \"message\": \"success\",\n  \"merchantReference\": \"000072152\",\n  \"status\": 2\n}\n```\n\n**Failed**\n\n```json\n{\n  \"payLaterOrderId\": \"PL1744792493935483\",\n  \"message\": \"failed\",\n  \"merchantReference\": \"000072152\",\n  \"status\": 3\n}\n```"
    },
    {
     "heading": "Status codes",
     "anchor": "status-codes",
     "url": "https://docs.paylaterapp.com/check-payment-status.html#status-codes",
     "markdown": "## Status codes\n\n| status | Meaning |\n| --- | --- |\n| `0` | Customer did not proceed |\n| `1` | Pending |\n| `2` | Success |\n| `3` | Failed |"
    },
    {
     "heading": "Errors",
     "anchor": "errors",
     "url": "https://docs.paylaterapp.com/check-payment-status.html#errors",
     "markdown": "## Errors\n\n```json\n{\n  \"error\": \"Order ID is required\"\n}\n```\n\n> **Tip: Prefer webhooks**\n>\n> For production, subscribe to [webhooks](https://docs.paylaterapp.com/webhooks.html) instead of tight polling."
    }
   ],
   "endpoints": [
    {
     "method": "GET",
     "path": "/api/paylater/merchant-portal/v2/web-checkout/status",
     "urls": {
      "sandbox": "https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status",
      "production": "https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/status"
     },
     "title": "Check Payment Status",
     "heading": "",
     "params": [
      {
       "name": "order_id",
       "type": "String",
       "required": true,
       "description": "Unique order ID.",
       "group": "Query parameters"
      }
     ],
     "kind": "api"
    }
   ]
  },
  {
   "slug": "get-shopper-information",
   "title": "Get Shopper Information",
   "section": "API Reference",
   "description": "Look up a shopper’s PayLater spending limit by mobile number and email.",
   "keywords": "shopper customer spending limit currentSpendingLimit allowedSpendingLimit mobile email lookup shopperId",
   "url": "https://docs.paylaterapp.com/get-shopper-information.html",
   "markdown_url": "https://docs.paylaterapp.com/get-shopper-information.md",
   "markdown": "# Get Shopper Information\n\nLook up a shopper’s current and allowed spending limits.\n\n**GET** `/api/paylater/merchant-portal/v2/web-checkout/shopper`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper`\n\n> **Info: Authentication**\n>\n> All requests require a bearer token from the [OAuth 2.0 token endpoint](https://docs.paylaterapp.com/authentication.html). Pass it as `Authorization: Bearer <ACCESS_TOKEN>`.\n\n## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n\n**Query parameters**\n\n- `mobile_number` (String, required): Shopper’s mobile number.\n- `email` (String, required): Shopper’s email address.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper?mobile_number=97412345678&email=test@sample.com' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>'\n```\n\n**Node.js**\n\n```javascript\nconst params = new URLSearchParams({\n  mobile_number: '97412345678',\n  email: 'test@sample.com',\n});\n\nconst res = await fetch(\n  `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper?${params}`,\n  {\n    method: 'GET',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n    },\n  },\n);\n\nconst shopper = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.get(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n    },\n    params={\n        \"mobile_number\": \"97412345678\",\n        \"email\": \"test@sample.com\",\n    },\n)\n\nshopper = resp.json()\n```\n\n**PHP**\n\n```php\n$query = http_build_query([\n    'mobile_number' => '97412345678',\n    'email' => 'test@sample.com',\n]);\n\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper?' . $query);\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n```\n\n## Response\n\nA `200 OK` returns the shopper’s identifiers and spending limits.\n\n**200 OK**\n\n```json\n{\n  \"shopperId\": \"uuid\",\n  \"email\": \"test@sample.com\",\n  \"mobileNumber\": \"97412345678\",\n  \"currentSpendingLimit\": 925.0,\n  \"allowedSpendingLimit\": 4000.0\n}\n```\n\n## Errors\n\n```json\n{\n  \"message\": \"Shopper not found\"\n}\n```\n\n```json\n{\n  \"message\": \"Missing API key in request headers\"\n}\n```\n\nSee [Error handling & response codes](https://docs.paylaterapp.com/errors.html) for the full catalogue.\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/get-shopper-information.html",
     "markdown": "# Get Shopper Information\n\nLook up a shopper’s current and allowed spending limits.\n\n**GET** `/api/paylater/merchant-portal/v2/web-checkout/shopper`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper`\n\n> **Info: Authentication**\n>\n> All requests require a bearer token from the [OAuth 2.0 token endpoint](https://docs.paylaterapp.com/authentication.html). Pass it as `Authorization: Bearer <ACCESS_TOKEN>`."
    },
    {
     "heading": "Request",
     "anchor": "request",
     "url": "https://docs.paylaterapp.com/get-shopper-information.html#request",
     "markdown": "## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n\n**Query parameters**\n\n- `mobile_number` (String, required): Shopper’s mobile number.\n- `email` (String, required): Shopper’s email address.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper?mobile_number=97412345678&email=test@sample.com' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>'\n```\n\n**Node.js**\n\n```javascript\nconst params = new URLSearchParams({\n  mobile_number: '97412345678',\n  email: 'test@sample.com',\n});\n\nconst res = await fetch(\n  `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper?${params}`,\n  {\n    method: 'GET',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n    },\n  },\n);\n\nconst shopper = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.get(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n    },\n    params={\n        \"mobile_number\": \"97412345678\",\n        \"email\": \"test@sample.com\",\n    },\n)\n\nshopper = resp.json()\n```\n\n**PHP**\n\n```php\n$query = http_build_query([\n    'mobile_number' => '97412345678',\n    'email' => 'test@sample.com',\n]);\n\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper?' . $query);\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n```"
    },
    {
     "heading": "Response",
     "anchor": "response",
     "url": "https://docs.paylaterapp.com/get-shopper-information.html#response",
     "markdown": "## Response\n\nA `200 OK` returns the shopper’s identifiers and spending limits.\n\n**200 OK**\n\n```json\n{\n  \"shopperId\": \"uuid\",\n  \"email\": \"test@sample.com\",\n  \"mobileNumber\": \"97412345678\",\n  \"currentSpendingLimit\": 925.0,\n  \"allowedSpendingLimit\": 4000.0\n}\n```"
    },
    {
     "heading": "Errors",
     "anchor": "errors",
     "url": "https://docs.paylaterapp.com/get-shopper-information.html#errors",
     "markdown": "## Errors\n\n```json\n{\n  \"message\": \"Shopper not found\"\n}\n```\n\n```json\n{\n  \"message\": \"Missing API key in request headers\"\n}\n```\n\nSee [Error handling & response codes](https://docs.paylaterapp.com/errors.html) for the full catalogue."
    }
   ],
   "endpoints": [
    {
     "method": "GET",
     "path": "/api/paylater/merchant-portal/v2/web-checkout/shopper",
     "urls": {
      "sandbox": "https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper",
      "production": "https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/shopper"
     },
     "title": "Get Shopper Information",
     "heading": "",
     "params": [
      {
       "name": "mobile_number",
       "type": "String",
       "required": true,
       "description": "Shopper’s mobile number.",
       "group": "Query parameters"
      },
      {
       "name": "email",
       "type": "String",
       "required": true,
       "description": "Shopper’s email address.",
       "group": "Query parameters"
      }
     ],
     "kind": "api"
    }
   ]
  },
  {
   "slug": "get-transaction-by-order-id",
   "title": "Get Transaction by Order ID",
   "section": "API Reference",
   "description": "Retrieve full customer, loan, and status details for a single PayLater order.",
   "keywords": "transaction order details customer loan emi grantedAmount installmentAmount noOfTerms orderStatus get",
   "url": "https://docs.paylaterapp.com/get-transaction-by-order-id.html",
   "markdown_url": "https://docs.paylaterapp.com/get-transaction-by-order-id.md",
   "markdown": "# Get Transaction by Order ID\n\nRetrieve full customer, loan/instalment, and status details for one order.\n\n**GET** `/api/paylater/merchant-portal/v2/web-checkout/transactions/{order_id}`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/{order_id}`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/{order_id}`\n\n> **Info: Authentication**\n>\n> All requests require a bearer token from the [OAuth 2.0 token endpoint](https://docs.paylaterapp.com/authentication.html). Pass it as `Authorization: Bearer <ACCESS_TOKEN>`.\n\n## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n\n**Path parameters**\n\n- `order_id` (String, required): Your merchant order ID.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/000072152' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/000072152',\n  {\n    method: 'GET',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n    },\n  },\n);\n\nconst transaction = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.get(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/000072152\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n    },\n)\n\ntransaction = resp.json()\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/000072152');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n    ],\n]);\n\n$transaction = json_decode(curl_exec($ch), true);\n```\n\n## Response\n\nA `200 OK` returns the customer, loan/instalment, and status details for the order.\n\n**200 OK**\n\n```json\n{\n  \"id\": 10129,\n  \"customer\": {\n    \"fullName\": \"JOHN SMITH\",\n    \"mobileNumber\": \"97466955189\",\n    \"email\": \"john@test.com\",\n    \"allowedSpendingLimit\": \"25000.0\"\n  },\n  \"loan\": {\n    \"emi\": 154.75,\n    \"grantedAmount\": 619.00,\n    \"installmentAmount\": 154.75,\n    \"noOfTerms\": 4\n  },\n  \"orderStatus\": {\n    \"message\": \"pending\",\n    \"status\": 1\n  }\n}\n```\n\nIf the order exists but the customer has not yet started the checkout, the response indicates that the order has not been initiated.\n\n**Not initiated**\n\n```json\n{\n  \"message\": \"Order not initiated\",\n  \"status\": 0\n}\n```\n\n## Errors\n\n```json\n{\n  \"message\": \"Order not initiated\"\n}\n```\n\n```json\n{\n  \"message\": \"Order ID not found\"\n}\n```\n\nSee [Error handling & response codes](https://docs.paylaterapp.com/errors.html) for the full catalogue.\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/get-transaction-by-order-id.html",
     "markdown": "# Get Transaction by Order ID\n\nRetrieve full customer, loan/instalment, and status details for one order.\n\n**GET** `/api/paylater/merchant-portal/v2/web-checkout/transactions/{order_id}`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/{order_id}`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/{order_id}`\n\n> **Info: Authentication**\n>\n> All requests require a bearer token from the [OAuth 2.0 token endpoint](https://docs.paylaterapp.com/authentication.html). Pass it as `Authorization: Bearer <ACCESS_TOKEN>`."
    },
    {
     "heading": "Request",
     "anchor": "request",
     "url": "https://docs.paylaterapp.com/get-transaction-by-order-id.html#request",
     "markdown": "## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n\n**Path parameters**\n\n- `order_id` (String, required): Your merchant order ID.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/000072152' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/000072152',\n  {\n    method: 'GET',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n    },\n  },\n);\n\nconst transaction = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.get(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/000072152\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n    },\n)\n\ntransaction = resp.json()\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/000072152');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n    ],\n]);\n\n$transaction = json_decode(curl_exec($ch), true);\n```"
    },
    {
     "heading": "Response",
     "anchor": "response",
     "url": "https://docs.paylaterapp.com/get-transaction-by-order-id.html#response",
     "markdown": "## Response\n\nA `200 OK` returns the customer, loan/instalment, and status details for the order.\n\n**200 OK**\n\n```json\n{\n  \"id\": 10129,\n  \"customer\": {\n    \"fullName\": \"JOHN SMITH\",\n    \"mobileNumber\": \"97466955189\",\n    \"email\": \"john@test.com\",\n    \"allowedSpendingLimit\": \"25000.0\"\n  },\n  \"loan\": {\n    \"emi\": 154.75,\n    \"grantedAmount\": 619.00,\n    \"installmentAmount\": 154.75,\n    \"noOfTerms\": 4\n  },\n  \"orderStatus\": {\n    \"message\": \"pending\",\n    \"status\": 1\n  }\n}\n```\n\nIf the order exists but the customer has not yet started the checkout, the response indicates that the order has not been initiated.\n\n**Not initiated**\n\n```json\n{\n  \"message\": \"Order not initiated\",\n  \"status\": 0\n}\n```"
    },
    {
     "heading": "Errors",
     "anchor": "errors",
     "url": "https://docs.paylaterapp.com/get-transaction-by-order-id.html#errors",
     "markdown": "## Errors\n\n```json\n{\n  \"message\": \"Order not initiated\"\n}\n```\n\n```json\n{\n  \"message\": \"Order ID not found\"\n}\n```\n\nSee [Error handling & response codes](https://docs.paylaterapp.com/errors.html) for the full catalogue."
    }
   ],
   "endpoints": [
    {
     "method": "GET",
     "path": "/api/paylater/merchant-portal/v2/web-checkout/transactions/{order_id}",
     "urls": {
      "sandbox": "https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/{order_id}",
      "production": "https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions/{order_id}"
     },
     "title": "Get Transaction by Order ID",
     "heading": "",
     "params": [
      {
       "name": "order_id",
       "type": "String",
       "required": true,
       "description": "Your merchant order ID.",
       "group": "Path parameters"
      }
     ],
     "kind": "api"
    }
   ]
  },
  {
   "slug": "get-transactions",
   "title": "Get Transactions",
   "section": "API Reference",
   "description": "Retrieve a paginated, filterable list of your PayLater transactions.",
   "keywords": "transactions list paginated page size search_text start_date end_date status filter get",
   "url": "https://docs.paylaterapp.com/get-transactions.html",
   "markdown_url": "https://docs.paylaterapp.com/get-transactions.md",
   "markdown": "# Get Transactions\n\nRetrieve a paginated, filterable list of transactions for your outlet.\n\n**GET** `/api/paylater/merchant-portal/v2/web-checkout/transactions`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions`\n\n> **Info: Authentication**\n>\n> All requests require a bearer token from the [OAuth 2.0 token endpoint](https://docs.paylaterapp.com/authentication.html). Pass it as `Authorization: Bearer <ACCESS_TOKEN>`.\n\n## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n\n**Query parameters**\n\n- `size` (Int, required): Records per page.\n- `page` (Int, required): Page number.\n- `search_text` (String, optional): Search by customer name / ID.\n- `start_date` (Date, optional): Start date filter (YYYY-MM-DD).\n- `end_date` (Date, optional): End date filter (YYYY-MM-DD).\n- `status` (String, optional): Transaction status filter.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions?size=20&page=0' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions?size=20&page=0',\n  {\n    method: 'GET',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n    },\n  },\n);\n\nconst transactions = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.get(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n    },\n    params={\n        \"size\": 20,\n        \"page\": 0,\n    },\n)\n\ntransactions = resp.json()\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions?size=20&page=0');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n```\n\n## Response\n\nReturns a paginated list of transactions, each with customer, loan, and status details (see [Get Transaction by Order ID](https://docs.paylaterapp.com/get-transaction-by-order-id.html) for the per-item shape).\n\n## Errors\n\n```json\n{\n  \"error\": \"Invalid API Key\"\n}\n```\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/get-transactions.html",
     "markdown": "# Get Transactions\n\nRetrieve a paginated, filterable list of transactions for your outlet.\n\n**GET** `/api/paylater/merchant-portal/v2/web-checkout/transactions`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions`\n\n> **Info: Authentication**\n>\n> All requests require a bearer token from the [OAuth 2.0 token endpoint](https://docs.paylaterapp.com/authentication.html). Pass it as `Authorization: Bearer <ACCESS_TOKEN>`."
    },
    {
     "heading": "Request",
     "anchor": "request",
     "url": "https://docs.paylaterapp.com/get-transactions.html#request",
     "markdown": "## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n\n**Query parameters**\n\n- `size` (Int, required): Records per page.\n- `page` (Int, required): Page number.\n- `search_text` (String, optional): Search by customer name / ID.\n- `start_date` (Date, optional): Start date filter (YYYY-MM-DD).\n- `end_date` (Date, optional): End date filter (YYYY-MM-DD).\n- `status` (String, optional): Transaction status filter.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions?size=20&page=0' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions?size=20&page=0',\n  {\n    method: 'GET',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n    },\n  },\n);\n\nconst transactions = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.get(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n    },\n    params={\n        \"size\": 20,\n        \"page\": 0,\n    },\n)\n\ntransactions = resp.json()\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions?size=20&page=0');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n```"
    },
    {
     "heading": "Response",
     "anchor": "response",
     "url": "https://docs.paylaterapp.com/get-transactions.html#response",
     "markdown": "## Response\n\nReturns a paginated list of transactions, each with customer, loan, and status details (see [Get Transaction by Order ID](https://docs.paylaterapp.com/get-transaction-by-order-id.html) for the per-item shape)."
    },
    {
     "heading": "Errors",
     "anchor": "errors",
     "url": "https://docs.paylaterapp.com/get-transactions.html#errors",
     "markdown": "## Errors\n\n```json\n{\n  \"error\": \"Invalid API Key\"\n}\n```"
    }
   ],
   "endpoints": [
    {
     "method": "GET",
     "path": "/api/paylater/merchant-portal/v2/web-checkout/transactions",
     "urls": {
      "sandbox": "https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions",
      "production": "https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/transactions"
     },
     "title": "Get Transactions",
     "heading": "",
     "params": [
      {
       "name": "size",
       "type": "Int",
       "required": true,
       "description": "Records per page.",
       "group": "Query parameters"
      },
      {
       "name": "page",
       "type": "Int",
       "required": true,
       "description": "Page number.",
       "group": "Query parameters"
      },
      {
       "name": "search_text",
       "type": "String",
       "required": false,
       "description": "Search by customer name / ID.",
       "group": "Query parameters"
      },
      {
       "name": "start_date",
       "type": "Date",
       "required": false,
       "description": "Start date filter (YYYY-MM-DD).",
       "group": "Query parameters"
      },
      {
       "name": "end_date",
       "type": "Date",
       "required": false,
       "description": "End date filter (YYYY-MM-DD).",
       "group": "Query parameters"
      },
      {
       "name": "status",
       "type": "String",
       "required": false,
       "description": "Transaction status filter.",
       "group": "Query parameters"
      }
     ],
     "kind": "api"
    }
   ]
  },
  {
   "slug": "full-refund",
   "title": "Full Refund",
   "section": "API Reference",
   "description": "Refund an entire PayLater order within the refund window.",
   "keywords": "refund full order_id reverse return 29 days 10 minutes post",
   "url": "https://docs.paylaterapp.com/full-refund.html",
   "markdown_url": "https://docs.paylaterapp.com/full-refund.md",
   "markdown": "# Full Refund\n\nRefund an entire order.\n\n**POST** `/api/paylater/merchant-portal/v2/web-checkout/refund`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund`\n\n> **Info: Refund rules**\n>\n> Orders can be refunded within 29 days, and no sooner than 10 minutes after the transaction. To refund part of an order, see [Partial Refund](https://docs.paylaterapp.com/partial-refund.html).\n\n## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n| `Content-Type` | `application/json` |\n\n**Body parameters**\n\n- `order_id` (String, required): Your merchant Order ID.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>' \\\n  --header 'Content-Type: application/json' \\\n  --data '{ \"order_id\": \"ORD-TEST-1\" }'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund',\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Content-Type': 'application/json',\n    },\n    body: JSON.stringify({ order_id: 'ORD-TEST-1' }),\n  },\n);\n\nconst data = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.post(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\"order_id\": \"ORD-TEST-1\"},\n)\n\ndata = resp.json()\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n        'Content-Type: application/json',\n    ],\n    CURLOPT_POSTFIELDS => json_encode([\n        'order_id' => 'ORD-TEST-1',\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n```\n\n## Response\n\nA `200 OK` confirms the refund request was accepted.\n\n**200 OK**\n\n```json\n{\n  \"message\": \"Refund request accepted for reference Id: ORD-TEST-1\"\n}\n```\n\n## Errors\n\n| Example response | Cause |\n| --- | --- |\n| `{ \"error\": \"Transaction Reference is required\" }` | Missing `order_id`. |\n| `{ \"error\": \"Invalid API Key\" }` | Bad/missing token. |\n| `{ \"error\": \"Refund Error\", \"message\": \"Order contains transactions other than down payment and cannot be refunded.\" }` | Not refundable. |\n| `{ \"error\": \"Refund Error\", \"message\": \"Transaction happened less than 10 minutes ago. Please try again later.\" }` | Too soon. |\n| `{ \"error\": \"Refund Error\", \"message\": \"Invalid Transaction Reference.\" }` | Unknown order. |\n\nSee [Error handling & response codes](https://docs.paylaterapp.com/errors.html) for the full catalogue.\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/full-refund.html",
     "markdown": "# Full Refund\n\nRefund an entire order.\n\n**POST** `/api/paylater/merchant-portal/v2/web-checkout/refund`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund`\n\n> **Info: Refund rules**\n>\n> Orders can be refunded within 29 days, and no sooner than 10 minutes after the transaction. To refund part of an order, see [Partial Refund](https://docs.paylaterapp.com/partial-refund.html)."
    },
    {
     "heading": "Request",
     "anchor": "request",
     "url": "https://docs.paylaterapp.com/full-refund.html#request",
     "markdown": "## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n| `Content-Type` | `application/json` |\n\n**Body parameters**\n\n- `order_id` (String, required): Your merchant Order ID.\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>' \\\n  --header 'Content-Type: application/json' \\\n  --data '{ \"order_id\": \"ORD-TEST-1\" }'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund',\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Content-Type': 'application/json',\n    },\n    body: JSON.stringify({ order_id: 'ORD-TEST-1' }),\n  },\n);\n\nconst data = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.post(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\"order_id\": \"ORD-TEST-1\"},\n)\n\ndata = resp.json()\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n        'Content-Type: application/json',\n    ],\n    CURLOPT_POSTFIELDS => json_encode([\n        'order_id' => 'ORD-TEST-1',\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n```"
    },
    {
     "heading": "Response",
     "anchor": "response",
     "url": "https://docs.paylaterapp.com/full-refund.html#response",
     "markdown": "## Response\n\nA `200 OK` confirms the refund request was accepted.\n\n**200 OK**\n\n```json\n{\n  \"message\": \"Refund request accepted for reference Id: ORD-TEST-1\"\n}\n```"
    },
    {
     "heading": "Errors",
     "anchor": "errors",
     "url": "https://docs.paylaterapp.com/full-refund.html#errors",
     "markdown": "## Errors\n\n| Example response | Cause |\n| --- | --- |\n| `{ \"error\": \"Transaction Reference is required\" }` | Missing `order_id`. |\n| `{ \"error\": \"Invalid API Key\" }` | Bad/missing token. |\n| `{ \"error\": \"Refund Error\", \"message\": \"Order contains transactions other than down payment and cannot be refunded.\" }` | Not refundable. |\n| `{ \"error\": \"Refund Error\", \"message\": \"Transaction happened less than 10 minutes ago. Please try again later.\" }` | Too soon. |\n| `{ \"error\": \"Refund Error\", \"message\": \"Invalid Transaction Reference.\" }` | Unknown order. |\n\nSee [Error handling & response codes](https://docs.paylaterapp.com/errors.html) for the full catalogue."
    }
   ],
   "endpoints": [
    {
     "method": "POST",
     "path": "/api/paylater/merchant-portal/v2/web-checkout/refund",
     "urls": {
      "sandbox": "https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund",
      "production": "https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund"
     },
     "title": "Full Refund",
     "heading": "",
     "params": [
      {
       "name": "order_id",
       "type": "String",
       "required": true,
       "description": "Your merchant Order ID.",
       "group": "Body parameters"
      }
     ],
     "kind": "api"
    }
   ]
  },
  {
   "slug": "partial-refund",
   "title": "Partial Refund",
   "section": "API Reference",
   "description": "Refund part of a PayLater order within the refund window.",
   "keywords": "partial refund amount order_id reverse return post",
   "url": "https://docs.paylaterapp.com/partial-refund.html",
   "markdown_url": "https://docs.paylaterapp.com/partial-refund.md",
   "markdown": "# Partial Refund\n\nRefund part of an order.\n\n**POST** `/api/paylater/merchant-portal/v2/web-checkout/refund`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund`\n\n> **Info: Refund rules**\n>\n> The same window as a full refund applies (within 29 days, no sooner than 10 minutes after the transaction). See [Full Refund](https://docs.paylaterapp.com/full-refund.html).\n\n## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n| `Content-Type` | `application/json` |\n\n**Body parameters**\n\n- `order_id` (String, required): Your merchant Order ID.\n- `amount` (String, required): Partial amount (less than the total order value).\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>' \\\n  --header 'Content-Type: application/json' \\\n  --data '{ \"order_id\": \"O-4756\", \"amount\": \"120.00\" }'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund',\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Content-Type': 'application/json',\n    },\n    body: JSON.stringify({\n      order_id: 'O-4756',\n      amount: '120.00',\n    }),\n  },\n);\n\nconst data = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.post(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"order_id\": \"O-4756\",\n        \"amount\": \"120.00\",\n    },\n)\n\ndata = resp.json()\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n        'Content-Type: application/json',\n    ],\n    CURLOPT_POSTFIELDS => json_encode([\n        'order_id' => 'O-4756',\n        'amount' => '120.00',\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n```\n\n## Response\n\n**200 OK**\n\n```json\n{\n  \"message\": \"Partial Refund request accepted for reference Id: O-4756\"\n}\n```\n\n## Errors\n\n| Example response | Cause |\n| --- | --- |\n| `{ \"error\": \"Transaction Reference is required\" }` | Missing `order_id`. |\n| `{ \"error\": \"Invalid API Key\" }` | Bad/missing token. |\n| `{ \"error\": \"Refund Error\", \"message\": \"Transaction happened less than 10 minutes ago. Please try again later.\" }` | Too soon. |\n| `{ \"error\": \"Refund Error\", \"message\": \"Invalid Transaction Reference.\" }` | Unknown order. |\n| `{ \"error\": \"Refund Error\", \"message\": \"Invalid Transaction type.\" }` | Not a refundable transaction type. |\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/partial-refund.html",
     "markdown": "# Partial Refund\n\nRefund part of an order.\n\n**POST** `/api/paylater/merchant-portal/v2/web-checkout/refund`\n\n- Sandbox: `https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund`\n- Production: `https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund`\n\n> **Info: Refund rules**\n>\n> The same window as a full refund applies (within 29 days, no sooner than 10 minutes after the transaction). See [Full Refund](https://docs.paylaterapp.com/full-refund.html)."
    },
    {
     "heading": "Request",
     "anchor": "request",
     "url": "https://docs.paylaterapp.com/partial-refund.html#request",
     "markdown": "## Request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Authorization` | `Bearer <ACCESS_TOKEN>` |\n| `Content-Type` | `application/json` |\n\n**Body parameters**\n\n- `order_id` (String, required): Your merchant Order ID.\n- `amount` (String, required): Partial amount (less than the total order value).\n\n### Example request\n\n**cURL**\n\n```bash\ncurl --location 'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund' \\\n  --header 'Authorization: Bearer <ACCESS_TOKEN>' \\\n  --header 'Content-Type: application/json' \\\n  --data '{ \"order_id\": \"O-4756\", \"amount\": \"120.00\" }'\n```\n\n**Node.js**\n\n```javascript\nconst res = await fetch(\n  'https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund',\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Content-Type': 'application/json',\n    },\n    body: JSON.stringify({\n      order_id: 'O-4756',\n      amount: '120.00',\n    }),\n  },\n);\n\nconst data = await res.json();\n```\n\n**Python**\n\n```python\nimport requests\n\nresp = requests.post(\n    \"https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"order_id\": \"O-4756\",\n        \"amount\": \"120.00\",\n    },\n)\n\ndata = resp.json()\n```\n\n**PHP**\n\n```php\n$ch = curl_init('https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund');\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . $accessToken,\n        'Content-Type: application/json',\n    ],\n    CURLOPT_POSTFIELDS => json_encode([\n        'order_id' => 'O-4756',\n        'amount' => '120.00',\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n```"
    },
    {
     "heading": "Response",
     "anchor": "response",
     "url": "https://docs.paylaterapp.com/partial-refund.html#response",
     "markdown": "## Response\n\n**200 OK**\n\n```json\n{\n  \"message\": \"Partial Refund request accepted for reference Id: O-4756\"\n}\n```"
    },
    {
     "heading": "Errors",
     "anchor": "errors",
     "url": "https://docs.paylaterapp.com/partial-refund.html#errors",
     "markdown": "## Errors\n\n| Example response | Cause |\n| --- | --- |\n| `{ \"error\": \"Transaction Reference is required\" }` | Missing `order_id`. |\n| `{ \"error\": \"Invalid API Key\" }` | Bad/missing token. |\n| `{ \"error\": \"Refund Error\", \"message\": \"Transaction happened less than 10 minutes ago. Please try again later.\" }` | Too soon. |\n| `{ \"error\": \"Refund Error\", \"message\": \"Invalid Transaction Reference.\" }` | Unknown order. |\n| `{ \"error\": \"Refund Error\", \"message\": \"Invalid Transaction type.\" }` | Not a refundable transaction type. |"
    }
   ],
   "endpoints": [
    {
     "method": "POST",
     "path": "/api/paylater/merchant-portal/v2/web-checkout/refund",
     "urls": {
      "sandbox": "https://connect.uat.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund",
      "production": "https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/web-checkout/refund"
     },
     "title": "Partial Refund",
     "heading": "",
     "params": [
      {
       "name": "order_id",
       "type": "String",
       "required": true,
       "description": "Your merchant Order ID.",
       "group": "Body parameters"
      },
      {
       "name": "amount",
       "type": "String",
       "required": true,
       "description": "Partial amount (less than the total order value).",
       "group": "Body parameters"
      }
     ],
     "kind": "api"
    }
   ]
  },
  {
   "slug": "webhooks",
   "title": "Webhooks",
   "section": "Webhooks",
   "description": "Receive signed, real-time PayLater notifications when an order's status changes, and verify them with HMAC SHA-256.",
   "keywords": "webhook notification event signature hmac sha256 md5 txHash verify merchantId orderId paylaterRef status secret callback",
   "url": "https://docs.paylaterapp.com/webhooks.html",
   "markdown_url": "https://docs.paylaterapp.com/webhooks.md",
   "markdown": "# Webhooks\n\nPayLater sends **real-time notifications** to your server via `HTTP POST` with a JSON body whenever a key transaction event occurs — so you don’t have to poll [Check Payment Status](https://docs.paylaterapp.com/check-payment-status.html).\n\n## Before you start\n\nTo receive webhooks you need:\n\n1. A **public HTTPS endpoint** that accepts `POST` requests.\n2. A **webhook secret** — request one from your PayLater account manager. It’s used to verify that each notification genuinely came from PayLater.\n\n**POST** `https://your-server.com/paylater/webhook · your endpoint`\n\n## The webhook request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Content-Type` | `application/json` |\n\n#### Body\n\n**POST body**\n\n```json\n{\n  \"merchantId\": \"<merchant_id>\",\n  \"orderId\": \"<order_id>\",\n  \"paylaterRef\": \"PL1746499849330726\",\n  \"status\": \"<status>\",\n  \"timestamp\": \"<timestamp>\",\n  \"signature\": \"<signature>\",\n  \"txHash\": \"<tx_hash>\",\n  \"comments\": \"<comments>\"\n}\n```\n\n**Payload fields**\n\n- `merchantId` (String, optional): Your unique merchant identifier.\n- `orderId` (String, optional): The order identifier you supplied when creating the payment.\n- `paylaterRef` (String, optional): PayLater’s internal reference for the order.\n- `status` (String, optional): Order status — one of `success`, `failed`, `pending`.\n- `timestamp` (Long, optional): Epoch timestamp of the event.\n- `txHash` (String, optional): MD5 hash of the concatenated payload (see below).\n- `signature` (String, optional): HMAC SHA-256 of `txHash`, keyed with your webhook secret.\n- `comments` (String, optional): Optional comments / metadata.\n\n## Verifying a webhook\n\n> **Danger: Always verify before trusting a webhook**\n>\n> Never act on a webhook until **both** checks below pass. An unverified request could come from anyone. Respond `200` only after verification succeeds.\n\nVerification is two steps:\n\n1. **Reconstruct `txHash`** — concatenate `merchantId + orderId + status + timestamp + comments`, uppercase the result, and take its **MD5** hash. It must equal the `txHash` field.\n2. **Validate `signature`** — compute the **HMAC SHA-256** of `txHash` using your `merchantWebhookSecret`. It must equal the `signature` field.\n\n**Node.js**\n\n```javascript\nconst crypto = require('crypto');\n\nfunction verifyWebhook(req) {\n  const { merchantId, orderId, status, timestamp, txHash, signature, comments } = req.body;\n\n  // 1. Reconstruct and compare txHash\n  const data = `${merchantId}${orderId}${status}${timestamp}${comments}`.toUpperCase();\n  const computedTxHash = crypto.createHash('md5').update(data).digest('hex');\n  if (computedTxHash !== txHash) return false;\n\n  // 2. Validate the signature\n  const computedSignature = crypto\n    .createHmac('sha256', process.env.WEBHOOK_SECRET)\n    .update(txHash)\n    .digest('hex');\n\n  return computedSignature === signature;\n}\n```\n\n**PHP**\n\n```php\n<?php\n$MERCHANT_SECRET = getenv('WEBHOOK_SECRET');\n$request = json_decode(file_get_contents(\"php://input\"), true);\n\n// 1. Reconstruct txHash\n$dataString = strtoupper(\n    $request['merchantId'] . $request['orderId'] . $request['status'] .\n    $request['timestamp'] . $request['comments']\n);\n$computedTxHash = md5($dataString);\n\n// 2. Validate signature\n$computedSignature = hash_hmac(\"sha256\", $computedTxHash, $MERCHANT_SECRET);\n\nif ($computedTxHash === $request['txHash'] && $computedSignature === $request['signature']) {\n    http_response_code(200);\n    echo json_encode([\"message\" => \"Webhook received successfully\"]);\n} else {\n    http_response_code(403);\n    echo json_encode([\"message\" => \"Invalid signature\"]);\n}\n```\n\n> **Tip: Respond quickly, process asynchronously**\n>\n> Return `200 OK` as soon as verification passes, then do heavy work (order fulfilment, emails) on a background queue. PayLater may retry if it doesn’t receive a timely `2xx`.\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/webhooks.html",
     "markdown": "# Webhooks\n\nPayLater sends **real-time notifications** to your server via `HTTP POST` with a JSON body whenever a key transaction event occurs — so you don’t have to poll [Check Payment Status](https://docs.paylaterapp.com/check-payment-status.html)."
    },
    {
     "heading": "Before you start",
     "anchor": "before-you-start",
     "url": "https://docs.paylaterapp.com/webhooks.html#before-you-start",
     "markdown": "## Before you start\n\nTo receive webhooks you need:\n\n1. A **public HTTPS endpoint** that accepts `POST` requests.\n2. A **webhook secret** — request one from your PayLater account manager. It’s used to verify that each notification genuinely came from PayLater.\n\n**POST** `https://your-server.com/paylater/webhook · your endpoint`"
    },
    {
     "heading": "The webhook request",
     "anchor": "the-webhook-request",
     "url": "https://docs.paylaterapp.com/webhooks.html#the-webhook-request",
     "markdown": "## The webhook request\n\n#### Headers\n\n| Header | Value |\n| --- | --- |\n| `Content-Type` | `application/json` |\n\n#### Body\n\n**POST body**\n\n```json\n{\n  \"merchantId\": \"<merchant_id>\",\n  \"orderId\": \"<order_id>\",\n  \"paylaterRef\": \"PL1746499849330726\",\n  \"status\": \"<status>\",\n  \"timestamp\": \"<timestamp>\",\n  \"signature\": \"<signature>\",\n  \"txHash\": \"<tx_hash>\",\n  \"comments\": \"<comments>\"\n}\n```\n\n**Payload fields**\n\n- `merchantId` (String, optional): Your unique merchant identifier.\n- `orderId` (String, optional): The order identifier you supplied when creating the payment.\n- `paylaterRef` (String, optional): PayLater’s internal reference for the order.\n- `status` (String, optional): Order status — one of `success`, `failed`, `pending`.\n- `timestamp` (Long, optional): Epoch timestamp of the event.\n- `txHash` (String, optional): MD5 hash of the concatenated payload (see below).\n- `signature` (String, optional): HMAC SHA-256 of `txHash`, keyed with your webhook secret.\n- `comments` (String, optional): Optional comments / metadata."
    },
    {
     "heading": "Verifying a webhook",
     "anchor": "verifying-a-webhook",
     "url": "https://docs.paylaterapp.com/webhooks.html#verifying-a-webhook",
     "markdown": "## Verifying a webhook\n\n> **Danger: Always verify before trusting a webhook**\n>\n> Never act on a webhook until **both** checks below pass. An unverified request could come from anyone. Respond `200` only after verification succeeds.\n\nVerification is two steps:\n\n1. **Reconstruct `txHash`** — concatenate `merchantId + orderId + status + timestamp + comments`, uppercase the result, and take its **MD5** hash. It must equal the `txHash` field.\n2. **Validate `signature`** — compute the **HMAC SHA-256** of `txHash` using your `merchantWebhookSecret`. It must equal the `signature` field.\n\n**Node.js**\n\n```javascript\nconst crypto = require('crypto');\n\nfunction verifyWebhook(req) {\n  const { merchantId, orderId, status, timestamp, txHash, signature, comments } = req.body;\n\n  // 1. Reconstruct and compare txHash\n  const data = `${merchantId}${orderId}${status}${timestamp}${comments}`.toUpperCase();\n  const computedTxHash = crypto.createHash('md5').update(data).digest('hex');\n  if (computedTxHash !== txHash) return false;\n\n  // 2. Validate the signature\n  const computedSignature = crypto\n    .createHmac('sha256', process.env.WEBHOOK_SECRET)\n    .update(txHash)\n    .digest('hex');\n\n  return computedSignature === signature;\n}\n```\n\n**PHP**\n\n```php\n<?php\n$MERCHANT_SECRET = getenv('WEBHOOK_SECRET');\n$request = json_decode(file_get_contents(\"php://input\"), true);\n\n// 1. Reconstruct txHash\n$dataString = strtoupper(\n    $request['merchantId'] . $request['orderId'] . $request['status'] .\n    $request['timestamp'] . $request['comments']\n);\n$computedTxHash = md5($dataString);\n\n// 2. Validate signature\n$computedSignature = hash_hmac(\"sha256\", $computedTxHash, $MERCHANT_SECRET);\n\nif ($computedTxHash === $request['txHash'] && $computedSignature === $request['signature']) {\n    http_response_code(200);\n    echo json_encode([\"message\" => \"Webhook received successfully\"]);\n} else {\n    http_response_code(403);\n    echo json_encode([\"message\" => \"Invalid signature\"]);\n}\n```\n\n> **Tip: Respond quickly, process asynchronously**\n>\n> Return `200 OK` as soon as verification passes, then do heavy work (order fulfilment, emails) on a background queue. PayLater may retry if it doesn’t receive a timely `2xx`."
    }
   ],
   "endpoints": [
    {
     "method": "POST",
     "path": "https://your-server.com/paylater/webhook · your endpoint",
     "urls": {},
     "title": "Webhooks",
     "heading": "Before you start",
     "params": [
      {
       "name": "merchantId",
       "type": "String",
       "required": false,
       "description": "Your unique merchant identifier.",
       "group": "Payload fields"
      },
      {
       "name": "orderId",
       "type": "String",
       "required": false,
       "description": "The order identifier you supplied when creating the payment.",
       "group": "Payload fields"
      },
      {
       "name": "paylaterRef",
       "type": "String",
       "required": false,
       "description": "PayLater’s internal reference for the order.",
       "group": "Payload fields"
      },
      {
       "name": "status",
       "type": "String",
       "required": false,
       "description": "Order status — one of `success`, `failed`, `pending`.",
       "group": "Payload fields"
      },
      {
       "name": "timestamp",
       "type": "Long",
       "required": false,
       "description": "Epoch timestamp of the event.",
       "group": "Payload fields"
      },
      {
       "name": "txHash",
       "type": "String",
       "required": false,
       "description": "MD5 hash of the concatenated payload (see below).",
       "group": "Payload fields"
      },
      {
       "name": "signature",
       "type": "String",
       "required": false,
       "description": "HMAC SHA-256 of `txHash`, keyed with your webhook secret.",
       "group": "Payload fields"
      },
      {
       "name": "comments",
       "type": "String",
       "required": false,
       "description": "Optional comments / metadata.",
       "group": "Payload fields"
      }
     ],
     "kind": "webhook"
    }
   ]
  },
  {
   "slug": "errors",
   "title": "Error handling & response codes",
   "section": "Errors",
   "description": "How PayLater reports errors, plus the common response codes and refund/status codes you’ll encounter.",
   "keywords": "error errors response codes status http invalid api key message reference handling troubleshoot",
   "url": "https://docs.paylaterapp.com/errors.html",
   "markdown_url": "https://docs.paylaterapp.com/errors.md",
   "markdown": "# Error handling & response codes\n\nPayLater returns conventional HTTP status codes and a JSON body describing what went wrong. Most errors carry an `error` (and sometimes a `message`) field.\n\n**Example error**\n\n```json\n{\n  \"error\": \"Refund Error\",\n  \"message\": \"Transaction happened less than 10 minutes ago. Please try again later.\"\n}\n```\n\n## Authentication errors\n\n| Example | Cause |\n| --- | --- |\n| `{ \"error\": \"invalid_client\", \"error_description\": \"Invalid client or Invalid client credentials\" }` | Wrong `client_id` / `client_secret`. |\n| `{ \"error\": \"unsupported_grant_type\", \"error_description\": \"Unsupported grant_type\" }` | `grant_type` must be `client_credentials`. |\n\n## Common API errors\n\n| Example | Cause |\n| --- | --- |\n| `{ \"error\": \"Invalid API Key\" }` | Missing or invalid bearer token. |\n| `{ \"error\": \"Order ID must be unique\" }` | Reusing an `order_id`. |\n| `{ \"error\": \"Order ID is required\" }` | Required parameter omitted. |\n| `{ \"error\": \"Merchant ID cannot be null\" }` | Missing outlet/merchant context. |\n| `{ \"error\": \"Amount must be between 300 and 25000\" }` | `amount` outside the accepted range. |\n| `{ \"message\": \"Shopper not found\" }` | Lookup did not match a registered shopper. |\n\n## Request to Pay errors\n\n| Reference | HTTP | Meaning |\n| --- | --- | --- |\n| `RTP-409-DUPLICATE-REFERENCE` | `409` | A Request-to-Pay already exists for this merchant order reference. |\n| `RTP-UPSTREAM-ERROR` | `404` | Unable to process Request-to-Pay right now. Please try again later. |\n\n## Refund errors\n\n| Message | Meaning |\n| --- | --- |\n| `Transaction Reference is required` | `order_id` omitted. |\n| `Order cannot be refunded as it happened more than 29 days ago.` | Outside the 29-day window. |\n| `Order contains transactions other than down payment and cannot be refunded.` | Not refundable. |\n| `Transaction happened less than 10 minutes ago. Please try again later.` | Must wait at least 10 minutes. |\n| `Invalid Transaction Reference.` | Unknown order. |\n| `Invalid Transaction type.` | Not a refundable transaction type. |\n\n## Payment status codes\n\n| status | Meaning |\n| --- | --- |\n| `0` | Customer did not proceed |\n| `1` | Pending |\n| `2` | Success |\n| `3` | Failed |\n\n> **Tip: Webhooks over polling**\n>\n> Wherever possible, rely on signed [webhooks](https://docs.paylaterapp.com/webhooks.html) for state changes instead of polling.\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/errors.html",
     "markdown": "# Error handling & response codes\n\nPayLater returns conventional HTTP status codes and a JSON body describing what went wrong. Most errors carry an `error` (and sometimes a `message`) field.\n\n**Example error**\n\n```json\n{\n  \"error\": \"Refund Error\",\n  \"message\": \"Transaction happened less than 10 minutes ago. Please try again later.\"\n}\n```"
    },
    {
     "heading": "Authentication errors",
     "anchor": "authentication-errors",
     "url": "https://docs.paylaterapp.com/errors.html#authentication-errors",
     "markdown": "## Authentication errors\n\n| Example | Cause |\n| --- | --- |\n| `{ \"error\": \"invalid_client\", \"error_description\": \"Invalid client or Invalid client credentials\" }` | Wrong `client_id` / `client_secret`. |\n| `{ \"error\": \"unsupported_grant_type\", \"error_description\": \"Unsupported grant_type\" }` | `grant_type` must be `client_credentials`. |"
    },
    {
     "heading": "Common API errors",
     "anchor": "common-api-errors",
     "url": "https://docs.paylaterapp.com/errors.html#common-api-errors",
     "markdown": "## Common API errors\n\n| Example | Cause |\n| --- | --- |\n| `{ \"error\": \"Invalid API Key\" }` | Missing or invalid bearer token. |\n| `{ \"error\": \"Order ID must be unique\" }` | Reusing an `order_id`. |\n| `{ \"error\": \"Order ID is required\" }` | Required parameter omitted. |\n| `{ \"error\": \"Merchant ID cannot be null\" }` | Missing outlet/merchant context. |\n| `{ \"error\": \"Amount must be between 300 and 25000\" }` | `amount` outside the accepted range. |\n| `{ \"message\": \"Shopper not found\" }` | Lookup did not match a registered shopper. |"
    },
    {
     "heading": "Request to Pay errors",
     "anchor": "request-to-pay-errors",
     "url": "https://docs.paylaterapp.com/errors.html#request-to-pay-errors",
     "markdown": "## Request to Pay errors\n\n| Reference | HTTP | Meaning |\n| --- | --- | --- |\n| `RTP-409-DUPLICATE-REFERENCE` | `409` | A Request-to-Pay already exists for this merchant order reference. |\n| `RTP-UPSTREAM-ERROR` | `404` | Unable to process Request-to-Pay right now. Please try again later. |"
    },
    {
     "heading": "Refund errors",
     "anchor": "refund-errors",
     "url": "https://docs.paylaterapp.com/errors.html#refund-errors",
     "markdown": "## Refund errors\n\n| Message | Meaning |\n| --- | --- |\n| `Transaction Reference is required` | `order_id` omitted. |\n| `Order cannot be refunded as it happened more than 29 days ago.` | Outside the 29-day window. |\n| `Order contains transactions other than down payment and cannot be refunded.` | Not refundable. |\n| `Transaction happened less than 10 minutes ago. Please try again later.` | Must wait at least 10 minutes. |\n| `Invalid Transaction Reference.` | Unknown order. |\n| `Invalid Transaction type.` | Not a refundable transaction type. |"
    },
    {
     "heading": "Payment status codes",
     "anchor": "payment-status-codes",
     "url": "https://docs.paylaterapp.com/errors.html#payment-status-codes",
     "markdown": "## Payment status codes\n\n| status | Meaning |\n| --- | --- |\n| `0` | Customer did not proceed |\n| `1` | Pending |\n| `2` | Success |\n| `3` | Failed |\n\n> **Tip: Webhooks over polling**\n>\n> Wherever possible, rely on signed [webhooks](https://docs.paylaterapp.com/webhooks.html) for state changes instead of polling."
    }
   ],
   "endpoints": []
  },
  {
   "slug": "magento",
   "title": "Magento 2 BNPL plugin setup",
   "section": "Plugins & Platforms",
   "description": "Install and configure the PayLater Buy Now, Pay Later plugin for Adobe Commerce / Magento 2 — step by step, with screenshots.",
   "keywords": "magento adobe commerce mage-os plugin module php 8.2 bin/magento app/code install upgrade uninstall cron client id client secret outlet id webhook secret payment mode uat live gateway no-code",
   "url": "https://docs.paylaterapp.com/magento.html",
   "markdown_url": "https://docs.paylaterapp.com/magento.md",
   "markdown": "# Magento\n\nThe PayLater plugin lets merchants integrate Buy Now, Pay Later (BNPL) into their Magento 2 stores, giving customers flexible payment options. This comprehensive guide walks you through the step-by-step installation and configuration process for a seamless integration.\n\n## Download plugin\n\n**Download:** [Magento 2 / Adobe Commerce v4.1.2 · latest](https://docs.paylaterapp.com/downloads/PayLater_PayLaterpay-4.1.2.zip) ([release notes](https://docs.paylaterapp.com/release-notes.html#magento-4-1-2))\n\nAlways install the latest version. Older releases are deprecated — see the [release notes](https://docs.paylaterapp.com/release-notes.html) for the full version history.\n\n> **Note: PHP requirement**\n>\n> The PayLater plugin needs **PHP 8.2 or newer**. Keep PHP up to date with the latest security release for your version.\n\n## Compatibility\n\n| Component | Supported |\n| --- | --- |\n| Platform | Magento 2.4.x, Adobe Commerce 2.4.x, Mage-OS (tested on Mage-OS 3.6.0, Magento 2.4.9 code base) |\n| PHP | 8.2 or newer (tested on 8.3) |\n| Cron | Magento cron must be running. The plugin reconciles pending PayLater orders every 10 minutes. |\n| Store currency | QAR |\n| Install method | Manual (`app/code`). The package is not available through Composer. |\n\n## Manual setup\n\nThe zip already contains the `PayLater/PayLaterpay` folder, so extract it straight into `app/code/`. Do not create the `PayLater/PayLaterpay` folder yourself first, or the module ends up one level too deep and Magento cannot find it.\n\nRun every command below as the user that owns the Magento files (usually the web server user, for example `www-data`), not as `root`. Files created by `root` stop the store and later commands from writing to `generated/` and `var/`.\n\n- Extract the zip into your Magento 2 `[ROOT]/app/code/` directory.\n- Check that `[ROOT]/app/code/PayLater/PayLaterpay/registration.php` exists.\n- Enable the module:\n\n```bash\ncd [ROOT]\nunzip PayLater_PayLaterpay-<version>.zip -d app/code/\nphp bin/magento module:enable PayLater_PayLaterpay\n```\n\n- Then run the following commands in order. Run `setup:static-content:deploy` only if your store is in production mode (check with `php bin/magento deploy:mode:show`). In default and developer mode Magento builds static files itself and refuses to run it.\n\n```bash\nphp bin/magento setup:upgrade\nphp bin/magento setup:di:compile\nphp bin/magento setup:static-content:deploy\nphp bin/magento cache:flush\n```\n\n### Upgrading\n\n- Put the store into maintenance mode: `php bin/magento maintenance:enable`.\n- Delete `[ROOT]/app/code/PayLater/PayLaterpay`, then extract the new zip into `app/code/` as above. Your admin settings are kept, because they are stored in the database.\n- Run the four commands above, then `php bin/magento maintenance:disable`.\n\n### Removing the plugin\n\n```bash\nphp bin/magento module:disable PayLater_PayLaterpay\nrm -rf app/code/PayLater/PayLaterpay\nphp bin/magento setup:upgrade\nphp bin/magento setup:di:compile\nphp bin/magento cache:flush\n```\n\nIn production mode, also run `setup:static-content:deploy` before `cache:flush`. `setup:upgrade` clears the compiled code, so the store shows errors until it is compiled again.\n\nBefore you remove it, wait until no orders are still waiting for a PayLater payment. Pending orders are only reconciled while the module is enabled.\n\n### Frontend widgets\n\nPLP — Product List Page\n\n![PayLater widget on the Magento product list page](https://docs.paylaterapp.com/assets/img/plugins/magento/01.png)\n\nPDP — Product Detail Page\n\n![PayLater widget on the Magento product detail page](https://docs.paylaterapp.com/assets/img/plugins/magento/02.png)\n\n![PayLater instalment breakdown on the Magento product detail page](https://docs.paylaterapp.com/assets/img/plugins/magento/03.png)\n\nLog in to your Magento Admin Panel.\n\n![Magento Admin Panel login screen](https://docs.paylaterapp.com/assets/img/plugins/magento/04.png)\n\n## Admin configuration\n\nAdmin configuration is a crucial step in setting up the PayLater plugin in your Magento 2 store. In this section you'll access the Magento Admin Panel and navigate through specific settings to tailor the PayLater payment gateway to your preferences.\n\n- Log in to your Magento Admin Panel.\n- Navigate to **Stores → Configuration → Sales → Payment Methods** and expand the **Other Payment Methods** tab. You will see the **PayLater — Shop Now Pay Later** option.\n\n![PayLater under Other Payment Methods in Magento configuration](https://docs.paylaterapp.com/assets/img/plugins/magento/05.png)\n\n## Payment settings\n\n| Setting | What to enter |\n| --- | --- |\n| Enabled | Turns the PayLater payment method on or off. |\n| Title | The payment method name shown at checkout. |\n| Client ID | Your OAuth client ID from PayLater, for example `merchant-815`. |\n| Client Secret | Your OAuth client secret from PayLater. |\n| Webhook Secret | Provided by PayLater after they register your webhook URL (see below). |\n| Minimum Order Amount | Smallest order total that can use PayLater. Must be 300 or higher; lower values are rejected on save. |\n| Outlet ID | Your outlet ID from PayLater. |\n| Payment Mode | **UAT** for testing with the [test credentials](https://docs.paylaterapp.com/test-environment.html), **Live** for production. Use the credentials that match the mode. |\n| Enable Logging | Logs every PayLater action. Useful while testing; turn it off in production. |\n| Pre-Checkout Inventory Check | Checks stock before redirecting the shopper to PayLater. Turn it off for stores that use multi-source inventory or external stock management. Magento still checks stock when the order is placed. |\n| Sort Order | Position of PayLater in the list of payment methods. |\n\nYour webhook URL is `https://<your-store>/ppaylater/webhook/notify`. Send it to the PayLater team; they register it and give you the **Webhook Secret**.\n\nUnder **Widget Settings** you can turn the PayLater widget on or off for the product list page and the product detail page, and set its main heading, sub heading and terms text.\n\n![PayLater payment settings in the Magento admin](https://docs.paylaterapp.com/assets/img/plugins/magento/06.png)\n\n![PayLater payment settings — Client ID, Client Secret and Outlet ID fields](https://docs.paylaterapp.com/assets/img/plugins/magento/07.png)\n\n- Save the configuration and **clear the cache**.\n\n## Testing\n\nTo ensure smooth functionality, it's recommended to test the PayLater payment option:\n\n- Add a product to your cart and proceed to checkout.\n- During checkout, select **PayLater** as the payment method.\n- You will be redirected to the secure PayLater platform to complete the BNPL payment process.\n- Use a test shopper account from the [Test environment](https://docs.paylaterapp.com/test-environment.html).\n- Please use **300 QAR** as the amount to test.\n\n> **Note: Order numbers PayLater has already seen**\n>\n> The test credentials are shared by many stores, and a new Magento store numbers its orders from `000000001`. If PayLater already holds an order number, the plugin retries once with a short suffix, for example `000000123-a7f2`. That reference is what appears in the PayLater merchant portal for the order; the Magento order number does not change. Versions before 4.1.1 cancelled the order instead, so upgrade if you see `Order ID already exists`.\n\n> **Note: Credentials**\n>\n> Get your `Client ID`, `Client Secret`, and `Outlet ID` from your PayLater account dashboard. For your `Webhook Secret`, share your webhook endpoint with the PayLater team. For sandbox values and test shoppers, see the [Test environment](https://docs.paylaterapp.com/test-environment.html).\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/magento.html",
     "markdown": "# Magento\n\nThe PayLater plugin lets merchants integrate Buy Now, Pay Later (BNPL) into their Magento 2 stores, giving customers flexible payment options. This comprehensive guide walks you through the step-by-step installation and configuration process for a seamless integration."
    },
    {
     "heading": "Download plugin",
     "anchor": "download-plugin",
     "url": "https://docs.paylaterapp.com/magento.html#download-plugin",
     "markdown": "## Download plugin\n\n**Download:** [Magento 2 / Adobe Commerce v4.1.2 · latest](https://docs.paylaterapp.com/downloads/PayLater_PayLaterpay-4.1.2.zip) ([release notes](https://docs.paylaterapp.com/release-notes.html#magento-4-1-2))\n\nAlways install the latest version. Older releases are deprecated — see the [release notes](https://docs.paylaterapp.com/release-notes.html) for the full version history.\n\n> **Note: PHP requirement**\n>\n> The PayLater plugin needs **PHP 8.2 or newer**. Keep PHP up to date with the latest security release for your version."
    },
    {
     "heading": "Compatibility",
     "anchor": "compatibility",
     "url": "https://docs.paylaterapp.com/magento.html#compatibility",
     "markdown": "## Compatibility\n\n| Component | Supported |\n| --- | --- |\n| Platform | Magento 2.4.x, Adobe Commerce 2.4.x, Mage-OS (tested on Mage-OS 3.6.0, Magento 2.4.9 code base) |\n| PHP | 8.2 or newer (tested on 8.3) |\n| Cron | Magento cron must be running. The plugin reconciles pending PayLater orders every 10 minutes. |\n| Store currency | QAR |\n| Install method | Manual (`app/code`). The package is not available through Composer. |"
    },
    {
     "heading": "Manual setup",
     "anchor": "manual-setup",
     "url": "https://docs.paylaterapp.com/magento.html#manual-setup",
     "markdown": "## Manual setup\n\nThe zip already contains the `PayLater/PayLaterpay` folder, so extract it straight into `app/code/`. Do not create the `PayLater/PayLaterpay` folder yourself first, or the module ends up one level too deep and Magento cannot find it.\n\nRun every command below as the user that owns the Magento files (usually the web server user, for example `www-data`), not as `root`. Files created by `root` stop the store and later commands from writing to `generated/` and `var/`.\n\n- Extract the zip into your Magento 2 `[ROOT]/app/code/` directory.\n- Check that `[ROOT]/app/code/PayLater/PayLaterpay/registration.php` exists.\n- Enable the module:\n\n```bash\ncd [ROOT]\nunzip PayLater_PayLaterpay-<version>.zip -d app/code/\nphp bin/magento module:enable PayLater_PayLaterpay\n```\n\n- Then run the following commands in order. Run `setup:static-content:deploy` only if your store is in production mode (check with `php bin/magento deploy:mode:show`). In default and developer mode Magento builds static files itself and refuses to run it.\n\n```bash\nphp bin/magento setup:upgrade\nphp bin/magento setup:di:compile\nphp bin/magento setup:static-content:deploy\nphp bin/magento cache:flush\n```\n\n### Upgrading\n\n- Put the store into maintenance mode: `php bin/magento maintenance:enable`.\n- Delete `[ROOT]/app/code/PayLater/PayLaterpay`, then extract the new zip into `app/code/` as above. Your admin settings are kept, because they are stored in the database.\n- Run the four commands above, then `php bin/magento maintenance:disable`.\n\n### Removing the plugin\n\n```bash\nphp bin/magento module:disable PayLater_PayLaterpay\nrm -rf app/code/PayLater/PayLaterpay\nphp bin/magento setup:upgrade\nphp bin/magento setup:di:compile\nphp bin/magento cache:flush\n```\n\nIn production mode, also run `setup:static-content:deploy` before `cache:flush`. `setup:upgrade` clears the compiled code, so the store shows errors until it is compiled again.\n\nBefore you remove it, wait until no orders are still waiting for a PayLater payment. Pending orders are only reconciled while the module is enabled.\n\n### Frontend widgets\n\nPLP — Product List Page\n\n![PayLater widget on the Magento product list page](https://docs.paylaterapp.com/assets/img/plugins/magento/01.png)\n\nPDP — Product Detail Page\n\n![PayLater widget on the Magento product detail page](https://docs.paylaterapp.com/assets/img/plugins/magento/02.png)\n\n![PayLater instalment breakdown on the Magento product detail page](https://docs.paylaterapp.com/assets/img/plugins/magento/03.png)\n\nLog in to your Magento Admin Panel.\n\n![Magento Admin Panel login screen](https://docs.paylaterapp.com/assets/img/plugins/magento/04.png)"
    },
    {
     "heading": "Admin configuration",
     "anchor": "admin-configuration",
     "url": "https://docs.paylaterapp.com/magento.html#admin-configuration",
     "markdown": "## Admin configuration\n\nAdmin configuration is a crucial step in setting up the PayLater plugin in your Magento 2 store. In this section you'll access the Magento Admin Panel and navigate through specific settings to tailor the PayLater payment gateway to your preferences.\n\n- Log in to your Magento Admin Panel.\n- Navigate to **Stores → Configuration → Sales → Payment Methods** and expand the **Other Payment Methods** tab. You will see the **PayLater — Shop Now Pay Later** option.\n\n![PayLater under Other Payment Methods in Magento configuration](https://docs.paylaterapp.com/assets/img/plugins/magento/05.png)"
    },
    {
     "heading": "Payment settings",
     "anchor": "payment-settings",
     "url": "https://docs.paylaterapp.com/magento.html#payment-settings",
     "markdown": "## Payment settings\n\n| Setting | What to enter |\n| --- | --- |\n| Enabled | Turns the PayLater payment method on or off. |\n| Title | The payment method name shown at checkout. |\n| Client ID | Your OAuth client ID from PayLater, for example `merchant-815`. |\n| Client Secret | Your OAuth client secret from PayLater. |\n| Webhook Secret | Provided by PayLater after they register your webhook URL (see below). |\n| Minimum Order Amount | Smallest order total that can use PayLater. Must be 300 or higher; lower values are rejected on save. |\n| Outlet ID | Your outlet ID from PayLater. |\n| Payment Mode | **UAT** for testing with the [test credentials](https://docs.paylaterapp.com/test-environment.html), **Live** for production. Use the credentials that match the mode. |\n| Enable Logging | Logs every PayLater action. Useful while testing; turn it off in production. |\n| Pre-Checkout Inventory Check | Checks stock before redirecting the shopper to PayLater. Turn it off for stores that use multi-source inventory or external stock management. Magento still checks stock when the order is placed. |\n| Sort Order | Position of PayLater in the list of payment methods. |\n\nYour webhook URL is `https://<your-store>/ppaylater/webhook/notify`. Send it to the PayLater team; they register it and give you the **Webhook Secret**.\n\nUnder **Widget Settings** you can turn the PayLater widget on or off for the product list page and the product detail page, and set its main heading, sub heading and terms text.\n\n![PayLater payment settings in the Magento admin](https://docs.paylaterapp.com/assets/img/plugins/magento/06.png)\n\n![PayLater payment settings — Client ID, Client Secret and Outlet ID fields](https://docs.paylaterapp.com/assets/img/plugins/magento/07.png)\n\n- Save the configuration and **clear the cache**."
    },
    {
     "heading": "Testing",
     "anchor": "testing",
     "url": "https://docs.paylaterapp.com/magento.html#testing",
     "markdown": "## Testing\n\nTo ensure smooth functionality, it's recommended to test the PayLater payment option:\n\n- Add a product to your cart and proceed to checkout.\n- During checkout, select **PayLater** as the payment method.\n- You will be redirected to the secure PayLater platform to complete the BNPL payment process.\n- Use a test shopper account from the [Test environment](https://docs.paylaterapp.com/test-environment.html).\n- Please use **300 QAR** as the amount to test.\n\n> **Note: Order numbers PayLater has already seen**\n>\n> The test credentials are shared by many stores, and a new Magento store numbers its orders from `000000001`. If PayLater already holds an order number, the plugin retries once with a short suffix, for example `000000123-a7f2`. That reference is what appears in the PayLater merchant portal for the order; the Magento order number does not change. Versions before 4.1.1 cancelled the order instead, so upgrade if you see `Order ID already exists`.\n\n> **Note: Credentials**\n>\n> Get your `Client ID`, `Client Secret`, and `Outlet ID` from your PayLater account dashboard. For your `Webhook Secret`, share your webhook endpoint with the PayLater team. For sandbox values and test shoppers, see the [Test environment](https://docs.paylaterapp.com/test-environment.html)."
    }
   ],
   "endpoints": []
  },
  {
   "slug": "woocommerce",
   "title": "WooCommerce BNPL plugin setup",
   "section": "Plugins & Platforms",
   "description": "Install and configure the PayLater Buy Now, Pay Later plugin for WooCommerce / WordPress — step by step, with screenshots.",
   "keywords": "woocommerce wordpress wp plugin php 8.2 install upgrade test mode client id client secret outlet id webhook secret link expiry gateway no-code",
   "url": "https://docs.paylaterapp.com/woocommerce.html",
   "markdown_url": "https://docs.paylaterapp.com/woocommerce.md",
   "markdown": "# WooCommerce\n\nEmpower your WordPress online store with the PayLater WooCommerce Plugin, a cutting-edge solution designed to enhance the purchasing journey for both merchants and customers. This seamlessly integrated plugin opens up a world of possibilities, allowing merchants to offer a convenient and flexible PayLater option at checkout.\n\n## Download plugin\n\n**Download:** [WordPress / WooCommerce v4.4.1 · latest](https://docs.paylaterapp.com/downloads/paylater_v4.4.1.zip) ([release notes](https://docs.paylaterapp.com/release-notes.html#woocommerce-4-4-1))\n\nAlways install the latest version. Older releases are deprecated — see the [release notes](https://docs.paylaterapp.com/release-notes.html) for the full version history.\n\n> **Note: PHP requirement**\n>\n> The PayLater plugin needs **PHP 8.2 or newer**. Keep PHP up to date with the latest security release for your version.\n\n## Installation\n\nPaylater empowers merchants to offer the popular Buy Now, Pay Later (BNPL) payment option to their customers on their WooCommerce websites. This document guides you through the installation and setup process of the PayLater plugin.\n\n### Before you begin\n\nEnsure you have a WordPress website with the WooCommerce plugin installed and activated.\n\n### Compatibility\n\n| Component | Tested with |\n| --- | --- |\n| WordPress | 6.6 |\n| WooCommerce | 9.3 |\n| Checkout | Classic (shortcode) and block-based checkout |\n| PHP | 8.2 or newer (tested on 8.3) |\n| Store currency | QAR |\n\n### Frontend Widgets:\n\nPLP Product List Page\n\n![PayLater widget on the WooCommerce product list page](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/01.png)\n\nPDP Product Detail Page\n\n![PayLater widget on the WooCommerce product detail page](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/02.png)\n\n![PayLater instalment breakdown on the WooCommerce product detail page](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/03.png)\n\n## Method 1: Automatic Installation\n\nAutomatic installation is the easiest option as WordPress handles the file transfers itself and you don’t need to leave your web browser\n\n- Log in to your WordPress admin dashboard\n- Navigate to **Plugins -> Add New Plugin**\n- Click on Upload Plugin\n- Select the downloaded PayLater plugin zip file.\n- Click Install Now and then Activate\n\n**Upgrading:** upload the new zip the same way. When WordPress says the plugin is already installed, click **Replace current with uploaded**. Your settings are kept.\n\n![Log in to your WordPress admin dashboard and navigate to Plugins](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/04.png)\n\n*Log in to your WordPress admin dashboard & Navigate to Plugins*\n\n![Click on Upload Plugin](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/05.png)\n\n*Click on Upload Plugin*\n\n![Select the downloaded PayLater plugin zip file](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/06.png)\n\n*Select the downloaded PayLater plugin zip file.*\n\n![Click Install Now and then Activate](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/07.png)\n\n*Click Install Now and then Activate*\n\n## Method 2: Manual Installation\n\nThe manual installation method involves downloading our plugin and uploading it to your web server via your favourite FTP application. Extract the zip and upload the `paylater` folder to `wp-content/plugins/`. The WordPress documentation covers this in [Manage Plugins](https://wordpress.org/documentation/article/manage-plugins/#Manual_Plugin_Installation)\n\n![PayLater Payment Gateway in the WordPress plugin list after uploading via FTP](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/08.png)\n\n*After uploading via FTP, PayLater appears in the Plugins list. Click Activate.*\n\n## Configurations\n\n- Once activated, navigate to **WooCommerce -> Settings -> Payments**\n- Locate PayLater in the list of available payment gateways and click on the Manage button.\n\n![PayLater in the WooCommerce payments list with the Manage button](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/09.png)\n\n- The PayLater settings are split across three tabs at the top of the page: **General**, **API Configuration** and **Widget Settings**. WooCommerce keeps separate credentials for testing and production, and **Test Mode** decides which set is used.\n\n| Tab | Setting | What to enter |\n| --- | --- | --- |\n| General | Enable PayLater, Title, Description | Turn the payment method on and set the name and description shown at checkout. |\n| General | Order Minimum | Smallest cart total that shows PayLater. Cannot be lower than 300 QAR. |\n| API Configuration | Test Mode | On (default): uses the Test credentials against UAT. Off: uses the Live credentials against production. |\n| API Configuration | Test Client ID / Test Client Secret | The UAT credentials, for example the ones on the [Test environment](https://docs.paylaterapp.com/test-environment.html) page. |\n| API Configuration | Live Client ID / Live Client Secret | Your production credentials from PayLater. |\n| API Configuration | Outlet ID | Your outlet ID from PayLater. |\n| API Configuration | Payment Link Expiry (minutes) | How long the shopper has to finish paying before the link expires. Default 10. |\n| API Configuration | Test Webhook Secret / Live Webhook Secret | Provided by PayLater for each environment after they register your webhook URL. The URL is shown in the **Webhook** section of this tab. |\n| Widget Settings | Enable PDP Widget, Enable PLP Widget and their messages | Turn the product detail (PDP) and product list (PLP) widgets on or off and set their main and sub messages. |\n\n![PayLater settings page with Client ID, Client Secret and Outlet ID fields](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/10.png)\n\n![PayLater configuration fields in the WooCommerce admin](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/11.png)\n\n![PayLater payment gateway configuration saved in WooCommerce](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/12.png)\n\n- Click Save Changes to complete the configuration\n\n## Testing\n\nTo ensure smooth functionality, it's recommended to test the PayLater payment option:\n\n- Add a product to your cart and proceed to checkout\n- During checkout, select PayLater as the payment method.\n- You will be redirected to the secure PayLater platform to complete the BNPL payment process.\n- Use test shopper account provided in the [Test environment](https://docs.paylaterapp.com/test-environment.html)\n- Please use 300 QAR as the amount to test\n\n> **Note: Credentials**\n>\n> Get your `Client ID`, `Client Secret`, and `Outlet ID` from your PayLater account dashboard. For your `Webhook Secret`, share your webhook endpoint with the PayLater team. For sandbox values and test shoppers, see the [Test environment](https://docs.paylaterapp.com/test-environment.html).\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/woocommerce.html",
     "markdown": "# WooCommerce\n\nEmpower your WordPress online store with the PayLater WooCommerce Plugin, a cutting-edge solution designed to enhance the purchasing journey for both merchants and customers. This seamlessly integrated plugin opens up a world of possibilities, allowing merchants to offer a convenient and flexible PayLater option at checkout."
    },
    {
     "heading": "Download plugin",
     "anchor": "download-plugin",
     "url": "https://docs.paylaterapp.com/woocommerce.html#download-plugin",
     "markdown": "## Download plugin\n\n**Download:** [WordPress / WooCommerce v4.4.1 · latest](https://docs.paylaterapp.com/downloads/paylater_v4.4.1.zip) ([release notes](https://docs.paylaterapp.com/release-notes.html#woocommerce-4-4-1))\n\nAlways install the latest version. Older releases are deprecated — see the [release notes](https://docs.paylaterapp.com/release-notes.html) for the full version history.\n\n> **Note: PHP requirement**\n>\n> The PayLater plugin needs **PHP 8.2 or newer**. Keep PHP up to date with the latest security release for your version."
    },
    {
     "heading": "Installation",
     "anchor": "installation",
     "url": "https://docs.paylaterapp.com/woocommerce.html#installation",
     "markdown": "## Installation\n\nPaylater empowers merchants to offer the popular Buy Now, Pay Later (BNPL) payment option to their customers on their WooCommerce websites. This document guides you through the installation and setup process of the PayLater plugin.\n\n### Before you begin\n\nEnsure you have a WordPress website with the WooCommerce plugin installed and activated.\n\n### Compatibility\n\n| Component | Tested with |\n| --- | --- |\n| WordPress | 6.6 |\n| WooCommerce | 9.3 |\n| Checkout | Classic (shortcode) and block-based checkout |\n| PHP | 8.2 or newer (tested on 8.3) |\n| Store currency | QAR |\n\n### Frontend Widgets:\n\nPLP Product List Page\n\n![PayLater widget on the WooCommerce product list page](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/01.png)\n\nPDP Product Detail Page\n\n![PayLater widget on the WooCommerce product detail page](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/02.png)\n\n![PayLater instalment breakdown on the WooCommerce product detail page](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/03.png)"
    },
    {
     "heading": "Method 1: Automatic Installation",
     "anchor": "method-1-automatic-installation",
     "url": "https://docs.paylaterapp.com/woocommerce.html#method-1-automatic-installation",
     "markdown": "## Method 1: Automatic Installation\n\nAutomatic installation is the easiest option as WordPress handles the file transfers itself and you don’t need to leave your web browser\n\n- Log in to your WordPress admin dashboard\n- Navigate to **Plugins -> Add New Plugin**\n- Click on Upload Plugin\n- Select the downloaded PayLater plugin zip file.\n- Click Install Now and then Activate\n\n**Upgrading:** upload the new zip the same way. When WordPress says the plugin is already installed, click **Replace current with uploaded**. Your settings are kept.\n\n![Log in to your WordPress admin dashboard and navigate to Plugins](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/04.png)\n\n*Log in to your WordPress admin dashboard & Navigate to Plugins*\n\n![Click on Upload Plugin](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/05.png)\n\n*Click on Upload Plugin*\n\n![Select the downloaded PayLater plugin zip file](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/06.png)\n\n*Select the downloaded PayLater plugin zip file.*\n\n![Click Install Now and then Activate](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/07.png)\n\n*Click Install Now and then Activate*"
    },
    {
     "heading": "Method 2: Manual Installation",
     "anchor": "method-2-manual-installation",
     "url": "https://docs.paylaterapp.com/woocommerce.html#method-2-manual-installation",
     "markdown": "## Method 2: Manual Installation\n\nThe manual installation method involves downloading our plugin and uploading it to your web server via your favourite FTP application. Extract the zip and upload the `paylater` folder to `wp-content/plugins/`. The WordPress documentation covers this in [Manage Plugins](https://wordpress.org/documentation/article/manage-plugins/#Manual_Plugin_Installation)\n\n![PayLater Payment Gateway in the WordPress plugin list after uploading via FTP](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/08.png)\n\n*After uploading via FTP, PayLater appears in the Plugins list. Click Activate.*"
    },
    {
     "heading": "Configurations",
     "anchor": "configurations",
     "url": "https://docs.paylaterapp.com/woocommerce.html#configurations",
     "markdown": "## Configurations\n\n- Once activated, navigate to **WooCommerce -> Settings -> Payments**\n- Locate PayLater in the list of available payment gateways and click on the Manage button.\n\n![PayLater in the WooCommerce payments list with the Manage button](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/09.png)\n\n- The PayLater settings are split across three tabs at the top of the page: **General**, **API Configuration** and **Widget Settings**. WooCommerce keeps separate credentials for testing and production, and **Test Mode** decides which set is used.\n\n| Tab | Setting | What to enter |\n| --- | --- | --- |\n| General | Enable PayLater, Title, Description | Turn the payment method on and set the name and description shown at checkout. |\n| General | Order Minimum | Smallest cart total that shows PayLater. Cannot be lower than 300 QAR. |\n| API Configuration | Test Mode | On (default): uses the Test credentials against UAT. Off: uses the Live credentials against production. |\n| API Configuration | Test Client ID / Test Client Secret | The UAT credentials, for example the ones on the [Test environment](https://docs.paylaterapp.com/test-environment.html) page. |\n| API Configuration | Live Client ID / Live Client Secret | Your production credentials from PayLater. |\n| API Configuration | Outlet ID | Your outlet ID from PayLater. |\n| API Configuration | Payment Link Expiry (minutes) | How long the shopper has to finish paying before the link expires. Default 10. |\n| API Configuration | Test Webhook Secret / Live Webhook Secret | Provided by PayLater for each environment after they register your webhook URL. The URL is shown in the **Webhook** section of this tab. |\n| Widget Settings | Enable PDP Widget, Enable PLP Widget and their messages | Turn the product detail (PDP) and product list (PLP) widgets on or off and set their main and sub messages. |\n\n![PayLater settings page with Client ID, Client Secret and Outlet ID fields](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/10.png)\n\n![PayLater configuration fields in the WooCommerce admin](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/11.png)\n\n![PayLater payment gateway configuration saved in WooCommerce](https://docs.paylaterapp.com/assets/img/plugins/woocommerce/12.png)\n\n- Click Save Changes to complete the configuration"
    },
    {
     "heading": "Testing",
     "anchor": "testing",
     "url": "https://docs.paylaterapp.com/woocommerce.html#testing",
     "markdown": "## Testing\n\nTo ensure smooth functionality, it's recommended to test the PayLater payment option:\n\n- Add a product to your cart and proceed to checkout\n- During checkout, select PayLater as the payment method.\n- You will be redirected to the secure PayLater platform to complete the BNPL payment process.\n- Use test shopper account provided in the [Test environment](https://docs.paylaterapp.com/test-environment.html)\n- Please use 300 QAR as the amount to test\n\n> **Note: Credentials**\n>\n> Get your `Client ID`, `Client Secret`, and `Outlet ID` from your PayLater account dashboard. For your `Webhook Secret`, share your webhook endpoint with the PayLater team. For sandbox values and test shoppers, see the [Test environment](https://docs.paylaterapp.com/test-environment.html)."
    }
   ],
   "endpoints": []
  },
  {
   "slug": "shopify",
   "title": "Shopify BNPL payment method setup",
   "section": "Plugins & Platforms",
   "description": "Integrate the PayLater Buy Now, Pay Later plugin into your Shopify store — manual setup, step by step, with screenshots.",
   "keywords": "shopify manual payment method webhook custom app order creation no-code",
   "url": "https://docs.paylaterapp.com/shopify.html",
   "markdown_url": "https://docs.paylaterapp.com/shopify.md",
   "markdown": "# Shopify\n\nThe PayLater plugin offers merchants the ability to integrate \"Buy Now, Pay Later\" (BNPL) functionality into their Shopify stores, providing customers with flexible payment options. This comprehensive guide will walk you through the step-by-step installation and configuration process for seamless integration.\n\n## How the customer journey works\n\nPlease read this before you set anything up. PayLater does not behave like a normal card payment, so understanding the flow in advance saves you and your shoppers a lot of confusion.\n\n> **Warning: The one thing to understand first**\n>\n> Shoppers are **not** redirected to PayLater during checkout, and PayLater is not a hosted step inside the checkout. The order is placed right away, and the payment link is sent to the shopper by **email after the order is placed**. Until the shopper pays, the order sits in Shopify as \"Payment pending\", which is expected and not an error.\n\n### The shopper's journey, step by step\n\n1. The shopper adds items to their cart and goes to Shopify's normal checkout.\n2. They choose **\"Split in 4 interest-free payments with PayLater\"** from the payment methods, alongside your other options.\n3. The order is placed immediately in Shopify. **The shopper is not redirected to PayLater at this point.** This is the single most misunderstood part of the flow.\n4. The Shopify order is created with a payment status of **\"Pending\"** and is not yet paid. Stock is committed to the order at this moment.\n5. Shopify notifies PayLater of the new order. Within a few seconds, PayLater creates a payment link for it.\n6. The payment link is delivered to the shopper by **email**. It is also written onto the Shopify order as an order note (\"PayLater Payment Link: ...\"), and the order is tagged **\"PayLater\"** and **\"PaymentLinkSent\"**, so you can always find and resend the link from your Shopify admin.\n7. The shopper opens the link, completes their PayLater application, and sets up the installment plan. This happens on PayLater's site, outside Shopify.\n8. When the payment is complete, the Shopify order is automatically marked as **Paid**, and you fulfill it as normal.\n9. Halfway through the payment window, if the order is still unpaid, the shopper automatically receives a reminder email with the link again.\n10. If the order is still unpaid when the window expires, the Shopify order is automatically canceled, the stock is restocked, the pending payment is voided so the order reads **\"Voided\"** rather than \"Payment pending\", and the shopper receives a cancellation email.\n11. Refunds are handled by you from the **PayLater merchant dashboard**, not from Shopify.\n\n### What every merchant must know before switching it on\n\nThese limits decide whether a shopper ever receives a payment link, so set your store up for them before you go live.\n\n- **Minimum order value is 300 QAR, maximum is 25,000 QAR.** An order outside this range gets no payment link and no email, so the shopper receives nothing at all. Hide or disable PayLater at checkout for baskets below 300 QAR so shoppers cannot select a method that cannot work for their order.\n- **An email address is required.** If a shopper checks out with a phone number only, there is no address to send the link to and no email goes out. The link still appears on the Shopify order, so you can send it to the shopper manually. To avoid this, require an email address at checkout (Shopify admin: **Settings → Checkout → Customer contact method**).\n- **Orders must be in Qatari Riyal.** PayLater settles in QAR, so your store's orders need to be in QAR.\n- **The payment window is 12 hours by default** and can be configured for your store. The reminder email is sent at the halfway point. The window can also be turned off entirely if you do not want orders to be canceled automatically.\n- **\"Payment pending\" is normal.** Until the shopper pays, the order legitimately sits as \"Payment pending\" in Shopify. This is expected behavior, not a problem with the order.\n\n## Manual Step\n\n### Step 1: Setup PayLater as a Manual Payment Method\n\n- Go to your Shopify Admin (ex. [yourstore.com].myshopify.com/admin) and click on Settings in the left-hand navigation panel\n\n![Shopify Admin with Settings in the left-hand navigation panel](https://docs.paylaterapp.com/assets/img/plugins/shopify/01.png)\n\n- Click on the “Payment Menu” from left menu & scroll down to the “Manual payment methods” section.\n\n![Shopify Payments settings showing the Manual payment methods section](https://docs.paylaterapp.com/assets/img/plugins/shopify/02.png)\n\n- In this section, click on the dropdown labeled “Manual payment methods” and select \"Create custom payment method\"\n\n![Manual payment methods dropdown with Create custom payment method selected](https://docs.paylaterapp.com/assets/img/plugins/shopify/03.png)\n\n- In the “Set up manual payment form”, copy/paste the details as shown below in the appropriate field (NOTE: You can write according to your checkout design):\n  - (Custom payment method name) Split in 4 interest-free payments with PayLater\n  - (Additional Details) PayLater allows you to split your purchase into 4 interest-free installments.\n  - (Payment Instructions) You'll receive an email with a secure link from PayLater to complete your payment.\n\n![Set up manual payment form filled in with the PayLater details](https://docs.paylaterapp.com/assets/img/plugins/shopify/04.png)\n\n- Click on the \"Activate\" button to save your work. You will see “Split in 4 interest-free payments with PayLater“ appear under the Manual Payment Methods.\n\n![Activated PayLater method listed under Manual payment methods](https://docs.paylaterapp.com/assets/img/plugins/shopify/05.png)\n\n### Step 2: Setting Webhook\n\n- In Settings, go to “Notifications“ in the left menu and then click on the Webhooks.\n\n![Shopify Notifications settings with the Webhooks option](https://docs.paylaterapp.com/assets/img/plugins/shopify/06.png)\n\n- Now click “Create webhook”\n\n![Webhooks section with the Create webhook button](https://docs.paylaterapp.com/assets/img/plugins/shopify/07.png)\n\n- In the “Add webhook” form, copy/paste the details as shown below in the appropriate field and click on Save\n  - Event (Order Creation)\n  - Format (JSON)\n  - URL ([https://shopify.paylaterapp.com/api/webhooks/shopify](https://shopify.paylaterapp.com/api/webhooks/shopify))\n  - Webhook API version (the newest dated version in the list, for example 2026-10. Do not choose Unstable: it can change without notice)\n\n![Add webhook form filled with event, format, URL and API version](https://docs.paylaterapp.com/assets/img/plugins/shopify/08.png)\n\n### Step 3: Linking Shopify to PayLater Backend App\n\n- In Settings, go to “Apps and sales channel“ in the left menu and then click on the Develop apps button on the top.\n\n![Apps and sales channels settings with the Develop apps button](https://docs.paylaterapp.com/assets/img/plugins/shopify/09.png)\n\n![App development page in Shopify settings](https://docs.paylaterapp.com/assets/img/plugins/shopify/10.png)\n\n![Allow custom app development confirmation in Shopify](https://docs.paylaterapp.com/assets/img/plugins/shopify/11.png)\n\n- Click on “Create an app“\n\n![Create an app button on the app development page](https://docs.paylaterapp.com/assets/img/plugins/shopify/12.png)\n\n- Set “PayLater Payment” as App name\n\n![Create an app dialog with PayLater Payment set as the app name](https://docs.paylaterapp.com/assets/img/plugins/shopify/13.png)\n\n- Fill App URL form with [https://shopify.paylaterapp.com/api/webhooks/shopify](https://shopify.paylaterapp.com/api/webhooks/shopify) value\n\n![App URL field set to the PayLater webhook URL](https://docs.paylaterapp.com/assets/img/plugins/shopify/14.png)\n\n- Select below mentioned Scopes\n  - Orders (Read and Write)\n  - Fulfillment Services (Read and Write)\n\n![Admin API scopes with Orders set to Read and Write](https://docs.paylaterapp.com/assets/img/plugins/shopify/15.png)\n\n![Admin API scopes with Fulfillment Services set to Read and Write](https://docs.paylaterapp.com/assets/img/plugins/shopify/16.png)\n\n- Also set Redirect URLs form with [https://shopify.paylaterapp.com/api/webhooks/shopify](https://shopify.paylaterapp.com/api/webhooks/shopify) value\n\n![Redirect URLs field set to the PayLater webhook URL](https://docs.paylaterapp.com/assets/img/plugins/shopify/17.png)\n\n- Click “Release”\n\n![App configuration page with the Release button](https://docs.paylaterapp.com/assets/img/plugins/shopify/18.png)\n\n- Set Version name (optional) and then click “Release”\n  - Version name (optional) ⇒ v1-2025\n\n![Release dialog with the version name set to v1-2025](https://docs.paylaterapp.com/assets/img/plugins/shopify/19.png)\n\n### Step 4: Install the App on Your Store\n\n- Releasing a version does **not** install the app on your store — you have to install it yourself. Open the **PayLater Payment** app again (Settings → **Apps and sales channels** → **Develop apps** → **PayLater Payment**).\n- On the app’s **Overview** page, click **Install app** in the **Installs** card and confirm the installation.\n\n![App Overview page with an arrow pointing to the Install app button in the Installs card](https://docs.paylaterapp.com/assets/img/plugins/shopify/20.png)\n\n> **Warning: Don’t skip this step**\n>\n> It’s easy to miss. If you release the version but never click **Install app**, the app stays uninstalled and PayLater won’t receive your orders. Make sure the app shows as installed before you go live.\n\n### Final Step:\n\n- Save all the keys you receive during this all process.\n- Send following keys to your account manager to activate your store for payments.\n  - Shop URL (your-store.myshopify.com)\n  - Client ID\n  - Shopify API Secret (shpss****************************)\n\n> **Tip: Find your .myshopify.com domain (Shop URL)**\n>\n> Your **Shop URL** is your store’s permanent `.myshopify.com` admin domain — not your public website address, and it can’t be changed.\n>\n> In your Shopify admin, go to **Settings → Domains**. Your `.myshopify.com` domain is listed separately from your custom domains.\n>\n> You can also read it from your admin URL: if the address bar shows `admin.shopify.com/store/r8atb0-1h`, your Shop URL is `r8atb0-1h.myshopify.com`.\n\n> **Info: Note**\n>\n> Need sandbox credentials and a test shopper account to try the flow before going live? See the [Test environment](https://docs.paylaterapp.com/test-environment.html).\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/shopify.html",
     "markdown": "# Shopify\n\nThe PayLater plugin offers merchants the ability to integrate \"Buy Now, Pay Later\" (BNPL) functionality into their Shopify stores, providing customers with flexible payment options. This comprehensive guide will walk you through the step-by-step installation and configuration process for seamless integration."
    },
    {
     "heading": "How the customer journey works",
     "anchor": "how-the-customer-journey-works",
     "url": "https://docs.paylaterapp.com/shopify.html#how-the-customer-journey-works",
     "markdown": "## How the customer journey works\n\nPlease read this before you set anything up. PayLater does not behave like a normal card payment, so understanding the flow in advance saves you and your shoppers a lot of confusion.\n\n> **Warning: The one thing to understand first**\n>\n> Shoppers are **not** redirected to PayLater during checkout, and PayLater is not a hosted step inside the checkout. The order is placed right away, and the payment link is sent to the shopper by **email after the order is placed**. Until the shopper pays, the order sits in Shopify as \"Payment pending\", which is expected and not an error.\n\n### The shopper's journey, step by step\n\n1. The shopper adds items to their cart and goes to Shopify's normal checkout.\n2. They choose **\"Split in 4 interest-free payments with PayLater\"** from the payment methods, alongside your other options.\n3. The order is placed immediately in Shopify. **The shopper is not redirected to PayLater at this point.** This is the single most misunderstood part of the flow.\n4. The Shopify order is created with a payment status of **\"Pending\"** and is not yet paid. Stock is committed to the order at this moment.\n5. Shopify notifies PayLater of the new order. Within a few seconds, PayLater creates a payment link for it.\n6. The payment link is delivered to the shopper by **email**. It is also written onto the Shopify order as an order note (\"PayLater Payment Link: ...\"), and the order is tagged **\"PayLater\"** and **\"PaymentLinkSent\"**, so you can always find and resend the link from your Shopify admin.\n7. The shopper opens the link, completes their PayLater application, and sets up the installment plan. This happens on PayLater's site, outside Shopify.\n8. When the payment is complete, the Shopify order is automatically marked as **Paid**, and you fulfill it as normal.\n9. Halfway through the payment window, if the order is still unpaid, the shopper automatically receives a reminder email with the link again.\n10. If the order is still unpaid when the window expires, the Shopify order is automatically canceled, the stock is restocked, the pending payment is voided so the order reads **\"Voided\"** rather than \"Payment pending\", and the shopper receives a cancellation email.\n11. Refunds are handled by you from the **PayLater merchant dashboard**, not from Shopify.\n\n### What every merchant must know before switching it on\n\nThese limits decide whether a shopper ever receives a payment link, so set your store up for them before you go live.\n\n- **Minimum order value is 300 QAR, maximum is 25,000 QAR.** An order outside this range gets no payment link and no email, so the shopper receives nothing at all. Hide or disable PayLater at checkout for baskets below 300 QAR so shoppers cannot select a method that cannot work for their order.\n- **An email address is required.** If a shopper checks out with a phone number only, there is no address to send the link to and no email goes out. The link still appears on the Shopify order, so you can send it to the shopper manually. To avoid this, require an email address at checkout (Shopify admin: **Settings → Checkout → Customer contact method**).\n- **Orders must be in Qatari Riyal.** PayLater settles in QAR, so your store's orders need to be in QAR.\n- **The payment window is 12 hours by default** and can be configured for your store. The reminder email is sent at the halfway point. The window can also be turned off entirely if you do not want orders to be canceled automatically.\n- **\"Payment pending\" is normal.** Until the shopper pays, the order legitimately sits as \"Payment pending\" in Shopify. This is expected behavior, not a problem with the order."
    },
    {
     "heading": "Manual Step",
     "anchor": "manual-step",
     "url": "https://docs.paylaterapp.com/shopify.html#manual-step",
     "markdown": "## Manual Step\n\n### Step 1: Setup PayLater as a Manual Payment Method\n\n- Go to your Shopify Admin (ex. [yourstore.com].myshopify.com/admin) and click on Settings in the left-hand navigation panel\n\n![Shopify Admin with Settings in the left-hand navigation panel](https://docs.paylaterapp.com/assets/img/plugins/shopify/01.png)\n\n- Click on the “Payment Menu” from left menu & scroll down to the “Manual payment methods” section.\n\n![Shopify Payments settings showing the Manual payment methods section](https://docs.paylaterapp.com/assets/img/plugins/shopify/02.png)\n\n- In this section, click on the dropdown labeled “Manual payment methods” and select \"Create custom payment method\"\n\n![Manual payment methods dropdown with Create custom payment method selected](https://docs.paylaterapp.com/assets/img/plugins/shopify/03.png)\n\n- In the “Set up manual payment form”, copy/paste the details as shown below in the appropriate field (NOTE: You can write according to your checkout design):\n  - (Custom payment method name) Split in 4 interest-free payments with PayLater\n  - (Additional Details) PayLater allows you to split your purchase into 4 interest-free installments.\n  - (Payment Instructions) You'll receive an email with a secure link from PayLater to complete your payment.\n\n![Set up manual payment form filled in with the PayLater details](https://docs.paylaterapp.com/assets/img/plugins/shopify/04.png)\n\n- Click on the \"Activate\" button to save your work. You will see “Split in 4 interest-free payments with PayLater“ appear under the Manual Payment Methods.\n\n![Activated PayLater method listed under Manual payment methods](https://docs.paylaterapp.com/assets/img/plugins/shopify/05.png)\n\n### Step 2: Setting Webhook\n\n- In Settings, go to “Notifications“ in the left menu and then click on the Webhooks.\n\n![Shopify Notifications settings with the Webhooks option](https://docs.paylaterapp.com/assets/img/plugins/shopify/06.png)\n\n- Now click “Create webhook”\n\n![Webhooks section with the Create webhook button](https://docs.paylaterapp.com/assets/img/plugins/shopify/07.png)\n\n- In the “Add webhook” form, copy/paste the details as shown below in the appropriate field and click on Save\n  - Event (Order Creation)\n  - Format (JSON)\n  - URL ([https://shopify.paylaterapp.com/api/webhooks/shopify](https://shopify.paylaterapp.com/api/webhooks/shopify))\n  - Webhook API version (the newest dated version in the list, for example 2026-10. Do not choose Unstable: it can change without notice)\n\n![Add webhook form filled with event, format, URL and API version](https://docs.paylaterapp.com/assets/img/plugins/shopify/08.png)\n\n### Step 3: Linking Shopify to PayLater Backend App\n\n- In Settings, go to “Apps and sales channel“ in the left menu and then click on the Develop apps button on the top.\n\n![Apps and sales channels settings with the Develop apps button](https://docs.paylaterapp.com/assets/img/plugins/shopify/09.png)\n\n![App development page in Shopify settings](https://docs.paylaterapp.com/assets/img/plugins/shopify/10.png)\n\n![Allow custom app development confirmation in Shopify](https://docs.paylaterapp.com/assets/img/plugins/shopify/11.png)\n\n- Click on “Create an app“\n\n![Create an app button on the app development page](https://docs.paylaterapp.com/assets/img/plugins/shopify/12.png)\n\n- Set “PayLater Payment” as App name\n\n![Create an app dialog with PayLater Payment set as the app name](https://docs.paylaterapp.com/assets/img/plugins/shopify/13.png)\n\n- Fill App URL form with [https://shopify.paylaterapp.com/api/webhooks/shopify](https://shopify.paylaterapp.com/api/webhooks/shopify) value\n\n![App URL field set to the PayLater webhook URL](https://docs.paylaterapp.com/assets/img/plugins/shopify/14.png)\n\n- Select below mentioned Scopes\n  - Orders (Read and Write)\n  - Fulfillment Services (Read and Write)\n\n![Admin API scopes with Orders set to Read and Write](https://docs.paylaterapp.com/assets/img/plugins/shopify/15.png)\n\n![Admin API scopes with Fulfillment Services set to Read and Write](https://docs.paylaterapp.com/assets/img/plugins/shopify/16.png)\n\n- Also set Redirect URLs form with [https://shopify.paylaterapp.com/api/webhooks/shopify](https://shopify.paylaterapp.com/api/webhooks/shopify) value\n\n![Redirect URLs field set to the PayLater webhook URL](https://docs.paylaterapp.com/assets/img/plugins/shopify/17.png)\n\n- Click “Release”\n\n![App configuration page with the Release button](https://docs.paylaterapp.com/assets/img/plugins/shopify/18.png)\n\n- Set Version name (optional) and then click “Release”\n  - Version name (optional) ⇒ v1-2025\n\n![Release dialog with the version name set to v1-2025](https://docs.paylaterapp.com/assets/img/plugins/shopify/19.png)\n\n### Step 4: Install the App on Your Store\n\n- Releasing a version does **not** install the app on your store — you have to install it yourself. Open the **PayLater Payment** app again (Settings → **Apps and sales channels** → **Develop apps** → **PayLater Payment**).\n- On the app’s **Overview** page, click **Install app** in the **Installs** card and confirm the installation.\n\n![App Overview page with an arrow pointing to the Install app button in the Installs card](https://docs.paylaterapp.com/assets/img/plugins/shopify/20.png)\n\n> **Warning: Don’t skip this step**\n>\n> It’s easy to miss. If you release the version but never click **Install app**, the app stays uninstalled and PayLater won’t receive your orders. Make sure the app shows as installed before you go live.\n\n### Final Step:\n\n- Save all the keys you receive during this all process.\n- Send following keys to your account manager to activate your store for payments.\n  - Shop URL (your-store.myshopify.com)\n  - Client ID\n  - Shopify API Secret (shpss****************************)\n\n> **Tip: Find your .myshopify.com domain (Shop URL)**\n>\n> Your **Shop URL** is your store’s permanent `.myshopify.com` admin domain — not your public website address, and it can’t be changed.\n>\n> In your Shopify admin, go to **Settings → Domains**. Your `.myshopify.com` domain is listed separately from your custom domains.\n>\n> You can also read it from your admin URL: if the address bar shows `admin.shopify.com/store/r8atb0-1h`, your Shop URL is `r8atb0-1h.myshopify.com`.\n\n> **Info: Note**\n>\n> Need sandbox credentials and a test shopper account to try the flow before going live? See the [Test environment](https://docs.paylaterapp.com/test-environment.html)."
    }
   ],
   "endpoints": []
  },
  {
   "slug": "odoo",
   "title": "Odoo 18 BNPL payment provider setup",
   "section": "Plugins & Platforms",
   "description": "Install and configure the PayLater Buy Now, Pay Later plugin for your Odoo online store — step by step, with screenshots.",
   "keywords": "odoo 18 payment provider module addons path update apps list developer mode upgrade client id client secret outlet id webhook secret environment uat live website no-code",
   "url": "https://docs.paylaterapp.com/odoo.html",
   "markdown_url": "https://docs.paylaterapp.com/odoo.md",
   "markdown": "# Odoo\n\nEmpower your Odoo online store with the PayLater Odoo Plugin, a cutting-edge solution designed to enhance the purchasing journey for both merchants and customers. This seamlessly integrated plugin opens up a world of possibilities, allowing merchants to offer a convenient and flexible PayLater option at checkout.\n\n## Download plugin\n\n**Download:** [Odoo v3.1 · latest](https://docs.paylaterapp.com/downloads/payment_paylater-v3.1.zip) ([release notes](https://docs.paylaterapp.com/release-notes.html#odoo-3-1))\n\nAlways install the latest version. Older releases are deprecated — see the [release notes](https://docs.paylaterapp.com/release-notes.html) for the full version history.\n\n## Installation\n\nPaylater empowers merchants to offer the popular Buy Now, Pay Later (BNPL) payment option to their customers on their Odoo websites. This document guides you through the installation and setup process of the PayLater plugin.\n\n### Before you begin\n\nEnsure you have an Odoo website up and running, with the **eCommerce** app installed.\n\n### Compatibility\n\n| Component | Supported |\n| --- | --- |\n| Odoo | **Odoo 18 only**. The module does not install on Odoo 17, and Odoo 19 is not supported yet. |\n| Required modules | `payment` and `website_sale` |\n| Store currency | QAR. Orders in any other currency are refused at checkout with \"PayLater only accepts payments in QAR\". |\n| Install method | Copy into an addons path on self-hosted Odoo or Odoo.sh. Odoo Online (SaaS) cannot install custom modules. |\n\n### Frontend Widgets:\n\nPLP Product List Page\n\n![PayLater widget on the Odoo product list page](https://docs.paylaterapp.com/assets/img/plugins/odoo/01.png)\n\nPDP Product Detail Page\n\n![PayLater widget on the Odoo product detail page](https://docs.paylaterapp.com/assets/img/plugins/odoo/02.png)\n\n![PayLater instalment breakdown on the Odoo product detail page](https://docs.paylaterapp.com/assets/img/plugins/odoo/03.png)\n\nCheckout Page\n\n![PayLater payment option on the Odoo checkout page](https://docs.paylaterapp.com/assets/img/plugins/odoo/04.png)\n\n## Install the module\n\n- Extract the zip. Inside it you will find a `payment_paylater` folder.\n- Copy the `payment_paylater` folder (not the outer folder from the zip) into one of your Odoo addons paths, for example `/mnt/extra-addons/payment_paylater`. On Odoo.sh, commit it to your project repository instead.\n- Restart the Odoo service.\n- Turn on **developer mode** (**Settings → Developer Tools → Activate the developer mode**).\n- Go to **Apps**, click **Update Apps List**, then remove the \"Apps\" filter and search for `paylater`.\n\n![Payment Provider: PayLater found in the Odoo Apps list after Update Apps List](https://docs.paylaterapp.com/assets/img/plugins/odoo/05.png)\n\n*Payment Provider: PayLater in the Apps list*\n\n- Click **Activate** (or **Install**) on **Payment Provider: PayLater**.\n\n![The installed Payment Provider: PayLater module page in Odoo Apps](https://docs.paylaterapp.com/assets/img/plugins/odoo/06.png)\n\n### Upgrading\n\nReplace the `payment_paylater` folder in your addons path with the one from the new zip and restart Odoo. Then go to **Apps**, remove the \"Apps\" filter, search for `paylater`, open the **⋮** menu on the PayLater card and click **Upgrade**. Your settings are kept.\n\n## Configurations\n\n- Navigate to **Website → Configuration → Payment Providers** (under **eCommerce**) and open **PayLater**.\n\n![Odoo Website Configuration Payment Provider screen](https://docs.paylaterapp.com/assets/img/plugins/odoo/07.png)\n\n- Fill in the PayLater fields on the **Credentials** tab:\n\n| Field | What to enter |\n| --- | --- |\n| Environment | **UAT (Testing)** with the [test credentials](https://docs.paylaterapp.com/test-environment.html), or **Live (Production)** with your live credentials. |\n| Client ID | Your OAuth client ID from PayLater. |\n| Client Secret | Your OAuth client secret from PayLater. |\n| Outlet ID | Your outlet ID from PayLater. |\n| Webhook Secret | Provided by PayLater after they register your webhook URL (see below). |\n| Minimum Order Amount | Orders below this amount do not see PayLater at checkout. Default 300. |\n\n- Set the provider **State** to **Enabled** (or **Test Mode**) and publish it.\n- Your webhook URL is `https://<your-store>/payment/paylater/callback`. Send it to the PayLater team; they register it and give you the Webhook Secret.\n\n![PayLater payment provider settings with Client ID, Client Secret and Outlet ID fields](https://docs.paylaterapp.com/assets/img/plugins/odoo/08.png)\n\n![PayLater payment provider configuration details in Odoo](https://docs.paylaterapp.com/assets/img/plugins/odoo/09.png)\n\n![PayLater payment provider enabled in Odoo](https://docs.paylaterapp.com/assets/img/plugins/odoo/10.png)\n\n- For widget settings, navigate to **Settings → PayLater**. The widgets are **off by default**: tick **Product List Page (Shop)** and **Product Detail Page** to show them, and set the minimum price, heading, sub heading and terms text.\n\n![PayLater widget settings under Odoo Settings](https://docs.paylaterapp.com/assets/img/plugins/odoo/11.png)\n\n- Click Save Changes to complete the configuration\n\n## Testing\n\nTo ensure smooth functionality, it's recommended to test the PayLater payment option:\n\n- Add a product to your cart and proceed to checkout\n- During checkout, select PayLater as the payment method.\n- You will be redirected to the secure PayLater platform to complete the BNPL payment process.\n- Use test shopper account provided in the [Test environment](https://docs.paylaterapp.com/test-environment.html).\n- Please use 300 QAR as the amount to test\n\n> **Note: Credentials**\n>\n> Get your `Client ID`, `Client Secret`, and `Outlet ID` from your PayLater account dashboard. For your `Webhook Secret`, share your webhook endpoint with the PayLater team. For sandbox values and test shoppers, see the [Test environment](https://docs.paylaterapp.com/test-environment.html).\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/odoo.html",
     "markdown": "# Odoo\n\nEmpower your Odoo online store with the PayLater Odoo Plugin, a cutting-edge solution designed to enhance the purchasing journey for both merchants and customers. This seamlessly integrated plugin opens up a world of possibilities, allowing merchants to offer a convenient and flexible PayLater option at checkout."
    },
    {
     "heading": "Download plugin",
     "anchor": "download-plugin",
     "url": "https://docs.paylaterapp.com/odoo.html#download-plugin",
     "markdown": "## Download plugin\n\n**Download:** [Odoo v3.1 · latest](https://docs.paylaterapp.com/downloads/payment_paylater-v3.1.zip) ([release notes](https://docs.paylaterapp.com/release-notes.html#odoo-3-1))\n\nAlways install the latest version. Older releases are deprecated — see the [release notes](https://docs.paylaterapp.com/release-notes.html) for the full version history."
    },
    {
     "heading": "Installation",
     "anchor": "installation",
     "url": "https://docs.paylaterapp.com/odoo.html#installation",
     "markdown": "## Installation\n\nPaylater empowers merchants to offer the popular Buy Now, Pay Later (BNPL) payment option to their customers on their Odoo websites. This document guides you through the installation and setup process of the PayLater plugin.\n\n### Before you begin\n\nEnsure you have an Odoo website up and running, with the **eCommerce** app installed.\n\n### Compatibility\n\n| Component | Supported |\n| --- | --- |\n| Odoo | **Odoo 18 only**. The module does not install on Odoo 17, and Odoo 19 is not supported yet. |\n| Required modules | `payment` and `website_sale` |\n| Store currency | QAR. Orders in any other currency are refused at checkout with \"PayLater only accepts payments in QAR\". |\n| Install method | Copy into an addons path on self-hosted Odoo or Odoo.sh. Odoo Online (SaaS) cannot install custom modules. |\n\n### Frontend Widgets:\n\nPLP Product List Page\n\n![PayLater widget on the Odoo product list page](https://docs.paylaterapp.com/assets/img/plugins/odoo/01.png)\n\nPDP Product Detail Page\n\n![PayLater widget on the Odoo product detail page](https://docs.paylaterapp.com/assets/img/plugins/odoo/02.png)\n\n![PayLater instalment breakdown on the Odoo product detail page](https://docs.paylaterapp.com/assets/img/plugins/odoo/03.png)\n\nCheckout Page\n\n![PayLater payment option on the Odoo checkout page](https://docs.paylaterapp.com/assets/img/plugins/odoo/04.png)"
    },
    {
     "heading": "Install the module",
     "anchor": "install-the-module",
     "url": "https://docs.paylaterapp.com/odoo.html#install-the-module",
     "markdown": "## Install the module\n\n- Extract the zip. Inside it you will find a `payment_paylater` folder.\n- Copy the `payment_paylater` folder (not the outer folder from the zip) into one of your Odoo addons paths, for example `/mnt/extra-addons/payment_paylater`. On Odoo.sh, commit it to your project repository instead.\n- Restart the Odoo service.\n- Turn on **developer mode** (**Settings → Developer Tools → Activate the developer mode**).\n- Go to **Apps**, click **Update Apps List**, then remove the \"Apps\" filter and search for `paylater`.\n\n![Payment Provider: PayLater found in the Odoo Apps list after Update Apps List](https://docs.paylaterapp.com/assets/img/plugins/odoo/05.png)\n\n*Payment Provider: PayLater in the Apps list*\n\n- Click **Activate** (or **Install**) on **Payment Provider: PayLater**.\n\n![The installed Payment Provider: PayLater module page in Odoo Apps](https://docs.paylaterapp.com/assets/img/plugins/odoo/06.png)\n\n### Upgrading\n\nReplace the `payment_paylater` folder in your addons path with the one from the new zip and restart Odoo. Then go to **Apps**, remove the \"Apps\" filter, search for `paylater`, open the **⋮** menu on the PayLater card and click **Upgrade**. Your settings are kept."
    },
    {
     "heading": "Configurations",
     "anchor": "configurations",
     "url": "https://docs.paylaterapp.com/odoo.html#configurations",
     "markdown": "## Configurations\n\n- Navigate to **Website → Configuration → Payment Providers** (under **eCommerce**) and open **PayLater**.\n\n![Odoo Website Configuration Payment Provider screen](https://docs.paylaterapp.com/assets/img/plugins/odoo/07.png)\n\n- Fill in the PayLater fields on the **Credentials** tab:\n\n| Field | What to enter |\n| --- | --- |\n| Environment | **UAT (Testing)** with the [test credentials](https://docs.paylaterapp.com/test-environment.html), or **Live (Production)** with your live credentials. |\n| Client ID | Your OAuth client ID from PayLater. |\n| Client Secret | Your OAuth client secret from PayLater. |\n| Outlet ID | Your outlet ID from PayLater. |\n| Webhook Secret | Provided by PayLater after they register your webhook URL (see below). |\n| Minimum Order Amount | Orders below this amount do not see PayLater at checkout. Default 300. |\n\n- Set the provider **State** to **Enabled** (or **Test Mode**) and publish it.\n- Your webhook URL is `https://<your-store>/payment/paylater/callback`. Send it to the PayLater team; they register it and give you the Webhook Secret.\n\n![PayLater payment provider settings with Client ID, Client Secret and Outlet ID fields](https://docs.paylaterapp.com/assets/img/plugins/odoo/08.png)\n\n![PayLater payment provider configuration details in Odoo](https://docs.paylaterapp.com/assets/img/plugins/odoo/09.png)\n\n![PayLater payment provider enabled in Odoo](https://docs.paylaterapp.com/assets/img/plugins/odoo/10.png)\n\n- For widget settings, navigate to **Settings → PayLater**. The widgets are **off by default**: tick **Product List Page (Shop)** and **Product Detail Page** to show them, and set the minimum price, heading, sub heading and terms text.\n\n![PayLater widget settings under Odoo Settings](https://docs.paylaterapp.com/assets/img/plugins/odoo/11.png)\n\n- Click Save Changes to complete the configuration"
    },
    {
     "heading": "Testing",
     "anchor": "testing",
     "url": "https://docs.paylaterapp.com/odoo.html#testing",
     "markdown": "## Testing\n\nTo ensure smooth functionality, it's recommended to test the PayLater payment option:\n\n- Add a product to your cart and proceed to checkout\n- During checkout, select PayLater as the payment method.\n- You will be redirected to the secure PayLater platform to complete the BNPL payment process.\n- Use test shopper account provided in the [Test environment](https://docs.paylaterapp.com/test-environment.html).\n- Please use 300 QAR as the amount to test\n\n> **Note: Credentials**\n>\n> Get your `Client ID`, `Client Secret`, and `Outlet ID` from your PayLater account dashboard. For your `Webhook Secret`, share your webhook endpoint with the PayLater team. For sandbox values and test shoppers, see the [Test environment](https://docs.paylaterapp.com/test-environment.html)."
    }
   ],
   "endpoints": []
  },
  {
   "slug": "opencart",
   "title": "OpenCart 4 BNPL extension setup",
   "section": "Plugins & Platforms",
   "description": "Install and configure the PayLater Buy Now, Pay Later plugin for OpenCart — step by step, with screenshots.",
   "keywords": "opencart 4 extension installer ocmod payment plugin php 8.2 client id client secret outlet id webhook secret mode uat production base url no-code",
   "url": "https://docs.paylaterapp.com/opencart.html",
   "markdown_url": "https://docs.paylaterapp.com/opencart.md",
   "markdown": "# OpenCart\n\nEmpower your OpenCart online store with the PayLater OpenCart Plugin, a cutting-edge solution designed to enhance the purchasing journey for both merchants and customers. This seamlessly integrated plugin opens up a world of possibilities, allowing merchants to offer a convenient and flexible PayLater option at checkout.\n\n## Download plugin\n\n**Download:** [OpenCart v3.1.1 · latest](https://docs.paylaterapp.com/downloads/opencart/3.1.1/paylater.ocmod.zip) ([release notes](https://docs.paylaterapp.com/release-notes.html#opencart-3-1-1))\n\nAlways install the latest version. Older releases are deprecated — see the [release notes](https://docs.paylaterapp.com/release-notes.html) for the full version history.\n\n> **Note: PHP requirement**\n>\n> The PayLater plugin needs **PHP 8.2 or newer**. Keep PHP up to date with the latest security release for your version.\n\n## Installation\n\nPaylater empowers merchants to offer the popular Buy Now, Pay Later (BNPL) payment option to their customers on their OpenCart websites. This document guides you through the installation and setup process of the PayLater plugin.\n\n### Before you begin\n\nEnsure you have an OpenCart store installed and running.\n\n### Compatibility\n\n| Component | Supported |\n| --- | --- |\n| OpenCart | **OpenCart 4.0.x** (tested on 4.0.2.3). OpenCart 3.x is not supported. |\n| PHP | 8.2 or newer (tested on 8.3) |\n| Store currency | QAR. A new OpenCart store has no QAR currency: add it under **System → Localisation → Currencies** and make it the default. PayLater and its widgets are shown only when the store's default currency and the shopper's chosen currency are both QAR, and the settings page warns you if the default is something else. |\n\n### Frontend Widgets:\n\nPLP Product List Page\n\n![PayLater widget on the OpenCart product list page](https://docs.paylaterapp.com/assets/img/plugins/opencart/01.png)\n\nPDP Product Detail Page\n\n![PayLater widget on the OpenCart product detail page](https://docs.paylaterapp.com/assets/img/plugins/opencart/02.png)\n\n![PayLater instalment breakdown on the OpenCart product detail page](https://docs.paylaterapp.com/assets/img/plugins/opencart/03.png)\n\n## Install the extension\n\nInstall the extension from the OpenCart admin with the Extension Installer.\n\n> **Note: Replacing an older version**\n>\n> If an older PayLater version is installed, first uninstall PayLater under **Extensions → Extensions → Payments**, then uninstall and delete it in **Extensions → Installer**. Otherwise the upload is refused with `Extension already installed!`. Note your Client ID, Client Secret and Outlet ID before you uninstall.\n>\n> Version 3.0 was packaged one folder too deep and was rejected with `Warning: Could not find install.json!`. If you tried it, delete `system/storage/marketplace/paylater.ocmod.zip` on your server before you upload the current version. Otherwise the installer answers `File already exist!`, and the failed file does not appear in the admin, so you cannot delete it there.\n\n- Navigate to **Extensions → Installer**, click **Upload** and choose the PayLater zip, then click **Install** next to PayLater in the list.\n\n![PayLater shown in the OpenCart Extension Installer after uploading](https://docs.paylaterapp.com/assets/img/plugins/opencart/04.png)\n\n*After uploading, PayLater appears in the Extension Installer list*\n\n## Configurations\n\n- Once installed, navigate to **Extensions → Extensions** and choose **Payments** from the type filter.\n- Locate PayLater in the list, click **Install** (the green + button), then **Edit**.\n\n![PayLater listed among the available payment gateways in OpenCart](https://docs.paylaterapp.com/assets/img/plugins/opencart/05.png)\n\n![Managing the PayLater payment gateway in OpenCart](https://docs.paylaterapp.com/assets/img/plugins/opencart/06.png)\n\n- Fill in the PayLater settings:\n\n| Setting | What to enter |\n| --- | --- |\n| Status | Turns the PayLater payment method on or off. |\n| Mode | **UAT** with the [test credentials](https://docs.paylaterapp.com/test-environment.html), or **Production** with your live credentials. |\n| Client ID / Client Secret | Your OAuth credentials from PayLater. |\n| Outlet ID | Your outlet ID from PayLater. |\n| UAT Base URL / Production Base URL | Pre-filled with `https://connect.uat.paylaterapp.com/` and `https://connect.paylaterapp.com/`. Leave them as they are unless PayLater tells you otherwise: your Client Secret is sent to these addresses. |\n| Payment Method Title | The payment method name shown at checkout. |\n| Sort Order | Position of PayLater in the list of payment methods. |\n| Webhook URL | Shown read-only. Send it to the PayLater team so they can register it. |\n| Webhook Secret | Provided by PayLater after they register your webhook URL. |\n| Widget settings | Turn the widget on or off for the product listing and product detail pages, and set its minimum amount (QAR), title and footer text. |\n\n![Entering the PayLater Client ID and Client Secret in the OpenCart configuration](https://docs.paylaterapp.com/assets/img/plugins/opencart/07.png)\n\n![Entering the PayLater Outlet ID in the OpenCart configuration](https://docs.paylaterapp.com/assets/img/plugins/opencart/08.png)\n\n- Click Save Changes to complete the configuration\n\n## Testing\n\nTo ensure smooth functionality, it's recommended to test the PayLater payment option:\n\n- Add a product to your cart and proceed to checkout\n- During checkout, select PayLater as the payment method.\n- You will be redirected to the secure PayLater platform to complete the BNPL payment process.\n- Use a test shopper account from the [Test environment](https://docs.paylaterapp.com/test-environment.html).\n- Please use 300 QAR as the amount to test\n\n> **Note: Credentials**\n>\n> Get your `Client ID`, `Client Secret`, and `Outlet ID` from your PayLater account dashboard. For your `Webhook Secret`, share your webhook endpoint with the PayLater team. For sandbox values and test shoppers, see the [Test environment](https://docs.paylaterapp.com/test-environment.html).\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/opencart.html",
     "markdown": "# OpenCart\n\nEmpower your OpenCart online store with the PayLater OpenCart Plugin, a cutting-edge solution designed to enhance the purchasing journey for both merchants and customers. This seamlessly integrated plugin opens up a world of possibilities, allowing merchants to offer a convenient and flexible PayLater option at checkout."
    },
    {
     "heading": "Download plugin",
     "anchor": "download-plugin",
     "url": "https://docs.paylaterapp.com/opencart.html#download-plugin",
     "markdown": "## Download plugin\n\n**Download:** [OpenCart v3.1.1 · latest](https://docs.paylaterapp.com/downloads/opencart/3.1.1/paylater.ocmod.zip) ([release notes](https://docs.paylaterapp.com/release-notes.html#opencart-3-1-1))\n\nAlways install the latest version. Older releases are deprecated — see the [release notes](https://docs.paylaterapp.com/release-notes.html) for the full version history.\n\n> **Note: PHP requirement**\n>\n> The PayLater plugin needs **PHP 8.2 or newer**. Keep PHP up to date with the latest security release for your version."
    },
    {
     "heading": "Installation",
     "anchor": "installation",
     "url": "https://docs.paylaterapp.com/opencart.html#installation",
     "markdown": "## Installation\n\nPaylater empowers merchants to offer the popular Buy Now, Pay Later (BNPL) payment option to their customers on their OpenCart websites. This document guides you through the installation and setup process of the PayLater plugin.\n\n### Before you begin\n\nEnsure you have an OpenCart store installed and running.\n\n### Compatibility\n\n| Component | Supported |\n| --- | --- |\n| OpenCart | **OpenCart 4.0.x** (tested on 4.0.2.3). OpenCart 3.x is not supported. |\n| PHP | 8.2 or newer (tested on 8.3) |\n| Store currency | QAR. A new OpenCart store has no QAR currency: add it under **System → Localisation → Currencies** and make it the default. PayLater and its widgets are shown only when the store's default currency and the shopper's chosen currency are both QAR, and the settings page warns you if the default is something else. |\n\n### Frontend Widgets:\n\nPLP Product List Page\n\n![PayLater widget on the OpenCart product list page](https://docs.paylaterapp.com/assets/img/plugins/opencart/01.png)\n\nPDP Product Detail Page\n\n![PayLater widget on the OpenCart product detail page](https://docs.paylaterapp.com/assets/img/plugins/opencart/02.png)\n\n![PayLater instalment breakdown on the OpenCart product detail page](https://docs.paylaterapp.com/assets/img/plugins/opencart/03.png)"
    },
    {
     "heading": "Install the extension",
     "anchor": "install-the-extension",
     "url": "https://docs.paylaterapp.com/opencart.html#install-the-extension",
     "markdown": "## Install the extension\n\nInstall the extension from the OpenCart admin with the Extension Installer.\n\n> **Note: Replacing an older version**\n>\n> If an older PayLater version is installed, first uninstall PayLater under **Extensions → Extensions → Payments**, then uninstall and delete it in **Extensions → Installer**. Otherwise the upload is refused with `Extension already installed!`. Note your Client ID, Client Secret and Outlet ID before you uninstall.\n>\n> Version 3.0 was packaged one folder too deep and was rejected with `Warning: Could not find install.json!`. If you tried it, delete `system/storage/marketplace/paylater.ocmod.zip` on your server before you upload the current version. Otherwise the installer answers `File already exist!`, and the failed file does not appear in the admin, so you cannot delete it there.\n\n- Navigate to **Extensions → Installer**, click **Upload** and choose the PayLater zip, then click **Install** next to PayLater in the list.\n\n![PayLater shown in the OpenCart Extension Installer after uploading](https://docs.paylaterapp.com/assets/img/plugins/opencart/04.png)\n\n*After uploading, PayLater appears in the Extension Installer list*"
    },
    {
     "heading": "Configurations",
     "anchor": "configurations",
     "url": "https://docs.paylaterapp.com/opencart.html#configurations",
     "markdown": "## Configurations\n\n- Once installed, navigate to **Extensions → Extensions** and choose **Payments** from the type filter.\n- Locate PayLater in the list, click **Install** (the green + button), then **Edit**.\n\n![PayLater listed among the available payment gateways in OpenCart](https://docs.paylaterapp.com/assets/img/plugins/opencart/05.png)\n\n![Managing the PayLater payment gateway in OpenCart](https://docs.paylaterapp.com/assets/img/plugins/opencart/06.png)\n\n- Fill in the PayLater settings:\n\n| Setting | What to enter |\n| --- | --- |\n| Status | Turns the PayLater payment method on or off. |\n| Mode | **UAT** with the [test credentials](https://docs.paylaterapp.com/test-environment.html), or **Production** with your live credentials. |\n| Client ID / Client Secret | Your OAuth credentials from PayLater. |\n| Outlet ID | Your outlet ID from PayLater. |\n| UAT Base URL / Production Base URL | Pre-filled with `https://connect.uat.paylaterapp.com/` and `https://connect.paylaterapp.com/`. Leave them as they are unless PayLater tells you otherwise: your Client Secret is sent to these addresses. |\n| Payment Method Title | The payment method name shown at checkout. |\n| Sort Order | Position of PayLater in the list of payment methods. |\n| Webhook URL | Shown read-only. Send it to the PayLater team so they can register it. |\n| Webhook Secret | Provided by PayLater after they register your webhook URL. |\n| Widget settings | Turn the widget on or off for the product listing and product detail pages, and set its minimum amount (QAR), title and footer text. |\n\n![Entering the PayLater Client ID and Client Secret in the OpenCart configuration](https://docs.paylaterapp.com/assets/img/plugins/opencart/07.png)\n\n![Entering the PayLater Outlet ID in the OpenCart configuration](https://docs.paylaterapp.com/assets/img/plugins/opencart/08.png)\n\n- Click Save Changes to complete the configuration"
    },
    {
     "heading": "Testing",
     "anchor": "testing",
     "url": "https://docs.paylaterapp.com/opencart.html#testing",
     "markdown": "## Testing\n\nTo ensure smooth functionality, it's recommended to test the PayLater payment option:\n\n- Add a product to your cart and proceed to checkout\n- During checkout, select PayLater as the payment method.\n- You will be redirected to the secure PayLater platform to complete the BNPL payment process.\n- Use a test shopper account from the [Test environment](https://docs.paylaterapp.com/test-environment.html).\n- Please use 300 QAR as the amount to test\n\n> **Note: Credentials**\n>\n> Get your `Client ID`, `Client Secret`, and `Outlet ID` from your PayLater account dashboard. For your `Webhook Secret`, share your webhook endpoint with the PayLater team. For sandbox values and test shoppers, see the [Test environment](https://docs.paylaterapp.com/test-environment.html)."
    }
   ],
   "endpoints": []
  },
  {
   "slug": "go-live",
   "title": "Production checklist",
   "section": "Go Live",
   "description": "Move your PayLater integration from sandbox to production.",
   "keywords": "go live production checklist launch deploy credentials switch endpoints urls",
   "url": "https://docs.paylaterapp.com/go-live.html",
   "markdown_url": "https://docs.paylaterapp.com/go-live.md",
   "markdown": "# Production checklist\n\nReady to take real payments? Work through this checklist before you flip to production.\n\n- Make sure you have a **live PayLater Merchant Dashboard account**, then get your production credentials (Client ID, Client Secret, Outlet ID) from **Settings → Integration** — see the [Quickstart](https://docs.paylaterapp.com/quickstart.html) for the exact steps.\n- Swap sandbox credentials for production values in your config / secret store.\n- Switch all base URLs from `connect.uat.paylaterapp.com` to `connect.paylaterapp.com`.\n- Point [webhooks](https://docs.paylaterapp.com/webhooks.html) at your production endpoint and load the production webhook secret.\n- Re-verify token refresh, link generation, status checks, and refunds end to end.\n- Confirm your success / failure redirect URLs are production URLs.\n\n> **Note: Can’t see the Integration section?**\n>\n> If **Settings → Integration** doesn’t appear in your Merchant Dashboard, your user role is missing the API-credentials permission. An admin can enable it:\n>\n> 1. Go to **User Management → Roles**.\n> 2. On your role, click **Edit** and open the **Admin** section.\n> 3. Toggle **API Credentials** on and save.\n> 4. Log out and log back in — **Settings → Integration** is now visible.\n\n## Production endpoints\n\n**Production endpoints**\n\n```bash\nToken:    https://connect.paylaterapp.com/auth/realms/api/protocol/openid-connect/token\nCheckout: https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/...\n```\n\n> **Info: Going live**\n>\n> Production credentials live in your **PayLater Merchant Dashboard** (Settings → Integration) and require a live merchant account. Until you're set up, keep building against the [Test environment](https://docs.paylaterapp.com/test-environment.html). Need a hand? [merchants@paylaterapp.com](mailto:merchants@paylaterapp.com).\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/go-live.html",
     "markdown": "# Production checklist\n\nReady to take real payments? Work through this checklist before you flip to production.\n\n- Make sure you have a **live PayLater Merchant Dashboard account**, then get your production credentials (Client ID, Client Secret, Outlet ID) from **Settings → Integration** — see the [Quickstart](https://docs.paylaterapp.com/quickstart.html) for the exact steps.\n- Swap sandbox credentials for production values in your config / secret store.\n- Switch all base URLs from `connect.uat.paylaterapp.com` to `connect.paylaterapp.com`.\n- Point [webhooks](https://docs.paylaterapp.com/webhooks.html) at your production endpoint and load the production webhook secret.\n- Re-verify token refresh, link generation, status checks, and refunds end to end.\n- Confirm your success / failure redirect URLs are production URLs.\n\n> **Note: Can’t see the Integration section?**\n>\n> If **Settings → Integration** doesn’t appear in your Merchant Dashboard, your user role is missing the API-credentials permission. An admin can enable it:\n>\n> 1. Go to **User Management → Roles**.\n> 2. On your role, click **Edit** and open the **Admin** section.\n> 3. Toggle **API Credentials** on and save.\n> 4. Log out and log back in — **Settings → Integration** is now visible."
    },
    {
     "heading": "Production endpoints",
     "anchor": "production-endpoints",
     "url": "https://docs.paylaterapp.com/go-live.html#production-endpoints",
     "markdown": "## Production endpoints\n\n**Production endpoints**\n\n```bash\nToken:    https://connect.paylaterapp.com/auth/realms/api/protocol/openid-connect/token\nCheckout: https://connect.paylaterapp.com/api/paylater/merchant-portal/v2/...\n```\n\n> **Info: Going live**\n>\n> Production credentials live in your **PayLater Merchant Dashboard** (Settings → Integration) and require a live merchant account. Until you're set up, keep building against the [Test environment](https://docs.paylaterapp.com/test-environment.html). Need a hand? [merchants@paylaterapp.com](mailto:merchants@paylaterapp.com)."
    }
   ],
   "endpoints": []
  },
  {
   "slug": "release-notes",
   "title": "Release Notes",
   "section": "Release Notes",
   "description": "Versioned release notes for the PayLater API and commerce plugins — what changed, when, and which package to download.",
   "keywords": "release notes changelog change log version versions whats new history updates semver semantic versioning api magento woocommerce shopify odoo opencart 4.0 3.0 v2 download package",
   "url": "https://docs.paylaterapp.com/release-notes.html",
   "markdown_url": "https://docs.paylaterapp.com/release-notes.md",
   "markdown": "# Release Notes\n\nEvery change to the PayLater API and our commerce plugins, newest first. The API and each plugin follow [Semantic Versioning](https://semver.org) on their own independent version timeline.\n\n## API\n\n### API v2.0 (2026-06-04)\n\nMajor release — OAuth 2.0 auth, all endpoints under /v2/, and snake_case payloads. Breaking changes from v1.\n\n- **Changed**: Authentication moved from a static `X-Api-Key` header to **OAuth 2.0 Bearer** tokens (`Authorization: Bearer <token>`). Obtain a token from `POST /auth/realms/api/protocol/openid-connect/token` with `grant_type=client_credentials`; access tokens expire (~3600s) and renew via the `refresh_token` grant (falling back to `client_credentials`).\n- **Changed**: Credentials are now a **Client ID + Client Secret** pair (previously a single permanent API key).\n- **Changed**: All endpoints moved under `/v2/` — e.g. `/web-checkout` → `/v2/web-checkout`, `/web-checkout/status` → `/v2/web-checkout/status`.\n- **Changed**: Request payloads are now `snake_case`: `outletId` → `outlet_id` (now an integer), `orderId` → `order_id`, `successRedirectUrl` → `success_redirect_url`, `failRedirectUrl` → `fail_redirect_url`.\n- **Removed**: `merchantId` is no longer sent in request bodies or query params — it is derived from the OAuth token at the gateway.\n- **Changed**: Status check query param `orderId` → `order_id` (`merchantId` no longer needed).\n- **Changed**: Full refund is now `POST /v2/web-checkout/refund` with `{\"order_id\":…}` in the body (was `GET …/refund` with query params). `transactionType=DOWN_PAYMENT` is no longer required and `transactionReference` → `order_id`.\n- **Changed**: Partial refund now uses the **same** `/v2/web-checkout/refund` endpoint as full refund — include an `amount` in the body to make it partial. The separate `/refund/partial` endpoint is gone.\n- **Added**: New `expiry_duration` field (minutes) controls the payment-link lifetime.\n- **Changed**: Webhooks are unchanged — same endpoint, payload shape, and HMAC-SHA256 signature. Note: the webhook payload still uses `merchantId` (camelCase), unlike v2 request bodies which are snake_case.\n\n## Magento\n\n### Magento 4.1.2 (2026-11-02)\n\nMaintenance update for newer PHP versions.\n\n**Download:** [PayLater_PayLaterpay-4.1.2.zip](https://docs.paylaterapp.com/downloads/PayLater_PayLaterpay-4.1.2.zip)\n\n- **Fixed**: No deprecation notices on PHP 8.4 and 8.5. The availability check now declares its optional cart parameter as nullable, and the module no longer calls `curl_close()`, which does nothing since PHP 8.0 and is deprecated in PHP 8.5. Behaviour is unchanged.\n\n### Magento 4.1.1 (2026-10-29)\n\nSecurity and reliability update. All merchants should upgrade.\n\n**Download:** [PayLater_PayLaterpay-4.1.1.zip](https://docs.paylaterapp.com/downloads/PayLater_PayLaterpay-4.1.1.zip)\n\n- **Fixed**: If PayLater confirms a payment for an order that Magento has already cancelled, the module now requests a full refund automatically and records the outcome in the order history. The shopper is told the payment is being refunded instead of seeing a success message.\n- **Fixed**: The product page and product list widget no longer changes the rest of the storefront (page spacing, background, headings). Its styles now apply to the widget only.\n- **Fixed**: Orders where the shopper never continued to PayLater are cancelled automatically once the payment link expires, releasing reserved stock.\n- **Fixed**: The background order check no longer misses PayLater orders on stores with many pending orders from other payment methods.\n- **Fixed**: A shopper who lands on the failure page for an order that was actually paid now sees the order confirmation instead of a restored cart.\n- **Fixed**: If a payment is confirmed after the order was cancelled, the automatic refund is retried for up to 24 hours (PayLater accepts refunds from 10 minutes after payment). Only then is the merchant asked to refund manually.\n- **Fixed**: The product and category widgets show the amount due today rounded to the fils, and the four instalments shown add up exactly to the price (for example QAR 333.33 shows 83.33, 83.33, 83.33 and 83.34). The category list used to show unrounded amounts such as \"Pay QAR 83.3325 today\".\n- **Fixed**: PayLater is no longer offered, on product pages or at checkout, for totals above QAR 25,000, which PayLater does not accept. Shoppers used to pick it and then see an error.\n- **Fixed**: A failure or cancellation notice from PayLater no longer cancels the order straight away. The module first checks the payment's status with PayLater: it cancels only if PayLater confirms the failure, keeps the order if PayLater reports it paid, and otherwise leaves it for the regular status check. This protects orders from late or repeated notices.\n- **Fixed**: If PayLater already holds the order number (for example a second store on the same PayLater account, or a store whose order numbers restarted), checkout no longer fails with \"Order ID already exists\". The module retries once with a short suffix, such as 000000123-a7f2, and that reference is what appears in the PayLater merchant portal for that order. Other orders keep their Magento order number.\n- **Fixed**: Error messages from PayLater are shown as a plain sentence, such as \"The maximum order value for PayLater at this merchant is QAR 25000.00\" or the reason a refund was refused. Shoppers used to see raw technical text, and admins often saw only \"Internal Server Error\" or \"Refund Error\".\n- **Fixed**: When a shopper cancels a payment, their cart comes back only if the cancelled order is the one they just placed. Before, the cart of their previous order, even an already paid one, could be reopened, risking a second payment.\n- **Security**: Hardened payment status handling.\n- **Security**: Hardened order creation at checkout.\n- **Security**: The payment return and cancel pages show an order's confirmation only to the shopper whose session placed it. Before, anyone could open another shopper's order confirmation by changing the order number in the address.\n- **Security**: The cancel page no longer displays text taken from the page address, which could be used to show misleading messages on the store.\n\n### Magento 4.1 (2026-07-23)\n\nSafer payment confirmation and an option to skip the pre-checkout stock check.\n\n**Download:** [PayLater_PayLaterpay-4.1.0.zip](https://docs.paylaterapp.com/downloads/PayLater_PayLaterpay-4.1.0.zip)\n\n- **Added**: Admin setting **Pre-Checkout Inventory Check**. Set it to No to skip the stock check before the shopper is sent to PayLater; Magento still checks stock when the order is placed.\n- **Fixed**: A paid order is moved to Processing before its invoice is created.\n- **Fixed**: A shopper who returns while PayLater is still confirming the payment sees the order confirmation, and the order waits for the background check. Only a payment PayLater reports as failed fails the order.\n- **Changed**: If PayLater confirms a payment for an order that Magento has already cancelled, the order is left unchanged and the case is logged for the merchant to reconcile.\n- **Changed**: Remaining Spotii names were removed from the module.\n\n### Magento 4.0 (2026-06-04)\n\nOAuth2 Bearer auth on the v2 web-checkout endpoints.\n\n**Download:** [PayLaterpay-v4.0.zip](https://docs.paylaterapp.com/downloads/PayLaterpay-v4.0.zip)\n\n- **Changed**: Authentication switched from `x-api-key` to **OAuth2 Bearer**.\n- **Changed**: Endpoints moved to `/v2/web-checkout`.\n- **Changed**: Payload field names are now `snake_case` (`outletId → outlet_id`, `orderId → order_id`, `successRedirectUrl → success_redirect_url`, `failRedirectUrl → fail_redirect_url`).\n- **Changed**: `expiry_duration` is now required on create-payment-link.\n- **Changed**: Full and partial refunds collapse onto one endpoint.\n- **Changed**: Admin settings rearranged — the Test/Live API Key fields are removed.\n- **Added**: New admin fields: Client ID and Client Secret. **Payment Mode** (UAT or Live) selects the environment.\n\n### Magento 3.0 (2026-05-17)\n\nOnline refunds, webhook receiver, and admin validation.\n\n- **Added**: Online refunds from the orders admin screen — calls PayLater’s gateway directly, no manual reversal needed.\n- **Added**: Full refund routing: `GET /web-checkout/refund` with `transactionType=DOWN_PAYMENT`.\n- **Added**: Partial refund routing: `POST /web-checkout/refund/partial` with `{\"amount\":…}`.\n- **Added**: Webhook receiver for server-to-server settlement.\n- **Security**: Webhook Secret admin field (encrypted) for signature verification.\n- **Changed**: Minimum Order Amount must be ≥ 300 at save time, with field validation for immediate feedback.\n- **Changed**: API Key validation preserves placeholder behaviour.\n\n### Magento 2.0 (2026-04-30)\n\nRefactor to the standard Magento redirect-gateway pattern + reconciliation cron.\n\n- **Changed**: Order lifecycle refactored to match the standard Magento redirect-gateway pattern (Authorization & Capture).\n- **Added**: New `paylater_reconcile_pending_orders` cron runs every 10 minutes — queries `web-checkout/status` for pending orders older than 3 minutes.\n\n### Magento 1.3 (2026-01-28)\n\nBranding refresh, visibility widgets, and admin workflow.\n\n- **Changed**: Updated branding; seamless redirect and order-success sync; Authorization & Capture.\n- **Added**: New visibility widgets and widget-management toggles in Stores → Configuration.\n- **Added**: Streamlined setup wizard and enhanced debug logging for API requests.\n\n### Magento 1.2 (2025-03-28)\n\nMagento 2.4.7+ support and checkout polish.\n\n- **Added**: Magento 2.4.7+ support — verified with PHP 8.1, 8.2, and 8.3.\n- **Added**: Payment-method logo and checkout display.\n- **Added**: Environment toggle, API management, setup wizard, and debug logging.\n\n## WooCommerce\n\n### WooCommerce 4.4.1 (2026-11-01)\n\nFix for stores using the block-based checkout, plus hardening.\n\n**Download:** [paylater_v4.4.1.zip](https://docs.paylaterapp.com/downloads/paylater_v4.4.1.zip)\n\n- **Fixed**: PayLater now appears in the block-based checkout, and the product widgets show, as soon as the API Configuration tab is saved. Before, they stayed off until the General and Widget Settings tabs had also been saved.\n- **Security**: Settings tab links, labels and the product widget footer are escaped before output, and error messages from the PayLater API are escaped before they are added to order notes.\n- **Changed**: Errors are logged to the WooCommerce log (WooCommerce → Status → Logs, source \"paylater\") instead of the PHP error log.\n\n### WooCommerce 4.4 (2026-10-29)\n\nSecurity and reliability update. All merchants should upgrade.\n\n**Download:** [paylater_v4.4.zip](https://docs.paylaterapp.com/downloads/paylater_v4.4.zip)\n\n- **Fixed**: Every payment link issued for an order is kept, so a payment on an earlier link (after a double click, the back button or a retry) is no longer lost. Refunds go to the link that was actually paid.\n- **Fixed**: Two payment links created for one order in the same second no longer collide.\n- **Fixed**: Webhooks for an unknown reference now return `404` so PayLater retries them, instead of being acknowledged and dropped.\n- **Fixed**: The return page checks the payment status with PayLater before failing an order, and a new payment link is refused for an order already paid.\n- **Fixed**: Payment link expiry is capped at 1,440 minutes, and WooCommerce no longer cancels an unpaid order while its PayLater link can still be paid.\n- **Fixed**: Status checks, refunds and webhooks for an order use the environment (test or live) it was paid in, so switching Test Mode no longer strands earlier orders.\n- **Fixed**: The block checkout no longer depends on jQuery, and unused scripts from the old iframe checkout were removed.\n- **Fixed**: Failure webhooks, including the `fail` status PayLater sends today, are checked with the status API: a link PayLater reports as paid is marked paid, only a confirmed failure fails the order, and otherwise the order stays payable so the shopper can retry.\n- **Fixed**: Refund order notes show the amount and currency in plain text and name the payment reference refunded.\n- **Fixed**: The widget uses the price as the shop displays it, and the instalments shown add up exactly to the price (for example QAR 333.33 shows 83.33, 83.33, 83.33 and 83.34).\n- **Fixed**: A notice from PayLater dated in the future no longer blocks later notices for the same payment.\n- **Security**: An order is only marked paid when the amount the shopper paid matches the current order total. A mismatch puts the order on hold with a note for review.\n- **Security**: Webhooks can no longer move an order backwards: replayed, duplicate or out-of-order notifications are ignored, and a failure on an older link never fails an order being paid with a newer one.\n- **Security**: Shoppers see a plain message when PayLater cannot create a payment link. The gateway response is recorded in the order notes and the WooCommerce log (source `paylater`) instead.\n- **Security**: Hardened storefront script handling.\n- **Changed**: PayLater is only offered, in both the classic and block checkout, for QAR orders between the configured minimum (at least 300 QAR) and 25,000 QAR. The product widgets follow the same limits.\n\n### WooCommerce 4.3 (2026-08-17)\n\nThe widget no longer changes the look of the rest of the store.\n\n**Download:** [paylater_v4.3.zip](https://docs.paylaterapp.com/downloads/paylater_v4.3.zip)\n\n- **Fixed**: The product page and product list widget styles now apply to the widget only. Before, they changed page spacing, fonts and headings across the store.\n- **Changed**: PayLater's stylesheet and script load only on WooCommerce pages (shop, product, cart, checkout and account), not on every page. Developers can change this with the `paylater_enqueue_frontend_assets` filter.\n\n### WooCommerce 4.2 (2026-08-09)\n\nPlugin name and version clean-up.\n\n**Download:** [paylater_v4.2.zip](https://docs.paylaterapp.com/downloads/paylater_v4.2.zip)\n\n- **Changed**: The plugin is listed as **PayLater** (was PayLater Pro) under WordPress → Plugins, and shows its real version number.\n- **Fixed**: After updating, browsers load the new checkout styles instead of keeping the old ones.\n\n### WooCommerce 4.1 (2026-07-12)\n\nRepackaged so the plugin installs through Upload Plugin.\n\n**Download:** [paylater_v4.1.zip](https://docs.paylaterapp.com/downloads/paylater_v4.1.zip)\n\n- **Fixed**: The download installs through **Plugins → Add New → Upload Plugin**: the zip now holds a single `paylater/` folder. Before, the plugin sat one folder too deep and WordPress could not install it. No code changes from 4.0.\n\n### WooCommerce 4.0 (2026-06-04)\n\nOAuth2 Bearer auth on the v2 web-checkout endpoints.\n\n- **Changed**: Authentication switched from `x-api-key` to **OAuth2 Bearer**.\n- **Changed**: Endpoints moved to `/v2/web-checkout`.\n- **Changed**: Payload field names are now `snake_case` (`outletId → outlet_id`, `orderId → order_id`, …).\n- **Changed**: `expiry_duration` is now required on create-payment-link.\n- **Changed**: Full and partial refunds collapse onto one endpoint.\n- **Changed**: Admin Test/Live API Key fields removed.\n- **Added**: New admin fields: Test Client ID, Test Client Secret, Live Client ID, Live Client Secret.\n\n### WooCommerce 3.0 (2026-05-17)\n\nOnline refunds, webhook receiver, and admin validation.\n\n- **Added**: Online refunds from the orders admin screen.\n- **Added**: Full refund routing: `GET /web-checkout/refund` with `transactionType=DOWN_PAYMENT`.\n- **Added**: Partial refund routing: `POST /web-checkout/refund/partial`.\n- **Added**: Webhook receiver for server-to-server settlement.\n- **Security**: Webhook Secret admin field (encrypted) for signature verification.\n- **Changed**: Minimum Order Amount must be ≥ 300 at save time, with field validation.\n- **Changed**: API Key validation preserves placeholder behaviour.\n\n### WooCommerce 2.1 (2026-04-30)\n\nCallback hardening and safer redirects.\n\n- **Security**: Callback `orderId` is now bound to the order’s stored reference; orders not initiated through PayLater are rejected.\n- **Changed**: All redirects switched to `wp_safe_redirect()`.\n- **Changed**: Error-field parsing aligned with the gateway contract.\n\n### WooCommerce 2.0 (2026-01-28)\n\nDynamic instalments, WooCommerce Blocks + HPOS, and branding.\n\n- **Added**: Dynamic monthly instalments, collapsible PDP widget, and PLP summary bar.\n- **Added**: WooCommerce Blocks integration; HPOS certified; unified asset management; hardened admin interface.\n- **Added**: Full-width branding, branded loader overlay, and mobile-optimised UI.\n\n### WooCommerce 1.0 (2025-06-28)\n\nFirst WooCommerce release.\n\n- **Added**: Direct API handshake, order-total logic, and classic checkout support.\n- **Added**: Centralized admin dashboard, dual-environment support, and automated refund engine.\n- **Security**: Secure API handling, security callback handler, and smart redirects.\n\n## Odoo\n\n### Odoo 3.1 (2026-10-29)\n\nSecurity and reliability update. All merchants should upgrade.\n\n**Download:** [payment_paylater-v3.1.zip](https://docs.paylaterapp.com/downloads/payment_paylater-v3.1.zip)\n\n- **Security**: Webhooks are matched to the order only by the signed `orderId`; unsigned fields can no longer redirect a webhook to another order.\n- **Security**: Hardened payment status handling on the shopper return page.\n- **Fixed**: Paid PayLater orders are now confirmed in Odoo and recorded in Accounting. Previously the payment succeeded but the sale order stayed unconfirmed.\n- **Fixed**: A repeated payment webhook no longer marks pending refunds as complete.\n- **Fixed**: Refunds can be issued from the Odoo admin again, and are booked once PayLater accepts them.\n- **Fixed**: Rejected webhooks now return a real `4xx` status so PayLater can retry.\n- **Fixed**: Shoppers always land on the payment status page after returning from PayLater, including when they did not complete the payment.\n- **Fixed**: Failure notices from PayLater (\"fail\", \"failed\", \"expired\" and similar) are now acted on, after the module confirms the payment's status with PayLater. Before, \"fail\" and \"expired\" were ignored, and a confirmed failure was never recorded.\n- **Fixed**: A scheduled check every 15 minutes asks PayLater about payments still waiting for a result, so a lost notice no longer leaves an order open. A payment still pending 24 hours after its link was created is closed, because the link has expired.\n- **Fixed**: A shopper can no longer start a second payment for an order that is already paid, and a second payment that still arrives is flagged in the order's chatter for a refund.\n- **Fixed**: Old, repeated or future-dated notices no longer change a payment.\n- **Fixed**: The widget is shown only from QAR 300 (or the configured minimum, if higher) up to QAR 25,000, and the instalments shown add up exactly to the price.\n- **Fixed**: Notices with an unrecognised status are recorded in the chatter.\n- **Changed**: PayLater is only offered for QAR orders between 300 and 25,000 QAR.\n\n### Odoo 3.0 (2026-06-04)\n\nOAuth2 Bearer auth on the v2 web-checkout endpoints.\n\n**Download:** [payment_paylater-v3.0.zip](https://docs.paylaterapp.com/downloads/payment_paylater-v3.0.zip)\n\n- **Changed**: Authentication switched from `x-api-key` to **OAuth2 Bearer**.\n- **Changed**: Endpoints moved to `/v2/web-checkout`.\n- **Changed**: Payload field names are now `snake_case` (`outletId → outlet_id`, `orderId → order_id`, …).\n- **Changed**: `expiry_duration` is now required on create-payment-link.\n- **Changed**: Full and partial refunds collapse onto one endpoint.\n- **Changed**: Admin Test/Live API Key fields removed.\n- **Added**: New admin fields: Client ID and Client Secret. **Environment** (UAT or Live) selects the environment.\n\n### Odoo 2.0 (2026-05-17)\n\nOnline refunds, webhook receiver, and admin validation.\n\n- **Added**: Online refunds from the orders admin screen.\n- **Added**: Full refund routing: `GET /web-checkout/refund` with `transactionType=DOWN_PAYMENT`.\n- **Added**: Partial refund routing: `POST /web-checkout/refund/partial`.\n- **Added**: Webhook receiver for server-to-server settlement.\n- **Security**: Webhook Secret admin field for signature verification.\n- **Changed**: Minimum Order Amount must be ≥ 300 at save time, with field validation.\n- **Changed**: API Key validation preserves placeholder behaviour.\n\n### Odoo 1.0 (2026-04-24)\n\nFirst Odoo release.\n\n- **Added**: Direct API handshake, order-total logic, and classic checkout support.\n- **Added**: Centralized admin dashboard, dual-environment support, and automated refund engine.\n- **Security**: Secure API handling, security callback handler, and smart redirects.\n\n## OpenCart\n\n### OpenCart 3.1.1 (2026-11-01)\n\nFixes for new installs and for stores not priced in QAR.\n\n**Download:** [paylater.ocmod.zip](https://docs.paylaterapp.com/downloads/opencart/3.1.1/paylater.ocmod.zip)\n\n- **Fixed**: A new install no longer shows blank UAT and Production Base URL fields, which broke every payment once saved. The defaults now show until the settings are first saved, a blank Base URL is saved as the PayLater address, and stores that already saved blank addresses use the PayLater addresses again.\n- **Fixed**: PayLater is only offered, and its product widgets only shown, when the store's default currency and the shopper's chosen currency are both Qatari Riyal. Before, totals in another currency were sent to PayLater as the same number of riyals. The settings page now warns when the default currency is not QAR.\n- **Fixed**: Removed `curl_close()` calls, which are deprecated in PHP 8.5.\n\n### OpenCart 3.1 (2026-10-29)\n\nSecurity and reliability update. All merchants should upgrade.\n\n**Download:** [paylater.ocmod.zip](https://docs.paylaterapp.com/downloads/opencart/3.1.0/paylater.ocmod.zip)\n\n- **Security**: An order is only marked paid when the amount the shopper paid matches the current order total. A mismatch is left for the merchant to review, with a note in the order history.\n- **Security**: Webhooks can no longer move an order backwards: replayed, duplicate or out-of-order notifications are ignored, and a paid order is never reset to pending or failed.\n- **Security**: Hardened payment status handling on the shopper return page.\n- **Fixed**: Every payment link generated for an order is tracked, so a payment on an earlier link (after a double click or the back button) is no longer lost. Refunds go to the link that was actually paid.\n- **Fixed**: A new payment link can no longer be generated for an order that is already paid.\n- **Fixed**: The package now installs through **Extensions → Installer**: `install.json` sits at the root of the zip.\n- **Fixed**: A shopper who cancels on the PayLater payment page now returns to their cart with a \"Payment was not completed\" message. Before, the store started a new, empty session on the way back (OpenCart's default session cookie setting), so the cart looked empty and no message appeared.\n- **Fixed**: If a payment is confirmed for an order the store has already cancelled, the shopper is no longer told the order was placed. They see a message that the payment will be refunded, and the order history asks the merchant to refund it.\n- **Fixed**: PayLater is only offered at checkout for order totals from QAR 300 to 25,000, which PayLater accepts. Shoppers were offered it and then refused at the pay button.\n- **Fixed**: The product page widget uses the price including tax, the same as the product list, and the widget is hidden above QAR 25,000. The amounts shown add up exactly to the price.\n- **Fixed**: A failure notice from PayLater no longer fails the order straight away. The extension checks the payment's status with PayLater first, so an old or repeated notice cannot fail an order the shopper is still paying, or has paid.\n- **Fixed**: A notice dated in the future no longer blocks later notices for the same payment.\n- **Fixed**: Notices with a status the extension does not recognise are now recorded in the order history.\n- **Fixed**: The admin rejects a widget minimum below QAR 300 or above 25,000.\n\n### OpenCart 3.0 (2026-06-04)\n\nOAuth2 Bearer auth on the v2 web-checkout endpoints.\n\n**Download:** [paylater.ocmod.zip](https://docs.paylaterapp.com/downloads/paylater.ocmod.zip)\n\n- **Changed**: Authentication switched from `x-api-key` to **OAuth2 Bearer**.\n- **Changed**: Endpoints moved to `/v2/web-checkout`.\n- **Changed**: Payload field names are now `snake_case` (`outletId → outlet_id`, `orderId → order_id`, …).\n- **Changed**: `expiry_duration` is now required on create-payment-link.\n- **Changed**: Full and partial refunds collapse onto one endpoint.\n- **Changed**: Admin Test/Live API Key fields removed.\n- **Added**: New admin fields: Client ID and Client Secret. **Mode** (UAT or Production) selects the environment.\n\n### OpenCart 2.0 (2026-05-17)\n\nOnline refunds, webhook receiver, and admin validation.\n\n- **Added**: Online refunds from the orders admin screen.\n- **Added**: Full refund routing: `GET /web-checkout/refund` with `transactionType=DOWN_PAYMENT`.\n- **Added**: Partial refund routing: `POST /web-checkout/refund/partial`.\n- **Added**: Webhook receiver for server-to-server settlement.\n- **Security**: Webhook Secret admin field (encrypted) for signature verification.\n- **Changed**: Minimum Order Amount must be ≥ 300 at save time, with field validation.\n- **Changed**: API Key validation preserves placeholder behaviour.\n\n### OpenCart 1.0 (2026-04-24)\n\nFirst OpenCart release.\n\n- **Added**: Core payment features: direct API handshake, order-total logic, and classic checkout support.\n- **Added**: Centralized admin dashboard, dual-environment support, and automated refund engine.\n- **Security**: Secure API handling, security callback handler, and smart redirects.\n",
   "sections": [
    {
     "heading": "",
     "anchor": "",
     "url": "https://docs.paylaterapp.com/release-notes.html",
     "markdown": "# Release Notes\n\nEvery change to the PayLater API and our commerce plugins, newest first. The API and each plugin follow [Semantic Versioning](https://semver.org) on their own independent version timeline."
    },
    {
     "heading": "API",
     "anchor": "api",
     "url": "https://docs.paylaterapp.com/release-notes.html#api",
     "markdown": "## API\n\n### API v2.0 (2026-06-04)\n\nMajor release — OAuth 2.0 auth, all endpoints under /v2/, and snake_case payloads. Breaking changes from v1.\n\n- **Changed**: Authentication moved from a static `X-Api-Key` header to **OAuth 2.0 Bearer** tokens (`Authorization: Bearer <token>`). Obtain a token from `POST /auth/realms/api/protocol/openid-connect/token` with `grant_type=client_credentials`; access tokens expire (~3600s) and renew via the `refresh_token` grant (falling back to `client_credentials`).\n- **Changed**: Credentials are now a **Client ID + Client Secret** pair (previously a single permanent API key).\n- **Changed**: All endpoints moved under `/v2/` — e.g. `/web-checkout` → `/v2/web-checkout`, `/web-checkout/status` → `/v2/web-checkout/status`.\n- **Changed**: Request payloads are now `snake_case`: `outletId` → `outlet_id` (now an integer), `orderId` → `order_id`, `successRedirectUrl` → `success_redirect_url`, `failRedirectUrl` → `fail_redirect_url`.\n- **Removed**: `merchantId` is no longer sent in request bodies or query params — it is derived from the OAuth token at the gateway.\n- **Changed**: Status check query param `orderId` → `order_id` (`merchantId` no longer needed).\n- **Changed**: Full refund is now `POST /v2/web-checkout/refund` with `{\"order_id\":…}` in the body (was `GET …/refund` with query params). `transactionType=DOWN_PAYMENT` is no longer required and `transactionReference` → `order_id`.\n- **Changed**: Partial refund now uses the **same** `/v2/web-checkout/refund` endpoint as full refund — include an `amount` in the body to make it partial. The separate `/refund/partial` endpoint is gone.\n- **Added**: New `expiry_duration` field (minutes) controls the payment-link lifetime.\n- **Changed**: Webhooks are unchanged — same endpoint, payload shape, and HMAC-SHA256 signature. Note: the webhook payload still uses `merchantId` (camelCase), unlike v2 request bodies which are snake_case."
    },
    {
     "heading": "Magento",
     "anchor": "magento",
     "url": "https://docs.paylaterapp.com/release-notes.html#magento",
     "markdown": "## Magento\n\n### Magento 4.1.2 (2026-11-02)\n\nMaintenance update for newer PHP versions.\n\n**Download:** [PayLater_PayLaterpay-4.1.2.zip](https://docs.paylaterapp.com/downloads/PayLater_PayLaterpay-4.1.2.zip)\n\n- **Fixed**: No deprecation notices on PHP 8.4 and 8.5. The availability check now declares its optional cart parameter as nullable, and the module no longer calls `curl_close()`, which does nothing since PHP 8.0 and is deprecated in PHP 8.5. Behaviour is unchanged.\n\n### Magento 4.1.1 (2026-10-29)\n\nSecurity and reliability update. All merchants should upgrade.\n\n**Download:** [PayLater_PayLaterpay-4.1.1.zip](https://docs.paylaterapp.com/downloads/PayLater_PayLaterpay-4.1.1.zip)\n\n- **Fixed**: If PayLater confirms a payment for an order that Magento has already cancelled, the module now requests a full refund automatically and records the outcome in the order history. The shopper is told the payment is being refunded instead of seeing a success message.\n- **Fixed**: The product page and product list widget no longer changes the rest of the storefront (page spacing, background, headings). Its styles now apply to the widget only.\n- **Fixed**: Orders where the shopper never continued to PayLater are cancelled automatically once the payment link expires, releasing reserved stock.\n- **Fixed**: The background order check no longer misses PayLater orders on stores with many pending orders from other payment methods.\n- **Fixed**: A shopper who lands on the failure page for an order that was actually paid now sees the order confirmation instead of a restored cart.\n- **Fixed**: If a payment is confirmed after the order was cancelled, the automatic refund is retried for up to 24 hours (PayLater accepts refunds from 10 minutes after payment). Only then is the merchant asked to refund manually.\n- **Fixed**: The product and category widgets show the amount due today rounded to the fils, and the four instalments shown add up exactly to the price (for example QAR 333.33 shows 83.33, 83.33, 83.33 and 83.34). The category list used to show unrounded amounts such as \"Pay QAR 83.3325 today\".\n- **Fixed**: PayLater is no longer offered, on product pages or at checkout, for totals above QAR 25,000, which PayLater does not accept. Shoppers used to pick it and then see an error.\n- **Fixed**: A failure or cancellation notice from PayLater no longer cancels the order straight away. The module first checks the payment's status with PayLater: it cancels only if PayLater confirms the failure, keeps the order if PayLater reports it paid, and otherwise leaves it for the regular status check. This protects orders from late or repeated notices.\n- **Fixed**: If PayLater already holds the order number (for example a second store on the same PayLater account, or a store whose order numbers restarted), checkout no longer fails with \"Order ID already exists\". The module retries once with a short suffix, such as 000000123-a7f2, and that reference is what appears in the PayLater merchant portal for that order. Other orders keep their Magento order number.\n- **Fixed**: Error messages from PayLater are shown as a plain sentence, such as \"The maximum order value for PayLater at this merchant is QAR 25000.00\" or the reason a refund was refused. Shoppers used to see raw technical text, and admins often saw only \"Internal Server Error\" or \"Refund Error\".\n- **Fixed**: When a shopper cancels a payment, their cart comes back only if the cancelled order is the one they just placed. Before, the cart of their previous order, even an already paid one, could be reopened, risking a second payment.\n- **Security**: Hardened payment status handling.\n- **Security**: Hardened order creation at checkout.\n- **Security**: The payment return and cancel pages show an order's confirmation only to the shopper whose session placed it. Before, anyone could open another shopper's order confirmation by changing the order number in the address.\n- **Security**: The cancel page no longer displays text taken from the page address, which could be used to show misleading messages on the store.\n\n### Magento 4.1 (2026-07-23)\n\nSafer payment confirmation and an option to skip the pre-checkout stock check.\n\n**Download:** [PayLater_PayLaterpay-4.1.0.zip](https://docs.paylaterapp.com/downloads/PayLater_PayLaterpay-4.1.0.zip)\n\n- **Added**: Admin setting **Pre-Checkout Inventory Check**. Set it to No to skip the stock check before the shopper is sent to PayLater; Magento still checks stock when the order is placed.\n- **Fixed**: A paid order is moved to Processing before its invoice is created.\n- **Fixed**: A shopper who returns while PayLater is still confirming the payment sees the order confirmation, and the order waits for the background check. Only a payment PayLater reports as failed fails the order.\n- **Changed**: If PayLater confirms a payment for an order that Magento has already cancelled, the order is left unchanged and the case is logged for the merchant to reconcile.\n- **Changed**: Remaining Spotii names were removed from the module.\n\n### Magento 4.0 (2026-06-04)\n\nOAuth2 Bearer auth on the v2 web-checkout endpoints.\n\n**Download:** [PayLaterpay-v4.0.zip](https://docs.paylaterapp.com/downloads/PayLaterpay-v4.0.zip)\n\n- **Changed**: Authentication switched from `x-api-key` to **OAuth2 Bearer**.\n- **Changed**: Endpoints moved to `/v2/web-checkout`.\n- **Changed**: Payload field names are now `snake_case` (`outletId → outlet_id`, `orderId → order_id`, `successRedirectUrl → success_redirect_url`, `failRedirectUrl → fail_redirect_url`).\n- **Changed**: `expiry_duration` is now required on create-payment-link.\n- **Changed**: Full and partial refunds collapse onto one endpoint.\n- **Changed**: Admin settings rearranged — the Test/Live API Key fields are removed.\n- **Added**: New admin fields: Client ID and Client Secret. **Payment Mode** (UAT or Live) selects the environment.\n\n### Magento 3.0 (2026-05-17)\n\nOnline refunds, webhook receiver, and admin validation.\n\n- **Added**: Online refunds from the orders admin screen — calls PayLater’s gateway directly, no manual reversal needed.\n- **Added**: Full refund routing: `GET /web-checkout/refund` with `transactionType=DOWN_PAYMENT`.\n- **Added**: Partial refund routing: `POST /web-checkout/refund/partial` with `{\"amount\":…}`.\n- **Added**: Webhook receiver for server-to-server settlement.\n- **Security**: Webhook Secret admin field (encrypted) for signature verification.\n- **Changed**: Minimum Order Amount must be ≥ 300 at save time, with field validation for immediate feedback.\n- **Changed**: API Key validation preserves placeholder behaviour.\n\n### Magento 2.0 (2026-04-30)\n\nRefactor to the standard Magento redirect-gateway pattern + reconciliation cron.\n\n- **Changed**: Order lifecycle refactored to match the standard Magento redirect-gateway pattern (Authorization & Capture).\n- **Added**: New `paylater_reconcile_pending_orders` cron runs every 10 minutes — queries `web-checkout/status` for pending orders older than 3 minutes.\n\n### Magento 1.3 (2026-01-28)\n\nBranding refresh, visibility widgets, and admin workflow.\n\n- **Changed**: Updated branding; seamless redirect and order-success sync; Authorization & Capture.\n- **Added**: New visibility widgets and widget-management toggles in Stores → Configuration.\n- **Added**: Streamlined setup wizard and enhanced debug logging for API requests.\n\n### Magento 1.2 (2025-03-28)\n\nMagento 2.4.7+ support and checkout polish.\n\n- **Added**: Magento 2.4.7+ support — verified with PHP 8.1, 8.2, and 8.3.\n- **Added**: Payment-method logo and checkout display.\n- **Added**: Environment toggle, API management, setup wizard, and debug logging."
    },
    {
     "heading": "WooCommerce",
     "anchor": "woocommerce",
     "url": "https://docs.paylaterapp.com/release-notes.html#woocommerce",
     "markdown": "## WooCommerce\n\n### WooCommerce 4.4.1 (2026-11-01)\n\nFix for stores using the block-based checkout, plus hardening.\n\n**Download:** [paylater_v4.4.1.zip](https://docs.paylaterapp.com/downloads/paylater_v4.4.1.zip)\n\n- **Fixed**: PayLater now appears in the block-based checkout, and the product widgets show, as soon as the API Configuration tab is saved. Before, they stayed off until the General and Widget Settings tabs had also been saved.\n- **Security**: Settings tab links, labels and the product widget footer are escaped before output, and error messages from the PayLater API are escaped before they are added to order notes.\n- **Changed**: Errors are logged to the WooCommerce log (WooCommerce → Status → Logs, source \"paylater\") instead of the PHP error log.\n\n### WooCommerce 4.4 (2026-10-29)\n\nSecurity and reliability update. All merchants should upgrade.\n\n**Download:** [paylater_v4.4.zip](https://docs.paylaterapp.com/downloads/paylater_v4.4.zip)\n\n- **Fixed**: Every payment link issued for an order is kept, so a payment on an earlier link (after a double click, the back button or a retry) is no longer lost. Refunds go to the link that was actually paid.\n- **Fixed**: Two payment links created for one order in the same second no longer collide.\n- **Fixed**: Webhooks for an unknown reference now return `404` so PayLater retries them, instead of being acknowledged and dropped.\n- **Fixed**: The return page checks the payment status with PayLater before failing an order, and a new payment link is refused for an order already paid.\n- **Fixed**: Payment link expiry is capped at 1,440 minutes, and WooCommerce no longer cancels an unpaid order while its PayLater link can still be paid.\n- **Fixed**: Status checks, refunds and webhooks for an order use the environment (test or live) it was paid in, so switching Test Mode no longer strands earlier orders.\n- **Fixed**: The block checkout no longer depends on jQuery, and unused scripts from the old iframe checkout were removed.\n- **Fixed**: Failure webhooks, including the `fail` status PayLater sends today, are checked with the status API: a link PayLater reports as paid is marked paid, only a confirmed failure fails the order, and otherwise the order stays payable so the shopper can retry.\n- **Fixed**: Refund order notes show the amount and currency in plain text and name the payment reference refunded.\n- **Fixed**: The widget uses the price as the shop displays it, and the instalments shown add up exactly to the price (for example QAR 333.33 shows 83.33, 83.33, 83.33 and 83.34).\n- **Fixed**: A notice from PayLater dated in the future no longer blocks later notices for the same payment.\n- **Security**: An order is only marked paid when the amount the shopper paid matches the current order total. A mismatch puts the order on hold with a note for review.\n- **Security**: Webhooks can no longer move an order backwards: replayed, duplicate or out-of-order notifications are ignored, and a failure on an older link never fails an order being paid with a newer one.\n- **Security**: Shoppers see a plain message when PayLater cannot create a payment link. The gateway response is recorded in the order notes and the WooCommerce log (source `paylater`) instead.\n- **Security**: Hardened storefront script handling.\n- **Changed**: PayLater is only offered, in both the classic and block checkout, for QAR orders between the configured minimum (at least 300 QAR) and 25,000 QAR. The product widgets follow the same limits.\n\n### WooCommerce 4.3 (2026-08-17)\n\nThe widget no longer changes the look of the rest of the store.\n\n**Download:** [paylater_v4.3.zip](https://docs.paylaterapp.com/downloads/paylater_v4.3.zip)\n\n- **Fixed**: The product page and product list widget styles now apply to the widget only. Before, they changed page spacing, fonts and headings across the store.\n- **Changed**: PayLater's stylesheet and script load only on WooCommerce pages (shop, product, cart, checkout and account), not on every page. Developers can change this with the `paylater_enqueue_frontend_assets` filter.\n\n### WooCommerce 4.2 (2026-08-09)\n\nPlugin name and version clean-up.\n\n**Download:** [paylater_v4.2.zip](https://docs.paylaterapp.com/downloads/paylater_v4.2.zip)\n\n- **Changed**: The plugin is listed as **PayLater** (was PayLater Pro) under WordPress → Plugins, and shows its real version number.\n- **Fixed**: After updating, browsers load the new checkout styles instead of keeping the old ones.\n\n### WooCommerce 4.1 (2026-07-12)\n\nRepackaged so the plugin installs through Upload Plugin.\n\n**Download:** [paylater_v4.1.zip](https://docs.paylaterapp.com/downloads/paylater_v4.1.zip)\n\n- **Fixed**: The download installs through **Plugins → Add New → Upload Plugin**: the zip now holds a single `paylater/` folder. Before, the plugin sat one folder too deep and WordPress could not install it. No code changes from 4.0.\n\n### WooCommerce 4.0 (2026-06-04)\n\nOAuth2 Bearer auth on the v2 web-checkout endpoints.\n\n- **Changed**: Authentication switched from `x-api-key` to **OAuth2 Bearer**.\n- **Changed**: Endpoints moved to `/v2/web-checkout`.\n- **Changed**: Payload field names are now `snake_case` (`outletId → outlet_id`, `orderId → order_id`, …).\n- **Changed**: `expiry_duration` is now required on create-payment-link.\n- **Changed**: Full and partial refunds collapse onto one endpoint.\n- **Changed**: Admin Test/Live API Key fields removed.\n- **Added**: New admin fields: Test Client ID, Test Client Secret, Live Client ID, Live Client Secret.\n\n### WooCommerce 3.0 (2026-05-17)\n\nOnline refunds, webhook receiver, and admin validation.\n\n- **Added**: Online refunds from the orders admin screen.\n- **Added**: Full refund routing: `GET /web-checkout/refund` with `transactionType=DOWN_PAYMENT`.\n- **Added**: Partial refund routing: `POST /web-checkout/refund/partial`.\n- **Added**: Webhook receiver for server-to-server settlement.\n- **Security**: Webhook Secret admin field (encrypted) for signature verification.\n- **Changed**: Minimum Order Amount must be ≥ 300 at save time, with field validation.\n- **Changed**: API Key validation preserves placeholder behaviour.\n\n### WooCommerce 2.1 (2026-04-30)\n\nCallback hardening and safer redirects.\n\n- **Security**: Callback `orderId` is now bound to the order’s stored reference; orders not initiated through PayLater are rejected.\n- **Changed**: All redirects switched to `wp_safe_redirect()`.\n- **Changed**: Error-field parsing aligned with the gateway contract.\n\n### WooCommerce 2.0 (2026-01-28)\n\nDynamic instalments, WooCommerce Blocks + HPOS, and branding.\n\n- **Added**: Dynamic monthly instalments, collapsible PDP widget, and PLP summary bar.\n- **Added**: WooCommerce Blocks integration; HPOS certified; unified asset management; hardened admin interface.\n- **Added**: Full-width branding, branded loader overlay, and mobile-optimised UI.\n\n### WooCommerce 1.0 (2025-06-28)\n\nFirst WooCommerce release.\n\n- **Added**: Direct API handshake, order-total logic, and classic checkout support.\n- **Added**: Centralized admin dashboard, dual-environment support, and automated refund engine.\n- **Security**: Secure API handling, security callback handler, and smart redirects."
    },
    {
     "heading": "Odoo",
     "anchor": "odoo",
     "url": "https://docs.paylaterapp.com/release-notes.html#odoo",
     "markdown": "## Odoo\n\n### Odoo 3.1 (2026-10-29)\n\nSecurity and reliability update. All merchants should upgrade.\n\n**Download:** [payment_paylater-v3.1.zip](https://docs.paylaterapp.com/downloads/payment_paylater-v3.1.zip)\n\n- **Security**: Webhooks are matched to the order only by the signed `orderId`; unsigned fields can no longer redirect a webhook to another order.\n- **Security**: Hardened payment status handling on the shopper return page.\n- **Fixed**: Paid PayLater orders are now confirmed in Odoo and recorded in Accounting. Previously the payment succeeded but the sale order stayed unconfirmed.\n- **Fixed**: A repeated payment webhook no longer marks pending refunds as complete.\n- **Fixed**: Refunds can be issued from the Odoo admin again, and are booked once PayLater accepts them.\n- **Fixed**: Rejected webhooks now return a real `4xx` status so PayLater can retry.\n- **Fixed**: Shoppers always land on the payment status page after returning from PayLater, including when they did not complete the payment.\n- **Fixed**: Failure notices from PayLater (\"fail\", \"failed\", \"expired\" and similar) are now acted on, after the module confirms the payment's status with PayLater. Before, \"fail\" and \"expired\" were ignored, and a confirmed failure was never recorded.\n- **Fixed**: A scheduled check every 15 minutes asks PayLater about payments still waiting for a result, so a lost notice no longer leaves an order open. A payment still pending 24 hours after its link was created is closed, because the link has expired.\n- **Fixed**: A shopper can no longer start a second payment for an order that is already paid, and a second payment that still arrives is flagged in the order's chatter for a refund.\n- **Fixed**: Old, repeated or future-dated notices no longer change a payment.\n- **Fixed**: The widget is shown only from QAR 300 (or the configured minimum, if higher) up to QAR 25,000, and the instalments shown add up exactly to the price.\n- **Fixed**: Notices with an unrecognised status are recorded in the chatter.\n- **Changed**: PayLater is only offered for QAR orders between 300 and 25,000 QAR.\n\n### Odoo 3.0 (2026-06-04)\n\nOAuth2 Bearer auth on the v2 web-checkout endpoints.\n\n**Download:** [payment_paylater-v3.0.zip](https://docs.paylaterapp.com/downloads/payment_paylater-v3.0.zip)\n\n- **Changed**: Authentication switched from `x-api-key` to **OAuth2 Bearer**.\n- **Changed**: Endpoints moved to `/v2/web-checkout`.\n- **Changed**: Payload field names are now `snake_case` (`outletId → outlet_id`, `orderId → order_id`, …).\n- **Changed**: `expiry_duration` is now required on create-payment-link.\n- **Changed**: Full and partial refunds collapse onto one endpoint.\n- **Changed**: Admin Test/Live API Key fields removed.\n- **Added**: New admin fields: Client ID and Client Secret. **Environment** (UAT or Live) selects the environment.\n\n### Odoo 2.0 (2026-05-17)\n\nOnline refunds, webhook receiver, and admin validation.\n\n- **Added**: Online refunds from the orders admin screen.\n- **Added**: Full refund routing: `GET /web-checkout/refund` with `transactionType=DOWN_PAYMENT`.\n- **Added**: Partial refund routing: `POST /web-checkout/refund/partial`.\n- **Added**: Webhook receiver for server-to-server settlement.\n- **Security**: Webhook Secret admin field for signature verification.\n- **Changed**: Minimum Order Amount must be ≥ 300 at save time, with field validation.\n- **Changed**: API Key validation preserves placeholder behaviour.\n\n### Odoo 1.0 (2026-04-24)\n\nFirst Odoo release.\n\n- **Added**: Direct API handshake, order-total logic, and classic checkout support.\n- **Added**: Centralized admin dashboard, dual-environment support, and automated refund engine.\n- **Security**: Secure API handling, security callback handler, and smart redirects."
    },
    {
     "heading": "OpenCart",
     "anchor": "opencart",
     "url": "https://docs.paylaterapp.com/release-notes.html#opencart",
     "markdown": "## OpenCart\n\n### OpenCart 3.1.1 (2026-11-01)\n\nFixes for new installs and for stores not priced in QAR.\n\n**Download:** [paylater.ocmod.zip](https://docs.paylaterapp.com/downloads/opencart/3.1.1/paylater.ocmod.zip)\n\n- **Fixed**: A new install no longer shows blank UAT and Production Base URL fields, which broke every payment once saved. The defaults now show until the settings are first saved, a blank Base URL is saved as the PayLater address, and stores that already saved blank addresses use the PayLater addresses again.\n- **Fixed**: PayLater is only offered, and its product widgets only shown, when the store's default currency and the shopper's chosen currency are both Qatari Riyal. Before, totals in another currency were sent to PayLater as the same number of riyals. The settings page now warns when the default currency is not QAR.\n- **Fixed**: Removed `curl_close()` calls, which are deprecated in PHP 8.5.\n\n### OpenCart 3.1 (2026-10-29)\n\nSecurity and reliability update. All merchants should upgrade.\n\n**Download:** [paylater.ocmod.zip](https://docs.paylaterapp.com/downloads/opencart/3.1.0/paylater.ocmod.zip)\n\n- **Security**: An order is only marked paid when the amount the shopper paid matches the current order total. A mismatch is left for the merchant to review, with a note in the order history.\n- **Security**: Webhooks can no longer move an order backwards: replayed, duplicate or out-of-order notifications are ignored, and a paid order is never reset to pending or failed.\n- **Security**: Hardened payment status handling on the shopper return page.\n- **Fixed**: Every payment link generated for an order is tracked, so a payment on an earlier link (after a double click or the back button) is no longer lost. Refunds go to the link that was actually paid.\n- **Fixed**: A new payment link can no longer be generated for an order that is already paid.\n- **Fixed**: The package now installs through **Extensions → Installer**: `install.json` sits at the root of the zip.\n- **Fixed**: A shopper who cancels on the PayLater payment page now returns to their cart with a \"Payment was not completed\" message. Before, the store started a new, empty session on the way back (OpenCart's default session cookie setting), so the cart looked empty and no message appeared.\n- **Fixed**: If a payment is confirmed for an order the store has already cancelled, the shopper is no longer told the order was placed. They see a message that the payment will be refunded, and the order history asks the merchant to refund it.\n- **Fixed**: PayLater is only offered at checkout for order totals from QAR 300 to 25,000, which PayLater accepts. Shoppers were offered it and then refused at the pay button.\n- **Fixed**: The product page widget uses the price including tax, the same as the product list, and the widget is hidden above QAR 25,000. The amounts shown add up exactly to the price.\n- **Fixed**: A failure notice from PayLater no longer fails the order straight away. The extension checks the payment's status with PayLater first, so an old or repeated notice cannot fail an order the shopper is still paying, or has paid.\n- **Fixed**: A notice dated in the future no longer blocks later notices for the same payment.\n- **Fixed**: Notices with a status the extension does not recognise are now recorded in the order history.\n- **Fixed**: The admin rejects a widget minimum below QAR 300 or above 25,000.\n\n### OpenCart 3.0 (2026-06-04)\n\nOAuth2 Bearer auth on the v2 web-checkout endpoints.\n\n**Download:** [paylater.ocmod.zip](https://docs.paylaterapp.com/downloads/paylater.ocmod.zip)\n\n- **Changed**: Authentication switched from `x-api-key` to **OAuth2 Bearer**.\n- **Changed**: Endpoints moved to `/v2/web-checkout`.\n- **Changed**: Payload field names are now `snake_case` (`outletId → outlet_id`, `orderId → order_id`, …).\n- **Changed**: `expiry_duration` is now required on create-payment-link.\n- **Changed**: Full and partial refunds collapse onto one endpoint.\n- **Changed**: Admin Test/Live API Key fields removed.\n- **Added**: New admin fields: Client ID and Client Secret. **Mode** (UAT or Production) selects the environment.\n\n### OpenCart 2.0 (2026-05-17)\n\nOnline refunds, webhook receiver, and admin validation.\n\n- **Added**: Online refunds from the orders admin screen.\n- **Added**: Full refund routing: `GET /web-checkout/refund` with `transactionType=DOWN_PAYMENT`.\n- **Added**: Partial refund routing: `POST /web-checkout/refund/partial`.\n- **Added**: Webhook receiver for server-to-server settlement.\n- **Security**: Webhook Secret admin field (encrypted) for signature verification.\n- **Changed**: Minimum Order Amount must be ≥ 300 at save time, with field validation.\n- **Changed**: API Key validation preserves placeholder behaviour.\n\n### OpenCart 1.0 (2026-04-24)\n\nFirst OpenCart release.\n\n- **Added**: Core payment features: direct API handshake, order-total logic, and classic checkout support.\n- **Added**: Centralized admin dashboard, dual-environment support, and automated refund engine.\n- **Security**: Secure API handling, security callback handler, and smart redirects."
    }
   ],
   "endpoints": []
  }
 ],
 "releases": [
  {
   "product": "API",
   "version": "v2.0",
   "date": "2026-06-04",
   "summary": "Major release — OAuth 2.0 auth, all endpoints under /v2/, and snake_case payloads. Breaking changes from v1.",
   "download": null,
   "changes": [
    {
     "type": "Changed",
     "text": "Authentication moved from a static <code>X-Api-Key</code> header to <strong>OAuth 2.0 Bearer</strong> tokens (<code>Authorization: Bearer &lt;token&gt;</code>). Obtain a token from <code>POST /auth/realms/api/protocol/openid-connect/token</code> with <code>grant_type=client_credentials</code>; access tokens expire (~3600s) and renew via the <code>refresh_token</code> grant (falling back to <code>client_credentials</code>)."
    },
    {
     "type": "Changed",
     "text": "Credentials are now a <strong>Client ID + Client Secret</strong> pair (previously a single permanent API key)."
    },
    {
     "type": "Changed",
     "text": "All endpoints moved under <code>/v2/</code> — e.g. <code>/web-checkout</code> → <code>/v2/web-checkout</code>, <code>/web-checkout/status</code> → <code>/v2/web-checkout/status</code>."
    },
    {
     "type": "Changed",
     "text": "Request payloads are now <code>snake_case</code>: <code>outletId</code> → <code>outlet_id</code> (now an integer), <code>orderId</code> → <code>order_id</code>, <code>successRedirectUrl</code> → <code>success_redirect_url</code>, <code>failRedirectUrl</code> → <code>fail_redirect_url</code>."
    },
    {
     "type": "Removed",
     "text": "<code>merchantId</code> is no longer sent in request bodies or query params — it is derived from the OAuth token at the gateway."
    },
    {
     "type": "Changed",
     "text": "Status check query param <code>orderId</code> → <code>order_id</code> (<code>merchantId</code> no longer needed)."
    },
    {
     "type": "Changed",
     "text": "Full refund is now <code>POST /v2/web-checkout/refund</code> with <code>{\"order_id\":…}</code> in the body (was <code>GET …/refund</code> with query params). <code>transactionType=DOWN_PAYMENT</code> is no longer required and <code>transactionReference</code> → <code>order_id</code>."
    },
    {
     "type": "Changed",
     "text": "Partial refund now uses the <strong>same</strong> <code>/v2/web-checkout/refund</code> endpoint as full refund — include an <code>amount</code> in the body to make it partial. The separate <code>/refund/partial</code> endpoint is gone."
    },
    {
     "type": "Added",
     "text": "New <code>expiry_duration</code> field (minutes) controls the payment-link lifetime."
    },
    {
     "type": "Changed",
     "text": "Webhooks are unchanged — same endpoint, payload shape, and HMAC-SHA256 signature. Note: the webhook payload still uses <code>merchantId</code> (camelCase), unlike v2 request bodies which are snake_case."
    }
   ],
   "anchor": "api-v2-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#api-v2-0"
  },
  {
   "product": "Magento",
   "version": "4.1.2",
   "date": "2026-11-02",
   "summary": "Maintenance update for newer PHP versions.",
   "download": "downloads/PayLater_PayLaterpay-4.1.2.zip",
   "changes": [
    {
     "type": "Fixed",
     "text": "No deprecation notices on PHP 8.4 and 8.5. The availability check now declares its optional cart parameter as nullable, and the module no longer calls <code>curl_close()</code>, which does nothing since PHP 8.0 and is deprecated in PHP 8.5. Behaviour is unchanged."
    }
   ],
   "anchor": "magento-4-1-2",
   "url": "https://docs.paylaterapp.com/release-notes.html#magento-4-1-2"
  },
  {
   "product": "Magento",
   "version": "4.1.1",
   "date": "2026-10-29",
   "summary": "Security and reliability update. All merchants should upgrade.",
   "download": "downloads/PayLater_PayLaterpay-4.1.1.zip",
   "changes": [
    {
     "type": "Fixed",
     "text": "If PayLater confirms a payment for an order that Magento has already cancelled, the module now requests a full refund automatically and records the outcome in the order history. The shopper is told the payment is being refunded instead of seeing a success message."
    },
    {
     "type": "Fixed",
     "text": "The product page and product list widget no longer changes the rest of the storefront (page spacing, background, headings). Its styles now apply to the widget only."
    },
    {
     "type": "Fixed",
     "text": "Orders where the shopper never continued to PayLater are cancelled automatically once the payment link expires, releasing reserved stock."
    },
    {
     "type": "Fixed",
     "text": "The background order check no longer misses PayLater orders on stores with many pending orders from other payment methods."
    },
    {
     "type": "Fixed",
     "text": "A shopper who lands on the failure page for an order that was actually paid now sees the order confirmation instead of a restored cart."
    },
    {
     "type": "Fixed",
     "text": "If a payment is confirmed after the order was cancelled, the automatic refund is retried for up to 24 hours (PayLater accepts refunds from 10 minutes after payment). Only then is the merchant asked to refund manually."
    },
    {
     "type": "Fixed",
     "text": "The product and category widgets show the amount due today rounded to the fils, and the four instalments shown add up exactly to the price (for example QAR 333.33 shows 83.33, 83.33, 83.33 and 83.34). The category list used to show unrounded amounts such as \"Pay QAR 83.3325 today\"."
    },
    {
     "type": "Fixed",
     "text": "PayLater is no longer offered, on product pages or at checkout, for totals above QAR 25,000, which PayLater does not accept. Shoppers used to pick it and then see an error."
    },
    {
     "type": "Fixed",
     "text": "A failure or cancellation notice from PayLater no longer cancels the order straight away. The module first checks the payment's status with PayLater: it cancels only if PayLater confirms the failure, keeps the order if PayLater reports it paid, and otherwise leaves it for the regular status check. This protects orders from late or repeated notices."
    },
    {
     "type": "Fixed",
     "text": "If PayLater already holds the order number (for example a second store on the same PayLater account, or a store whose order numbers restarted), checkout no longer fails with \"Order ID already exists\". The module retries once with a short suffix, such as 000000123-a7f2, and that reference is what appears in the PayLater merchant portal for that order. Other orders keep their Magento order number."
    },
    {
     "type": "Fixed",
     "text": "Error messages from PayLater are shown as a plain sentence, such as \"The maximum order value for PayLater at this merchant is QAR 25000.00\" or the reason a refund was refused. Shoppers used to see raw technical text, and admins often saw only \"Internal Server Error\" or \"Refund Error\"."
    },
    {
     "type": "Fixed",
     "text": "When a shopper cancels a payment, their cart comes back only if the cancelled order is the one they just placed. Before, the cart of their previous order, even an already paid one, could be reopened, risking a second payment."
    },
    {
     "type": "Security",
     "text": "Hardened payment status handling."
    },
    {
     "type": "Security",
     "text": "Hardened order creation at checkout."
    },
    {
     "type": "Security",
     "text": "The payment return and cancel pages show an order's confirmation only to the shopper whose session placed it. Before, anyone could open another shopper's order confirmation by changing the order number in the address."
    },
    {
     "type": "Security",
     "text": "The cancel page no longer displays text taken from the page address, which could be used to show misleading messages on the store."
    }
   ],
   "anchor": "magento-4-1-1",
   "url": "https://docs.paylaterapp.com/release-notes.html#magento-4-1-1"
  },
  {
   "product": "Magento",
   "version": "4.1",
   "date": "2026-07-23",
   "summary": "Safer payment confirmation and an option to skip the pre-checkout stock check.",
   "download": "downloads/PayLater_PayLaterpay-4.1.0.zip",
   "changes": [
    {
     "type": "Added",
     "text": "Admin setting <strong>Pre-Checkout Inventory Check</strong>. Set it to No to skip the stock check before the shopper is sent to PayLater; Magento still checks stock when the order is placed."
    },
    {
     "type": "Fixed",
     "text": "A paid order is moved to Processing before its invoice is created."
    },
    {
     "type": "Fixed",
     "text": "A shopper who returns while PayLater is still confirming the payment sees the order confirmation, and the order waits for the background check. Only a payment PayLater reports as failed fails the order."
    },
    {
     "type": "Changed",
     "text": "If PayLater confirms a payment for an order that Magento has already cancelled, the order is left unchanged and the case is logged for the merchant to reconcile."
    },
    {
     "type": "Changed",
     "text": "Remaining Spotii names were removed from the module."
    }
   ],
   "anchor": "magento-4-1",
   "url": "https://docs.paylaterapp.com/release-notes.html#magento-4-1"
  },
  {
   "product": "Magento",
   "version": "4.0",
   "date": "2026-06-04",
   "summary": "OAuth2 Bearer auth on the v2 web-checkout endpoints.",
   "download": "downloads/PayLaterpay-v4.0.zip",
   "changes": [
    {
     "type": "Changed",
     "text": "Authentication switched from <code>x-api-key</code> to <strong>OAuth2 Bearer</strong>."
    },
    {
     "type": "Changed",
     "text": "Endpoints moved to <code>/v2/web-checkout</code>."
    },
    {
     "type": "Changed",
     "text": "Payload field names are now <code>snake_case</code> (<code>outletId → outlet_id</code>, <code>orderId → order_id</code>, <code>successRedirectUrl → success_redirect_url</code>, <code>failRedirectUrl → fail_redirect_url</code>)."
    },
    {
     "type": "Changed",
     "text": "<code>expiry_duration</code> is now required on create-payment-link."
    },
    {
     "type": "Changed",
     "text": "Full and partial refunds collapse onto one endpoint."
    },
    {
     "type": "Changed",
     "text": "Admin settings rearranged — the Test/Live API Key fields are removed."
    },
    {
     "type": "Added",
     "text": "New admin fields: Client ID and Client Secret. <strong>Payment Mode</strong> (UAT or Live) selects the environment."
    }
   ],
   "anchor": "magento-4-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#magento-4-0"
  },
  {
   "product": "Magento",
   "version": "3.0",
   "date": "2026-05-17",
   "summary": "Online refunds, webhook receiver, and admin validation.",
   "download": null,
   "changes": [
    {
     "type": "Added",
     "text": "Online refunds from the orders admin screen — calls PayLater’s gateway directly, no manual reversal needed."
    },
    {
     "type": "Added",
     "text": "Full refund routing: <code>GET /web-checkout/refund</code> with <code>transactionType=DOWN_PAYMENT</code>."
    },
    {
     "type": "Added",
     "text": "Partial refund routing: <code>POST /web-checkout/refund/partial</code> with <code>{\"amount\":…}</code>."
    },
    {
     "type": "Added",
     "text": "Webhook receiver for server-to-server settlement."
    },
    {
     "type": "Security",
     "text": "Webhook Secret admin field (encrypted) for signature verification."
    },
    {
     "type": "Changed",
     "text": "Minimum Order Amount must be ≥ 300 at save time, with field validation for immediate feedback."
    },
    {
     "type": "Changed",
     "text": "API Key validation preserves placeholder behaviour."
    }
   ],
   "anchor": "magento-3-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#magento-3-0"
  },
  {
   "product": "Magento",
   "version": "2.0",
   "date": "2026-04-30",
   "summary": "Refactor to the standard Magento redirect-gateway pattern + reconciliation cron.",
   "download": null,
   "changes": [
    {
     "type": "Changed",
     "text": "Order lifecycle refactored to match the standard Magento redirect-gateway pattern (Authorization &amp; Capture)."
    },
    {
     "type": "Added",
     "text": "New <code>paylater_reconcile_pending_orders</code> cron runs every 10 minutes — queries <code>web-checkout/status</code> for pending orders older than 3 minutes."
    }
   ],
   "anchor": "magento-2-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#magento-2-0"
  },
  {
   "product": "Magento",
   "version": "1.3",
   "date": "2026-01-28",
   "summary": "Branding refresh, visibility widgets, and admin workflow.",
   "download": null,
   "changes": [
    {
     "type": "Changed",
     "text": "Updated branding; seamless redirect and order-success sync; Authorization &amp; Capture."
    },
    {
     "type": "Added",
     "text": "New visibility widgets and widget-management toggles in Stores → Configuration."
    },
    {
     "type": "Added",
     "text": "Streamlined setup wizard and enhanced debug logging for API requests."
    }
   ],
   "anchor": "magento-1-3",
   "url": "https://docs.paylaterapp.com/release-notes.html#magento-1-3"
  },
  {
   "product": "Magento",
   "version": "1.2",
   "date": "2025-03-28",
   "summary": "Magento 2.4.7+ support and checkout polish.",
   "download": null,
   "changes": [
    {
     "type": "Added",
     "text": "Magento 2.4.7+ support — verified with PHP 8.1, 8.2, and 8.3."
    },
    {
     "type": "Added",
     "text": "Payment-method logo and checkout display."
    },
    {
     "type": "Added",
     "text": "Environment toggle, API management, setup wizard, and debug logging."
    }
   ],
   "anchor": "magento-1-2",
   "url": "https://docs.paylaterapp.com/release-notes.html#magento-1-2"
  },
  {
   "product": "WooCommerce",
   "version": "4.4.1",
   "date": "2026-11-01",
   "summary": "Fix for stores using the block-based checkout, plus hardening.",
   "download": "downloads/paylater_v4.4.1.zip",
   "changes": [
    {
     "type": "Fixed",
     "text": "PayLater now appears in the block-based checkout, and the product widgets show, as soon as the API Configuration tab is saved. Before, they stayed off until the General and Widget Settings tabs had also been saved."
    },
    {
     "type": "Security",
     "text": "Settings tab links, labels and the product widget footer are escaped before output, and error messages from the PayLater API are escaped before they are added to order notes."
    },
    {
     "type": "Changed",
     "text": "Errors are logged to the WooCommerce log (WooCommerce → Status → Logs, source \"paylater\") instead of the PHP error log."
    }
   ],
   "anchor": "woocommerce-4-4-1",
   "url": "https://docs.paylaterapp.com/release-notes.html#woocommerce-4-4-1"
  },
  {
   "product": "WooCommerce",
   "version": "4.4",
   "date": "2026-10-29",
   "summary": "Security and reliability update. All merchants should upgrade.",
   "download": "downloads/paylater_v4.4.zip",
   "changes": [
    {
     "type": "Fixed",
     "text": "Every payment link issued for an order is kept, so a payment on an earlier link (after a double click, the back button or a retry) is no longer lost. Refunds go to the link that was actually paid."
    },
    {
     "type": "Fixed",
     "text": "Two payment links created for one order in the same second no longer collide."
    },
    {
     "type": "Fixed",
     "text": "Webhooks for an unknown reference now return <code>404</code> so PayLater retries them, instead of being acknowledged and dropped."
    },
    {
     "type": "Fixed",
     "text": "The return page checks the payment status with PayLater before failing an order, and a new payment link is refused for an order already paid."
    },
    {
     "type": "Fixed",
     "text": "Payment link expiry is capped at 1,440 minutes, and WooCommerce no longer cancels an unpaid order while its PayLater link can still be paid."
    },
    {
     "type": "Fixed",
     "text": "Status checks, refunds and webhooks for an order use the environment (test or live) it was paid in, so switching Test Mode no longer strands earlier orders."
    },
    {
     "type": "Fixed",
     "text": "The block checkout no longer depends on jQuery, and unused scripts from the old iframe checkout were removed."
    },
    {
     "type": "Fixed",
     "text": "Failure webhooks, including the <code>fail</code> status PayLater sends today, are checked with the status API: a link PayLater reports as paid is marked paid, only a confirmed failure fails the order, and otherwise the order stays payable so the shopper can retry."
    },
    {
     "type": "Fixed",
     "text": "Refund order notes show the amount and currency in plain text and name the payment reference refunded."
    },
    {
     "type": "Fixed",
     "text": "The widget uses the price as the shop displays it, and the instalments shown add up exactly to the price (for example QAR 333.33 shows 83.33, 83.33, 83.33 and 83.34)."
    },
    {
     "type": "Fixed",
     "text": "A notice from PayLater dated in the future no longer blocks later notices for the same payment."
    },
    {
     "type": "Security",
     "text": "An order is only marked paid when the amount the shopper paid matches the current order total. A mismatch puts the order on hold with a note for review."
    },
    {
     "type": "Security",
     "text": "Webhooks can no longer move an order backwards: replayed, duplicate or out-of-order notifications are ignored, and a failure on an older link never fails an order being paid with a newer one."
    },
    {
     "type": "Security",
     "text": "Shoppers see a plain message when PayLater cannot create a payment link. The gateway response is recorded in the order notes and the WooCommerce log (source <code>paylater</code>) instead."
    },
    {
     "type": "Security",
     "text": "Hardened storefront script handling."
    },
    {
     "type": "Changed",
     "text": "PayLater is only offered, in both the classic and block checkout, for QAR orders between the configured minimum (at least 300 QAR) and 25,000 QAR. The product widgets follow the same limits."
    }
   ],
   "anchor": "woocommerce-4-4",
   "url": "https://docs.paylaterapp.com/release-notes.html#woocommerce-4-4"
  },
  {
   "product": "WooCommerce",
   "version": "4.3",
   "date": "2026-08-17",
   "summary": "The widget no longer changes the look of the rest of the store.",
   "download": "downloads/paylater_v4.3.zip",
   "changes": [
    {
     "type": "Fixed",
     "text": "The product page and product list widget styles now apply to the widget only. Before, they changed page spacing, fonts and headings across the store."
    },
    {
     "type": "Changed",
     "text": "PayLater's stylesheet and script load only on WooCommerce pages (shop, product, cart, checkout and account), not on every page. Developers can change this with the <code>paylater_enqueue_frontend_assets</code> filter."
    }
   ],
   "anchor": "woocommerce-4-3",
   "url": "https://docs.paylaterapp.com/release-notes.html#woocommerce-4-3"
  },
  {
   "product": "WooCommerce",
   "version": "4.2",
   "date": "2026-08-09",
   "summary": "Plugin name and version clean-up.",
   "download": "downloads/paylater_v4.2.zip",
   "changes": [
    {
     "type": "Changed",
     "text": "The plugin is listed as <strong>PayLater</strong> (was PayLater Pro) under WordPress → Plugins, and shows its real version number."
    },
    {
     "type": "Fixed",
     "text": "After updating, browsers load the new checkout styles instead of keeping the old ones."
    }
   ],
   "anchor": "woocommerce-4-2",
   "url": "https://docs.paylaterapp.com/release-notes.html#woocommerce-4-2"
  },
  {
   "product": "WooCommerce",
   "version": "4.1",
   "date": "2026-07-12",
   "summary": "Repackaged so the plugin installs through Upload Plugin.",
   "download": "downloads/paylater_v4.1.zip",
   "changes": [
    {
     "type": "Fixed",
     "text": "The download installs through <strong>Plugins → Add New → Upload Plugin</strong>: the zip now holds a single <code>paylater/</code> folder. Before, the plugin sat one folder too deep and WordPress could not install it. No code changes from 4.0."
    }
   ],
   "anchor": "woocommerce-4-1",
   "url": "https://docs.paylaterapp.com/release-notes.html#woocommerce-4-1"
  },
  {
   "product": "WooCommerce",
   "version": "4.0",
   "date": "2026-06-04",
   "summary": "OAuth2 Bearer auth on the v2 web-checkout endpoints.",
   "download": null,
   "changes": [
    {
     "type": "Changed",
     "text": "Authentication switched from <code>x-api-key</code> to <strong>OAuth2 Bearer</strong>."
    },
    {
     "type": "Changed",
     "text": "Endpoints moved to <code>/v2/web-checkout</code>."
    },
    {
     "type": "Changed",
     "text": "Payload field names are now <code>snake_case</code> (<code>outletId → outlet_id</code>, <code>orderId → order_id</code>, …)."
    },
    {
     "type": "Changed",
     "text": "<code>expiry_duration</code> is now required on create-payment-link."
    },
    {
     "type": "Changed",
     "text": "Full and partial refunds collapse onto one endpoint."
    },
    {
     "type": "Changed",
     "text": "Admin Test/Live API Key fields removed."
    },
    {
     "type": "Added",
     "text": "New admin fields: Test Client ID, Test Client Secret, Live Client ID, Live Client Secret."
    }
   ],
   "anchor": "woocommerce-4-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#woocommerce-4-0"
  },
  {
   "product": "WooCommerce",
   "version": "3.0",
   "date": "2026-05-17",
   "summary": "Online refunds, webhook receiver, and admin validation.",
   "download": null,
   "changes": [
    {
     "type": "Added",
     "text": "Online refunds from the orders admin screen."
    },
    {
     "type": "Added",
     "text": "Full refund routing: <code>GET /web-checkout/refund</code> with <code>transactionType=DOWN_PAYMENT</code>."
    },
    {
     "type": "Added",
     "text": "Partial refund routing: <code>POST /web-checkout/refund/partial</code>."
    },
    {
     "type": "Added",
     "text": "Webhook receiver for server-to-server settlement."
    },
    {
     "type": "Security",
     "text": "Webhook Secret admin field (encrypted) for signature verification."
    },
    {
     "type": "Changed",
     "text": "Minimum Order Amount must be ≥ 300 at save time, with field validation."
    },
    {
     "type": "Changed",
     "text": "API Key validation preserves placeholder behaviour."
    }
   ],
   "anchor": "woocommerce-3-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#woocommerce-3-0"
  },
  {
   "product": "WooCommerce",
   "version": "2.1",
   "date": "2026-04-30",
   "summary": "Callback hardening and safer redirects.",
   "download": null,
   "changes": [
    {
     "type": "Security",
     "text": "Callback <code>orderId</code> is now bound to the order’s stored reference; orders not initiated through PayLater are rejected."
    },
    {
     "type": "Changed",
     "text": "All redirects switched to <code>wp_safe_redirect()</code>."
    },
    {
     "type": "Changed",
     "text": "Error-field parsing aligned with the gateway contract."
    }
   ],
   "anchor": "woocommerce-2-1",
   "url": "https://docs.paylaterapp.com/release-notes.html#woocommerce-2-1"
  },
  {
   "product": "WooCommerce",
   "version": "2.0",
   "date": "2026-01-28",
   "summary": "Dynamic instalments, WooCommerce Blocks + HPOS, and branding.",
   "download": null,
   "changes": [
    {
     "type": "Added",
     "text": "Dynamic monthly instalments, collapsible PDP widget, and PLP summary bar."
    },
    {
     "type": "Added",
     "text": "WooCommerce Blocks integration; HPOS certified; unified asset management; hardened admin interface."
    },
    {
     "type": "Added",
     "text": "Full-width branding, branded loader overlay, and mobile-optimised UI."
    }
   ],
   "anchor": "woocommerce-2-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#woocommerce-2-0"
  },
  {
   "product": "WooCommerce",
   "version": "1.0",
   "date": "2025-06-28",
   "summary": "First WooCommerce release.",
   "download": null,
   "changes": [
    {
     "type": "Added",
     "text": "Direct API handshake, order-total logic, and classic checkout support."
    },
    {
     "type": "Added",
     "text": "Centralized admin dashboard, dual-environment support, and automated refund engine."
    },
    {
     "type": "Security",
     "text": "Secure API handling, security callback handler, and smart redirects."
    }
   ],
   "anchor": "woocommerce-1-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#woocommerce-1-0"
  },
  {
   "product": "Odoo",
   "version": "3.1",
   "date": "2026-10-29",
   "summary": "Security and reliability update. All merchants should upgrade.",
   "download": "downloads/payment_paylater-v3.1.zip",
   "changes": [
    {
     "type": "Security",
     "text": "Webhooks are matched to the order only by the signed <code>orderId</code>; unsigned fields can no longer redirect a webhook to another order."
    },
    {
     "type": "Security",
     "text": "Hardened payment status handling on the shopper return page."
    },
    {
     "type": "Fixed",
     "text": "Paid PayLater orders are now confirmed in Odoo and recorded in Accounting. Previously the payment succeeded but the sale order stayed unconfirmed."
    },
    {
     "type": "Fixed",
     "text": "A repeated payment webhook no longer marks pending refunds as complete."
    },
    {
     "type": "Fixed",
     "text": "Refunds can be issued from the Odoo admin again, and are booked once PayLater accepts them."
    },
    {
     "type": "Fixed",
     "text": "Rejected webhooks now return a real <code>4xx</code> status so PayLater can retry."
    },
    {
     "type": "Fixed",
     "text": "Shoppers always land on the payment status page after returning from PayLater, including when they did not complete the payment."
    },
    {
     "type": "Fixed",
     "text": "Failure notices from PayLater (\"fail\", \"failed\", \"expired\" and similar) are now acted on, after the module confirms the payment's status with PayLater. Before, \"fail\" and \"expired\" were ignored, and a confirmed failure was never recorded."
    },
    {
     "type": "Fixed",
     "text": "A scheduled check every 15 minutes asks PayLater about payments still waiting for a result, so a lost notice no longer leaves an order open. A payment still pending 24 hours after its link was created is closed, because the link has expired."
    },
    {
     "type": "Fixed",
     "text": "A shopper can no longer start a second payment for an order that is already paid, and a second payment that still arrives is flagged in the order's chatter for a refund."
    },
    {
     "type": "Fixed",
     "text": "Old, repeated or future-dated notices no longer change a payment."
    },
    {
     "type": "Fixed",
     "text": "The widget is shown only from QAR 300 (or the configured minimum, if higher) up to QAR 25,000, and the instalments shown add up exactly to the price."
    },
    {
     "type": "Fixed",
     "text": "Notices with an unrecognised status are recorded in the chatter."
    },
    {
     "type": "Changed",
     "text": "PayLater is only offered for QAR orders between 300 and 25,000 QAR."
    }
   ],
   "anchor": "odoo-3-1",
   "url": "https://docs.paylaterapp.com/release-notes.html#odoo-3-1"
  },
  {
   "product": "Odoo",
   "version": "3.0",
   "date": "2026-06-04",
   "summary": "OAuth2 Bearer auth on the v2 web-checkout endpoints.",
   "download": "downloads/payment_paylater-v3.0.zip",
   "changes": [
    {
     "type": "Changed",
     "text": "Authentication switched from <code>x-api-key</code> to <strong>OAuth2 Bearer</strong>."
    },
    {
     "type": "Changed",
     "text": "Endpoints moved to <code>/v2/web-checkout</code>."
    },
    {
     "type": "Changed",
     "text": "Payload field names are now <code>snake_case</code> (<code>outletId → outlet_id</code>, <code>orderId → order_id</code>, …)."
    },
    {
     "type": "Changed",
     "text": "<code>expiry_duration</code> is now required on create-payment-link."
    },
    {
     "type": "Changed",
     "text": "Full and partial refunds collapse onto one endpoint."
    },
    {
     "type": "Changed",
     "text": "Admin Test/Live API Key fields removed."
    },
    {
     "type": "Added",
     "text": "New admin fields: Client ID and Client Secret. <strong>Environment</strong> (UAT or Live) selects the environment."
    }
   ],
   "anchor": "odoo-3-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#odoo-3-0"
  },
  {
   "product": "Odoo",
   "version": "2.0",
   "date": "2026-05-17",
   "summary": "Online refunds, webhook receiver, and admin validation.",
   "download": null,
   "changes": [
    {
     "type": "Added",
     "text": "Online refunds from the orders admin screen."
    },
    {
     "type": "Added",
     "text": "Full refund routing: <code>GET /web-checkout/refund</code> with <code>transactionType=DOWN_PAYMENT</code>."
    },
    {
     "type": "Added",
     "text": "Partial refund routing: <code>POST /web-checkout/refund/partial</code>."
    },
    {
     "type": "Added",
     "text": "Webhook receiver for server-to-server settlement."
    },
    {
     "type": "Security",
     "text": "Webhook Secret admin field for signature verification."
    },
    {
     "type": "Changed",
     "text": "Minimum Order Amount must be ≥ 300 at save time, with field validation."
    },
    {
     "type": "Changed",
     "text": "API Key validation preserves placeholder behaviour."
    }
   ],
   "anchor": "odoo-2-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#odoo-2-0"
  },
  {
   "product": "Odoo",
   "version": "1.0",
   "date": "2026-04-24",
   "summary": "First Odoo release.",
   "download": null,
   "changes": [
    {
     "type": "Added",
     "text": "Direct API handshake, order-total logic, and classic checkout support."
    },
    {
     "type": "Added",
     "text": "Centralized admin dashboard, dual-environment support, and automated refund engine."
    },
    {
     "type": "Security",
     "text": "Secure API handling, security callback handler, and smart redirects."
    }
   ],
   "anchor": "odoo-1-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#odoo-1-0"
  },
  {
   "product": "OpenCart",
   "version": "3.1.1",
   "date": "2026-11-01",
   "summary": "Fixes for new installs and for stores not priced in QAR.",
   "download": "downloads/opencart/3.1.1/paylater.ocmod.zip",
   "changes": [
    {
     "type": "Fixed",
     "text": "A new install no longer shows blank UAT and Production Base URL fields, which broke every payment once saved. The defaults now show until the settings are first saved, a blank Base URL is saved as the PayLater address, and stores that already saved blank addresses use the PayLater addresses again."
    },
    {
     "type": "Fixed",
     "text": "PayLater is only offered, and its product widgets only shown, when the store's default currency and the shopper's chosen currency are both Qatari Riyal. Before, totals in another currency were sent to PayLater as the same number of riyals. The settings page now warns when the default currency is not QAR."
    },
    {
     "type": "Fixed",
     "text": "Removed <code>curl_close()</code> calls, which are deprecated in PHP 8.5."
    }
   ],
   "anchor": "opencart-3-1-1",
   "url": "https://docs.paylaterapp.com/release-notes.html#opencart-3-1-1"
  },
  {
   "product": "OpenCart",
   "version": "3.1",
   "date": "2026-10-29",
   "summary": "Security and reliability update. All merchants should upgrade.",
   "download": "downloads/opencart/3.1.0/paylater.ocmod.zip",
   "changes": [
    {
     "type": "Security",
     "text": "An order is only marked paid when the amount the shopper paid matches the current order total. A mismatch is left for the merchant to review, with a note in the order history."
    },
    {
     "type": "Security",
     "text": "Webhooks can no longer move an order backwards: replayed, duplicate or out-of-order notifications are ignored, and a paid order is never reset to pending or failed."
    },
    {
     "type": "Security",
     "text": "Hardened payment status handling on the shopper return page."
    },
    {
     "type": "Fixed",
     "text": "Every payment link generated for an order is tracked, so a payment on an earlier link (after a double click or the back button) is no longer lost. Refunds go to the link that was actually paid."
    },
    {
     "type": "Fixed",
     "text": "A new payment link can no longer be generated for an order that is already paid."
    },
    {
     "type": "Fixed",
     "text": "The package now installs through <strong>Extensions → Installer</strong>: <code>install.json</code> sits at the root of the zip."
    },
    {
     "type": "Fixed",
     "text": "A shopper who cancels on the PayLater payment page now returns to their cart with a \"Payment was not completed\" message. Before, the store started a new, empty session on the way back (OpenCart's default session cookie setting), so the cart looked empty and no message appeared."
    },
    {
     "type": "Fixed",
     "text": "If a payment is confirmed for an order the store has already cancelled, the shopper is no longer told the order was placed. They see a message that the payment will be refunded, and the order history asks the merchant to refund it."
    },
    {
     "type": "Fixed",
     "text": "PayLater is only offered at checkout for order totals from QAR 300 to 25,000, which PayLater accepts. Shoppers were offered it and then refused at the pay button."
    },
    {
     "type": "Fixed",
     "text": "The product page widget uses the price including tax, the same as the product list, and the widget is hidden above QAR 25,000. The amounts shown add up exactly to the price."
    },
    {
     "type": "Fixed",
     "text": "A failure notice from PayLater no longer fails the order straight away. The extension checks the payment's status with PayLater first, so an old or repeated notice cannot fail an order the shopper is still paying, or has paid."
    },
    {
     "type": "Fixed",
     "text": "A notice dated in the future no longer blocks later notices for the same payment."
    },
    {
     "type": "Fixed",
     "text": "Notices with a status the extension does not recognise are now recorded in the order history."
    },
    {
     "type": "Fixed",
     "text": "The admin rejects a widget minimum below QAR 300 or above 25,000."
    }
   ],
   "anchor": "opencart-3-1",
   "url": "https://docs.paylaterapp.com/release-notes.html#opencart-3-1"
  },
  {
   "product": "OpenCart",
   "version": "3.0",
   "date": "2026-06-04",
   "summary": "OAuth2 Bearer auth on the v2 web-checkout endpoints.",
   "download": "downloads/paylater.ocmod.zip",
   "changes": [
    {
     "type": "Changed",
     "text": "Authentication switched from <code>x-api-key</code> to <strong>OAuth2 Bearer</strong>."
    },
    {
     "type": "Changed",
     "text": "Endpoints moved to <code>/v2/web-checkout</code>."
    },
    {
     "type": "Changed",
     "text": "Payload field names are now <code>snake_case</code> (<code>outletId → outlet_id</code>, <code>orderId → order_id</code>, …)."
    },
    {
     "type": "Changed",
     "text": "<code>expiry_duration</code> is now required on create-payment-link."
    },
    {
     "type": "Changed",
     "text": "Full and partial refunds collapse onto one endpoint."
    },
    {
     "type": "Changed",
     "text": "Admin Test/Live API Key fields removed."
    },
    {
     "type": "Added",
     "text": "New admin fields: Client ID and Client Secret. <strong>Mode</strong> (UAT or Production) selects the environment."
    }
   ],
   "anchor": "opencart-3-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#opencart-3-0"
  },
  {
   "product": "OpenCart",
   "version": "2.0",
   "date": "2026-05-17",
   "summary": "Online refunds, webhook receiver, and admin validation.",
   "download": null,
   "changes": [
    {
     "type": "Added",
     "text": "Online refunds from the orders admin screen."
    },
    {
     "type": "Added",
     "text": "Full refund routing: <code>GET /web-checkout/refund</code> with <code>transactionType=DOWN_PAYMENT</code>."
    },
    {
     "type": "Added",
     "text": "Partial refund routing: <code>POST /web-checkout/refund/partial</code>."
    },
    {
     "type": "Added",
     "text": "Webhook receiver for server-to-server settlement."
    },
    {
     "type": "Security",
     "text": "Webhook Secret admin field (encrypted) for signature verification."
    },
    {
     "type": "Changed",
     "text": "Minimum Order Amount must be ≥ 300 at save time, with field validation."
    },
    {
     "type": "Changed",
     "text": "API Key validation preserves placeholder behaviour."
    }
   ],
   "anchor": "opencart-2-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#opencart-2-0"
  },
  {
   "product": "OpenCart",
   "version": "1.0",
   "date": "2026-04-24",
   "summary": "First OpenCart release.",
   "download": null,
   "changes": [
    {
     "type": "Added",
     "text": "Core payment features: direct API handshake, order-total logic, and classic checkout support."
    },
    {
     "type": "Added",
     "text": "Centralized admin dashboard, dual-environment support, and automated refund engine."
    },
    {
     "type": "Security",
     "text": "Secure API handling, security callback handler, and smart redirects."
    }
   ],
   "anchor": "opencart-1-0",
   "url": "https://docs.paylaterapp.com/release-notes.html#opencart-1-0"
  }
 ]
}
