<div id="integration-guide">

<div class="ig-section" id="securepay-embedded-components">
<div class="ig-section-header">
<div class="ig-section-icon" style="background:#fef7f0;color:#ea580c;"><i class="fas fa-window-maximize"></i></div>
<h2>Migrating from SecurePay to Embedded Components</h2>
</div>

<p class="ig-section-desc">For businesses that take payments through SecurePay today and want the new checkout to live <strong>on your own website</strong> — the payment form inside your page, no redirect. Part 1 is for the business owner; Part 2 is for your developer.</p>

<!-- Who -->
<div class="ig-subsection">
<h3>Who this guide is for</h3>
<ul>
<li><strong>Your SecurePay form is embedded today</strong> — an iframe, or <code>displaymode=iframe</code>. Hosted Checkout does not support iframe embedding; Embedded Components is the path that keeps the form on the page.</li>
<li><strong>You redirect to SecurePay today but want to upgrade</strong> — keep customers on the site through payment, in your layout, with Apple Pay built in.</li>
</ul>

```admonition
icon: fas fa-info-circle
title: Not you?
content: Redirecting to SecurePay and want the simplest migration? Use <a href="/online-payments/migration/securepay-hosted-checkout">SecurePay → Hosted Checkout</a>. For the Ticket API, stored cards and recurring payments, or a plugin, start from the migration overview.
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 rather than a swap — and Embedded Components is a chance to improve the checkout, not just replace it.</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>No redirect.</strong> Cart, details and payment on your page — fewer abandoned checkouts</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Your brand throughout.</strong> Teya-hosted fields themed to match your page</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>3D Secure, currency conversion and Apple Pay built in.</strong> The 3DS challenge renders inline</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Payment confirmations that can't get lost.</strong> A signed webhook confirms every successful payment server-to-server</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Still SAQ A.</strong> Card data never touches your page or servers — the same PCI category as a redirect</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Refunds in the Business Portal</strong>, and pre-authorisations with partial capture</span></div>
</div>
</div>

<!-- Comparison -->
<div class="ig-subsection">
<h3>Embedded Components or Hosted Checkout — which is right for you?</h3>
<p>Both use the same credentials, session API and webhooks, and both keep you SAQ A. The difference is where the customer pays and how much front-end work that takes.</p>

<div style="overflow-x:auto;">
<table class="ig-comparison">
<thead>
<tr><th></th><th>Hosted Checkout</th><th>Embedded Components</th></tr>
</thead>
<tbody>
<tr><td><strong>Customer experience</strong></td><td>Redirect to a Teya-hosted page and back</td><td>Pays on <strong>your</strong> page, no redirect</td></tr>
<tr><td><strong>Development effort</strong></td><td>Lower — one server-side call and a redirect</td><td>Moderate — the same call plus a front-end SDK integration</td></tr>
<tr><td><strong>Branding and layout</strong></td><td>Logo and messages on Teya's page</td><td>Your page, your layout; themeable fields</td></tr>
<tr><td><strong>Apple Pay</strong></td><td>—</td><td>Built into the Checkout component</td></tr>
<tr><td><strong>3DS, DCC, refunds, webhooks, PCI</strong></td><td colspan="2">Identical</td></tr>
</tbody>
</table>
</div>

<p>Choose <strong>Hosted Checkout</strong> if you want the least effort and checkout design isn't a differentiator — see <a href="/online-payments/migration/securepay-hosted-checkout">SecurePay → Hosted Checkout</a>. Choose <strong>Embedded Components</strong> if your integration was embedded, conversion justifies removing the redirect, or you want Apple Pay. The server side is identical, so you can start with Hosted Checkout and move later without redoing it.</p>
</div>

<!-- Options -->
<div class="ig-subsection">
<h3>Your options</h3>
<p><strong>Checkout component</strong> <span class="ig-badge ig-badge-recommended">Recommended</span> — a complete payment form (card fields, pay button, Apple Pay, DCC) in one block. Least code; right for most migrations.</p>
<ul>
<li><strong>Card component</strong> — one card field in your own form with your own pay button. See <a href="/online-payments/embedded-components/overview">Embedded Components</a>.</li>
<li><strong>Hosted Checkout</strong> — the redirect path. See <a href="/online-payments/migration/securepay-hosted-checkout">SecurePay → Hosted Checkout</a>.</li>
<li><strong>Payment links</strong> — payment URLs, no website needed. See <a href="/online-payments/migration/ticket-api-payment-links">Ticket API → Payment Links</a>.</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> Get 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 — days including testing, not weeks. It sits a notch above the Hosted Checkout path because of the front-end work. SecurePay keeps working in parallel until you switch it off.</p>

```admonition
icon: fas fa-paper-plane
title: What to send your developer
content: (1) A link to this page, pointing at Part 2. (2) Access to a <strong>development store</strong> — ask your Teya contact. (3) Two questions to answer back: does the integration need <strong>pre-authorisations</strong>, and does it use <strong>stored cards or recurring payments</strong>? Recurring is migrated last — see <a href="/online-payments/migration/stored-cards">stored cards &amp; recurring</a>.
```
</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">Replace the signed SecurePay form (redirect or iframe) with a checkout session created on your server and the Checkout component mounted on your page.</p>

<!-- Before / after -->
<div class="ig-subsection">
<h3>How it works: before and after</h3>
<p><strong>Today (SecurePay):</strong></p>
<ol>
<li>Your page posts a form signed with a <code>checkhash</code> to SecurePay — a redirect or an iframe (<code>displaymode=iframe</code>).</li>
<li>The customer pays on SecurePay's page or frame; 3D Secure runs there.</li>
<li>SecurePay redirects to <code>returnurlsuccess</code> and posts an <code>orderhash</code> to <code>returnurlsuccessserver</code>.</li>
<li>Your server verifies the <code>orderhash</code> and fulfils the order.</li>
</ol>
<p><strong>After (Embedded Components):</strong></p>
<ol>
<li>Your server gets an OAuth access token and creates a <strong>checkout session</strong>, receiving a <code>session_token</code>.</li>
<li>Your page initialises the Teya SDK with that token and mounts the <strong>Checkout component</strong> — Teya-hosted iframe fields; card data never enters your DOM or server.</li>
<li>The customer pays in place; 3D Secure runs inside the component.</li>
<li>Your <code>onSuccess</code> / <code>onError</code> callback fires, and Teya sends a signed <strong>webhook</strong> to your server — the source of truth for fulfilment.</li>
</ol>
<p><strong>The biggest change:</strong> the signed form post, <code>checkhash</code>, <code>orderhash</code> check and any iframe plumbing are deleted — the component replaces them, and there are no return URLs at all.</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 <strong>development store</strong>. 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 receive a <strong>Client ID</strong>, a <strong>Client Secret</strong> (shown once) and your <strong>Store ID</strong>. Repeat in the production Business Portal at go-live; never mix the two.</p>

<p><strong>SecurePay equivalent:</strong> <code>merchantid</code> + <code>paymentgatewayid</code> become the Store ID; the <code>checkhash</code> secret key becomes the Client ID and Client Secret.</p>

<h4>2. Environments</h4>
<div style="overflow-x:auto;">
<table class="ig-comparison">
<thead>
<tr><th></th><th>Staging</th><th>Production</th></tr>
</thead>
<tbody>
<tr><td><strong>API</strong></td><td><code>https://api.teya.xyz</code></td><td><code>https://api.teya.com</code></td></tr>
<tr><td><strong>OAuth</strong></td><td><code>https://id.teya.xyz/oauth/v2/oauth-token</code></td><td><code>https://id.teya.com/oauth/v2/oauth-token</code></td></tr>
<tr><td><strong>SDK</strong></td><td><code>developmentMode: true</code></td><td>flag omitted</td></tr>
</tbody>
</table>
</div>

<h4>3. Webhook</h4>

<p>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 per webhook</strong>, used in Step 5.</p>

<p><strong>SecurePay equivalent:</strong> <code>returnurlsuccessserver</code> + <code>orderhash</code>, verified with an RSA signature instead of a shared-secret hash. Only successful payments produce a webhook; failures surface in <code>onError</code> and via session lookup.</p>

<h4>4. Find the legacy 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>displaymode</code>, <code>skipreceiptpage</code>, <code>merchantlogo</code>, <code>pagetype</code>. Each hit is a line on your to-do list.</p>

<h4>5. Ground rules</h4>
<div class="ig-features">
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Staging first.</strong> Payment, webhook and refund end to end before touching production</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Keep SecurePay live in parallel</strong> after the switch, so rollback is a config change</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Never expose the Client Secret</strong> in browser code. The front end only ever sees the single-use <code>session_token</code></span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Never fulfil from a browser-side signal.</strong> <code>onSuccess</code> is for UX; the webhook 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 (today)</th><th>Embedded Components (new)</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></td><td><code>client_id</code> + <code>client_secret</code> → OAuth Bearer token</td><td>No per-request HMAC. Cache and refresh from <code>expires_in</code> (currently 15 min in production, 24 h in staging).</td></tr>
<tr><td>HTML form posted to SecurePay</td><td><code>POST /v2/checkout/sessions</code> → SDK mounts the component with the <code>session_token</code></td><td>The browser never calls Teya's API; it only hosts the component.</td></tr>
<tr><td><code>displaymode=iframe</code> + iframe result plumbing</td><td>The component <strong>is</strong> the embedded experience</td><td>Teya-hosted iframe fields; your iframe glue code goes.</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, CZK, HUF; <strong>ISK has no subunit</strong> — 100 ISK is <code>100</code>.</td></tr>
<tr><td><code>currency</code></td><td><code>amount.currency</code> — ISO 4217 alphabetic</td><td><code>ISK</code>, <code>EUR</code>, <code>CZK</code>, <code>HUF</code>, <code>GBP</code>…</td></tr>
<tr><td><code>orderid</code> (max 12 alphanumeric)</td><td><code>merchant_reference</code> + optional <code>metadata</code></td><td>Write the legacy <code>orderid</code> into <code>metadata</code> during transition if it helps reconciliation.</td></tr>
<tr><td><code>language</code></td><td>SDK <code>locale</code> option</td><td>Set on <code>initTeyaBlocks</code>.</td></tr>
<tr><td><code>buyername</code>, <code>buyeremail</code></td><td><code>customer.name</code>, <code>customer.email</code> on the session</td><td>Prefill only.</td></tr>
<tr><td><code>itemdescription_0</code> …</td><td><code>line_items[]</code> on the session</td><td>Description, quantity, unit price in minor units.</td></tr>
<tr><td><code>returnurlsuccess</code> / <code>returnurlcancel</code> / <code>returnurlerror</code></td><td><code>onSuccess</code> / <code>onError</code> callbacks</td><td>No redirects; your page decides what happens next.</td></tr>
<tr><td><code>returnurlsuccessserver</code> + <code>orderhash</code></td><td>Webhook <code>payment.succeeded.v1</code>, RSA-signed</td><td>Verify the signature over the raw body, then fulfil.</td></tr>
<tr><td><code>skipreceiptpage</code>, <code>merchantlogo</code>, <code>pagetype</code></td><td>Not applicable — there is no Teya page</td><td>Theme the component to match your page.</td></tr>
<tr><td>Verified by Visa / MC SecureCode</td><td>Automatic 3D Secure inside the component</td><td>Challenge renders inline. Nothing to build.</td></tr>
<tr><td>Dynamic Currency Conversion</td><td>Automatic in the Checkout and Card components</td><td>Nothing to implement.</td></tr>
<tr><td><em>(no equivalent)</em></td><td>Apple Pay</td><td>In the Checkout component; one-off domain verification (Step 3).</td></tr>
<tr><td><em>(no equivalent)</em></td><td>Pre-authorisation + partial capture</td><td>See After the payment.</td></tr>
<tr><td>Ticket API</td><td>Payment links</td><td><a href="/online-payments/migration/ticket-api-payment-links">Ticket API → Payment Links</a></td></tr>
<tr><td>Recurring payments / stored cards</td><td>Card on File</td><td>Migrate last — <a href="/online-payments/migration/stored-cards">stored cards &amp; recurring</a>.</td></tr>
<tr><td>Refunds via portal / manual</td><td><code>POST /v3/refunds</code> or the Business Portal</td><td>Partial and multiple refunds.</td></tr>
</tbody>
</table>
</div>
</div>

<!-- Steps -->
<div class="ig-subsection">
<h3>Step-by-step</h3>
<p>Steps 1–5 build the Checkout component end to end, in cURL against staging. Other languages, and the interactive quickstart, are on the <a href="/online-payments/embedded-components/checkout/get-started">Embedded Checkout get started</a> page.</p>

<h4>Step 1 — Get an access token (server)</h4>
<p>Exchange <code>client_id</code> and <code>client_secret</code> for a short-lived Bearer token; this replaces <code>checkhash</code> signing. Add <code>captures/create</code> to the scopes if you use pre-authorisations.</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": "eyJ...", "token_type": "Bearer", "expires_in": 900 }
```

<p><strong>Things to get right:</strong> derive the cache lifetime from <code>expires_in</code> (currently 15 min production, 24 h staging); refresh when under a minute remains or on any <code>401</code>; one cache per environment. Full reference: <a href="/online-payments/authenticate">Authenticate</a>.</p>

<h4>Step 2 — Create a checkout session (server)</h4>
<p>When the customer reaches payment, create a session describing the intent — amount, currency, reference. The response carries the <code>session_token</code> your page hands to the SDK. This replaces the signed form; no card data is involved.</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",
    "metadata": { "legacy_orderid": "ORDER1230001" }
  }'
```

```json
{
  "session_id": "14411456-79c8-4d8c-8498-f01907093b83",
  "session_token": "eyJhbGciOiJSUzI1NiJ9...",
  "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>; ISK face value as-is. The most common migration bug.</li>
<li><code>Idempotency-Key</code> is required and unique per order attempt.</li>
<li>The <code>session_token</code> is single-use and the session expires — create it when the customer reaches payment, not on cart load; create a fresh one for a retry.</li>
<li>Return only the <code>session_token</code> to the front end through a small endpoint of your own (e.g. <code>POST /api/teya-session</code>) — never the access token or client secret. Store the <code>session_id</code> against the order first.</li>
</ul>
<p>Full reference: <a href="/online-payments/embedded-components/checkout/get-started">Embedded Checkout</a> · <a href="/apis/online-payments/apis">API reference</a>.</p>

<h4>Step 3 — Mount the Checkout component (front end)</h4>
<p>Install the SDK (<code>npm install @teyaproduct/teya-blocks-js</code>; React also <code>@teyaproduct/teya-blocks-react</code>), initialise it with the session token from your backend, and mount the component into a container. This replaces the SecurePay form or iframe.</p>

```javascript
import { initTeyaBlocks } from '@teyaproduct/teya-blocks-js';

// 1. Ask YOUR backend for a session (Step 2)
const { session_token } = await (await fetch('/api/teya-session', { method: 'POST' })).json();

// 2. Initialise the SDK
const teya = await initTeyaBlocks(session_token, {
  locale: 'en',
  appearance: { theme: 'default' },
  // Routes SDK calls to Teya staging. Remove for production.
  developmentMode: true,
});

// 3. Mount the checkout - card fields, pay button, 3DS, Apple Pay, DCC included
const checkout = teya.elements.create('checkout', {
  onSuccess: (response) => {
    // Show your confirmation. Fulfil from the webhook (Step 5), not here.
    window.location.href = '/order/confirmation';
  },
  onError: (error) => {
    // The component shows the error inline; the customer can retry in place.
    console.error('Payment failed', error);
  },
});

checkout.mount('#checkout-container');
```

<p><strong>Things to get right:</strong></p>
<ul>
<li><code>developmentMode: true</code> is required in staging — without it the SDK targets production and the staging token is rejected. Gate it on environment config; never ship it to production.</li>
<li>3D Secure runs inside the component. Delete any special handling your iframe integration had for the 3DS redirect.</li>
<li>Apple Pay needs a one-off domain verification per domain and per environment (Business Portal → <strong>Integrations → Apple Pay</strong>). Cards work without it.</li>
<li>Theme with <code>appearance</code> — presets, CSS variables and fonts on the <a href="/online-payments/embedded-components/checkout/customize">Customize</a> page.</li>
</ul>
<p>Full reference: <a href="/online-payments/embedded-components/sdk-initialization">SDK Initialization</a> · <a href="/online-payments/embedded-components/checkout/get-started">Embedded Checkout</a> (HTML/JS and React).</p>

<h4>Variant: the Card component</h4>
<p>For your own form layout and pay button, mount a <code>card</code> element instead and call <code>card.submitPayment()</code> from your button; use its <code>onChange</code> event (<code>event.complete</code>) to enable the button. Session and webhook are unchanged; Apple Pay is a separate element. See <a href="/online-payments/embedded-components/card/get-started">Card element</a>.</p>

<h4>Step 4 — Handle the result</h4>
<p>Use <code>onSuccess</code> / <code>onError</code> for the user experience only; fulfil from the webhook. If the customer closed the page mid-payment or the webhook is delayed, look the session up by ID — this replaces the <code>returnurlsuccess</code> redirect and <code>orderhash</code> check.</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",
  "amount": { "value": 5443, "currency": "EUR" },
  "type": "SALE",
  "payment_status": "SUCCESS"
}
```

<p><strong>Things to get right:</strong> <code>session_status</code> runs <code>ACTIVE</code> → <code>PROCESSING</code> → <code>COMPLETED</code> / <code>EXPIRED</code>; trust <code>payment_status</code> only once the session is <code>COMPLETED</code>. Store the <code>transaction_id</code> from the completed session or the webhook — you need it for refunds and receipts. Full reference: <a href="/apis/online-payments/apis">API reference</a>.</p>

<h4>Step 5 — Receive and verify the webhook (server)</h4>
<p>Teya posts a signed <code>payment.succeeded.v1</code> event to your webhook URL when a payment succeeds — your fulfilment trigger, replacing <code>returnurlsuccessserver</code>, and one that no longer depends on the customer's browser. Verify the <code>x-teya-signature</code> header (SHA256 with RSA, Base64) against the public key for this webhook, over the entire raw body, before parsing. Match the order on <code>data.merchant_reference</code> or <code>data.session_id</code>.</p>
<p><strong>Things to get right:</strong></p>
<ul>
<li>Be idempotent — the same event can arrive more than once. Return <code>200</code> promptly; do slow work asynchronously.</li>
<li>Verify the raw bytes, not re-serialised JSON; the key is per webhook and per environment.</li>
<li>Behind a CDN or strict firewall, allowlist Teya's IPs — see <a href="/online-payments/troubleshooting">Troubleshooting</a>.</li>
<li>Check your implementation with the <a href="/online-payments/webhook-signature-validator">Webhook Signature Validator</a> before go-live.</li>
</ul>
<p>Full reference: <a href="/online-payments/webhooks">Webhooks</a> — payload fields, verification steps and sample code.</p>
</div>

<!-- After the payment -->
<div class="ig-subsection">
<h3>After the payment</h3>
<p>Identical to the Hosted Checkout path — the server side doesn't care how the payment was collected. Refunds and captures report their outcome in the synchronous response; there is no webhook for them.</p>
<p><strong>Refunds.</strong> Full, partial and multiple refunds up to the original amount — by API or in the Business Portal with no code. <code>201</code> is processed; <code>202</code> is still processing, retry with the same idempotency key. A DCC payment is refunded in the currency the customer chose.<br><code>POST /v3/refunds</code> · scope <code>refunds/create</code> · <a href="/online-payments/payment-actions">Payment actions</a></p>
<p><strong>Status lookup.</strong> Reconcile by session; store the <code>transaction_id</code> with each order.<br><code>GET /v2/checkout/sessions/{session_id}</code> · scope <code>checkout/sessions/id/get</code> · <a href="/apis/online-payments/apis">API reference</a></p>
<p><strong>Digital receipts (new, optional).</strong> Email the customer a receipt for a completed payment.<br><code>POST /v1/transactions/{transaction_id}/receipts</code> · scope <code>transactions/id/receipts/create</code> · <a href="/online-payments/payment-actions">Payment actions</a></p>
<p><strong>Pre-authorisations (new).</strong> SecurePay never offered holds; the new platform does. 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. Create the session with <code>"type": "PRE_AUTHORISATION"</code>; the component flow is otherwise identical (no DCC on pre-authorisations). Capture once per authorisation, any amount up to the total, with <code>POST /v1/transactions/{transaction_id}/capture</code> (scope <code>captures/create</code>); the remainder is released. To release a hold early, refund the uncaptured payment — that reverses it. 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>Test in staging with <code>developmentMode: true</code> and the test cards on the <a href="/online-payments/testing">Testing</a> page. If your dev 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>Frictionless card: <code>onSuccess</code> fires, webhook verified, session <code>COMPLETED</code> / <code>SUCCESS</code>, order fulfilled once</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>3DS challenge card: challenge renders inline; payment completes</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Declined card: error shown in the component, customer retries in place, order not marked paid</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Abandoned checkout: no webhook, session expires, order not fulfilled</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Expired session and reused token both fail cleanly; your code creates a fresh session</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Amounts: your main currency charges exactly the right amount in the Business Portal (minor-units trap)</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Locale and theming: the component renders in your language and matches your page on desktop and mobile</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>DCC: a foreign-currency card triggers the offer; the flow completes either way</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Full refund, then two partial refunds; a third exceeding the original fails</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Webhook idempotency: replaying the same event doesn't double-fulfil</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Token expiry: the integration recovers from a <code>401</code> by refreshing</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>If used: pre-authorise → partial capture → remainder released; Apple Pay domain verified and a test payment completes</span></div>
</div>

<h4>Go live</h4>
<ol>

<li>Create production credentials in the <a href="https://business.teya.com" target="_blank" rel="noopener">Business Portal</a> and register your production webhook; store the public key it issues.</li>

<li>Verify your domain for Apple Pay in production if you use it — staging verification doesn't carry over.</li>
<li>Swap hosts via config (<code>id.teya.xyz</code> → <code>id.teya.com</code>, <code>api.teya.xyz</code> → <code>api.teya.com</code>) and remove <code>developmentMode: true</code>.</li>
<li>Process one small real transaction end to end: payment → webhook → refund.</li>
<li>Run SecurePay in parallel behind a flag for a day or two; watch logs, webhook deliveries and the Business Portal.</li>
<li>Retire the legacy integration: delete the SecurePay form or iframe code. Teya retires the old credentials for you; there is nothing to request.</li>
</ol>
</div>

<!-- Common errors -->
<div class="ig-subsection">
<h3>Common errors</h3>
<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>Staging session token rejected by the SDK</td><td><code>developmentMode: true</code> missing</td><td>Add it in staging; remove in production</td></tr>
<tr><td>Payments land in the wrong environment</td><td><code>developmentMode</code> shipped to production, or hosts mixed</td><td>Gate on environment config; audit hosts</td></tr>
<tr><td>Charged amount is 100× wrong</td><td>Decimal → minor-units conversion missed</td><td>€54.43 is <code>5443</code>; ISK face value as-is</td></tr>
<tr><td>Component won't mount / token error</td><td>Session token already used, or session expired</td><td>One token per mount; create a fresh session</td></tr>
<tr><td><code>401</code> on API calls</td><td>Access token expired</td><td>Refresh; derive cache lifetime from <code>expires_in</code></td></tr>
<tr><td><code>403</code> / <code>409</code></td><td>Missing scope / same <code>Idempotency-Key</code>, different body</td><td>Add the scope (Step 1) / unique key per order attempt</td></tr>
<tr><td>Webhook never arrives</td><td>URL unreachable, firewall, or non-<code>200</code> response</td><td>Public HTTPS, no auth; allowlist Teya IPs; return <code>200</code></td></tr>
<tr><td>Signature verification fails</td><td>Wrong key, or a parsed body</td><td>Per-webhook, per-environment key; verify the raw body; use the Signature Validator</td></tr>
<tr><td>Apple Pay button doesn't render</td><td>Domain not verified in this environment</td><td>Verify staging and production separately</td></tr>
</tbody>
</table>
</div>

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

</div>

<div class="ig-subsection">
<h3>Next steps</h3>
<ul>
<li><a href="/online-payments/embedded-components/checkout/get-started">Embedded Checkout</a> - Interactive quickstart in HTML/JS, React and five backend languages</li>
<li><a href="/online-payments/embedded-components/sdk-initialization">SDK Initialization</a> - <code>initTeyaBlocks()</code> options and <code>developmentMode</code></li>
<li><a href="/online-payments/webhooks">Webhooks</a> - Payload fields and signature verification</li>
<li><a href="/online-payments/testing">Testing</a> - Test cards and integration checklist</li>
<li><a href="/online-payments/migration/stored-cards">Stored cards &amp; recurring</a> - The last stage of the migration</li>
</ul>
</div>

</div>
</div>
