<div id="integration-guide">

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

<p class="ig-section-desc">For partners and platforms whose systems talk directly to <strong>RPG (Borgun Payment API)</strong> on behalf of the merchants you serve. The first half is a summary you can forward; the second half is the developer guide.</p>

<!-- Who -->
<div class="ig-subsection">
<h3>Who this guide is for</h3>
<ul>
<li>Your code calls URLs containing <code>borgun.is</code>, <code>rpgapi</code> or <code>/api/payment</code>, authenticated with a <strong>Private Access Token</strong>.</li>
<li>You run 3D Secure yourself through <code>/api/mpi/v2/enrollment</code> and <code>/api/mpi/v2/validation</code>.</li>
<li>Your checkout page loads <code>BAPIjs</code> / <code>borgun-payment.js</code> to create single-use tokens.</li>
</ul>

```admonition
icon: fas fa-map-signs
title: Not you?
content: Redirects to <code>securepay.borgun.is</code>, payment URLs created in advance, and stored cards charged without the customer present each have their own guide.
link: /partners/online-payments/migration/overview
```
</div>

<!-- Why -->
<div class="ig-subsection">
<h3>Why migrate, and what you gain</h3>
<p>RPG runs on Teya's legacy platform, which is retiring in early 2027; onboarding to it stops soon. The new platform is an upgrade, not a like-for-like swap: it removes the parts of RPG that cost the most to build and maintain.</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 3D Secure to build</strong> — enrollment, challenge and validation run inside the component</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>No card data on your systems</strong> — Teya-hosted fields inside your page, a smaller PCI scope</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 for every successful payment</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Apple Pay and currency conversion</strong> without extra code</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span><strong>Refunds without a developer</strong>, and partial captures on pre-authorisations</span></div>
</div>
</div>

<!-- Options -->
<div class="ig-subsection">
<h3>Your options</h3>
<p><strong>Checkout component</strong> <span class="ig-badge ig-badge-recommended">Recommended</span> — the closest match to an RPG checkout. The customer stays on your page; one block provides card fields, pay button, 3D Secure, Apple Pay and currency conversion.</p>
<ul>
<li><strong>Card component</strong> — a single card field inside your own form with your own pay button. See <a href="/partners/online-payments/embedded-components/overview">Embedded Components</a>.</li>
<li><strong>Hosted Checkout</strong> — the customer is redirected to a Teya page and back. Least code, different customer experience. See <a href="/partners/online-payments/hosted-checkout/get-started">Hosted Checkout</a>.</li>
<li><strong>Direct API</strong> — your servers send card data to Teya, as RPG allows today. Restricted: needs evidence of PCI DSS compliance (SAQ D). Only where components have no equivalent — see the <a href="/partners/online-payments/migration/rpg">RPG capability reference</a>.</li>
</ul>
</div>

<!-- 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 RPG</div>
</div>
<p>For a one-off-payments RPG integration this is a small development project; pre-authorisations and custom logic sit at the upper end. RPG keeps working in parallel until you switch it off.</p>

```admonition
icon: fas fa-paper-plane
title: What to line up before you build
content: (1) A staging partner account and development store from your Teya partner contact. (2) The list of merchants on this integration, so credentials can be exchanged store by store. (3) Whether any of them use <strong>pre-authorisations</strong> or <strong>stored cards / recurring payments</strong> (multi-use tokens) — both change the plan.
```

</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">How an RPG integration maps onto a checkout session and the Checkout component, with 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 (RPG)</strong></p>
<ol>
<li>Your form or <code>BAPIjs</code> collects the card; card data or a single-use token reaches your server.</li>
<li>Your server runs 3D Secure: enrollment, ACS redirect, <code>TermURL</code> callback, validation, <code>MdStatus</code>.</li>
<li>Your server calls <code>POST /api/payment</code> with the <code>ThreeDSecure</code> block and reads <code>ActionCode</code>.</li>
<li>Optionally a <code>PaymentCreate</code> webhook arrives and you call back <code>GET /api/events/{EventId}</code>.</li>
</ol>
<p><strong>After (Embedded Components)</strong></p>
<ol>
<li>Your server gets a short-lived access token and creates a <strong>checkout session</strong>; the response carries a session token.</li>
<li>Your page initialises the SDK with that token and mounts the <strong>Checkout component</strong>. Card data goes into Teya-hosted fields, never into your DOM or server.</li>
<li>The customer pays in place; 3D Secure runs inside the component.</li>
<li><code>onSuccess</code> fires for the user experience; a signed <strong>webhook</strong> confirms the payment to your server and drives fulfilment.</li>
</ol>
<p><strong>The biggest change:</strong> the whole MPI flow and all card-data or single-use-token handling are deleted — the component replaces both.</p>
</div>

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

<h4>1. Credentials</h4>

<p>Each store gets its own <code>client_id</code>, <code>client_secret</code>, <code>store_id</code> and webhook public key. For merchants already on RPG, <a href="/partners/online-payments/migration/rpg-credentials">exchange each merchant's RPG private key</a> for new credentials — one call per store, production only. New merchants come through the <a href="/partners/online-payments/register-merchants">registration flow</a>. For staging, ask your Teya partner contact for a development store and create credentials in the <a href="https://business.teya.xyz" target="_blank" rel="noopener">Staging Business Portal</a>.</p>

<h4>2. Environments</h4>
<p>Staging is <code>api.teya.xyz</code> / <code>id.teya.xyz</code> / <code>business.teya.xyz</code> with <code>developmentMode: true</code> in the SDK; production is the same hosts on <code>.com</code> without the flag. Full table on <a href="/partners/online-payments/embedded-components/checkout/get-started">Embedded Checkout</a>.</p>

<h4>3. Webhook</h4>
<p>Teya sends a signed <code>payment.succeeded.v1</code> event to a public HTTPS endpoint with no authentication on it.</p>

<p>Pass the URL as <code>webhook_url</code> in the credential exchange and store the returned <code>payment_webhook_public_key</code> against that store.</p>

<p>Unlike RPG's <code>PaymentCreate</code> / <code>Refund</code> / <code>Capture</code> events, only successful customer payments produce a webhook today; refunds and captures you trigger return their outcome in the API response. See <a href="/partners/online-payments/webhooks">Webhooks</a>.</p>

<h4>4. Find the legacy code</h4>
<p>Search for <code>borgun.is</code>, <code>rpgapi</code>, <code>/api/payment</code>, <code>/api/token/single</code>, <code>/api/mpi</code>, <code>TransactionType</code>, <code>ActionCode</code>, <code>MdStatus</code>, <code>TermURL</code>, <code>MpiToken</code>, <code>BAPIjs</code>, <code>borgun-payment</code>, <code>PrivateAccessToken</code>, <code>BorgunRPGNet</code>, <code>RPGClient</code>. Each hit is a line on the to-do list. The .NET SDK is a thin wrapper over the same endpoints, so the mapping below applies to its methods one-to-one; the new platform has no server-side SDK — plain HTTPS on the server, the JS SDK in the browser.</p>

<h4>5. Ground rules</h4>
<div class="ig-features">
<div class="ig-feature"><i class="fas fa-check"></i> <span>Complete a payment, webhook and refund end to end in staging before touching production code</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Keep RPG live behind a flag for a day or two after switching, so rollback is a config change</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Never expose the Client Secret in browser code or repositories</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Fulfil orders from the webhook, never from a browser callback alone</span></div>
</div>
</div>

<!-- Mapping -->
<div class="ig-subsection">
<h3>What replaces what</h3>
<p>The <a href="/partners/online-payments/migration/rpg">RPG capability reference</a> maps each capability to its replacement. This table goes one level down: the calls and fields an RPG developer has to change.</p>

<div style="overflow-x:auto;">
<table class="ig-comparison">
<thead>
<tr><th>RPG today</th><th>New platform</th><th>What it means for you</th></tr>
</thead>
<tbody>
<tr><td>Private Access Token (permanent, Basic auth)</td><td><code>client_id</code> + <code>client_secret</code> → short-lived Bearer token</td><td>Cache and refresh from <code>expires_in</code> (currently 15 min in production, 24 h in staging).</td></tr>
<tr><td>Public Access Token (browser tokenisation)</td><td>Not needed — the session token comes from your backend per payment</td><td>Delete with the <code>BAPIjs</code> code.</td></tr>
<tr><td><code>POST /api/payment</code>, <code>TransactionType: "Sale"</code></td><td><code>POST /v2/checkout/sessions</code>, <code>type: "SALE"</code> + the component</td><td>The server sends intent only — amount, currency, reference. No card data, no 3DS block.</td></tr>
<tr><td><code>POST /api/payment</code>, <code>TransactionType: "PreAuthorization"</code></td><td><code>type: "PRE_AUTHORISATION"</code> on the same endpoint</td><td>See <em>Pre-authorisations</em> below.</td></tr>
<tr><td><code>PUT /api/payment/{id}/capture</code> (full only)</td><td><code>POST /v1/transactions/{id}/capture</code>, <code>{"amount": n}</code></td><td>Partial capture gained. Once per authorisation; the remainder is released.</td></tr>
<tr><td><code>PUT /api/payment/{id}/cancel</code></td><td><code>POST /v3/refunds</code> on the uncaptured payment</td><td>A refund before capture reverses the authorisation. No separate cancel.</td></tr>
<tr><td><code>PUT /api/payment/{id}/refund</code> + <code>PartialAmount</code></td><td><code>POST /v3/refunds</code> with <code>transaction_id</code>, <code>amount</code> — or the Business Portal</td><td>Same semantics: partial, multiple, total ≤ original.</td></tr>
<tr><td><code>GET /api/payment/{id}</code></td><td><code>GET /v2/checkout/sessions/{session_id}</code></td><td><code>session_status</code> + <code>payment_status</code> instead of <code>TransactionStatus</code>.</td></tr>
<tr><td><code>POST /api/token/single</code> + <code>BAPIjs.getToken</code></td><td>Gone — the component's hosted fields do this</td><td>No token creation, lifetime management or disable calls.</td></tr>
<tr><td><code>/api/mpi/v2/enrollment</code>, <code>/validation</code>, <code>TermURL</code>, <code>MdStatus</code>, <code>MpiToken</code></td><td>Gone — 3D Secure runs inside the component, challenge inline</td><td>Delete the whole flow.</td></tr>
<tr><td><code>POST /api/payment</code> with raw <code>PAN</code></td><td>Direct API — restricted, PCI SAQ D evidence required</td><td>Use the component unless you have no alternative.</td></tr>
<tr><td><code>POST /api/token/multi</code>, <code>VerifyCard</code></td><td>Card on File</td><td>Separate track — <a href="/partners/online-payments/migration/stored-cards">Stored cards &amp; recurring</a>.</td></tr>
<tr><td><code>Amount</code> — 2 implied decimals, including ISK (100 ISK = <code>10000</code>)</td><td><code>amount.value</code> — minor units per ISO 4217; ISK has no subunit (100 ISK = <code>100</code>, €54.43 = <code>5443</code>)</td><td><strong>Divide RPG ISK amounts by 100.</strong> The most likely silent bug in an ISK migration; other currencies are unchanged.</td></tr>
<tr><td><code>Currency</code> — ISO numeric (<code>"352"</code>)</td><td><code>amount.currency</code> — ISO alphabetic (<code>"ISK"</code>)</td><td>Lookup change.</td></tr>
<tr><td><code>OrderId</code> — exactly 12 chars, A–Z/0–9</td><td><code>merchant_reference</code> (free-form) + 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>TransactionDate</code></td><td>Not sent — Teya timestamps server-side</td><td>Delete.</td></tr>
<tr><td><code>ActionCode</code> (<code>"000"</code> = approved) + <code>TransactionStatus</code></td><td><code>payment_status</code> (<code>SUCCESS</code> / <code>FAILED</code>) on the session</td><td>No action-code tables; declines show in the component and the customer retries in place.</td></tr>
<tr><td><code>DynamicDescriptors</code></td><td>Not exposed on checkout sessions</td><td>Tell your Teya contact before migrating if you rely on them.</td></tr>
<tr><td><code>PaymentCreate</code> webhook + <code>GET /api/events/{EventId}</code></td><td><code>payment.succeeded.v1</code>, RSA-signed (<code>x-teya-signature</code>)</td><td>Verify by signature instead of calling back.</td></tr>
<tr><td>B-Online</td><td>Business Portal</td><td>Credentials, webhooks, refunds, transaction visibility.</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. Samples are cURL and minimal JavaScript; other languages are on the <a href="/partners/online-payments/embedded-components/checkout/get-started">Embedded Checkout Get Started</a> page.</p>

<h4>Step 1 — Fetch an access token (server)</h4>
<p>Exchange <code>client_id</code> + <code>client_secret</code> for a Bearer token. This replaces the permanent Private Access Token: cache the token per environment and refresh it before <code>expires_in</code> runs out or on a <code>401</code>. Scopes for this guide: <code>checkout/sessions/create</code>, <code>checkout/sessions/id/get</code>, <code>refunds/create</code>, plus <code>captures/create</code> for 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": "eyJhbGciOiJSUzI1NiJ9...", "token_type": "Bearer", "expires_in": 900 }
```

<p>Full reference: <a href="/online-payments/authenticate">Authenticate</a>.</p>

<h4>Step 2 — Create a checkout session (server)</h4>
<p>When the customer clicks Pay, your server describes the payment and gets back a session token for the page. This replaces <code>POST /api/payment</code>, but the body carries no card data and no 3DS block — only the intent.</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": 2490, "currency": "ISK" },
    "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 per ISO 4217: 100 ISK = <code>100</code>, not RPG's <code>10000</code>.</li>
<li><code>Idempotency-Key</code> is unique per order attempt; retries with the same key can't create duplicate sessions.</li>
<li>Create the session when the customer clicks Pay, not when the cart loads — sessions expire.</li>
<li>Return only <code>session_token</code> to the browser through a small endpoint of your own — never the access token or the Client Secret.</li>
</ul>
<p>Full reference: <a href="/apis/online-payments/apis">API Reference</a>.</p>

<h4>Step 3 — Mount the Checkout component (frontend)</h4>
<p>Install <code>@teyaproduct/teya-blocks-js</code>, initialise the SDK with the session token from your backend, and mount the component. This replaces your card form and <code>BAPIjs.getToken</code>: the fields are Teya-hosted, so card data never enters your DOM.</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: 'flat' },
  // 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) => { window.location.href = '/order/confirmation'; },
  onError: (error) => console.error('Payment failed', error),
});

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

<p><strong>Things to get right</strong></p>
<ul>
<li>A staging session token only works with <code>developmentMode: true</code>; a production token only works without it. Gate the flag on your own environment.</li>
<li>Apple Pay needs a one-off domain verification per domain in the Business Portal under <strong>Integrations &gt; Apple Pay</strong>; card payments work without it.</li>
<li>Where did 3D Secure go? Inside the component. Enrollment, the inline challenge, validation and <code>MdStatus</code> are no longer your code.</li>
</ul>
<p>Full reference: <a href="/partners/online-payments/embedded-components/sdk-initialization">SDK Initialization</a> and <a href="/partners/online-payments/embedded-components/checkout/get-started">Embedded Checkout</a>.</p>

<h4>Variant: the Card component</h4>
<p>For your own form layout and pay button, create a <code>card</code> element instead and call <code>card.submitPayment()</code> from your button; the session and everything after are the same. No built-in Apple Pay button. See <a href="/partners/online-payments/embedded-components/card/get-started">Card Element</a>.</p>

<h4>Step 4 — Handle the result</h4>
<p><code>onSuccess</code> / <code>onError</code> are for the user experience: navigate to a confirmation page, show an error state. The webhook (Step 5) is the source of truth for fulfilment. As a fallback (customer closed the browser, webhook delayed), look the session up by ID. This replaces <code>GET /api/payment/{id}</code> and the "only <code>000</code> means approved" rule.</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": 2490, "currency": "ISK" },
  "type": "SALE",
  "payment_status": "SUCCESS"
}
```

<p><strong>Things to get right</strong></p>
<ul>
<li><code>session_status</code> runs <code>ACTIVE</code> → <code>PROCESSING</code> → <code>COMPLETED</code> or <code>EXPIRED</code>; trust <code>payment_status</code> only once <code>COMPLETED</code>.</li>
<li>Store the <code>transaction_id</code> from the webhook against the order — it is the key for refunds and receipts.</li>
</ul>
<p>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 registered URL when a payment succeeds — the equivalent of RPG's <code>PaymentCreate</code>, verified by signature instead of a call back to <code>GET /api/events/{EventId}</code>. Verify <code>x-teya-signature</code> (SHA256withRSA, Base64) over the <strong>raw request body</strong> with the public key issued for this webhook, before parsing anything.</p>

```json
{
  "event": "payment.succeeded.v1",
  "timestamp": "2025-12-02T12:04:22Z",
  "data": {
    "transaction_id": "tr_019adef3-0d07-7543-9b74-efa2d5b6fa2a",
    "session_id": "019adef2-e5b3-7e00-9fe1-61a5a01f3eef",
    "merchant_reference": "ORDER-2026-00042",
    "amount": { "value": 2490, "currency": "ISK" },
    "status": "SUCCESS"
  }
}
```

<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 and do slow work asynchronously.</li>
<li>Register the route before any JSON body parser so you verify the raw bytes; use the key for <em>this</em> webhook in <em>this</em> environment.</li>
<li>Check your handler with the <a href="/partners/online-payments/webhook-signature-validator">Webhook Signature Validator</a> before go-live.</li>
</ul>
<p>Full reference: <a href="/partners/online-payments/webhooks">Webhooks</a>.</p>
</div>

<!-- After the payment -->
<div class="ig-subsection">
<h3>After the payment</h3>
<p>Each operation returns its outcome in the synchronous API response — there is no webhook for refunds or captures today, so record the response.</p>

<h4>Refunds</h4>
<p>Replaces <code>PUT /api/payment/{id}/refund</code>, same rules: partial and multiple refunds, total ≤ original. <code>201</code> is processed; <code>202</code> is still processing — retry with the same <code>Idempotency-Key</code>. Staff can also refund from the Business Portal. Payments taken on RPG before cutover stay refundable through B-Online for a transition period.</p>

```bash
curl -X POST https://api.teya.xyz/v3/refunds \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "transaction_id": "tr_019adef3-0d07-7543-9b74-efa2d5b6fa2a", "amount": 1000 }'
```

<p>See <a href="/partners/online-payments/payment-actions">Payment Actions</a>.</p>

<h4>Payment status and receipts</h4>
<p>Status: <code>GET /v2/checkout/sessions/{session_id}</code> (Step 4) replaces <code>GET /api/payment/{id}</code>; the Business Portal covers day-to-day visibility. Receipts are new — <code>POST /v1/transactions/{id}/receipts</code> emails a Teya-generated receipt (scope <code>transactions/id/receipts/create</code>). See <a href="/partners/online-payments/payment-actions">Payment Actions</a>.</p>

<h4>Pre-authorisations</h4>
<p>Skip this if you only send <code>TransactionType: "Sale"</code>. Create the session with <code>type: "PRE_AUTHORISATION"</code>; the component flow is unchanged and the funds are held (no DCC on pre-auth). Capture with <code>POST /v1/transactions/{id}/capture</code> and <code>{"amount": n}</code> — any amount up to the authorised total, but <strong>exactly once</strong>: the remainder is released, so capture the final amount. There is no cancel call: refund the uncaptured payment to reverse the authorisation; untouched holds expire on their own. 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; confirm before migrating pre-auth flows. See <a href="/partners/online-payments/payment-actions">Payment Actions</a>.</p>
</div>

<!-- Testing -->
<div class="ig-subsection">
<h3>Testing and going live</h3>
<p>Test in staging with <code>developmentMode: true</code> and the cards on the <a href="/partners/online-payments/testing">Testing</a> page — frictionless, challenge and declined scenarios are all there. If your development machine isn't publicly reachable, tunnel it so staging webhooks can reach you.</p>

<p><strong>Definition of done</strong></p>
<div class="ig-features">
<div class="ig-feature"><i class="fas fa-check"></i> <span>Frictionless card: <code>onSuccess</code> fires, webhook received and verified, session <code>COMPLETED</code> / <code>SUCCESS</code></span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Challenge card: the challenge renders inline and the payment completes</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Declined card: error shown in the component, customer can retry, order not marked paid</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Abandoned or expired session: no webhook, nothing fulfilled, your code creates a fresh session</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Amounts: an ISK (and, if relevant, EUR) payment charges exactly the right amount in the Business Portal</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>Replaying the same webhook doesn't double-fulfil</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Integration recovers from a <code>401</code> by refreshing the token</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Pre-auth users: authorise, capture a partial amount, remainder released</span></div>
<div class="ig-feature"><i class="fas fa-check"></i> <span>Apple Pay users: domain verified, button renders, test payment completes</span></div>
</div>

<p><strong>Go live</strong></p>
<ol>

<li>Exchange each merchant's RPG key for production credentials (with <code>webhook_url</code>) and store the credentials and public key per store.</li>

<li>Swap hosts by config (<code>.xyz</code> → <code>.com</code>) and remove <code>developmentMode</code>.</li>
<li>Process one small real payment end to end: payment → webhook → refund.</li>
<li>Run RPG in parallel for a day or two behind a flag; watch logs, webhook deliveries and the Business Portal.</li>
<li>Retire the legacy integration: delete the payment, MPI, tokenisation and webhook code paths. Teya retires the RPG access tokens 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>Charged amount is 100× too big (ISK)</td><td>RPG's two-implied-decimals convention carried over</td><td>Divide legacy ISK amounts by 100</td></tr>
<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><code>401</code> on API calls</td><td>Access token expired — currently 15 min in production, not permanent like the RPG token</td><td>Refresh from <code>expires_in</code>; retry once on <code>401</code></td></tr>
<tr><td><code>403</code></td><td>Missing scope</td><td>Add the scope to the token request (Step 1)</td></tr>
<tr><td><code>409</code></td><td>Same <code>Idempotency-Key</code>, different body</td><td>Unique key per order attempt; reuse only for retries of the identical request</td></tr>
<tr><td><code>422</code> creating a session</td><td>Numeric currency code or non-integer amount</td><td>Alphabetic <code>currency</code>; integer <code>amount.value</code> in minor units</td></tr>
<tr><td>Webhook never arrives</td><td>URL not public, no valid certificate, auth on the endpoint, or non-<code>200</code> responses</td><td>Public HTTPS, no auth, return <code>200</code> after processing</td></tr>
<tr><td>Signature verification fails</td><td>Parsed body re-serialised, or the wrong key</td><td>Verify the raw body; use the key issued for this webhook in this environment</td></tr>
<tr><td>Payment succeeded but not in the Business Portal</td><td>Wrong environment</td><td>Staging payments are in <code>business.teya.xyz</code>, production in <code>business.teya.com</code></td></tr>
<tr><td>Waiting for a refund or capture webhook</td><td>Only successful customer payments send one</td><td>Read the outcome from the API response</td></tr>
</tbody>
</table>
</div>

```admonition
icon: fas fa-life-ring
title: Need help?
content: Your Teya partner contact can provision staging accounts and development stores, confirm pre-authorisation availability for your merchants and plan the stored-card import. For integration questions, see <a href="/support">Support</a>.
```

</div>

<div class="ig-subsection">
<h3>Next steps</h3>
<ul>
<li><a href="/partners/online-payments/migration/stored-cards">Stored cards &amp; recurring</a> - Migrate multi-use tokens once one-off payments are live</li>
<li><a href="/partners/online-payments/embedded-components/checkout/get-started">Embedded Checkout</a> - Interactive quickstart in your language and framework</li>
<li><a href="/partners/online-payments/webhooks">Webhooks</a> - Payload fields and signature verification</li>
<li><a href="/partners/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>
