@vobs/payment 0.3.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +182 -0
- package/dist/alipay/client.cjs +112 -0
- package/dist/alipay/client.cjs.map +1 -0
- package/dist/alipay/client.d.cts +36 -0
- package/dist/alipay/client.d.ts +36 -0
- package/dist/alipay/client.js +110 -0
- package/dist/alipay/client.js.map +1 -0
- package/dist/alipay/config.cjs +67 -0
- package/dist/alipay/config.cjs.map +1 -0
- package/dist/alipay/config.d.cts +30 -0
- package/dist/alipay/config.d.ts +30 -0
- package/dist/alipay/config.js +65 -0
- package/dist/alipay/config.js.map +1 -0
- package/dist/alipay/index.cjs +592 -0
- package/dist/alipay/index.cjs.map +1 -0
- package/dist/alipay/index.d.cts +12 -0
- package/dist/alipay/index.d.ts +12 -0
- package/dist/alipay/index.js +580 -0
- package/dist/alipay/index.js.map +1 -0
- package/dist/alipay/notify.cjs +70 -0
- package/dist/alipay/notify.cjs.map +1 -0
- package/dist/alipay/notify.d.cts +52 -0
- package/dist/alipay/notify.d.ts +52 -0
- package/dist/alipay/notify.js +68 -0
- package/dist/alipay/notify.js.map +1 -0
- package/dist/alipay/payment.cjs +49 -0
- package/dist/alipay/payment.cjs.map +1 -0
- package/dist/alipay/payment.d.cts +24 -0
- package/dist/alipay/payment.d.ts +24 -0
- package/dist/alipay/payment.js +47 -0
- package/dist/alipay/payment.js.map +1 -0
- package/dist/alipay/plugin.cjs +300 -0
- package/dist/alipay/plugin.cjs.map +1 -0
- package/dist/alipay/plugin.d.cts +43 -0
- package/dist/alipay/plugin.d.ts +43 -0
- package/dist/alipay/plugin.js +296 -0
- package/dist/alipay/plugin.js.map +1 -0
- package/dist/alipay/query.cjs +80 -0
- package/dist/alipay/query.cjs.map +1 -0
- package/dist/alipay/query.d.cts +26 -0
- package/dist/alipay/query.d.ts +26 -0
- package/dist/alipay/query.js +78 -0
- package/dist/alipay/query.js.map +1 -0
- package/dist/alipay/refund.cjs +104 -0
- package/dist/alipay/refund.cjs.map +1 -0
- package/dist/alipay/refund.d.cts +32 -0
- package/dist/alipay/refund.d.ts +32 -0
- package/dist/alipay/refund.js +102 -0
- package/dist/alipay/refund.js.map +1 -0
- package/dist/alipay/response.cjs +38 -0
- package/dist/alipay/response.cjs.map +1 -0
- package/dist/alipay/response.d.cts +19 -0
- package/dist/alipay/response.d.ts +19 -0
- package/dist/alipay/response.js +35 -0
- package/dist/alipay/response.js.map +1 -0
- package/dist/alipay/types.cjs +4 -0
- package/dist/alipay/types.cjs.map +1 -0
- package/dist/alipay/types.d.cts +242 -0
- package/dist/alipay/types.d.ts +242 -0
- package/dist/alipay/types.js +3 -0
- package/dist/alipay/types.js.map +1 -0
- package/dist/alipay/wap.cjs +50 -0
- package/dist/alipay/wap.cjs.map +1 -0
- package/dist/alipay/wap.d.cts +24 -0
- package/dist/alipay/wap.d.ts +24 -0
- package/dist/alipay/wap.js +48 -0
- package/dist/alipay/wap.js.map +1 -0
- package/dist/index-B1aUR7Vt.d.ts +49 -0
- package/dist/index-B8pzZxGq.d.cts +44 -0
- package/dist/index-BWnIrwRi.d.ts +44 -0
- package/dist/index-Dyrkl86r.d.cts +49 -0
- package/dist/index.cjs +1071 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -37
- package/dist/index.js +1052 -37
- package/dist/index.js.map +1 -0
- package/dist/wechat/client.cjs +119 -0
- package/dist/wechat/client.cjs.map +1 -0
- package/dist/wechat/client.d.cts +39 -0
- package/dist/wechat/client.d.ts +39 -0
- package/dist/wechat/client.js +117 -0
- package/dist/wechat/client.js.map +1 -0
- package/dist/wechat/config.cjs +76 -0
- package/dist/wechat/config.cjs.map +1 -0
- package/dist/wechat/config.d.cts +29 -0
- package/dist/wechat/config.d.ts +29 -0
- package/dist/wechat/config.js +74 -0
- package/dist/wechat/config.js.map +1 -0
- package/dist/wechat/index.cjs +612 -0
- package/dist/wechat/index.cjs.map +1 -0
- package/dist/wechat/index.d.cts +10 -0
- package/dist/wechat/index.d.ts +10 -0
- package/dist/wechat/index.js +602 -0
- package/dist/wechat/index.js.map +1 -0
- package/dist/wechat/notify.cjs +73 -0
- package/dist/wechat/notify.cjs.map +1 -0
- package/dist/wechat/notify.d.cts +53 -0
- package/dist/wechat/notify.d.ts +53 -0
- package/dist/wechat/notify.js +71 -0
- package/dist/wechat/notify.js.map +1 -0
- package/dist/wechat/payment.cjs +186 -0
- package/dist/wechat/payment.cjs.map +1 -0
- package/dist/wechat/payment.d.cts +50 -0
- package/dist/wechat/payment.d.ts +50 -0
- package/dist/wechat/payment.js +184 -0
- package/dist/wechat/payment.js.map +1 -0
- package/dist/wechat/plugin.cjs +305 -0
- package/dist/wechat/plugin.cjs.map +1 -0
- package/dist/wechat/plugin.d.cts +40 -0
- package/dist/wechat/plugin.d.ts +40 -0
- package/dist/wechat/plugin.js +301 -0
- package/dist/wechat/plugin.js.map +1 -0
- package/dist/wechat/refund.cjs +112 -0
- package/dist/wechat/refund.cjs.map +1 -0
- package/dist/wechat/refund.d.cts +26 -0
- package/dist/wechat/refund.d.ts +26 -0
- package/dist/wechat/refund.js +110 -0
- package/dist/wechat/refund.js.map +1 -0
- package/dist/wechat/response.cjs +50 -0
- package/dist/wechat/response.cjs.map +1 -0
- package/dist/wechat/response.d.cts +20 -0
- package/dist/wechat/response.d.ts +20 -0
- package/dist/wechat/response.js +46 -0
- package/dist/wechat/response.js.map +1 -0
- package/dist/wechat/types.cjs +4 -0
- package/dist/wechat/types.cjs.map +1 -0
- package/dist/wechat/types.d.cts +200 -0
- package/dist/wechat/types.d.ts +200 -0
- package/dist/wechat/types.js +3 -0
- package/dist/wechat/types.js.map +1 -0
- package/package.json +37 -28
- package/src/alipay/alipay.test.ts +231 -0
- package/src/alipay/client.ts +67 -0
- package/src/alipay/config.ts +72 -0
- package/src/alipay/index.ts +32 -0
- package/src/alipay/notify.ts +92 -0
- package/src/alipay/payment.ts +60 -0
- package/src/alipay/plugin.ts +64 -0
- package/src/alipay/query.ts +52 -0
- package/src/alipay/refund.ts +85 -0
- package/src/alipay/response.ts +40 -0
- package/src/alipay/types.ts +271 -0
- package/src/alipay/wap.ts +55 -0
- package/src/index.ts +78 -0
- package/src/wechat/client.ts +68 -0
- package/src/wechat/config.ts +81 -0
- package/src/wechat/index.ts +30 -0
- package/src/wechat/notify.ts +93 -0
- package/src/wechat/payment.ts +177 -0
- package/src/wechat/plugin.ts +59 -0
- package/src/wechat/refund.ts +80 -0
- package/src/wechat/response.ts +54 -0
- package/src/wechat/types.ts +223 -0
- package/src/wechat/wechat.test.ts +393 -0
- package/dist/alipay-official.d.ts +0 -34
- package/dist/alipay-official.js +0 -206
- package/dist/handlers.d.ts +0 -45
- package/dist/handlers.js +0 -105
- package/dist/jeepay.d.ts +0 -39
- package/dist/jeepay.js +0 -206
- package/dist/lakala.d.ts +0 -41
- package/dist/lakala.js +0 -243
- package/dist/manager.d.ts +0 -67
- package/dist/manager.js +0 -538
- package/dist/mock-driver.d.ts +0 -22
- package/dist/mock-driver.js +0 -59
- package/dist/state-machine.d.ts +0 -41
- package/dist/state-machine.js +0 -60
- package/dist/store.d.ts +0 -33
- package/dist/store.js +0 -76
- package/dist/types.d.ts +0 -168
- package/dist/types.js +0 -11
- package/dist/verify.d.ts +0 -34
- package/dist/verify.js +0 -55
- package/dist/wechat-official.d.ts +0 -40
- package/dist/wechat-official.js +0 -208
package/dist/wechat-official.js
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/** @license MIT
|
|
2
|
-
* Copyright (c) 2026 vobsjs
|
|
3
|
-
* @vobs/payment
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* WeChat Pay official driver (API v3) — Native (PC scan) / JSAPI (official account, miniapp webview) / H5.
|
|
7
|
-
*
|
|
8
|
-
* Covered scenes (web):
|
|
9
|
-
* - `pc` → Native payment → `{kind:'qr', qrUrl: code_url}`
|
|
10
|
-
* - `h5` + openid → JSAPI payment → `{kind:'jsapi', payParams}` (official account / WeChat built-in browser)
|
|
11
|
-
* - `h5` (no openid) → H5 payment → `{kind:'redirect', payUrl: h5_url}`
|
|
12
|
-
* - `miniapp` + openid → JSAPI payment (miniapp webview)
|
|
13
|
-
*
|
|
14
|
-
* Security:
|
|
15
|
-
* - Request signing: Authorization `WECHATPAY2-SHA256-RSA2048` (merchant private key SHA256withRSA)
|
|
16
|
-
* - Callback: AES-256-GCM decryption (apiV3Key) + optional platform-cert verification (enforced when platformPublicKeyPem is set) + time-window/nonce anti-replay
|
|
17
|
-
* - Channel self-verification path: callbacks bypass the framework's generic HMAC protocol; this driver verifies the channel itself
|
|
18
|
-
*
|
|
19
|
-
* Dependencies: node:crypto (Node built-in, zero third-party). Sandbox needs sandbox keys/certs from the merchant platform (configure baseUrl per the sandbox docs when integrating).
|
|
20
|
-
*/
|
|
21
|
-
import { createDecipheriv, createPrivateKey, createPublicKey, randomBytes, sign, verify, } from 'node:crypto';
|
|
22
|
-
const DEFAULT_BASE = 'https://api.mch.weixin.qq.com';
|
|
23
|
-
function isWxResource(v) {
|
|
24
|
-
if (typeof v !== 'object' || v === null)
|
|
25
|
-
return false;
|
|
26
|
-
const r = v;
|
|
27
|
-
return typeof r['ciphertext'] === 'string' && typeof r['nonce'] === 'string';
|
|
28
|
-
}
|
|
29
|
-
export function createWechatOfficialDriver(store, options) {
|
|
30
|
-
const baseUrl = options.baseUrl ?? DEFAULT_BASE;
|
|
31
|
-
const timeWindowSec = options.timeWindowSec ?? 300;
|
|
32
|
-
const fetchImpl = options.fetchImpl ?? ((...args) => fetch(...args));
|
|
33
|
-
const privateKey = createPrivateKey(options.privateKeyPem);
|
|
34
|
-
const platformKey = options.platformPublicKeyPem
|
|
35
|
-
? createPublicKey(options.platformPublicKeyPem)
|
|
36
|
-
: undefined;
|
|
37
|
-
/** API v3 request signature header. */
|
|
38
|
-
function authorizeHeader(method, urlPath, body) {
|
|
39
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
40
|
-
const nonce = randomBytes(16).toString('hex');
|
|
41
|
-
const message = `${method}\n${urlPath}\n${timestamp}\n${nonce}\n${body}\n`;
|
|
42
|
-
const signature = sign('RSA-SHA256', Buffer.from(message), privateKey).toString('base64');
|
|
43
|
-
return `WECHATPAY2-SHA256-RSA2048 mchid="${options.mchId}",nonce_str="${nonce}",signature="${signature}",timestamp="${timestamp}",serial_no="${options.serialNo}"`;
|
|
44
|
-
}
|
|
45
|
-
async function request(method, urlPath, body) {
|
|
46
|
-
const headers = {
|
|
47
|
-
Authorization: authorizeHeader(method, urlPath, body ?? ''),
|
|
48
|
-
Accept: 'application/json',
|
|
49
|
-
};
|
|
50
|
-
if (body !== undefined)
|
|
51
|
-
headers['Content-Type'] = 'application/json';
|
|
52
|
-
const res = await fetchImpl(`${baseUrl}${urlPath}`, {
|
|
53
|
-
method,
|
|
54
|
-
headers,
|
|
55
|
-
...(body !== undefined ? { body } : {}),
|
|
56
|
-
});
|
|
57
|
-
const text = await res.text();
|
|
58
|
-
if (!res.ok) {
|
|
59
|
-
throw new Error(`wechat_api_error:${res.status}:${text}`);
|
|
60
|
-
}
|
|
61
|
-
return JSON.parse(text);
|
|
62
|
-
}
|
|
63
|
-
/** JSAPI invocation params (for the frontend wx.requestPayment): paySign signed with the merchant private key. */
|
|
64
|
-
function buildJsapiParams(prepayId) {
|
|
65
|
-
const timeStamp = String(Math.floor(Date.now() / 1000));
|
|
66
|
-
const nonceStr = randomBytes(16).toString('hex');
|
|
67
|
-
const pkg = `prepay_id=${prepayId}`;
|
|
68
|
-
const message = `${options.appId}\n${timeStamp}\n${nonceStr}\n${pkg}\n`;
|
|
69
|
-
const paySign = sign('RSA-SHA256', Buffer.from(message), privateKey).toString('base64');
|
|
70
|
-
return {
|
|
71
|
-
appId: options.appId,
|
|
72
|
-
timeStamp,
|
|
73
|
-
nonceStr,
|
|
74
|
-
package: pkg,
|
|
75
|
-
signType: 'RSA',
|
|
76
|
-
paySign,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
async function createOrder(input) {
|
|
80
|
-
const base = {
|
|
81
|
-
appid: options.appId,
|
|
82
|
-
mchid: options.mchId,
|
|
83
|
-
description: input.subject,
|
|
84
|
-
out_trade_no: input.orderId,
|
|
85
|
-
notify_url: input.notifyUrl,
|
|
86
|
-
amount: { total: input.amount, currency: 'CNY' },
|
|
87
|
-
};
|
|
88
|
-
const openid = input.extras?.['openid'];
|
|
89
|
-
if (input.scene === 'miniapp' || (input.scene === 'h5' && typeof openid === 'string')) {
|
|
90
|
-
// JSAPI (official account / miniapp webview; requires openid)
|
|
91
|
-
const res = await request('POST', '/v3/pay/transactions/jsapi', JSON.stringify({ ...base, payer: { openid } }));
|
|
92
|
-
return { kind: 'jsapi', payParams: buildJsapiParams(res.prepay_id) };
|
|
93
|
-
}
|
|
94
|
-
if (input.scene === 'pc') {
|
|
95
|
-
// Native scan-to-pay
|
|
96
|
-
const res = await request('POST', '/v3/pay/transactions/native', JSON.stringify(base));
|
|
97
|
-
return {
|
|
98
|
-
kind: 'qr',
|
|
99
|
-
qrUrl: res.code_url,
|
|
100
|
-
expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
// H5 (mobile browser)
|
|
104
|
-
const h5Input = {
|
|
105
|
-
...base,
|
|
106
|
-
scene_info: {
|
|
107
|
-
payer_client_ip: input.extras?.['clientIp'] ?? '127.0.0.1',
|
|
108
|
-
h5_info: { type: 'Wap' },
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
const res = await request('POST', '/v3/pay/transactions/h5', JSON.stringify(h5Input));
|
|
112
|
-
return { kind: 'redirect', payUrl: res.h5_url };
|
|
113
|
-
}
|
|
114
|
-
function verifyCallback(payload, headers) {
|
|
115
|
-
const h = (headers ?? {});
|
|
116
|
-
const ts = h['wechatpay-timestamp'];
|
|
117
|
-
const nonce = h['wechatpay-nonce'];
|
|
118
|
-
const signature = h['wechatpay-signature'];
|
|
119
|
-
if (!ts || !nonce) {
|
|
120
|
-
return { ok: false, reason: 'missing_wechatpay_headers' };
|
|
121
|
-
}
|
|
122
|
-
// time-window check (anti-replay)
|
|
123
|
-
if (Math.abs(Date.now() / 1000 - Number(ts)) > timeWindowSec) {
|
|
124
|
-
return { ok: false, reason: 'timestamp_expired' };
|
|
125
|
-
}
|
|
126
|
-
// optional: platform-cert verification (signed string = timestamp\nnonce\nbody\n)
|
|
127
|
-
if (platformKey) {
|
|
128
|
-
const rawBody = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
129
|
-
const ok = verify('RSA-SHA256', Buffer.from(`${ts}\n${nonce}\n${rawBody}\n`), platformKey, Buffer.from(signature ?? '', 'base64'));
|
|
130
|
-
if (!ok) {
|
|
131
|
-
return { ok: false, reason: 'bad_platform_signature' };
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
// AES-256-GCM decrypt (apiV3Key confidentiality → successful decryption proves the message came from WeChat)
|
|
135
|
-
let obj;
|
|
136
|
-
try {
|
|
137
|
-
obj =
|
|
138
|
-
typeof payload === 'string'
|
|
139
|
-
? JSON.parse(payload)
|
|
140
|
-
: payload;
|
|
141
|
-
const resource = obj['resource'];
|
|
142
|
-
if (!isWxResource(resource)) {
|
|
143
|
-
return { ok: false, reason: 'bad_resource' };
|
|
144
|
-
}
|
|
145
|
-
// WeChat ciphertext format: ciphertext = encrypted || authTag(16B)
|
|
146
|
-
const buf = Buffer.from(resource.ciphertext, 'base64');
|
|
147
|
-
const decipher = createDecipheriv('aes-256-gcm', Buffer.from(options.apiV3Key), Buffer.from(resource.nonce));
|
|
148
|
-
if (resource.associated_data)
|
|
149
|
-
decipher.setAAD(Buffer.from(resource.associated_data));
|
|
150
|
-
decipher.setAuthTag(buf.subarray(buf.length - 16));
|
|
151
|
-
const plain = Buffer.concat([
|
|
152
|
-
decipher.update(buf.subarray(0, buf.length - 16)),
|
|
153
|
-
decipher.final(),
|
|
154
|
-
]).toString('utf8');
|
|
155
|
-
const data = JSON.parse(plain);
|
|
156
|
-
// Only consume the nonce after platform authentication and AES-GCM decryption succeed.
|
|
157
|
-
if (!store.addNonce(`wx:${nonce}`)) {
|
|
158
|
-
return { ok: false, reason: 'nonce_reused' };
|
|
159
|
-
}
|
|
160
|
-
if (data['trade_state'] !== 'SUCCESS') {
|
|
161
|
-
return { ok: false, reason: `trade_state:${String(data['trade_state'])}` };
|
|
162
|
-
}
|
|
163
|
-
const amount = data['amount'];
|
|
164
|
-
const orderId = data['out_trade_no'];
|
|
165
|
-
const paidAmount = amount?.['total'];
|
|
166
|
-
if (typeof orderId !== 'string' || typeof paidAmount !== 'number') {
|
|
167
|
-
return { ok: false, reason: 'bad_payload' };
|
|
168
|
-
}
|
|
169
|
-
return {
|
|
170
|
-
ok: true,
|
|
171
|
-
orderId,
|
|
172
|
-
paidAmount,
|
|
173
|
-
...(typeof data['transaction_id'] === 'string'
|
|
174
|
-
? { channelTxnId: data['transaction_id'] }
|
|
175
|
-
: {}),
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
return { ok: false, reason: 'decrypt_failed' };
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
async function queryOrder(orderId) {
|
|
183
|
-
try {
|
|
184
|
-
const urlPath = `/v3/pay/transactions/out-trade-no/${encodeURIComponent(orderId)}?mchid=${options.mchId}`;
|
|
185
|
-
const res = await request('GET', urlPath);
|
|
186
|
-
const state = res.trade_state;
|
|
187
|
-
if (state === 'SUCCESS') {
|
|
188
|
-
return { state: 'paid', ...(res.success_time ? { paidAt: res.success_time } : {}) };
|
|
189
|
-
}
|
|
190
|
-
if (state === 'CLOSED' || state === 'PAYERROR') {
|
|
191
|
-
return { state: 'closed' };
|
|
192
|
-
}
|
|
193
|
-
return { state: 'created' };
|
|
194
|
-
}
|
|
195
|
-
catch {
|
|
196
|
-
return { state: 'unknown', raw: { error: 'query_failed' } };
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return {
|
|
200
|
-
name: 'wechat_official',
|
|
201
|
-
scenes: ['pc', 'h5', 'miniapp'],
|
|
202
|
-
callbackVerification: 'driver',
|
|
203
|
-
createOrder,
|
|
204
|
-
verifyCallback,
|
|
205
|
-
queryOrder,
|
|
206
|
-
successResponseText: 'SUCCESS',
|
|
207
|
-
};
|
|
208
|
-
}
|