@crvouga/嘲笑鸟-服务-flex
本地模拟器。 真正的API合同. 部分 嘲笑鸟。 。 。 .
国家模拟器 弹性 (withflex.com) HSA/FSA支付测试套件API:产品
(从记录的目录中回答) payment (一次),
subscription, (中文(简体) ), off_session 财务报告和审定财务报表 setup 模式,订阅,客户,设置意图,
退款,即 主机退出页面,而由 Svix 签名的 Webhooks Flex 贴出回。 驱动真实的UI检出
checkout.withflex.com 然后在5分钟的调和器上等在这里
毫秒:页面是本地的,签名的webhook一卡通即可到达应用程序
已接受.
- 行动范围: 支助
- Flex没有公布机器可读规格:合同(
openapi.yaml)是手写自 电线塑造了消费者的阅读和写作B/billing/flex/),和每一个字段的zod 满足了计划要求。 订阅模式,price_data.recurring和订阅 对象跟随 Flex API 参考文献 (这里) 没有沙盒记录).
安装
npm install -D @crvouga/mockingbird-service-flex
仅限无害环境管理。 节点 22或 Bun 1.2. 无土生属地. 给它服务
npx mockingbird-flex serve, (中文(简体) ), createServer 从 ./server (节点),或 createRuntime 带有任意
获取服务器 .
使用量
将应用程序指向模拟器:
| 信封 | 数值 |
|---|---|
FLEX_API_BASE_URL | http://127.0.0.1:8792 (或 …/__admin/ns/<namespace>(中文(简体) ) |
FLEX_API_KEY | 任意 fsk_test_… 键(测试模式); fsk_… 是活模式; 其他格式获得401 |
FLEX_WEBHOOK_SECRET | 与 --webhook-secret数字 : fwhsec_<base64> 或者说 whsec_<base64> |
npx mockingbird-flex serve --port 8792 \
--webhook-url http://127.0.0.1:3000/billing/webhooks/flex \
--webhook-secret "$FLEX_WEBHOOK_SECRET"
import { createServer } from "@crvouga/mockingbird-service-flex/server"
// Test setup: the emulator, posting signed webhooks to the app's handler.
const server = await createServer({
webhooks: {
url: "http://127.0.0.1:3000/billing/webhooks/flex",
secret: "fwhsec_ZmxleC1tb2NrLXNpZ25pbmcta2V5",
},
})
// Application code: FLEX_API_BASE_URL is server.url, FLEX_API_KEY any fsk_test_ key.
const baseUrl = server.url
const created = await fetch(`${baseUrl}/v1/checkout/sessions`, {
method: "POST",
headers: { "content-type": "application/json", authorization: "Bearer fsk_test_suite" },
body: JSON.stringify({
checkout_session: {
success_url: "https://app.test/done?session_id={CHECKOUT_SESSION_ID}",
cancel_url: "https://app.test/cart",
client_reference_id: "attempt-1",
line_items: [
{ price_data: { product: "fprod_01m0tgysj4ahvf8fas60c2ef2d", unit_amount: 4500 }, quantity: 1 },
],
},
}),
})
const { checkout_session } = (await created.json()) as {
checkout_session: { checkout_session_id: string; url: string }
}
// Test control: open checkout_session.url in the browser and pay with 4000 0512 3000 0072, or:
await fetch(`${baseUrl}/__admin/sessions/${checkout_session.checkout_session_id}/complete`, {
method: "POST",
})
await server.close()
浏览器 E2E 套件
运行 npx mockingbird-flex serve 在端口上,点 FLEX_API_BASE_URL 和浏览器
在模拟器的页面上登陆而不是 checkout.withflex.com: 没有网络, 没有共享 Flex
账户,而签名的webhook一旦点击Pay就立即到达了应用程序. 页面 URL 需要
模拟者的出处,或 --public-url 当给予。 按主机查找 Flex 标签的套件
(单位:千美元)/\bcheckout\.withflex\.com\b/与
--public-url http://checkout.withflex.com.localhost:8792:铬溶解于每个
*.localhost 名称以回转。 其他浏览器可能需要主机条目.
路线
所有API体被包裹: {product: {…}}, (中文(简体) ), {checkout_session: {…}}, (中文(简体) ), {customer: {…}}, (中文(简体) ),
{setup_intent: {…}}, (中文(简体) ), {products: […], has_more}, (中文(简体) ), {checkout_sessions: […], has_more}。 。 。 .
错误是 {error: {type, message, param?}}。 。 。 .
| 路线 | 行为 |
|---|---|
GET /v1/products?limit=&starting_after= | 古老的第一; limit 1–100(默认 10). 药丸先出,再出出产. |
POST /v1/products | {product: {name, description?, url?, client_reference_id?, metadata?}}新产品活跃, hsa_fsa_eligibility: null 直至保密(PUT /__admin/products/:id) (中文(简体) ). |
GET /v1/products/{id} | product_id, name, description, url, client_reference_id, hsa_fsa_eligibility, visit_type, active, test_mode, metadata, created_at。 。 。 . |
PATCH /v1/products/{id} | {product: {active?, name?, description?, url?, metadata?}}; 发射 product.updated。 。 。 . |
POST /v1/checkout/sessions | Idempotency-Key 很荣幸. mode payment (第1行项目), subscription (1行项目: price_data.recurring 实值 {interval: day|week|month|year, interval_count?}, 其他 400; 可选 subscription_data: {cancel_at_period_end?, metadata?}, (中文(简体) ), setup (客户和无细列项目,其他400个), off_session (客户+已保存) payment_method,在回答前充电:答复已经 complete,或者其扩大的付款意向是 requires_payment_method 编号 : requires_action 百分比 offSessionOutcome) (中文(简体) ). 未知或不活跃的产品、客户和支付方法为400个. redirect_url 财务报告和审定财务报表 url 是主机页面. |
GET /v1/checkout/sessions/{id}?expand_customer=true&expand_payment_intent=true | 会话; 扩展返回 customer 编号 : payment_intent 对象而不是标识. |
GET /v1/checkout/sessions?client_reference_id=&limit=&starting_after= | 最新第一(我们的模棱两可的恢复). |
POST /v1/checkout/sessions/{id}/refund | Idempotency-Key 很荣幸. {checkout_session: {}} (全文)或 {checkout_session: {amount}};400 当未付或资金过多时. |
POST /v1/customers | Idempotency-Key 很荣幸. {customer: {first_name, last_name, email, phone}} (所有要求). |
GET /v1/setup_intents/{id}?expand=customer,payment_method | setup_intent_id, status, customer, payment_method。 。 。 . |
GET /v1/subscriptions/{id} | {subscription: {subscription_id, status, items, customer, default_payment_method, cancel_at_period_end, current_period_start, current_period_end, canceled_at, metadata, test_mode, created_at}}。付费订阅模式会开始一个: active, (中文(简体) ), items = 其经常性细列项目,第一个经常性项目的间隔期(月/年步骤夹住月份的最后一天),记入刚刚使用的卡片 subscription 持有其身份证明. |
认证 : Authorization: Bearer fsk_test_… 或者说 fsk_…; 缺少密钥,或任何其他格式
(单位:千美元)sk_test_…, (中文(简体) ), whsec_…,为 401 authentication_error。 。 。 . test_mode 在创建对象上
钥匙 相同的密钥 + 相同的正体重放存储的响应; 相同的密钥 + 不同的正体是 400
idempotency_error;飞行中键同时提出的请求为409个.
主机页面
GET /pay/{sessionId} 制作一个普通形式( 无脚本) , 使用 data-testid编号
flex-mock-email, (中文(简体) ), -first-name, (中文(简体) ), -last-name, (中文(简体) ), -phone, (中文(简体) ), -card, (中文(简体) ), -exp, (中文(简体) ), -cvc, (中文(简体) ), -zip, (中文(简体) ),
-pay, (中文(简体) ), -cancel, (中文(简体) ), -error, (中文(简体) ), -amount,并在字母步骤上 -lmn-submit输入还携带
页:1 name和占位器 我们的密码定位器寻找(cardNumber, (中文(简体) ), expiry, (中文(简体) ), cvc, (中文(简体) ),
postalCode, (中文(简体) ), email(中文(简体) ). POST /pay/{sessionId} 提出:
| 纸牌 | 结果 |
|---|---|
4000 0512 3000 0072 | HSA卡:成功. |
4242 4242 4242 4242 (或任何其他有效卡) | 成功; 如果行项目的产品是 letter_of_medical_necessity,会话得到 next_action: {type: "collect_letter_of_medical_necessity", collect_letter_of_medical_necessity: {url}} 和浏览器跳到该步骤;提交它完成支付. |
4000 0000 0000 0002 | 缩写: 402页 <div role="alert">Your card was declined.</div>; 付款意向为 requires_payment_method。 。 。 . |
成功 302 个 success_url 与 {CHECKOUT_SESSION_ID} 替换( 绘图和
%7BCHECKOUT_SESSION_ID%7D(三); GET /pay/{id}/cancel (取消链接) cancel_url, (中文(简体) ),
会话开始。 在一个没有客户的会话上建立联系电子邮件 。 在一个
页面 URL 包含的命名空间 /__admin/ns/<name> (浏览器不发送信头); publicUrl
覆盖来源.
网络呼号
签署(svix-id, (中文(简体) ), svix-timestamp =墙钟, svix-signature: v1,<base64 HMAC-SHA256(key, "<id>.<ts>.<body>")>,密钥 = base64-解码后的秘密 fwhsec_编号 :whsec_) (中文(简体) ).
车身 : {event: {event_id, event_type, object, event_dt, test_mode, created_at}}。与Flex一样
OpenAPI (英语:EventObject和生产交付, object 持有一个键
资源: 退出事件 {checkout_session} (这样) object.checkout_session.checkout_session_id, (中文(简体) ),
付款意向事件 {payment_intent}, (中文(简体) ), charge.* 事件 {charge} (其意图是:
charge.payment_intent, (中文(简体) ), refund.* 财务报告和审定财务报表 charge.refund.updated 事件 {refund} (其意图是:
refund.payment_intent_id, (中文(简体) ), customer.subscription.* {subscription} 财务报告和审定财务报表 product.updated
{product}。付款意向、收费和退款对象名称为“不退出会话”:接收者
通过付款意向找到付款.
| 活动 | 何时 |
|---|---|
payment_intent.succeeded,则 checkout.session.completed | 页面(或 …/complete或非会期收费) |
customer.subscription.created (订阅),在这之前 | 订阅模式会话解决 |
checkout.session.async_payment_succeeded | 解决会议的意图 processing |
checkout.session.async_payment_failed | a 减少(页数、行政、会外) |
checkout.session.expired | …/expire,或 expires_at 传递到模拟器时钟( 默认 24 h) |
checkout.session.refunded (会话,每笔退款在 refunds,则 refund.created (两次), charge.refunded, (中文(简体) ), refund.updated, (中文(简体) ), charge.refund.updated | 每次退款,通过API或仪表板(POST /__admin/sessions/:id/refund) (中文(简体) ). 退货和收费事项是按订单生产的,提供一份仪表盘退货;每份退货 items 标明其付款意向 |
product.updated | PATCH /v1/products/{id} 财务报告和审定财务报表 PUT /__admin/products/:id |
checkout_session.completed, (中文(简体) ), checkout_session.expired | 别名,用: PUT /__admin/settings {"eventNaming": "underscored"} |
端点只接收它所订阅的事件类型, 如Flex的仪表板: pass
webhooks: {events: [...]} (默认:每类)或 PUT /__admin/webhook-endpoints订阅
所以测试不能通过事件制作
从来没有收到.
POST /__admin/events {type, session | product} 需要时会发出任何类型的信号。 非-2xx的答案是:
重审(即5秒、5分、30分、2小时); GET /__admin/webhooks, (中文(简体) ), …/events, (中文(简体) ),
POST /__admin/webhooks/flush, (中文(简体) ), …/:id/replay, (中文(简体) ), PUT /__admin/webhook-endpoints 和往常一样.
管理员(超过标准合同)
| 路线 | 效果 |
|---|---|
PUT /__admin/products/:id | {hsa_fsa_eligibility?, active?, test_mode?, visit_type?, client_reference_id?, name?, metadata?}; 发射 product.updated。 。 。 . |
POST /__admin/sessions/:id/complete | {card?}: 结算如已支付(HSA卡,除非 card 实值 4242…) (中文(简体) ). |
POST /__admin/sessions/:id/decline | 付款意向 requires_payment_method。 。 。 . |
POST /__admin/sessions/:id/expire | 届会议 ∗∗ expired。 。 。 . |
POST /__admin/sessions/:id/refund | {amount?}:在Flex的仪表板(默认全额)中退款;出出退事. 409,当未付时,400美元超过未注资的数额. |
POST /__admin/sessions/:id/require_action | {next_action_type?}数字 : collect_letter_of_medical_necessity (违约), provide_second_payment_method, (中文(简体) ), provide_alternative_payment_method, (中文(简体) ), payment_failed。 。 。 . |
PUT /__admin/sessions/:id/payment-intent | {status, amount_received?}数字 : requires_payment_method, (中文(简体) ), requires_action, (中文(简体) ), processing, (中文(简体) ), succeeded, (中文(简体) ), canceled。 。 。 . |
GET /__admin/sessions, (中文(简体) ), GET /__admin/sessions/:id | 命名空间的会话. |
POST /__admin/events | 为会话或产品发送任意事件类型 . |
GET/PUT /__admin/settings | {eventNaming, offSessionOutcome, sessionTtlSeconds, lmnOnRegularCard, publicUrl}。 。 。 . |
POST /__admin/tick | 现在到期的会话( 模拟器每100 ms 计时) . |
每个管弦乐团状态都可以达到:待定(开放),行动(需要)require_action,或一个
意图 requires_action),处理,取消(意图) canceled,或已过期),失败
(单位:千美元)decline),成功,退款(全额退款),隔离(部分退款,
amount_mismatch,重复会议).
错误预设( E)POST /__admin/faults {"preset": "<name>", "count"?: n}· ; GET /__admin/faults/presets:
create_4xx 四百个, create_5xx (创造,然后500:恢复通过),
create_5xx_not_created, (中文(简体) ), timeout (创建, 16 s后答案, 超过客户端的 15 s 中止 ;
params.delayMs 超过, invalid_shape (无) redirect_url编号 :url, (中文(简体) ), amount_mismatch
(单位:千美元)amount_total +100), (中文(简体) ), duplicate_sessions_for_client_reference (两会,然后500),
refund_4xx, (中文(简体) ), server_error, (中文(简体) ), webhook_duplicate, (中文(简体) ), webhook_reorder, (中文(简体) ), webhook_drop。 。 。 .
命名空间
x-mockingbird-namespace页:1 /__admin/ns/<name> 前缀在 FLEX_API_BASE_URL,或用 API 键:
PUT /__admin/credentials {"credentials": {"<FLEX_API_KEY>": "<namespace>"}}。 。 。 .
企业
src/corpus/products.ts 是消费者每一行的产品方面
flexCatalogMappings 参考固定图 (663行,以
bun scripts/corpus.ts <fixture.json>:产品代号、客户参考、 acme_purpose 编号 :
acme_merchant_product_id 编号 : acme_client_reference_id 元数据我们的目录验证
比较、资格和访问类型。 每个产品都是活性的 测试模式,所以我们的验证
复制每个绘图行自己的 active 旗帜。 没有沙盒记录( 没有证书) ,
所以产品名称被合成.
故意不模拟
- 真实的卡片处理,花旗iframes和分价支付:页面是一个平面形式,以及
只能以
next_action: provide_second_payment_method。 。 。 . - 医疗需要函调查表:一个提交按钮就位.
- 测试/活数据分离:活键看到相同的对象(仅限
test_mode页:1. - 离职后订阅生命周期:续订、发票和
invoice.*事件, 审判,customer.subscription.updated编号 :.deleted,以及订阅更新/取消 路线。 订户留下来active其第一个时期。 价格price编号在 使用内线price_data。 。 。 . - 债券、晋升守则
allow_promotion_codes部分捕捉, 争议. - Flex的准确错误文本和ID:形状跟随我们的消费者所读;ID长相像
fprod_01z…, (中文(简体) ),fcs_01z…, (中文(简体) ),fcus_…, (中文(简体) ),fpi_…, (中文(简体) ),fseti_…, (中文(简体) ),fpm_…, (中文(简体) ),fevt_…。 。 。 .
API 密码
| 导出 | 仁爱 | 说明 |
|---|---|---|
FlexAPI | 类 | 进程模拟器 : fetch(request), (中文(简体) ), reset(), (中文(简体) ), settle(id), (中文(简体) ), decline(id), (中文(简体) ), expire(id), (中文(简体) ), requireAction(id, type), (中文(简体) ), setPaymentIntent(id, patch), (中文(简体) ), applyRefund(id, amount), (中文(简体) ), dashboardRefund(id, amount?), (中文(简体) ), putProduct(product), (中文(简体) ), emitFor(type, target), (中文(简体) ), tick(), (中文(简体) ), sessions(), (中文(简体) ), subscriptions(), (中文(简体) ), present(session, view)选项: sqlite, (中文(简体) ), now, (中文(简体) ), namespace, (中文(简体) ), publicNamespace, (中文(简体) ), products, (中文(简体) ), settings, (中文(简体) ), onEvent。 。 。 . |
createRuntime | 函数 | 服务合同的模拟者。 选项 : webhooks: {url, secret, events?, retryDelaysMs?, fetch?}, (中文(简体) ), products, (中文(简体) ), settings, (中文(简体) ), tickMs, (中文(简体) ), clock, (中文(简体) ), seed, (中文(简体) ), adminKey, (中文(简体) ), onLog。 。 。 . |
FLEX_PRESETS | 对象 | 每个命名的断层预设. |
FLEX_NAMESPACE | 字符串 | 服务名称, "flex"。 。 。 . |
FLEX_EVENT_TYPES | 数组 | 包括了每个网络事件类型,别名. |
eventObjectKey | 函数 | 事件类型的密钥 object 被嵌入(refund.created → refund, (中文(简体) ), charge.refund.updated → refund) (中文(简体) ). |
keyMode | 函数 | "test" 页:1 fsk_test_…, (中文(简体) ), "live" 页:1 fsk_…,否则 undefined。 。 。 . |
isNextActionType | 函数 | 字符串是否为下一个动作类型 . |
CARDS, (中文(简体) ), classifyCard, (中文(简体) ), substituteSessionId | 数值 | 主机页面的测试卡,它的卡分类器,以及 {CHECKOUT_SESSION_ID} 替换. |
CORPUS_ROWS, (中文(简体) ), corpusProduct | 数值 | 记录的产品体及其行-产品映射. |
DEFAULT_SETTINGS, (中文(简体) ), ELIGIBILITIES, (中文(简体) ), NEXT_ACTION_TYPES, (中文(简体) ), PAYMENT_INTENT_STATUSES, (中文(简体) ), SUBSCRIPTION_STATUSES | 数值 | 默认和enums. |
periodEnd | 函数 | periodEnd(startMs, {interval, interval_count?}): 计费期的结束(划时代 ms). |
document, (中文(简体) ), operationIds, (中文(简体) ), supportedOperationIds | 数值 | 销售商为OpenAPI合同及其业务代号. |
createServer, (中文(简体) ), serveTarget, (中文(简体) ), DEFAULT_PORT (单位:千美元)./server(中文(简体) ) | 节点 | 服务过来 node:http (过期时每100毫秒划勾) serve CLI目标(--webhook-url, (中文(简体) ), --webhook-secret, (中文(简体) ), --public-url, (中文(简体) ), --event-naming; 港口8792. |
部分 嘲笑鸟。 。 。 .