@posx/yeahpay
v1.1.1
Published
YeahPay payment integration library
Readme
@posx/yeahpay
YeahPay(云 MISPOS)支付协议封装。库只抽象协议,轮询、重试、UI 全部留给业务。
✨ 特性
- 🔐 报文加解密与签名(RSA-2048 + AES-128-CBC + SHA-256)
- 🏦 银行卡、二维码主扫、二维码被扫
- 🧭 支付状态判读:把协议原文归一成
pending / paid / unpaid - 💰 退款、当日撤销、取消订单、结算、重打印
- 🧪 内置模拟,便于开发
- 📱 基于 Capacitor HTTP,无 CORS 限制
- 📝 TypeScript 类型完整
📦 安装
npm install @posx/yeahpay需要 Capacitor 环境(v5/v6/v7)。若项目未安装:npm install @capacitor/core。
🚀 快速开始
import { YeahPayClient, IYeahPayApiConfig } from '@posx/yeahpay'
const config: IYeahPayApiConfig = {
asyncUrl: 'https://your-yeahpay-server.com/async',
syncUrl: 'https://your-yeahpay-server.com/sync', // 同步端点
appId: 'YOUR_APP_ID',
deviceSn: 'YOUR_DEVICE_SERIAL',
serverPublicKey: 'YOUR_SERVER_RSA_PUBLIC_KEY', // PKCS8 PEM
clientPrivateKey: 'YOUR_CLIENT_RSA_PRIVATE_KEY', // PKCS8 PEM
signatureKey: 'YOUR_SIGNATURE_KEY'
}
const client = new YeahPayClient(config)syncUrl 是必填项。走同步端点的有三个方法:createSyncCardCharge、queryOrderSync、cancelOrder。
缺了它,这三个方法会把请求打到字面量 undefined 拼成的地址上(库当前不校验,1.0.x 起即如此)。
🧭 支付状态判读(v1.1.0 新增)
这是本版本的核心。以前业务要自己拆 code、data.status、data.success、HTTP 状态码,
才能判断「这笔钱到底收没收到」。现在库直接给结论。
三个新字段
发起支付 / 订单查询 / 取消订单的返回值上,多了三个可选字段:
interface IYeahPayResponse {
// —— 原有字段,行为完全不变 ——
success: boolean // 仅表示 code === 0(网关受理),不表示支付成功
code: string
message: string
data?: any
traceId?: string
isTestData?: boolean
// —— v1.1.0 新增 ——
outcome?: YeahPayOutcome // 这笔支付当前收没收到钱
reason?: YeahPayReason // 归类后的原因,给界面文案 / i18n
diagnostic?: string // 原始定位信息,给日志
}outcome —— 业务唯一需要看的字段
| 值 | 含义 | 业务该做什么 |
|---|---|---|
| 'paid' | 已收到钱 | 出货 / 打小票,终态 |
| 'unpaid' | 确定没收到,且不会再收到 | 提示失败,终态 |
| 'pending' | 还没定 | 继续轮询 queryOrder |
| undefined | 本次操作不描述支付状态 | 见下方说明 |
const r = await client.queryOrder({ bizOrderId })
if (r.outcome === 'paid') return onSuccess()
if (r.outcome === 'unpaid') return onFailed(r.reason)
// pending 或 undefined → 继续轮询判断支付结果请用 outcome,不要用 success。 success 只代表网关受理了这次请求,
一笔被持卡人取消的支付同样会 success: false, code: '-1027' —— 但那是明确的「没收到钱」,
而网关 500 也是 success: false —— 那是「还不知道」。两者业务处理完全相反。
outcome 为 undefined 的两种情况
- 该操作描述的不是这笔支付 —— 退款、当日撤销、结算、重打印。 退款成功不等于原支付没收到钱,所以库不填。
- 遇到未知业务码 —— 库不猜。业务按
pending处理(继续轮询)即可, 查询接口最终会给出确定结论。
注意:
simulateCreateCharge/simulateQueryOrder不产出outcome。 如果开发模式依赖模拟接口,需自行处理,否则会一直轮询下去。
reason —— 给用户看的原因
type YeahPayReason =
| 'cancelled_by_user' | 'card_error' | 'terminal_timeout' | 'issuer_declined'
| 'order_closed' | 'terminal_unbound'| 'terminal_offline' | 'invalid_request'
| 'duplicate_order' | 'auth_failed' | 'rate_limited' | 'gateway_error'
| 'network_error' | 'cancel_rejected' | 'order_not_found'有限枚举,直接拿去做 i18n key。不要把 message 直接显示给用户 —— 那是网关原文,
中英混杂、措辞随版本变。
diagnostic —— 给日志看的原文
格式 <层>/<码>[#<子码>],一眼能看出问题出在哪一层:
| 样例 | 含义 |
|---|---|
| gateway/-1027 | 网关业务码 |
| gateway/-1028#340 | 读卡失败,附终端子码(从 message 尾部 (340) 提取) |
| http/502 | HTTP 层错误,网关业务逻辑没跑到 |
| transport/ECONNREFUSED | 请求没出得去客户端 |
| client/missing_authCode | 参数校验,请求根本没发 |
打日志时带上 diagnostic 和 traceId,排查时可直接对上网关侧记录。
传输失败时
网络异常时库仍然抛错(与旧版一致),但会在 error 对象上挂一个 yeahpay 属性:
try {
await client.createCardCharge({ amount, bizOrderId })
} catch (err: any) {
const v = err.yeahpay // { outcome, reason, diagnostic } | undefined
if (v?.outcome === 'unpaid') return onFailed(v.reason) // 确定没发出去
// 否则订单可能已创建,必须去查,不能直接判失败
}ECONNREFUSED、UnknownHostException 这类「请求从没离开客户端」的错误判 unpaid;
超时、连接中断判 pending —— 请求可能已经到达网关,订单可能已创建。
🔧 API 概览
class YeahPayClient {
constructor(config: IYeahPayApiConfig)
// 发起支付(填 outcome)
createCardCharge(req: IYeahPayCreateChargeRequest): Promise<IYeahPayResponse>
createSyncCardCharge(req: IYeahPayCreateChargeRequest): Promise<IYeahPayResponse>
createQRScanCharge(req: IYeahPayCreateChargeRequest): Promise<IYeahPayResponse> // 主扫,需 authCode
createQRCodeCharge(req: IYeahPayCreateChargeRequest): Promise<IYeahPayResponse> // 被扫,需 payWay
// 查询(填 outcome)
queryOrder(req: IYeahPayQueryRequest): Promise<IYeahPayResponse>
queryOrderSync(req: IYeahPayQueryRequest): Promise<IYeahPayResponse>
// 取消订单(填 outcome)
cancelOrder(bizOrderId: string): Promise<IYeahPayResponse>
// 以下不填 outcome —— 描述的不是原支付
refundCardCharge(req: IYeahPayRefundRequest): Promise<IYeahPayResponse>
cancelCardCharge(req: Omit<IYeahPayRefundRequest, 'amount'>): Promise<IYeahPayResponse>
refundQRCharge(req: IYeahPayRefundRequest): Promise<IYeahPayResponse>
settlement(bizSettlementFlowId: string): Promise<IYeahPayResponse>
reprint(bizOrderId: string, bizRefundId?: string): Promise<IYeahPayResponse>
// 模拟(不填 outcome)
simulateCreateCharge(req: IYeahPaySimulateChargeRequest): Promise<IYeahPayResponse>
simulateQueryOrder(req: IYeahPayQueryRequest): Promise<IYeahPayResponse>
}判读逻辑本身也单独导出,便于业务对历史数据复判:
import { interpret } from '@posx/yeahpay'⬆️ 从 1.0.x 升级到 1.1.0
业务代码零改动即可升级。 新增的三个字段都是可选的,原有六个字段的取值、 请求报文的组装(URL、header、加密体)与 1.0.18 逐字段一致。
升级后建议按下面改造,把手写的状态判断删掉:
// Before —— 业务自己拆协议
if (r.success && r.data?.status === 2) onSuccess()
else if (r.code === '-1027') onCancelled()
else if (r.data?.success === 'true') onSuccess()
else poll()
// After
if (r.outcome === 'paid') onSuccess()
if (r.outcome === 'unpaid') onFailed(r.reason)
// 其余继续轮询同时检查:配置里是否补了 syncUrl;日志里是否带上了 diagnostic。
🧪 运行
npm test # 单元测试
npm run test:integration
npm run build📄 许可证
MIT(见 LICENSE)
版本:v1.1.0
