JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 341
  • Score
    100M100P100Q92113F
  • License MIT

Embed-an-iframe (or redirect-to-PSP) checkout component for ödematik. Cards never touch ödematik or merchant servers — they go directly from the buyer's browser to the configured PSP (iyzico, PayTR, Param).

Package Exports

  • @odematik/billing
  • @odematik/billing/package.json

Readme

@odematik/billing

Drop-in React component that renders a secure checkout UI on top of your configured payment provider (iyzico, PayTR, Param, …).

The merchant configures their existing PSP credentials in the ödematik dashboard. ödematik then orchestrates the flow — but card data never touches ödematik servers and never touches your merchant's servers. It flows directly from the buyer's browser to the PSP's hosted iframe.

  • 🔒 Zero card data exposure for ödematik and the merchant
  • 🧱 Zero runtime dependencies (only React peer)
  • ⚛️ Works with React 18 and 19, ESM + CJS, full .d.ts
  • 🪟 Strict postMessage origin validation
  • 🌍 SSR-safe (no DOM access at module load)

Architecture

 Merchant backend                                                  ödematik API
       │  POST /v1/checkout_sessions  (Authorization: Bearer sk_…)
       │  ───────────────────────────────────────────────────────▶
       │  ◀──────────── { client_secret: "cs_…" } ────────────────
       │
       │  passes client_secret to the frontend
       ▼
 Browser  ──── <OdematikCheckout clientSecret="cs_…"/> ──┐
                                                         │
       ödematik API: GET /v1/checkout_sessions/lookup  ◀─┘
                returns provider iframe URL
       │
       │  iframe loads from PSP (iyzico / PayTR / Param)
       ▼
 PSP's hosted form  ── card data ──▶  PSP servers
       │
       │  postMessage("odematik-checkout", { type: "success" }) to parent
       ▼
 SDK fires onSuccess(payment)

The merchant uses the SDK with a single-use clientSecret. No publishable keys, no card data. The SDK fetches the session, embeds the PSP's iframe, and forwards the result via callbacks.

Install

npm install @odematik/billing react react-dom
pnpm add @odematik/billing react react-dom

Requires react@>=18. Zero runtime dependencies beyond React.

Quick start

1. Server: create a checkout session

// /api/checkout/route.ts — runs on your server, using your secret key
const res = await fetch(`${process.env.ODEMATIK_API_BASE}/v1/checkout_sessions`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.ODEMATIK_SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: 125000,                 // minor units (1.250,00 ₺)
    currency: 'TRY',
    merchant_reference: 'ORDER-42', // your internal order id
    return_url: 'https://shop.example.com/checkout/done',
  }),
});
const { client_secret } = await res.json();
return Response.json({ clientSecret: client_secret });

2. Client: render <OdematikCheckout>

'use client';
import { OdematikCheckout, type PaymentResult } from '@odematik/billing';

export function Checkout({ clientSecret }: { clientSecret: string }) {
  return (
    <OdematikCheckout
      clientSecret={clientSecret}
      apiBase={process.env.NEXT_PUBLIC_ODEMATIK_API_BASE!}  // required, no default
      onSuccess={(p: PaymentResult) => {
        window.location.href = `/order/${p.paymentId}`;
      }}
      onError={(err) => console.error(err.code, err.message)}
      onCancel={() => console.log('user closed')}
      onExpired={() => location.reload()}
    />
  );
}

apiBase is required as of 0.3.0 — there is no default. Always pass the URL ödematik gave you (e.g. https://api.your-odematik-host.example). This prevents the SDK from silently sending lookups to a hostname you don't control.

That's it. The SDK never sees the card; your backend never sees the card; ödematik never sees the card.

Props

Prop Type Notes
clientSecret string Required. The cs_… returned by POST /v1/checkout_sessions. The SDK throws on mount if a sk_… secret is passed by mistake.
apiBase string Required. Base URL of your ödematik API. Must be HTTPS in production (localhost allowed for dev).
showCardPreview boolean Show the animated 3D card above the form (iframe mode only). Default true.
onSuccess (payment) => void Fires once the PSP confirms a successful payment. Iframe mode only — in redirect mode the user lands on your successUrl instead.
onError (err: OdematikBillingError) => void Network / lookup / PSP errors.
onCancel () => void User dismissed the PSP form.
onExpired () => void Session 404'd or expiresAt passed.
className / style Outer wrapper.

Embed modes

The session response from /v1/checkout_sessions/lookup includes an embed object that tells the SDK how to render:

embed: {
  type: 'iframe' | 'redirect',
  url: string,          // PSP-hosted page or iframe src
  height?: number,      // initial iframe height (overridden later via postMessage)
  allowedOrigins: string[],  // origins the SDK will accept postMessage from
}
  • type: 'iframe' — the SDK embeds the URL in a sandboxed iframe and waits for the PSP to postMessage the result. Works for PSPs that support a postMessage protocol (or for custom ödematik-hosted forms).
  • type: 'redirect' — the SDK renders a styled CTA button. Click performs window.location.href = url. The PSP's hosted page processes the payment, then redirects back to the merchant's configured successUrl / failureUrl. Use this for iyzico Checkout Form, PayTR iframe API, Param POS, and any other PSP whose result is delivered via HTTP redirect rather than postMessage.

onSuccess does not fire in redirect mode — the user has left the page by then. Show success on your successUrl route instead.

Security model

  • No publishable key, no card data crosses our SDK. The SDK fetches a single-use session via clientSecret, then either embeds the PSP's hosted form in a sandboxed iframe or redirects to it. The card form lives on the PSP's domain (e.g. *.iyzipay.com).
  • Strict origin validation. Inbound postMessage events are dropped unless the sender's origin appears in embed.allowedOrigins.
  • Origin handshake. On iframe load the SDK posts the parent's origin to the iframe so the iframe can targetOrigin-target every reply instead of broadcasting with '*'.
  • PAN mask defense-in-depth. Even if a compromised PSP iframe sent an unmasked PAN in numberDisplay, the SDK refuses to render any string containing more than 10 digits (BIN + last4 only).
  • HTTPS enforced for both apiBase and the embed url.
  • Secret-key trap. If you accidentally pass sk_… instead of cs_…, the SDK throws on mount before contacting the network.
  • credentials: 'omit', cache: 'no-store', referrerPolicy: 'no-referrer' on the lookup fetch.
  • Iframe sandbox is allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox — enough for 3DS pop-ups, no parent-window hijacking.
  • No card data is ever logged, persisted, or exposed in callbacks. Only post-success metadata (last4, brand, paymentId) is observable.
  • Zero runtime dependencies — minimal supply-chain surface.

What ödematik does and doesn't do

ödematik merchant PSP (iyzico/…)
Has merchant's PSP credentials ✅ encrypted at rest ✅ owns them
Sees card PAN / CVC ❌ never ❌ never
Holds funds depends on PSP
Issues invoices, manages subscriptions
Brand-routes / falls over to backup PSP
Needs PCI-DSS L1 ❌ (SAQ A scope)
Needs BDDK ödeme kuruluşu lisansı ❌ (no fund custody)

Browser support

Modern evergreen browsers (Chrome, Edge, Firefox, Safari ≥ 14).

Fonts

The package does not load any fonts itself (perf + privacy). Add this to your <head> for the exact look:

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
  href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap"
  rel="stylesheet"
/>

Otherwise the component falls back to the system font stack.

Development

cd packages/billing
npm install
npm run typecheck
npm run build

License

MIT © ödematik