Embedded Checkout
Додайте захищені поля картки та Apple Pay або Google Pay до власного оформлення замовлення.
Розмістіть захищені поля картки у власному оформленні замовлення. Форма та кнопка надсилання належать вам; Tapaya виконує валідацію картки, токенізацію та 3-D Secure. Вбудовані платежі працюють для схвалених мерчантів, а для тестування доступна пісочниця (sandbox). Цей посібник використовує Tapaya.js 0.2.0 і Node.js SDK 0.3.0.
- 1. Your frontend to Your backend: Prepare payment.
- 2. Your backend to Tapaya: Create or resume payment.
- 3. Tapaya to Your backend: Payment and client secret.
- 4. Your backend to Your frontend: Client secret and status.
- 5. Your frontend to Tapaya: Confirm card and 3-D Secure.
- 6. Tapaya to Your frontend: Payment outcome.
- 7. Your backend to Tapaya: Read order's payment.
- 8. Tapaya to Your backend: Payment status.
- 9. Your backend to Your frontend: Show verified result.
Способи оплати
Embedded Checkout підтримує картки, Apple Pay і Google Pay. Для карток використовуйте власну кнопку оплати, а для Apple Pay і Google Pay офіційні кнопки гаманців. Див. Додавання Apple Pay і Google Pay.
Перш ніж почати
Встановіть SDK
Один пакет містить серверний клієнт, браузерний SDK і React-хук:
npm install @tapayadot/checkout| Імпорт | Де використовувати |
|---|---|
@tapayadot/checkout | Лише на вашому сервері. Імпорт у браузерній збірці спричиняє виняток, тож ваш API-ключ ніколи не потрапляє на сторінку. |
@tapayadot/checkout/client | На вашій сторінці оформлення замовлення. |
@tapayadot/checkout/react | На вашій сторінці оформлення замовлення, якщо ви використовуєте React. |
Налаштуйте свій сервер
Вбудованим платежам URL повернення не потрібні. Наведені нижче серверні приклади мають версії HTTP (fetch), Node.js SDK і cURL, які всі викликають той самий API мерчанта й не змінюють поля картки в браузері.
| Node.js SDK | HTTP (fetch) і cURL | |
|---|---|---|
| Ключ | TAPAYA_SECRET_KEY, який зчитує new Tapaya() | TAPAYA_CHECKOUT_API_KEY |
| Середовище | TAPAYA_ENVIRONMENT=sandbox або production. Без цієї змінної ключ tapaya_live_ вибирає робоче середовище, а інші ключі типово використовують пісочницю. | TAPAYA_CHECKOUT_API_URL: https://api.sandbox.tapaya.com або https://api.tapaya.com |
| Режим платформи | Передавайте merchantToken замовлення в кожен виклик або задайте його через new Tapaya(key, { merchantToken }) | Надсилайте X-Tapaya-Merchant-Token у кожному запиті. Допоміжна функція fetch зчитує його з TAPAYA_MERCHANT_TOKEN. |
Checkout-ключу мерчанта більше нічого не потрібно. У режимі платформи використовуйте Secret key організації, а бекенд, який обслуговує кількох мерчантів, має брати токен з автентифікованого замовлення. Див. автентифікацію Checkout. Client secret видається окремо для кожного платежу.
Пройдіть онбординг з онлайн-платежами та перевірте в налаштуваннях Checkout, що мерчант має статус Ready.
Дозвольте Tapaya у своїй CSP
Завантажувач отримує Tapaya.js 0.2.0 з https://js.tapaya.com/checkout/0.2.0/checkout.js; версія браузерного скрипта
не залежить від версії Node.js SDK. Додайте origin ресурсів Tapaya до своєї наявної Content Security
Policy:
script-src https://js.tapaya.com
frame-src https://js.tapaya.comБраузер отримує publishable key і client secret конкретного платежу. Ваш секретний API-ключ мерчанта
залишається на вашому сервері, а дані картки та скрипти платіжного провайдера залишаються у фреймі Tapaya. Для локального тестування
розмістіть Tapaya.js на власному сервері та передайте його origin завантажувачу, наприклад
loadTapaya(publishableKey, { apiOrigin, origin: 'http://localhost:4191' }).
Як це працює
- Ваша сторінка розміщує поля картки Tapaya та спрямовує їх на ендпоінт на вашому сервері.
- Під час завантаження сторінки та коли клієнт платить, поля картки викликають цей ендпоінт. Він повертає платіж
замовлення як
clientSecret: облікові дані для одного платежу, якими поля картки підтверджують платіж безпосередньо в Tapaya. Код вашої сторінки ніколи його не зчитує. - Ваша сторінка стежить за
outcomeплатежу. Коли платіж завершено, ваш сервер перевіряє його, перш ніж ви виконаєте замовлення.
Прийом платежу
Отримайте налаштування для браузера
Один раз отримайте налаштування для браузера на своєму сервері та додайте їх до конфігурації свого фронтенду.
Тримайте цю допоміжну функцію на своєму сервері. Вона перевіряє HTTP-помилки, за запитом повертає null, якщо платежу замовлення немає,
і не виконує повторних спроб автоматично.
const apiOrigin = new URL(process.env.TAPAYA_CHECKOUT_API_URL).origin;
const apiKey = process.env.TAPAYA_CHECKOUT_API_KEY;
const merchantToken = process.env.TAPAYA_MERCHANT_TOKEN;
export async function paymentRequest(path, { method = 'GET', body, key, allowMissing = false } = {}) {
const response = await fetch(new URL(path, apiOrigin), {
method,
headers: {
Authorization: `Bearer ${apiKey}`,
...(merchantToken ? { 'X-Tapaya-Merchant-Token': merchantToken } : {}),
...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
...(key ? { 'Idempotency-Key': key } : {}),
},
...(body === undefined ? {} : { body: JSON.stringify(body) }),
redirect: 'error',
cache: 'no-store',
signal: AbortSignal.timeout(35000),
});
if (allowMissing && response.status === 404) return null;
if (!response.ok) throw new Error(`Payment request failed (${response.status})`);
return response.json();
}
const { publishableKey } = await paymentRequest('/merchant/payments/configuration');
export const config = { publishableKey, apiOrigin };API повертає publishableKey і environment. Задайте apiOrigin на основі URL API, який ви викликали; це не поле
відповіді з конфігурацією.
Обидва значення можна безпечно включати у свою сторінку. Publishable key використовують pk_test_ з тестовими ключами шлюзу та pk_live_
з ключами шлюзу робочого режиму. Завжди передавайте завантажувачу apiOrigin, повернений вашим сервером, щоб браузер і сервер
працювали в тому самому середовищі.
Додайте поля картки
Додайте контейнер до своєї наявної форми оформлення замовлення:
<form id="checkout">
<!-- Your other checkout inputs go here. -->
<div id="card-fields"></div>
<button type="submit" disabled>Pay</button>
<p id="message" role="status"></p>
</form>Завантажте Tapaya та розмістіть поля. prepare є ендпоінтом, який ви додасте на наступному кроці.
import { loadTapaya } from '@tapayadot/checkout/client'
import { config } from './config' // { publishableKey, apiOrigin } from your server
const form = document.querySelector<HTMLFormElement>('#checkout')!
const button = form.querySelector<HTMLButtonElement>('button')!
const message = document.querySelector<HTMLElement>('#message')!
const tapaya = await loadTapaya(config.publishableKey, { apiOrigin: config.apiOrigin })
const payment = tapaya.createPayment({
prepare: '/api/payments',
onChange: ({ canSubmit, outcome }) => {
button.disabled = !canSubmit
if (outcome === 'completed') location.assign('/checkout/confirmation')
if (outcome === 'ended') message.textContent = 'This payment cannot be completed. Start a new checkout.'
},
})
await payment.mount('#card-fields')Коли поля розміщуються, вони викликають prepare, щоб продовжити наявний платіж замовлення. Перезавантаження під час платежу ніколи
не призводить до повторного стягнення оплати: натомість сторінка показує поточний стан платежу.
Поверніть платіж замовлення
Підключіть цей обробник до POST /api/payments. getCheckoutOrder() є функцією вашого власного застосунку: автентифікуйте
клієнта, перевірте доступ до його замовлення та поверніть збережені id, amount, currency і status цього замовлення. Застосуйте
до маршруту свій звичайний захист від CSRF.
Перш ніж створювати спробу, прочитайте наявний платіж. Повторно використовуйте невизначений платіж, а нову спробу створюйте
лише після відмови або скасування з retryAllowed: true.
import { paymentRequest } from './tapaya.js';
import { getCheckoutOrder } from './orders.js';
export async function findPayment(order) {
const query = new URLSearchParams({ merchantOrderId: order.id });
const payment = await paymentRequest(`/merchant/payments?${query}`, { allowMissing: true });
if (payment && (payment.merchantOrderId !== order.id ||
payment.amount !== order.amount || payment.currency !== order.currency)) {
throw new Error('Payment does not match the order');
}
return payment;
}
export function browserStatus(payment) {
if (payment.status === 'successful') return 'completed';
if (['declined', 'cancelled'].includes(payment.status) && !payment.retryAllowed) return 'ended';
return 'open';
}
export async function POST(request) {
const order = await getCheckoutOrder(request);
const payable = order.status === 'pending_payment';
let payment = await findPayment(order);
if (payable && (!payment ||
(['declined', 'cancelled'].includes(payment.status) && payment.retryAllowed))) {
// Derive the same key on retries, including after a lost creation response.
let key = `${order.id}:${payment ? `after:${payment.id}` : 'initial'}`;
if (/[^\x21-\x7e]/.test(key)) {
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(key));
key = `prepare:${Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('')}`;
}
payment = await paymentRequest('/merchant/payments', {
method: 'POST', key,
body: { merchantOrderId: order.id, amount: order.amount, currency: order.currency },
});
if (payment.merchantOrderId !== order.id || payment.amount !== order.amount || payment.currency !== order.currency) {
throw new Error('Payment does not match the order');
}
}
if (!payment) return Response.json({ error: 'Order cannot accept payment' }, { status: 409 });
return Response.json(
{ clientSecret: payable ? payment.clientSecret : null, status: browserStatus(payment) },
{ headers: { 'Cache-Control': 'no-store' } },
);
}Використовуйте код валюти у верхньому регістрі та збережений ID замовлення довжиною щонайбільше 200 символів. Не змінюйте дані замовлення після початку платежу. У разі тайм-ауту знову прочитайте платіж замовлення, перш ніж вирішувати, чи потрібна нова спроба. Ніколи не генеруйте новий ключ лише тому, що відповідь було втрачено.
REST API повертає стани платежу. Node SDK зіставляє їх зі status, якого очікують поля картки; інтеграції через fetch і cURL
застосовують те саме зіставлення у власному ендпоінті prepare:
status API | status ендпоінта prepare |
|---|---|
successful | completed |
declined або cancelled з retryAllowed: false | ended |
ready, pending, action_required або відмова чи скасування, після яких дозволено повторну спробу | open |
Надсилайте оплату зі свого обробника
Додайте це до того самого скрипта. Ваш обробник валідує вашу форму та викликає submit():
form.addEventListener('submit', async (event) => {
event.preventDefault()
if (!form.reportValidity() || !payment.snapshot.canSubmit) return
await payment.submit().catch(() => {
// The card fields show the reason. The shopper can try again when canSubmit allows it.
})
})submit() викликає prepare для платежу, потім валідує картку, виконує 3-D Secure і підтверджує платіж
у Tapaya.
Покажіть результат
onChange отримує snapshot платежу щоразу, коли він змінюється, зокрема коли сторінка завантажується з платежем, який
уже завершено. Орієнтуйтеся на snapshot.outcome:
outcome | Що показувати |
|---|---|
open | Кнопку оплати, активну, поки canSubmit має значення true. Після відмови стан залишається open, і наступний submit() отримує нову спробу. |
pending | Індикатор виконання. Результат ще невідомий, тож повторно оплатити нічого не можна. SDK автоматично перевіряє статус, поки сторінка видима. |
completed | Вашу сторінку підтвердження. Там ваш сервер перевіряє замовлення. |
ended | Нове оформлення замовлення. Для цього замовлення більше спроб неможливо. |
Викликайте payment.destroy(), коли компонент оформлення замовлення демонтується. Для React використовуйте usePayment() з
@tapayadot/checkout/react. Він приймає ті самі параметри плюс publishableKey і apiOrigin і повертає
fieldRef для контейнера, поточний snapshot і submit().
Перевіряйте перед виконанням замовлення
Результат у браузері призначений лише для відображення. На сторінці підтвердження прочитайте платіж замовлення на своєму сервері й виконуйте замовлення лише тоді, коли платіж завершено:
import { findPayment } from './payments.js';
const order = await getCheckoutOrder(request);
const payment = await findPayment(order); // Checks order ID, amount, and currency.
if (payment?.status === 'successful') await markOrderPaid(order.id, payment.id);Подбайте, щоб markOrderPaid() записувала платіж лише один раз, навіть якщо її викликають повторно, і запускайте виконання замовлення на основі цього
збереженого стану.
Звірка замовлень, які клієнти залишили
Клієнт може закрити вкладку до того, як відкриється сторінка підтвердження. Запустіть заплановане завдання, яке для неоплачених замовлень викликає verify() або
читає GET /merchant/payments?merchantOrderId=... з тими самими перевірками. Відхилені та
скасовані спроби дозволяють нову спробу, тож неоплачене замовлення залишається open: знімайте з нього резервування за власним розкладом, і
лише тоді, коли його paymentStatus не має значення pending. Невдалий запит чи тайм-аут не є відмовою, тому тримайте замовлення
зарезервованим, доки результат платежу не стане відомим.
Додавання Apple Pay і Google Pay
Гаманці з'являються у фреймі Tapaya поруч із полями картки. Щоб вони відобразилися, перевірте, що:
- Гаманці увімкнено в налаштуваннях Checkout.
- Apple Pay: магазин має активний домен магазину.
- Google Pay у робочому середовищі: налаштовано Google Pay merchant ID.
- Пристрій, браузер і гаманець клієнта підтримують цей спосіб оплати.
Передайте wallets із сумою оформлення замовлення в мінімальних одиницях. Tapaya показує доступні способи оплати, а непідтримувані чи
вимкнені гаманці залишаються прихованими. Використовуйте цей приклад замість прикладу createPayment() лише для карток:
const payment = tapaya.createPayment({
prepare: '/api/payments',
wallets: { amount: order.totalInMinorUnits, currency: order.currency },
onWalletClick: async () => {
if (!form.reportValidity()) throw new Error('Complete the checkout details.')
// Validate the cart and reserve the order on your server.
const response = await fetch('/api/payments', { method: 'POST' })
if (!response.ok) throw new Error('Could not prepare the payment.')
return response.json() // { clientSecret, status }
},
onChange: (snapshot) => {
button.hidden = snapshot.paymentMethod !== 'card'
button.disabled = !snapshot.canSubmit
if (snapshot.outcome === 'completed') location.assign('/checkout/confirmation')
},
})
await payment.mount('#card-fields')Сума, що відображається в браузері, має надходити з вашого сервера, який однаково обчислює та валідує суму, коли готує платіж.
Натискання кнопок гаманців
Офіційна кнопка гаманця відкриває нативне платіжне вікно гаманця після натискання клієнтом. onWalletClick валідує ваше
оформлення замовлення та резервує його платіж, коли натиснуто цю кнопку, ніколи під час розміщення полів чи вибору способу оплати. Без цієї
функції callback гаманці використовують prepare або clientSecret. Якщо ви резервуєте замовлення лише після натискання, подбайте, щоб функція callback
prepare, яка викликається під час розміщення, продовжувала наявне замовлення, не створюючи нового.
Залишайте власну кнопку оплати видимою лише тоді, коли snapshot.paymentMethod має значення card; гаманці використовують свої фірмові кнопки
замість payment.submit(). Результати оплати гаманцем проходять той самий процес перевірки та відновлення, що й картки. Закриття
платіжного вікна гаманця залишає оформлення замовлення доступним; невизначений платіж залишається в очікуванні й блокує повторне стягнення оплати.
Оновлення суми
Передавайте wallets: { amount, currency, disabled: true }, поки обов'язкові дані оформлення замовлення не заповнено. Оновлюйте
суму або вмикайте кнопки без повторного розміщення:
payment.update({ wallets: { amount: 2450, currency: 'EUR', disabled: false } })Передайте wallets: null, щоб приховати гаманці, доки сума не буде готова. Tapaya відмовляє в підтвердженні, якщо підготовлений платіж
відрізняється від суми в гаманці. React-хук usePayment() приймає ці параметри й оновлює суму, коли
змінюється wallets.
Власний вибір способу оплати
Якщо вибір способу оплати реалізує ваша сторінка, передайте paymentMethod: 'card', 'apple_pay' або 'google_pay', щоб відобразити
лише цей спосіб. Недоступний вибраний гаманець показує напис walletUnavailable і повідомляє
snapshot.error.code: 'wallet_unavailable', коли можна безпечно почати інший платіж; запропонуйте у своєму
виборі інший спосіб оплати. Блокуйте вибір, поки snapshot.busy має значення true або платіж має стан pending чи action_required.
Фрейми та CSP
SDK додає до свого фрейму allow="payment". Якщо ваш магазин розміщено в іншому фреймі, цей зовнішній фрейм також має дозволяти
оплату. Apple Pay між різними origin потребує Safari 17 або новішої версії. Скрипти провайдерів гаманців залишаються у фреймі Tapaya, тож
вашій сторінці не потрібні додаткові origin провайдерів у CSP.
Обробка простроченого доступу браузера
Термін дії client secret спливає через 24 години, і тоді clientSecret має значення null. Платіж, який так і не було надіслано,
тоді скасовується, а наступний prepare() починає нову спробу для того самого замовлення. Платіж в очікуванні залишається pending
у браузері, а SDK продовжує викликати ваш ендпоінт, доки ваш сервер не повідомить його результат як status. Читання на сервері
й далі працює, тож ваше заплановане завдання в будь-якому разі його завершить.
Діагностика серверних помилок
Node SDK викидає PaymentsApiError для відхилених запитів до API. Перевіряйте його status, code, message,
fieldErrors і requestId на своєму сервері. Не допускайте потрапляння облікових даних і client secret до журналів і показуйте власний
текст для клієнтів.
Відповідність вашому брендингу
Поля картки використовують чинний колір Checkout мерчанта з налаштувань
Checkout як акцент фокуса (colorFocus).
Значення, які ви передаєте в appearance.variables, його перевизначають, і надсилаються лише задані вами змінні.