feat(M08-A): 接入充值微信支付调起
This commit is contained in:
@@ -21,6 +21,7 @@ function createHarness(overrides = {}) {
|
||||
...overrides.plan
|
||||
},
|
||||
orders: [],
|
||||
callbacks: [],
|
||||
nextOrderId: 900,
|
||||
purchaseCount: overrides.purchaseCount ?? 0,
|
||||
walletCredits: []
|
||||
@@ -71,12 +72,60 @@ function createHarness(overrides = {}) {
|
||||
state.orders.push(order);
|
||||
return [{ insertId: order.id, affectedRows: 1 }, []];
|
||||
}
|
||||
if (sql.includes('FROM qipai_recharge_orders') && sql.includes('user_id = ?')) {
|
||||
const order = state.orders.find((item) =>
|
||||
item.tenantId === String(params[0])
|
||||
&& item.id === String(params[1])
|
||||
&& item.userId === String(params[2])
|
||||
);
|
||||
return [order ? [toOrderRow(order)] : [], []];
|
||||
}
|
||||
if (sql.includes('SELECT openid FROM qipai_user_identities')) {
|
||||
return [[{ openid: 'openid-recharge-test' }], []];
|
||||
}
|
||||
if (sql.includes('provider_prepay_id')) {
|
||||
const order = state.orders.find((item) =>
|
||||
item.tenantId === String(params[1])
|
||||
&& item.id === String(params[2])
|
||||
&& item.userId === String(params[3])
|
||||
);
|
||||
order.providerPrepayId = params[0];
|
||||
return [{ affectedRows: 1 }, []];
|
||||
}
|
||||
if (sql.includes('FROM qipai_recharge_orders') && sql.includes('recharge_no = ?')) {
|
||||
const order = state.orders.find((item) => item.rechargeNo === String(params[0]));
|
||||
return [order ? [{ ...toOrderRow(order), tenantId: order.tenantId }] : [], []];
|
||||
}
|
||||
if (sql.includes('FROM qipai_recharge_orders') && sql.includes('FOR UPDATE')) {
|
||||
const order = state.orders.find((item) =>
|
||||
item.tenantId === String(params[0]) && item.id === String(params[1])
|
||||
);
|
||||
return [order ? [toOrderRow(order)] : [], []];
|
||||
}
|
||||
if (sql.includes('INSERT IGNORE INTO qipai_payment_callbacks')) {
|
||||
const duplicate = state.callbacks.some((item) =>
|
||||
item.tenantId === String(params[0]) && item.callbackId === String(params[1])
|
||||
);
|
||||
if (!duplicate) {
|
||||
state.callbacks.push({
|
||||
tenantId: String(params[0]),
|
||||
callbackId: String(params[1]),
|
||||
status: 'RECEIVED',
|
||||
payload: params[2]
|
||||
});
|
||||
}
|
||||
return [{ affectedRows: duplicate ? 0 : 1 }, []];
|
||||
}
|
||||
if (sql.includes('provider_payment_id')) {
|
||||
const order = state.orders.find((item) =>
|
||||
item.tenantId === String(params[3]) && item.id === String(params[4])
|
||||
);
|
||||
order.status = 'PAID';
|
||||
order.providerPaymentId = String(params[0]);
|
||||
order.providerCallbackId = String(params[1]);
|
||||
order.rawNotify = params[2];
|
||||
return [{ affectedRows: 1 }, []];
|
||||
}
|
||||
if (sql.includes("SET status = 'PAID'")) {
|
||||
const order = state.orders.find((item) =>
|
||||
item.tenantId === String(params[1]) && item.id === String(params[2])
|
||||
@@ -92,11 +141,23 @@ function createHarness(overrides = {}) {
|
||||
order.status = 'CREDITED';
|
||||
return [{ affectedRows: 1 }, []];
|
||||
}
|
||||
if (sql.includes('UPDATE qipai_payment_callbacks')) {
|
||||
const callback = state.callbacks.find((item) =>
|
||||
item.tenantId === String(params[0]) && item.callbackId === String(params[1])
|
||||
) ?? state.callbacks.find((item) =>
|
||||
item.tenantId === String(params[1]) && item.callbackId === String(params[2])
|
||||
);
|
||||
if (callback) callback.status = sql.includes('REJECTED') ? 'REJECTED' : 'PROCESSED';
|
||||
return [{ affectedRows: 1 }, []];
|
||||
}
|
||||
throw new Error(`Unexpected SQL: ${sql}`);
|
||||
}
|
||||
};
|
||||
|
||||
const pool = {
|
||||
async execute(sql, params) {
|
||||
return connection.execute(sql, params);
|
||||
},
|
||||
async getConnection() {
|
||||
return connection;
|
||||
}
|
||||
@@ -113,7 +174,58 @@ function createHarness(overrides = {}) {
|
||||
}
|
||||
};
|
||||
|
||||
return { service: new RechargeService(pool, wallet), state };
|
||||
const wechat = {
|
||||
paymentRepository: {
|
||||
async resolveConfig(_connection, tenantId, platformAppId, storeId, provider) {
|
||||
state.resolvedConfig = { tenantId, platformAppId, storeId, provider };
|
||||
return {
|
||||
credentialRef: 'wechat-test',
|
||||
settings: {
|
||||
rechargeDescription: '测试充值',
|
||||
rechargeNotifyUrl: 'https://api.txyundm.cn/app-api/recharge/wechat/notify'
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
client: {
|
||||
async createJsapiPrepay(_credential, input) {
|
||||
state.prepayInput = input;
|
||||
return {
|
||||
prepayId: 'wx-recharge-prepay',
|
||||
paymentParams: {
|
||||
timeStamp: '1782360000',
|
||||
nonceStr: 'nonce',
|
||||
package: 'prepay_id=wx-recharge-prepay',
|
||||
signType: 'RSA',
|
||||
paySign: 'signed'
|
||||
}
|
||||
};
|
||||
},
|
||||
verifyAndDecrypt(_credential, headers, rawBody) {
|
||||
state.verifiedNotification = { headers, rawBody };
|
||||
return {
|
||||
out_trade_no: state.orders[0].rechargeNo,
|
||||
transaction_id: 'wx-recharge-transaction',
|
||||
trade_state: 'SUCCESS',
|
||||
amount: { total: state.orders[0].payAmountCents },
|
||||
payer: { openid: 'redacted' }
|
||||
};
|
||||
}
|
||||
},
|
||||
credentials: new Map([[
|
||||
'wechat-test',
|
||||
{
|
||||
appId: 'wx-test-app',
|
||||
merchantId: '1900000109',
|
||||
serialNo: 'MERCHANT-SERIAL',
|
||||
privateKeyPem: 'test-private-key',
|
||||
apiV3Key: '0123456789abcdef0123456789abcdef',
|
||||
platformCertificates: { 'PLATFORM-SERIAL': 'test-cert' }
|
||||
}
|
||||
]])
|
||||
};
|
||||
|
||||
return { service: new RechargeService(pool, wallet, wechat), state };
|
||||
}
|
||||
|
||||
function toOrderRow(order) {
|
||||
@@ -217,4 +329,41 @@ const baseOrder = {
|
||||
assert.equal(state.walletCredits.length, 1);
|
||||
}
|
||||
|
||||
{
|
||||
const { service, state } = createHarness();
|
||||
const created = await service.createRechargeOrder(baseOrder);
|
||||
const prepay = await service.createWechatPrepay({
|
||||
tenantId: '7',
|
||||
platformAppId: '9',
|
||||
userId: '21',
|
||||
rechargeOrderId: created.rechargeOrderId
|
||||
});
|
||||
assert.equal(prepay.package, 'prepay_id=wx-recharge-prepay');
|
||||
assert.equal(state.resolvedConfig.storeId, '11');
|
||||
assert.equal(state.prepayInput.outTradeNo, created.rechargeNo);
|
||||
assert.equal(state.prepayInput.amountCents, 10000);
|
||||
assert.equal(state.orders[0].providerPrepayId, 'wx-recharge-prepay');
|
||||
|
||||
const notified = await service.processWechatNotification({
|
||||
timestamp: '1782360001',
|
||||
nonce: 'notify-nonce',
|
||||
serial: 'PLATFORM-SERIAL',
|
||||
signature: 'signed'
|
||||
}, '{"resource":"encrypted"}', 'recharge-notify');
|
||||
assert.equal(notified.status, 'CREDITED');
|
||||
assert.equal(state.orders[0].providerPaymentId, 'wx-recharge-transaction');
|
||||
assert.equal(state.orders[0].providerCallbackId, 'PLATFORM-SERIAL:wx-recharge-transaction:SUCCESS');
|
||||
assert.equal(state.walletCredits.length, 1);
|
||||
assert.equal(JSON.parse(state.orders[0].rawNotify).payer, undefined);
|
||||
|
||||
const duplicate = await service.processWechatNotification({
|
||||
timestamp: '1782360001',
|
||||
nonce: 'notify-nonce',
|
||||
serial: 'PLATFORM-SERIAL',
|
||||
signature: 'signed'
|
||||
}, '{"resource":"encrypted"}', 'recharge-notify');
|
||||
assert.equal(duplicate.idempotent, true);
|
||||
assert.equal(state.walletCredits.length, 1);
|
||||
}
|
||||
|
||||
console.log('PASS: M07-B recharge plans validate limits and credit paid callbacks idempotently.');
|
||||
|
||||
Reference in New Issue
Block a user