"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const uuid_1 = require("oak-domain/lib/utils/uuid"); const payClazz_1 = require("../utils/payClazz"); const assert_1 = tslib_1.__importDefault(require("assert")); const withdraw_1 = require("./withdraw"); const Exception_1 = require("../types/Exception"); /** * 开始退款的逻辑 * @param context * @param data */ async function startRefunding(context, data) { (0, assert_1.default)(!data.pay && data.payId); const [pay] = await context.select('pay', { data: { id: 1, paid: 1, refunded: 1, iState: 1, entity: 1, entityId: 1, refundable: 1, deposit: { id: 1, accountId: 1, }, order: { id: 1, settled: 1, settlement$order: { $entity: 'settlement', data: { id: 1, accountId: 1, price: 1, iState: 1, }, filter: { iState: 'unsettled', } } } }, filter: { id: data.payId, } }, { dontCollect: true }); const { paid, refunded, deposit, order } = pay; if (paid - refunded < data.price) { throw new Exception_1.RefundExceedMax(); } if (!pay.refundable) { throw new Exception_1.PayUnRefundable(); } if (deposit) { // 充值不可能从account渠道支付 (0, assert_1.default)(pay.entity !== 'account'); if (!data.withdrawId && !order?.id) { //充值退款需创建关联的accountOper,若为提现则在withdraw中创建 data.accountOper$entity = [ { id: await (0, uuid_1.generateNewIdAsync)(), action: 'create', data: { id: await (0, uuid_1.generateNewIdAsync)(), accountId: deposit.accountId, type: 'refund', totalPlus: -data.price, availPlus: -data.price, refundablePlus: -data.price, }, } ]; } } else { (0, assert_1.default)(order); //订单退款的钱要有明确的来源account const { accountOper$entity: opers } = data; (0, assert_1.default)(opers && opers instanceof Array, '订单退款一定要有相应的account来源'); if (order.settled) { // 已经分账 let amount = 0; opers.forEach(({ action, data }) => { (0, assert_1.default)(action === 'create'); const { type, totalPlus, availPlus, refundablePlus } = data; (0, assert_1.default)(type === 'refund'); (0, assert_1.default)(totalPlus === availPlus); amount += -totalPlus; }); (0, assert_1.default)(amount === data.price); } else { //未完成分账的订单退款,关联的AccountOper的totalPlus总和等于refund的price //同时更新对应的settlement的price let amount = 0; const { settlement$order: settlements } = order; for (const oper of opers) { const { action, data } = oper; (0, assert_1.default)(action === 'create'); const { type, totalPlus, accountId } = data; (0, assert_1.default)(type === 'refund'); amount += -totalPlus; const settlement = settlements?.find((ele) => ele.accountId === accountId); (0, assert_1.default)(!!settlement, '请从订单分账的相关账户中选择订单退款的account来源'); const settlementNewPrice = settlement.price + totalPlus; //可能为负值 await context.operate('settlement', { id: await (0, uuid_1.generateNewIdAsync)(), action: 'refund', data: { price: settlementNewPrice, }, filter: { id: settlement.id, } }, {}); } (0, assert_1.default)(amount === data.price); } } data.pay = { id: (0, uuid_1.generateNewId)(), action: 'startRefunding', data: {}, }; } /** * 退款成功的逻辑 * @param context * @param data */ async function succeedRefunding(context, refundId) { const pays = await context.select('pay', { data: { id: 1, price: 1, refunded: 1, entity: 1, entityId: 1, iState: 1, paid: 1, applicationId: 1, application: { systemId: 1, }, deposit: { id: 1, accountId: 1, }, refund$pay: { $entity: 'refund', data: { id: 1, price: 1, iState: 1, loss: 1, withdrawId: 1, }, filter: { id: refundId, }, } }, filter: { refund$pay: { '#sqp': 'in', id: refundId, }, } }, { forUpdate: true, dontCollect: true }); (0, assert_1.default)(pays.length === 1); const [pay] = pays; const { price, paid, refunded, refund$pay: refunds, application, applicationId, entity, entityId } = pay; (0, assert_1.default)(refunds.length === 1); const [refund] = refunds; const { price: refundPrice, loss: refundLoss, withdrawId } = refund; const refunded2 = refunded + refundPrice; (0, assert_1.default)(refunded2 <= paid); const action = refunded2 === paid ? 'refundAll' : 'refundPartially'; await context.operate('pay', { id: await (0, uuid_1.generateNewIdAsync)(), action, data: { refunded: refunded2, refundable: refunded2 < paid, }, filter: { id: pay.id, } }, {}); let d = undefined; //退回至account的退款无渠道退款补偿 if (entity === 'account') { //退回至account中的退款,创建accountOper,更新account余额 await context.operate('accountOper', { id: await (0, uuid_1.generateNewIdAsync)(), action: 'create', data: { id: await (0, uuid_1.generateNewIdAsync)(), accountId: pay.entityId, entity: 'refund', entityId: refund.id, type: 'consumeBack', totalPlus: refundPrice - refundLoss, availPlus: refundPrice - refundLoss, } }, {}); } else { //订单退回至非account的退款会使得系统对应账户的资金发生流出 const payClazz = await (0, payClazz_1.getPayClazz)(entity, entityId, context); // 实际执行的退款的额度应该是price - loss const [delta, sysAccountEntity, sysAccountEntityId] = payClazz.calcRefundTax(refundPrice - refundLoss); // sysAccount上减掉实际退款的额度-税费 await context.operate('sysAccountOper', { id: await (0, uuid_1.generateNewIdAsync)(), action: 'create', data: { id: await (0, uuid_1.generateNewIdAsync)(), delta: refundLoss - refundPrice - delta, entity: sysAccountEntity, entityId: sysAccountEntityId, refundId: refund.id, type: 'refund', } }, {}); d = delta; } let cnt = 2; if (refundLoss || d) { // 如果有退回或者要缴纳的税费,进入system相关联的account账户 const [account] = await context.select('account', { data: { id: 1, }, filter: { entity: 'system', entityId: application.systemId, }, }, { dontCollect: true }); if (d) { await context.operate('accountOper', { id: await (0, uuid_1.generateNewIdAsync)(), action: 'create', data: { id: await (0, uuid_1.generateNewIdAsync)(), accountId: account.id, type: d < 0 ? 'taxRefund' : 'tax', totalPlus: -d, availPlus: -d, entity: 'refund', entityId: refund.id, }, }, {}); } cnt++; if (refundLoss) { (0, assert_1.default)(refundLoss > 0); await context.operate('accountOper', { id: await (0, uuid_1.generateNewIdAsync)(), action: 'create', data: { id: await (0, uuid_1.generateNewIdAsync)(), accountId: account.id, type: 'earn', totalPlus: refundLoss, availPlus: refundLoss, entity: 'refund', entityId: refund.id, }, }, {}); } } // 如果有withdraw,尝试去同步withdraw的状态 if (withdrawId) { return cnt + await (0, withdraw_1.updateWithdrawState)(context, withdrawId); } return cnt; } async function failRefunding(context, refundId) { const pays = await context.select('pay', { data: { id: 1, price: 1, refunded: 1, entity: 1, entityId: 1, iState: 1, paid: 1, applicationId: 1, application: { systemId: 1, }, deposit: { id: 1, accountId: 1, }, refund$pay: { $entity: 'refund', data: { id: 1, price: 1, loss: 1, iState: 1, withdrawId: 1, }, filter: { id: refundId, }, }, order: { id: 1, settled: 1, settlement$order: { $entity: 'settlement', data: { id: 1, accountId: 1, price: 1, iState: 1, }, filter: { iState: 'unsettled', } } } }, filter: { refund$pay: { '#sqp': 'in', id: refundId, }, } }, { forUpdate: true, dontCollect: true }); (0, assert_1.default)(pays.length === 1); const [pay] = pays; const { refunded, refund$pay: refunds, order } = pay; (0, assert_1.default)(refunds.length === 1); const [refund] = refunds; const { withdrawId } = refund; const action = refunded === 0 ? 'stopRefunding' : 'refundPartially'; await context.operate('pay', { id: await (0, uuid_1.generateNewIdAsync)(), action, data: { refundable: true, }, filter: { id: pay.id, } }, {}); // 如果有withdraw,尝试去同步withdraw的状态 if (withdrawId) { return 1 + await (0, withdraw_1.updateWithdrawState)(context, withdrawId); } else { (0, assert_1.default)(order); // 说明是从order那条线产生的refund,此时要把当时扣除掉的account部分返还 const accountOpers = await context.select('accountOper', { data: { id: 1, totalPlus: 1, availPlus: 1, accountId: 1, }, filter: { type: 'refund', entity: 'refund', entityId: refundId, }, }, { dontCollect: true }); if (order.settled) { // 已经分账 let amount = 0; for (const oper of accountOpers) { const { totalPlus, availPlus, accountId } = oper; (0, assert_1.default)(totalPlus < 0 && totalPlus === availPlus); await context.operate('accountOper', { id: await (0, uuid_1.generateNewIdAsync)(), action: 'create', data: { id: await (0, uuid_1.generateNewIdAsync)(), totalPlus: -totalPlus, availPlus: -availPlus, type: 'refundFailure', entity: 'refund', entityId: refundId, accountId, } }, {}); amount += -availPlus; } (0, assert_1.default)(amount === refund.price); } else { //未完成分账的订单,需要修改settlement let amount = 0; const { settlement$order: settlements } = order; for (const oper of accountOpers) { const { totalPlus, availPlus, accountId } = oper; amount += -totalPlus; const settlement = settlements?.find((ele) => ele.accountId === accountId); (0, assert_1.default)(!!settlement); const settlementNewPrice = settlement.price - totalPlus; await context.operate('settlement', { id: await (0, uuid_1.generateNewIdAsync)(), action: 'refundFailure', data: { price: settlementNewPrice, }, filter: { id: settlement.id, } }, {}); } (0, assert_1.default)(amount === refund.price); } return 1 + accountOpers.length; } } const triggers = [ { entity: 'refund', action: 'create', when: 'commit', name: '当refund建立时,尝试触发外部退款操作', strict: 'makeSure', fn: async ({ ids }, context) => { (0, assert_1.default)(ids.length === 1); const [refund] = await context.select('refund', { data: { id: 1, price: 1, loss: 1, pay: { id: 1, price: 1, iState: 1, refunded: 1, entity: 1, entityId: 1, applicationId: 1, }, }, filter: { id: ids[0], }, }, {}); const { id, price, pay } = refund; const { price: payPrice, refunded, entity, entityId, applicationId } = pay; const payClazz = await (0, payClazz_1.getPayClazz)(entity, entityId, context); // 若对应entity的账户里钱不够,则不发起退款 const accountEntity = (0, payClazz_1.getAccountEntity)(entity); if (entity !== 'account') { const accountAmount = await payClazz.getAccountAmount(context); if (accountAmount < price) { return; } } const data = await payClazz.refund(refund, context); if (data) { (0, assert_1.default)(data.externalId); const closeFn = context.openRootMode(); await context.operate('refund', { id: await (0, uuid_1.generateNewIdAsync)(), data, action: 'update', filter: { id, } }, { dontCollect: true }); closeFn(); } return; }, }, { entity: 'refund', action: 'succeed', when: 'after', asRoot: true, name: '退款成功时,更新对应的pay状态以及对应的withdraw状态', fn: async ({ operation }, context) => { const { filter } = operation; const refundId = filter?.id; (0, assert_1.default)(typeof refundId === 'string'); return succeedRefunding(context, refundId); } }, { entity: 'refund', action: 'fail', when: 'after', asRoot: true, name: '退款失败时,更新对应的pay状态以及对应的withdraw状态', fn: async ({ operation }, context) => { const { filter } = operation; const refundId = filter?.id; (0, assert_1.default)(typeof refundId === 'string'); return failRefunding(context, refundId); } }, { entity: 'refund', name: '当发起退款时,置pay的状态并做相应检查', action: 'create', asRoot: true, when: 'before', fn: async ({ operation }, context) => { const { data } = operation; (0, assert_1.default)(!(data instanceof Array)); await startRefunding(context, data); return 1; } }, ]; exports.default = triggers;