关于 payment-provider-framework
面向在 VTEX IO 中开发或维护支付连接器的技能,涵盖框架接线、配置、卡数据请求及测试发布流程,并强调 TypeScript 与配置模式约束。
主要功能
- 01配置 paymentProvider builder 与 configuration.json
- 02实现 PaymentProvider 路由与服务
- 03处理 Gateway 重试及安全代理卡流程
- 04检查 TypeScript 3.9.7 与依赖兼容性
- 05规划测试、发布和 VTEX homologation
适用场景
- 01搭建新的 VTEX IO 支付连接器
- 02排查 builder 或类型声明导致的构建失败
- 03配置支付方式、自定义字段及功能标志
- 04测试并发布支付连接器
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vtex/ skills --skill payment-provider-framework安装后在 Claude Code 中输入/payment-provider-framework确认已加载。
详细介绍
Payment Provider Framework(VTEX IO)
适用场景
使用此技能的场景:
- 创建或维护以 VTEX IO 应用实现的支付连接器(而非自行托管的独立 HTTP 服务)
- 在
node/index.ts中接入@vtex/payment-provider、PaymentProvider和PaymentProviderService - 配置
paymentProvider构建器、configuration.json(支付方式、customFields、功能标志) - 在 IO 上实现
this.retry(request),以满足 Gateway 重试语义 - 扩展
SecureExternalClient,并在 IO 上的银行卡流程请求中传递secureProxy - 通过支付关联、工作区、beta/stable 版本发布、VTEX App Store 和 VTEX 认证进行测试
请勿将此技能用于:
- PPP HTTP 契约、逐字段响应要求,以及概述中的九个端点——请使用
payment-provider-protocol - 幂等性和重复
paymentId处理——请使用payment-idempotency - 异步
undefined状态、callbackUrl通知与重试(IO 与非 IO)——请使用payment-async-flow - IO 接入以外的 PCI 规则、日志记录和令牌语义——请使用
payment-pci-security - POS / Sales App 刷卡流程覆盖层(
Venda Direta方式、Payment Apps、终端序列号)——请将此技能与payment-ppp-pos配合使用
决策规则
- IO 上的 PPF:「Payment Provider Framework 是基于 VTEX IO 构建支付连接器的方式。」该应用使用 IO 基础设施;API 路由、请求/响应类型和 Secure Proxy 均按照 VTEX 指南集成。请从官方 Payment Provider Framework 文档中介绍的示例应用开始。
- 前提条件:遵循 Payment Provider Protocol 文章以及在 VTEX 上集成新支付服务商指南中的实现前提条件。
- 依赖项:在应用的
node文件夹中添加@vtex/payment-provider(例如,在package.json中使用1.x)。将@vtex/api保留在devDependencies中(例如使用6.x);链接操作可能会将其升级到高于6.x的版本,这可以接受。如果类型出错,请删除项目根目录和node文件夹中的node_modules和yarn.lock,然后分别在两处运行yarn install -f。 paymentProvider构建器:在manifest.json中,将"paymentProvider": "1.x"与node并列,以应用 Payment Gateway 回调和 PPP 路由的策略。configuration.json:声明paymentMethods,使构建器能够实现这些支付方式,而无需在/manifest上重新声明所有内容。使用与 List Payment Provider Manifest API 参考相符的名称;只有在支付方式确实是全新的时候才创建名称。在 Admin 中添加新支付方式可能需要提交支持工单。PaymentProvider:每个 PPP 路由对应一个类方法;TypeScript 会强制校验结构——请参阅 API 参考中的 Payment Flow 端点。PaymentProviderService:注册默认路由/manifest、/payments、/settlements、/refunds、/cancellations、/inbound;需要时传入额外的routes/clients。- 覆盖
/manifest:仅限已批准的用例——请提交工单。路由覆盖结构示例请参阅 Preferred pattern 部分。 - 可配置选项:使用
configuration.json/构建器选项设置implementsOAuth、implementsSplit、usesProviderHeadersName、usesBankInvoiceEnglishName、usesSecureProxy、requiresDocument、acceptSplitPartialRefund、usesAutoSettleOptions等标志。在 IO 上设置name,并依赖自动生成的serviceUrl,除非文档另有说明。不要臆造字段——未知键(例如usesTestSuite)会导致构建器验证错误。规范字段列表和customFields格式请参阅下文的「configuration.json 架构」限制。 - 网关重试:在 PPF 中,协议要求重试时调用
this.retry(request)——请参阅 PPP 文章中的 Payment authorization 部分。 - IO 上的卡数据:「优先使用
SecureExternalClient,并将 Create Payment 中的secureProxy: secureProxyUrl作为参数;目标地址必须在允许列表中。」Secure Proxy 支持的Content-Type仅有application/json和application/x-www-form-urlencoded。**重要:**只有 Create Payment(授权)请求携带secureProxyUrl。授权后的操作(取消、捕获、退款)不会传输卡数据,必须通过ExternalClient携带凭据并使用outbound-access策略直接调用 PSP API。 - Checkout 测试:账号必须获准使用 IO 连接器(提交注明应用名称和账号的工单)。发布 beta 版本,在
master上安装,等待约 1 小时,打开 affiliation URL,启用测试模式和工作区,配置支付条件(约 10 分钟),然后下测试订单;之后发布 stable 版本并进行 homologation。 - 发布:按照 Billing Options 指南配置
billingOptions;按照 Submitting your app 提交应用。根据 Integrating a new payment provider on VTEX 指南准备 homologation 材料(连接器应用名称、合作伙伴联系人、生产端点、允许使用的账号、新支付方式/流程)(SLA 通常约为 30 天)。 - 更新:在新的 beta 版本中发布更改,重新测试 affiliations,然后发布 stable 版本;如有要求,重新进行 homologation。
硬性限制
约束:Builder-Hub 使用 TypeScript 3.9.7——代码和依赖项必须兼容
vtex.builder-hub 使用 TypeScript 3.9.7 编译 IO 应用。它还会 忽略 tsconfig.json 中的 skipLibCheck: true——node_modules 中的每个 .d.ts 文件都会进行类型检查。这意味着,即使你自己的代码有效,只要某个传递依赖带有较新的 .d.ts 语法,就会导致构建失败,并产生数百条与你的代码无关的错误。
为何重要
Agent 和开发者经常编写使用 TS 4.x 及更高版本语法的代码,或安装最新的 @types/* 包。构建会因开发者从未碰过的文件中出现难以理解的错误而失败,导致大量无谓的迭代。
禁止使用的语法(与 TS 3.9.7 不兼容)
| 语法 | 最低 TS 版本 | 示例 |
|---|---|---|
| 模板字面量类型 | 4.1 | type X = `${string}/${string}` |
| 带类型的 catch 子句 | 4.0 | catch (error: any) |
override 关键字 | 4.3 | 类中的 override method() |
import type ... = require() | 4.5 | import type X = require("pkg") |
satisfies 运算符 | 4.9 | obj satisfies Type |
正确的 catch 块写法
// CORRECT — TS 3.9.7 compatible
try {
// ...
} catch (error) {
const err = error as any
console.log(err.message)
}
// WRONG — TS 4.0+ only
try {
// ...
} catch (error: any) {
console.log(error.message)
}
未使用的变量会导致错误,而非警告。 builder-hub 会将已声明但未使用的变量视为编译错误。避免解构出未使用的字段:
// WRONG — if callbackUrl is not used, build fails
const { paymentId, callbackUrl, value } = authorization
// CORRECT
const { paymentId, value } = authorization
安全的依赖项版本(兼容 TS 3.9.7)
在 node/package.json 中使用 resolutions,将传递依赖固定到不包含较新 .d.ts 语法的版本。**/<package> 模式也会固定嵌套副本。
{
"dependencies": {
"@vtex/payment-provider": "1.x"
},
"devDependencies": {
"@vtex/api": "6.50.1",
"@types/node": "12.20.55",
"@types/express-serve-static-core": "4.17.2",
"@types/express": "4.17.10",
"@types/serve-static": "1.15.0",
"@opentelemetry/api": "1.0.4",
"typescript": "3.9.7"
},
"resolutions": {
"@types/node": "12.20.55",
"@types/express-serve-static-core": "4.17.2",
"@types/express": "4.17.10",
"@types/serve-static": "1.15.0",
"@opentelemetry/api": "1.0.4",
"**/@types/express-serve-static-core": "4.17.2",
"**/@types/koa": "2.15.0"
}
}
| 包 | 安全版本 | 首个不兼容版本 | 原因 |
|---|---|---|---|
@types/node | 12.20.55 | 13.x+(部分 API) | .d.ts 中使用了较新的语法 |
@types/express-serve-static-core | 4.17.2 | 4.17.13+ | 模板字面量类型 |
@types/express | 4.17.10 | 4.17.11+ | 依赖 @types/express-serve-static-core@^4.17.18 |
@types/koa | 2.15.0 | 3.x | import type ... = require() |
@opentelemetry/api | 1.0.4 | 更新的版本 | TS 4.x 语法 |
@types/serve-static | 1.15.0 | 更新的版本 | 传递依赖问题 |
**诊断新出现的不兼容包:**如果构建因 node_modules 中 .d.ts 文件的错误而失败,请根据错误路径确定所属的包,测试较旧版本,直到找到不含较新语法的版本,并将其同时添加到 devDependencies 和 resolutions(使用 **/<package> 模式)。
检测
如果生成的代码使用了上表中的任何语法,或者 package.json 缺少针对类型包的 resolutions,请停止操作并先修复,再尝试运行 vtex link。
约束:configuration.json 只能使用有效的架构字段,且 customFields 格式必须正确
paymentProvider 构建器会根据严格的架构验证 configuration.json。未知字段会导致构建错误。select 类型字段的 customFields[].options 数组必须使用 text 和 value 键,不能使用 label。
为何重要
如果 usesTestSuite 或 useAntifraud 等字段不在当前架构中,就会导致 vtex link 立即失败。在 select 选项中使用 label 而非 text,会导致 Admin UI 无法正常工作,或验证失败。
标准字段(请对照当前 VTEX 文档确认):
name(必填)、serviceUrl(IO 上自动设置)、implementsOAuth、implementsSplit、usesProviderHeadersName、usesBankInvoiceEnglishName、usesSecureProxy、requiresDocument、acceptSplitPartialRefund、usesAutoSettleOptions、paymentMethods、customFields。
已知会导致构建失败的字段:usesTestSuite(架构中不存在)。
select 类型的正确 customFields
{
"name": "AcmePayConnector",
"usesAutoSettleOptions": true,
"paymentMethods": [
{ "name": "Visa", "allowsSplit": "onCapture" }
],
"customFields": [
{
"name": "Environment",
"type": "select",
"options": [
{ "text": "Sandbox", "value": "sandbox" },
{ "text": "Production", "value": "production" }
]
}
]
}
错误的 customFields
{
"customFields": [
{
"name": "Environment",
"type": "select",
"options": [
{ "label": "Sandbox", "value": "sandbox" }
]
}
]
}
检测
如果 configuration.json 包含不在标准字段列表中的键,或 select 选项使用了 label 而非 text,请停止操作并先修复,再进行构建。
约束:在 configuration.json 中声明 paymentProvider 构建器和真实的连接器标识
IO 连接器必须在 manifest.json 中包含 paymentProvider 构建器,并提供 paymentProvider/configuration.json,其中包含非占位符的 name 和准确的 paymentMethods。不要在生产配置中使用字面占位符 "MyConnector"(或类似名称)。
重要性
缺少该构建器,PPP 路由和 Gateway 策略就不会接入。占位符名称会导致 Admin、关联关系和认证流程出问题。
检测
如果 manifest.json 缺少 paymentProvider,或者 configuration.json 仍使用示例占位符名称,请停止发布并修复。
正确
{
"name": "PartnerAcmeCard",
"paymentMethods": [
{ "name": "Visa", "allowsSplit": "onCapture" },
{ "name": "BankInvoice", "allowsSplit": "onAuthorize" }
]
}
错误
{
"name": "MyConnector",
"paymentMethods": []
}
约束:仅通过 PaymentProviderService 和 PaymentProvider 实现注册 PPP 路由
该服务必须封装一个继承自 @vtex/payment-provider 中 PaymentProvider 的类,以注册标准 PPP 路径。除非 VTEX 明确规定了替代方案,否则不要在不使用该包的情况下手动实现相同的路由。
重要性
路由缺失或不匹配会导致 Gateway 调用和认证流程出错;该包能确保处理程序与协议保持一致。
检测
如果 node/index.ts 手动暴露 PPP 路径,却没有使用连接器类实例化 PaymentProviderService,请按照文档中的模式进行调整。
正确
import { PaymentProviderService } from "@vtex/payment-provider";
import { YourPaymentConnector } from "./connector";
export default new PaymentProviderService({
connector: YourPaymentConnector,
});
错误
// Ad-hoc router only — no PaymentProviderService / PaymentProvider base
export default someCustomRouterWithoutPPPPackage;
约束:PaymentProviderService 的 clients 字段需要 { implementation, options },不能直接传入类
向 PaymentProviderService 传入自定义 IOClients 时,clients 字段需要一个包含 implementation(类)和 options(重试/超时配置)的对象,遵循 @vtex/api 中的 ServiceConfig 接口。直接传入类会导致运行时错误。
重要性
这是一个常见错误,会产生令人困惑的运行时错误,而不是明确的类型错误,因为 PPF 类型可能不会严格强制执行此要求。
正确
import { PaymentProviderService } from '@vtex/payment-provider'
import MyConnector from './connector'
import { Clients } from './clients'
export default new PaymentProviderService({
connector: MyConnector,
clients: {
implementation: Clients,
options: {
default: {
retries: 2,
timeout: 15000,
},
},
},
})
错误
export default new PaymentProviderService({
connector: MyConnector,
clients: Clients, // WRONG — expects { implementation, options }
})
约束:IO 上的 Gateway 重试应使用 this.retry(request)
如果 PPP 流程要求在 IO 上执行重试,处理程序必须按照协议规定调用 this.retry(request),而不是使用绕过框架的自定义重试辅助函数。
重要性
“Gateway 需要由框架驱动的重试行为;省略该行为会导致授权和结算行为不一致。”
检测
搜索支付处理程序中的协议重试场景;如果重试未使用 this.retry 实现,请在发布前修复。
正确
// Inside a PaymentProvider subclass method, when the protocol requires retry:
return this.retry(request);
错误
// Re-implementing gateway retry with setTimeout/fetch instead of this.retry
await fetch(callbackUrl, { method: "POST", body: JSON.stringify(payload) });
约束:在 IO 上通过 Secure Proxy 转发卡授权调用,并使用允许列表中的目标地址
对于采用 usesSecureProxy 行为的 IO 卡支付流程,代理 HTTP 调用必须通过 SecureExternalClient(或等效的 VTEX 模式)进行,必须将 secureProxy 设置为支付请求中的 secureProxyUrl,并且目标地址必须是 VTEX 允许列表中的 PCI 端点。仅支持 application/json 或 application/x-www-form-urlencoded 请求体。如果 usesSecureProxy 为 false,支付服务商必须通过 PCI 认证,并按照 VTEX 要求为 serviceUrl 提供 AOC。
重要性
“跳过 Secure Proxy 或使用错误的内容类型会导致 PCI 范围、代理验证或收单机构集成出问题,进而阻碍认证流程,或导致卡数据以不安全的方式暴露。”
检测
检查客户端代码中是否存在包含卡令牌的 POST 请求,而请求配置中没有 secureProxy,或请求目标未在 VTEX 注册。
正确
import { SecureExternalClient, CardAuthorization } from "@vtex/payment-provider";
import type { InstanceOptions, IOContext, RequestConfig } from "@vtex/api";
export class MyPCICertifiedClient extends SecureExternalClient {
constructor(protected context: IOContext, options?: InstanceOptions) {
super("https://pci-certified.example.com", context, options);
}
public authorize = (cardRequest: CardAuthorization) =>
this.http.post(
"authorize",
{
holder: cardRequest.holderToken,
number: cardRequest.numberToken,
expiration: cardRequest.expiration,
csc: cardRequest.cscToken,
},
{
headers: { Authorization: "Bearer ..." },
secureProxy: cardRequest.secureProxyUrl,
} as RequestConfig
);
}
错误
// Direct outbound call with raw card fields and no secureProxy
await http.post("https://acquirer.example/pay", { pan, cvv, expiry });
约束:只有 Create Payment 会接收 secureProxyUrl——身份验证后的操作会直接调用 PSP
secureProxyUrl 字段仅存在于 Create Payment(授权)请求中。取消、捕获和退款操作不携带卡数据,也不会接收 secureProxyUrl。这些操作必须使用 ExternalClient(来自 @vtex/api)和 API 凭据直接调用 PSP API,并通过 manifest.json 中的 outbound-access 策略进行保护。
为何重要
在取消、捕获或退款处理程序中尝试使用 SecureExternalClient 或 secureProxyUrl 会失败,因为这些请求中的该字段为 undefined。这并非 PCI 合规问题——这些操作只会引用交易 ID,不会涉及卡数据。
检测
如果取消、捕获或退款处理程序引用了 secureProxyUrl 或使用了 SecureExternalClient,请停止。这些操作必须使用 ExternalClient 直接通过 HTTP 调用 PSP。
正确——双客户端模式
// clients/pspSecure.ts — for authorization only (via Secure Proxy)
import { SecureExternalClient } from "@vtex/payment-provider";
import type { InstanceOptions, IOContext } from "@vtex/api";
export class PspSecureClient extends SecureExternalClient {
constructor(ctx: IOContext, opts?: InstanceOptions) {
super("https://api.psp.com", ctx, opts);
}
public async authorize(data: object, secureProxyUrl: string) {
return this.http.post("/v1/payments", data, {
secureProxy: secureProxyUrl,
} as any)
}
}
// clients/psp.ts — for cancel, capture, refund (direct calls)
import { ExternalClient } from "@vtex/api";
import type { InstanceOptions, IOContext } from "@vtex/api";
export class PspClient extends ExternalClient {
constructor(ctx: IOContext, opts?: InstanceOptions) {
super("https://api.psp.com", ctx, opts);
}
public async capture(transactionId: string, amount: number) {
return this.http.post(`/v1/payments/${transactionId}/capture`, { amount })
}
public async cancel(transactionId: string) {
return this.http.post(`/v1/payments/${transactionId}/cancel`, {})
}
public async refund(transactionId: string, amount: number) {
return this.http.post(`/v1/payments/${transactionId}/refund`, { amount })
}
}
错误
// WRONG — trying to use SecureExternalClient for capture
async settle(settlement: SettlementRequest) {
const client = this.context.clients.pspSecure as PspSecureClient
// secureProxyUrl is undefined here — this will fail
await client.capture(settlement.tid, settlement.value, settlement.secureProxyUrl)
}
约束:不要在客户端类之外访问客户端实例的 .http
ExternalClient 和 SecureExternalClient 上的 http 属性受 protected 保护。从 PaymentProvider 子类调用 client.http.post(...) 会导致 builder-hub 中的 TypeScript 编译错误。
为何重要
开发者尝试从连接器类而不是通过客户端的公共方法发起 HTTP 调用时,经常会犯这个错误。builder-hub 会强制执行 protected 访问限制,导致构建失败。
检测
如果连接器代码访问了客户端实例上的 .http(例如,this.context.clients.myClient.http.post(...)),请停止。应在客户端子类中公开一个公共方法。
正确
// Inside the client class — this.http is accessible (protected = same class)
export class PspClient extends ExternalClient {
public async capturePayment(tid: string, amount: number) {
return this.http.post(`/v1/payments/${tid}/capture`, { amount })
}
}
// Inside the connector — call the public method
async settle(settlement: SettlementRequest) {
const clients = this.context.clients as any as { psp: PspClient }
return clients.psp.capturePayment(settlement.tid, settlement.value)
}
错误
// Inside the connector — .http is protected, build fails
async settle(settlement: SettlementRequest) {
const clients = this.context.clients as any as { psp: PspClient }
return clients.psp.http.post(`/v1/capture`, { amount: settlement.value })
// ^^^^ TS error: Property 'http' is protected
}
首选模式
项目文件结构
/
├── manifest.json # App identity, builders, policies
├── paymentProvider/
│ └── configuration.json # Payment methods, connector options
├── service.json # Runtime config (memory, timeout, replicas, custom routes)
└── node/
├── package.json # Dependencies WITH resolutions for TS 3.9.7
├── tsconfig.json # TypeScript config (skipLibCheck ignored by builder)
├── yarn.lock # REQUIRED — builder rejects build without it
├── index.ts # Entry point: exports PaymentProviderService
├── connector.ts # Class extending PaymentProvider
├── clients/
│ ├── index.ts # IOClients with getOrSet
│ ├── psp.ts # ExternalClient for direct PSP calls (cancel/capture/refund)
│ └── pspSecure.ts # SecureExternalClient for card authorization via Secure Proxy
└── typings/
└── psp.ts # TypeScript interfaces
关键文件说明:
node/中的yarn.lock为必需文件——没有它,构建器会拒绝构建。不要将其添加到.vtexignore。service.json放在项目根目录,不要放在node/中。configuration.json放在paymentProvider/中,不要放在根目录。
manifest.json 中的 builders 和 policies
{
"builders": {
"node": "7.x",
"paymentProvider": "1.x"
},
"policies": [
{ "name": "vbase-read-write" },
{ "name": "outbound-access", "attrs": { "host": "api.psp.com", "path": "/*" } },
{ "name": "outbound-access", "attrs": { "host": "api.sandbox.psp.com", "path": "/*" } }
]
}
注意:如果使用 VBase 存储状态,则必须配置 vbase-read-write 策略。否则,vbase.saveJSON() 会返回 403。
tsconfig.json
{
"compilerOptions": {
"target": "es2019",
"module": "commonjs",
"lib": ["es2019"],
"outDir": "./dist",
"rootDir": ".",
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["**/*.ts"],
"exclude": ["node_modules", "**/node_modules", "dist"]
}
注意:builder-hub 会使用自己的值覆盖 outDir、rootDir 和 paths。skipLibCheck: true 会被忽略——所有 .d.ts 文件都会被检查。应改用 package.json 中的 resolutions 控制依赖的类型版本。
注册 IOClients
import { IOClients } from '@vtex/api'
import { PspClient } from './psp'
import { PspSecureClient } from './pspSecure'
export class Clients extends IOClients {
public get psp(): PspClient {
return this.getOrSet('psp', PspClient)
}
public get pspSecure(): PspSecureClient {
return this.getOrSet('pspSecure', PspSecureClient)
}
}
在连接器中访问自定义客户端和 IOContext
import { PaymentProvider } from '@vtex/payment-provider'
import type { Clients } from './clients'
export default class MyConnector extends PaymentProvider {
// IOContext is at this.context.vtex, NOT this.context directly
private get account(): string {
const vtexCtx = (this.context as any).vtex as { account: string; workspace: string }
return vtexCtx?.account || ''
}
private get workspace(): string {
const vtexCtx = (this.context as any).vtex as { account: string; workspace: string }
return vtexCtx?.workspace || 'master'
}
// Custom clients require a cast
private get clients() {
return this.context.clients as any as {
psp: Clients['psp']
pspSecure: Clients['pspSecure']
vbase: import('@vtex/api').VBase
}
}
// VBase is available directly
private get vbase() {
return this.context.clients.vbase
}
}
使用客户端、自定义路由和 service.json 的 PaymentProviderService
// node/index.ts
import { PaymentProviderService } from '@vtex/payment-provider'
import MyConnector from './connector'
import { Clients } from './clients'
export default new PaymentProviderService({
connector: MyConnector,
clients: {
implementation: Clients,
options: {
default: {
retries: 2,
timeout: 15000,
},
},
},
// Optional: custom routes (e.g., for redirect callbacks)
// routes: {
// myCallback: myCallbackHandler,
// },
})
使用自定义路由时,请在项目根目录的 service.json 中注册:
{
"memory": 256,
"ttl": 10,
"timeout": 10,
"minReplicas": 2,
"maxReplicas": 10,
"routes": {
"myCallback": {
"path": "/_v/my-connector/callback",
"public": true
}
}
}
PPF 构建器会将自身的路由(PPP 端点)与你的路由合并——无需重新声明标准 PPP 路由。
通过 affiliation 获取凭据
PPF 会自动将 affiliation 标头映射到连接器属性:
| VTEX 标头 | 连接器属性 | 典型用途 |
|---|---|---|
X-PROVIDER-API-AppKey | this.apiKey | PSP 客户端 ID / API 密钥 |
X-PROVIDER-API-AppToken | this.appToken | PSP 客户端密钥 / API 令牌 |
this.isTestSuite 表示该交易是否为测试交易(sandbox)。
configuration.json 中 customFields 的自定义设置可通过每个请求中的 authorization 对象获取(具体访问方式取决于 PPF 版本——请查看 @vtex/payment-provider 中的类型定义)。
PPF 响应辅助函数——各辅助函数会自动填充的内容
使用来自 @vtex/payment-provider 的 Authorizations、Settlements 和 Refunds 辅助函数时:
| 辅助函数 | 从请求中自动填充 | 不要作为第二个参数传入 |
|---|---|---|
Authorizations.approve(auth, { ... }) | paymentId | — |
Authorizations.deny(auth, { ... }) | paymentId | nsu(不属于 deny 类型) |
Settlements.approve(settle, { settleId }) | paymentId、value | value(已从请求中合并) |
Refunds.approve(refund, { refundId }) | paymentId、value | value(已从请求中合并) |
如果严格类型中未包含 delayToCancel,但要在 approveCard 中添加它,请使用带类型断言的展开语法:
// TS 3.9.7 compatible
const baseResponse = Authorizations.approve(authorization, {
authorizationId: result.authorizationId,
nsu: result.nsu,
tid: result.tid,
acquirer: 'MyPSP',
code: '200',
message: 'Approved',
})
const response = {
...baseResponse,
delayToCancel: 21600,
delayToAutoSettle: 21600,
delayToAutoSettleAfterAntifraud: 1800,
} as any
return response
PSP 集成检查清单(与提供商无关)
在接入 PSP 客户端之前:
- 打开 PSP 的 API 浏览器 / OpenAPI 规范,确定各环境(测试/正式)的基础 URL。
- 请勿在
baseURL和操作路径中重复版本号或路径段(例如,如果基础 URL 为https://checkout-test.psp.com/v71,则支付路径应为/payments,而不是/v71/payments)。 - 在完成连接器实现之前,先通过测试调用(例如创建支付)进行验证。
- 如果 PSP 需要 OAuth 令牌,请实现访问令牌的内存缓存——VTEX Gateway 在某些流程中有 2 秒超时,依次调用令牌接口和 API 会超时。
// Simple in-memory token cache (TS 3.9.7 compatible)
const tokenCache: Record<string, { token: string; expiresAt: number }> = {}
function getCachedToken(clientId: string): string | null {
const entry = tokenCache[clientId]
if (entry && Date.now() < entry.expiresAt) return entry.token
return null
}
function setCachedToken(clientId: string, token: string, expiresIn: number): void {
tokenCache[clientId] = {
token,
expiresAt: Date.now() + (expiresIn - 300) * 1000,
}
}
测试用关联 URL 格式
https://{account}.myvtex.com/admin/affiliations/connector/Vtex.PaymentGateway.Connectors.PaymentProvider.PaymentProviderConnector_{connector-name}/
将 {connector-name} 替换为 ${vendor}-${appName}-${appMajor}(例如:vtex-payment-provider-example-v1)。
测试流程概要:发布 beta 版本(例如 [email protected]——参见 Making your app publicly available 文档),在 master 上安装,等待约 1 小时,打开关联,在 Payment Control 下启用 Enable test mode 并设置 Workspace(通常为 master),添加支付条件,等待约 10 分钟,然后下单;接着部署稳定版本并完成认证。
在生产环境使用前,请将所有示例供应商名称、端点和凭据替换为真实应用的对应信息。
常见故障模式
- 缺少
paymentProviderbuilder,或paymentMethods为空/配置错误,导致/manifest和 Admin 无法正确列出支付方式。 - 在
node_modules/.d.ts文件中出现数百条 TS 错误,导致构建失败——package.json中缺少resolutions,无法将类型包固定到与 TS 3.9.7 兼容的版本。 skipLibCheck: true不起作用——builder-hub 会忽略此设置。请改用resolutions。- 在 connector 代码中使用
catch (error: any)或其他 TS 4.x+ 语法——请使用与 TS 3.9.7 兼容的写法。 configuration.json中包含无效字段(usesTestSuite、自创的键)——会导致 builder 验证错误。customFields的下拉选项使用label而非text——会导致 Admin UI 异常或验证失败。node/目录中缺少yarn.lock——builder 会拒绝构建。- 将
service.json放在node/目录中,而不是项目根目录。 - 将
clients: Clients直接传给PaymentProviderService,而非传入{ implementation: Clients, options: {...} }。 - 在 connector 中访问
client.http.post(...)——http是protected;请在 client 类中公开方法。 - 使用
SecureExternalClient处理 cancel/capture/refund——只有 Create Payment 会携带secureProxyUrl;授权后的操作使用ExternalClient。 - 访问
this.context.account,而不是this.context.vtex.account——IOContext 嵌套在.vtex下。 - 类型或安装版本不一致(
@vtex/api/@vtex/payment-provider),且没有在根目录和node中执行干净的重新安装流程。 - 跳过
this.retry(request),并使用临时 HTTP 逻辑重复实现重试——Gateway 行为会与 PPP 不一致。 - 卡片请求未使用
secureProxy、Content-Type错误,或目标地址不在允许列表中——Secure Proxy 或 PCI 审核会失败。 - 测试时未将账户加入允许列表、没有可售商品,或未等待主安装/支付条件传播完成。
- 未经 VTEX 批准就覆盖
/manifest,或在主版本升级后保留过期的x-provider-app。 - Homologation 工单缺少生产端点、允许的账户或购买流程详情。
vtex link --no-watch会隐藏编译错误——建议使用 watch 模式查看完整错误输出。- 在
masterworkspace 上运行vtex link会失败——请使用 dev workspace(vtex use dev-workspace)。 manifest.json中缺少vbase-read-write策略——vbase.saveJSON()会返回 403。- PSP 基础 URL 中路径段重复——例如版本已在基础 URL 中时出现
/checkout/v71/v71/payments。
审查清单
- 该连接器是否为使用
PaymentProvider+PaymentProviderService的 IO 应用(而非仅提供独立中间件指南)? -
manifest.json和paymentProvider/configuration.json是否与实际连接器名称及支持的方法一致? -
configuration.json是否只使用有效的架构字段?customFields的选择选项是否使用text(而非label)? -
package.json是否包含resolutions,将@types/*和其他软件包固定到兼容 TS 3.9.7 的版本? - 所有代码是否兼容 TS 3.9.7?(不使用
catch (e: any)、模板字面量类型、override或satisfies) -
yarn.lock是否存在于node/中,且未列入.vtexignore? -
service.json是否位于项目根目录(而非node/中)? -
PaymentProviderService是否通过clients: { implementation, options }实例化(而非直接传入类)? - 客户端类是否公开方法,而非依赖从外部访问
this.http? - 是否仅在 Create Payment(授权)时使用
SecureExternalClient,而在 cancel/capture/refund 时使用ExternalClient? - 可选的 manifest 覆盖项是否已获工单批准,且
handler/ headers /x-provider-app是否正确? - 每个路由实现是否都与
@vtex/payment-provider中的类型及 payment-provider-protocol 中的响应形状一致? - 是否在需要时使用
this.retry(request)实现 Gateway 重试? - 卡支付流程是否使用
SecureExternalClient(或等效方案),并设置secureProxy: secureProxyUrl和允许列表中的目标地址? - Beta/预发布环境测试是否在发布稳定版本前遵循 affiliation、测试模式、工作区和支付条件相关步骤?
- 计费、App Store 提交和认证所需的前置条件是否记录在内部发布清单中?
-
manifest.json是否包含针对 PSP 主机的vbase-read-write和outbound-access策略?
相关技能
payment-provider-protocol— PPP 端点、HTTP 方法和响应形状payment-idempotency—paymentId/requestId和重试payment-async-flow—undefined状态、callbackUrl(IO 重试与通知)以及基于重定向的流程payment-pci-security— IO 接线之外的 PCI 和 Secure Proxy 语义payment-ppp-pos— POS / Sales App 面对面刷卡流程补充(Venda Direta方法、Payment Apps、终端回调)