@yumi-finance/widget
v1.0.5
Published
Yumi BNPL checkout widget
Readme
Yumi Widget
Embeddable Yumi BNPL widget: one script or React components; checkout opens in an iframe. After login in the iframe the widget requests a signature; the merchant returns it from their backend (or locally for testing).
Install
npm install @yumi-finance/widgetCDN (no build):
<script src="https://cdn.jsdelivr.net/npm/@yumi-finance/[email protected]/dist/yumi-widget.min.js"></script>React (example)
Minimum: publicKey, getAppSignature, and on click — open({ orderId, amount }).
import { YumiProvider, useYumi } from '@yumi-finance/widget/react'
// In production getAppSignature calls your backend and returns appSignature
const getAppSignature = async (payload: {
publicKey: string
timestamp: number
nonce: string
privyUserId: string
}): Promise<string> => {
const res = await fetch('/api/sign', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
const data = await res.json()
if (!res.ok || data.error) throw new Error(data.error ?? 'Sign failed')
return data.appSignature
}
function CheckoutButton() {
const { open, isReady, error } = useYumi()
return (
<>
{error && <p>Error: {error.message}</p>}
<button
disabled={!isReady}
onClick={() => open({ orderId: 'order_' + Date.now(), amount: 0.04 })}
>
Pay with Yumi
</button>
</>
)
}
export default function App() {
return (
<YumiProvider config={{ publicKey: 'YOUR_PUBLIC_KEY', getAppSignature }}>
<CheckoutButton />
</YumiProvider>
)
}Optional: onClose, onEvent, onError.
Script only (no React)
Same contract: Yumi.init({ publicKey, getAppSignature }), on click yumi.open({ orderId, amount }).
const yumi = Yumi.init({
publicKey: 'YOUR_PUBLIC_KEY',
getAppSignature: async payload => {
const res = await fetch('/api/sign', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
const { appSignature } = await res.json()
if (!res.ok) throw new Error('Sign failed')
return appSignature
},
})
document.getElementById('payBtn').onclick = () => yumi.open({ orderId: 'order_123', amount: 0.04 })Parameters
| Where | Field | Required | Description |
| ------ | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| init | publicKey | yes | Merchant public key (Ed25519). Merchants generate their own keypair; the public key is passed here, the secret key is used only on the backend for signing. |
| init | getAppSignature | yes | (payload) => Promise<string>. Widget passes { publicKey, timestamp, nonce, privyUserId }; you return the signature (usually from your backend). |
| open | orderId | yes | Order or intent id. |
| open | amount | yes | Full order amount in USD (e.g. 0.04 for 4 cents). This amount is split into 4 equal payments (Pay in 4). |
App registration
Before using the widget, register your application on the Yumi backend: https://yumi-app-dev.up.railway.app/. API docs (Swagger): https://yumi-app-dev.up.railway.app/docs.
- Get an App UUID from Yumi.
- Generate a new Ed25519 keypair locally (see Key generation below). Keep the secret key on your side; you will send only the public key to the server.
- Sign the canonical JSON of the payload
{ "publicKey": "<your-public-key-base64>" }with your secret key (Ed25519 detached, base64-encoded signature). - Call
POST /auth-apps/register-appwith body:
{ "uuid": "YOUR-APP-UUID", "signature": "<signature_base64>", "payload": { "publicKey": "<public_key_base64>" } }Your app is registered. Configuration may take a few minutes; contact Yumi if needed.
Backend signing
Merchants generate a new EdDSA (Ed25519) signing keypair locally. Use the public key in Yumi.init({ publicKey }); keep the secret key only on your backend (e.g. in env) and never expose it to the frontend.
Generate keypair (Node, one-off):
const nacl = require('tweetnacl')
const util = require('tweetnacl-util')
const keypair = nacl.sign.keyPair()
const publicKeyBase64 = util.encodeBase64(keypair.publicKey)
const secretKeyBase64 = util.encodeBase64(keypair.secretKey)
// Use publicKeyBase64 in Yumi.init(); store secretKeyBase64 in env (e.g. YUMI_SECRET_KEY).Your /api/sign endpoint receives the payload { publicKey, timestamp, nonce, privyUserId } and must return { appSignature }: the base64-encoded Ed25519 signature of the canonical JSON of that payload, signed with your secret key.
Example (Node; deps: tweetnacl, tweetnacl-util, json-canonicalize):
const nacl = require('tweetnacl')
const util = require('tweetnacl-util')
const { canonicalize } = require('json-canonicalize')
function signLoginPayload(payload, secretKeyBase64) {
const secretKey = util.decodeBase64(secretKeyBase64)
const message = canonicalize(payload)
const signature = nacl.sign.detached(util.decodeUTF8(message), secretKey)
return util.encodeBase64(signature)
}
app.post('/api/sign', (req, res) => {
try {
res.json({ appSignature: signLoginPayload(req.body, process.env.YUMI_SECRET_KEY) })
} catch (err) {
res.status(400).json({ error: err.message })
}
})License
MIT
