# CoverPay Link SDKs

All three SDKs open CoverPay's checkout from a **link token** your server created. The SDK never holds a secret key and never sets the amount. `onSuccess` returns a **public token**; your server exchanges it (`POST /v1/link/public_token/exchange`). Exactly one of `onSuccess` / `onExit` runs per open.

Providers (Klarna, Affirm, Afterpay, Zip) and CoverPay's own plan take the shopper's whole page to approve, then send them back to the page that opened Link. Web and React handle that return; call it on every load of the checkout page.

## Web / JavaScript: `@coverpay/sdk`

```js
import { CoverPayLink } from '@coverpay/sdk';

const callbacks = {
  onSuccess: (publicToken, metadata) => exchangeOnYourServer(publicToken),
  onExit: (error, metadata) => {},
};
CoverPayLink.handleRedirect(callbacks);   // once on page load; returns true if it handled a return
const link = CoverPayLink.create({ token: linkToken, ...callbacks, onEvent: (name, metadata) => {} });
payLaterButton.addEventListener('click', () => link.open());
```

| Option | Type | Description |
|---|---|---|
| `token` | string | Required. `lt_test_…` or `lt_live_…`. Anything else throws immediately. |
| `onSuccess` | `(publicToken, metadata) => void` | Required. `publicToken` is `null` only when the provider approved but CoverPay had not confirmed yet: do not charge again, wait for your webhook. |
| `onExit` | `(error, metadata) => void` | Shopper closed Link (`error` null) or it failed (`error.code`, `error.message`). |
| `onEvent` | `(name, metadata) => void` | Funnel events: `OPEN`, `OFFERS_LOADED`, `OFFER_SELECTED`, `STEP_CHANGE`, `CLOSE`. |

Handler methods: `open()`, `exit()` (close as if the shopper did), `destroy()` (remove without callbacks).

`onSuccess` metadata:

```js
{ provider: "zip",            // klarna | affirm | afterpay | zip | coverpay
  sessionId: "pi_3UO3…",      // CoverPay payment id (also returned by the exchange)
  status: "succeeded",        // succeeded | requires_capture | processing
  paymentIntentId: "pi_3UO3…" // Stripe PaymentIntent for Stripe-backed providers, else null
}
```

The return result is in the URL fragment (`#coverpay_status=success&coverpay_public_token=public-sandbox-…&coverpay_provider=zip`), so it never reaches server logs. The public token is useless without your secret key.

Deprecated: `CoverPay.create({ clientId }).open({ amount })` works only in sandbox (amount from the browser). `checkEligibility` always rejects: call `POST /v1/checkout/verify-eligibility` from your server.

## React: `@coverpay/react`

```tsx
import { useCoverPayLink } from '@coverpay/react';

function PayLaterButton({ linkToken }: { linkToken: string | null }) {
  const { open, ready, error } = useCoverPayLink({
    token: linkToken,
    onSuccess: (publicToken, metadata) => { /* POST to your server */ },
    onExit: (error, metadata) => {},
  });
  return <button disabled={!ready} onClick={() => open()}>Pay later</button>;
}
```

Returns `open()` (call from a click), `ready` (true once a valid token is set), `exit()`, `error` (set if the token is not a CoverPay link token). The hook handles the redirect return on mount, so mount it on the page that opened Link.

## iOS: `CoverPaySDK` (Swift Package Manager, iOS 14+)

```swift
import CoverPaySDK

CoverPayLink.open(linkToken: linkToken, from: self) { result in
    switch result {
    case .success(let payment):   // payment.publicToken, .provider, .sessionId, .status
        api.completeOrder(publicToken: payment.publicToken, orderId: orderId)
    case .failure(.userCancelled): break
    case .failure(let error): showError(error.localizedDescription)
    }
}
```

SwiftUI: `.coverPayLink(isPresented: $showCheckout, linkToken: linkToken) { result in … }`.

`publicToken` is nil only if the provider approved but CoverPay had not confirmed yet: rely on your webhook. The completion runs exactly once; swiping the sheet down counts as a cancel. `checkEligibility` on iOS fails immediately: call `POST /v1/checkout/verify-eligibility` from your server. `open(params:)` (amount from the app) is deprecated and sandbox-only.

## Server: exchange

```
POST https://api.coverpayme.com/v1/link/public_token/exchange
x-api-key: cp_test_…
{ "public_token": "public-sandbox-…" }
```

See quickstart.md and openapi.yaml.
