mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-07 21:55:46 +08:00
feat(trade): 下单写入 provider 并修复报表口径
资金凭证创建时由 method 派生 provider,成功路径兜底补齐;SQL 回填历史 posted_amount/provider;时段分布改为日均。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
Target Server Version : 160014 (160014)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 21/07/2026 14:12:39
|
||||
Date: 22/07/2026 18:36:13
|
||||
*/
|
||||
|
||||
|
||||
@@ -127,7 +127,9 @@ INSERT INTO "public"."iam_perm_menu" VALUES (4040111, 4040130, 'merchant:app:rou
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (4040109, 4040131, 'channel:merchant:create', 'admin', 'ChannelMerchantCreate', 'menu.payment.merchant.channelMerchant.create', NULL, 't', 'f', '/payment/merchant/channel-merchant/ChannelMerchantCreate', '/payment/merchant/channel-merchant/create', NULL, 9, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-05 16:00:00+00', '2026-07-11 03:37:15.428444+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (4040117, 4040130, 'merchant:store', 'admin', 'MchStoreInfoList', 'menu.payment.merchant.store', NULL, 't', 'f', '/payment/merchant/store/MchStoreInfoList', '/payment/merchant/store', NULL, 15, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-24 16:00:00+00', '2026-07-11 03:37:15.424776+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (602, 6, 'trade:fund', 'admin', 'PayTradeList', 'menu.trade.payTrade', 'lucide:arrow-left-right', 'f', 'f', '/payment/order/PayTradeList', '/trade/pay-trade', NULL, 1, 'f', 't', 'f', NULL, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-01 15:09:04.769906+00', '2026-07-17 06:18:15.474015+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (901, 4, 'device:qrcode', 'admin', 'DeviceQrCode', 'menu.device.qrcode', 'lucide:qr-code', 'f', 'f', '/payment/device/qrcode/DeviceQrCode', '/device/qrcode', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-25 01:21:34.634639+00', '2026-07-21 06:30:00+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (8, NULL, 'develop', 'admin', 'Develop', 'menu.develop', 'lucide:wrench', 'f', 'f', NULL, '/develop', NULL, 5, 'f', 't', 'f', 1, 1, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-23 16:00:00+00', '2026-06-23 16:00:00+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (9, NULL, 'device', 'admin', 'DeviceManagement', 'menu.device', 'lucide:qr-code', 'f', 'f', NULL, '/device', NULL, 4.2, 'f', 't', 'f', 1, 1, 1, 't', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-25 01:21:34.631155+00', '2026-07-21 06:30:00+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (204, 2, 'demos:artemis', 'admin', 'ArtemisDemo', 'menu.demos.artemis', 'lucide:send', 'f', 'f', '/demos/artemis/ArtemisDemo', '/demos/artemis', NULL, 4, 'f', 't', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-18 00:00:00+00', '2026-06-18 00:00:00+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (900001, 304, 'iam:social', 'admin', 'ThirdPlatform', 'menu.system.config.thirdPlatform', 'lucide:share-2', 'f', 'f', '/system/config/third-platform/ThirdPlatform', '/system/config/third-platform', NULL, 5, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-13 02:15:41.505142+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (4040103, 4040130, 'merchant:profile', 'admin', 'MchInfoManage', 'menu.payment.merchant.profile', NULL, 't', 'f', '/payment/merchant/manage/info/MchInfoManage', '/payment/merchant/manage/info', NULL, 1, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-11 03:37:15.424776+00');
|
||||
@@ -143,7 +145,6 @@ INSERT INTO "public"."iam_perm_menu" VALUES (603, 6, 'trade:refund', 'admin', 'R
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (309, 308, 'system:notify:notice', 'admin', 'SystemNotify', 'menu.system.notify.notice', 'lucide:megaphone', 'f', 'f', '/system/notify/notice/NoticeList', '/system/notify/notice', NULL, 20, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, '2026-06-24 16:00:00+00', '2026-06-24 16:00:00+00', NULL);
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (308, 3, 'system:notify', 'admin', 'SystemNotice', 'menu.system.notify', 'lucide:bell', 'f', 'f', NULL, '/system/notify', '/system/notify/notice', 20, 'f', 't', 'f', 1, 1, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-01 07:16:30.927645+00', '2026-07-01 07:16:30.927645+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (40502, 40508, 'payment:isv', 'admin', 'AlipayIsvAppManage', 'menu.payment.config.alipayApp', NULL, 't', 'f', '/payment/channel/alipay/manage/app/AlipayIsvAppManage', '/payment/config/product/app-manage', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-07-11 03:37:15.43231+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (901, 4, 'device:qrcode', 'admin', 'DeviceQrCode', 'menu.device.qrcode', 'lucide:qr-code', 'f', 'f', '/payment/device/qrcode/DeviceQrCode', '/device/qrcode', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-25 01:21:34.634639+00', '2026-06-25 12:51:25.24304+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (4040119, 4040132, 'channel:app', 'admin', 'WechatIsvMchAppManage', 'menu.payment.merchant.channelMerchant.wechatIsvApp', NULL, 't', 'f', '/payment/channel/wechat/manage/mch/isv-app/WechatIsvMchAppManage', '/payment/merchant/channel-merchant/wechat-isv-mch-app-manage', NULL, 14, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-05 01:49:32.557374+00', '2026-07-11 03:37:15.430392+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (30403, 301, 'system:protocol', 'admin', 'UserProtocol', 'menu.system.basic.protocol', 'lucide:file-text', 'f', 'f', '/system/basic/protocol/UserProtocolList', '/system/basic/protocol', NULL, 2, 'f', 't', 'f', 1, 1, 1, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-24 10:32:13.183371+00', '2026-07-16 08:43:35.038937+00');
|
||||
INSERT INTO "public"."iam_perm_menu" VALUES (803, 8, 'develop:auth', 'admin', 'ChannelAuth', 'menu.develop.auth', 'lucide:key-round', 'f', 'f', '/payment/develop/auth/ChannelAuth', '/develop/auth', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-09 16:00:00+00', '2026-07-09 16:00:00+00');
|
||||
|
||||
@@ -313,3 +313,83 @@ DROP INDEX IF EXISTS "idx_pay_callback_record_channel";
|
||||
ALTER TABLE "public"."pay_callback_record" DROP COLUMN IF EXISTS "channel";
|
||||
CREATE INDEX IF NOT EXISTS "idx_pay_callback_record_product" ON "public"."pay_callback_record" USING btree ("product");
|
||||
|
||||
-- ----------------------------
|
||||
-- 数据修复: 成功结算类资金凭证补齐 posted_amount
|
||||
-- 报表成交口径为 status=success AND posted_amount>0;若历史成功单未走
|
||||
-- PayUniHandleService 回写导致 posted_amount 仍为 0,工作台/分析页金额会全 0。
|
||||
-- 预授权冻结(trade_type=authorize) 入账金额恒为 0,故意排除。
|
||||
-- 可重复执行(幂等)。
|
||||
-- ----------------------------
|
||||
UPDATE "public"."pay_trade"
|
||||
SET "posted_amount" = "amount"
|
||||
WHERE "status" = 'success'
|
||||
AND "trade_type" <> 'authorize'
|
||||
AND COALESCE("posted_amount", 0) = 0
|
||||
AND COALESCE("amount", 0) > 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- 数据修复: 补齐支付渠道 provider(渠道分布/成功率报表依赖)
|
||||
-- 报表 SQL 强制 provider IS NOT NULL;历史单创建时未写 provider 会导致四张渠道图全空。
|
||||
-- 优先用目录表 pay_md_provider_method(method→provider) 回填容器, 再同步到 pay_trade。
|
||||
-- 可重复执行(幂等)。
|
||||
-- ----------------------------
|
||||
|
||||
-- 1) 普通支付容器: method → provider
|
||||
UPDATE "public"."pay_normal_order" o
|
||||
SET "provider" = m."provider"
|
||||
FROM "public"."pay_md_provider_method" m
|
||||
WHERE o."provider" IS NULL
|
||||
AND o."method" IS NOT NULL
|
||||
AND o."method" = m."method"
|
||||
AND m."deleted" = false
|
||||
AND m."provider" IS NOT NULL;
|
||||
|
||||
-- 2) 网关支付容器: method → provider
|
||||
UPDATE "public"."pay_gateway_order" o
|
||||
SET "provider" = m."provider"
|
||||
FROM "public"."pay_md_provider_method" m
|
||||
WHERE o."provider" IS NULL
|
||||
AND o."method" IS NOT NULL
|
||||
AND o."method" = m."method"
|
||||
AND m."deleted" = false
|
||||
AND m."provider" IS NOT NULL;
|
||||
|
||||
-- 3) 资金凭证: 从普通支付容器同步
|
||||
UPDATE "public"."pay_trade" t
|
||||
SET "provider" = o."provider"
|
||||
FROM "public"."pay_normal_order" o
|
||||
WHERE t."trade_type" = 'normal'
|
||||
AND t."container_id" = o."id"
|
||||
AND t."provider" IS NULL
|
||||
AND o."provider" IS NOT NULL;
|
||||
|
||||
-- 4) 资金凭证: 从网关支付容器同步
|
||||
UPDATE "public"."pay_trade" t
|
||||
SET "provider" = o."provider"
|
||||
FROM "public"."pay_gateway_order" o
|
||||
WHERE t."trade_type" = 'gateway'
|
||||
AND t."container_id" = o."id"
|
||||
AND t."provider" IS NULL
|
||||
AND o."provider" IS NOT NULL;
|
||||
|
||||
-- 5) 资金凭证兜底: 容器仍无 provider 时直接 JOIN 目录表(按容器 method)
|
||||
UPDATE "public"."pay_trade" t
|
||||
SET "provider" = m."provider"
|
||||
FROM "public"."pay_normal_order" o
|
||||
JOIN "public"."pay_md_provider_method" m
|
||||
ON m."method" = o."method" AND m."deleted" = false AND m."provider" IS NOT NULL
|
||||
WHERE t."trade_type" = 'normal'
|
||||
AND t."container_id" = o."id"
|
||||
AND t."provider" IS NULL
|
||||
AND o."method" IS NOT NULL;
|
||||
|
||||
UPDATE "public"."pay_trade" t
|
||||
SET "provider" = m."provider"
|
||||
FROM "public"."pay_gateway_order" o
|
||||
JOIN "public"."pay_md_provider_method" m
|
||||
ON m."method" = o."method" AND m."deleted" = false AND m."provider" IS NOT NULL
|
||||
WHERE t."trade_type" = 'gateway'
|
||||
AND t."container_id" = o."id"
|
||||
AND t."provider" IS NULL
|
||||
AND o."method" IS NOT NULL;
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ public class DashboardTradeController {
|
||||
|
||||
// ===== 时段 =====
|
||||
|
||||
@Operation(summary = "24小时时段分布(指定天数或自定义区间)")
|
||||
@Operation(summary = "时段分布(日均): 区间内按小时汇总后除以天数, 补齐 0-23")
|
||||
@GetMapping("/hourly-dist")
|
||||
public Result<List<HourlyDistItemResult>> hourlyDist(
|
||||
@RequestParam(defaultValue = "7") int days,
|
||||
|
||||
@@ -184,15 +184,19 @@ public class DashboardTradeService {
|
||||
|
||||
// ===== 时段分布 =====
|
||||
|
||||
/// 24 小时时段分布(指定天数), 补齐 0-23 缺失小时
|
||||
/// 时段分布(日均, 指定天数): 区间内按小时汇总后 ÷ 天数, 补齐 0-23
|
||||
public List<HourlyDistItemResult> hourlyDist(int days) {
|
||||
OffsetDateTime[] range = daysRange(days);
|
||||
return fillHourlyDist(tradeReportMapper.hourlyDist(range[0], range[1]));
|
||||
int daysSpan = clampDays(days);
|
||||
return toDailyAverage(fillHourlyDist(tradeReportMapper.hourlyDist(range[0], range[1])), daysSpan);
|
||||
}
|
||||
|
||||
/// 24 小时时段分布(自定义区间)
|
||||
/// 时段分布(日均, 自定义区间): 区间内按小时汇总后 ÷ 天数, 补齐 0-23
|
||||
public List<HourlyDistItemResult> hourlyDist(String startStr, String endStr) {
|
||||
return fillHourlyDist(tradeReportMapper.hourlyDist(parseDateStart(startStr), parseDateEndExclusive(endStr)));
|
||||
OffsetDateTime start = parseDateStart(startStr);
|
||||
OffsetDateTime end = parseDateEndExclusive(endStr);
|
||||
long daysSpan = Math.max(1L, java.time.Duration.between(start, end).toDays());
|
||||
return toDailyAverage(fillHourlyDist(tradeReportMapper.hourlyDist(start, end)), daysSpan);
|
||||
}
|
||||
|
||||
// ===== 金额区间分桶 =====
|
||||
@@ -303,7 +307,7 @@ public class DashboardTradeService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 时段分布补齐 0-23 缺失小时
|
||||
/// 时段分布补齐 0-23 缺失小时(此时仍为区间合计, 日均见 [toDailyAverage])
|
||||
private List<HourlyDistItemResult> fillHourlyDist(List<HourlyDistItemResult> rows) {
|
||||
Map<Integer, HourlyDistItemResult> indexed = new HashMap<>(rows.size());
|
||||
for (HourlyDistItemResult row : rows) {
|
||||
@@ -313,14 +317,33 @@ public class DashboardTradeService {
|
||||
for (int h = 0; h < 24; h++) {
|
||||
HourlyDistItemResult row = indexed.get(h);
|
||||
if (row != null) {
|
||||
if (row.getCount() == null) {
|
||||
row.setCount(0D);
|
||||
}
|
||||
if (row.getAmount() == null) {
|
||||
row.setAmount(0L);
|
||||
}
|
||||
result.add(row);
|
||||
} else {
|
||||
result.add(new HourlyDistItemResult().setHour(h).setAmount(0L).setCount(0L));
|
||||
result.add(new HourlyDistItemResult().setHour(h).setAmount(0L).setCount(0D));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 将区间内按小时汇总的金额/笔数转为日均(天数至少为 1)
|
||||
/// 笔数保留 1 位小数; 金额(分)四舍五入为 Long
|
||||
private List<HourlyDistItemResult> toDailyAverage(List<HourlyDistItemResult> rows, long daysSpan) {
|
||||
long days = Math.max(1L, daysSpan);
|
||||
for (HourlyDistItemResult row : rows) {
|
||||
long amount = row.getAmount() == null ? 0L : row.getAmount();
|
||||
double count = row.getCount() == null ? 0D : row.getCount();
|
||||
row.setAmount(Math.round((double) amount / days));
|
||||
row.setCount(Math.round(count * 10.0 / days) / 10.0);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/// 金额区间补齐 5 个缺失桶(保持固定顺序)
|
||||
private List<AmountRangeItemResult> fillAmountRange(List<AmountRangeItemResult> rows) {
|
||||
Map<String, Long> indexed = new HashMap<>(rows.size());
|
||||
|
||||
@@ -2,9 +2,7 @@ package cn.daxpay.open.payment.strategy;
|
||||
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.PayProviderEnum;
|
||||
import cn.daxpay.open.platform.core.exception.business.UnsupportedAbilityException;
|
||||
import cn.daxpay.open.payment.strategy.PaymentStrategy;
|
||||
import cn.daxpay.open.payment.strategy.product.AbsProductStrategy;
|
||||
import cn.daxpay.open.payment.strategy.ProductStrategySupport;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public class PayTrade extends MchBaseEntity {
|
||||
/// 通道商户号(冗余自业务容器, 路由确定后写入; 权威在容器 channelMchNo)
|
||||
private String channelMchNo;
|
||||
|
||||
/// 支付渠道(冗余自业务容器, 支付成功sync后回填; 权威在容器 provider)
|
||||
/// 支付渠道(冗余自业务容器; 下单时由 method→[PayProviderEnum] 写入, 成功路径兜底补齐)
|
||||
/// @see cn.daxpay.open.platform.core.enums.pay.channel.PayProviderEnum
|
||||
private String provider;
|
||||
|
||||
|
||||
@@ -136,8 +136,8 @@ public interface TradeReportMapper extends MPJBaseMapper<PayTrade> {
|
||||
|
||||
// ===== 时段分布 =====
|
||||
|
||||
/// 24 小时时段分布: 按 CST 小时(0-23)分组, 返回各时段成交金额(分)与笔数
|
||||
/// 仅含有成交的小时(Service 补齐 0-23 缺失时段)
|
||||
/// 24 小时时段分布: 按 CST 小时(0-23)分组, 返回各时段成交金额(分)与笔数(区间合计)
|
||||
/// 仅含有成交的小时(Service 补齐 0-23 缺失时段后, 再除以区间天数得到日均)
|
||||
@Select("SELECT EXTRACT(HOUR FROM pay_time AT TIME ZONE 'Asia/Shanghai')::int AS hour, "
|
||||
+ "COALESCE(SUM(posted_amount), 0)::bigint AS amount, "
|
||||
+ "COUNT(*)::bigint AS count "
|
||||
|
||||
@@ -6,22 +6,22 @@ import lombok.experimental.Accessors;
|
||||
|
||||
/// # 时段分布单项结果
|
||||
///
|
||||
/// 一小时区间内的成交金额与笔数(按业务时区 Asia/Shanghai 取 HOUR).
|
||||
/// 按业务时区 Asia/Shanghai 取 HOUR(0-23) 在所选区间内汇总后, 由 Service 除以区间天数得到**日均**。
|
||||
/// Service 层负责补齐 0-23 缺失小时, 保证前端柱图完整。
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "时段分布单项")
|
||||
@Schema(title = "时段分布单项(日均)")
|
||||
public class HourlyDistItemResult {
|
||||
|
||||
/// 小时(0-23)
|
||||
@Schema(description = "小时(0-23)")
|
||||
private Integer hour;
|
||||
|
||||
/// 成交金额(分)
|
||||
@Schema(description = "成交金额(分)")
|
||||
/// 日均成交金额(分, 四舍五入)
|
||||
@Schema(description = "日均成交金额(分)")
|
||||
private Long amount;
|
||||
|
||||
/// 成交笔数
|
||||
@Schema(description = "成交笔数")
|
||||
private Long count;
|
||||
/// 日均成交笔数(保留 1 位小数)
|
||||
@Schema(description = "日均成交笔数")
|
||||
private Double count;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ import cn.daxpay.open.payment.trade.order.entity.PayTrade;
|
||||
import cn.daxpay.open.payment.trade.notice.service.TradeNoticeBridge;
|
||||
import cn.daxpay.open.payment.trade.runtime.service.plugin.PayPluginAssistService;
|
||||
import cn.daxpay.open.payment.trade.util.PayTradeAmountUtil;
|
||||
import cn.daxpay.open.payment.trade.util.PayTradeProviderUtil;
|
||||
import cn.daxpay.open.platform.core.enums.pay.notice.NoticeEventEnum;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -47,10 +49,11 @@ public class PayUniHandleService {
|
||||
if (result.getRelationOrderNo() != null) {
|
||||
trade.setRelationOrderNo(result.getRelationOrderNo());
|
||||
}
|
||||
// 按 status/tradeType 回写入账金额后再落库
|
||||
updateTradeWithPosted(trade);
|
||||
if (isGateway(trade)) {
|
||||
GatewayPayOrder order = gatewayPayOrderManager.findById(trade.getContainerId()).orElse(null);
|
||||
// 渠道分布报表依赖 trade.provider: 空则从容器/method 兜底
|
||||
applyProviderFallback(trade, order);
|
||||
updateTradeWithPosted(trade);
|
||||
if (order != null) {
|
||||
applyGatewayReceipts(order, result);
|
||||
if (Objects.equals(trade.getStatus(), PayFundStatusEnum.SUCCESS.getCode())) {
|
||||
@@ -61,6 +64,8 @@ public class PayUniHandleService {
|
||||
}
|
||||
} else {
|
||||
NormalPayOrder order = payNormalOrderManager.findById(trade.getContainerId()).orElse(null);
|
||||
applyProviderFallback(trade, order);
|
||||
updateTradeWithPosted(trade);
|
||||
if (order != null) {
|
||||
applyNormalReceipts(order, result);
|
||||
if (Objects.equals(trade.getStatus(), PayFundStatusEnum.SUCCESS.getCode())) {
|
||||
@@ -80,13 +85,14 @@ public class PayUniHandleService {
|
||||
|
||||
/// 支付成功后续处理(同步/回调路径), 含通道回执写容器
|
||||
public void paySuccess(PayTrade trade, PaySyncResultBo syncResult) {
|
||||
updateTradeWithPosted(trade);
|
||||
if (isGateway(trade)) {
|
||||
GatewayPayOrder order = gatewayPayOrderManager.findById(trade.getContainerId()).orElse(null);
|
||||
applyGatewaySyncReceipts(trade, order, syncResult);
|
||||
applyProviderFallback(trade, order);
|
||||
updateTradeWithPosted(trade);
|
||||
if (order != null) {
|
||||
order.setStatus(GatewayOrderStatusEnum.PAID.getCode());
|
||||
order.setPayTime(trade.getPayTime());
|
||||
applyGatewaySyncReceipts(trade, order, syncResult);
|
||||
gatewayPayOrderManager.updateById(order);
|
||||
}
|
||||
// 商户出站通知(系统协议)
|
||||
@@ -95,10 +101,12 @@ public class PayUniHandleService {
|
||||
return;
|
||||
}
|
||||
NormalPayOrder order = payNormalOrderManager.findById(trade.getContainerId()).orElse(null);
|
||||
applyNormalSyncReceipts(trade, order, syncResult);
|
||||
applyProviderFallback(trade, order);
|
||||
updateTradeWithPosted(trade);
|
||||
if (order != null) {
|
||||
order.setStatus(NormalPayOrderStatusEnum.PAID.getCode());
|
||||
order.setPayTime(trade.getPayTime());
|
||||
applyNormalSyncReceipts(trade, order, syncResult);
|
||||
payNormalOrderManager.updateById(order);
|
||||
}
|
||||
// 商户出站通知(系统协议)
|
||||
@@ -108,8 +116,17 @@ public class PayUniHandleService {
|
||||
|
||||
/// 支付成功后续处理(回调路径, 无回执详情)
|
||||
public void paySuccess(PayTrade trade) {
|
||||
updateTradeWithPosted(trade);
|
||||
markContainerPaid(trade);
|
||||
if (isGateway(trade)) {
|
||||
GatewayPayOrder order = gatewayPayOrderManager.findById(trade.getContainerId()).orElse(null);
|
||||
applyProviderFallback(trade, order);
|
||||
updateTradeWithPosted(trade);
|
||||
markContainerPaid(trade, order);
|
||||
} else {
|
||||
NormalPayOrder order = payNormalOrderManager.findById(trade.getContainerId()).orElse(null);
|
||||
applyProviderFallback(trade, order);
|
||||
updateTradeWithPosted(trade);
|
||||
markContainerPaid(trade, order);
|
||||
}
|
||||
// 商户出站通知(系统协议)
|
||||
tradeNoticeBridge.dispatchPay(trade, NoticeEventEnum.PAY_SUCCESS);
|
||||
payPluginAssistService.paySuccess(trade);
|
||||
@@ -176,22 +193,29 @@ public class PayUniHandleService {
|
||||
gatewayPayOrderManager.updateById(order);
|
||||
}
|
||||
|
||||
private void markContainerPaid(PayTrade trade) {
|
||||
if (isGateway(trade)) {
|
||||
GatewayPayOrder order = gatewayPayOrderManager.findById(trade.getContainerId()).orElse(null);
|
||||
if (order != null) {
|
||||
order.setStatus(GatewayOrderStatusEnum.PAID.getCode());
|
||||
order.setPayTime(trade.getPayTime());
|
||||
gatewayPayOrderManager.updateById(order);
|
||||
}
|
||||
/// 容器置已支付; 若已加载实体则复用, 避免重复查询, 并同步 provider
|
||||
private void markContainerPaid(PayTrade trade, GatewayPayOrder order) {
|
||||
if (order == null) {
|
||||
return;
|
||||
}
|
||||
NormalPayOrder normalOrder = payNormalOrderManager.findById(trade.getContainerId()).orElse(null);
|
||||
if (normalOrder != null) {
|
||||
normalOrder.setStatus(NormalPayOrderStatusEnum.PAID.getCode());
|
||||
normalOrder.setPayTime(trade.getPayTime());
|
||||
payNormalOrderManager.updateById(normalOrder);
|
||||
order.setStatus(GatewayOrderStatusEnum.PAID.getCode());
|
||||
order.setPayTime(trade.getPayTime());
|
||||
if (StrUtil.isBlank(order.getProvider()) && StrUtil.isNotBlank(trade.getProvider())) {
|
||||
order.setProvider(trade.getProvider());
|
||||
}
|
||||
gatewayPayOrderManager.updateById(order);
|
||||
}
|
||||
|
||||
private void markContainerPaid(PayTrade trade, NormalPayOrder order) {
|
||||
if (order == null) {
|
||||
return;
|
||||
}
|
||||
order.setStatus(NormalPayOrderStatusEnum.PAID.getCode());
|
||||
order.setPayTime(trade.getPayTime());
|
||||
if (StrUtil.isBlank(order.getProvider()) && StrUtil.isNotBlank(trade.getProvider())) {
|
||||
order.setProvider(trade.getProvider());
|
||||
}
|
||||
payNormalOrderManager.updateById(order);
|
||||
}
|
||||
|
||||
/// 支付失败: 容器 FAILED
|
||||
@@ -292,9 +316,15 @@ public class PayUniHandleService {
|
||||
return;
|
||||
}
|
||||
if (Objects.nonNull(syncResult.getProvider())) {
|
||||
order.setProvider(syncResult.getProvider().getCode());
|
||||
String providerCode = syncResult.getProvider().getCode();
|
||||
if (order != null) {
|
||||
order.setProvider(providerCode);
|
||||
}
|
||||
// 冗余至资金凭证, 渠道分布报表/资金列表免 JOIN 容器
|
||||
trade.setProvider(syncResult.getProvider().getCode());
|
||||
trade.setProvider(providerCode);
|
||||
}
|
||||
if (order == null) {
|
||||
return;
|
||||
}
|
||||
order.setBuyerId(syncResult.getBuyerId());
|
||||
order.setTradeProduct(syncResult.getTradeProduct());
|
||||
@@ -309,9 +339,15 @@ public class PayUniHandleService {
|
||||
return;
|
||||
}
|
||||
if (Objects.nonNull(syncResult.getProvider())) {
|
||||
order.setProvider(syncResult.getProvider().getCode());
|
||||
String providerCode = syncResult.getProvider().getCode();
|
||||
if (order != null) {
|
||||
order.setProvider(providerCode);
|
||||
}
|
||||
// 冗余至资金凭证, 渠道分布报表/资金列表免 JOIN 容器
|
||||
trade.setProvider(syncResult.getProvider().getCode());
|
||||
trade.setProvider(providerCode);
|
||||
}
|
||||
if (order == null) {
|
||||
return;
|
||||
}
|
||||
order.setBuyerId(syncResult.getBuyerId());
|
||||
order.setTradeProduct(syncResult.getTradeProduct());
|
||||
@@ -321,6 +357,33 @@ public class PayUniHandleService {
|
||||
order.setErrorMsg(null);
|
||||
}
|
||||
|
||||
/// trade.provider 为空时从容器 provider / method 兜底, 并回写容器空 provider
|
||||
private void applyProviderFallback(PayTrade trade, NormalPayOrder order) {
|
||||
String containerProvider = order != null ? order.getProvider() : null;
|
||||
String method = order != null ? order.getMethod() : null;
|
||||
String provider = PayTradeProviderUtil.coalesceProvider(trade.getProvider(), containerProvider, method);
|
||||
if (StrUtil.isBlank(provider)) {
|
||||
return;
|
||||
}
|
||||
trade.setProvider(provider);
|
||||
if (order != null && StrUtil.isBlank(order.getProvider())) {
|
||||
order.setProvider(provider);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyProviderFallback(PayTrade trade, GatewayPayOrder order) {
|
||||
String containerProvider = order != null ? order.getProvider() : null;
|
||||
String method = order != null ? order.getMethod() : null;
|
||||
String provider = PayTradeProviderUtil.coalesceProvider(trade.getProvider(), containerProvider, method);
|
||||
if (StrUtil.isBlank(provider)) {
|
||||
return;
|
||||
}
|
||||
trade.setProvider(provider);
|
||||
if (order != null && StrUtil.isBlank(order.getProvider())) {
|
||||
order.setProvider(provider);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isGateway(PayTrade trade) {
|
||||
return Objects.equals(trade.getTradeType(), PayTradeTypeEnum.GATEWAY.getCode());
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import cn.daxpay.open.platform.core.util.TradeNoGenerateUtil;
|
||||
import cn.daxpay.open.platform.system.entity.config.platform.security.PlatformPaySecurityConfig;
|
||||
import cn.daxpay.open.platform.system.service.config.security.PlatformSecurityConfigService;
|
||||
import cn.daxpay.open.payment.trade.util.PayTradeInitUtil;
|
||||
import cn.daxpay.open.payment.trade.util.PayTradeProviderUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -57,6 +58,7 @@ public class GatewayPayHandleService {
|
||||
private final PlatformSecurityConfigService platformSecurityConfigService;
|
||||
|
||||
/// 自注入,保证 [GatewayPayHandleService#createTrade] / [GatewayPayHandleService#paySuccess] 走 Spring 事务代理
|
||||
@Lazy
|
||||
private final GatewayPayHandleService self;
|
||||
|
||||
/// 发起网关支付
|
||||
@@ -163,7 +165,8 @@ public class GatewayPayHandleService {
|
||||
}
|
||||
|
||||
// 默认上送网关业务单号; 特殊通道返回后可覆盖
|
||||
// channelMchNo 取自路由后的 payParam; storeNo 冗余自预下单容器
|
||||
// channelMchNo 取自路由后的 payParam; storeNo 冗余自预下单容器; provider 由 method 派生
|
||||
String provider = PayTradeProviderUtil.resolveProviderByMethod(payParam.getMethod());
|
||||
PayTrade trade = PayTradeInitUtil.initProcessing(
|
||||
order.getAppId(),
|
||||
TradeNoGenerateUtil.pay(),
|
||||
@@ -174,13 +177,16 @@ public class GatewayPayHandleService {
|
||||
source,
|
||||
payParam.getChannelMchNo(),
|
||||
order.getStoreNo(),
|
||||
order.getTitle());
|
||||
order.getTitle(),
|
||||
provider);
|
||||
payTradeManager.save(trade);
|
||||
|
||||
this.fillRouteOnOrder(order, payParam, clientEnv, device);
|
||||
order.setStatus(GatewayOrderStatusEnum.PAYING.getCode());
|
||||
order.setChannel(channel);
|
||||
order.setMethod(payParam.getMethod());
|
||||
// 支付渠道: 与 trade 同步, 渠道分布报表免 JOIN
|
||||
order.setProvider(provider);
|
||||
order.setLimitPay(payParam.getLimitPay() != null
|
||||
? String.join(",", payParam.getLimitPay()) : null);
|
||||
order.setOpenid(payParam.getOpenId());
|
||||
|
||||
@@ -23,6 +23,7 @@ import cn.daxpay.open.payment.trade.order.entity.PayTrade;
|
||||
import cn.daxpay.open.payment.trade.runtime.mq.NormalPayTimeoutMessage;
|
||||
import cn.daxpay.open.payment.trade.runtime.mq.PayArtemisConstants;
|
||||
import cn.daxpay.open.payment.trade.util.PayTradeInitUtil;
|
||||
import cn.daxpay.open.payment.trade.util.PayTradeProviderUtil;
|
||||
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
|
||||
import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -109,6 +110,8 @@ public class NormalPayAssistService {
|
||||
// --- 支付路由 ---
|
||||
normalOrder.setChannel(channel);
|
||||
normalOrder.setMethod(payParam.getMethod());
|
||||
// 支付渠道: 由 method 派生, 渠道分布报表/详情展示用
|
||||
normalOrder.setProvider(PayTradeProviderUtil.resolveProviderByMethod(payParam.getMethod()));
|
||||
normalOrder.setLimitPay(payParam.getLimitPay() != null
|
||||
? String.join(",", payParam.getLimitPay()) : null);
|
||||
normalOrder.setOpenid(payParam.getOpenId());
|
||||
@@ -136,7 +139,7 @@ public class NormalPayAssistService {
|
||||
}
|
||||
|
||||
/// 组装资金交易(未落库); relationOrderNo 默认=orderNo, 特殊通道支付返回后可覆盖
|
||||
/// channelMchNo / storeNo 冗余自容器,资金列表免 JOIN
|
||||
/// channelMchNo / storeNo / provider 冗余自容器,资金列表与渠道报表免 JOIN
|
||||
private PayTrade buildPayTrade(NormalPayParam payParam, NormalPayOrder normalOrder,
|
||||
String tradeNo, String orderNo, String source) {
|
||||
return PayTradeInitUtil.initProcessing(
|
||||
@@ -149,7 +152,8 @@ public class NormalPayAssistService {
|
||||
source,
|
||||
normalOrder.getChannelMchNo(),
|
||||
normalOrder.getStoreNo(),
|
||||
normalOrder.getTitle());
|
||||
normalOrder.getTitle(),
|
||||
normalOrder.getProvider());
|
||||
}
|
||||
|
||||
/// 注册超时关单延时消息
|
||||
|
||||
@@ -7,7 +7,7 @@ import cn.daxpay.open.platform.core.enums.pay.channel.CurrencyEnum;
|
||||
/// # 资金交易初始化工具
|
||||
///
|
||||
/// PROCESSING 态资金凭证公共字段的单一事实源。
|
||||
/// 调用方负责 tradeType / container / source / channelMchNo / storeNo 等业务差异,本工具只填
|
||||
/// 调用方负责 tradeType / container / source / channelMchNo / storeNo / provider 等业务差异,本工具只填
|
||||
/// 「待处理结算类交易」公共规则:
|
||||
/// - currency = CNY
|
||||
/// - postedAmount = 0(成功后由 [cn.daxpay.open.payment.trade.runtime.service.pay.common.PayUniHandleService]
|
||||
@@ -31,6 +31,7 @@ public final class PayTradeInitUtil {
|
||||
/// @param channelMchNo 通道商户号(冗余自容器/路由结果,可空仅当路由未定时)
|
||||
/// @param storeNo 门店号(冗余自容器,可空)
|
||||
/// @param title 订单标题(冗余自容器,资金列表/工作台免JOIN)
|
||||
/// @param provider 支付渠道(由 method 派生,可空;权威亦可在容器 provider)
|
||||
public static PayTrade initProcessing(
|
||||
String appId,
|
||||
String tradeNo,
|
||||
@@ -41,7 +42,8 @@ public final class PayTradeInitUtil {
|
||||
String source,
|
||||
String channelMchNo,
|
||||
String storeNo,
|
||||
String title) {
|
||||
String title,
|
||||
String provider) {
|
||||
return new PayTrade()
|
||||
.setAppId(appId)
|
||||
.setTradeNo(tradeNo)
|
||||
@@ -58,6 +60,8 @@ public final class PayTradeInitUtil {
|
||||
.setSource(source)
|
||||
// 轻量组织冗余: 权威在业务容器
|
||||
.setChannelMchNo(channelMchNo)
|
||||
.setStoreNo(storeNo);
|
||||
.setStoreNo(storeNo)
|
||||
// 支付渠道: 下单时由 method 派生, 渠道分布报表免 JOIN
|
||||
.setProvider(provider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.daxpay.open.payment.trade.util;
|
||||
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.PayProviderEnum;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/// # 资金凭证支付渠道解析
|
||||
///
|
||||
/// 报表/资金列表依赖 [cn.daxpay.open.payment.trade.order.entity.PayTrade#getProvider];
|
||||
/// 渠道在下单时即可由支付方式确定,不依赖三方 sync 透传。
|
||||
/// [PayMethodEnum#OTHER] 无固定渠道归属时返回 null。
|
||||
public final class PayTradeProviderUtil {
|
||||
|
||||
private PayTradeProviderUtil() {
|
||||
}
|
||||
|
||||
/// 由支付方式编码解析支付渠道编码;method 空/未知/OTHER 返回 null(不抛异常)
|
||||
public static String resolveProviderByMethod(String methodCode) {
|
||||
if (StrUtil.isBlank(methodCode)) {
|
||||
return null;
|
||||
}
|
||||
return Arrays.stream(PayMethodEnum.values())
|
||||
.filter(o -> Objects.equals(o.getCode(), methodCode))
|
||||
.findFirst()
|
||||
.map(PayMethodEnum::getProvider)
|
||||
.map(PayProviderEnum::getCode)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/// 优先保留已有 provider;否则用容器已存值;再否则由 method 派生
|
||||
public static String coalesceProvider(String tradeProvider, String containerProvider, String methodCode) {
|
||||
if (StrUtil.isNotBlank(tradeProvider)) {
|
||||
return tradeProvider;
|
||||
}
|
||||
if (StrUtil.isNotBlank(containerProvider)) {
|
||||
return containerProvider;
|
||||
}
|
||||
return resolveProviderByMethod(methodCode);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user