Widget pembayaran
Hantar seseorang ke halaman Atli yang selamat untuk membayar atau menambah kad, apabila aplikasi anda tidak dapat memaparkan borang Stripe sendiri.
Bila hendak menggunakannya
Pembantu AI dan klien MCP, yang bercakap dengan orang dalam sembang dan tidak mempunyai skrin sendiri untuk meletakkan borang kad.
Membayar di luar aliran, seperti bot dalam Telegram atau WhatsApp, atau menyelesaikannya pada peranti lain atau kemudian.
Mana-mana klien lain yang tidak dapat memuatkan SDK Stripe, seperti terminal atau aplikasi suara.

setup_intent
Orang itu menyimpan kad ke akaunnya tanpa membayar sekarang, supaya klien anda boleh membeli dengannya kemudian.
Mulakan penambahan kad
POST /user/payment-methods/setup-intentmenjawab dengan tiga medan di sebelahclient_secret.Untuk kad kumpulan, gunakan
POST /groups/:groupId/cards/setupdanPOST /groups/:groupId/cards/confirmdengan cara yang sama. Jawapannya menamakan id itusetup_intent_id.curl -X POST https://api.heyatli.com/user/payment-methods/setup-intent \ -H 'Authorization: Bearer YOUR_TOKEN' { "id": "seti_…", "client_secret": "seti_…_secret_…", "sealed_secret": "eyJhbGciOiJFQ0RILUVTIi…", "key_id": "QvWE2gHWYExKj2Yc…", "seal_signature": "c2lnbmF0dXJl…", "options": "{\"lang\":\"en\",\"message\":\"Plan for Australia\"}", "options_signature": "b3B0aW9ucw…" }Hantar pautan kepada orang itu
Letakkan ketiga-tiga nilai itu dalam pautan ke halaman pembayaran, dengan
typeditetapkan kepadasetup_intent. Orang itu hanya melihat borang Stripe, tanpa jumlah.const link = new URL('https://heyatli.com/pay'); link.search = new URLSearchParams({ type: 'setup_intent', sealed_secret: response.sealed_secret, key_id: response.key_id, seal_signature: response.seal_signature, return_id: response.id, // Only when the response has them, and always together. options goes in // base64url-encoded (Buffer in Node; TextEncoder and btoa in a browser). ...(response.options && { options: Buffer.from(response.options).toString('base64url'), options_signature: response.options_signature, }), }).toString();Simpan kad
POST /user/payment-methods/confirmmenyimpan kad ke akaun apabila orang itu selesai. KemudianGET /user/payment-methodsmenyenaraikannya.curl https://api.heyatli.com/user/payment-methods/confirm \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "setup_intent_id": "seti_…" }'
Apa yang masuk dalam pautan
| Parameter | Wajib | Fungsinya |
|---|---|---|
| type | Ya |
|
| sealed_secret | Ya | Nilai daripada respons, tepat seperti yang diterima. |
| key_id | Ya | Nilai daripada respons. Ia menamakan kunci tempat rahsia itu dimeterai. |
| seal_signature | Ya | Nilai daripada respons. Halaman memeriksanya dan menolak pautan yang nilainya tiada atau tidak sepadan. |
| options | Tidak |
|
| options_signature | Tidak | Nilai daripada respons, tanpa diubah, bersama |
| lang | Tidak | Bahasa halaman, salah satu daripada 15 bahasa laman ini. Tanpanya, bahasa yang dipilih orang itu di heyatli.com, kemudian bahasa pelayar. |
| theme | Tidak |
|
| brand | Tidak |
|
| redirect_success_url | Tidak | Ke mana orang itu dihantar selepas itu. Mesti |
| redirect_cancel_url | Tidak | Ke mana orang itu dihantar selepas itu. Mesti |
| redirect_failure_url | Tidak | Ke mana orang itu dihantar selepas itu. Mesti |
| return_id | Tidak | Rujukan anda sendiri, ditambah pada pengubahan hala. Huruf, nombor, |
Cara mengendalikan pautan
Hantar nilai tanpa mengubahnya.
URLSearchParamsmengendalikan pengekodan.Hantar juga pilihan halaman. Jika respons mengandungi
optionsdanoptions_signature, tambah kedua-duanya:optionsdikodkan base64url,options_signatureseperti yang diterima. Jangan ubah JSON sebelum mengekodnya.Kekal dalam satu persekitaran. Nilai Sandbox hanya dibuka di
staging.heyatli.com/pay, dan nilai Production hanya diheyatli.com/pay.Layan seperti kata laluan. Hantar hanya kepada pemilik akaun, dan jangan biarkan ia masuk ke dalam log.
Satu pesanan pada satu masa. Pembelian baharu membatalkan pesanan tertunda sebelumnya dan pautannya, jadi jangan simpan pautan.
Jika ketiga-tiga medan tiada, buat panggilan sekali lagi. Jangan sekali-kali letak
client_secretbiasa dalam pautan.Ubah hala tidak membuktikan apa-apa. Percaya hanya pada semakan dalam langkah terakhir.
Cara penyulitan dan tandatangan berfungsi
Anda tidak memerlukan semua ini untuk menghantar pautan. Ia ada supaya anda dapat melihat apa yang berlaku dan menyemak apa yang dikembalikan pelayan. Kedua-duanya menggunakan kunci awam, pada alamat ini untuk persekitaran yang anda pilih:
- Halaman pembayaran
- https://heyatli.com/pay
- Kunci penyulitan
- https://heyatli.com/api/keys
- Kunci penandatanganan
- https://api.heyatli.com/keys
Penyulitan
sealed_secret ialah client_secret sebagai JWE padat, format yang sudah selamat dalam URL. Ia disulitkan kepada kunci awam X25519 laman, jadi hanya pelayan laman yang boleh membacanya. key_id ialah id kunci itu, dan sepadan dengan kid JWE.
- alg
- ECDH-ES
- enc
- A256GCM
- kid
- key_id
- Key
- JWK: kty OKP, crv X25519, x = public_key
API melakukannya untuk anda, dengan kunci pertama dalam senarai. Dalam kod, ia seperti ini:
import { CompactEncrypt, importJWK } from 'jose';
const { keys: [key] } = await (await fetch('https://heyatli.com/api/keys')).json();
const publicKey = await importJWK(
{ kty: 'OKP', crv: 'X25519', x: key.public_key },
'ECDH-ES'
);
const sealedSecret = await new CompactEncrypt(new TextEncoder().encode(clientSecret))
.setProtectedHeader({ alg: 'ECDH-ES', enc: 'A256GCM', kid: key.id })
.encrypt(publicKey);Tandatangan
Oleh kerana sesiapa boleh menyulitkan, nilai yang dimeterai tidak menunjukkan siapa yang mencipta. Jadi API menandatanganinya dengan kunci peribadi Ed25519 yang hanya dipegangnya. Mesej yang ditandatangani ialah bait UTF-8 bagi tiga baris ini, digabungkan dengan baris baharu, tanpa baris baharu di hujung:
atli-seal-v1
<key_id>
<sealed_secret>Beginilah API membentuk tandatangan, dengan kunci peribadi yang tidak pernah keluar daripadanya:
// What the API does. Only it holds the private key.
const message = new TextEncoder().encode(
`atli-seal-v1\n${keyId}\n${sealedSecret}`
);
const signature = await crypto.subtle.sign('Ed25519', privateKey, message);
const sealSignature = Buffer.from(signature).toString('base64url');seal_signature ialah tandatangan mentah 64 bait dalam base64url tanpa lapik, iaitu 86 aksara. Setiap persekitaran menandatangani dengan kuncinya sendiri, dan senarai boleh mengandungi dua semasa penggiliran.
Halaman pembayaran memeriksa ini sebelum membuka apa-apa, dan menolak pautan yang tidak mempunyainya atau tidak sepadan. Untuk menyemak sendiri apa yang dikembalikan pelayan, bina semula mesej itu dan sahkan tandatangan dengan kunci penandatanganan:
// In Node. sealedSecret, keyId and sealSignature come from the API response.
const { keys } = await (await fetch('https://api.heyatli.com/keys')).json();
const base64url = (value) => Uint8Array.from(Buffer.from(value, 'base64url'));
const message = new TextEncoder().encode(
`atli-seal-v1\n${keyId}\n${sealedSecret}`
);
// The list can hold two keys during a rotation: one match is enough.
const checks = keys.map(async (key) =>
crypto.subtle.verify(
'Ed25519',
await crypto.subtle.importKey('raw', base64url(key.public_key), 'Ed25519', false, ['verify']),
base64url(sealSignature),
message
)
);
const valid = (await Promise.all(checks)).some(Boolean);Mengubah sealed_secret atau key_id merosakkan tandatangan. Ia tidak meliputi type, pengubahan hala atau parameter lain, dan tidak membuktikan bahawa orang yang membuka pautan memiliki akaun itu. Pautan bertandatangan terus berfungsi selagi Stripe menerima rahsia itu.
Pilihan halaman
API memulangkan options sebagai teks JSON. Ia mengandungi message, teks biasa sehingga 300 aksara, dan lang, bahasa mesej itu. Kunci lain mungkin ditambah, dan halaman mengabaikan kunci yang tidak dikenalinya. Mesej mengikut bahasa permintaan anda: X-Locale, Accept-Language atau ?lang=, seperti di seluruh API. Dalam pautan ia berbentuk base64url, jadi tidak perlu dilepaskan dan kekal pendek dalam apa jua bahasa. Pilihan hanyalah tambahan: jika tiada atau gagal disemak, halaman tetap menerima bayaran tanpanya.
// response.options, as the API returns it
{"lang":"en","message":"Plan for Australia"}options_signature dibuat dengan kunci yang sama ke atas empat baris ini, dengan <options> ialah JSON tepat seperti yang dipulangkan API. Halaman menyahkod nilai daripada pautan dan menyemaknya. Oleh sebab rahsia yang dimeterai termasuk di dalamnya, pilihan yang dipindahkan ke pautan lain tidak akan lulus:
atli-options-v1
<key_id>
<sealed_secret>
<options>