@bemony/payment-form-react

中文文档
0.2.0发布于 2026/11/11TypeScriptES Module近 30 天下载 3503

Official React adapter for the hosted Bemony Payment Form.

原始使用文档
文档目录19 节
  1. Requisitos
  2. Instalação
  3. Preparação no backend
  4. Integração com ref
  5. Integração com Provider e hook
  6. Props
  7. Bandeiras de cartão habilitadas
  8. Versões da API
  9. Ambientes
  10. Contrato de pay()
  11. Fallback de client por campo
  12. amount na versão enriquecida
  13. Estados e erros
  14. Remontagem e React Strict Mode
  15. Next.js e SSR
  16. Aparência
  17. Risk collectors e CSP
  18. Migração e rollout
  19. Regras de segurança

@bemony/payment-form-react

Adaptador React oficial do @bemony/payment-form.

O pacote monta a mesma instância segura do SDK Web e oferece duas formas de integração:

  • ref, para checkouts em que formulário e botão estão próximos;
  • BemonyPaymentFormProvider + useBemonyPaymentForm, para compartilhar estado e pay() entre componentes.

O adaptador não realiza requests financeiros diretamente, não contém regras próprias de pagamento e não duplica o estado canônico do SDK Web.

Requisitos

  • React >=19 <20;
  • React DOM >=19 <20;
  • CheckoutSession criada no backend;
  • origem do checkout cadastrada na Bemony;
  • CSP compatível com os frames e collectors utilizados.

Instalação

pnpm add @bemony/payment-form-react

@bemony/payment-form é instalado como dependência do adaptador. Não é necessário montar os dois pacotes separadamente.

Preparação no backend

Crie a CheckoutSession no servidor usando a Secret Key. O navegador deve receber somente o sessionClientSecret e as instruções públicas de riskCollectors.

const response = await fetch(
  `${process.env.BEMONY_ORCHESTRATOR_API_BASE_URL}/v1/checkout-sessions`,
  {
    method: 'POST',
    headers: {
      authorization: `Bearer ${process.env.BEMONY_SECRET_KEY}`,
      'content-type': 'application/json',
      'idempotency-key': crypto.randomUUID(),
      'x-bemony-api-version': '2026-11-02'
    },
    body: JSON.stringify({
      mode: 'CREATE',
      domainId: 'cdom_...',
      checkout: { checkoutConfigId: 'ccfg_...' }
    })
  }
);

if (!response.ok) throw new Error('Não foi possível criar a CheckoutSession.');
const session = await response.json();

Nunca coloque BEMONY_SECRET_KEY em variável pública do React, bundle do navegador ou propriedade do componente.

Integração com ref

Use esta forma quando o botão que chama pay() pertence ao mesmo componente.

'use client';

import {
  BemonyPaymentForm,
  PAYMENT_FORM_API_VERSION,
  type BemonyPaymentFormRef,
  type PayInput,
  type PaymentFormError,
  type RiskCollector
} from '@bemony/payment-form-react';
import { useRef, useState } from 'react';

type CheckoutProps = Readonly<{
  sessionClientSecret: string;
  riskCollectors?: readonly RiskCollector[];
}>;

export function Checkout({ sessionClientSecret, riskCollectors }: CheckoutProps) {
  const formRef = useRef<BemonyPaymentFormRef>(null);
  const [ready, setReady] = useState(false);
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState<PaymentFormError | null>(null);

  const input: PayInput = {
    merchantOrderId: 'order_123',
    merchantReference: 'merchant-reference-123',
    currency: 'BRL',
    operation: 'PURCHASE',
    customer: {
      firstName: 'Ana',
      lastName: 'Silva',
      email: '[email protected]'
    },
    items: [
      {
        id: 'item_123',
        sku: 'SKU-123',
        description: 'Produto',
        unitAmount: 10990,
        quantity: 1,
        type: 'ONE_TIME'
      }
    ],
    amount: 10990,
    context: { funnelId: 'checkout', funnelStep: 'payment', channel: 'WEB' }
  };

  async function submit() {
    setSubmitting(true);
    setError(null);
    try {
      const payment = await formRef.current!.pay(input);
      console.log(payment.paymentId, payment.status);
    } catch (cause) {
      setError(cause as PaymentFormError);
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <section>
      <BemonyPaymentForm
        ref={formRef}
        clientSecret={sessionClientSecret}
        environment="sandbox"
        version={PAYMENT_FORM_API_VERSION.TOKEN_INTENT}
        riskCollectors={riskCollectors}
        threeDS={{ presentation: 'modal' }}
        appearance={{ theme: 'system' }}
        onReady={() => setReady(true)}
        onStatusChange={(status) =>
          setSubmitting(
            ['TOKENIZING', 'SUBMITTING', 'AUTHENTICATING_3DS', 'PROCESSING'].includes(status)
          )
        }
        onError={setError}
      />

      {error && <p role="alert">{error.message}</p>}
      <button type="button" disabled={!ready || submitting} onClick={() => void submit()}>
        {submitting ? 'Processando…' : 'Pagar'}
      </button>
    </section>
  );
}

Integração com Provider e hook

Use esta forma quando o botão, resumo ou tratamento de estado está em outro componente. O Provider é a autoridade da configuração; não repita clientSecret, environment ou version no componente filho.

'use client';

import {
  BemonyPaymentForm,
  BemonyPaymentFormProvider,
  PAYMENT_FORM_API_VERSION,
  useBemonyPaymentForm,
  type PayInput,
  type RiskCollector
} from '@bemony/payment-form-react';

function PayButton({ input }: Readonly<{ input: PayInput }>) {
  const { ready, state, error, pay } = useBemonyPaymentForm();

  return (
    <>
      {error && <p role="alert">{error.message}</p>}
      <button type="button" disabled={!ready || state.isSubmitting} onClick={() => void pay(input)}>
        {state.isSubmitting ? 'Processando…' : 'Pagar'}
      </button>
    </>
  );
}

type CheckoutWithProviderProps = Readonly<{
  session: Readonly<{
    sessionClientSecret: string;
    riskCollectors?: readonly RiskCollector[];
  }>;
  input: PayInput;
}>;

export function CheckoutWithProvider({ session, input }: CheckoutWithProviderProps) {
  return (
    <BemonyPaymentFormProvider
      clientSecret={session.sessionClientSecret}
      environment="sandbox"
      version={PAYMENT_FORM_API_VERSION.TOKEN_INTENT}
      riskCollectors={session.riskCollectors}
      threeDS={{ presentation: 'modal' }}
    >
      <BemonyPaymentForm className="payment-form" />
      <PayButton input={input} />
    </BemonyPaymentFormProvider>
  );
}

useBemonyPaymentForm() retorna:

CampoTipoUso
readybooleanformulário pronto para receber pay()
statePaymentFormStateestado, envio, resultado e erro atuais
errorPaymentFormError | nullúltimo erro público
pay(input: PayInput) => Promise<PayOutcome>inicia ou compartilha a operação ativa

O hook só pode ser usado dentro de BemonyPaymentFormProvider.

Props

PropTipoPadrãoDescrição
clientSecretstringobrigatóriosegredo público da CheckoutSession
environment'sandbox' | 'production' | 'production_aws'sandboxinfraestrutura hospedada
version'2026-09-25' | '2026-10-20' | '2026-10-30'2026-10-30compatibilidade do protocolo hospedado
riskCollectorsreadonly RiskCollector[]ausenteinstruções públicas devolvidas pela sessão
appearancePaymentFormAppearancesistematema e variáveis visuais
threeDS{ presentation?: 'modal' | 'inline' }modalapresentação do challenge
classNamestringausenteclasse aplicada ao container React
onReady() => voidausenteiframe e Card Frame prontos
onCardStateChange(state: CardState) => voidausentecompletude e validade dos campos
onStatusChange(status) => voidausentetransição do estado público
onSuccess(payment) => voidausentepagamento AUTHORIZED ou CAPTURED
onError(error) => voidausentemesmo erro público rejeitado por pay()
onEvent(event) => voidausentetelemetria segura de montagem/pagamento

No Provider, children também é obrigatório. No componente standalone, clientSecret é obrigatório; dentro do Provider, a configuração vem do contexto.

Bandeiras de cartão habilitadas

Não passe uma lista de bandeiras por prop. A CheckoutSession é a autoridade e o Card Frame aplica a configuração. Se uma projeção legada estiver ausente ou inválida, o frame assume apenas VISA e MASTERCARD para preservar esses pagamentos; nenhuma outra rede é habilitada por esse fallback. Quando a bandeira detectada não é aceita, onCardStateChange informa valid: false e nenhum token é criado; desabilite o botão enquanto o estado for inválido.

<BemonyPaymentForm
  clientSecret={sessionClientSecret}
  onCardStateChange={(state) => setCanPay(state.valid)}
/>

Caso pay() seja chamado, capture PaymentFormError.code === 'CARD_BRAND_NOT_SUPPORTED'. É uma falha local, retryable: true, classificada como VALIDATION em CARD_TOKENIZATION; o usuário pode corrigir o cartão sem gerar uma tentativa financeira.

Versões da API

import {
  DEFAULT_PAYMENT_FORM_API_VERSION,
  PAYMENT_FORM_API_VERSION,
  type PaymentFormApiVersion
} from '@bemony/payment-form-react';
VersãoConstanteUso
2026-09-25PAYMENT_FORM_API_VERSION.LEGACYcontrato legado
2026-10-20PAYMENT_FORM_API_VERSION.ENRICHMENTenriquecimento e body completo
2026-10-30PAYMENT_FORM_API_VERSION.TOKEN_INTENTToken Intent/CVV

O padrão do protocolo hospedado é DEFAULT_PAYMENT_FORM_API_VERSION, atualmente 2026-10-30. O tipo é fechado e o valor é validado novamente pelo SDK e pelo hosted BFF. A versão enviada pelo BFF à API é definida pelo deploy via BEMONY_API_VERSION.

A prop version controla somente o handshake entre o adaptador, o SDK e o formulário hospedado. Ela não substitui o header HTTP usado pelo backend para criar a CheckoutSession.

Ambientes

  • sandbox: padrão compatível atual conectado à Railway;
  • production: produção legada hospedada na Vercel;
  • production_aws: produção candidata hospedada na AWS.

O componente nunca recebe endpoint ou origem arbitrária. A configuração dos hosts pertence aos deploys Bemony.

Contrato de pay()

O adaptador reexporta os mesmos tipos do SDK Web:

import type {
  Address,
  CardStorageConsent,
  CheckoutClient,
  CommerceContext,
  Customer,
  CustomerHistory,
  PayInput,
  PayOutcome,
  PaymentAmounts,
  PaymentItem,
  ShippingAddress
} from '@bemony/payment-form-react';

PayInput suporta pedido, customer, endereços, consentimento, itens, impostos/descontos, total de integridade, contexto comercial, URLs, metadata e contexto do client. Sessão, token, PAN, CVC, IP, User-Agent observado e evidência 3DS não pertencem ao input público.

Fallback de client por campo

Se o integrador enviar somente um campo de client, apenas esse campo substitui o valor observado. Os demais continuam sendo preenchidos pelo SDK quando disponíveis. A mesma regra vale dentro de client.utm e client.browserInfo; um objeto parcial nunca apaga o restante do contexto.

amount na versão enriquecida

amount é um eco opcional do total calculado pelo Runtime. Ele não define o valor cobrado. Uma divergência retorna 422 AMOUNT_MISMATCH.

publicCheckoutUrl está tipado para a evolução do Runtime, mas não deve ser enviado antes de o ambiente de destino publicar suporte ao campo.

Estados e erros

MOUNTING → READY → TOKENIZING → SUBMITTING → PROCESSING
                                      ├→ AUTHENTICATING_3DS → PROCESSING
                                      ├→ AUTHORIZED/CAPTURED
                                      ├→ DECLINED/FAILED/EXPIRED
                                      └→ UNKNOWN

Trate a rejeição de pay() mesmo quando onError estiver configurado:

try {
  const result = await formRef.current!.pay(input);
  // Não faça fulfillment apenas com o resultado do browser.
  console.log(result.paymentId);
} catch (cause) {
  const error = cause as PaymentFormError;
  console.error(error.code, error.requestId, error.details);
}
  • requestId correlaciona a resposta do Runtime;
  • commandId identifica o comando local;
  • retryable: true permite uma nova tentativa controlada, não replay automático;
  • PAYMENT_STATUS_UNKNOWN exige consulta/webhook antes de qualquer nova cobrança;
  • payment.transaction.attempts é diagnóstico público, não fonte de fulfillment.

O produto consumidor escolhe a mensagem apresentada ao comprador. O Embed preserva o erro público da API e não reinterpreta o domínio financeiro.

Remontagem e React Strict Mode

O adaptador adia a montagem de forma cancelável para suportar o ciclo especulativo do React Strict Mode sem deixar dois iframes ativos.

Uma alteração real em clientSecret, environment, version, apresentação do 3DS, appearance ou riskCollectors remonta o iframe. appearance e riskCollectors são comparados por valor, então objetos equivalentes recriados durante renderizações comuns não provocam remontagem.

Callbacks são lidos pela referência mais recente e não exigem desmontar a instância quando apenas a função muda.

Next.js e SSR

Use o componente a partir de uma fronteira Client Component:

'use client';

import { BemonyPaymentForm } from '@bemony/payment-form-react';

Crie a CheckoutSession em Route Handler, Server Action protegida ou backend próprio. Envie ao Client Component apenas sessionClientSecret e dados públicos da sessão. Nunca use uma variável NEXT_PUBLIC_* para Secret Key ou token interno.

Aparência

<BemonyPaymentForm
  clientSecret={clientSecret}
  appearance={{
    theme: 'dark',
    variables: {
      colorPrimary: '#00e68a',
      colorBackground: '#101513',
      colorText: '#ffffff',
      colorDanger: '#ff5b6e',
      colorBorder: '#33413a',
      borderRadius: '12px',
      fontFamily: 'Inter, sans-serif'
    },
    threeDS: { modalBackdrop: 'rgba(0, 0, 0, 0.72)' }
  }}
/>

Risk collectors e CSP

Passe riskCollectors diretamente da CheckoutSession ao componente ou Provider. Collector lento, bloqueado ou indisponível não bloqueia o pagamento e não deve ser persistido pelo checkout.

Durante a transição atual, use uma política consolidada que preserve as fontes exigidas pelo seu site e permita os hosts Bemony e os collectors habilitados:

default-src 'self';
frame-src https://form.bemony.com https://bemony-payment-form.vercel.app https://bemony-payment-form-sandbox.vercel.app https://embed-payment-orchestration.bemony.com;
script-src 'self' https://js.stripe.com https://tst.kaptcha.com https://ssl.kaptcha.com;
connect-src 'self' https://form.bemony.com https://bemony-payment-form.vercel.app https://bemony-payment-form-sandbox.vercel.app https://embed-payment-orchestration.bemony.com https://api.stripe.com https://r.stripe.com https://m.stripe.com https://tst.kaptcha.com https://ssl.kaptcha.com;

Migração e rollout

  1. publique primeiro o hosted Payment Form compatível com apiVersion;
  2. valide o hosted usando a versão antiga do pacote;
  3. publique Web e React com tag latest;
  4. atualize um consumidor de teste explicitamente para 0.2.0;
  5. valide legado, enriquecimento, Token Intent, 3DS e reconciliação.

Omitir version usa 2026-10-30 no handshake. Consumidores antigos não mudam de pacote nem recebem a nova implementação até atualizarem sua dependência.

Regras de segurança

  • não envie Secret Key, token interno, PAN, CVC ou evidência 3DS por props;
  • não registre clientSecret, token do cartão ou valor bruto de collectors;
  • não permita que o browser escolha endpoints privados;
  • não libere pedido apenas com onSuccess; confirme por webhook ou consulta autenticada;
  • não implemente tokenização, 3DS ou retry financeiro fora do SDK.

版本与发布记录

@bemony/payment-form-react 共发布 25 个版本,首次发布于 2026/10/08;过去 12 个月发布了 25 个版本。

版本发布日期距今
0.2.0latest2026/11/11今天
0.2.0-next.4next2026/11/10今天
0.2.0-next.32026/10/3111 天前
0.2.0-next.22026/10/3111 天前
0.2.0-next.12026/10/2022 天前
0.2.0-next.02026/10/1625 天前

关于 @bemony/payment-form-react 的常见问题

@bemony/payment-form-react 是什么?

Official React adapter for the hosted Bemony Payment Form.

如何安装 @bemony/payment-form-react?

在项目目录运行 npm install @bemony/payment-form-react,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=19.0.0 <20.0.0)、react-dom(>=19.0.0 <20.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@bemony/payment-form-react 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bemony/payment-form-react 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@bemony/payment-form-react 可以商用吗?

@bemony/payment-form-react 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@bemony/payment-form-react 的下载量有多少?

根据 npm 官方下载统计,@bemony/payment-form-react 在 2026/10/08 – 2026/11/06 这 30 天内被下载 3,503 次,日均约 117 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bemony/payment-form-react 还在维护吗?

最新版本 0.2.0 发布于 2026/11/11(今天);至今共发布 25 个版本,其中过去 12 个月发布了 25 个;首次发布于 2026/10/08。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。