npm package: @myazahq/pos-sdk
Description: Production-grade crypto POS payment SDK — accepts any crypto token in React, Next.js, Vue, Svelte, or plain JS
Author: Myaza
Latest version: 1.0.11

# @myazahq/pos-sdk

Accept crypto payments in your storefront with a single method call — no webhooks, no Web3 setup required. Powered by [Gridlog](https://gridlog.io).

---

## Install

```bash
npm install @myazahq/pos-sdk
# yarn add @myazahq/pos-sdk
# pnpm add @myazahq/pos-sdk
```

Works with **React, Next.js, Vue, Svelte, Nuxt, Vite**, and plain JavaScript.

---

## How it works

```
new MyazaPOS()  →  sdk.init()  →  sdk.checkout()
```

That's the entire integration. `init()` fetches your merchant profile automatically (name, logo) and `checkout()` opens a built-in modal that handles network selection, displays a wallet address and QR code, and polls for payment status — all automatically. You just listen for events and fulfil the order.

---

## Quick Start

```ts
import { MyazaPOS } from '@myazahq/pos-sdk';

const sdk = new MyazaPOS({ merchantId: 'YOUR_MERCHANT_ID' });

await sdk.init(); // fetches merchant profile + validates config

// Listen for the payment you care about
sdk.on('payment.confirmed', ({ session }) => {
  fulfillOrder(session.id);
});

// Open the full checkout UI — one line
await sdk.checkout({ amount: 5000, currency: 'NGN' });
```

> **SSR frameworks (Next.js, Nuxt):** The SDK manipulates the DOM and must only run in the browser.
> Wrap it in `useEffect` (React) or `onMounted` (Vue) — see [Framework Examples](#framework-examples).

---

## `sdk.checkout()` — Start here

`checkout()` is the primary method. Call it when your customer clicks "Pay". It handles the full payment flow end-to-end:

1. Fetches supported networks in the background (the modal opens instantly while this loads).
2. Shows a **network & token selector** modal, pre-filled with the amount, your merchant name, and a live exchange rate for each token.
3. Once the customer confirms, transitions to the **payment modal** — wallet address, QR code, live countdown.
4. Polls for payment status and fires events at every stage.
5. Closes itself on success, failure, or cancellation.

You can call `checkout()` (or `createSession()`) multiple times on the same SDK instance — no page reload or re-initialisation required. Each call resets the payment state automatically.

```ts
// Minimal — shows the full network/token selector
await sdk.checkout({ amount: 5000, currency: 'NGN' });

// With bank payout — Gridlog sends fiat to this account after payment
await sdk.checkout({
  amount:              5000,
  currency:            'NGN',
  payoutAccountNumber: '0123456789',
  payoutNetworkId:     'bank-network-uuid',   // from sdk.getBanks()
});
```

### Parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `amount` | `number` | required | Fiat amount (e.g. `5000`). |
| `currency` | `string` | `'NGN'` | Fiat currency code. |
| `payoutAccountNumber` | `string` | — | Bank account for fiat payout after payment. |
| `payoutNetworkId` | `string` | — | Bank network ID from `getBanks()`. Required when `payoutAccountNumber` is set. |

Returns `null` if the customer closes the modal without paying, or a `PaymentSession` object otherwise.

---

## Payout to a bank account

To route the fiat payout to a specific bank account, look up the bank and verify the account number first:

```ts
// 1. List banks that support NGN payouts
const banks = await sdk.getBanks('NGN');
// → [{ id, name, networkId, currency, country, available }, ...]

// 2. Confirm the account holder before charging the customer
const account = await sdk.resolveAccount(banks[0].networkId, '0123456789');

if (!account.matched) {
  // Account not found — show an error to the user
  return;
}

console.log('Account holder:', account.accountName); // "John Doe"

// 3. Pass verified details into checkout
await sdk.checkout({
  amount:              5000,
  currency:            'NGN',
  payoutAccountNumber: account.accountNumber,
  payoutNetworkId:     account.networkId,
});
```

Both `payoutAccountNumber` and `payoutNetworkId` are optional, but they must be provided together — one without the other will be rejected.

---

## Events

Register handlers with `sdk.on()` before calling `checkout()`.

```ts
sdk.on('payment.created', ({ session }) => {
  // Payment session opened — address is ready
  console.log('Send', session.token, 'to', session.address);
});

sdk.on('payment.received', ({ sessionId }) => {
  // Crypto arrived — payout is processing
});

sdk.on('payment.confirmed', ({ session }) => {
  // Payout complete — safe to fulfil the order
  fulfillOrder(session.id);
});

sdk.on('payment.failed', ({ sessionId, reason }) => {
  console.error('Payment failed:', reason);
});

sdk.on('payment.expired', ({ sessionId }) => {
  // Session timed out — customer can try again
});

sdk.on('payment.cancelled', ({ sessionId }) => {
  // Customer closed the modal
});
```

---

## Configuration

```ts
const sdk = new MyazaPOS({
  merchantId:        'YOUR_MERCHANT_ID',  // required
  isSandbox:         false,               // true → use Gridlog sandbox (testing only)
  pollIntervalMs:    5_000,               // how often to poll for status (default: 5 sec)
  maxPollDurationMs: 900_000,             // give up polling after this long (default: 15 min)

  theme: {
    primaryColor:    '#6366f1',           // buttons and highlights
    backgroundColor: '#ffffff',           // modal background
    textColor:       '#1a1a2e',           // all text
    borderRadius:    12,                  // corner radius in px
    fontFamily:      'Inter, sans-serif',
    logoUrl:         'https://your-site.com/logo.png',  // must be HTTPS
    colorScheme:     'light',             // 'light' | 'dark' | 'system'
    customCss:       '.myaza-cancel-btn { display: none; }',
  },
});
```

> **Merchant name:** You do not need to configure your store name. The SDK fetches it automatically from your Gridlog merchant profile during `init()` and displays it in the checkout modal header.

For advanced config (fraud rules, plugins, multi-tenant, observability) see [MAINTAINERS.md](MAINTAINERS.md).

---

## Sandbox vs Production

```ts
// Sandbox — safe for development and testing
const sdk = new MyazaPOS({ merchantId: 'YOUR_MERCHANT_ID', isSandbox: true });

// Production — the default
const sdk = new MyazaPOS({ merchantId: 'YOUR_MERCHANT_ID' });
```

| | API base URL | Config |
|---|---|---|
| Production | `https://secureapi.gridlog.io/api/v1` | `isSandbox: false` (default) |
| Sandbox | `https://sandbox.secureapi.gridlog.io/api/v1` | `isSandbox: true` |
| Custom | Your own URL | `apiBaseUrl: 'https://...'` |

`apiBaseUrl` takes precedence over `isSandbox` when both are set.

When `isSandbox: true`, the SDK logs a warning immediately at construction time so you cannot accidentally ship a sandbox config to production.

---

## Framework Examples

### React

```tsx
import { useEffect, useRef } from 'react';
import { MyazaPOS } from '@myazahq/pos-sdk';

export function PayButton({ amount }: { amount: number }) {
  const sdkRef = useRef<MyazaPOS | null>(null);

  useEffect(() => {
    const sdk = new MyazaPOS({ merchantId: 'YOUR_MERCHANT_ID' });
    sdk.init().then(() => { sdkRef.current = sdk; });
    sdk.on('payment.confirmed', ({ session }) => fulfillOrder(session.id));
    return () => { void sdk.terminate(); };
  }, []);

  return (
    <button onClick={() => sdkRef.current?.checkout({ amount, currency: 'NGN' })}>
      Pay with Crypto
    </button>
  );
}
```

### Next.js (App Router)

```tsx
'use client';

import { useEffect, useRef } from 'react';
import { MyazaPOS } from '@myazahq/pos-sdk';

export function PayButton({ amount }: { amount: number }) {
  const sdkRef = useRef<MyazaPOS | null>(null);

  useEffect(() => {
    const sdk = new MyazaPOS({ merchantId: 'YOUR_MERCHANT_ID' });
    sdk.init().then(() => { sdkRef.current = sdk; });
    return () => { void sdk.terminate(); };
  }, []);

  return (
    <button onClick={() => sdkRef.current?.checkout({ amount, currency: 'NGN' })}>
      Pay with Crypto
    </button>
  );
}
```

For Next.js Pages Router or anywhere you need lazy loading:

```ts
const { MyazaPOS } = await import('@myazahq/pos-sdk');
```

### Vue 3

```vue
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
import { MyazaPOS } from '@myazahq/pos-sdk';

const props = defineProps<{ amount: number }>();
let sdk: MyazaPOS | null = null;

onMounted(async () => {
  sdk = new MyazaPOS({ merchantId: 'YOUR_MERCHANT_ID' });
  await sdk.init();
  sdk.on('payment.confirmed', ({ session }) => fulfillOrder(session.id));
});

onUnmounted(() => { sdk?.terminate(); });
</script>

<template>
  <button @click="sdk?.checkout({ amount: props.amount, currency: 'NGN' })">
    Pay with Crypto
  </button>
</template>
```

### Nuxt 3

```vue
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
import type { MyazaPOS as TSDK } from '@myazahq/pos-sdk';

let sdk: TSDK | null = null;

onMounted(async () => {
  const { MyazaPOS } = await import('@myazahq/pos-sdk');
  sdk = new MyazaPOS({ merchantId: 'YOUR_MERCHANT_ID' });
  await sdk.init();
});

onUnmounted(() => { sdk?.terminate(); });
</script>
```

### Svelte / SvelteKit

```svelte
<script lang="ts">
  import { onMount, onDestroy } from 'svelte';
  import type { MyazaPOS as TSDK } from '@myazahq/pos-sdk';

  export let amount: number;
  let sdk: TSDK | null = null;

  onMount(async () => {
    const { MyazaPOS } = await import('@myazahq/pos-sdk');
    sdk = new MyazaPOS({ merchantId: 'YOUR_MERCHANT_ID' });
    await sdk.init();
  });

  onDestroy(() => { sdk?.terminate(); });
</script>

<button on:click={() => sdk?.checkout({ amount, currency: 'NGN' })}>
  Pay with Crypto
</button>
```

### CDN (no build step)

```html
<script src="https://cdn.jsdelivr.net/npm/@myazahq/pos-sdk/dist/index.umd.js"></script>
<script>
  const sdk = new MyazaPOS.MyazaPOS({ merchantId: 'YOUR_MERCHANT_ID' });

  sdk.init().then(() => {
    document.getElementById('pay-btn').addEventListener('click', () => {
      sdk.checkout({ amount: 5000, currency: 'NGN' });
    });

    sdk.on('payment.confirmed', ({ session }) => {
      window.location.href = '/order-complete?id=' + session.id;
    });
  });
</script>
```

---

## Server-Side Verification

Always verify payment status from your **server** before fulfilling an order. Never trust client-side events alone.

```js
// Your backend — keep the Bearer token server-side only
const res = await fetch(`https://secureapi.gridlog.io/api/v1/sdk/pos/sessions/${sessionId}?merchantId=<merchantId>`, 

const data = await res.json();

if (data.data.status === 'paid_out') {
  await fulfillOrder(data.data.id);
}
```

Store your Bearer token in a secret manager (e.g. AWS Secrets Manager, Doppler) — never expose it in client-side code or commit it to source control.

---

## All SDK Methods

| Method | Returns | Description |
|---|---|---|
| `sdk.init()` | `Promise<this>` | Initialise the SDK. Fetches merchant profile. Call once before anything else. |
| `sdk.checkout(params)` | `Promise<PaymentSession \| null>` | **Start here.** Opens the full checkout UI. |
| `sdk.createSession(params)` | `Promise<PaymentSession>` | Low-level. Opens the payment modal directly when you already know the chain and token. Requires `chain` and `token`. |
| `sdk.getBanks(currency)` | `Promise<Bank[]>` | List banks available for fiat payout (e.g. `'NGN'`). |
| `sdk.resolveAccount(networkId, accountNumber)` | `Promise<BankAccountResolution>` | Confirm an account holder name before charging. |
| `sdk.getNetworks()` | `Promise<SupportedNetwork[]>` | List supported blockchains and tokens. |
| `sdk.getExchangeRate(currency, coin)` | `Promise<ExchangeRate>` | Current exchange rate for a currency/token pair. |
| `sdk.on(event, handler)` | `this` | Subscribe to an SDK event. |
| `sdk.once(event, handler)` | `this` | Subscribe once — unsubscribes automatically after firing. |
| `sdk.off(event, handler)` | `this` | Unsubscribe. |
| `sdk.use(plugin)` | `this` | Register a plugin at runtime. |
| `sdk.registerMerchant(config)` | `this` | Register a merchant for multi-tenant operation. |
| `sdk.getState()` | `SDKState` | Current state machine state. |
| `sdk.terminate()` | `Promise<void>` | Clean shutdown — cancels session, closes modal, uninstalls plugins. |

---

## Maintainers

Architecture details, plugin authoring, fraud config, multi-tenant setup, CI/CD, and build instructions: [MAINTAINERS.md](MAINTAINERS.md).

---

## Licence

MIT © Myaza