feat(M07-B): 接入充值优惠规则
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { RechargeError, RechargeService } from '../dist/wallets/recharge-service.js';
|
||||
|
||||
const future = new Date(Date.now() + 86_400_000);
|
||||
const past = new Date(Date.now() - 86_400_000);
|
||||
|
||||
function createHarness(overrides = {}) {
|
||||
const state = {
|
||||
plan: {
|
||||
id: '31',
|
||||
tenantId: '7',
|
||||
storeId: '11',
|
||||
name: '充 100 送 20',
|
||||
payAmountCents: 10000,
|
||||
giftAmountCents: 2000,
|
||||
scopeType: 'STORE',
|
||||
startsAt: past,
|
||||
endsAt: future,
|
||||
purchaseLimitPerUser: 2,
|
||||
status: 'ACTIVE',
|
||||
...overrides.plan
|
||||
},
|
||||
orders: [],
|
||||
nextOrderId: 900,
|
||||
purchaseCount: overrides.purchaseCount ?? 0,
|
||||
walletCredits: []
|
||||
};
|
||||
|
||||
const connection = {
|
||||
async beginTransaction() {},
|
||||
async commit() {},
|
||||
async rollback() {},
|
||||
release() {},
|
||||
async execute(sql, params) {
|
||||
if (sql.includes('FROM qipai_recharge_orders') && sql.includes('client_request_id')) {
|
||||
const duplicate = state.orders.find((order) =>
|
||||
order.tenantId === String(params[0])
|
||||
&& order.userId === String(params[1])
|
||||
&& order.clientRequestId === params[2]
|
||||
);
|
||||
return [duplicate ? [toOrderRow(duplicate)] : [], []];
|
||||
}
|
||||
if (sql.includes('FROM qipai_recharge_plans')) {
|
||||
if (
|
||||
state.plan
|
||||
&& state.plan.tenantId === String(params[0])
|
||||
&& state.plan.id === String(params[1])
|
||||
) {
|
||||
return [[state.plan], []];
|
||||
}
|
||||
return [[], []];
|
||||
}
|
||||
if (sql.includes('COUNT(*) AS total')) {
|
||||
return [[{ total: state.purchaseCount }], []];
|
||||
}
|
||||
if (sql.includes('INSERT INTO qipai_recharge_orders')) {
|
||||
const order = {
|
||||
id: String(state.nextOrderId++),
|
||||
tenantId: String(params[0]),
|
||||
userId: String(params[1]),
|
||||
storeId: params[2] === null ? null : String(params[2]),
|
||||
planId: String(params[3]),
|
||||
rechargeNo: params[4],
|
||||
clientRequestId: params[5],
|
||||
payAmountCents: Number(params[6]),
|
||||
giftAmountCents: Number(params[7]),
|
||||
traceId: params[8],
|
||||
status: 'PENDING_PAYMENT',
|
||||
paymentId: null
|
||||
};
|
||||
state.orders.push(order);
|
||||
return [{ insertId: order.id, affectedRows: 1 }, []];
|
||||
}
|
||||
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("SET status = 'PAID'")) {
|
||||
const order = state.orders.find((item) =>
|
||||
item.tenantId === String(params[1]) && item.id === String(params[2])
|
||||
);
|
||||
order.status = 'PAID';
|
||||
order.paymentId = String(params[0]);
|
||||
return [{ affectedRows: 1 }, []];
|
||||
}
|
||||
if (sql.includes("SET status = 'CREDITED'")) {
|
||||
const order = state.orders.find((item) =>
|
||||
item.tenantId === String(params[0]) && item.id === String(params[1])
|
||||
);
|
||||
order.status = 'CREDITED';
|
||||
return [{ affectedRows: 1 }, []];
|
||||
}
|
||||
throw new Error(`Unexpected SQL: ${sql}`);
|
||||
}
|
||||
};
|
||||
|
||||
const pool = {
|
||||
async getConnection() {
|
||||
return connection;
|
||||
}
|
||||
};
|
||||
const wallet = {
|
||||
async credit(input) {
|
||||
state.walletCredits.push(input);
|
||||
return {
|
||||
ledgerEntryId: String(state.walletCredits.length),
|
||||
cashBalanceCents: input.cashDeltaCents,
|
||||
giftBalanceCents: input.giftDeltaCents,
|
||||
idempotent: false
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return { service: new RechargeService(pool, wallet), state };
|
||||
}
|
||||
|
||||
function toOrderRow(order) {
|
||||
return {
|
||||
id: order.id,
|
||||
userId: order.userId,
|
||||
storeId: order.storeId,
|
||||
planId: order.planId,
|
||||
rechargeNo: order.rechargeNo,
|
||||
payAmountCents: order.payAmountCents,
|
||||
giftAmountCents: order.giftAmountCents,
|
||||
status: order.status
|
||||
};
|
||||
}
|
||||
|
||||
const baseOrder = {
|
||||
tenantId: '7',
|
||||
userId: '21',
|
||||
planId: '31',
|
||||
storeId: '11',
|
||||
clientRequestId: 'client-001',
|
||||
traceId: 'recharge-test'
|
||||
};
|
||||
|
||||
{
|
||||
const { service, state } = createHarness();
|
||||
const created = await service.createRechargeOrder(baseOrder);
|
||||
assert.equal(created.status, 'PENDING_PAYMENT');
|
||||
assert.equal(created.payAmountCents, 10000);
|
||||
assert.equal(created.giftAmountCents, 2000);
|
||||
assert.equal(created.idempotent, false);
|
||||
assert.equal(state.orders.length, 1);
|
||||
|
||||
const duplicate = await service.createRechargeOrder(baseOrder);
|
||||
assert.equal(duplicate.rechargeOrderId, created.rechargeOrderId);
|
||||
assert.equal(duplicate.idempotent, true);
|
||||
assert.equal(state.orders.length, 1);
|
||||
}
|
||||
|
||||
{
|
||||
const { service } = createHarness({ plan: { status: 'DISABLED' } });
|
||||
await assert.rejects(
|
||||
() => service.createRechargeOrder(baseOrder),
|
||||
(error) => error instanceof RechargeError && error.code === 'RECHARGE_PLAN_DISABLED'
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const { service } = createHarness({ plan: { endsAt: past } });
|
||||
await assert.rejects(
|
||||
() => service.createRechargeOrder(baseOrder),
|
||||
(error) => error instanceof RechargeError && error.code === 'RECHARGE_PLAN_EXPIRED'
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const { service } = createHarness({ purchaseCount: 2 });
|
||||
await assert.rejects(
|
||||
() => service.createRechargeOrder(baseOrder),
|
||||
(error) => error instanceof RechargeError && error.code === 'RECHARGE_LIMIT_REACHED'
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const { service, state } = createHarness();
|
||||
const created = await service.createRechargeOrder(baseOrder);
|
||||
const credited = await service.markPaidAndCredit({
|
||||
tenantId: '7',
|
||||
rechargeOrderId: created.rechargeOrderId,
|
||||
paymentId: '501',
|
||||
traceId: 'payment-callback'
|
||||
});
|
||||
assert.equal(credited.status, 'CREDITED');
|
||||
assert.equal(credited.idempotent, false);
|
||||
assert.equal(state.orders[0].status, 'CREDITED');
|
||||
assert.equal(state.walletCredits.length, 1);
|
||||
assert.deepEqual(
|
||||
{
|
||||
businessType: state.walletCredits[0].businessType,
|
||||
businessId: state.walletCredits[0].businessId,
|
||||
cashDeltaCents: state.walletCredits[0].cashDeltaCents,
|
||||
giftDeltaCents: state.walletCredits[0].giftDeltaCents,
|
||||
storeId: state.walletCredits[0].storeId
|
||||
},
|
||||
{
|
||||
businessType: 'RECHARGE',
|
||||
businessId: created.rechargeOrderId,
|
||||
cashDeltaCents: 10000,
|
||||
giftDeltaCents: 2000,
|
||||
storeId: '11'
|
||||
}
|
||||
);
|
||||
|
||||
const duplicateCallback = await service.markPaidAndCredit({
|
||||
tenantId: '7',
|
||||
rechargeOrderId: created.rechargeOrderId,
|
||||
paymentId: '501',
|
||||
traceId: 'payment-callback'
|
||||
});
|
||||
assert.equal(duplicateCallback.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