tmerclub-doc/结算与资金流/必看!!!如果你不想平台亏损之资金流动.md
2025-03-19 15:04:57 +08:00

14 KiB
Raw Permalink Blame History

mall4j微服务商城的资金流动

mall4j商城除了用户下单以外最重要的东西便是结算。下面我们从几个方面来熟悉资金的流动返现如果资金没算好会造成十分严重的后果。

平台会根据类目从用户的实际支付金额中抽取一部分佣金的。扣完佣金后剩余部分会加上平台优惠补贴的金额,全部给到了商家账户里的。

如何进行测试

要想知道你的结算金额是否正确,最基本的需要通过一个测试:

1. 先购买3件商品
2. 发货
3. 退一件,商家允许退款
4. 确认收货
5. 再退一件,商家允许退款
6. 修改订单确认收货、下单、支付、退款申请的时间为一个月前。
7. 调用强制取消未退款订单的定时任务。
8. 调用结算的定时任务,结算给商家。 

如果在这几个流程,用户支付金额 + 平台优惠金额 - 平台佣金 = 商家收入 可以判断为基本正确,但会有很多特殊情况发生,所以要完整判断,还需要根据业务具体情况来确定

如何确定收支平衡

当商家上架商品的时候,一般会有一个销售价,这个销售价便是商家希望能够通过商品的销售可以获取的收益。

  1. 如果商品有一般的商家性的营销活动如:满减满折、优惠券之类的,商家可以获取的金额,实际上应该是用户支付的金额。即商家收入 = 用户支付金额 - 平台佣金
  2. 如果涉及到平台性活动:平台优惠券、会员全平台折扣、积分等。因为这个是平台发放的活动,强制所有商家参与的时候,这部分优惠的金额要商家进行补贴,即商家收入 = 用户支付金额 + 平台补贴支出金额 - 平台佣金
  3. 如果涉及退款 商家收入 = 用户支付金额 - 退款成功金额 - 未退款项平台佣金

综合上面三种情况得出:用户支付金额 + 平台支出金额 - 平台佣金 = 商家收入 + 用户退款收入

当有更多的营销活动,更多的收入支出方,只要知道这个平衡公式即可:支出a + 支出b +支出c + ... = 收入a + 收入b +收入c + ...

在上面的情况下,可以看出平台只收取了平台佣金,需要注意一点可能因为平台活动会亏损,需要妥善安排平台的活动。

支付

进行支付时,商家、平台获取待结算收入

简单的可以得出这个公式:

我们来看看这段代码在哪:

  1. 首先我们来看,默认接收支付成功消息进行处理的方法com.mall4j.cloud.multishop.listener.OrderNotifyShopConsumer

    这里做了一个操作商家收入 = 用户支付金额 + 平台补贴支出金额 - 分销金额(暂无) - 平台佣金待结算金额:

			// 1. 商家应收 = 商品价格 - 商家优惠 - 分销金额(暂无)
            // 2. 商家应收 = 用户支付 + 平台补贴 - 分销金额(暂无)
            // 商家实际待结算金额 = 用户支付 + 平台补贴 - 订单分销佣金 - 平台佣金
            Long changeAmount = orderSimpleAmountInfo.getActualTotal() + orderSimpleAmountInfo.getPlatformAmount() - orderSimpleAmountInfo.getDistributionAmount() - orderSimpleAmountInfo.getPlatformCommission();

            // 商家添加未结算金额(因为添加了唯一索引,所以是不用怕多次加钱)
            shopWalletMapper.addUnsettledAmount(orderSimpleAmountInfo.getShopId(), changeAmount);

  1. 然后这里还有平台未结算金额的计算

平加未结算金额 = 平台佣金 - 平台优惠分摊优惠金额

            long platformChangeAmount = orderSimpleAmountInfo.getPlatformCommission() - orderSimpleAmountInfo.getPlatformAmount();

结算

注意:所有商品进行退款之后,也会进行结算的操作,可以看看退款这部分内容

1.结算

要确认收货15天之后进行结算商家和平台的未结算金额转成结算金额。

当商品有平台性质的活动时,此时,有部分金额是平台进行补贴的参考上文中如何确定收支平衡 的这一部分得出,由于我们上一步已经计算得到商家未结算金额了,所以结算时只需要减去退款成功金额,结算金额 = 未结算金额 - 退款成功金额

我们来看看这段代码在哪:com.mall4j.cloud.multishop.listener.OrderNotifyShopConsumer

		// 1. 商家结算金额 = 商家未结算金额 - 退款成功金额
		// 2. 平台结算金额 = 平台未结算金额 - 退款成功金额
		Long changeAmount = shopPayLog.getChangeAmount();
		changeAmount = changeAmount - shopRefundLog.getChangeAmount();

至于为啥,还是看看退款这个文档。

退款

退款与结算息息相关,而且是整个系统最复杂的地方

刚才在确认收货的时候有提及退款的功能,退款的逻辑稍微复杂一点点,首先要根据用户的退款时间来进行判断。首先我们确定的是,用户能退款的金额上线,是用户支付的金额。对于退款来说,用户的金额即所支付的金额在商家待结算金额。

也就是说当商家同意退款的时候,需要从商家未结算金额里减去对应的金额。

这里的退款有一点需要值得注意:

2. 部分退款

因为我们的退款是支持部分退款的。如果用户将分商品进行了部分退款,那么退款的钱从平台出多少,商家出多少呢?

这么一想问题又会复杂化,我们从前面已经规定了,平台根据商品类目收取佣金,所以在部分退款后,平台补贴和平台的佣金都要进行改变,此时商家和平台的未结算金额改变可以参考com.mall4j.cloud.multishop.listener.OrderRefundShopConsumer#subShopSettlementAmount(OrderRefundDto)

        // 商家改变金额 = 原商家应收(下单时商家应收) - 现在商家应收 - 平台佣金应退
        //             = (用户支付 + 平台补贴) - 分销金额 - (实付金额 + 平台补贴) - 平台佣金 *(退款金额 / 实付金额)
        //             = (用户支付 + 平台补贴)*(退款金额 / 实付金额) - 分销金额
        //             = (用户支付 + 平台补贴 - 平台佣金) * (退款金额 / 实付金额) - 分销金额
        //             = 退款金额 + (平台补贴 - 平台佣金)* (退款金额 / 实付金额) - 分销金额
        //             = 退款金额 + 平台补贴改变量 - 平台佣金改变量 - 分销金额

        // 平台补贴改变量 = 平台补贴金额 * 退款金额 / 商品实际金额 
        Long changePlatformAmount = PriceUtil.divideByBankerRounding(orderChangeShopWalletAmountBO.getPlatformAllowanceAmount() * orderChangeShopWalletAmountBO.getRefundAmount(), orderChangeShopWalletAmountBO.getActualTotal());
        // 平台佣金改变量 = 平台佣金 * 退款金额 / 商品实际金额 
        Long changePlatformCommission = PriceUtil.divideByBankerRounding(orderChangeShopWalletAmountBO.getPlatformCommission() * orderChangeShopWalletAmountBO.getRefundAmount(), orderChangeShopWalletAmountBO.getActualTotal());
        // 商家改变金额 = 退款金额 + 平台补贴金额改变量 - 平台佣金 - 分销金额(暂无)
        Long shopRealRefundAmount = orderChangeShopWalletAmountBO.getRefundAmount() + changePlatformAmount - changePlatformCommission - orderChangeShopWalletAmountBO.getDistributionAmount();
        // 用户申请的退款金额
        shopWalletLog.setUserAmount(orderChangeShopWalletAmountBO.getRefundAmount());
        // 回退的平台补贴
        shopWalletLog.setPlatformAmount(changePlatformAmount);
        // 回退的平台佣金
        shopWalletLog.setPlatformCommission(changePlatformCommission);
        // 商家改变金额
        shopWalletLog.setChangeAmount(shopRealRefundAmount);
	...
        // 平台实际上的改变金额 = 当前退款改变的平台佣金 - 当前退款退款改变的平台补贴金额
        // 订单退款,补贴肯定是减少了,但是对于平台来说是赚钱了
        // 订单退款了,平台的佣金也是减少了,所以对于平台来说是亏钱的。
        long platformRealRefundAmount = shopWalletLog.getPlatformAmount() - shopWalletLog.getPlatformCommission();

5. 强制取消申请超时的退款订单

根据 部分退款 这部分内容,我们可以得出,结算给商家,需要在退款流程结束之后。如果退款一直不处理的话,是不是商家就一直不用结算呢?系统当然不能这样,所以我们规定了申请退款的最大时限同时规定了几个时间1)最大确认收货退款时间7天 2)退款最长申请时间,当申请时间过了这个时间段之后,会取消退款申请

与取消退款申请超时的订单相关的定时任务:com.mall4j.cloud.order.task.OrderTask.OrderRefundTask#cancelWhenTimeOut()

与退款有关的几个常量com.mall4j.cloud.common.constant.Constant

	/**
	 * 最大确认收货退款时间7天
	 */
	public static final int MAX_FINALLY_REFUND_TIME = 7;

	/**
	 * 退款最长申请时间,当申请时间过了这个时间段之后,会取消退款申请
	 */
	public static final int MAX_REFUND_APPLY_TIME = 7;
	/**
	 * 离即将退款超时x小时时提醒
	 */
	public static final int MAX_REFUND_HOUR = 12;

6. 参与商家活动时,订单项分摊优惠金额

当商品A90元和商品B10元进行购买的时候两者相加为100元此时有一个满100减10的店铺活动。用户支付了90元买走了100元的商品。然后进行B商品的退款请问这个用户可以申请退10元吗

很明显是不能退10元的因为如果B商品可以退10元A商品就可以退90元那么他如果只选择退A商品岂不是能白白获得B商品难道有优惠活动就不允许退款吗这明显是不合理的毕竟常见的优惠活动别的平台都能退款凭啥你不能退款所以我们要引入一个概念分摊金额

分摊金额简单的解释就是当A和B同时有优惠的时候那么这个优惠就会按照比例分摊到每个订单项不是商品是订单项上即订单项A分摊优惠金额 = 优惠金额 * (订单项A/ 参与该活动的订单项目金额之和)

根据等式可得:

商品A分摊金额 = 优惠金额10元 * 商品A金额90元/ (商品A金额90元 + 商品B金额10元) = 9元

商品B分摊金额 = 优惠金额10元 * 商品B金额10元/ (商品B金额90元 + 商品B金额10元) = 1元

此时该订单项可退款金额 = 商品金额 - 商品分摊金额

根据等式可以知道用户不能退10元只能退 商品B金额10元 - 商品B分摊金额1元 = 9元

7. 参与平台活动时,订单项分摊优惠金额

当用户参与平台活动如满100减10因为这个是平台发放的活动强制所有商家参与的时候这部分优惠的金额要商家进行补贴。

当商品A90元和商品B10元进行购买的时候两者相加为100元用户只要支付90元商家收到100元。平台亏10元。

用户进行B商品的退款请问这个用户可以申请退10元吗

不可以,因为按照上面的 参与商家活动时,订单项分摊优惠金额 来说用户只为这件商品付款了9元所以只能退9元。但是当用户选择退9元的时候商家要从他的钱包当中扣减10元。因为其中1元是平台支出的用户选择了退款应该归还这1元给平台。

注意:

我们是有协商部分退款的说法的,也就是当用户选择部分退款的时候,这部分应该刚按照比例退回给平台,并扣除商家的这部分金额。

可以参考com.mall4j.cloud.order.controller.app.OrderRefundController#apply(OrderRefundDto)

            // 计算该订单的分销金额
        newOrderRefund.setDistributionTotalAmount(orderService.sumTotalDistributionAmountByOrderItem(orderItemList));
            // 计算平台退款金额(退款时将这部分钱退回给平台,所以商家要扣除从平台这里获取的金额)
            newOrderRefund.setPlatformRefundAmount(order.getPlatformAmount());

8. 退款导致订单结算

由于我们系统支持部分金额退款所以当100元的商品用户选择退90元的时候要结算10元给商家。我们并不是每退一次就结算一次而是当订单里面的订单项都退完的时候该订单会被取消此时结算给商家。而这里的结算用的是在接收到退款成功通知之后进行的。发送部分退款完成的。

可以参考com.yami.shop.service.impl.OrderRefundServiceImpl#verifyRefund(OrderRefundDto,String)

// 订单在已支付,已发货,确认收货的情况下,退款导致订单关闭,需要判断下是否要进行结算给商家的操作,这个结算的计算是退款完成后结算的,所以不要随便改变顺序
        boolean canRefundFlag = 
           		 Objects.equals(dbOrder.getStatus(),OrderStatus.PAYED.value())
                || Objects.equals(dbOrder.getStatus(), OrderStatus.CONSIGNMENT.value())
                || Objects.equals(dbOrder.getStatus(), OrderStatus.SUCCESS.value());
        if (canRefundFlag && Objects.equals(order.getStatus(),OrderStatus.CLOSE.value())) 		{
            // 通知还原优惠券
            refundCoupon(dbOrder, order);
            handlePartialRefund(dbOrder);
        }
...
        // 2.发送消息给商家,将需要结算的钱进行结算
        // 减少商家待结算金额,增加已结算金额
        SendStatus sendStatus = orderRefundSuccessSettlementTemplate.syncSend(RocketMqConstant.ORDER_REFUND_SUCCESS_SETTLEMENT_TOPIC, new GenericMessage<>(orderChangeShopWalletAmountBO)).getSendStatus();


提现的流程

  1. 当商家选择进行提现申请的时候,会冻结申请中的金额。
  2. 平台管理员线下银行卡转账。
  3. 平台上提交转账信息,完成。

退款说明

  1. 由于退款时必须订单有支付钱或者积分才能走流程所以如果整个订单如果有一个订单项积分并且钱为0就只能进行整单退款。