Skip to content

Оплата на React ​

English

@checkout-kit/react подключает платёжный движок к приложению на React 19. Он не заменяет API вашего сервера и ваши поля формы. Необязательный UI-кит даёт готовое оформление, но ваша собственная дизайн-система так же хорошо работает с теми же хуками.

Установка из npm ​

sh
npm install @checkout-kit/core @checkout-kit/runtime-browser @checkout-kit/provider-paypal @checkout-kit/react @checkout-kit/ui react@^19 react-dom@^19

Пакет в npm · Все варианты установки

Полный небольшой пример ​

Скопируйте два файла в свой проект под именами quickstart-engine.ts и quickstart.tsx. Это настоящие файлы из репозитория, их проверяет сборка TypeScript, поэтому они всегда рабочие. Ещё понадобятся маршруты PayPal на вашем сервере. Создайте движок один раз в браузере и запустите приложение этим кодом.

Настройка движка:

ts
import { createCheckout, defineProvider } from '@checkout-kit/core'
import { createBrowserRuntime } from '@checkout-kit/runtime-browser'
import type { PayPalConfig } from '@checkout-kit/provider-paypal'

/** Call once in the browser. baseUrl points at the merchant's PayPal API. */
export const createPayPalCheckout = (baseUrl = '/api', returnPath = '/checkout/return') => {
  const runtime = createBrowserRuntime({
    returnPath,
    security: { allowInsecureLocalhost: import.meta.env.DEV },
  })
  const engine = createCheckout({
    providers: [
      defineProvider({
        id: 'paypal',
        config: { baseUrl, credentials: 'include' } satisfies PayPalConfig,
        load: () => import('@checkout-kit/provider-paypal'),
      }),
    ],
    runners: runtime.runners,
    storage: runtime.storage,
    returnUrl: runtime.returnUrl,
    defaultProviderId: 'paypal',
  })
  return {
    engine,
    runtime,
    async pay(planId: string) {
      // Recovery retains the original intent/key if the merchant API was unavailable.
      const restored = await engine.hydrate(runtime.readReturnParams())
      return restored ?? engine.pay({ input: { planId }, instrument: { kind: 'none' } })
    },
  }
}

Экран на React:

tsx
import type { CheckoutEngine, PaymentResult } from '@checkout-kit/core'
import { CheckoutProvider, CheckoutRoot, PaymentActionHost, useCheckout } from '@checkout-kit/react'
import { Button, Money, Panel } from '@checkout-kit/ui'
import '@checkout-kit/ui/styles.css'

type Pay = (planId: string) => Promise<PaymentResult>

function Payment({ planId, pay }: { planId: string; pay: Pay }) {
  const { engine, phase, intent, error, isBusy, isLocked, isSettled } = useCheckout()
  const hasFinalOutcome = intent && ['succeeded', 'declined', 'canceled'].includes(intent.status)
  return (
    <Panel title="Checkout">
      {intent && <Money amount={intent.amount} currency={intent.currency} />}
      <p role="status">{phase}</p>
      {error && <p role="alert">{error.message}</p>}
      <Button busy={isBusy} disabled={isLocked} onClick={() => void pay(planId)}>
        Pay with PayPal
      </Button>
      {phase === 'failed' && (
        <Button variant="secondary" onClick={() => void pay(planId)}>
          Retry the same payment
        </Button>
      )}
      {isSettled && hasFinalOutcome && (
        <Button variant="secondary" onClick={() => engine.reset()}>
          Start over
        </Button>
      )}
      <PaymentActionHost />
    </Panel>
  )
}

export function PayPalCheckout({
  engine,
  planId,
  pay,
}: {
  engine: CheckoutEngine
  planId: string
  pay: Pay
}) {
  return (
    <CheckoutProvider engine={engine}>
      <CheckoutRoot theme="auto">
        <Payment planId={planId} pay={pay} />
      </CheckoutRoot>
    </CheckoutProvider>
  )
}

Как работает пример:

  • цену назначает сервер по planId, а Money только показывает её;
  • PaymentActionHost выполняет шаг подтверждения в PayPal;
  • если результат неясен (например, оборвалась сеть), кнопка «попробовать снова» повторяет восстановление или ту же попытку, поэтому деньги не спишутся дважды;
  • кнопка «начать заново» появляется только тогда, когда у платежа подтверждён окончательный статус.

Сколько живёт движок ​

Создавайте один движок на одну оплату и вне отрисовки (не в теле компонента). Если создавать новый движок при каждой отрисовке, теряются подписки и платёж, который уже идёт.

Пока шаг выполняется, PaymentActionHost должен оставаться на экране. Если он пропадёт, шаг остановится.

  • CheckoutProvider передаёт движок хукам внутри себя.
  • CheckoutRoot добавляет CSS-тему, плотность и атрибуты платформы.

Если вместо @checkout-kit/ui у вас свой интерфейс, нужен только CheckoutProvider.

Читайте только нужное состояние ​

ХукЧто возвращает
useCheckoutEngine()Команды движка. Компонент не перерисовывается при смене состояния.
useCheckoutSnapshot()Текущее состояние: фаза, платёж, шаг, ошибка, провайдер и его возможности.
useCheckout()Состояние плюс движок и флаги isBusy, isSettled и isLocked.
useCheckoutSelector(selector, isEqual?)Только выбранную вами часть. Перерисовка — только когда эта часть меняется. Можно передать свою функцию сравнения.
usePaymentState({ isDirty, isValidating })Состояние, которое учитывает и вашу форму (изменена, идёт проверка).
tsx
import { useCheckoutSelector } from '@checkout-kit/react'
const total = useCheckoutSelector(
  (s) => ({ amount: s.intent?.amount, currency: s.intent?.currency }),
  (a, b) => a.amount === b.amount && a.currency === b.currency,
)

Правила и подводные камни:

  • селектор должен только читать данные, без побочных эффектов;
  • isSettled значит «движок остановился». Сюда входят и ошибки, и неясный результат. Это не значит «оплачено»;
  • isLocked тоже равен true в окончательных состояниях. Не отключайте кнопку повтора навсегда только по нему — добавьте своё условие для восстановления или новой попытки.

Дополнительные шаги и восстановление ​

  • PaymentActionHost сам запускает шаг (autoRun включён по умолчанию).
  • surface позволяет выбрать, где показывать шаг, если провайдер это поддерживает.
  • onSettled — только для интерфейса: показать сообщение, перейти на другую страницу. Выдача заказа — задача сервера.
  • При autoRun={false} вызывайте engine.runPendingAction сами и передавайте место для показа (mount).

Компонент не даёт запустить один и тот же шаг дважды, даже если провайдер присылает несколько шагов подряд с одинаковым id.

Компоненты React и UI можно рендерить на сервере (SSR), но браузерный runtime создавайте только в браузере. См. работа в браузере и решение проблем.

Apache-2.0 · Headless-движок, адаптеры провайдеров и запускаемые примеры.