| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- /**
- * 订单服务:商品、订单创建、状态机与幂等标记已支付
- *
- * 订单状态:
- * PENDING —— 已创建待支付
- * PAID —— 支付成功(已发货)
- * CLOSED —— 超时/关闭
- */
- const { paymentId, outTradeNo } = require('../utils/id');
- /** 商品目录(价格单位:分;retail_price_usd 为海外渠道展示用) */
- const PRODUCTS = [
- {
- id: 'p001',
- name: '橘子',
- quota: 1,
- retail_price: 1100, // ¥11.00
- retail_price_usd: 0,
- expire_policy: 'once',
- expire_policy_text: '一次性',
- description: '演示商品',
- methods: ['wechat', 'alipay', 'stripe', 'apple', 'google'],
- },
- ];
- /** 订单存储(内存 Map;演示足够,重启丢失) */
- const orders = new Map();
- /**
- * 按 id 查找商品
- * @returns {object|undefined}
- */
- function findProduct(productId) {
- return PRODUCTS.find((p) => p.id === productId);
- }
- /**
- * 创建订单
- * @param {object} param
- * @param {string} param.productId 商品 ID
- * @param {string} param.source 来源:app|web
- * @param {string} param.currency 币种(默认 cny,按商品零售价计)
- * @returns {{ id, productId, name, amount, currency, status, outTradeNo, createdAt }}
- */
- function createOrder({ productId, source = 'app', currency = 'cny' }) {
- const product = findProduct(productId);
- if (!product) {
- const err = new Error(`product not found: ${productId}`);
- err.code = 'PRODUCT_NOT_FOUND';
- throw err;
- }
- const order = {
- id: paymentId(),
- outTradeNo: outTradeNo(),
- productId,
- name: product.name,
- amount: product.retail_price,
- currency,
- status: 'PENDING',
- channel: null,
- channelTradeNo: null,
- source,
- createdAt: new Date().toISOString(),
- paidAt: null,
- };
- orders.set(order.id, order);
- return order;
- }
- /** 查询订单 */
- function getOrder(orderId) {
- return orders.get(orderId);
- }
- /** 按商户订单号(out_trade_no)查询订单(回调通知用) */
- function getOrderByOutTradeNo(outTradeNo) {
- for (const order of orders.values()) {
- if (order.outTradeNo === outTradeNo) return order;
- }
- return null;
- }
- /** 按商户订单号标记已支付(幂等) */
- function markPaidByOutTradeNo(outTradeNo, meta) {
- const order = getOrderByOutTradeNo(outTradeNo);
- return order ? markPaid(order.id, meta) : false;
- }
- /**
- * 标记订单已支付(幂等):仅 PENDING → PAID 成功执行一次
- * @returns {boolean} 本次是否真正完成变更
- */
- function markPaid(orderId, { channel, channelTradeNo }) {
- const order = orders.get(orderId);
- if (!order) return false;
- if (order.status !== 'PENDING') return false; // 已处理过,幂等
- order.status = 'PAID';
- order.channel = channel;
- order.channelTradeNo = channelTradeNo;
- order.paidAt = new Date().toISOString();
- return true;
- }
- /**
- * 查询支付状态(前端轮询/最终确认)
- * @returns {{ success: boolean, note: string }}
- */
- function getStatus(orderId) {
- const order = orders.get(orderId);
- if (!order) {
- return { success: false, note: '订单不存在' };
- }
- if (order.status === 'PAID') {
- return { success: true, note: '支付成功' };
- }
- if (order.status === 'CLOSED') {
- return { success: false, note: '订单已关闭' };
- }
- return { success: false, note: '等待支付' };
- }
- /** 全部商品列表(前端定价展示) */
- function listProducts() {
- return PRODUCTS;
- }
- module.exports = {
- findProduct,
- createOrder,
- getOrder,
- markPaid,
- getStatus,
- listProducts,
- };
|