From 7888562f60b6327ff7208adbcaf90be89efcd93f Mon Sep 17 00:00:00 2001 From: wuxicheng <1441859745@qq.com> Date: Fri, 9 Jun 2023 17:03:08 +0800 Subject: [PATCH] =?UTF-8?q?mq=E4=BC=98=E5=8C=96,=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E6=B7=BB=E5=8A=A0=E9=87=8D=E8=AF=95=E6=9C=BA?= =?UTF-8?q?=E5=88=B6,=E7=BB=9F=E4=B8=80=E5=BC=82=E5=B8=B8=E7=AE=A1?= =?UTF-8?q?=E7=90=86,=E5=B9=B6=E6=B7=BB=E5=8A=A0=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/constant/RedisKeyConstant.java | 11 + .../bnyer/common/core/domain/DypayConfig.java | 22 +- .../rocketmq/constant/RocketMqTopic.java | 5 + .../domain/pay/ThirdPayOrderPushMessage.java | 21 + .../handle/EnhanceMessageHandler.java | 6 + .../img/listener/GoldRewardConsumer.java | 2 +- .../listener/ImgReturnMessageConsumer.java | 2 +- .../img/listener/VipRecordCreateConsumer.java | 2 +- .../listener/OrderReturnMessageConsumer.java | 2 +- .../listener/vip/VipOrderCancelConsumer.java | 2 +- .../vip/VipOrderPayNotifyConsumer.java | 2 +- .../service/impl/VipOrderServiceImpl.java | 4 - .../com/bnyer/pay/bean/bo/DyPushOrderBo.java | 220 ++++++++++ .../com/bnyer/pay/bean/bo/PushOrderBo.java | 93 +++++ .../com/bnyer/pay/bean/dto/PushOrderDto.java | 25 ++ .../java/com/bnyer/pay/bean/vo/PayInfoVo.java | 6 + .../bnyer/pay/constant/DYPayConstants.java | 9 + .../design/strategy/AbstractPayStrategy.java | 12 +- .../pay/design/strategy/DYPayStrategy.java | 98 +++-- .../pay/design/strategy/IPayStrategy.java | 7 + .../pay/design/strategy/KSPayStrategy.java | 30 +- .../bnyer/pay/enums/EnumDyOrderStatus.java | 37 ++ .../com/bnyer/pay/exception/PayException.java | 41 ++ .../listener/PayReturnMessageConsumer.java | 2 +- .../listener/ThirdPayOrderPushConsumer.java | 67 ++++ .../com/bnyer/pay/manager/DyPayManager.java | 182 +++++++++ .../com/bnyer/pay/manager/KsPayManager.java | 186 +++++++++ .../com/bnyer/pay/service/PayInfoService.java | 1 + .../bnyer/pay/service/UnifiedPayService.java | 15 +- .../pay/service/impl/PayInfoServiceImpl.java | 32 +- .../service/impl/UnifiedPayServiceImpl.java | 83 +++- .../bnyer/pay/utils/PayRestTemplateUtil.java | 376 +++++++++++++----- .../bnyer/pay/mapper/DypayConfigMapper.xml | 3 +- 33 files changed, 1416 insertions(+), 190 deletions(-) create mode 100644 bnyer-common/bnyer-common-rocketmq/src/main/java/com/bnyer/common/rocketmq/domain/pay/ThirdPayOrderPushMessage.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/bo/DyPushOrderBo.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/bo/PushOrderBo.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/dto/PushOrderDto.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/enums/EnumDyOrderStatus.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/exception/PayException.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/listener/ThirdPayOrderPushConsumer.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/manager/DyPayManager.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/manager/KsPayManager.java diff --git a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/constant/RedisKeyConstant.java b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/constant/RedisKeyConstant.java index cb8d6bc..da97778 100644 --- a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/constant/RedisKeyConstant.java +++ b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/constant/RedisKeyConstant.java @@ -166,4 +166,15 @@ public class RedisKeyConstant { * 支付回调rediskey */ public static final String PAY_NOTIFY_LOCK_KEY = "bnyer.pay.notify.lock:"; + + /** + * 抖音ACCESS_TOKEN + */ + public static final String DY_ACCESS_TOKEN_KEY = "bnyer.pay.dy.access.token"; + + /** + * 快手ACCESS_TOKEN + */ + public static final String KS_ACCESS_TOKEN_KEY = "bnyer.pay.ks.access.token"; + } diff --git a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/DypayConfig.java b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/DypayConfig.java index ee4ee11..d132cc9 100644 --- a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/DypayConfig.java +++ b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/DypayConfig.java @@ -1,17 +1,10 @@ package com.bnyer.common.core.domain; -import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; -import java.util.Date; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; +import lombok.*; /** * @author :WXC @@ -36,17 +29,24 @@ public class DypayConfig extends BaseDomain { private String appid; /** - * 秘钥 + * 应用秘钥 + */ + @TableField(value = "appSecret") + @ApiModelProperty(value="应用秘钥") + private String appSecret; + + /** + * 支付秘钥 */ @TableField(value = "salt") - @ApiModelProperty(value="秘钥") + @ApiModelProperty(value="支付秘钥") private String salt; /** * 令牌 */ @TableField(value = "token") - @ApiModelProperty(value="令牌") + @ApiModelProperty(value="支付令牌") private String token; /** diff --git a/bnyer-common/bnyer-common-rocketmq/src/main/java/com/bnyer/common/rocketmq/constant/RocketMqTopic.java b/bnyer-common/bnyer-common-rocketmq/src/main/java/com/bnyer/common/rocketmq/constant/RocketMqTopic.java index 18a4004..5144205 100644 --- a/bnyer-common/bnyer-common-rocketmq/src/main/java/com/bnyer/common/rocketmq/constant/RocketMqTopic.java +++ b/bnyer-common/bnyer-common-rocketmq/src/main/java/com/bnyer/common/rocketmq/constant/RocketMqTopic.java @@ -40,4 +40,9 @@ public class RocketMqTopic { */ public static final String GOLD_REWARD_TOPIC = "gold-reward-topic"; + /** + * 第三方支付订单推送队列 + */ + public static final String THIRD_PAY_ORDER_PUSH_TOPIC = "third-pay-order-push-topic"; + } diff --git a/bnyer-common/bnyer-common-rocketmq/src/main/java/com/bnyer/common/rocketmq/domain/pay/ThirdPayOrderPushMessage.java b/bnyer-common/bnyer-common-rocketmq/src/main/java/com/bnyer/common/rocketmq/domain/pay/ThirdPayOrderPushMessage.java new file mode 100644 index 0000000..b9d485a --- /dev/null +++ b/bnyer-common/bnyer-common-rocketmq/src/main/java/com/bnyer/common/rocketmq/domain/pay/ThirdPayOrderPushMessage.java @@ -0,0 +1,21 @@ +package com.bnyer.common.rocketmq.domain.pay; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * @author :WXC + * @description : + */ +@Getter +@Setter +@NoArgsConstructor +public class ThirdPayOrderPushMessage { + + /** + * 支付单号 + */ + private String payId; + +} diff --git a/bnyer-common/bnyer-common-rocketmq/src/main/java/com/bnyer/common/rocketmq/handle/EnhanceMessageHandler.java b/bnyer-common/bnyer-common-rocketmq/src/main/java/com/bnyer/common/rocketmq/handle/EnhanceMessageHandler.java index c321cb5..9ef57f7 100644 --- a/bnyer-common/bnyer-common-rocketmq/src/main/java/com/bnyer/common/rocketmq/handle/EnhanceMessageHandler.java +++ b/bnyer-common/bnyer-common-rocketmq/src/main/java/com/bnyer/common/rocketmq/handle/EnhanceMessageHandler.java @@ -121,6 +121,12 @@ public abstract class EnhanceMessageHandler { public void dispatchMessage(BnyerMessage message) { // 基础日志记录被父类处理了 log.info("消费者收到消息[{}]", JSONObject.toJSON(message)); + //消息去重 + if (handleMsgRepeat(message)){ + log.info("消息id{}属于重复消息,已过滤。",message.getMessageKey()); + return; + } + //自定义消息过滤规则 if (filter(message)) { log.info("消息id{}不满足消费条件,已过滤。",message.getMessageKey()); return; diff --git a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/listener/GoldRewardConsumer.java b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/listener/GoldRewardConsumer.java index cbb406c..07a882d 100644 --- a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/listener/GoldRewardConsumer.java +++ b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/listener/GoldRewardConsumer.java @@ -137,7 +137,7 @@ public class GoldRewardConsumer extends EnhanceMessageHandler implements RocketM @Override protected boolean filter(BnyerMessage message) { - return super.handleMsgRepeat(message); + return false; } diff --git a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/listener/ImgReturnMessageConsumer.java b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/listener/ImgReturnMessageConsumer.java index ba1ef07..4a7f91d 100644 --- a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/listener/ImgReturnMessageConsumer.java +++ b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/listener/ImgReturnMessageConsumer.java @@ -40,7 +40,7 @@ public class ImgReturnMessageConsumer extends EnhanceMessageHandler implements R @Override protected boolean filter(BnyerMessage message) { - return super.handleMsgRepeat(message); + return false; } @Override diff --git a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/listener/VipRecordCreateConsumer.java b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/listener/VipRecordCreateConsumer.java index f5c0e5d..f5a466c 100644 --- a/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/listener/VipRecordCreateConsumer.java +++ b/bnyer-services/bnyer-img/src/main/java/com/bnyer/img/listener/VipRecordCreateConsumer.java @@ -45,7 +45,7 @@ public class VipRecordCreateConsumer extends EnhanceMessageHandler implements Ro @Override protected boolean filter(BnyerMessage message) { - return super.handleMsgRepeat(message); + return false; } @Override diff --git a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/listener/OrderReturnMessageConsumer.java b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/listener/OrderReturnMessageConsumer.java index c40ead5..d836b0f 100644 --- a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/listener/OrderReturnMessageConsumer.java +++ b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/listener/OrderReturnMessageConsumer.java @@ -42,7 +42,7 @@ public class OrderReturnMessageConsumer extends EnhanceMessageHandler implements @Override protected boolean filter(BnyerMessage message) { - return super.handleMsgRepeat(message); + return false; } @Override diff --git a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/listener/vip/VipOrderCancelConsumer.java b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/listener/vip/VipOrderCancelConsumer.java index abd06b2..4827abb 100644 --- a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/listener/vip/VipOrderCancelConsumer.java +++ b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/listener/vip/VipOrderCancelConsumer.java @@ -50,7 +50,7 @@ public class VipOrderCancelConsumer extends EnhanceMessageHandler implements Roc @Override protected boolean filter(BnyerMessage message) { - return super.handleMsgRepeat(message); + return false; } diff --git a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/listener/vip/VipOrderPayNotifyConsumer.java b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/listener/vip/VipOrderPayNotifyConsumer.java index ba0f47e..925daf8 100644 --- a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/listener/vip/VipOrderPayNotifyConsumer.java +++ b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/listener/vip/VipOrderPayNotifyConsumer.java @@ -63,7 +63,7 @@ public class VipOrderPayNotifyConsumer extends EnhanceMessageHandler implements @Override protected boolean filter(BnyerMessage message) { - return super.handleMsgRepeat(message); + return false; } @Override diff --git a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/service/impl/VipOrderServiceImpl.java b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/service/impl/VipOrderServiceImpl.java index b805e39..fdeb102 100644 --- a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/service/impl/VipOrderServiceImpl.java +++ b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/service/impl/VipOrderServiceImpl.java @@ -1,9 +1,7 @@ package com.bnyer.order.service.impl; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.ObjectUtil; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.bnyer.common.core.domain.R; @@ -262,13 +260,11 @@ public class VipOrderServiceImpl implements VipOrderService { //发消息,添加用户会员记录 AddUserVipRecordMessage addUserVipRecordMessage = buildVipRecordMsg(vipOrder); -// rocketMQEnhanceTemplate.sendAsyncMsg(RocketMqTopic.VIP_RECORD_CREATE_TOPIC,null, addUserVipRecordMessage); orderMqMessageRecordService.sendAsyncMsg(RocketMqTopic.VIP_RECORD_CREATE_TOPIC,null, addUserVipRecordMessage); //发送开会员画意值奖励并写入记录消息 GoldRewardMessage goldMsg = buildGoldRewardMsg(vipOrder.getUserId(),GoldEnum.BUY_VIP.getGoldNum(), GoldEnum.BUY_VIP.getCode(),null,vipOrder.getUserClientType()); -// rocketMQEnhanceTemplate.sendAsyncMsg(RocketMqTopic.GOLD_REWARD_TOPIC,null,goldMsg); orderMqMessageRecordService.sendAsyncMsg(RocketMqTopic.GOLD_REWARD_TOPIC,null,goldMsg); } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/bo/DyPushOrderBo.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/bo/DyPushOrderBo.java new file mode 100644 index 0000000..8598d25 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/bo/DyPushOrderBo.java @@ -0,0 +1,220 @@ +package com.bnyer.pay.bean.bo; + +import com.bnyer.pay.enums.EnumDyOrderStatus; +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; +import java.util.List; + +/** + * 抖音推送订单入参 + */ +@Getter +@Setter +@NoArgsConstructor +public class DyPushOrderBo implements Serializable { + + private static final long serialVersionUID = 1866990048913415295L; + + /** + * 第三方在抖音开放平台申请的 ClientKey + * 注意:POI 订单必传 + * 是否必填:否 + */ + @SerializedName(value = "client_key") + private String clientKey; + + /** + * 服务端 API 调用标识,通过 getAccessToken 获取 + * 是否必填:是 + */ + @SerializedName(value = "access_token") + private String accessToken; + + /** + * POI 店铺同步时使用的开发者侧店铺 ID,购买店铺 ID,长度 < 256 byte + * + * 注意:POI 订单必传 + * 是否必填:否 + */ + @SerializedName(value = "ext_shop_id") + private String extShopId; + + /** + * 做订单展示的字节系 app 名称,目前为固定值“douyin” + * 是否必填:是 + */ + @SerializedName(value = "app_name") + private String appName; + + /** + * 小程序用户的 open_id,通过 code2Session 获取 + * 是否必填:是 + */ + @SerializedName(value = "open_id") + private String openId; + + /** + * json string,根据不同订单类型有不同的结构体,请参见 order_detail 字段说明(json string) + * 是否必填:是 + */ + @SerializedName(value = "order_detail") + private String orderDetail; + + /** + * 普通小程序订单订单状态,POI 订单可以忽略 + 0:待支付 + 1:已支付 + 2:已取消(用户主动取消或者超时未支付导致的关单) + 4:已核销(核销状态是整单核销,即一笔订单买了 3 个券,核销是指 3 个券核销的整单) + 5:退款中 + 6:已退款 + 8:退款失败 + 注意:普通小程序订单必传,担保支付分账依赖该状态 + 是否必填:否 + */ + @SerializedName(value = "order_status") + private Integer orderStatus; + + /** + * 订单类型,枚举值: + * + * 0:普通小程序订单(非POI订单) + * 9101:团购券订单(POI 订单) + * 9001:景区门票订单(POI订单) + * 是否必填:是 + */ + @SerializedName(value = "order_type") + private Integer orderType; + + /** + * 订单信息变更时间,13 位毫秒级时间戳 + * 是否必填:是 + */ + @SerializedName(value = "update_time") + private Integer updateTime; + + /** + * 自定义字段,用于关联具体业务场景下的特殊参数,长度 < 2048byte + * 是否必填:否 + */ + @SerializedName(value = "extra") + private String extra; + + @Getter + @Setter + @NoArgsConstructor + public static class OrderDetails implements Serializable { + private static final long serialVersionUID = 3320384952853603934L; + /** + * 开发者侧业务单号。用作幂等控制。该订单号是和担保支付的支付单号绑定的,也就是预下单时传入的 out_order_no 字段,长度 <= 64byte + * 是否必填:是 + */ + @SerializedName(value = "order_id") + private String orderId; + + /** + * 订单创建的时间,13 位毫秒时间戳 + * 是否必填:是 + */ + @SerializedName(value = "create_time") + private Integer createTime; + + /** + * 订单状态,建议采用以下枚举值: + * + * 待支付 + * 已支付 + * 已取消 + * 已超时 + * 已核销 + * 退款中 + * 已退款 + * 退款失败 + * 是否必填:是 + */ + @SerializedName(value = "status") + private EnumDyOrderStatus status; + + /** + * 订单商品总数 + * 是否必填:是 + */ + @SerializedName(value = "amount") + private Integer amount; + + /** + * 订单总价,单位为分 + * 是否必填:是 + */ + @SerializedName(value = "total_price") + private Integer totalPrice; + + /** + * 小程序订单详情页 path,长度<=1024 byte + * 是否必填:是 + */ + @SerializedName(value = "detail_url") + private String detailUrl; + + /** + * 子订单商品列表,不可为空 + * 是否必填:是 + */ + @SerializedName(value = "item_list") + private List itemList; + + @Getter + @Setter + @NoArgsConstructor + public static class Goods implements Serializable { + + private static final long serialVersionUID = 1418159270677457444L; + + /** + * 开发者侧商品 ID,长度 <= 64 byte + * 是否必填:是 + */ + @SerializedName(value = "item_code") + private String itemCode; + + /** + * 子订单商品图片 URL,长度 <= 512 byte + * 是否必填:是 + */ + @SerializedName(value = "img") + private String img; + + /** + * 子订单商品介绍标题,长度 <= 256 byte + * 是否必填:是 + */ + @SerializedName(value = "title") + private String title; + + /** + * 子订单商品介绍副标题,长度 <= 256 byte + * 是否必填:否 + */ + @SerializedName(value = "sub_title") + private String subTitle; + + /** + * 单类商品的数目 + * 是否必填:否 + */ + @SerializedName(value = "amount") + private Integer amount; + + /** + * 单类商品的总价,单位为分 + * 是否必填:是 + */ + @SerializedName(value = "price") + private Integer price; + } + } +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/bo/PushOrderBo.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/bo/PushOrderBo.java new file mode 100644 index 0000000..2f38484 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/bo/PushOrderBo.java @@ -0,0 +1,93 @@ +package com.bnyer.pay.bean.bo; + +import com.bnyer.common.core.enums.EnumPayStatus; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Date; +import java.util.List; + +/** + * @author :WXC + * @description : + */ +@Getter +@Setter +@NoArgsConstructor +public class PushOrderBo { + + /** + * 开发者侧业务单号 + */ + private String payId; + + /** + * 订单创建的时间 + */ + private Date createTime; + + /** + * 订单变更的时间 + */ + private Date updateTime; + + /** + * 支付状态 + */ + private EnumPayStatus payStatus; + + /** + * 商品总数 + */ + private Integer goodsNum; + + /** + * 订单总价,单位为分 + */ + private Integer totalPrice; + + /** + * 商品列表 + */ + private List goodsList; + + /** + * 商品列表 + */ + @Getter + @Setter + @NoArgsConstructor + public static class Goods{ + /** + * 开发者侧商品 ID + */ + private String itemCode; + + /** + * 子订单商品图片 URL + */ + private String img; + + /** + * 子订单商品介绍标题 + */ + private String title; + + /** + * 子订单商品介绍副标题 + */ + private String subTitle; + + /** + * 单类商品的数目 + */ + private Integer goodsNum; + + /** + * 单类商品的总价,单位为分 + */ + private Integer price; + } + +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/dto/PushOrderDto.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/dto/PushOrderDto.java new file mode 100644 index 0000000..0f21ef2 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/dto/PushOrderDto.java @@ -0,0 +1,25 @@ +package com.bnyer.pay.bean.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; + +/** + * @author :WXC + * @description : + */ +@Getter +@Setter +@NoArgsConstructor +public class PushOrderDto { + + /** + * 支付单号 + */ + @NotBlank(message = "支付单号不能为空") + @ApiModelProperty(value = "支付单号") + private String payId; +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/vo/PayInfoVo.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/vo/PayInfoVo.java index 20e0159..e950fc3 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/vo/PayInfoVo.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/bean/vo/PayInfoVo.java @@ -35,6 +35,12 @@ public class PayInfoVo { @ApiModelProperty(value="单笔对账时间") private Date singleTime; + @ApiModelProperty(value="创建时间") + private Date createTime; + + @ApiModelProperty(value="修改时间") + private Date updateTime; + @ApiModelProperty(value="支付类型:wxpay/alipay/kspay/dypay") private String payType; diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/constant/DYPayConstants.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/constant/DYPayConstants.java index 86a7259..8f824f4 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/constant/DYPayConstants.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/constant/DYPayConstants.java @@ -10,6 +10,10 @@ public class DYPayConstants { * 登陆 */ public static final String CODE_2_SESSION = "https://developer.toutiao.com/api/apps/v2/jscode2session"; + /** + * 获取AccessToken + */ + public static final String GET_ACCESS_TOKEN = "https://developer.toutiao.com/api/apps/v2/token"; /** * 生成预支付单 */ @@ -27,5 +31,10 @@ public class DYPayConstants { * 退款 */ public static final String CREATE_REFUND = "https://developer.toutiao.com/api/apps/ecpay/v1/create_refund"; + + /** + * 订单推送 + */ + public static final String ORDER_PUSH = "https://developer.toutiao.com/api/apps/order/v2/push"; } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/AbstractPayStrategy.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/AbstractPayStrategy.java index 847ca01..7405b49 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/AbstractPayStrategy.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/AbstractPayStrategy.java @@ -6,6 +6,7 @@ import com.alipay.api.msg.MsgConstants; import com.bnyer.common.core.constant.RedisKeyConstant; import com.bnyer.common.core.enums.EnumPayType; import com.bnyer.common.redis.service.RedissonService; +import com.bnyer.pay.bean.bo.PushOrderBo; import com.bnyer.pay.bean.dto.PayNotifyCheckDto; import com.bnyer.pay.bean.vo.PayInfoDetailsVo; import com.bnyer.pay.service.PayInfoService; @@ -29,7 +30,8 @@ public abstract class AbstractPayStrategy implements IPayStrategy { private static RedissonService redissonService; @Autowired - public void setBean(PayInfoService payInfoService, RedissonService redissonService) { + public void setBean(PayInfoService payInfoService, + RedissonService redissonService) { AbstractPayStrategy.payInfoService = payInfoService; AbstractPayStrategy.redissonService = redissonService; } @@ -109,4 +111,12 @@ public abstract class AbstractPayStrategy implements IPayStrategy { return result; } + /** + * 推送订单,由各子类实现 + * @param pushOrderBo + */ + public void pushOrder(PushOrderBo pushOrderBo) { + + } + } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/DYPayStrategy.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/DYPayStrategy.java index e21e319..2ce26bd 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/DYPayStrategy.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/DYPayStrategy.java @@ -10,33 +10,30 @@ import com.bnyer.common.core.enums.EnumPayType; import com.bnyer.common.core.enums.ResponseEnum; import com.bnyer.common.core.exception.ServiceException; import com.bnyer.common.core.utils.StringUtils; -import com.bnyer.pay.bean.bo.QueryOrderBo; -import com.bnyer.pay.bean.bo.RefundBo; -import com.bnyer.pay.bean.bo.UnifiedOrderBo; -import com.bnyer.pay.constant.DYPayConstants; +import com.bnyer.pay.bean.bo.*; import com.bnyer.pay.bean.dto.EditPayInfoNotifyDto; import com.bnyer.pay.bean.dto.PayNotifyCheckDto; +import com.bnyer.pay.bean.vo.ThirdQueryOrderVo; +import com.bnyer.pay.bean.vo.ThirdRefundVo; +import com.bnyer.pay.bean.vo.ThirdUnifiedOrderVo; +import com.bnyer.pay.constant.DYPayConstants; +import com.bnyer.pay.enums.EnumDyOrderStatus; import com.bnyer.pay.enums.EnumDyPayStatus; import com.bnyer.pay.enums.EnumPayChannel; import com.bnyer.pay.enums.EnumPayConfigStatus; +import com.bnyer.pay.exception.PayException; import com.bnyer.pay.mapper.DypayConfigMapper; import com.bnyer.pay.service.PayInfoService; import com.bnyer.pay.utils.DYPayUtil; +import com.bnyer.pay.manager.DyPayManager; import com.bnyer.pay.utils.MoneyUtil; -import com.bnyer.pay.utils.PayRestTemplateUtil; -import com.bnyer.pay.bean.vo.ThirdRefundVo; -import com.bnyer.pay.bean.vo.ThirdUnifiedOrderVo; -import com.bnyer.pay.bean.vo.ThirdQueryOrderVo; import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest; import com.github.binarywang.wxpay.bean.result.BaseWxPayResult; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; +import java.util.*; /** * @author :WXC @@ -49,8 +46,6 @@ public class DYPayStrategy extends AbstractPayStrategy{ private static DYPayUtil dyPayUtil; - private static PayRestTemplateUtil payRestTemplateUtil; - private static PayInfoService payInfoService; private static DypayConfigMapper dypayConfigMapper; @@ -58,10 +53,9 @@ public class DYPayStrategy extends AbstractPayStrategy{ private final static String failThirdCode = "-1"; @Autowired - public void setBean(DYPayUtil dyPayUtil, PayRestTemplateUtil payRestTemplateUtil, - PayInfoService payInfoService,DypayConfigMapper dypayConfigMapper) { + public void setBean(DYPayUtil dyPayUtil, + PayInfoService payInfoService, DypayConfigMapper dypayConfigMapper) { DYPayStrategy.dyPayUtil = dyPayUtil; - DYPayStrategy.payRestTemplateUtil = payRestTemplateUtil; DYPayStrategy.payInfoService = payInfoService; DYPayStrategy.dypayConfigMapper = dypayConfigMapper; } @@ -75,17 +69,18 @@ public class DYPayStrategy extends AbstractPayStrategy{ if (CollUtil.isEmpty(dypayConfigList)){ throw new ServiceException(ResponseEnum.PAY_CONFIG_ERROR); } - DypayConfig dypayConfig = dypayConfigList.get(0); - return dypayConfig; + return dypayConfigList.get(0); } @Override public ThirdUnifiedOrderVo unifiedOrder(UnifiedOrderBo bo) { String result = ""; DypayConfig dypayConfig = getDypayConfig(); + DyPayManager dyPayManager = new DyPayManager(dypayConfig); String appId = dypayConfig.getAppid(); String backurl = dypayConfig.getBackurl(); String salt = dypayConfig.getSalt(); + String requestStr = ""; try { //加签验签的参数需要排序 Map params = new TreeMap<>(); @@ -123,9 +118,9 @@ public class DYPayStrategy extends AbstractPayStrategy{ payJson.put("notify_url",backurl); log.info("请求参数{}", payJson); //预下单接口 - result = payRestTemplateUtil.dyPostRequest(payJson, DYPayConstants.CREATE_ORDER); - } catch (Exception e) { - e.printStackTrace(); + requestStr = JSON.toJSONString(payJson); + result = dyPayManager.postRequest(requestStr, DYPayConstants.CREATE_ORDER); + } catch (PayException e) { log.error("抖音支付:支付异常,payId:{},error{}", bo.getPayId(), e.getMessage()); throw new ServiceException(ResponseEnum.PAY_FAILS); } @@ -245,6 +240,7 @@ public class DYPayStrategy extends AbstractPayStrategy{ @Override public ThirdQueryOrderVo queryOrder(QueryOrderBo bo) { DypayConfig dypayConfig = getDypayConfig(); + DyPayManager dyPayManager = new DyPayManager(dypayConfig); String appId = dypayConfig.getAppid(); String salt = dypayConfig.getSalt(); String result = ""; @@ -262,9 +258,8 @@ public class DYPayStrategy extends AbstractPayStrategy{ queryOrderJson.put("out_order_no", bo.getPayId()); queryOrderJson.put("sign", sign); //发起请求 - result = payRestTemplateUtil.dyPostRequest(queryOrderJson, DYPayConstants.QUERY_ORDER); - }catch (Exception e){ - e.printStackTrace(); + result = dyPayManager.postRequest(JSON.toJSONString(queryOrderJson), DYPayConstants.QUERY_ORDER); + }catch (PayException e){ log.error("抖音支付:订单查询异常,payId:{},error{}", bo.getPayId(), e.getMessage()); throw new ServiceException(ResponseEnum.ORDER_QUERY_FAILS); } @@ -318,4 +313,57 @@ public class DYPayStrategy extends AbstractPayStrategy{ public ThirdRefundVo refund(RefundBo bo) { return null; } + + @Override + public void pushOrder(PushOrderBo pushOrderBo) { + log.info("开始订单推送......,request:{}",JSON.toJSONString(pushOrderBo)); + DypayConfig dypayConfig = getDypayConfig(); + DyPayManager dyPayManager = new DyPayManager(dypayConfig); + String requestStr = ""; + String resultStr = ""; + try { + //获取accessToken + String accessToken = dyPayManager.getAccessToken(false); + DyPushOrderBo dyPushOrderBo = new DyPushOrderBo(); + dyPushOrderBo.setAccessToken(accessToken); + dyPushOrderBo.setAppName("douyin"); + dyPushOrderBo.setOpenId(dypayConfig.getAppid()); + dyPushOrderBo.setOrderStatus(EnumDyOrderStatus.getEnumDyOrderStatusByPayStatus(pushOrderBo.getPayStatus()).getStatus()); + dyPushOrderBo.setOrderType(0); + dyPushOrderBo.setUpdateTime(Integer.parseInt(String.valueOf(pushOrderBo.getUpdateTime().getTime()))); + //订单详情 + DyPushOrderBo.OrderDetails orderDetails = new DyPushOrderBo.OrderDetails(); + orderDetails.setOrderId(pushOrderBo.getPayId()); + orderDetails.setAmount(pushOrderBo.getGoodsNum()); + orderDetails.setCreateTime(Integer.parseInt(String.valueOf(pushOrderBo.getCreateTime().getTime()))); + orderDetails.setDetailUrl("www.baidu.com"); + orderDetails.setStatus(EnumDyOrderStatus.getEnumDyOrderStatusByPayStatus(pushOrderBo.getPayStatus())); + orderDetails.setTotalPrice(pushOrderBo.getTotalPrice()); + //封装商品列表 + List goodsList = pushOrderBo.getGoodsList(); + List pushItemList = new ArrayList<>(); + goodsList.forEach(goods -> { + DyPushOrderBo.OrderDetails.Goods pushItem = new DyPushOrderBo.OrderDetails.Goods(); + pushItem.setItemCode(goods.getItemCode()); + pushItem.setAmount(goods.getGoodsNum()); + pushItem.setImg(goods.getImg()); + pushItem.setPrice(goods.getPrice()); + pushItem.setSubTitle(goods.getSubTitle()); + pushItem.setTitle(goods.getTitle()); + pushItemList.add(pushItem); + }); + orderDetails.setItemList(pushItemList); + dyPushOrderBo.setOrderDetail(JSON.toJSONString(orderDetails.toString())); + //发起请求 + requestStr = JSON.toJSONString(dyPushOrderBo); + resultStr = dyPayManager.postRequest(JSON.toJSONString(dyPushOrderBo), DYPayConstants.ORDER_PUSH); + log.info("抖音支付:抖音订单推送接口调用成功,request:{},result:{}",requestStr,resultStr); + }catch (PayException e){ + log.error("抖音支付:抖音订单推送接口调用异常,request:{},error{}", requestStr, e.getMessage()); + } + } + + } + + diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/IPayStrategy.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/IPayStrategy.java index 517ee70..079a176 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/IPayStrategy.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/IPayStrategy.java @@ -1,5 +1,6 @@ package com.bnyer.pay.design.strategy; +import com.bnyer.pay.bean.bo.PushOrderBo; import com.bnyer.pay.bean.bo.QueryOrderBo; import com.bnyer.pay.bean.bo.RefundBo; import com.bnyer.pay.bean.bo.UnifiedOrderBo; @@ -46,6 +47,12 @@ public interface IPayStrategy { */ ThirdRefundVo refund(RefundBo bo); + /** + * 推送订单 + * @param pushOrderBo + */ + void pushOrder(PushOrderBo pushOrderBo); + //===========待完成================ // TODO: 2023/04/03 退款查询 // void refundQuery(); diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/KSPayStrategy.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/KSPayStrategy.java index d970c5d..737a1b3 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/KSPayStrategy.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/KSPayStrategy.java @@ -14,19 +14,19 @@ import com.bnyer.common.core.utils.StringUtils; import com.bnyer.pay.bean.bo.QueryOrderBo; import com.bnyer.pay.bean.bo.RefundBo; import com.bnyer.pay.bean.bo.UnifiedOrderBo; -import com.bnyer.pay.constant.KSPayConstants; import com.bnyer.pay.bean.dto.EditPayInfoNotifyDto; import com.bnyer.pay.bean.dto.PayNotifyCheckDto; +import com.bnyer.pay.bean.vo.ThirdQueryOrderVo; +import com.bnyer.pay.bean.vo.ThirdRefundVo; +import com.bnyer.pay.bean.vo.ThirdUnifiedOrderVo; +import com.bnyer.pay.constant.KSPayConstants; import com.bnyer.pay.enums.EnumPayChannel; import com.bnyer.pay.enums.EnumPayConfigStatus; +import com.bnyer.pay.manager.KsPayManager; import com.bnyer.pay.mapper.KspayConfigMapper; import com.bnyer.pay.service.PayInfoService; import com.bnyer.pay.utils.KSPayUtil; import com.bnyer.pay.utils.MoneyUtil; -import com.bnyer.pay.utils.PayRestTemplateUtil; -import com.bnyer.pay.bean.vo.ThirdRefundVo; -import com.bnyer.pay.bean.vo.ThirdUnifiedOrderVo; -import com.bnyer.pay.bean.vo.ThirdQueryOrderVo; import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; @@ -49,8 +49,6 @@ public class KSPayStrategy extends AbstractPayStrategy{ private static KSPayUtil ksPayUtil; - private static PayRestTemplateUtil payRestTemplateUtil; - private static PayInfoService payInfoService; private static KspayConfigMapper kspayConfigMapper; @@ -58,10 +56,9 @@ public class KSPayStrategy extends AbstractPayStrategy{ private final static String failThirdCode = "-1"; @Autowired - public void setBean(KSPayUtil ksPayUtil, PayRestTemplateUtil payRestTemplateUtil, - PayInfoService payInfoService, KspayConfigMapper kspayConfigMapper) { + public void setBean(KSPayUtil ksPayUtil, + PayInfoService payInfoService, KspayConfigMapper kspayConfigMapper) { KSPayStrategy.ksPayUtil = ksPayUtil; - KSPayStrategy.payRestTemplateUtil = payRestTemplateUtil; KSPayStrategy.payInfoService = payInfoService; KSPayStrategy.kspayConfigMapper = kspayConfigMapper; } @@ -75,14 +72,14 @@ public class KSPayStrategy extends AbstractPayStrategy{ if (CollUtil.isEmpty(kspayConfigList)){ throw new ServiceException(ResponseEnum.PAY_CONFIG_ERROR); } - KspayConfig kspayConfig = kspayConfigList.get(0); - return kspayConfig; + return kspayConfigList.get(0); } @Override public ThirdUnifiedOrderVo unifiedOrder(UnifiedOrderBo bo) { String result = ""; KspayConfig kspayConfig = getKspayConfig(); + KsPayManager ksPayManager = new KsPayManager(kspayConfig); String openId = bo.getOpenId(); String appId = kspayConfig.getAppid(); String backurl = kspayConfig.getBackurl(); @@ -125,13 +122,11 @@ public class KSPayStrategy extends AbstractPayStrategy{ payJson.put("notify_url", bo.getCurrDate()); log.info("请求参数{}", payJson); //预下单接口 - String ksPayAccessToken = payRestTemplateUtil.ksPostRequestUrlencoded(kspayConfig); - result = payRestTemplateUtil.ksPostRequestJson(payJson, KSPayConstants.CREATE_ORDER, appId, ksPayAccessToken); + result = ksPayManager.postRequest(payJson.toJSONString(), KSPayConstants.CREATE_ORDER); log.info("=================================="); log.info("快手预下单result{}", result); log.info("=================================="); } catch (Exception e) { - e.printStackTrace(); log.error("快手支付:支付异常,payId:{},error{}", bo.getPayId(), e.getMessage()); throw new ServiceException(ResponseEnum.PAY_FAILS); } @@ -246,12 +241,11 @@ public class KSPayStrategy extends AbstractPayStrategy{ @Override public ThirdQueryOrderVo queryOrder(QueryOrderBo bo) { KspayConfig kspayConfig = getKspayConfig(); + KsPayManager ksPayManager = new KsPayManager(kspayConfig); String appId = kspayConfig.getAppid(); String secret = kspayConfig.getSecret(); String result = ""; try { - //accessToken - String ksPayAccessToken = payRestTemplateUtil.ksPostRequestUrlencoded(kspayConfig); //加签验签的参数需要排序 Map params = new TreeMap<>(); //小程序APPID @@ -264,7 +258,7 @@ public class KSPayStrategy extends AbstractPayStrategy{ queryOrderJson.put("out_order_no", bo.getPayId()); queryOrderJson.put("sign", sign); //发起请求 - result = payRestTemplateUtil.ksPostRequestJson(queryOrderJson, KSPayConstants.QUERY_ORDER, appId, ksPayAccessToken); + result = ksPayManager.postRequest(queryOrderJson.toJSONString(), KSPayConstants.QUERY_ORDER); }catch (Exception e){ e.printStackTrace(); log.error("快手支付:订单查询异常,payId:{},error{}", bo.getPayId(), e.getMessage()); diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/enums/EnumDyOrderStatus.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/enums/EnumDyOrderStatus.java new file mode 100644 index 0000000..7c4e3e1 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/enums/EnumDyOrderStatus.java @@ -0,0 +1,37 @@ +package com.bnyer.pay.enums; + +import com.bnyer.common.core.enums.EnumPayStatus; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * @author :WXC + * @Date :2023/05/05 + * @description : + */ +@Getter +@AllArgsConstructor +public enum EnumDyOrderStatus { + 待支付(1,EnumPayStatus.NO_PAY), + 已支付(2,EnumPayStatus.SUCCESS), + 已取消(3,null), + 已超时(4,null), + 已核销(5,null), + 退款中(6,null), + 已退款(7,null), + 退款失败(8,null), + ; + + private final int status; + + private final EnumPayStatus payStatus; + + public static EnumDyOrderStatus getEnumDyOrderStatusByPayStatus(EnumPayStatus status) { + return Arrays.stream(values()) + .filter(dyOrderStatus -> dyOrderStatus.getPayStatus() == status) + .findFirst().orElseThrow(() -> new SecurityException("status 未匹配上对应的支付状态")); + } + +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/exception/PayException.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/exception/PayException.java new file mode 100644 index 0000000..e1dafcf --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/exception/PayException.java @@ -0,0 +1,41 @@ +package com.bnyer.pay.exception; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author :WXC + * @description : + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class PayException extends Exception { + private static final long serialVersionUID = -7296122902130895085L; + + /** + * 自定义错误讯息. + */ + private String customErrorMsg; + + /** + * 错误代码. + */ + private String errCode; + + /** + * 错误代码描述. + */ + private String errCodeDes; + + + public PayException(String customErrorMsg) { + super(customErrorMsg); + this.customErrorMsg = customErrorMsg; + } + + public PayException(String customErrorMsg, Throwable tr) { + super(customErrorMsg, tr); + this.customErrorMsg = customErrorMsg; + } + +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/listener/PayReturnMessageConsumer.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/listener/PayReturnMessageConsumer.java index f6c51f5..7dc9aae 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/listener/PayReturnMessageConsumer.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/listener/PayReturnMessageConsumer.java @@ -42,7 +42,7 @@ public class PayReturnMessageConsumer extends EnhanceMessageHandler implements R @Override protected boolean filter(BnyerMessage message) { - return super.handleMsgRepeat(message); + return false; } @Override diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/listener/ThirdPayOrderPushConsumer.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/listener/ThirdPayOrderPushConsumer.java new file mode 100644 index 0000000..f845eb4 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/listener/ThirdPayOrderPushConsumer.java @@ -0,0 +1,67 @@ +package com.bnyer.pay.listener; + +import com.bnyer.common.core.exception.ServiceException; +import com.bnyer.common.rocketmq.constant.RocketMqTopic; +import com.bnyer.common.rocketmq.domain.BnyerMessage; +import com.bnyer.common.rocketmq.domain.pay.ThirdPayOrderPushMessage; +import com.bnyer.common.rocketmq.handle.EnhanceMessageHandler; +import com.bnyer.pay.bean.dto.PushOrderDto; +import com.bnyer.pay.service.UnifiedPayService; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * @author :WXC + * @description :第三方支付订单推送 + */ +@Slf4j +@Component +@RocketMQMessageListener(topic = RocketMqTopic.THIRD_PAY_ORDER_PUSH_TOPIC,consumerGroup = RocketMqTopic.THIRD_PAY_ORDER_PUSH_TOPIC) +public class ThirdPayOrderPushConsumer extends EnhanceMessageHandler implements RocketMQListener { + + + @Resource + private UnifiedPayService unifiedPayService; + + @Override + protected void handleMessage(BnyerMessage message) throws Exception { + super.dispatchMessage(message); + try { + ThirdPayOrderPushMessage thirdPayOrderPushMessage = message.getObject(ThirdPayOrderPushMessage.class); + PushOrderDto pushOrderDto = new PushOrderDto(); + pushOrderDto.setPayId(thirdPayOrderPushMessage.getPayId()); + unifiedPayService.pushOrder(pushOrderDto); + } catch (ServiceException e) { + log.error("订单推送失败:"+e.getMessage()); + } + } + + @Override + protected void handleMaxRetriesExceeded(BnyerMessage message) { + + } + + @Override + protected boolean filter(BnyerMessage message) { + return false; + } + + @Override + protected boolean isRetry() { + return true; + } + + @Override + protected boolean throwException() { + return false; + } + + @Override + public void onMessage(BnyerMessage message) { + + } +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/manager/DyPayManager.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/manager/DyPayManager.java new file mode 100644 index 0000000..78bd64b --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/manager/DyPayManager.java @@ -0,0 +1,182 @@ +package com.bnyer.pay.manager; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.bnyer.common.core.constant.RedisKeyConstant; +import com.bnyer.common.core.domain.DypayConfig; +import com.bnyer.common.core.utils.SpringUtils; +import com.bnyer.common.core.utils.StringUtils; +import com.bnyer.common.redis.service.RedisService; +import com.bnyer.pay.constant.DYPayConstants; +import com.bnyer.pay.exception.PayException; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.util.json.GsonParser; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestTemplate; + +import java.text.MessageFormat; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * @author :WXC + * @Date :2023/04/24 + * @description : + */ +@Slf4j +public class DyPayManager { + + private RestTemplate restTemplate; + + private RedisService redisService; + + private final DypayConfig dypayConfig; + + private final int MAX_RETRY_TIMES = 2; + + public DyPayManager(DypayConfig dypayConfig){ + this.dypayConfig = dypayConfig; + init(); + } + + private void init() { + this.restTemplate = SpringUtils.getBean(RestTemplate.class); + this.redisService = SpringUtils.getBean(RedisService.class); + } + + /** + * 抖音小程序获取accessToken + * 为了保障应用的数据安全,只能在开发者服务器使用 AppSecret,如果小程序存在泄露 AppSecret 的问题,字节小程序平台将有可能下架该小程序,并暂停该小程序相关服务。 + * + * access_token 是小程序的全局唯一调用凭据,开发者调用小程序支付时需要使用 access_token。access_token 的有效期为 2 个小时,需要定时刷新 access_token,重复获取会导致之前一次获取的 access_token 的有效期缩短为 5 分钟。 + */ + public String getAccessToken(boolean isRefresh) throws PayException { + if (!isRefresh){ + Object cacheObject = redisService.getCacheObject(RedisKeyConstant.DY_ACCESS_TOKEN_KEY); + if (Objects.nonNull(cacheObject)){ + return cacheObject.toString(); + } + } + String requestStr = ""; + try { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + HttpEntity formEntity = new HttpEntity<>(MessageFormat.format("app_id={0}&app_secret={1}&&grant_type={2}" + , dypayConfig.getAppid(), dypayConfig.getAppSecret(), "client_credentials"), headers); + String resultStr = restTemplate.postForObject(DYPayConstants.GET_ACCESS_TOKEN, formEntity, String.class); + String url = DYPayConstants.GET_ACCESS_TOKEN; + requestStr = formEntity.getBody(); + if (StringUtils.isBlank(resultStr) || !resultStr.startsWith("{")){ + log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, resultStr); + throw convertDyException(GsonParser.parse("第三方返回格式有误!")); + } + JSONObject resultObj = JSONObject.parseObject(resultStr); + String errNo = StringUtils.isNotBlank(resultObj.getString("err_no"))?resultObj.getString("err_no"):resultObj.getString("err_code"); + if ("0".equals(errNo)) { + log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, resultObj, resultStr); + //存入redis + String accessToken = resultObj.getJSONObject("data").getString("access_token"); + String expiresIn = resultObj.getJSONObject("data").getString("expires_in"); + redisService.setCacheObject(RedisKeyConstant.DY_ACCESS_TOKEN_KEY,accessToken,Long.parseLong(expiresIn), TimeUnit.SECONDS); + return accessToken; + }else { + throw convertDyException(GsonParser.parse(resultStr)); + } + } catch (Exception e) { + log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", DYPayConstants.GET_ACCESS_TOKEN, requestStr, e.getMessage()); + throw (e instanceof PayException) ? (PayException) e : new PayException(e.getMessage(), e); + } + } + + /** + * post请求 + */ + public String postRequest(String requestStr, String url) throws PayException { + return postRequest(0,requestStr,url); + } + + /** + * post请求 + */ + public String postRequest(int retryTimes, String requestStr, String url) throws PayException { + String resultStr = ""; + try { + HttpHeaders headers = new HttpHeaders(); + //所有的请求需要用JSON格式发送 + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity formEntity = new HttpEntity<>(requestStr, headers); + resultStr = restTemplate.postForObject(url, formEntity, String.class); + if (StringUtils.isBlank(resultStr) || !resultStr.startsWith("{")){ + log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, resultStr); + throw new PayException("第三方返回格式有误!"); + } + JSONObject resultObj = JSONObject.parseObject(resultStr); + String errNo = StringUtils.isNotBlank(resultObj.getString("err_no"))?resultObj.getString("err_no"):resultObj.getString("err_code"); + if ("0".equals(errNo)) { + log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, resultObj, resultStr); + return resultStr; + }else { + throw convertDyException(GsonParser.parse(resultStr)); + } + }catch (PayException e){ + log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage()); + if (retryTimes > MAX_RETRY_TIMES){ + throw new PayException("抖音接口调用失败,已超过最大重试次数"); + } + //如果是access_token过期,刷新token在重试 + if ("40004".equals(e.getErrCode())){ + String accessToken = refreshAccessToken(); + JSONObject requestJsonObj = JSON.parseObject(requestStr); + requestJsonObj.put("access_token",accessToken); + requestStr = requestJsonObj.toJSONString(); + } + retryTimes ++; + postRequest(retryTimes,requestStr,url); + } + return resultStr; + } + + /** + * 刷新access_token + */ + public String refreshAccessToken() throws PayException { + return refreshAccessToken(0); + } + + /** + * 刷新access_token + * @param retryTimes + */ + public String refreshAccessToken(int retryTimes) throws PayException { + String accessToken = ""; + try { + accessToken = getAccessToken( true); + } catch (PayException e) { + if (retryTimes > MAX_RETRY_TIMES){ + throw new PayException("抖音接口获取AccessToken调用失败,已超过最大重试次数"); + } + retryTimes ++; + refreshAccessToken(retryTimes); + } + return accessToken; + } + + /** + * 转换异常 + * @param jsonObject + * @return + */ + private PayException convertDyException(JsonObject jsonObject) { + JsonElement codeElement = jsonObject.get("err_no") != null ? jsonObject.get("err_no"):jsonObject.get("err_code"); + String code = codeElement == null ? null : codeElement.getAsString(); + String message = jsonObject.get("error_msg").getAsString(); + PayException payException = new PayException(message); + payException.setErrCode(code); + payException.setErrCodeDes(message); + return payException; + } +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/manager/KsPayManager.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/manager/KsPayManager.java new file mode 100644 index 0000000..7c45ddf --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/manager/KsPayManager.java @@ -0,0 +1,186 @@ +package com.bnyer.pay.manager; + +import com.alibaba.fastjson.JSONObject; +import com.bnyer.common.core.constant.RedisKeyConstant; +import com.bnyer.common.core.domain.KspayConfig; +import com.bnyer.common.core.utils.SpringUtils; +import com.bnyer.common.core.utils.StringUtils; +import com.bnyer.common.redis.service.RedisService; +import com.bnyer.pay.constant.KSPayConstants; +import com.bnyer.pay.exception.PayException; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.util.json.GsonParser; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestTemplate; + +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * @author :WXC + * @Date :2023/04/24 + * @description : + */ +@Slf4j +public class KsPayManager { + + private RestTemplate restTemplate; + + private RedisService redisService; + + private final KspayConfig kspayConfig; + + private final int MAX_RETRY_TIMES = 2; + + public KsPayManager(KspayConfig kspayConfig){ + this.kspayConfig = kspayConfig; + init(); + } + + private void init() { + this.restTemplate = SpringUtils.getBean(RestTemplate.class); + this.redisService = SpringUtils.getBean(RedisService.class); + } + + /** + * 该授权方式使用 OAuth2 的 client credentials 模式,获取小程序全局唯一后台接口调用凭据。 + * + * 说明:access_token48小时内有效,未超出有效截止时间,开发者重新调用获取新的access_token,则新老token同时有效。避免token过期,开发者可根据业务实际情况,注意间隔请求时间,不要超出48小时。 + * @param isRefresh + * @return + * @throws PayException + */ + public String getAccessToken(boolean isRefresh) throws PayException { + if (!isRefresh){ + Object cacheObject = redisService.getCacheObject(RedisKeyConstant.KS_ACCESS_TOKEN_KEY); + if (Objects.nonNull(cacheObject)){ + return cacheObject.toString(); + } + } + String requestStr = ""; + try { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + HttpEntity formEntity = new HttpEntity<>(MessageFormat.format("app_id={0}&app_secret={1}&&grant_type={2}" + , kspayConfig.getAppid(), kspayConfig.getSecret(), "client_credentials"), headers); + String resultStr = restTemplate.postForObject(KSPayConstants.GET_ACCESS_TOKEN, formEntity, String.class); + String url = KSPayConstants.GET_ACCESS_TOKEN; + requestStr = formEntity.getBody(); + if (StringUtils.isBlank(resultStr) || !resultStr.startsWith("{")){ + log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, resultStr); + throw convertDyException(GsonParser.parse("第三方返回格式有误!")); + } + JSONObject resultObj = JSONObject.parseObject(resultStr); + String resultCode = resultObj.getString("result"); + if ("1".equals(resultCode)) { + log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, resultObj, resultStr); + //存入redis + String accessToken = resultObj.getString("access_token"); + String expiresIn = resultObj.getString("expires_in"); + redisService.setCacheObject(RedisKeyConstant.KS_ACCESS_TOKEN_KEY,accessToken,Long.parseLong(expiresIn), TimeUnit.SECONDS); + return accessToken; + }else { + throw convertDyException(GsonParser.parse(resultStr)); + } + } catch (Exception e) { + log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", KSPayConstants.GET_ACCESS_TOKEN, requestStr, e.getMessage()); + throw (e instanceof PayException) ? (PayException) e : new PayException(e.getMessage(), e); + } + } + + /** + * post请求 + */ + public String postRequest(String requestStr, String url) throws PayException { + Map urlParams = new HashMap<>(); + urlParams.put("app_id", kspayConfig.getAppid()); + urlParams.put("access_token", getAccessToken(false)); + return postRequest(0,urlParams,requestStr,url); + } + + /** + * post请求 + */ + public String postRequest(int retryTimes,Map urlParams, String requestStr, String url) throws PayException { + String resultStr = ""; + try { + HttpHeaders headers = new HttpHeaders(); + //所有的请求需要用JSON格式发送 + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity formEntity = new HttpEntity<>(requestStr, headers); + resultStr = restTemplate.postForObject(url + "/?app_id={app_id}&access_token={access_token}", formEntity, String.class, urlParams); + if (StringUtils.isBlank(resultStr) || !resultStr.startsWith("{")){ + log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, resultStr); + throw new PayException("第三方返回格式有误!"); + } + JSONObject resultObj = JSONObject.parseObject(resultStr); + String resultCode = resultObj.getString("result"); + if ("1".equals(resultCode)) { + log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, resultObj, resultStr); + return resultStr; + }else { + throw convertDyException(GsonParser.parse(resultStr)); + } + }catch (PayException e){ + log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage()); + if (retryTimes > MAX_RETRY_TIMES){ + throw new PayException("快手接口调用失败,已超过最大重试次数"); + } + //如果是access_token过期,刷新token在重试 + if ("10000011".equals(e.getErrCode())){ + urlParams.put("app_id", kspayConfig.getAppid()); + urlParams.put("access_token", refreshAccessToken()); + } + retryTimes ++; + postRequest(retryTimes,urlParams,requestStr,url); + } + return resultStr; + } + + /** + * 刷新access_token + */ + public String refreshAccessToken() throws PayException { + return refreshAccessToken(0); + } + + /** + * 刷新access_token + * @param retryTimes + */ + public String refreshAccessToken(int retryTimes) throws PayException { + String accessToken = ""; + try { + accessToken = getAccessToken( true); + } catch (PayException e) { + if (retryTimes > MAX_RETRY_TIMES){ + throw new PayException("快手接口获取AccessToken调用失败,已超过最大重试次数"); + } + retryTimes ++; + refreshAccessToken(retryTimes); + } + return accessToken; + } + + /** + * 转换异常 + * @param jsonObject + * @return + */ + private PayException convertDyException(JsonObject jsonObject) { + JsonElement codeElement = jsonObject.get("result"); + String code = codeElement == null ? null : codeElement.getAsString(); + String message = jsonObject.get("error_msg").getAsString(); + PayException payException = new PayException(message); + payException.setErrCode(code); + payException.setErrCodeDes(message); + return payException; + } +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/PayInfoService.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/PayInfoService.java index 482f927..acb82bd 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/PayInfoService.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/PayInfoService.java @@ -28,4 +28,5 @@ public interface PayInfoService { * @return */ PayInfoDetailsVo queryPayInfoDetails(String payId); + } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java index 40edb1b..a14f833 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java @@ -1,14 +1,11 @@ package com.bnyer.pay.service; -import com.bnyer.pay.bean.dto.RefundDto; -import com.bnyer.pay.bean.dto.UnifiedOrderDto; +import com.bnyer.pay.bean.dto.PushOrderDto; import com.bnyer.pay.bean.dto.QueryOrderDto; +import com.bnyer.pay.bean.dto.RefundDto; import com.bnyer.pay.bean.dto.UnifiedOrderExtDto; -import com.bnyer.pay.bean.vo.ThirdRefundVo; -import com.bnyer.pay.bean.vo.UnifiedOrderVo; import com.bnyer.pay.bean.vo.QueryOrderVo; - -import javax.servlet.http.HttpServletRequest; +import com.bnyer.pay.bean.vo.UnifiedOrderVo; /** * @author :WXC @@ -36,4 +33,10 @@ public interface UnifiedPayService { * @param dto */ void refund(RefundDto dto); + + /** + * 统一推送订单(目前暂时只有抖音支付需要) + * @param pushOrderDto + */ + void pushOrder(PushOrderDto pushOrderDto); } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/PayInfoServiceImpl.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/PayInfoServiceImpl.java index 0147a7f..9b5d470 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/PayInfoServiceImpl.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/PayInfoServiceImpl.java @@ -4,6 +4,9 @@ import cn.hutool.core.util.ObjectUtil; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.bnyer.common.core.domain.PayInfo; +import com.bnyer.common.core.domain.R; +import com.bnyer.common.core.enums.EnumPayStatus; +import com.bnyer.common.core.enums.EnumPayType; import com.bnyer.common.core.enums.EnumSceneCode; import com.bnyer.common.core.enums.ResponseEnum; import com.bnyer.common.core.exception.ServiceException; @@ -11,20 +14,29 @@ import com.bnyer.common.core.utils.bean.EntityConvertUtil; import com.bnyer.common.rocketmq.constant.RocketMqTag; import com.bnyer.common.rocketmq.constant.RocketMqTopic; import com.bnyer.common.rocketmq.domain.order.VipOrderPayNotifyMessage; +import com.bnyer.common.rocketmq.domain.pay.ThirdPayOrderPushMessage; import com.bnyer.common.rocketmq.template.RocketMQEnhanceTemplate; +import com.bnyer.order.api.bean.query.VipOrderExtQuery; +import com.bnyer.order.api.bean.vo.VipOrderVo; +import com.bnyer.pay.bean.bo.PushOrderBo; import com.bnyer.pay.bean.dto.AddPayInfoDto; import com.bnyer.pay.bean.dto.EditPayInfoNotifyDto; import com.bnyer.pay.bean.dto.EditPayInfoSingleDto; import com.bnyer.pay.bean.vo.PayInfoDetailsVo; +import com.bnyer.pay.design.factory.PayFactory; +import com.bnyer.pay.design.strategy.IPayStrategy; import com.bnyer.pay.mapper.PayInfoMapper; import com.bnyer.pay.service.PayInfoService; import com.bnyer.pay.service.PayMqMessageRecordService; +import com.bnyer.pay.utils.MoneyUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -51,15 +63,8 @@ public class PayInfoServiceImpl implements PayInfoService { */ @Override public void addPayInfo(AddPayInfoDto dto) { - //判断是否是原来的订单表订单号支付,如果是更新信息即可,否则创建支付单信息 - PayInfo oldPayInfo = payInfoMapper.selectOne(new LambdaQueryWrapper().eq(PayInfo::getOrderNo, dto.getOrderNo())); PayInfo payInfo = dto.toEntity(); - if (ObjectUtil.isNull(oldPayInfo)){ - payInfoMapper.insert(payInfo); - }else { - payInfo.setId(oldPayInfo.getId()); - payInfoMapper.updateById(payInfo); - } + payInfoMapper.insert(payInfo); } /** @@ -94,9 +99,17 @@ public class PayInfoServiceImpl implements PayInfoService { // 发送消息,订单支付成功 VipOrderPayNotifyMessage vipOrderPayNotifyMessage = new VipOrderPayNotifyMessage(); vipOrderPayNotifyMessage.setOrderNo(orderNo); -// rocketMQEnhanceTemplate.sendAsyncMsg(RocketMqTopic.ORDER_PAY_NOTIFY_TOPIC, RocketMqTag.ORDER_VIP_TAG, vipOrderPayNotifyMessage); mqMessageRecordService.sendAsyncMsg(RocketMqTopic.ORDER_PAY_NOTIFY_TOPIC, RocketMqTag.ORDER_VIP_TAG, vipOrderPayNotifyMessage); break; + default: + throw new ServiceException("未匹配上对应的支付场景"); + } + //推送订单 + EnumPayType enumPayType = EnumPayType.getEnumPayTypeByType(payInfo.getPayType()); + if (EnumPayType.DY_PAY == enumPayType){ + ThirdPayOrderPushMessage thirdPayOrderPushMessage = new ThirdPayOrderPushMessage(); + thirdPayOrderPushMessage.setPayId(payInfo.getPayId()); + mqMessageRecordService.sendAsyncMsg(RocketMqTopic.THIRD_PAY_ORDER_PUSH_TOPIC,null,thirdPayOrderPushMessage); } } @@ -129,4 +142,5 @@ public class PayInfoServiceImpl implements PayInfoService { PayInfoDetailsVo payInfoDetailsVo = EntityConvertUtil.copy(payInfo, PayInfoDetailsVo.class); return payInfoDetailsVo; } + } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/UnifiedPayServiceImpl.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/UnifiedPayServiceImpl.java index 739a770..ef60a65 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/UnifiedPayServiceImpl.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/UnifiedPayServiceImpl.java @@ -3,6 +3,7 @@ package com.bnyer.pay.service.impl; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.bnyer.common.core.domain.PayInfo; import com.bnyer.common.core.domain.R; @@ -11,12 +12,14 @@ import com.bnyer.common.core.exception.ServiceException; import com.bnyer.common.core.utils.OrderUtil; import com.bnyer.common.core.utils.StringUtils; import com.bnyer.common.core.utils.bean.EntityConvertUtil; -import com.bnyer.common.core.utils.ip.IpUtils; import com.bnyer.common.core.vo.UserInfoVo; +import com.bnyer.common.rocketmq.constant.RocketMqTopic; +import com.bnyer.common.rocketmq.domain.pay.ThirdPayOrderPushMessage; import com.bnyer.common.security.utils.SecurityUtils; import com.bnyer.order.api.bean.query.VipOrderExtQuery; import com.bnyer.order.api.bean.vo.VipOrderVo; import com.bnyer.order.api.remote.RemoteVipOrderService; +import com.bnyer.pay.bean.bo.PushOrderBo; import com.bnyer.pay.bean.bo.QueryOrderBo; import com.bnyer.pay.bean.bo.RefundBo; import com.bnyer.pay.bean.bo.UnifiedOrderBo; @@ -31,14 +34,18 @@ import com.bnyer.pay.enums.EnumKsPayStatus; import com.bnyer.pay.enums.EnumWxPayStatus; import com.bnyer.pay.mapper.PayInfoMapper; import com.bnyer.pay.service.PayInfoService; +import com.bnyer.pay.service.PayMqMessageRecordService; import com.bnyer.pay.service.UnifiedPayService; import com.bnyer.pay.utils.MoneyUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import javax.annotation.Resource; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Objects; /** @@ -56,6 +63,9 @@ public class UnifiedPayServiceImpl implements UnifiedPayService { @Autowired private PayInfoService payInfoService; + @Resource + private PayMqMessageRecordService mqMessageRecordService; + @Autowired private RemoteVipOrderService remoteVipOrderService; @@ -114,6 +124,13 @@ public class UnifiedPayServiceImpl implements UnifiedPayService { AddPayInfoDto addPayInfoDto = buildPayInfo(thirdUnifiedOrderVo, unifiedOrderBo,dto); payInfoService.addPayInfo(addPayInfoDto); UnifiedOrderVo unifiedOrderVo = EntityConvertUtil.copy(thirdUnifiedOrderVo, UnifiedOrderVo.class); + //推送订单 + EnumPayType enumPayType = EnumPayType.getEnumPayTypeByType(addPayInfoDto.getPayType()); + if (EnumPayType.DY_PAY == enumPayType){ + ThirdPayOrderPushMessage thirdPayOrderPushMessage = new ThirdPayOrderPushMessage(); + thirdPayOrderPushMessage.setPayId(addPayInfoDto.getPayId()); + mqMessageRecordService.sendAsyncMsg(RocketMqTopic.THIRD_PAY_ORDER_PUSH_TOPIC,null,thirdPayOrderPushMessage); + } return unifiedOrderVo; } @@ -332,4 +349,68 @@ public class UnifiedPayServiceImpl implements UnifiedPayService { return payInfo; } + /** + * 推送订单 + * @param pushOrderDto + */ + @Override + public void pushOrder(PushOrderDto pushOrderDto) { + PayInfo payInfo = null; + if (StringUtils.isNotBlank(pushOrderDto.getPayId())){ + payInfo = payInfoMapper.selectOne(new LambdaQueryWrapper().eq(PayInfo::getPayId, pushOrderDto.getPayId())); + } + if (Objects.isNull(payInfo)){ + log.error("推送失败,订单信息不存在,pushOrderDto:{}",JSON.toJSONString(pushOrderDto)); + throw new ServiceException("订单信息不存在"); + } + EnumSceneCode enumSceneCode = EnumSceneCode.getSceneCodeByCode(payInfo.getSceneCode()); + PushOrderBo pushOrderBo = null; + //构建各个场景推送订单,并统一入口参数 + switch (enumSceneCode){ + case VIP_RECHARGE: + pushOrderBo = buildVipPushOrder(payInfo); + break; + case UNKNOWN: + throw new ServiceException("未匹配上对应的场景编码"); + } + IPayStrategy payStrategy = PayFactory.getInstance().getConcreteStrategy(payInfo.getPayType()); + //开始推送 + payStrategy.pushOrder(pushOrderBo); + } + + /** + * 构建会员场景推送订单信息 + * @param payInfo + * @return + */ + private PushOrderBo buildVipPushOrder(PayInfo payInfo) { + //查询订单服务:会员订单信息表 + VipOrderExtQuery vipOrderExtQuery = new VipOrderExtQuery(); + vipOrderExtQuery.setOrderNo(payInfo.getOrderNo()); + R vipOrderVoR = remoteVipOrderService.queryVipOrder(vipOrderExtQuery); + if (!vipOrderVoR.isSuccess()){ + log.error("内部接口:查询会员订单调用失败:request:{} result{}", JSON.toJSONString(vipOrderExtQuery),JSON.toJSONString(vipOrderVoR)); + throw new ServiceException(vipOrderVoR.getMsg()); + } + PushOrderBo pushOrderBo = new PushOrderBo(); + VipOrderVo vipOrderVo = vipOrderVoR.getData(); + List goodsList = new ArrayList<>(); + //封装商品信息 + PushOrderBo.Goods goods = new PushOrderBo.Goods(); + // TODO: 2023/06/08 会员写死1,目前只支持一个订单买一个商品,不可以选择购买多个商品下单所以会员场景写死成1即可,若以后需求有变动在说 + goods.setGoodsNum(1); + goods.setImg(""); + goods.setItemCode(vipOrderVo.getVipCode()); + goods.setPrice(Integer.parseInt(MoneyUtil.yuanToFen(vipOrderVo.getPayAmount().toString()))); + goods.setTitle(payInfo.getGoodsSubject()); + + pushOrderBo.setGoodsNum(goodsList.size()); + pushOrderBo.setUpdateTime(payInfo.getUpdateTime()); + pushOrderBo.setCreateTime(payInfo.getCreateTime()); + pushOrderBo.setPayStatus(EnumPayStatus.getEnumPayStatusByStatus(payInfo.getPayStatus())); + pushOrderBo.setPayId(payInfo.getPayId()); + pushOrderBo.setGoodsList(goodsList); + return pushOrderBo; + } + } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/PayRestTemplateUtil.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/PayRestTemplateUtil.java index 2022f05..30e0d8f 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/PayRestTemplateUtil.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/PayRestTemplateUtil.java @@ -1,107 +1,269 @@ -package com.bnyer.pay.utils; - -import com.alibaba.fastjson.JSONObject; -import com.bnyer.common.core.domain.KspayConfig; -import com.bnyer.pay.constant.KSPayConstants; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -import java.text.MessageFormat; -import java.util.HashMap; -import java.util.Map; - -/** - * @author :WXC - * @Date :2023/04/24 - * @description : - */ -@Slf4j -@Component -public class PayRestTemplateUtil { - - @Autowired - private RestTemplate restTemplate; - - /** - * 快手小程序post请求 - * code2session - */ - public String ksPostRequestUrlencoded(JSONObject jsonObject, String url) { - String result = ""; - try { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - HttpEntity formEntity = new HttpEntity<>(MessageFormat.format("js_code={0}&app_id={1}&&app_secret={2}", jsonObject.get("js_code"), jsonObject.get("appid"), jsonObject.get("secret")), headers); - result = restTemplate.postForObject(url, formEntity, String.class); - } catch (Exception e) { - log.error("快手小程序post请求异常{}", url); - log.error("post请求异常:{}", e.getMessage()); - e.printStackTrace(); - } - return result; - } - - /** - * 快手小程序获取accessToken - */ - public String ksPostRequestUrlencoded(KspayConfig kspayConfig) { - String result = ""; - try { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - HttpEntity formEntity = new HttpEntity<>(MessageFormat.format("app_id={0}&app_secret={1}&&grant_type={2}" - , kspayConfig.getAppid(), kspayConfig.getSecret(), "client_credentials"), headers); - result = restTemplate.postForObject(KSPayConstants.GET_ACCESS_TOKEN, formEntity, String.class); - } catch (Exception e) { - log.error("快手小程序post请求异常{}", KSPayConstants.GET_ACCESS_TOKEN); - log.error("post请求异常:{}", e.getMessage()); - e.printStackTrace(); - } - return result; - } - - /** - * 快手 - * 支付 退款 结算 - */ - public String ksPostRequestJson(JSONObject jsonObject, String url, String appId, String accessToken) { - String result = ""; - try { - Map map = new HashMap<>(); - map.put("app_id", appId); - map.put("access_token", accessToken); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - HttpEntity formEntity = new HttpEntity<>(jsonObject, headers); - result = restTemplate.postForObject(url + "/?app_id={app_id}&access_token={access_token}", formEntity, String.class, map); - } catch (Exception e) { - log.error("快手小程序post请求异常{}", url); - log.error("post请求异常:{}", e.getMessage()); - e.printStackTrace(); - } - return result; - } - - /** - * 抖音小程序post请求 - */ - public String dyPostRequest(JSONObject jsonObject, String url) { - String result = ""; - try { - HttpHeaders headers = new HttpHeaders(); - //所有的请求需要用JSON格式发送 - headers.setContentType(MediaType.APPLICATION_JSON); - HttpEntity formEntity = new HttpEntity<>(jsonObject, headers); - result = restTemplate.postForObject(url, formEntity, String.class); - } catch (Exception e) { - log.error("抖音小程序post请求异常{}", url); - e.printStackTrace(); - } - return result; - } -} +//package com.bnyer.pay.utils; +// +//import com.alibaba.fastjson.JSONObject; +//import com.bnyer.common.core.constant.RedisKeyConstant; +//import com.bnyer.common.core.domain.DypayConfig; +//import com.bnyer.common.core.domain.KspayConfig; +//import com.bnyer.common.core.exception.ServiceException; +//import com.bnyer.common.core.utils.StringUtils; +//import com.bnyer.common.redis.service.RedisService; +//import com.bnyer.pay.constant.DYPayConstants; +//import com.bnyer.pay.constant.KSPayConstants; +//import com.bnyer.pay.exception.DyPayException; +//import com.google.gson.JsonElement; +//import com.google.gson.JsonObject; +//import lombok.extern.slf4j.Slf4j; +//import me.chanjar.weixin.common.util.json.GsonParser; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.http.HttpEntity; +//import org.springframework.http.HttpHeaders; +//import org.springframework.http.MediaType; +//import org.springframework.stereotype.Component; +//import org.springframework.web.client.RestTemplate; +// +//import java.text.MessageFormat; +//import java.util.HashMap; +//import java.util.Map; +//import java.util.Objects; +//import java.util.concurrent.TimeUnit; +// +///** +// * @author :WXC +// * @Date :2023/04/24 +// * @description : +// */ +//@Slf4j +//@Component +//public class PayRestTemplateUtil { +// +// @Autowired +// private RestTemplate restTemplate; +// +// @Autowired +// private RedisService redisService; +// +// private final int MAX_RETRY_TIMES = 2; +// +// /** +// * 快手小程序post请求 +// * code2session +// */ +// public String ksPostRequestUrlencoded(JSONObject jsonObject, String url) { +// String result = ""; +// try { +// HttpHeaders headers = new HttpHeaders(); +// headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); +// HttpEntity formEntity = new HttpEntity<>(MessageFormat.format("js_code={0}&app_id={1}&&app_secret={2}", jsonObject.get("js_code"), jsonObject.get("appid"), jsonObject.get("secret")), headers); +// result = restTemplate.postForObject(url, formEntity, String.class); +// } catch (Exception e) { +// log.error("快手小程序post请求异常{}", url); +// log.error("post请求异常:{}", e.getMessage()); +// e.printStackTrace(); +// } +// return result; +// } +// +// /** +// * 快手小程序获取accessToken +// */ +// public String ksPostRequestUrlencoded(KspayConfig kspayConfig,boolean isRefresh) { +// if (!isRefresh){ +// Object cacheObject = redisService.getCacheObject(RedisKeyConstant.KS_ACCESS_TOKEN_KEY); +// if (Objects.nonNull(cacheObject)){ +// return cacheObject.toString(); +// } +// } +// String accessToken = ""; +// try { +// HttpHeaders headers = new HttpHeaders(); +// headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); +// HttpEntity formEntity = new HttpEntity<>(MessageFormat.format("app_id={0}&app_secret={1}&&grant_type={2}" +// , kspayConfig.getAppid(), kspayConfig.getSecret(), "client_credentials"), headers); +// accessToken = restTemplate.postForObject(KSPayConstants.GET_ACCESS_TOKEN, formEntity, String.class); +// //存入redis +// redisService.setCacheObject(RedisKeyConstant.KS_ACCESS_TOKEN_KEY,accessToken,2L, TimeUnit.HOURS); +// } catch (Exception e) { +// log.error("快手小程序post请求异常{}", KSPayConstants.GET_ACCESS_TOKEN); +// log.error("post请求异常:{}", e.getMessage()); +// e.printStackTrace(); +// } +// return accessToken; +// } +// +// /** +// * 快手 +// * 支付 退款 结算 +// */ +// public String ksPostRequestJson(JSONObject jsonObject, String url, String appId, String accessToken) { +// String result = ""; +// try { +// Map map = new HashMap<>(); +// map.put("app_id", appId); +// map.put("access_token", accessToken); +// HttpHeaders headers = new HttpHeaders(); +// headers.setContentType(MediaType.APPLICATION_JSON); +// HttpEntity formEntity = new HttpEntity<>(jsonObject, headers); +// result = restTemplate.postForObject(url + "/?app_id={app_id}&access_token={access_token}", formEntity, String.class, map); +// } catch (Exception e) { +// log.error("快手小程序post请求异常{}", url); +// log.error("post请求异常:{}", e.getMessage()); +// e.printStackTrace(); +// } +// return result; +// } +// +// /** +// * 抖音小程序获取accessToken +// * 为了保障应用的数据安全,只能在开发者服务器使用 AppSecret,如果小程序存在泄露 AppSecret 的问题,字节小程序平台将有可能下架该小程序,并暂停该小程序相关服务。 +// * +// * access_token 是小程序的全局唯一调用凭据,开发者调用小程序支付时需要使用 access_token。access_token 的有效期为 2 个小时,需要定时刷新 access_token,重复获取会导致之前一次获取的 access_token 的有效期缩短为 5 分钟。 +// */ +// public String dyGetAccessToken(DypayConfig dypayConfig,boolean isRefresh) throws DyPayException { +// if (!isRefresh){ +// Object cacheObject = redisService.getCacheObject(RedisKeyConstant.DY_ACCESS_TOKEN_KEY); +// if (Objects.nonNull(cacheObject)){ +// return cacheObject.toString(); +// } +// } +// String requestStr = ""; +// try { +// HttpHeaders headers = new HttpHeaders(); +// headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); +// HttpEntity formEntity = new HttpEntity<>(MessageFormat.format("app_id={0}&app_secret={1}&&grant_type={2}" +// , dypayConfig.getAppid(), dypayConfig.getAppSecret(), "client_credentials"), headers); +// String resultStr = restTemplate.postForObject(DYPayConstants.GET_ACCESS_TOKEN, formEntity, String.class); +// String url = DYPayConstants.GET_ACCESS_TOKEN; +// requestStr = formEntity.getBody(); +// if (StringUtils.isBlank(resultStr) || !resultStr.startsWith("{")){ +// log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, resultStr); +// throw convertDyException(GsonParser.parse("第三方返回格式有误!")); +// } +// JSONObject resultObj = JSONObject.parseObject(resultStr); +// String errNo = StringUtils.isNotBlank(resultObj.getString("err_no"))?resultObj.getString("err_no"):resultObj.getString("err_code"); +// if (!"0".equals(errNo)) { +// log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, resultObj, resultStr); +// //存入redis +// String accessToken = resultObj.getJSONObject("data").getString("access_token"); +// String expiresIn = resultObj.getJSONObject("data").getString("expires_in"); +// redisService.setCacheObject(RedisKeyConstant.DY_ACCESS_TOKEN_KEY,accessToken,Long.parseLong(expiresIn), TimeUnit.SECONDS); +// return accessToken; +// }else { +// throw convertDyException(GsonParser.parse(resultStr)); +// } +// } catch (Exception e) { +// log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", DYPayConstants.GET_ACCESS_TOKEN, requestStr, e.getMessage()); +// throw (e instanceof DyPayException) ? (DyPayException) e : new DyPayException(e.getMessage(), e); +// } +// } +// +// /** +// * 抖音小程序post请求 +// */ +// public String dyPostRequest(String requestStr, String url) throws DyPayException { +// return dyPostRequest(0,requestStr,url); +// } +// +// /** +// * 抖音小程序post请求 +// */ +// public String dyPostRequest(int retryTimes, String requestStr, String url) throws DyPayException { +// String resultStr = ""; +// try { +// HttpHeaders headers = new HttpHeaders(); +// //所有的请求需要用JSON格式发送 +// headers.setContentType(MediaType.APPLICATION_JSON); +// HttpEntity formEntity = new HttpEntity<>(requestStr, headers); +// resultStr = restTemplate.postForObject(url, formEntity, String.class); +// if (StringUtils.isBlank(resultStr) || !resultStr.startsWith("{")){ +// log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, resultStr); +// throw new DyPayException("第三方返回格式有误!"); +// } +// JSONObject resultObj = JSONObject.parseObject(resultStr); +// String errNo = StringUtils.isNotBlank(resultObj.getString("err_no"))?resultObj.getString("err_no"):resultObj.getString("err_code"); +// if (!"0".equals(errNo)) { +// log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, resultObj, resultStr); +// return resultStr; +// }else { +// throw convertDyException(GsonParser.parse(resultStr)); +// } +// }catch (DyPayException e){ +// log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage()); +// String accessToken = refreshAccessToken(dypayConfig, requestStr, DYPayConstants.ORDER_PUSH, e); +// if (retryTimes > MAX_RETRY_TIMES){ +// throw new DyPayException("抖音接口调用失败,已超过最大重试次数"); +// } +// retryTimes ++; +// dyPostRequest(retryTimes,requestStr,url); +// } +// return resultStr; +// } +// +// /** +// * token过期请求重试 +// * @param dypayConfig +// * @param requestStr +// * @param e +// */ +// private String refreshAccessToken(DypayConfig dypayConfig,String url, String requestStr, DyPayException e) { +// //如果token过期的话,刷新token并重试 +// String accessToken = ""; +// if ("4004".equals(e.getErrCode())){ +// try { +// accessToken = dyRefreshAccessToken(dypayConfig); +// } catch (DyPayException dyPayException) { +// throw new ServiceException(dyPayException.getMessage()); +// } +// } +// String result = ""; +// if ("4004".equals(e.getErrCode())){ +// try { +// result = dyPostRequest(requestStr, url); +// } catch (DyPayException dyPayException) { +// log.error("抖音支付:抖音订单推送接口调用异常,request:{},error{}", requestStr, e.getMessage()); +// } +// } +// return accessToken; +// } +// +// /** +// * 刷新抖音token +// * @param dypayConfig +// */ +// public String dyRefreshAccessToken(DypayConfig dypayConfig) throws DyPayException { +// return dyRefreshAccessToken(0,dypayConfig); +// } +// +// /** +// * 刷新抖音token +// * @param retryTimes +// * @param dypayConfig +// */ +// public String dyRefreshAccessToken(int retryTimes, DypayConfig dypayConfig) throws DyPayException { +// String accessToken = ""; +// try { +// accessToken = dyGetAccessToken(dypayConfig, true); +// } catch (DyPayException e) { +// if (retryTimes > MAX_RETRY_TIMES){ +// throw new DyPayException("抖音接口调用失败,已超过最大重试次数"); +// } +// retryTimes ++; +// dyRefreshAccessToken(retryTimes, dypayConfig); +// } +// return accessToken; +// } +// +// /** +// * 抖音转换异常 +// * @param jsonObject +// * @return +// */ +// private DyPayException convertDyException(JsonObject jsonObject) { +// JsonElement codeElement = jsonObject.get("err_no") != null ? jsonObject.get("err_no"):jsonObject.get("err_code"); +// String code = codeElement == null ? null : codeElement.getAsString(); +// String message = jsonObject.get("error_msg").getAsString(); +// DyPayException dyPayException = new DyPayException(message); +// dyPayException.setErrCode(code); +// dyPayException.setErrCodeDes(message); +// return dyPayException; +// } +//} diff --git a/bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/DypayConfigMapper.xml b/bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/DypayConfigMapper.xml index ceae969..1848150 100644 --- a/bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/DypayConfigMapper.xml +++ b/bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/DypayConfigMapper.xml @@ -6,6 +6,7 @@ + @@ -18,6 +19,6 @@ - id, appid,salt, token, backurl, `status`, remark, create_time, update_time,sort,is_show + id, appid,app_secret,salt, token, backurl, `status`, remark, create_time, update_time,sort,is_show