关于 integrate-hosted-fields
面向开发者的 Payroc Hosted Fields 集成指导,涵盖无卡支付流程。它依据本地 API 架构、指南和 SDK 源码推进实现,并要求通过 UAT 检查点验证。
主要功能
- 01引导配置 Hosted Fields 和销售流程
- 02提供令牌化与定期扣款指导
- 03提供本地 API 架构和错误参考
- 04核对 SDK 配置与方法
- 05设置 UAT 检查点并指导排错
适用场景
- 01在结账页嵌入 Payroc 卡片字段
- 02保存支付信息以便后续扣款
- 03使用已保存卡片实现定期扣款
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ payroc/ skills --skill integrate-hosted-fields安装后在 Claude Code 中输入/integrate-hosted-fields确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/_sources.md3-d-secure.mdadd-your-own-fields.mdapi-schema.mdauthenticate-your-session.mdclose-a-session.mdcreate-a-payment-form.mderror-response-format.mdhosted-fields-sdk.jsidempotency.mdidentity-call.mdpagination.mdrecurring-billing.mdrun-a-card-sale-guide.mdrun-a-sale-with-3-d-secure.mdrun-a-sale.mdsave-a-customers-payment-details.mdsecure-tokens-api-schema.mdstyle-your-fields.mdupdate-a-customers-payment-details.mdupdate-saved-payment-details-guide.md详细介绍
Payroc Hosted Fields 集成
范围:CNP(无卡支付)Hosted Fields——一次性销售交易、令牌化(保存支付详情)和定期扣款。
版本检查(先运行此项)
在宣布任何内容或开始流程前,请确认此技能为最新版本:
- 从上方 frontmatter 的
metadata.version字段读取此技能的版本。 - 获取已发布的副本并读取其
metadata.version:https://raw.githubusercontent.com/payroc/skills/main/plugins/payroc/transaction/skills/integrate-hosted-fields/SKILL.md - 将两者作为语义化版本进行比较:
-
此版本 >= 已发布版本 → 静默继续,不发送消息。(开发者运行尚未发布的较新版本是预期情况,且没有问题。)
-
此版本 < 已发布版本 → 告知开发者:
⚠️ 此技能已有更新版本(v<published>)发布——你当前运行的是 v<current>。建议升级以获得最佳效果。
然后询问他们是要继续使用当前版本,还是先停止并升级,并遵从他们的选择。
-
无法获取(离线、网络错误、404)→ 简要说明无法验证版本,然后继续。
-
首次调用时,向开发者宣布:
Payroc Hosted Fields 集成 我将引导你完成从凭证设置到成功进行 UAT 测试交易的全过程。 本指南涵盖通过 Payroc Hosted Fields 进行的无卡支付——Payroc 托管的卡片输入控件会直接嵌入你自己的结账页面。
Hosted Fields 的工作方式(三个部分):
- 你的服务器调用 Payroc API 生成会话令牌——该令牌授权 JS 库呈现安全的卡片字段
- Payroc 的 JS 库会将 iFrame 卡片输入字段注入你的页面;客户无需离开你的网站
- 提交时,该库会将卡片详情令牌化,并向你提供一个一次性令牌;你的服务器使用该令牌调用 Payroc 支付 API 并处理扣款
**Hosted Fields 与 Hosted Payment Pages 的区别:**如果你需要基于重定向的流程,让客户短暂离开你的网站并在 Payroc 托管的页面上付款,那就是 Hosted Payment Pages——这是本技能不涵盖的另一款产品。
[如果有 MCP 连接检查工具,请在此处运行,并在继续之前展示结果。]
快速参考
# Identity service (Bearer token)
POST https://identity.uat.payroc.com/authorize
x-api-key: <api-key>
# Session token
POST https://api.uat.payroc.com/v1/processing-terminals/{processingTerminalId}/hosted-fields-sessions
Authorization: Bearer <token>
Idempotency-Key: <uuid-v4>
Content-Type: application/json
# Payments API
POST https://api.uat.payroc.com/v1/payments
Authorization: Bearer <token>
Idempotency-Key: <uuid-v4>
Content-Type: application/json
参考资料
所有来源都位于下方本地 references/ 文件中——此技能基于这些文件生成内容,而非实时查询或记忆。这些本地快照是此技能的权威来源;其来源 URL 和上次同步日期记录在 references/_sources.md 中。这里有三种互补的来源,各自负责解答不同类型的问题——用错来源是已知的失误模式:
- API 架构参考(
references/api-schema.md)——Payroc API 传输数据的值和结构的唯一真实来源。 字段名称、枚举值、必填字段、请求和响应架构。需要确认 Payroc API 字段的名称 或 它接受哪些值 时,请查阅此文件。它是 OpenAPI 规范中经过整理的部分。 - 叙述性指南(
references/<page>.md)——实施方法的唯一真实来源。 如何安排调用顺序、端到端流程是什么样的、JS 库如何配合使用。使用这些指南了解 如何组合 集成步骤。指南有时会对具体的 SDK 结构表述得不够确定——遇到这种情况时,请查阅第三种来源。 - SDK JS 文件(
references/hosted-fields-sdk.js)——SDK 配置结构、接受的子项键以及公共 API 方法的唯一真实来源。 此本地快照与放入<script>标签中的 JS 相同。SDK 会遍历fields.card(以及其他字段)中的固定键列表,并静默忽略其他内容;它会在hostedFields实例上公开固定列表中的方法。叙述性指南对具体的放置位置和命名可能含糊不清(已知的文档门户问题,且此前运行的反馈也证实了这一点)——SDK 源码则不会。需要验证属性名称、接受的子项键或方法名称时,请以文本形式阅读源码。
如果你的问题是“如何构造请求正文?”——请查阅架构参考。如果你的问题是“这些调用应该按什么顺序进行?”——请查阅指南。如果你的问题是“new Payroc.hostedFields({...}) 实际接受什么?”或“cardForm.<method> 是否存在?”——请查阅 SDK 源码。
| 来源 | 本地文件 |
|---|---|
| API 架构参考(枚举、必填字段、请求/响应架构) | references/api-schema.md |
| 错误响应格式(RFC 7807 信封 + Payroc errors[] + 规范错误类型目录) | references/error-response-format.md |
| 步骤 1 — 验证会话 | references/authenticate-your-session.md |
| 步骤 2 — 创建支付表单 | references/create-a-payment-form.md |
| 步骤 3 — 执行销售 | references/run-a-sale.md |
| 扩展 — 设置字段样式 | references/style-your-fields.md |
| 扩展 — 添加自定义字段 | references/add-your-own-fields.md |
| 扩展 — 关闭会话 | references/close-a-session.md |
| 扩展 — 保存客户的支付详情 | references/save-a-customers-payment-details.md |
| 扩展 — 更新客户的支付详情 | references/update-a-customers-payment-details.md |
| 扩展 — 定期计费 | references/recurring-billing.md |
| 3-D Secure — 概述 | references/3-d-secure.md |
| 3-D Secure — 使用 3DS 执行销售(实施流程) | references/run-a-sale-with-3-d-secure.md |
| SDK JS 文件(SDK 配置结构和公共 API 方法名称的权威来源——请以文本形式阅读) | references/hosted-fields-sdk.js — 与通过 <script> 加载的 JS 相同 |
本地快照的来源 URL 和上次同步日期见 references/_sources.md——如果内容看起来已过时,请从该文件重新生成。
核心原则
- 先检查,再提问 — 提问前先阅读代码库;根据发现跳过显而易见的问题,并提出有针对性的问题。
- 先提问,再编写代码 — 提出初步了解问题,并等待开发者回答后再编写任何实现代码。一边提问一边提供代码并“假设”答案,会绕过这道关卡;初步了解是一个决策节点,而不是开场白。
- 先验证,再推进 — 开发者确认当前步骤的检查点已通过 UAT 后,才能进入下一步。
- 阅读本地参考资料,不要猜测;并根据问题选择合适的资料。 资料来源有 三 种,而不是两种。针对问题使用错误的资料,记忆中看似正确、实则错误的值就会混入答案。
- 架构、字段名称、枚举、必填字段 →
references/api-schema.md。 任何通过网络传输到 API 的内容都属于此类。最常见的名称包括:paymentRequest、payment(响应)、transactionResult、card、secureTokenSummary、schemas-credentialOnFile、standingInstructions、firstTxnReferenceData、hostedFieldsCreateSessionRequest、HostedFieldsCreateSessionRequestScenario、StandingInstructionsSequence、StandingInstructionsProcessingModel。在架构参考中按名称查找,比在说明文档中逐一搜索结构更快。 - 实现方式、调用顺序、库的接入方式 → 说明指南(
references/<page>.md)。“应该先调用哪个接口?”和“如何接入 JS 库?”属于指南问题,而不是架构问题。 - SDK 配置结构、接受的子键、公共 API 方法名称 → SDK JS 文件本身(
references/hosted-fields-sdk.js)。此处的本地快照与通过<script>加载的 JS 相同。SDK 会根据固定的遍历列表验证fields.card[key],并静默忽略无法识别的键;它会在hostedFields实例上公开一组固定方法。说明文档在具体位置和方法命名方面存在模糊之处,但 SDK 源码不会。需要确认属性名称、fields.card中接受的子键或cardForm上的方法时,请阅读 JS 源码。 - 请求和响应架构并不相同——两者都要查阅。 不要假设响应会与请求保持一致。对于 Hosted Fields,响应使用
card(而非paymentMethod);cardSchemeReferenceId位于transactionResult上,而不是卡片上。根据请求名称推断响应内容是一个已知的常见陷阱。 - JS 库的 CDN URL、SRI 哈希和
libVersion都使用四段式版本号。 不要凭记忆填写版本号——请从references/create-a-payment-form.md(以及references/hosted-fields-sdk.js中的快照)读取,并使用完整的四段式版本号(例如,UAT 中为1.7.0.261457,生产环境中为1.7.0.261471)。UAT 和生产环境发布的版本可能不同。
- 架构、字段名称、枚举、必填字段 →
- 逐项查阅后再输出。 输出任何枚举值(例如
channel、scenario、paymentMethod.type、transactionResult.status、standingInstructions.sequence、processingModel、secCode、accountType)、SDK 配置属性或接受的子键,或cardForm上的任何方法调用前,请先查阅相应的本地参考资料。不要把此技能中列出的失败模式(参见步骤 3、步骤 4、错误分类和反模式)视为权威来源——列出这些内容是为了让你能从症状识别错误,而不是让你跳过参考资料。枚举值应从references/api-schema.md中读取,不要凭记忆填写。最常见的这类错误,是使用看似符合英语习惯、实则并不存在的枚举值(例如用"internet"作为channel,或把"approved"当作唯一的成功状态)。 - 绝不硬编码凭据 — API 密钥和终端 ID 必须来自环境变量或密钥管理器,不得写入源代码。
- 先诊断,再继续 — 如果某一步失败,请暂停并按照错误分类排查后再继续。
- 会话令牌按会话生成 — 每个结账会话都要生成一个新的会话令牌。10 分钟的有效期是有意设置的;切勿在不同用户或请求之间缓存令牌。
信息收集
首先,扫描代码库。 查找:
- 服务端使用的语言和框架
- 现有的结账、订单或支付相关路由和处理程序
- 任何现有的 HTTP 客户端基础设施(HttpClient、fetch、axios、RestClient 等)
- 是否存在部分 Hosted Fields 实现(会话令牌调用、Payroc JS 库引用)
- 环境变量或配置的管理方式
- 是否存在可扩展的
PaymentService或类似服务
用你发现的信息,以开发者自己的术语来组织问题,避免询问可以从现有信息中推断的内容。
然后询问仍需确认的事项。至少确认:
问题 1 — 集成类型:
| Hosted Fields | Hosted Payment Pages | |
|---|---|---|
| 客户体验 | 留在你的页面上 — Payroc 卡片组件嵌入你的表单 | 暂时离开你的网站,在 Payroc 托管页面上付款 |
| 支付执行 | 你的服务端使用一次性令牌调用 Payroc payments API | 提交表单时自动处理;Payroc 会将客户重定向回来 |
| 此技能 | ✓ 此处涵盖 | 独立技能 — 此处不涵盖 |
- Hosted Fields → 继续
- Hosted Payment Pages → 说明两者的区别,并询问是否停止或转而采用其他方案
问题 2 — 交易类型:
- 销售(立即扣款) → 按标准流程继续
- 保存支付详情(令牌化) → 说明这需要
tokenization场景;确认开发者希望将卡片详情存入令牌库以供后续使用 - 两者都需要 → 先实现销售,再将令牌化作为扩展功能添加
- 定期扣款 → 说明这需要先完成令牌化(首次扣款时必须保存卡片,并在后续扣款中重复使用);确认开发者希望实现持续付款指令。到达步骤 4 时,还要阅读
references/recurring-billing.md。
问题 3 — 支付方式:
Hosted Fields 支持 Card、ACH 和 PAD,但每个页面只能使用一种类型。确认开发者需要哪一种。如果不明确,默认使用 Card。
问题 4 — 3-D Secure / SCA:
仅针对 Card 流程询问。ACH 和 PAD 不在 3DS 的适用范围内。
- 不需要 3DS → 按标准 Card 流程继续
- 需要 — 面向英国 / 欧盟客户、受 PSD2 SCA 约束,或商户希望实现责任转移 → 指出以下额外前提条件(Payroc 支持团队必须为终端启用 3DS,并且必须注册 webhook URL);集成流程会在
submissionSuccess和支付之间增加一次 MPI 调用,并在支付请求正文中加入threeDSecure区块。编写任何 3DS 代码之前,先阅读references/run-a-sale-with-3-d-secure.md。 - 不确定 → 如果客户在英国或欧洲经济区,默认选择“需要”。根据 PSD2 SCA,收单机构会软拒绝未经身份验证的交易。
等待开发者回答后再继续。 在四个问题都得到明确确认之前,不要编写实现代码、提供示例或进入“前提条件”部分。答案将完全决定工作范围——目标为 HPP 的开发者需要采用不同流程;ACH 的表单配置与 Card 不同;而 3DS 会增加一项前提条件,必须先联系 Payroc 支持团队启动,之后才能进行端到端代码测试。
前提条件
以下条件是在 UAT 中运行和测试集成所需的,并非编写代码所需。如果开发者已经具备这些条件,那很好。如果还没有,也不要因此停下:让代码从环境变量读取每个值,然后继续构建。开发者可以在测试前填入这些变量。
- API 密钥——用于从 Payroc 的身份服务获取 Bearer 令牌。它不同于 HPP 终端密钥;这是由 Payroc Integrations 团队提供、有效期更长的 API 凭证。
- 处理终端 ID——由 Payroc Integrations 团队提供的 UAT 终端 ID。UAT 是 Payroc 的测试环境;没有自助注册流程。
- 已确认 UAT 访问权限——核心销售流程中的 Hosted Fields 无需配置 Self-Care Portal(无需注册收据 URL,不同于 HPP)。
**如果缺少任何条件——发出提醒,但不要阻止开发。**检查代码库中是否已有环境变量命名惯例,并与之保持一致;否则建议使用 PAYROC_API_KEY 和 PAYROC_TERMINAL_ID 之类的名称。编写代码以从这些变量读取凭证,然后告知开发者还缺少什么以及如何获取:
- API 密钥 / UAT 访问权限 → 联系 Payroc Integrations 团队;测试前设置这些变量。与此同时,我可以继续构建。
- 如果开发者已经有现有 HPP 集成的 Payroc 凭证,那么同一个 API 密钥和处理终端 ID 也适用于 Hosted Fields——这里由 API 密钥生成 Bearer 令牌。请明确说明这一点,让他们知道无需申请新凭证;他们现有环境变量叫什么,就继续使用那些变量。
**如果开发者对问题 4(3DS / SCA)回答了“是”:**再添加两个前提条件,这两项必须开始办理,之后才能端到端测试 3DS:
- 已为终端启用 3DS——Payroc 支持团队会在处理终端上启用 3DS。完成此操作前,MPI 端点(
/merchant/mpi)不会响应。 - 已向 Payroc 支持团队注册 Webhook URL——Payroc 会将 MPI 结果(
mpiReference、result、status、eci)发送到商户提供的 URL。该 URL 必须可从 Payroc 的网络访问,并且必须先向支持团队注册,之后 3DS 销售才能完成。
请开发者现在就发送电子邮件至 [email protected],启动这两项流程,因为它们需要人工处理。构建代码不依赖这两项,可以并行进行;但在两项都完成之前,无法完成端到端 UAT 3DS 交易。
检查点
凭证已确认,或者开发者已了解还缺少什么、如何获取,以及代码会从哪些环境变量读取凭证,并已选择继续。(如果涉及 3DS:是否已发送电子邮件至 [email protected],申请启用终端并注册 Webhook?这些流程可以与开发并行进行。)不要对缺少的事项只字不提,但也不要因此阻止开发。
步骤 1 — 身份验证:获取 Bearer 令牌
阅读:references/authenticate-your-session.md(Hosted Fields 会话令牌流程——步骤 1 和 2)
阅读:references/identity-call.md(Bearer 令牌交换的权威参考——端点 URL、请求头名称、响应结构)
不要猜测身份服务端点 URL、
x-api-key请求头名称或响应字段名称——请阅读references/identity-call.md,并且只输出该文档所述内容。
在编写任何内容之前,先阅读这些页面。根据本地参考资料确认并使用:
- 身份服务端点(测试环境或生产环境)
- 必需的请求头及其格式
- 包含访问令牌的响应字段
- 令牌有效期,以及令牌过期后的处理方式
实现一个服务端函数,使用 API 密钥向身份服务发送 POST 请求并返回访问令牌。该函数必须在服务端运行——绝不能将 API 密钥发送到浏览器。
检查点
调用是否返回状态码为 200 且包含 access_token?如果没有,请先确认 API 密钥值和端点 URL,再继续。
步骤 2 — 创建会话令牌
阅读:references/authenticate-your-session.md(同一页面涵盖这两个身份验证调用)
会话令牌授权 JS 库为特定结账会话呈现字段。请阅读参考文档并确认:
- 会话令牌端点(测试环境与生产环境)
- 必需的请求正文字段:
libVersion、scenario,以及Idempotency-Key标头 libVersion的值 — 不要凭记忆硬编码;请先在步骤 3 中阅读references/create-a-payment-form.md获取该值,然后返回并使用当前值libVersion是一个四段式字符串(例如,UAT 中为1.7.0.261457,生产环境中为1.7.0.261471)。create-a-payment-form页面生成的 CDN URL 中也会出现相同的四段式版本号 — 两者必须匹配。不要将其截断为三段,也不要用记忆中的其他版本替代。references/api-schema.md中hostedFieldsCreateSessionRequest.libVersion的说明也列出了当前生产版本。libVersion因环境而异。 UAT 和生产环境可能会发布不同的四段式版本号。“到处使用相同的值”只在同一环境内成立,不适用于不同环境之间 — 切换到生产环境时,请一并重新查阅生产环境的create-a-payment-form.md中的libVersion、CDN URL 和 SRI 哈希值。切勿沿用 UAT 版本。- 有效的
scenario值来自references/api-schema.md中的HostedFieldsCreateSessionRequestScenario枚举(销售或销售并令牌化使用payment,仅保存使用tokenization)。只要你打算实际扣取卡款,就使用payment— 即使你还想在同一次调用中保存卡片。 - 会话有效期(自创建起 10 分钟)
- 响应中包含会话令牌的字段
实现一个服务端端点,按需生成会话令牌并将其返回给前端。该端点应:
- 生成新的 Bearer 令牌(或重用尚未接近过期时间的令牌)
- 使用
libVersion、scenario: "payment"和唯一的Idempotency-Key向会话令牌端点发送 POST 请求 - 将
token值返回给客户端
重要: 每次加载结账页面时都要生成新的会话令牌。不要在不同用户之间或不同页面导航之间缓存或重用会话令牌 — 10 分钟的有效期很短,过期令牌会导致表单初始化静默失败。
检查点
调用是否返回状态码 200,且包含 token 字段?如果没有,请检查 Bearer 令牌是否有效,以及路径中的终端 ID 是否正确。
步骤 3 — 加载 JS 库并初始化表单
阅读:references/create-a-payment-form.md
编写任何内容之前,请先阅读该页面。从本地参考资料中确认并使用:
- JS 库的 CDN URL — 从参考资料中读取;不要凭记忆硬编码版本号
- 当前版本的 SRI 哈希值(
integrity属性) - 步骤 2 请求正文中所需的
libVersion值 - 字段配置选项及其
target选择器 initialize()调用及其返回值submissionSuccess事件载荷 — 尤其是token字段及其有效期
实现前端集成:
-
在页面的
<head>中(或@section Scripts块中)添加一个<script>标签,指向 CDN URL 并带上 SRI 哈希值脚本加载顺序: Payroc SDK 必须在初始化代码运行前加载。如果你使用的模板引擎或框架会将正文内容与延迟加载的脚本区段(页脚、脚本包、布局局部文件等)分开,请确保 SDK 的
<script>标签和初始化代码块都放在同一个延迟加载区段中,并且 SDK 标签在前。如果初始化代码放在正文中,而 SDK 标签被延迟加载,程序将静默失败 —Payroc将未定义,且不会引发错误。 -
在支付表单中添加容器元素 — 为每个字段(
cardholderName、cardNumber、expiryDate、cvv)添加带有 CSS 类或 ID 选择器的<div>元素,并添加一个提交目标 -
使用会话令牌、
mode: "payment"选项以及指向容器选择器的字段配置,初始化托管字段对象 -
调用
.initialize() -
监听
submissionSuccess— 当库验证并对卡片进行令牌化后,此事件会触发;事件载荷包含服务器所需的一次性令牌 -
监听
error— 记录或显示来自type: "config"、"init"、"field"和"submission"类别的错误
输出 new Payroc.hostedFields(...) 配置前 — 阅读 SDK 源码
叙述性文档 create-a-payment-form.md 是正确的起点,但有记录显示,文档门户至少在一个 SDK 结构问题上含糊不清(之前有一次执行在 cardForm.submitFields() 和 cardForm.submit() 之间摇摆 — 这两个方法都不存在)。你在 <script> 中加载的 SDK JS 也是一个可读的 JS 文件,并且已在本地生成快照。 在输出配置前,请阅读并搜索 references/hosted-fields-sdk.js:
- 要阅读的内容。 本地快照
references/hosted-fields-sdk.js— 也就是你将在<script>中加载的同一份 JS(其头部记录了 UAT/生产环境 CDN URL 和四段式libVersion)。不要根据训练数据转述 SDK;请直接读取文件内容。 - 要验证的内容。
- SDK 在
fields.card中遍历的键列表(以及fields.ach/fields.pad中对应的容器)。列表之外的任何内容都会被静默忽略。搜索对fields.card的遍历,并读取所接受的键。 hostedFields实例公开的方法集合 — 即允许调用哪些cardForm.*方法。搜索构造函数和原型、Object.assign或类主体。
- SDK 在
- 然后再输出。 将每个容器目标都放在所接受的键列表中。仅针对公开的接口连接各方法调用。如果你假设存在的方法不在列表中,请不要编造另一个方法名再试 — 相关责任由 SDK 内部承担。
已知的 SDK 结构错误模式
这是先前模型在此技能上犯过的错误。列出这些错误是为了帮助你识别症状——解决方法始终是阅读 SDK 源码,并按其当前接受的内容输出,而不是“使用下面的值”。
- 症状: 即使配置在顶层包含提交目标属性(例如
submitButton: { target: '#pay' }),SDK 仍会触发error,并显示"A submit button is required for payment forms"。过去的错误做法: 将提交目标放在配置对象的顶层;SDK 只会从fields.card内读取它。验证: 在references/hosted-fields-sdk.js中搜索遍历fields.card[...]的代码,并确认它实际在哪里查找提交键。 - 症状: 在手动点击处理程序中出现运行时
TypeError: cardForm.submitFields is not a function(也见过.submit()、.validate())。过去的错误做法: 假定 SDK 提供可编程的提交触发方法,并绑定payButton.addEventListener('click', () => cardForm.submitFields())。验证: 在references/hosted-fields-sdk.js中搜索实例上的方法;如果没有你的方法,说明 SDK 正在自行渲染提交元素(以 iframe 的形式)并处理点击——删除手动处理程序。 - 症状: 字段容器显示带样式的边框/背景(页面级 CSS),但无法交互;没有注入 iframe,也不会触发
error事件。错误分类中已将其归为脚本加载顺序问题——此处再次交叉引用,以便你能在一处查看完整的结构相关症状集。
如有疑问:SDK JS 是文本。阅读 references/hosted-fields-sdk.js。
检查点
卡片输入字段是否在页面上渲染?提交测试卡片详情时,submissionSuccess 是否触发(且负载中带有令牌)?如果没有,请检查浏览器控制台中的 error 事件。
步骤 3b — 3-D Secure(仅当问题 4 的回答为“是”时)
阅读:references/run-a-sale-with-3-d-secure.md
开始编写前先阅读该页面。3DS 流程会增加一次服务器调用,并在付款请求中添加一个代码块:
submissionSuccess触发后(步骤 3),你的服务器使用一次性令牌、金额、币种、订单 ID、电子邮件、processingTerminalId,以及可选的cardholderChallenge(REQUIRED表示强制进行质询,OPTIONAL表示由发卡机构决定),调用GET /merchant/mpi(测试环境:payments.uat.payroc.com,生产环境:payments.payroc.com)。- Payroc 执行 3DS 质询,并将结果发送到你通过 [email protected] 注册的Webhook URL。负载包含
mpiReference、result、status、eci。 - 你的 Webhook 将
mpiReference与orderId(或类似标识)关联并持久化,以便步骤 4 查找。 - 在步骤 4 的付款请求中,添加
threeDSecure代码块:{ "serviceProvider": "gateway", "mpiReference": "<from webhook>" }。该代码块会将身份验证信息传递给收单机构,以实现责任转移。
PSD2 / 英国 SCA 豁免(TRA、低金额、MIT): 公开文档没有详细说明这些内容。如果开发者需要明确处理豁免,请升级至 Payroc Integrations 团队,不要自行猜测。
检查点
MPI GET 是否返回 200?你的 Webhook 是否收到包含 mpiReference 的结果?如果两者都没有发生,可能是终端尚未启用 3DS——请先向 Payroc 支持团队确认,再继续。
步骤 4 — 执行付款
阅读:references/run-a-sale.md
开始编写前先阅读该页面。根据本地参考资料,确认:
- 付款 API 端点(测试环境还是生产环境)
- 必需的请求头:
Content-Type、Authorization: Bearer <token>、Idempotency-Key - 最小请求正文:
channel、processingTerminalId、order(orderId、以最小面值表示的金额、币种)和paymentMethod - 一次性令牌对应的
paymentMethod结构:type: "singleUseToken"、token: <value from submissionSuccess> - 成功的 HTTP 状态,以及如何确认付款已处理
请从
references/api-schema.md中读取每个枚举值,不要根据英文是否听起来合理来推断。 请求正文中每个枚举类型的字符串(channel、paymentMethod.type、standingInstructions.sequence、processingModel、secCode、accountType、货币、国家)都必须取自本地架构参考。**已知的过往错误:**模型填入了channel: "internet",因为这个值听起来适合浏览器内结账;Payroc 返回 HTTP 400,并在错误详情中列出了枚举值。纠正方法不是在这里记住正确值,而是在构造正文时,从references/api-schema.md中读取paymentRequest.channel(或你即将设置的其他字段)。听起来似乎合理的近似值(internet、online、ecommerce、card-not-present)是最常见的错误形式。
在 submissionSuccess 处理程序中,将一次性令牌发送到你的服务器。然后,你的服务器:
- 生成(或复用)有效的 Bearer 令牌
- 使用一次性令牌构造付款请求正文
- **如果使用了 3DS(已执行步骤 3b):**在付款请求正文中添加
threeDSecure: { serviceProvider: "gateway", mpiReference: "<value from your MPI webhook>" } - 使用全新的
Idempotency-Key向付款 API 发送 POST 请求 - 读取 HTTP 状态码——201 表示 Payroc 已处理请求,并不表示发卡机构已批准。批准/拒绝结果位于响应正文中(
transactionResult.status和transactionResult.responseCode)。应根据响应正文而非 HTTP 状态码进行分支判断。
请根据完整枚举构建成功分支,不要依赖记忆中的子集。 编写成功检查时,请读取
references/api-schema.md中的transactionResult.status枚举。找出与银行批准相对应的所有值,并根据这些值进行分支判断。**已知的过往错误:**模型只对status == "approved"进行分支判断,导致 API 返回"ready"(已授权并排队等待扣款),且responseCode: "A"/responseMessage: "APPROVAL"时,将实际批准的交易悄悄记录为失败。修复方法不是“也接受ready”——根本错误在于根据对枚举的不完整理解来构建检查。请读取枚举。
框架说明——AJAX 处理程序中的防伪/CSRF。接收来自
submissionSuccess的一次性令牌的端点通过fetch/XHR POST 请求调用,而不是通过传统 HTML 表单提交。大多数服务器框架默认会对POST强制执行 CSRF/防伪检查。有两种选择,按优先顺序排列:(1) 传递令牌——从渲染的页面中读取框架的 CSRF 令牌,并在submissionSuccess回调中将其作为请求标头发送;(2) 在这个处理程序上禁用检查——可用于演示和仅供内部使用的工具,但不适用于生产环境。框架具体说明:
- **ASP.NET Core / Razor Pages。**必须在
PageModel类级别应用[IgnoreAntiforgeryToken]——将其应用于单个处理程序方法会在构建时触发MVC1001。若要保留防伪检查,请在表单中渲染@Html.AntiForgeryToken(),并将生成的__RequestVerificationToken值作为RequestVerificationToken标头发送。- **Django。**发送
csrftokenCookie 值作为X-CSRFToken请求标头,或者使用@csrf_exempt装饰视图。- **Rails。**发送
csrf-tokenmeta 标签的值作为X-CSRF-Token标头,或者在控制器操作上使用skip_before_action :verify_authenticity_token。- **Express +
csurf。**根据你的csurf配置,将令牌作为csrf-token(或X-CSRF-Token)标头发送。- **Laravel。**发送
XSRF-TOKENCookie 值作为X-XSRF-TOKEN标头(Laravel 默认行为),或者将路由添加到VerifyCsrfToken::$except。
**一次性令牌只能使用一次。**如果付款 API 调用因真实拒付以外的任何原因失败,开发者必须生成新的会话令牌并重新初始化表单,之后客户才能重试——一次性令牌不能再次提交。
检查点
使用测试卡时,支付 API 在 UAT 中是否返回 HTTP 201?如果没有,请使用以下错误分类。
本地测试策略
所有步骤(会话令牌生成、表单呈现、支付 API 调用)都可以在 localhost 上运行,因为没有浏览器重定向,也没有来自 Payroc 的服务器端 webhook。整个流程都在开发者的浏览器会话中运行。
错误分类
| 症状 | 可能原因 | 解决方法 |
|---|---|---|
表单字段未渲染;submissionSuccess 从未触发 | 会话令牌已过期(生成后超过 10 min),或未调用 initialize() | 在页面加载时重新生成会话令牌;检查 error 事件中的 type: "init" 或 type: "config" |
error 事件的 type: "config" | 构造函数中缺少会话令牌或令牌无效 | 确认已正确将会话令牌传入 new Payroc.hostedFields({sessionToken: ...}) |
| Payment API 返回 401 | Bearer 令牌已过期,或 API 密钥错误 | 重新获取 Bearer 令牌;验证与 identity service 配合使用的 x-api-key 值 |
| Payment API 返回 409 | 重用了幂等键 | 每次支付请求都生成一个新的 UUID v4 — 切勿重用幂等键 |
| Payment API 返回 400/422 | 一次性令牌已被使用或已过期(超过 30-min 有效期,或已使用) | 令牌只能使用一次;客户必须重新输入卡片信息 — 生成新的会话令牌并重新初始化表单 |
| CDN 脚本加载失败 | CDN URL 已过期或不正确 | 从 references/create-a-payment-form.md 中读取当前 URL 和 SRI 哈希 |
libVersion 不匹配错误 | 会话令牌请求中的 libVersion 与已加载的 JS 库版本不匹配 | 从 references/create-a-payment-form.md 中读取二者,确保版本一致 |
| 字段容器可见(CSS 已渲染带样式的边框或背景),但无法交互 — 未注入 iframe,控制台也没有错误 | 初始化代码在 Payroc SDK 脚本加载前执行。new Payroc.hostedFields(...) 会抛出 ReferenceError: Payroc is not defined;由于从未创建 cardForm,.on('error', ...) 处理程序也不会绑定,因此错误被静默吞掉 | 确保 SDK <script> 标签在初始化代码运行前已渲染并执行。对于任何会延迟脚本的布局,请将 SDK 标签和初始化代码块放在同一个延迟执行区块中,并将 SDK 标签置于前面 |
SDK 开始初始化(iframe 开始加载)后立即触发:"CVV wrapper is required for card forms. If you do not have a wrapper, please provide false." | cvv 字段配置缺少 wrapperTarget 属性,或使用了错误的键名(wrapper 而不是 wrapperTarget) | 在 cvv 字段配置中添加 wrapperTarget: false。如果存在包装元素,请传入其 CSS 选择器字符串,而不是 false。注意:wrapper: false(错误的键)会被静默忽略 — 属性名必须是 wrapperTarget |
| 支付被拒(状态不是 201,或响应中包含拒绝标记) | 测试卡不适用于 UAT | 使用 Payroc UAT 测试卡;检查响应正文中的 transactionResult.status 和 transactionResult.responseCode |
| 返回 HTTP 201,但客户称卡被拒 | 将 HTTP 201 与批准混为一谈 | 201 表示 Payroc 已接受并处理请求,并不代表发卡机构已批准。批准或拒绝结果包含在 transactionResult.status(枚举包括 declined)和 transactionResult.responseCode 中(例如 A=已批准、D=已拒绝、R=转介、C=CVV 失败)。应根据正文进行分支判断,而不是根据 201。 |
CSP 阻止 Payroc CDN 主机 — 脚本无法加载,也不会触发 error 事件 | Content Security Policy 中缺少 Payroc CDN 主机 | 将 Payroc CDN 主机(即 <script> 的 src 所在主机 — 请参阅 references/hosted-fields-sdk.js 中的快照标头,查看准确的 UAT 和 prod 主机)添加到 script-src(用于 JS 文件)以及 frame-src / child-src(用于库注入的 iFrame)。还要为令牌化 XHR 添加 connect-src。正式上线时,记得将 UAT 主机替换为 prod 主机。 |
| MPI 端点返回 404 或身份验证错误 | 终端未启用 3DS,或未注册 webhook URL | 发送邮件至 [email protected],以便在终端启用 3DS 并注册 webhook URL;两项都完成前,MPI / 3DS 调用都不会成功。 |
Payments API 返回 HTTP 400,错误详情中指出 "channel"(或其他枚举类型字段)并列出可接受的值 | 输出了非枚举值(此前出错值:channel: "internet"、"online"、"ecommerce");该技能没有读取架构参考,而是给出了一个听起来合理的近似值 | 从 references/api-schema.md 中读取 paymentRequest.channel(或相关字段),并输出列出的某个值。不要根据英语直觉推断枚举值。 |
即使配置顶层存在提交目标属性,SDK 仍触发 error,提示 "A submit button is required for payment forms" | 该属性位于顶层,而 SDK 只会从 fields.card 内读取与提交相关的键;迭代列表之外的内容都会被静默忽略 | 阅读 references/hosted-fields-sdk.js,并搜索对 fields.card[...] 的迭代。将提交目标放在 SDK 实际读取的位置。形似提交相关属性的顶层键不属于架构定义。 |
手动点击处理程序触发运行时错误 TypeError: cardForm.<method> is not a function(此前出错的方法:submitFields、submit、validate) | 该方法不属于 SDK 的公开接口 — 在 fields.card 内配置提交目标后,SDK 会将其渲染为 iframe,并负责处理点击 | 阅读 references/hosted-fields-sdk.js,确认 hostedFields 实例公开了哪些方法。如果你的方法不在列表中,说明 SDK 会在内部处理提交 — 删除手动处理程序,不要猜测另一个方法名。 |
应用记录了支付失败,但银行实际上已批准 — 响应显示 responseCode: "A" / responseMessage: "APPROVAL" | 服务端成功检查逻辑只基于记忆中的部分 transactionResult.status 值进行分支判断(此前出错情况:只接受 "approved";"ready" — 已授权并排队等待扣款 — 被静默视为失败) | 从 references/api-schema.md 中读取 transactionResult.status 枚举。根据所有与银行批准对应的值进行分支判断。不要依据对该枚举的不完整认知来编写成功检查逻辑。 |
验证清单
- API 密钥和终端 ID 从环境变量中读取——不写在源代码中
- Bearer 令牌在服务端生成——不暴露给浏览器
- 每次结账页面加载时生成会话令牌——不在不同会话之间缓存
- 从
references/create-a-payment-form.md中读取 JS 库 CDN URL 和 SRI 哈希——不凭记忆硬编码 - 会话令牌请求中的
libVersion与 CDN URL 中的版本一致 - 在调用支付 API 前,已确认
submissionSuccess会触发并返回令牌 - 每个支付请求都使用全新的 UUID v4 幂等性密钥
- 已在 UAT 中使用测试卡确认 HTTP 201
- 单次使用令牌未被记录,也未在当前 API 调用完成后继续存储
- 请求体中的每个枚举值(
channel、paymentMethod.type、standingInstructions.*等)均从references/api-schema.md读取,而非凭记忆填写 - 已对照
references/hosted-fields-sdk.js(以文本形式读取)核实 SDK 配置键和任何cardForm.*方法调用 - 服务端基于
transactionResult.status的成功分支涵盖references/api-schema.md枚举中的所有批准值(例如"ready")——而非只判断一个凭记忆填写的值
反模式
硬编码 JS 库版本或 CDN URL——每次发布都会更新版本;版本号递增时,硬编码的 URL 会在不报错的情况下失效。
跨页面加载缓存会话令牌——令牌 10 分钟后过期,缓存的令牌可能会在缓慢的结账过程中失效。每次加载结账页面时都要生成新令牌。
重复使用幂等性密钥——对不同的支付请求发送相同的密钥会导致 409;每个请求都要生成全新的 UUID v4。
在 submissionSuccess 触发前调用支付 API——单次使用令牌只有在库触发 submissionSuccess 后才会存在;轮询令牌或构造伪造令牌都会失败。
向客户端暴露 Bearer 令牌——Bearer 令牌授予 API 访问权限;只能将其保留在服务端。返回给客户端的会话令牌经过作用域限制,且设计为短时有效。
使用相同的单次使用令牌重试——该令牌只能使用一次。遇到任何需要重试的情况时,用户都必须从新的会话令牌开始,重新完成表单流程。
将初始化代码放在 SDK 脚本尚未加载的位置——许多服务端模板引擎和前端框架会将页面主体内容与延迟脚本块分开,并按顺序渲染。如果初始化代码放在主体中,而 SDK <script> 标签放在延迟区块中,初始化代码执行时 Payroc 仍未定义。此错误不会显现,因为 cardForm 从未创建,.on('error', ...) 处理程序也从未绑定。将 SDK <script> 标签和初始化代码块放在同一个延迟区块/页面主体底部位置,并先放 SDK 标签。
在 CVV 配置中使用 wrapper: false 而不是 wrapperTarget: false——正确的属性名是 wrapperTarget。SDK 会静默忽略错误的属性名 wrapper,因此配置验证错误仍会存在,而且 SDK 自身的错误消息不会指出键名不匹配。始终使用 wrapperTarget,不要使用 wrapper。没有包装元素时,值应为布尔值 false;存在包装元素时,值应为其 CSS 选择器字符串。
// Correct
cvv: { target: '.card-cvv', wrapperTarget: false },
// Wrong — silently ignored, config error persists
cvv: { target: '.card-cvv', wrapper: false },
未读取架构参考就填写枚举值。 channel、paymentMethod.type、standingInstructions.sequence、processingModel、secCode、accountType 和 transactionResult.status 都是枚举。以往的失败案例:channel: "internet"(这个值听起来适用于浏览器内结账,但不在枚举中);只针对 "approved" 判断成功,漏掉了 "ready"。该技能无法为你列出规范值——它们位于 references/api-schema.md 中。每次构造请求体以及每次编写响应分支前,都要阅读架构参考。
信任叙述性 SDK 文档中的属性和方法名称。 叙述性文档 create-a-payment-form.md 至少有一个 SDK 结构问题的描述含糊不清(之前一次运行中,对 submitFields() 和 submit() 犹豫不决)。references/hosted-fields-sdk.js 是唯一明确说明 new Payroc.hostedFields({...}) 接受什么参数以及 cardForm 有哪些方法的来源。输出任何配置或 cardForm.* 调用前,请先将其作为文本读取。
添加调用 cardForm 方法的手动点击处理程序。 过去的失败案例中,有人臆造了 cardForm.submitFields()、cardForm.submit() 和 cardForm.validate()——这些方法都不由 SDK 提供。在 fields.card 中配置提交目标后,SDK 会将其渲染为 iframe,并自行处理点击。你走错方向的明显信号是出现 TypeError: cardForm.<method> is not a function。请阅读 references/hosted-fields-sdk.js,确认实际存在的方法;如果你打算调用的方法不在其中,就删除手动处理程序,不要猜测其他方法名称。
根据对枚举的不完整理解来构建成功检查。 一次实际运行返回了 status: "ready",以及 responseCode: "A" / responseMessage: "APPROVAL"——银行已批准,但由于检查条件是 status == "approved",应用却记录了失败。解决方法不是将 "ready" 加入硬编码列表;根本的元错误在于凭记忆推断枚举,而不是查阅架构参考。请阅读 references/api-schema.md 中的 transactionResult.status,找出所有与银行批准对应的值,并针对完整的值集合进行分支处理。
完成
确认 HTTP 201 且所有检查清单项均通过后:
**集成完成。**你已完成以下内容:
- Bearer token — 使用 API 密钥从 Payroc 的身份服务在服务器端获取;用于授权所有 Payroc API 调用。
- Session token — 为每个结账会话生成的短期令牌;用于授权 JS 库渲染卡号字段。
- Hosted Fields 表单 — Payroc 的 JS 库会在你的页面中渲染 iFrame 卡号输入字段;卡片数据不会经过你的服务器。
- 单次使用令牌 — 表单提交时由库在客户端生成;传递给你的服务器以执行扣款。
- Payment API 调用 — 你的服务器会将单次使用令牌 POST 到 Payroc 的 payments API;HTTP 201 表示扣款成功。
- 已在 UAT 中验证 — 已使用测试卡确认端到端交易。
正式上线前:将 UAT 端点 URL 替换为生产 URL(identity、session token、payments API),并将凭据指向生产终端。重新从生产环境的
create-a-payment-form.md页面读取libVersion、CDN URL 和 SRI 哈希 — 这些值因环境而异,UAT 版本无法用于生产环境。
根据尚未实现的内容,提供后续步骤:
- 令牌化 — 将客户的卡片详情保存为安全令牌,以便日后扣款;在 session token 请求中使用
scenario: "tokenization"和 secure-tokens API - 周期性扣款 — 在首次扣款时配置常设指令,并在后续由商户发起的交易中重复使用已保存的令牌;阅读
references/recurring-billing.md - 更新客户已保存的卡片 — 当客户的卡片过期或变更时,无需创建新令牌即可更新已保存的 secureToken 对应的卡片详情。此流程使用
scenario: "tokenization"(而非payment),且不涉及交易 — 无需扣款或零金额授权。实现前请阅读references/update-a-customers-payment-details.md。 - 3-D Secure — 如果开发者在初始沟通时跳过了 3DS,但现在需要启用(例如,收单机构对英国交易进行软拒绝),请参阅上文的步骤 3b
- 样式 — 通过
styles配置选项使用 CSS 自定义托管字段的外观 - 自定义字段 — 使用
onPreSubmit验证,在卡片字段旁收集其他数据 - 关闭会话时的清理 — 当客户在结账过程中取消或离开页面时,调用托管字段实例的
destroy()以移除 iFrame。这与 session token 的 10 分钟自动过期不同,并会释放客户端资源。具体 API 请阅读references/close-a-session.md。 - Hosted Payment Pages — 如果开发者之后需要基于重定向的流程(例如在其他页面处理 ACH),请使用 HPP 技能