<div id="integration-guide">

<div class="ig-section" id="securepay-hosted-checkout">
<div class="ig-section-header">
<div class="ig-section-icon" style="background:#f0f7ff;color:#008BB9;"><i class="fas fa-external-link-alt"></i></div>
<h2>Migrating from SecurePay to Hosted Checkout</h2>
</div>

<p class="ig-section-desc">For businesses whose website redirects customers to a SecurePay payment page. One server-side call replaces the signed form post; your customers keep paying on a Teya-hosted page.</p>

<!-- Who -->
<div class="ig-subsection">
<h3>Who this guide is for</h3>
<ul>

<li>Your website sends customers to <code>securepay.borgun.is</code> (or <code>test.borgun.is/SecurePay</code>) to pay, and they come back to your site afterwards.</li>

<li>The integration posts an HTML form signed with a <code>checkhash</code> and confirms payment from an <code>orderhash</code>.</li>
<li>The form is <strong>not</strong> embedded in your own page (<code>displaymode=iframe</code>) — Hosted Checkout does not support iframes.</li>
</ul>

```admonition
icon: fas fa-question-circle
title: Not you?
content: Embedded form, Ticket API, stored cards or recurring payments, or you sell through a partner platform — a different guide applies.
link: /online-payments/migration/overview
```
</div>

<!-- Why -->
<div class="ig-subsection">
<h3>Why migrate, and what you gain</h3>
<p>SecurePay runs on Teya's legacy platform, which is retiring in <strong>early 2027</strong>; onboarding to it stops soon. The new platform is an upgrade, not a like-for-like swap.</p>
<div class="ig-features">
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>More completed payments</strong> — merchants on the new platform generally see higher conversion: modern 3D Secure with fewer challenges, less checkout drop-off, and card retries that keep the sale</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>The same experience for customers</strong> — click Pay, enter a card on a Teya page, come back</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Payment confirmations that can't get lost</strong> — every successful payment is confirmed server-to-server with a signed webhook, whether or not the customer makes it back</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Card retries without losing the sale</strong> — a declined card keeps the customer on the payment page</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>3D Secure and currency conversion built in</strong> — nothing to build or configure</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Refunds without a developer</strong> — in the Business Portal, or by API</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Pre-authorisations</strong> — holds with later, including partial, capture</span></div>
</div>
</div>

<!-- Options -->
<div class="ig-subsection">
<h3>Your options</h3>
<p><strong>Hosted Checkout</strong> <span class="ig-badge ig-badge-recommended">Recommended</span> — the like-for-like path for a SecurePay redirect: lowest effort, same customer experience.</p>
<ul>
<li><a href="/online-payments/migration/securepay-embedded-components">Embedded Components</a> — worth a look before you decide, even if you are happy with a redirect today. Teya's secure card fields sit inside your own checkout page, so the customer completes the whole journey on your site, in your branding, with Apple Pay on the page and no hand-off to another domain; checkouts like this typically convert better. It is somewhat more frontend work than Hosted Checkout, and the only path if you used <code>displaymode=iframe</code>. The <a href="/online-payments/migration/securepay-embedded-components">guide</a> opens with a side-by-side comparison to help you choose.</li>
<li><a href="/online-payments/migration/ticket-api-payment-links">Payment Links</a> — the successor to the Ticket API.</li>
<li><a href="/online-payments/migration/stored-cards">Stored cards &amp; recurring</a> — migrate last, after one-off payments are live.</li>
</ul>
</div>

<!-- What it involves -->
<div class="ig-subsection">
<h3>What the migration involves</h3>
<div class="ig-flow-steps">
<div class="ig-flow-step"><span class="ig-flow-step-num">1</span> Credentials</div>
<span class="ig-flow-arrow"><i class="fas fa-arrow-right"></i></span>
<div class="ig-flow-step"><span class="ig-flow-step-num">2</span> Build</div>
<span class="ig-flow-arrow"><i class="fas fa-arrow-right"></i></span>
<div class="ig-flow-step"><span class="ig-flow-step-num">3</span> Test in staging</div>
<span class="ig-flow-arrow"><i class="fas fa-arrow-right"></i></span>
<div class="ig-flow-step"><span class="ig-flow-step-num">4</span> Go live</div>
<span class="ig-flow-arrow"><i class="fas fa-arrow-right"></i></span>
<div class="ig-flow-step"><span class="ig-flow-step-num">5</span> Retire SecurePay</div>
</div>
<p>A small development project — <strong>days including testing, not weeks</strong>. A plain-HTML site with no backend sits at the upper end: the new platform needs one small server-side component (Step 1 below).</p>

```admonition
icon: fas fa-paper-plane
title: What to send your developer
content: (1) Ask your Teya account manager for a <strong>development store</strong>. (2) Forward this page. (3) Ask them to confirm whether your site has a backend today, and whether you also use the Ticket API or recurring payments — those have their own guides.
```

</div>

</div>

<div class="ig-divider"></div>

<div class="ig-section" id="developer-guide">
<div class="ig-section-header">
<div class="ig-section-icon" style="background:#eff6ff;color:#2563eb;"><i class="fas fa-code"></i></div>
<h2>Developer guide</h2>
</div>
<p class="ig-section-desc">What replaces each SecurePay form parameter, and the one request you need at each step.</p>

<!-- Before / after -->
<div class="ig-subsection">
<h3>How it works: before and after</h3>
<p><strong>Today (SecurePay)</strong></p>
<ol>
<li>The customer clicks Pay; the browser posts a <code>checkhash</code>-signed form straight to SecurePay.</li>
<li>The customer pays on the SecurePay page.</li>
<li>SecurePay redirects to <code>returnurlsuccess</code> and posts an <code>orderhash</code> to <code>returnurlsuccessserver</code>.</li>
</ol>
<p><strong>After (Hosted Checkout)</strong></p>
<ol>
<li>The customer clicks Pay; your <strong>server</strong> gets an access token and creates a <strong>checkout session</strong>, receiving a <code>session_id</code> and <code>session_url</code>.</li>
<li>You redirect the customer to the <code>session_url</code>; they pay there, 3D Secure included.</li>
<li>Teya redirects to your <code>success_url</code> (or <code>cancel_url</code>) and sends a signed <strong>webhook</strong> to your server.</li>
<li>Your server verifies the webhook (or looks the session up) and fulfils the order.</li>
</ol>
<p><strong>The biggest change:</strong> the browser no longer talks to Teya directly — your server makes one API call first, and confirmation comes from the server side, never the redirect.</p>
</div>

<!-- Before you start -->
<div class="ig-subsection">
<h3>Before you start</h3>

<h4>1. Credentials</h4>

<p>Ask your Teya contact for a development store with an ecommerce subscription. In the <a href="https://business.teya.xyz" target="_blank" rel="noopener">Staging Business Portal</a>, select the store, go to <strong>Settings → Integrations</strong> and create an ecommerce API credential: you get a <strong>Client ID</strong>, a <strong>Client Secret</strong> (shown once) and your <strong>Store ID</strong>. Repeat at <code>business.teya.com</code> for production; the two sets never mix. See <a href="/online-payments/authenticate">Authenticate</a>.</p>

<p><strong>SecurePay equivalent:</strong> <code>merchantid</code> + <code>paymentgatewayid</code> become the Store ID; the secret key becomes the OAuth client credentials.</p>

<h4>2. Environments</h4>
<p>Staging is <code>api.teya.xyz</code>, <code>id.teya.xyz</code> and <code>business.teya.xyz</code>; production is the same hosts on <code>.com</code>. Full table: <a href="/online-payments/hosted-checkout/get-started">Hosted Checkout Get Started</a>.</p>

<h4>3. Webhook</h4>

<p>Teya sends a signed webhook when a payment succeeds. Register a public HTTPS endpoint with no authentication in the Business Portal (<strong>Settings → Integrations → Add webhook</strong>); the Portal issues a <strong>public key for that webhook</strong>, per webhook and per environment.</p>

<p><strong>SecurePay equivalent:</strong> <code>returnurlsuccessserver</code> and its <code>orderhash</code> HMAC — same purpose, verified with an RSA signature instead. The webhook fires <strong>only for successful payments</strong> (<code>payment.succeeded.v1</code>); failures are covered by the session lookup in Step 5. See <a href="/online-payments/webhooks">Webhooks</a>.</p>

<h4>4. Find the SecurePay code</h4>
<p>Search your codebase for <code>borgun.is</code>, <code>securepay</code>, <code>checkhash</code>, <code>orderhash</code>, <code>merchantid</code>, <code>paymentgatewayid</code>, <code>returnurlsuccess</code>, <code>returnurlsuccessserver</code>, <code>returnurlcancel</code>, <code>returnurlerror</code>, <code>itemdescription_</code>, <code>itemcount_</code>, <code>itemunitamount_</code>, <code>itemamount_</code>, <code>skipreceiptpage</code>, <code>merchantlogo</code>, <code>pagetype</code>, <code>displaymode</code>. Each hit is a line on your to-do list. Found <code>displaymode=iframe</code>? Use <a href="/online-payments/migration/securepay-embedded-components">SecurePay → Embedded Components</a> instead.</p>

<h4>5. Ground rules</h4>
<div class="ig-features">
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Test in staging first</strong> — payment, webhook and refund end to end</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Keep SecurePay live in parallel</strong> for a day or two after you switch</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Never expose the Client Secret</strong> in browser code or repositories</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Never confirm a payment from the redirect</strong> — the webhook or session lookup is the source of truth</span></div>
</div>
</div>

<!-- Mapping -->
<div class="ig-subsection">
<h3>What replaces what</h3>

<div style="overflow-x:auto;">
<table class="ig-comparison">
<thead>
<tr><th>SecurePay</th><th>Hosted Checkout</th><th>What it means for you</th></tr>
</thead>
<tbody>
<tr><td><code>merchantid</code> + <code>paymentgatewayid</code></td><td><code>store_id</code></td><td>One UUID replaces both.</td></tr>
<tr><td>Secret key + HMAC <code>checkhash</code> per request</td><td><code>client_id</code> + <code>client_secret</code> → OAuth Bearer token</td><td>No per-request signing. Cache the token and refresh from <code>expires_in</code> (currently 15 minutes in production, 24 hours in staging).</td></tr>
<tr><td>HTML form posted from the browser</td><td><code>POST /v2/checkout/sessions</code> from your server, then redirect to <code>session_url</code></td><td><strong>The biggest change.</strong> Needs a backend (Step 1).</td></tr>
<tr><td><code>amount</code> as a decimal string (<code>54,43</code>)</td><td><code>amount.value</code>, integer in minor units (<code>5443</code>)</td><td>×100 for EUR, GBP, USD, CZK, HUF. <strong>ISK has no subunit: 100 ISK is <code>100</code>.</strong></td></tr>
<tr><td><code>currency</code></td><td><code>amount.currency</code></td><td>Same ISO 4217 codes. HRK is gone; Croatia uses EUR.</td></tr>
<tr><td><code>orderid</code> (12 alphanumeric)</td><td><code>merchant_reference</code> + optional <code>metadata</code></td><td>Free-form. Keep the legacy <code>orderid</code> in <code>metadata</code> during transition.</td></tr>
<tr><td><code>reference</code></td><td><code>metadata</code> keys</td><td>Up to 10 key-value pairs.</td></tr>
<tr><td><code>language</code> (<code>IS</code>, <code>EN</code>, <code>CZ</code>…)</td><td><code>language</code>, BCP 47 (<code>is-IS</code>, <code>cs-CZ</code>)</td><td>Optional; browser language if omitted.</td></tr>
<tr><td><code>buyername</code>, <code>buyeremail</code></td><td><code>customer.name</code>, <code>.email</code>, <code>.phone_number</code></td><td>Prefill only; they don't force collection.</td></tr>
<tr><td><code>pagetype=1</code></td><td><code>required_customer_fields: ["SHIPPING_ADDRESS", "PHONE", "EMAIL"]</code></td><td>Explicit per field; returned in the session lookup.</td></tr>
<tr><td><code>itemdescription_0</code>, <code>itemcount_0</code>, <code>itemunitamount_0</code></td><td><code>line_items[].description</code>, <code>.quantity</code>, <code>.unit_price</code></td><td>JSON array; unit price in minor units. Shown on the payment page.</td></tr>
<tr><td><code>returnurlsuccess</code></td><td><code>success_url</code></td><td>Still a browser redirect; never a payment confirmation.</td></tr>
<tr><td><code>returnurlcancel</code></td><td><code>cancel_url</code></td><td>Rendered as a Back option on the checkout.</td></tr>
<tr><td><code>returnurlerror</code></td><td><code>failure_url</code></td><td>Terminal errors only. Declined cards stay on the checkout to retry.</td></tr>
<tr><td><code>returnurlsuccessserver</code> + <code>orderhash</code> POST</td><td>Webhook <code>payment.succeeded.v1</code>, RSA-signed (<code>x-teya-signature</code>)</td><td>Verify the signature, then fulfil. Success-only.</td></tr>
<tr><td>Reading <code>orderhash</code> on the success return</td><td>Signed webhook, or <code>GET /v2/checkout/sessions/{session_id}</code></td><td><strong>Never</strong> trust the redirect to confirm success.</td></tr>
<tr><td><code>skipreceiptpage=1</code></td><td><code>post_success_payment: "REDIRECT"</code></td><td>Straight to your success page. <code>SHOW_SUCCESS_PAGE</code> shows Teya's receipt first.</td></tr>
<tr><td><code>merchantlogo</code></td><td>Business Portal setting</td><td>Logo, checkout and receipt messages, privacy and T&amp;C URLs are configured once — see <a href="/online-payments/hosted-checkout/customize">Customize</a>.</td></tr>
<tr><td><code>displaymode=iframe</code></td><td>Not supported</td><td>Use <a href="/online-payments/embedded-components/overview">Embedded Components</a>.</td></tr>
<tr><td>Verified by Visa / SecureCode setup</td><td>Automatic 3D Secure</td><td>No code, no configuration.</td></tr>
<tr><td>Dynamic Currency Conversion</td><td>Automatic</td><td>Offered when the card currency differs from the transaction currency.</td></tr>
<tr><td><em>(no equivalent)</em></td><td>Pre-authorisation + capture</td><td>New. Session <code>type: "PRE_AUTHORISATION"</code>; see <a href="#after-the-payment">After the payment</a>.</td></tr>
<tr><td>Ticket API</td><td>Payment Links (<code>POST /v2/payment-links</code>)</td><td>See <a href="/online-payments/migration/ticket-api-payment-links">Ticket API → Payment Links</a>.</td></tr>
<tr><td>Recurring / stored cards</td><td>Card on File</td><td>One-off payments first, then <a href="/online-payments/migration/stored-cards">Stored cards &amp; recurring</a>.</td></tr>
<tr><td>Refunds by manual process</td><td><code>POST /v3/refunds</code>, or the Business Portal</td><td>Full, partial and multiple refunds; no code for Portal refunds.</td></tr>
</tbody>
</table>
</div>
</div>

<!-- Steps -->
<div class="ig-subsection">
<h3>Step-by-step</h3>
<p>Requests are cURL against staging. Other languages: see <a href="/online-payments/hosted-checkout/get-started">Hosted Checkout Get Started</a>.</p>

<h4>Step 1. Add a backend (if you don't have one)</h4>
<p>SecurePay worked from a plain HTML page because the browser posted a signed form. Hosted Checkout uses OAuth credentials that must never reach the browser, so token fetching, session creation and webhook handling run server-side. If you already have a backend, skip this step. If your site is static, a single endpoint is enough — a PHP file on your host, or a serverless function.</p>

<h4>Step 2. Get an access token</h4>
<p>Exchange <code>client_id</code> and <code>client_secret</code> for a Bearer token. This replaces the per-request <code>checkhash</code>: cache the token, derive the TTL from <code>expires_in</code>, refresh before expiry or on any <code>401</code>.</p>

```bash
curl -X POST https://id.teya.xyz/oauth/v2/oauth-token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=$TEYA_CLIENT_ID" \
  -d "client_secret=$TEYA_CLIENT_SECRET" \
  -d "scope=checkout/sessions/create checkout/sessions/id/get refunds/create"
```

```json
{ "access_token": "eyJhbGciOiJSUzI1NiJ9...", "token_type": "Bearer", "expires_in": 900 }
```

<p><strong>Things to get right:</strong> one cache per environment; don't fetch a token per payment; add <code>captures/create</code> if you use pre-authorisation. Full reference: <a href="/online-payments/hosted-checkout/get-started">Hosted Checkout Get Started</a>.</p>

<h4>Step 3. Create a checkout session</h4>
<p>When the customer clicks Pay, your server creates a session. This replaces the SecurePay form: every parameter you put in the form goes in this JSON body.</p>

```bash
curl -X POST https://api.teya.xyz/v2/checkout/sessions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "store_id": "123e4567-e89b-12d3-a456-426614174000",
    "type": "SALE",
    "amount": { "value": 5443, "currency": "EUR" },
    "merchant_reference": "ORDER-2026-00042",
    "line_items": [
      { "description": "Widget Pro", "quantity": 2, "unit_price": 2500 },
      { "description": "Delivery", "quantity": 1, "unit_price": 443 }
    ],
    "customer": { "name": "Jan Novák", "email": "jan@example.com" },
    "success_url": "https://yourstore.com/order/success",
    "cancel_url": "https://yourstore.com/cart",
    "metadata": { "legacy_orderid": "ORDER1230001" }
  }'
```

```json
{
  "session_id": "14411456-79c8-4d8c-8498-f01907093b83",
  "session_url": "https://checkout.teya.com/session/14411456-79c8-4d8c-8498-f01907093b83",
  "session_status": "ACTIVE"
}
```

<p><strong>Things to get right:</strong></p>
<ul>
<li><code>amount.value</code> is an integer in minor units: €54.43 is <code>5443</code>; 100 ISK is <code>100</code>. The most common bug in SecurePay migrations.</li>
<li><code>Idempotency-Key</code> is required — unique per order attempt; reuse it only to retry the identical request.</li>
<li>Sessions expire after 15 minutes: create one at the Pay click, not on cart load.</li>
<li>Store the <code>session_id</code> against the order before redirecting.</li>
</ul>
<p>Full reference: <a href="/online-payments/hosted-checkout/get-started">Hosted Checkout Get Started</a>, <a href="/apis/online-payments/apis">API Reference</a>.</p>

<h4>Step 4. Redirect the customer</h4>
<p>Send the browser to the <code>session_url</code> exactly as returned. As with SecurePay, the customer enters their card on Teya's page, not yours.</p>

<h4>Step 5. Handle the return and look up the session</h4>
<p>The customer lands on <code>success_url</code>, <code>cancel_url</code>, or <code>failure_url</code> on a terminal error. Unlike SecurePay, the redirect carries <strong>no payment data</strong>. Fulfil from the webhook (Step 6) and use the lookup to cover the gaps: a customer on your success page before the webhook arrives, or a failed payment, which produces no webhook.</p>

```bash
curl https://api.teya.xyz/v2/checkout/sessions/$SESSION_ID \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```json
{
  "session_id": "14411456-79c8-4d8c-8498-f01907093b83",
  "session_status": "COMPLETED",
  "payment_status": "SUCCESS",
  "type": "SALE",
  "amount": { "value": 5443, "currency": "EUR" },
  "merchant_reference": "ORDER-2026-00042",
  "transaction_id": "tr_72302a4d-b2f9-4663-93f8-d9260e5370c8"
}
```

<p><strong>Things to get right:</strong> <code>session_status</code> runs <code>ACTIVE</code> → <code>PROCESSING</code> → <code>COMPLETED</code> or <code>EXPIRED</code>; trust <code>payment_status</code> (<code>NONE</code>, <code>SUCCESS</code>, <code>FAILED</code>) only once <code>COMPLETED</code>. Store the <code>transaction_id</code> for refunds and receipts. Completion logic that runs only on the success page drops orders from customers who close the browser after paying. Full reference: <a href="/apis/online-payments/apis">API Reference</a>.</p>

<h4>Step 6. Receive and verify the webhook</h4>
<p>Teya POSTs a <code>payment.succeeded.v1</code> event to your webhook URL when a payment succeeds — the equivalent of <code>returnurlsuccessserver</code>, verified by RSA signature instead of an <code>orderhash</code> HMAC, and delivered whether or not the customer returns.</p>
<ul>
<li>Verify the <code>x-teya-signature</code> header (SHA256withRSA, Base64) against the webhook's public key over the <strong>entire raw body</strong>, before parsing.</li>
<li>Match the order on <code>data.session_id</code> or <code>data.merchant_reference</code>; store <code>data.transaction_id</code>.</li>
<li>Be idempotent — the same event can arrive twice. Return <code>200</code> promptly; do slow work asynchronously.</li>
<li>Test with the <a href="/online-payments/webhook-signature-validator">Webhook Signature Validator</a> before go-live.</li>
</ul>
<p>Full reference, including the payload: <a href="/online-payments/webhooks">Webhooks</a>.</p>
</div>

<!-- After the payment -->
<div class="ig-subsection" id="after-the-payment">
<h3>After the payment</h3>
<p>Refunds, captures and receipts return their outcome in the synchronous API response; there is no webhook for them. All are visible in the Business Portal.</p>

<h4>Refunds</h4>
<p>On SecurePay, refunds were a manual process. Now: <code>POST /v3/refunds</code> with the <code>transaction_id</code> and an <code>amount</code> in minor units (scope <code>refunds/create</code>). Partial and multiple refunds are allowed up to the original amount; <code>201</code> is processed, <code>202</code> is still processing — retry with the same <code>Idempotency-Key</code>. Staff can also refund in the Business Portal with no code. See <a href="/online-payments/payment-actions">Payment actions</a>.</p>

<h4>Payment status</h4>
<p>Use the session lookup from Step 5 for order-level status and the Business Portal for reporting. Keep the <code>transaction_id</code> on every order.</p>

<h4>Digital receipts</h4>
<p>New: <code>POST /v1/transactions/{transaction_id}/receipts</code> (scope <code>transactions/id/receipts/create</code>) emails a Teya-generated receipt. See <a href="/online-payments/payment-actions">Payment actions</a>.</p>

<h4>Pre-authorisations</h4>
<p>Create the session with <code>"type": "PRE_AUTHORISATION"</code>; the flow is otherwise identical, and DCC is not offered on holds. Capture with <code>POST /v1/transactions/{transaction_id}/capture</code> (scope <code>captures/create</code>) for any amount up to the authorised total, <strong>once per authorisation</strong>; the remainder is released. To release a hold early, refund the uncaptured payment — it reverses the authorisation. Pre-authorisations are currently available for certain tourism-related business activities; if your business needs them for another use case, contact your Teya account manager. Capture is API-only today. See <a href="/online-payments/payment-actions">Payment actions</a>.</p>
</div>

<!-- Testing and go-live -->
<div class="ig-subsection">
<h3>Testing and going live</h3>
<p>Do all of this in staging with the cards on the <a href="/online-payments/testing">Testing</a> page. If your development environment isn't publicly reachable, tunnel it so staging webhooks can reach you.</p>

<h4>Definition of done</h4>
<div class="ig-features">
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Happy path</strong> — webhook received and verified; session <code>COMPLETED</code>/<code>SUCCESS</code>; order fulfilled once</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>3D Secure challenge</strong> — completes; customer returns to <code>success_url</code></span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Declined card</strong> — customer retries on the payment page; order not marked paid</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Cancel</strong> — lands on <code>cancel_url</code>; no webhook; not fulfilled</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Abandon after paying</strong> — webhook still arrives; order fulfilled without the customer returning</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Session expiry</strong> — after 15 minutes the customer sees the expiry page; your code creates a fresh session</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Amounts</strong> — your main currency charges exactly the right amount (check the Portal)</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Required fields, language, DCC</strong> — fields collected; page in the right language; a foreign-currency card triggers the DCC offer</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Refunds</strong> — full refund; two partial refunds succeed; a third exceeding the original fails</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Webhook idempotency</strong> — replaying an event doesn't double-fulfil</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Token expiry</strong> — the integration recovers from a <code>401</code> by refreshing</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Pre-auth users</strong> — authorise, capture a partial amount, remainder released</span></div>
</div>

<h4>Going live</h4>
<ol>

<li>Create production credentials and register your production webhook at <code>business.teya.com</code>; store the new public key.</li>

<li>Configure the Portal checkout settings: logo, messages, privacy and T&amp;C URLs.</li>
<li>Swap hosts via config: <code>id.teya.xyz → id.teya.com</code>, <code>api.teya.xyz → api.teya.com</code>.</li>
<li>Process one small real transaction end to end: payment → webhook → refund.</li>
<li>Switch traffic; keep SecurePay behind a flag for a day or two so rollback is a config change. Watch logs, webhook deliveries and the Portal.</li>
<li>Remove the SecurePay form code. Teya retires the old SecurePay credentials for you once the platform is decommissioned; there is nothing to request.</li>
</ol>
</div>

<!-- Common errors -->
<div class="ig-subsection">
<h3>Common errors</h3>
<p>What a SecurePay developer trips on. For the general catalogue, see <a href="/online-payments/troubleshooting">Troubleshooting</a>.</p>

<div style="overflow-x:auto;">
<table class="ig-comparison">
<thead>
<tr><th>Symptom</th><th>Likely cause</th><th>Fix</th></tr>
</thead>
<tbody>
<tr><td>Charged amount is 100× wrong</td><td>Minor-units conversion missed, or applied to ISK</td><td>€54.43 = <code>5443</code>; ISK face value as-is</td></tr>
<tr><td><code>401</code> once the access token expires (currently about 15 minutes in production)</td><td>Token cached like a static secret key</td><td>TTL from <code>expires_in</code>; refresh on <code>401</code></td></tr>
<tr><td>Customer sees "Session expired"</td><td>Session created on cart load</td><td>Create it at the Pay click</td></tr>
<tr><td>Orders marked paid that weren't, or never marked paid</td><td>Fulfilling from the redirect, as with <code>orderhash</code></td><td>Fulfil from the webhook; back it with the session lookup</td></tr>
<tr><td>Webhook never arrives</td><td>Endpoint not public, has auth, bad certificate, or returns non-<code>200</code></td><td>Public HTTPS, no auth; allowlist Teya's webhook IPs if you filter inbound traffic; always return <code>200</code></td></tr>
<tr><td>Signature verification fails</td><td>Wrong key, or body parsed before verifying</td><td>Key for <em>this</em> webhook in <em>this</em> environment; verify the raw body; use the Signature Validator</td></tr>
<tr><td>Payment succeeded but not in the Portal</td><td>Wrong environment</td><td>Staging in <code>business.teya.xyz</code>, production in <code>business.teya.com</code></td></tr>
<tr><td>"No compatible stores found" when creating credentials</td><td>Store lacks the ecommerce subscription</td><td>Ask your Teya contact to add it</td></tr>
</tbody>
</table>
</div>

```admonition
icon: fas fa-life-ring
title: Need help?
content: Your Teya account manager can provide a development store, credentials and a named migration contact. For integration questions, see <a href="/support">Support</a>.
```

</div>

<div class="ig-subsection">
<h3>Next steps</h3>
<ul>
<li><a href="/online-payments/hosted-checkout/get-started">Hosted Checkout Get Started</a> - The canonical guide, with code in other languages</li>
<li><a href="/online-payments/hosted-checkout/customize">Customize</a> - Branding, messages and session options</li>
<li><a href="/online-payments/webhooks">Webhooks</a> - Payload and signature verification</li>
<li><a href="/online-payments/testing">Testing</a> - Test cards and integration checklist</li>
<li><a href="/apis/online-payments/apis">API Reference</a> - Full API endpoint documentation</li>
</ul>
</div>

</div>
</div>
