From 542f4207d238675b7d2e2893d66b1f83d0037e6e Mon Sep 17 00:00:00 2001 From: wuxicheng <1441859745@qq.com> Date: Mon, 24 Apr 2023 17:06:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8A=96=E9=9F=B3=E3=80=81?= =?UTF-8?q?=E5=BF=AB=E6=89=8B=E6=94=AF=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/constant/RedisKeyConstant.java | 5 + .../bnyer/common/core/domain/DypayConfig.java | 69 ++++++ .../bnyer/common/core/domain/KspayConfig.java | 62 +++++ .../com/bnyer/common/core/domain/PayInfo.java | 18 +- .../bnyer/common/core/enums/EnumPayType.java | 6 +- .../service/impl/VipOrderServiceImpl.java | 2 +- .../pay/config/RestTemplateConfiguration.java | 27 +++ .../bnyer/pay/constant/DYPayConstants.java | 27 +++ .../bnyer/pay/constant/KSPayConstants.java | 50 ++++ .../bnyer/pay/controller/DYPayController.java | 42 ++++ .../bnyer/pay/controller/KSPayController.java | 50 ++++ .../bnyer/pay/design/factory/PayFactory.java | 2 + .../design/strategy/AbstractPayStrategy.java | 79 +++++-- .../pay/design/strategy/AliPayStrategy.java | 20 +- .../pay/design/strategy/DYPayStrategy.java | 215 ++++++++++++++++++ .../pay/design/strategy/KSPayStrategy.java | 214 +++++++++++++++++ .../pay/design/strategy/WxPayStrategy.java | 19 +- .../bnyer/pay/dto/EditPayInfoSingleDto.java | 37 +++ .../com/bnyer/pay/dto/PayNotifyCheckDto.java | 5 + .../com/bnyer/pay/dto/UnifiedOrderDto.java | 8 +- .../bnyer/pay/mapper/DypayConfigMapper.java | 13 ++ .../bnyer/pay/mapper/KspayConfigMapper.java | 13 ++ .../com/bnyer/pay/mapper/PayInfoMapper.java | 5 +- .../bnyer/pay/service/DypayConfigService.java | 12 + .../bnyer/pay/service/KspayConfigService.java | 12 + .../service/impl/DypayConfigServiceImpl.java | 15 ++ .../service/impl/KspayConfigServiceImpl.java | 15 ++ .../pay/service/impl/PayInfoServiceImpl.java | 51 +++-- .../java/com/bnyer/pay/utils/DYPayUtil.java | 138 +++++++++++ .../java/com/bnyer/pay/utils/KSPayUtil.java | 133 +++++++++++ .../bnyer/pay/utils/PayRestTemplateUtil.java | 107 +++++++++ .../java/com/bnyer/pay/vo/PayInOrderVo.java | 11 +- .../bnyer/pay/mapper/DypayConfigMapper.xml | 21 ++ .../bnyer/pay/mapper/KspayConfigMapper.xml | 20 ++ .../com/bnyer/pay/mapper/PayInfoMapper.xml | 18 +- 35 files changed, 1476 insertions(+), 65 deletions(-) create mode 100644 bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/DypayConfig.java create mode 100644 bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/KspayConfig.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/config/RestTemplateConfiguration.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/constant/DYPayConstants.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/constant/KSPayConstants.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/DYPayController.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/KSPayController.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/DYPayStrategy.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/KSPayStrategy.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/dto/EditPayInfoSingleDto.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/mapper/DypayConfigMapper.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/mapper/KspayConfigMapper.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/DypayConfigService.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/KspayConfigService.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/DypayConfigServiceImpl.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/KspayConfigServiceImpl.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/DYPayUtil.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/KSPayUtil.java create mode 100644 bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/PayRestTemplateUtil.java create mode 100644 bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/DypayConfigMapper.xml create mode 100644 bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/KspayConfigMapper.xml 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 903e961..e92f167 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 @@ -141,4 +141,9 @@ public class RedisKeyConstant { * 热搜词存入时间 */ public static final String HOT_KEY_WORD_TIME_KEY = "bnyer.hotkeywordtime"; + + /** + * 支付回调rediskey + */ + public static final String PAY_NOTIFY_LOCK_KEY = "bnyer.pay.notify.lock:"; } 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 new file mode 100644 index 0000000..ee4ee11 --- /dev/null +++ b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/DypayConfig.java @@ -0,0 +1,69 @@ +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; + +/** + * @author :WXC + * @description : + */ +/** + * 抖音支付配置表 + */ +@ApiModel(value="com-bnyer-common-core-domain-PayDypayConfig") +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "pay_dypay_config") +public class DypayConfig extends BaseDomain { + /** + * appid + */ + @TableField(value = "appid") + @ApiModelProperty(value="appid") + private String appid; + + /** + * 秘钥 + */ + @TableField(value = "salt") + @ApiModelProperty(value="秘钥") + private String salt; + + /** + * 令牌 + */ + @TableField(value = "token") + @ApiModelProperty(value="令牌") + private String token; + + /** + * 回调地址url + */ + @TableField(value = "backurl") + @ApiModelProperty(value="回调地址url") + private String backurl; + + /** + * 帐号状态(0正常 1停用) + */ + @TableField(value = "status") + @ApiModelProperty(value="帐号状态(0正常 1停用)") + private String status; + + @TableField(value = "remark") + @ApiModelProperty(value="") + private String remark; +} diff --git a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/KspayConfig.java b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/KspayConfig.java new file mode 100644 index 0000000..84f2c3f --- /dev/null +++ b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/KspayConfig.java @@ -0,0 +1,62 @@ +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; + +/** + * @author :WXC + * @description : + */ +/** + * 快手支付配置表 + */ +@ApiModel(value="com-bnyer-common-core-domain-PayKspayConfig") +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "pay_kspay_config") +public class KspayConfig extends BaseDomain { + /** + * appid + */ + @TableField(value = "appid") + @ApiModelProperty(value="appid") + private String appid; + + /** + * 秘钥 + */ + @TableField(value = "secret") + @ApiModelProperty(value="秘钥") + private String secret; + + /** + * 回调地址url + */ + @TableField(value = "backurl") + @ApiModelProperty(value="回调地址url") + private String backurl; + + /** + * 帐号状态(0正常 1停用) + */ + @TableField(value = "status") + @ApiModelProperty(value="帐号状态(0正常 1停用)") + private String status; + + @TableField(value = "remark") + @ApiModelProperty(value="") + private String remark; +} diff --git a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/PayInfo.java b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/PayInfo.java index 93c9dda..30e8a37 100644 --- a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/PayInfo.java +++ b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/PayInfo.java @@ -73,12 +73,19 @@ public class PayInfo extends BaseDomain { /** - * 支付类型:wxpay/alipay + * 支付类型:wxpay/alipay/kspay/dypay */ @TableField(value = "pay_type") - @ApiModelProperty(value="支付类型:wxpay/alipay") + @ApiModelProperty(value="支付类型:wxpay/alipay/kspay/dypay") private String payType; + /** + * 支付渠道 + */ + @TableField(value = "pay_channel") + @ApiModelProperty(value="支付渠道") + private String payChannel; + /** * 交易类型:JSAPI等 */ @@ -93,6 +100,13 @@ public class PayInfo extends BaseDomain { @ApiModelProperty(value="支付单号(第三方返回)") private String payNo; + /** + * 用户侧订单号(第三方返回) + */ + @TableField(value = "trade_no") + @ApiModelProperty(value="用户侧订单号(第三方返回)") + private String tradeNo; + /** * appid */ diff --git a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/enums/EnumPayType.java b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/enums/EnumPayType.java index 35aa29d..f1ff32f 100644 --- a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/enums/EnumPayType.java +++ b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/enums/EnumPayType.java @@ -13,8 +13,10 @@ import java.util.Objects; @AllArgsConstructor public enum EnumPayType { - WX_PAY("wxpay","微信"), - ALI_PAY("alipay","支付宝"), + WX_PAY("wxpay","微信支付"), + ALI_PAY("alipay","支付宝支付"), + DY_PAY("dypay","抖音支付"), + KS_PAY("kspay","快手支付"), ; private final String type; 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 6f073a5..68d776b 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 @@ -124,7 +124,7 @@ public class VipOrderServiceImpl extends ServiceImpl i case AI_VIP: break; default: - throw new ServiceException("categoryCode未匹配上对应分类"); + throw new ServiceException("vipTypeCode 未匹配上对应分类"); } return vipOrder; } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/config/RestTemplateConfiguration.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/config/RestTemplateConfiguration.java new file mode 100644 index 0000000..9a4fe66 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/config/RestTemplateConfiguration.java @@ -0,0 +1,27 @@ +package com.bnyer.pay.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +/** + * @author chengkun + * @date 2022/4/28 17:27 + */ +@Configuration +public class RestTemplateConfiguration { + @Bean + public RestTemplate restTemplate(ClientHttpRequestFactory factory) { + return new RestTemplate(factory); + } + + @Bean + public ClientHttpRequestFactory simpleClientHttpRequestFactory(){ + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(15000); + factory.setReadTimeout(10000); + return factory; + } +} 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 new file mode 100644 index 0000000..bd6a5b3 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/constant/DYPayConstants.java @@ -0,0 +1,27 @@ +package com.bnyer.pay.constant; + +/** + * @author :WXC + * @Date :2023/04/23 + * @description :抖音支付常量池 + */ +public class DYPayConstants { + /** + * 登陆 + */ + public static final String CODE_2_SESSION = "https://developer.toutiao.com/api/apps/v2/jscode2session"; + /** + * 生成预支付单 + */ + public static final String CREATE_ORDER = "https://developer.toutiao.com/api/apps/ecpay/v1/create_order"; + /** + * 分账 + */ + public static final String SETTLE = "https://developer.toutiao.com/api/apps/ecpay/v1/settle"; + + /** + * 退款 + */ + public static final String CREATE_REFUND = "https://developer.toutiao.com/api/apps/ecpay/v1/create_refund"; +} + diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/constant/KSPayConstants.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/constant/KSPayConstants.java new file mode 100644 index 0000000..878db4b --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/constant/KSPayConstants.java @@ -0,0 +1,50 @@ +package com.bnyer.pay.constant; + +/** + * @author :WXC + * @Date :2023/04/23 + * @description : 快手支付常量池 + */ +public class KSPayConstants { + /** + * https://mp.kuaishou.com/docs/develop/server/code2Session.html + * code获取openId sessionKey + */ + public static final String CODE_2_SESSION = "https://open.kuaishou.com/oauth2/mp/code2session"; + + /** + * https://mp.kuaishou.com/docs/develop/server/getAccessToken.html + * 接口调用凭证 + */ + public static final String GET_ACCESS_TOKEN = "https://open.kuaishou.com/oauth2/access_token"; + + /** + * https://mp.kuaishou.com/docs/develop/server/epay/interfaceDefinition.html + * 预下单接口 + */ + public static final String CREATE_ORDER = "https://open.kuaishou.com/openapi/mp/developer/epay/create_order"; + + /** + * 退款 + * https://mp.kuaishou.com/docs/develop/server/epay/interfaceDefinition.html#_1-3%E6%94%AF%E4%BB%98%E5%9B%9E%E8%B0%83 + */ + public static final String APPLY_REFUND = "https://open.kuaishou.com/openapi/mp/developer/epay/apply_refund"; + + /** + * 结算 + * https://mp.kuaishou.com/docs/develop/server/epay/interfaceDefinition.html#_1-3%E6%94%AF%E4%BB%98%E5%9B%9E%E8%B0%83 + */ + public static final String APPLY_SETTLE = "https://open.kuaishou.com/openapi/mp/developer/epay/settle"; + + /** + * 超时时间 + */ + public static final int expireTime = 1800; + + + /** + * https://mp.kuaishou.com/docs/operate/platformAgreement/epayServiceCharge.html + * 商品类目编号 + */ + public static final int GOODS_TYPE_VIP = 1273; +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/DYPayController.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/DYPayController.java new file mode 100644 index 0000000..5f4a950 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/DYPayController.java @@ -0,0 +1,42 @@ +package com.bnyer.pay.controller; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.bnyer.common.core.enums.EnumPayType; +import com.bnyer.pay.design.factory.PayFactory; +import com.bnyer.pay.design.strategy.IPayStrategy; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author :WXC + * @Date :2023/04/24 + * @description : + */ +@Api(value = "抖音支付相关接口",tags = "抖音支付相关接口") +@RestController +@Slf4j +public class DYPayController { + + /** + * 抖音支付结果通知 + */ + @ApiOperation(value = "抖音支付结果通知") + @ResponseBody + @PostMapping("/dypayBack") + public JSONObject dyPayNotify(@RequestBody JSONObject object, HttpServletRequest request) { + log.info("抖音支付异步通知开始==============》{}", object); + IPayStrategy payStrategy = PayFactory.getInstance().getConcreteStrategy(EnumPayType.DY_PAY.getType()); + String payNotify = payStrategy.parsePayNotify(JSON.toJSONString(object)); + return JSON.parseObject(payNotify); + } + + +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/KSPayController.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/KSPayController.java new file mode 100644 index 0000000..08c477f --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/KSPayController.java @@ -0,0 +1,50 @@ +package com.bnyer.pay.controller; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.bnyer.common.core.enums.EnumPayType; +import com.bnyer.pay.design.factory.PayFactory; +import com.bnyer.pay.design.strategy.IPayStrategy; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author :WXC + * @Date :2023/04/24 + * @description : + */ +@Api(value = "快手支付相关接口",tags = "快手支付相关接口") +@RestController +@Slf4j +public class KSPayController { + + /** + * 快手支付结果通知 + */ + @ApiOperation(value = "快手支付结果通知") + @ResponseBody + @PostMapping("/kspayBack") + public JSONObject ksPayNotify(@RequestBody JSONObject object, HttpServletRequest request) { + log.info("快手微信支付异步通知开始==============》{}", object); + log.info("快手微信支付kwaisign==============》{}", request.getHeader("kwaisign")); + String kwaisign = request.getHeader("kwaisign"); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("data",object); + jsonObject.put("sign",kwaisign); + String string = JSONObject.toJSONString(jsonObject); + IPayStrategy payStrategy = PayFactory.getInstance().getConcreteStrategy(EnumPayType.KS_PAY.getType()); + String payNotify = payStrategy.parsePayNotify(string); + return JSON.parseObject(payNotify); + } + + +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/factory/PayFactory.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/factory/PayFactory.java index 2905b36..9127098 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/factory/PayFactory.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/factory/PayFactory.java @@ -17,6 +17,8 @@ public class PayFactory { private static final Map strategyMap = new ImmutableMap.Builder() .put(EnumPayType.ALI_PAY.getType(),new AliPayStrategy()) .put(EnumPayType.WX_PAY.getType(),new WxPayStrategy()) + .put(EnumPayType.DY_PAY.getType(),new WxPayStrategy()) + .put(EnumPayType.KS_PAY.getType(),new WxPayStrategy()) .build(); public static class SingletonHolder{ 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 b6ac5aa..9ade772 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 @@ -1,17 +1,22 @@ package com.bnyer.pay.design.strategy; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; import com.alipay.api.msg.MsgConstants; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.bnyer.common.core.constant.RedisKeyConstant; import com.bnyer.common.core.domain.PayInfo; import com.bnyer.common.core.enums.EnumPayType; +import com.bnyer.common.redis.service.RedissonService; import com.bnyer.pay.dto.PayNotifyCheckDto; import com.bnyer.pay.mapper.PayInfoMapper; import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse; import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RLock; import javax.annotation.Resource; import java.util.Objects; +import java.util.concurrent.TimeUnit; /** * @author :WXC @@ -24,48 +29,84 @@ public abstract class AbstractPayStrategy implements IPayStrategy { @Resource private PayInfoMapper payInfoMapper; + @Resource + private RedissonService redissonService; + /** * 校验是否已支付避免重复调用 * @param checkDto * @return */ public String payNotifyCheck(PayNotifyCheckDto checkDto){ + log.error("回调结果校验开始,支付单号:{}",checkDto.getPayId()); String resultMsg = ""; - PayInfo payInfo = payInfoMapper.selectOne(new LambdaQueryWrapper().eq(PayInfo::getPayId, checkDto.getPayId()).eq(PayInfo::getIsShow, "1")); - if (Objects.isNull(payInfo)){ - return buildNotifyCheckResultMsg(checkDto.getPayType(),false,"Order_Not_Exist"); - } - log.info("查询到支付订单信息为:{}", JSON.toJSONString(payInfo)); - //订单中的金额 - String payInfoPayAmount = payInfo.getPayAmount().toString(); - //回调中的金额 - String notifyPayAmount = checkDto.getPayAmount(); - //对账状态 - Integer singleStatus = payInfo.getSingleStatus(); - log.info("回调中的金额:{},订单中的金额:{}",notifyPayAmount,payInfoPayAmount); - if (payInfoPayAmount.equals(notifyPayAmount)){ - if (Objects.nonNull(singleStatus)){ - resultMsg = buildNotifyCheckResultMsg(checkDto.getPayType(),true,"OK"); + RLock rLock = redissonService.getRLock(RedisKeyConstant.PAY_NOTIFY_LOCK_KEY + checkDto.getPayId()); + try{ + if(rLock.tryLock(2L, 10L, TimeUnit.SECONDS)){ + PayInfo payInfo = payInfoMapper.selectOne(new LambdaQueryWrapper().eq(PayInfo::getPayId, checkDto.getPayId()).eq(PayInfo::getIsShow, "1")); + if (Objects.isNull(payInfo)){ + return buildNotifyCheckResultMsg(checkDto.getPayType(),false,"business fail"); + } + log.info("查询到支付订单信息为:{}", JSON.toJSONString(payInfo)); + //订单中的金额 + String payInfoPayAmount = payInfo.getPayAmount().toString(); + //回调中的金额 + String notifyPayAmount = checkDto.getPayAmount(); + //对账状态 + Integer singleStatus = payInfo.getSingleStatus(); + log.info("回调中的金额:{},订单中的金额:{}",notifyPayAmount,payInfoPayAmount); + if (payInfoPayAmount.equals(notifyPayAmount)){ + if (Objects.nonNull(singleStatus)){ + resultMsg = buildNotifyCheckResultMsg(checkDto.getPayType(),true,checkDto.getMsg()); + } + }else { + resultMsg = buildNotifyCheckResultMsg(checkDto.getPayType(),false,"business fail"); + } + }else { + log.error("获取锁失败,支付单号:{}",checkDto.getPayId()); + resultMsg = buildNotifyCheckResultMsg(checkDto.getPayType(),false,"business fail"); } - }else { - resultMsg = buildNotifyCheckResultMsg(checkDto.getPayType(),false,"Amount_Diff"); + }catch (Exception e){ + e.printStackTrace(); + log.error("回调结果校验失败,支付单号:{}",checkDto.getPayId()); + resultMsg = buildNotifyCheckResultMsg(checkDto.getPayType(),false,"business fail"); + }finally { + rLock.unlock(); } return resultMsg; } /** - * 构建校验后回调返回结果信息 + * 构建回调返回结果信息 * @param payType 支付方式 * @param isSuccess 是否构建成功消息 * @param msg 消息内容 * @return */ - private String buildNotifyCheckResultMsg(EnumPayType payType,boolean isSuccess,String msg){ + public String buildNotifyCheckResultMsg(EnumPayType payType,boolean isSuccess,String msg){ String result = ""; if (EnumPayType.WX_PAY == payType){ result = isSuccess?WxPayNotifyResponse.success(msg):WxPayNotifyResponse.fail(msg); }else if (EnumPayType.ALI_PAY == payType){ result = isSuccess?MsgConstants.SUCCESS:MsgConstants.FAIL; + } else if (EnumPayType.KS_PAY == payType) { + JSONObject returnObj = new JSONObject(); + if (isSuccess){ + returnObj.put("result", 1); + }else { + returnObj.put("result", 0); + } + returnObj.put("message_id", msg); + result = JSON.toJSONString(returnObj); + } else if (EnumPayType.DY_PAY == payType) { + JSONObject returnObj = new JSONObject(); + if (isSuccess){ + returnObj.put("err_no", 0); + }else { + returnObj.put("err_no", -1); + } + returnObj.put("err_tips", msg); + result = JSON.toJSONString(returnObj); } return result; } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/AliPayStrategy.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/AliPayStrategy.java index 6adaa4c..096f9f2 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/AliPayStrategy.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/AliPayStrategy.java @@ -30,6 +30,7 @@ import com.bnyer.pay.mapper.AlipayConfigMapper; import com.bnyer.pay.service.PayInfoService; import com.bnyer.pay.vo.PayInOrderVo; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -52,6 +53,9 @@ public class AliPayStrategy extends AbstractPayStrategy { @Resource private AlipayConfigMapper alipayConfigMapper; + @Autowired + private PayInfoService payInfoService; + @Transactional(propagation = Propagation.REQUIRED,rollbackFor=Exception.class) @Override public PayInOrderVo unifiedOrder(UnifiedOrderDto dto) { @@ -74,7 +78,7 @@ public class AliPayStrategy extends AbstractPayStrategy { AlipayTradeAppPayModel model = new AlipayTradeAppPayModel(); model.setSubject(dto.getGoodsSubject()); model.setBody(dto.getGoodsDesc()); - model.setOutTradeNo(dto.getOrderId()); + model.setOutTradeNo(dto.getPayId()); //直接固定好过期时间比较好 model.setTimeExpire(DateUtil.formatDateTime(DateUtils.getDateByType(EnumTimeUnit.MINUTE,dto.getCurrDate(),30))); // model.setTimeoutExpress(AliPayConstant.timeoutExpress); @@ -88,8 +92,8 @@ public class AliPayStrategy extends AbstractPayStrategy { if(response.isSuccess()){ PayInOrderVo vo = new PayInOrderVo(); vo.setOutStr(response.getBody()); - vo.setOrderId(dto.getOrderId()); vo.setAppid(appid); + vo.setPayId(dto.getPayId()); return vo; }else{ throw new ServiceException(response.getMsg()); @@ -101,12 +105,10 @@ public class AliPayStrategy extends AbstractPayStrategy { } } - @Transactional(propagation = Propagation.REQUIRED,rollbackFor=Exception.class) @Override public String parsePayNotify(String params) { Map inMap = JSON.parseObject(params, new TypeReference>() {}); log.info("支付宝支付回调开始: AliPayStrategy.parsePayNotify inMap:{}",JSON.toJSONString(inMap)); - PayInfoService payInfoService = SpringUtils.getBean(PayInfoService.class); try{ //--------------------------1.解析并拿到参数--------------------- String appId = inMap.get("app_id"); @@ -142,8 +144,9 @@ public class AliPayStrategy extends AbstractPayStrategy { if(tradeStatus.equals("TRADE_FINISHED") || tradeStatus.equals("TRADE_SUCCESS")){ PayNotifyCheckDto payNotifyCheckDto = new PayNotifyCheckDto(); payNotifyCheckDto.setPayType(EnumPayType.ALI_PAY); - payNotifyCheckDto.setPayId(tradeNo); + payNotifyCheckDto.setPayId(outTradeNo); payNotifyCheckDto.setPayAmount(totalAmount); + payNotifyCheckDto.setMsg("OK"); String notifyCheckResult = super.payNotifyCheck(payNotifyCheckDto); if (StringUtils.isNotBlank(notifyCheckResult)){ return notifyCheckResult; @@ -155,18 +158,17 @@ public class AliPayStrategy extends AbstractPayStrategy { editPayInfoNotifyDto.setPayId(outTradeNo); editPayInfoNotifyDto.setPayTime(gmtPayment); editPayInfoNotifyDto.setPayAmount(totalAmount); - editPayInfoNotifyDto.setPayNo(tradeNo); editPayInfoNotifyDto.setAppId(appId); - editPayInfoNotifyDto.setPayType(EnumPayType.ALI_PAY.getType()); + editPayInfoNotifyDto.setPayNo(tradeNo); payInfoService.editPayInfoNotify(editPayInfoNotifyDto); }else if(tradeStatus.equals("TRADE_CLOSED")){ log.info("支付宝支付回调:TRADE_CLOSED 状态不做处理"); //交易关闭不作处理:退款或未支付超时关闭 - TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); +// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }catch (Exception e){ log.info("支付宝支付回调:处理过程异常,error:{}",e.getMessage()); - TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); +// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return MsgConstants.SUCCESS; } 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 new file mode 100644 index 0000000..aeb3a6c --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/DYPayStrategy.java @@ -0,0 +1,215 @@ +package com.bnyer.pay.design.strategy; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.bnyer.common.core.context.SecurityContextHolder; +import com.bnyer.common.core.domain.DypayConfig; +import com.bnyer.common.core.domain.KspayConfig; +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.constant.DYPayConstants; +import com.bnyer.pay.constant.KSPayConstants; +import com.bnyer.pay.dto.EditPayInfoNotifyDto; +import com.bnyer.pay.dto.PayNotifyCheckDto; +import com.bnyer.pay.dto.UnifiedOrderDto; +import com.bnyer.pay.enums.EnumPayConfigStatus; +import com.bnyer.pay.mapper.DypayConfigMapper; +import com.bnyer.pay.mapper.KspayConfigMapper; +import com.bnyer.pay.service.PayInfoService; +import com.bnyer.pay.utils.DYPayUtil; +import com.bnyer.pay.utils.KSPayUtil; +import com.bnyer.pay.utils.PayRestTemplateUtil; +import com.bnyer.pay.vo.PayInOrderVo; +import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest; +import com.github.binarywang.wxpay.bean.result.BaseWxPayResult; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * @author :WXC + * @Date :2023/04/23 + * @description : + */ +@Slf4j +@Component +public class DYPayStrategy extends AbstractPayStrategy{ + + @Autowired + private DYPayUtil dyPayUtil; + + @Autowired + private PayRestTemplateUtil payRestTemplateUtil; + + @Autowired + private PayInfoService payInfoService; + + @Autowired + private DypayConfigMapper dypayConfigMapper; + + @Override + public PayInOrderVo unifiedOrder(UnifiedOrderDto dto) { + try { + List dypayConfigList = dypayConfigMapper.selectList(new LambdaQueryWrapper().eq(DypayConfig::getStatus, EnumPayConfigStatus.ENABLE.getCode())); + if (CollUtil.isEmpty(dypayConfigList)){ + throw new ServiceException(ResponseEnum.PAY_CONFIG_ERROR); + } + DypayConfig dypayConfig = dypayConfigList.get(0); + String appId = dypayConfig.getAppid(); + String backurl = dypayConfig.getBackurl(); + String salt = dypayConfig.getSalt(); + //加签验签的参数需要排序 + Map params = new TreeMap<>(); + //小程序APPID + params.put("app_id",appId); + //开发者侧的订单号。需保证同一小程序下不可重复 + params.put("out_order_no", dto.getPayId()); + //支付价格。单位为[分],取值范围:[1,10000000000] 100元 = 100*100 分 + params.put("total_amount", BaseWxPayRequest.yuanToFen(dto.getPayAmount())); + //商品描述。 + params.put("subject", dto.getGoodsSubject()); + //商品详情 + params.put("body", dto.getGoodsDesc()); + //订单过期时间(秒) 5min-2day + params.put("valid_time", 1800); + //开发者自定义字段,回调原样回传。超过最大长度会被截断 + params.put("cp_extra", "xx平台充值"); + //通知地址 + params.put("notify_url", backurl); + //签名,详见https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/ecpay/TE + String sign = dyPayUtil.getSign(params,salt); + params.put("sign", sign); + + //以JSON格式拼好以下参数发送请求 + JSONObject payJson = new JSONObject(); + payJson.put("app_id", appId); + payJson.put("out_order_no", dto.getPayId()); + //此处需要传入一个数值类型,string会报错。。 + payJson.put("total_amount", BaseWxPayRequest.yuanToFen(dto.getPayAmount())); + payJson.put("subject",dto.getGoodsSubject()); + payJson.put("body", dto.getGoodsDesc()); + payJson.put("valid_time", 1800); + payJson.put("sign", sign); + //payJson.put("cp_extra", "开发者自定义字段"); + payJson.put("notify_url",backurl); + log.info("请求参数{}", payJson); + //预下单接口 + String result = payRestTemplateUtil.dyPostRequest(payJson, DYPayConstants.CREATE_ORDER); + log.info("=================================="); + log.info("抖音预下单result{}", result); + log.info("=================================="); + if (!"".equals(result)) { + JSONObject jsonObject = JSONObject.parseObject(result); + String errNo = jsonObject.getString("err_no"); + if ("0".equals(errNo)) { + JSONObject data = jsonObject.getJSONObject("data"); + String orderId = data.getString("order_id"); + String orderToken = data.getString("order_token"); + if (null != orderToken && null != orderId) { + //保存预下单信息 + PayInOrderVo payInOrderVo = new PayInOrderVo(); + //把order_no和order_info_token返回前端用于调起收银台 + payInOrderVo.setPayId(dto.getPayId()); + payInOrderVo.setPayOrderId(orderId); + payInOrderVo.setOrderToken(orderToken); + return payInOrderVo; + } else { + return null; + } + } else { + log.error("抖音支付:统一下单接口调用失败,payId:{},error{}", dto.getPayId(), jsonObject.getString("error_msg")); + throw new ServiceException(ResponseEnum.PAY_FAILS); + } + } else { + log.error("抖音支付:统一下单接口调用失败,payId:{},error{}", dto.getPayId(), "请求结果返回为空"); + throw new ServiceException(ResponseEnum.PAY_FAILS); + } + } catch (Exception e) { + e.printStackTrace(); + log.error("抖音支付:支付异常,payId:{},error{}", dto.getPayId(), e.getMessage()); + throw new ServiceException(ResponseEnum.PAY_FAILS); + } + } + + @Override + public String parsePayNotify(String params) { + List dypayConfigList = dypayConfigMapper.selectList(new LambdaQueryWrapper() + .eq(DypayConfig::getStatus, EnumPayConfigStatus.ENABLE.getCode())); + if (CollUtil.isEmpty(dypayConfigList)){ + throw new ServiceException(ResponseEnum.PAY_CONFIG_ERROR); + } + DypayConfig dypayConfig = dypayConfigList.get(0); + String appId = dypayConfig.getAppid(); + String token = dypayConfig.getToken(); + JSONObject object = JSONObject.parseObject(params); + //随机数 + String nonce = object.getString("nonce"); + //时间戳 + Integer timestamp = object.getInteger("timestamp"); + //签名 + String msgSignature = object.getString("msg_signature"); + //订单信息的json字符串 + String message = object.getString("msg"); + JSONObject msgJsonObj = JSON.parseObject(message); + //校验回调签名 + String signMessage = dyPayUtil.getCallbackSignature(timestamp, nonce, message,token); + //签名校验 + if (msgSignature.equals(signMessage)) { + //固定值SUCCESS + String status = msgJsonObj.getString("status"); + //开发者侧的订单号 + String outOrderNo = msgJsonObj.getString("cp_orderno"); + //支付渠道: 1-微信支付,2-支付宝支付,10-抖音支付 + String payChannel = msgJsonObj.getString("way"); + //支付金额,单位为分 + Integer totalAmount = msgJsonObj.getInteger("total_amount"); + //支付渠道侧PC单号,支付页面可见(微信支付宝侧的订单号) + String paymentOrderNo = msgJsonObj.getString("payment_order_no"); + //抖音侧订单号 + String dyOrderId = msgJsonObj.getString("order_id"); + //这里无论回调失败还是成功,都需要都各个业务层去处理相关逻辑 + if("success".equals(status)){ + //处理业务 + PayNotifyCheckDto payNotifyCheckDto = new PayNotifyCheckDto(); + payNotifyCheckDto.setPayType(EnumPayType.DY_PAY); + payNotifyCheckDto.setPayAmount(BaseWxPayResult.fenToYuan(totalAmount)); + payNotifyCheckDto.setPayId(outOrderNo); + payNotifyCheckDto.setMsg("success"); + String notifyCheck = super.payNotifyCheck(payNotifyCheckDto); + if (StringUtils.isNotBlank(notifyCheck)){ + return notifyCheck; + } + log.info("抖音支付回调,交易正常:封装参数修改内部支付单信息"); + EditPayInfoNotifyDto editPayInfoNotifyDto = new EditPayInfoNotifyDto(); + editPayInfoNotifyDto.setPayAmount(BaseWxPayResult.fenToYuan(totalAmount)); + editPayInfoNotifyDto.setPayTime(DateUtil.formatDateTime(new Date())); + editPayInfoNotifyDto.setAppId(appId); + editPayInfoNotifyDto.setPayId(outOrderNo); + editPayInfoNotifyDto.setPayNo(dyOrderId); + editPayInfoNotifyDto.setTradeNo(paymentOrderNo); + editPayInfoNotifyDto.setPayChannel(payChannel); + payInfoService.editPayInfoNotify(editPayInfoNotifyDto); + //正确处理后返回以下内容格式通知小程序平台不再持续回调 + return super.buildNotifyCheckResultMsg(EnumPayType.DY_PAY,true,"success"); + }else { + log.info("抖音支付回调不是支付成功不做处理:支付状态:{},payId:{}",status,outOrderNo); + } + } else { + log.info("抖音支付回调签名校验失败,payId:{}",msgJsonObj.getString("cp_orderno")); + } + return super.buildNotifyCheckResultMsg(EnumPayType.DY_PAY,true,"business fail"); + } +} 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 new file mode 100644 index 0000000..c0ed602 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/KSPayStrategy.java @@ -0,0 +1,214 @@ +package com.bnyer.pay.design.strategy; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.bnyer.common.core.context.SecurityContextHolder; +import com.bnyer.common.core.domain.KspayConfig; +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.constant.KSPayConstants; +import com.bnyer.pay.dto.EditPayInfoNotifyDto; +import com.bnyer.pay.dto.PayNotifyCheckDto; +import com.bnyer.pay.dto.UnifiedOrderDto; +import com.bnyer.pay.enums.EnumPayConfigStatus; +import com.bnyer.pay.mapper.KspayConfigMapper; +import com.bnyer.pay.service.PayInfoService; +import com.bnyer.pay.utils.KSPayUtil; +import com.bnyer.pay.utils.PayRestTemplateUtil; +import com.bnyer.pay.vo.PayInOrderVo; +import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest; +import com.github.binarywang.wxpay.bean.result.BaseWxPayResult; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +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; + +/** + * @author :WXC + * @Date :2023/04/23 + * @description : + */ +@Slf4j +@Component +public class KSPayStrategy extends AbstractPayStrategy{ + + @Autowired + private KSPayUtil ksPayUtil; + + @Autowired + private PayRestTemplateUtil payRestTemplateUtil; + + @Autowired + private PayInfoService payInfoService; + + @Autowired + private KspayConfigMapper kspayConfigMapper; + + @Override + public PayInOrderVo unifiedOrder(UnifiedOrderDto dto) { + try { + List kspayConfigList = kspayConfigMapper.selectList(new LambdaQueryWrapper().eq(KspayConfig::getStatus, EnumPayConfigStatus.ENABLE.getCode())); + if (CollUtil.isEmpty(kspayConfigList)){ + throw new ServiceException(ResponseEnum.PAY_CONFIG_ERROR); + } + KspayConfig kspayConfig = kspayConfigList.get(0); + String openId = SecurityContextHolder.get("ksCode"); + String appId = kspayConfig.getAppid(); + String backurl = kspayConfig.getBackurl(); + String secret = kspayConfig.getSecret(); + //加签验签的参数需要排序 + Map params = new TreeMap<>(); + //小程序APPID + params.put("app_id", appId); + //开发者侧的订单号。需保证同一小程序下不可重复 + params.put("out_order_no", dto.getPayId()); + //快手用户在当前小程序的open_id,可通过login操作获取 + params.put("open_id", openId); + //用户支付金额,单位为[分]。不允许传非整数的数值。 + params.put("total_amount", BaseWxPayRequest.yuanToFen(dto.getPayAmount())); + //商品描述。 + params.put("subject", dto.getGoodsSubject()); + //商品详情 + params.put("detail", dto.getGoodsDesc()); + //商品类型,不同商品类目的编号见 担保支付商品类目编号 + params.put("type", dto.getGoodsType()); + //订单过期时间,单位秒,300s - 172800s + params.put("expire_time", KSPayConstants.expireTime); + //开发者自定义字段,回调原样回传。超过最大长度会被截断 +// params.put("attach", "支付测试"); + //通知地址 + params.put("notify_url", backurl); + String sign = ksPayUtil.calcSign(params, secret); + + JSONObject payJson = new JSONObject(); + payJson.put("out_order_no", dto.getPayId()); + payJson.put("open_id", openId); + payJson.put("total_amount", BaseWxPayRequest.yuanToFen(dto.getPayAmount())); + payJson.put("subject", dto.getGoodsSubject()); + payJson.put("detail", dto.getGoodsDesc()); + payJson.put("type", dto.getGoodsType()); + payJson.put("expire_time", 1800); + payJson.put("sign", sign); +// payJson.put("attach", "测试支付"); + payJson.put("notify_url", dto.getCurrDate()); + log.info("请求参数{}", payJson); + //预下单接口 + String ksPayAccessToken = payRestTemplateUtil.ksPostRequestUrlencoded(kspayConfig); + String result = payRestTemplateUtil.ksPostRequestJson(payJson, KSPayConstants.CREATE_ORDER, appId, ksPayAccessToken); + log.info("=================================="); + log.info("快手预下单result{}", result); + log.info("=================================="); + if (!"".equals(result)) { + JSONObject jsonObject = JSONObject.parseObject(result); + String resultCode = jsonObject.getString("result"); + if ("1".equals(resultCode)) { + JSONObject data = jsonObject.getJSONObject("order_info"); + String orderNo = data.getString("order_no"); + String orderToken = data.getString("order_info_token"); + if (null != orderToken && null != orderNo) { + //保存预下单信息 + PayInOrderVo payInOrderVo = new PayInOrderVo(); + //把order_no和order_info_token返回前端用于调起收银台 + payInOrderVo.setPayId(dto.getPayId()); + payInOrderVo.setPayOrderId(orderNo); + payInOrderVo.setOrderToken(orderToken); + return payInOrderVo; + } else { + return null; + } + } else { + log.error("快手支付:统一下单接口调用失败,payId:{},error{}", dto.getPayId(), jsonObject.getString("error_msg")); + throw new ServiceException(ResponseEnum.PAY_FAILS); + } + } else { + log.error("快手支付:统一下单接口调用失败,payId:{},error{}", dto.getPayId(), "请求结果返回为空"); + throw new ServiceException(ResponseEnum.PAY_FAILS); + } + } catch (Exception e) { + e.printStackTrace(); + log.error("快手支付:支付异常,payId:{},error{}", dto.getPayId(), e.getMessage()); + throw new ServiceException(ResponseEnum.PAY_FAILS); + } + } + + @Override + public String parsePayNotify(String params) { + List kspayConfigList = kspayConfigMapper.selectList(new LambdaQueryWrapper().eq(KspayConfig::getStatus, EnumPayConfigStatus.ENABLE.getCode())); + if (CollUtil.isEmpty(kspayConfigList)){ + throw new ServiceException(ResponseEnum.PAY_CONFIG_ERROR); + } + KspayConfig kspayConfig = kspayConfigList.get(0); + String appId = kspayConfig.getAppid(); + String secret = kspayConfig.getSecret(); + JSONObject jsonObject = JSONObject.parseObject(params); + JSONObject object = jsonObject.getJSONObject("data"); + String kwaisign = jsonObject.getString("sign"); + String jsonString = JSONObject.toJSONString(object, SerializerFeature.WRITE_MAP_NULL_FEATURES, SerializerFeature.QuoteFieldNames); + log.info("jsonString:" + jsonString); + if (null != kwaisign) { + jsonString = jsonString + secret; + //签名校验 + if (kwaisign.equals(DigestUtils.md5Hex(jsonString))) { + //当前回调消息的唯一ID,在同一个消息多次通知时,保持一致。 + String messageId = object.getString("message_id"); + JSONObject data = object.getJSONObject("data"); + //支付渠道。取值:UNKNOWN - 未知|WECHAT-微信|ALIPAY-支付宝 + String channel = data.getString("channel"); + //订单支付状态。 取值: PROCESSING-处理中|SUCCESS-成功|FAILED-失败 + String status = data.getString("status"); + //订单金额 + Integer orderAmount = data.getInteger("order_amount"); + //用户侧支付页交易单号 + String tradeNo = data.getString("trade_no"); + //商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 + String outOrderNo = data.getString("out_order_no"); + //快手小程序平台订单号。 + String ksOrderNo = data.getString("ks_order_no"); + //回调支付成功 + if ("SUCCESS".equals(status)) { + //处理业务 + PayNotifyCheckDto payNotifyCheckDto = new PayNotifyCheckDto(); + payNotifyCheckDto.setPayType(EnumPayType.KS_PAY); + payNotifyCheckDto.setPayAmount(BaseWxPayResult.fenToYuan(orderAmount)); + payNotifyCheckDto.setPayId(outOrderNo); + payNotifyCheckDto.setMsg(messageId); + String notifyCheck = super.payNotifyCheck(payNotifyCheckDto); + if (StringUtils.isNotBlank(notifyCheck)){ + return notifyCheck; + } + log.info("快手支付回调,交易正常:封装参数修改内部支付单信息"); + EditPayInfoNotifyDto editPayInfoNotifyDto = new EditPayInfoNotifyDto(); + editPayInfoNotifyDto.setPayAmount(BaseWxPayResult.fenToYuan(orderAmount)); + editPayInfoNotifyDto.setPayTime(DateUtil.formatDateTime(new Date())); + editPayInfoNotifyDto.setAppId(appId); + editPayInfoNotifyDto.setPayId(outOrderNo); + editPayInfoNotifyDto.setPayNo(ksOrderNo); + editPayInfoNotifyDto.setTradeNo(tradeNo); + editPayInfoNotifyDto.setPayChannel(channel); + payInfoService.editPayInfoNotify(editPayInfoNotifyDto); + //正确处理后返回以下内容格式通知小程序平台不再持续回调 + return super.buildNotifyCheckResultMsg(EnumPayType.KS_PAY,true,messageId); + }else { + log.info("快手支付回调不是支付成功不做处理:支付状态:{},payId:{}",status,object.getString("trade_no")); + } + } else { + log.info("快手支付回调签名校验失败,payId:{}",object.getString("trade_no")); + } + } else { + log.info("快手支付回调参数丢失,payId:{}",object.getString("trade_no")); + } + return super.buildNotifyCheckResultMsg(EnumPayType.KS_PAY,true,"business fail"); + } +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/WxPayStrategy.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/WxPayStrategy.java index 0b206bb..21780c5 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/WxPayStrategy.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/design/strategy/WxPayStrategy.java @@ -6,6 +6,7 @@ import com.alibaba.fastjson.TypeReference; import com.alipay.api.internal.util.AlipaySignature; import com.alipay.api.msg.MsgConstants; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.bnyer.common.core.context.SecurityContextHolder; import com.bnyer.common.core.domain.AlipayConfig; import com.bnyer.common.core.domain.WxpayConfig; import com.bnyer.common.core.enums.EnumPayType; @@ -53,6 +54,9 @@ public class WxPayStrategy extends AbstractPayStrategy { @Autowired private WxPayManager wxPayManager; + @Autowired + private PayInfoService payInfoService; + @Override public PayInOrderVo unifiedOrder(UnifiedOrderDto dto) { log.info("微信支付:统一下单接口调用开始,WxPayStrategy.unifiedOrder dto:{}",JSON.toJSONString(dto)); @@ -78,10 +82,11 @@ public class WxPayStrategy extends AbstractPayStrategy { private PayInOrderVo jsApiPay(UnifiedOrderDto dto) { WxpayConfig wxPayConfig = wxPayManager.getWxPayConfigByTradeType(dto.getTradeType()); WxPayService wxPayService = wxPayManager.getWxPayService(wxPayConfig); + String openId = SecurityContextHolder.get("wxCode"); try { WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest(); - orderRequest.setOpenid(dto.getOpenId()); - orderRequest.setOutTradeNo(dto.getOrderId()); + orderRequest.setOpenid(openId); + orderRequest.setOutTradeNo(dto.getPayId()); orderRequest.setBody(dto.getGoodsDesc()); orderRequest.setSpbillCreateIp(dto.getIp()); orderRequest.setTotalFee(BaseWxPayRequest.yuanToFen(dto.getPayAmount())); @@ -93,7 +98,7 @@ public class WxPayStrategy extends AbstractPayStrategy { } //返回数据 PayInOrderVo payInOrderVo = new PayInOrderVo(); - payInOrderVo.setOrderId(dto.getOrderId()); + payInOrderVo.setPayId(dto.getPayId()); payInOrderVo.setAppid(wxPayConfig.getAppid()); payInOrderVo.setMchid(wxPayConfig.getMchid()); payInOrderVo.setPrepayid(wxPayUnifiedOrderResult.getPrepayId()); @@ -103,7 +108,7 @@ public class WxPayStrategy extends AbstractPayStrategy { payInOrderVo.setTradeType(wxPayUnifiedOrderResult.getTradeType()); return payInOrderVo; } catch (WxPayException e) { - log.error("微信支付:统一下单接口调用失败,orderId:{},error{}", dto.getOrderId(), e.getMessage()); + log.error("微信支付:统一下单接口调用失败,payId:{},error{}", dto.getPayId(), e.getMessage()); throw new ServiceException(ResponseEnum.PAY_FAILS); } } @@ -116,7 +121,6 @@ public class WxPayStrategy extends AbstractPayStrategy { @Override public String parsePayNotify(String params) { log.info("微信支付回调开始: WxPayStrategy.parsePayNotify params:{}",params); - PayInfoService payInfoService = SpringUtils.getBean(PayInfoService.class); Map inMap = null; try { inMap = WXPayUtil.xmlToMap(params); @@ -146,6 +150,7 @@ public class WxPayStrategy extends AbstractPayStrategy { payNotifyCheckDto.setPayType(EnumPayType.WX_PAY); payNotifyCheckDto.setPayId(outTradeNo); payNotifyCheckDto.setPayAmount(fenToYuan); + payNotifyCheckDto.setMsg("OK"); String notifyCheck = super.payNotifyCheck(payNotifyCheckDto); if (StringUtils.isNotBlank(notifyCheck)){ return notifyCheck; @@ -158,10 +163,10 @@ public class WxPayStrategy extends AbstractPayStrategy { editPayInfoNotifyDto.setPayId(outTradeNo); editPayInfoNotifyDto.setPayNo(transactionId); payInfoService.editPayInfoNotify(editPayInfoNotifyDto); - return WxPayNotifyResponse.success("OK"); + return super.buildNotifyCheckResultMsg(EnumPayType.WX_PAY,true,"OK"); } catch (Exception e) { log.error("微信支付回调结果异常,异常原因{}", e.getMessage()); - return WxPayNotifyResponse.fail(e.getMessage()); + return super.buildNotifyCheckResultMsg(EnumPayType.WX_PAY,false,e.getMessage()); } } } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/dto/EditPayInfoSingleDto.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/dto/EditPayInfoSingleDto.java new file mode 100644 index 0000000..0008067 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/dto/EditPayInfoSingleDto.java @@ -0,0 +1,37 @@ +package com.bnyer.pay.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.ibatis.annotations.Param; + +/** + * @author :WXC + * @Date :2023/04/24 + * @description : + */ +@Getter +@Setter +@NoArgsConstructor +public class EditPayInfoSingleDto { + /** + * 支付单号:商户侧 + */ + private String payId; + /** + * 支付单号:第三方返回 + */ + private String payNo; + /** + * 快手用户侧订单号/抖音支付宝微信侧单号:第三方返回 + */ + private String tradeNo; + /** + * 支付时间 + */ + private String payTime; + /** + * 支付渠道 + */ + private String payChannel; +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/dto/PayNotifyCheckDto.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/dto/PayNotifyCheckDto.java index 5f0b28f..c65a65f 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/dto/PayNotifyCheckDto.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/dto/PayNotifyCheckDto.java @@ -29,4 +29,9 @@ public class PayNotifyCheckDto { * 支付金额 */ private String payAmount; + + /** + * 消息 + */ + private String msg; } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/dto/UnifiedOrderDto.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/dto/UnifiedOrderDto.java index 53e1dc2..82865fc 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/dto/UnifiedOrderDto.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/dto/UnifiedOrderDto.java @@ -17,9 +17,9 @@ import java.util.Date; public class UnifiedOrderDto { /** - * 业务主订单id:关联内部业务订单表 + * 内部支付单号 */ - private String orderId; + private String payId; /** * trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。 */ @@ -40,6 +40,10 @@ public class UnifiedOrderDto { * 交易类型 1--JSAPI支付(小程序appId支付)、2--Native支付、3--app支付,4--JSAPI支付(公众号appId支付) 6-H5支付 ,必要参数 */ private String tradeType; + /** + * 商品类型:快手、抖音支付需要 + */ + private Integer goodsType; /** * 商品标题:会员充值 */ diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/mapper/DypayConfigMapper.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/mapper/DypayConfigMapper.java new file mode 100644 index 0000000..4c28dc9 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/mapper/DypayConfigMapper.java @@ -0,0 +1,13 @@ +package com.bnyer.pay.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bnyer.common.core.domain.DypayConfig; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author :WXC + * @description : + */ +@Mapper +public interface DypayConfigMapper extends BaseMapper { +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/mapper/KspayConfigMapper.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/mapper/KspayConfigMapper.java new file mode 100644 index 0000000..81ac1cc --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/mapper/KspayConfigMapper.java @@ -0,0 +1,13 @@ +package com.bnyer.pay.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bnyer.common.core.domain.KspayConfig; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author :WXC + * @description : + */ +@Mapper +public interface KspayConfigMapper extends BaseMapper { +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/mapper/PayInfoMapper.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/mapper/PayInfoMapper.java index 9f1b3c9..07257fc 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/mapper/PayInfoMapper.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/mapper/PayInfoMapper.java @@ -2,6 +2,7 @@ package com.bnyer.pay.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.bnyer.common.core.domain.PayInfo; +import com.bnyer.pay.dto.EditPayInfoSingleDto; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -13,9 +14,7 @@ import org.apache.ibatis.annotations.Param; public interface PayInfoMapper extends BaseMapper { /** * 修改支付订单状态和对账信息 - * @param payId - * @param payNo * @return */ - int editPayInfoForSingle(@Param("payId") String payId, @Param("payTime") String payTime, @Param("payNo") String payNo); + int editPayInfoForSingle(EditPayInfoSingleDto dto); } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/DypayConfigService.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/DypayConfigService.java new file mode 100644 index 0000000..591d5d6 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/DypayConfigService.java @@ -0,0 +1,12 @@ +package com.bnyer.pay.service; + +import com.bnyer.common.core.domain.DypayConfig; +import com.baomidou.mybatisplus.extension.service.IService; + /** + * @author :WXC + * @description : + */ +public interface DypayConfigService extends IService{ + + +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/KspayConfigService.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/KspayConfigService.java new file mode 100644 index 0000000..7d5ecbe --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/KspayConfigService.java @@ -0,0 +1,12 @@ +package com.bnyer.pay.service; + +import com.bnyer.common.core.domain.KspayConfig; +import com.baomidou.mybatisplus.extension.service.IService; + /** + * @author :WXC + * @description : + */ +public interface KspayConfigService extends IService{ + + +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/DypayConfigServiceImpl.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/DypayConfigServiceImpl.java new file mode 100644 index 0000000..01bafed --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/DypayConfigServiceImpl.java @@ -0,0 +1,15 @@ +package com.bnyer.pay.service.impl; + +import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bnyer.common.core.domain.DypayConfig; +import com.bnyer.pay.mapper.DypayConfigMapper; +import com.bnyer.pay.service.DypayConfigService; +/** + * @author :WXC + * @description : + */ +@Service +public class DypayConfigServiceImpl extends ServiceImpl implements DypayConfigService { + +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/KspayConfigServiceImpl.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/KspayConfigServiceImpl.java new file mode 100644 index 0000000..32188dc --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/KspayConfigServiceImpl.java @@ -0,0 +1,15 @@ +package com.bnyer.pay.service.impl; + +import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bnyer.common.core.domain.KspayConfig; +import com.bnyer.pay.mapper.KspayConfigMapper; +import com.bnyer.pay.service.KspayConfigService; +/** + * @author :WXC + * @description : + */ +@Service +public class KspayConfigServiceImpl extends ServiceImpl implements KspayConfigService { + +} 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 51dcc6b..8ca763b 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 @@ -8,18 +8,22 @@ import com.bnyer.common.core.context.SecurityContextHolder; import com.bnyer.common.core.domain.PayInfo; import com.bnyer.common.core.domain.R; import com.bnyer.common.core.enums.EnumSceneCode; +import com.bnyer.common.core.enums.EnumUserClientType; import com.bnyer.common.core.enums.ResponseEnum; import com.bnyer.common.core.exception.ServiceException; +import com.bnyer.common.core.utils.OrderUtil; import com.bnyer.common.core.utils.bean.EntityConvertUtil; import com.bnyer.common.core.utils.ip.IpUtils; import com.bnyer.common.rocketmq.config.RocketMqConstant; import com.bnyer.img.api.dto.QueryVipOrderDto; import com.bnyer.img.api.remote.RemoteVipOrderService; import com.bnyer.img.api.vo.VipOrderVo; +import com.bnyer.pay.constant.KSPayConstants; import com.bnyer.pay.design.factory.PayFactory; import com.bnyer.pay.design.strategy.IPayStrategy; import com.bnyer.pay.dto.AddPayInfoDto; import com.bnyer.pay.dto.EditPayInfoNotifyDto; +import com.bnyer.pay.dto.EditPayInfoSingleDto; import com.bnyer.pay.dto.UnifiedOrderDto; import com.bnyer.pay.mapper.PayInfoMapper; import com.bnyer.pay.service.PayInfoService; @@ -67,6 +71,10 @@ public class PayInfoServiceImpl extends ServiceImpl impl public PayInOrderVo addPayInOrder(AddPayInfoDto dto, HttpServletRequest request) { //支付金额 String payAmount = ""; + //商品类型:快手支付需要 + int goodsType; + //payId + String payId; EnumSceneCode enumSceneCode = EnumSceneCode.getSceneCodeByCode(dto.getSceneCode()); switch (enumSceneCode){ //会员充值场景 @@ -80,12 +88,15 @@ public class PayInfoServiceImpl extends ServiceImpl impl } VipOrderVo vipOrderVo = vipOrderVoListR.getData().get(0); payAmount = vipOrderVo.getPayAmount().toString(); + goodsType = KSPayConstants.GOODS_TYPE_VIP; + payId = OrderUtil.getOrderId("RV",new Date(), EnumUserClientType.getCodeByType(vipOrderVo.getUserClientType()) + ,String.valueOf(vipOrderVo.getUserId())); break; default: throw new ServiceException("sceneCode未匹配上对应支付场景"); } //构建统一下单请求实体 - UnifiedOrderDto unifiedOrderDto = buildUnifiedOrderDto(dto, payAmount, request); + UnifiedOrderDto unifiedOrderDto = buildUnifiedOrderDto(dto, goodsType,payAmount,payId, request); //下单,获取第三方返回信息 IPayStrategy IPayStrategy = PayFactory.getInstance().getConcreteStrategy(dto.getPayType()); PayInOrderVo payInOrderVo = IPayStrategy.unifiedOrder(unifiedOrderDto); @@ -105,12 +116,11 @@ public class PayInfoServiceImpl extends ServiceImpl impl */ private PayInfo buildPayInfo(PayInOrderVo payInOrderVo, UnifiedOrderDto unifiedOrderDto, AddPayInfoDto dto) { PayInfo payInfo = new PayInfo(); - payInfo.setPayId(payInOrderVo.getOrderId()); payInfo.setOrderId(dto.getOrderId()); payInfo.setSceneCode(dto.getSceneCode()); payInfo.setRemark(dto.getRemark()); payInfo.setPayType(dto.getPayType()); - payInfo.setPayId(unifiedOrderDto.getOrderId()); + payInfo.setPayId(unifiedOrderDto.getPayId()); payInfo.setPayAmount(new BigDecimal(unifiedOrderDto.getPayAmount())); payInfo.setAppid(payInOrderVo.getAppid()); payInfo.setGoodsSubject(unifiedOrderDto.getGoodsSubject()); @@ -124,22 +134,25 @@ public class PayInfoServiceImpl extends ServiceImpl impl /** * 构建统一下单请求实体 - * @param dto 入参 + * + * @param dto 入参 + * @param goodsType * @param payAmount 不同支付场景下的支付金额 - * @param request 请求request用于获取ip地址 + * @param payId 内部系统支付单号 + * @param request 请求request用于获取ip地址 * @return */ - private UnifiedOrderDto buildUnifiedOrderDto(AddPayInfoDto dto, String payAmount, HttpServletRequest request) { + private UnifiedOrderDto buildUnifiedOrderDto(AddPayInfoDto dto, int goodsType, String payAmount, String payId, HttpServletRequest request) { //当前时间 Date currDate = new Date(); //ip地址 String ip = IpUtils.getIpAddr(request); - String wxCode = SecurityContextHolder.get("wxCode"); UnifiedOrderDto unifiedOrderDto = EntityConvertUtil.copy(dto, UnifiedOrderDto.class); + unifiedOrderDto.setPayId(payId); unifiedOrderDto.setIp(ip); unifiedOrderDto.setCurrDate(currDate); unifiedOrderDto.setPayAmount(payAmount); - unifiedOrderDto.setOpenId(wxCode); + unifiedOrderDto.setGoodsType(goodsType); return unifiedOrderDto; } @@ -147,21 +160,20 @@ public class PayInfoServiceImpl extends ServiceImpl impl * 修改支付单、并发消息到订单系统修改订单状态 * @param editPayInfoNotifyDto */ - @Transactional(propagation = Propagation.REQUIRED,rollbackFor=Exception.class) + @Transactional(rollbackFor=Exception.class) @Override public void editPayInfoNotify(EditPayInfoNotifyDto editPayInfoNotifyDto) { log.info("开始修改内部支付订单:回调入参, editPayInfoNotifyDto:{}",JSON.toJSONString(editPayInfoNotifyDto)); String payId = editPayInfoNotifyDto.getPayId(); - String payNo = editPayInfoNotifyDto.getPayNo(); - String payTime = editPayInfoNotifyDto.getPayTime(); PayInfo payInfo = payInfoMapper.selectOne(new LambdaQueryWrapper().eq(PayInfo::getPayId, payId)); if (Objects.isNull(payInfo)){ log.error("查询支付订单数据返回失败"); throw new ServiceException("查询支付订单数据返回失败"); } - int i = payInfoMapper.editPayInfoForSingle(payId,payTime,payNo); + //修改支付单信息 + EditPayInfoSingleDto editPayInfoSingleDto = buildEditPayInfoSingleDto(editPayInfoNotifyDto); + int i = payInfoMapper.editPayInfoForSingle(editPayInfoSingleDto); if(i==0){ - TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); log.error("内部支付订单修改失败,payId:{}",payId); throw new ServiceException("内部支付订单修改失败"); } @@ -197,4 +209,17 @@ public class PayInfoServiceImpl extends ServiceImpl impl break; } } + + /** + * 构建修改支付单信息实体 + * @param editPayInfoNotifyDto + * @return + */ + private EditPayInfoSingleDto buildEditPayInfoSingleDto(EditPayInfoNotifyDto editPayInfoNotifyDto) { + EditPayInfoSingleDto editPayInfoSingleDto = new EditPayInfoSingleDto(); + editPayInfoSingleDto.setPayId(editPayInfoNotifyDto.getPayId()); + editPayInfoSingleDto.setPayTime(editPayInfoNotifyDto.getPayTime()); + editPayInfoSingleDto.setPayChannel(editPayInfoSingleDto.getPayChannel()); + return editPayInfoSingleDto; + } } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/DYPayUtil.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/DYPayUtil.java new file mode 100644 index 0000000..f164be4 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/DYPayUtil.java @@ -0,0 +1,138 @@ +package com.bnyer.pay.utils; + +import org.springframework.stereotype.Component; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; + +/** + * @author :WXC + * @Date :2023/04/23 + * @description :抖音支付工具类 + */ +@Component +public class DYPayUtil { + /** + * 发起请求时的签名 + */ + public String getSign(Map paramsMap,String salt) { + List paramsArr = new ArrayList<>(); + for (Map.Entry entry : paramsMap.entrySet()) { + String key = entry.getKey(); + if (key.equals("other_settle_params")) { + continue; + } + String value = entry.getValue().toString(); + + value = value.trim(); + if (value.startsWith("\"") && value.endsWith("\"") && value.length() > 1) { + value = value.substring(1, value.length() - 1); + } + value = value.trim(); + if (value.equals("") || value.equals("null")) { + continue; + } + switch (key) { + // 字段用于标识身份,不参与签名 + case "app_id": + case "thirdparty_id": + case "sign": + break; + default: + paramsArr.add(value); + break; + } + } + // 支付密钥值 + paramsArr.add(salt); + Collections.sort(paramsArr); + StringBuilder signStr = new StringBuilder(); + String sep = ""; + for (String s : paramsArr) { + signStr.append(sep).append(s); + sep = "&"; + } + return md5FromStr(signStr.toString()); + } + + public String md5FromStr(String inStr) { + MessageDigest md5; + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return ""; + } + + byte[] byteArray = inStr.getBytes(StandardCharsets.UTF_8); + byte[] md5Bytes = md5.digest(byteArray); + StringBuilder hexValue = new StringBuilder(); + for (byte md5Byte : md5Bytes) { + int val = ((int) md5Byte) & 0xff; + if (val < 16) { + hexValue.append("0"); + } + hexValue.append(Integer.toHexString(val)); + } + return hexValue.toString(); + } + + /** + * 回调验证签名 + */ + public String getCallbackSignature(int timestamp, String nonce, String msg,String token) { + List sortedString = new ArrayList<>(); + sortedString.add(String.valueOf(timestamp)); + sortedString.add(nonce); + sortedString.add(msg); + sortedString.add(token); + Collections.sort(sortedString); + StringBuilder sb = new StringBuilder(); + sortedString.forEach(sb::append); + return getSha1(sb.toString().getBytes()); + } + + public String getSha1(byte[] input) { + MessageDigest mDigest; + try { + mDigest = MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return ""; + } + byte[] result = mDigest.digest(input); + StringBuilder sb = new StringBuilder(); + for (byte b : result) { + sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1)); + } + return sb.toString(); + } + + /** + * 手机号登陆信息解密 + */ + public String decrypt(String encryptedData, String sessionKey, String iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException { + Base64.Decoder decoder = Base64.getDecoder(); + byte[] sessionKeyBytes = decoder.decode(sessionKey); + byte[] ivBytes = decoder.decode(iv); + byte[] encryptedBytes = decoder.decode(encryptedData); + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + SecretKeySpec skeySpec = new SecretKeySpec(sessionKeyBytes, "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); + cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec); + byte[] ret = cipher.doFinal(encryptedBytes); + return new String(ret); + } +} + diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/KSPayUtil.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/KSPayUtil.java new file mode 100644 index 0000000..4d694d2 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/KSPayUtil.java @@ -0,0 +1,133 @@ +package com.bnyer.pay.utils; + +import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import static javax.crypto.Cipher.DECRYPT_MODE; + +/** + * @author :WXC + * @Date :2023/04/23 + * @description :快手支付工具类 + */ +@Slf4j +@Component +public class KSPayUtil { + + /** + * 快手小程序返回的加密数据的解密函数 + * + * @param sessionKey 有效的sessionKey,通过 login code 置换 + * @param encryptedData 返回的加密数据(base64编码) + * @param iv 返回的加密IV(base64编码) + * @return 返回解密的字符串数据 + */ + public String decrypt(String sessionKey, String encryptedData, String iv) { + // Base64解码数据 + byte[] aesKey = Base64.decodeBase64(sessionKey); + byte[] ivBytes = Base64.decodeBase64(iv); + byte[] cipherBytes = Base64.decodeBase64(encryptedData); + + byte[] plainBytes = decrypt0(aesKey, ivBytes, cipherBytes); + + return new String(plainBytes, StandardCharsets.UTF_8); + } + + /** + * AES解密函数. 使用 AES/CBC/PKCS5Padding 模式 + * + * @param aesKey 密钥,长度16 + * @param iv 偏移量,长度16 + * @param cipherBytes 密文信息 + * @return 明文 + */ + private byte[] decrypt0(byte[] aesKey, byte[] iv, byte[] cipherBytes) { + SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + try { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(DECRYPT_MODE, keySpec, ivSpec); + return cipher.doFinal(cipherBytes); + } catch (Exception e) { + log.error("decrypt error:{}", e.getMessage()); + throw new RuntimeException(e); + } + } + + /** + * https://mp.kuaishou.com/docs/develop/server/epay/interfaceDefinition.html + *

+ * https://mp.kuaishou.com/docs/develop/server/payment/serverSignature.html + * 支付签名 + */ + + public String buildMd5(Map dataMap, String appSecret) { + String signStr = genSignStr(dataMap); + return DigestUtils.md5Hex(signStr + appSecret); + } + + private String genSignStr(Map data) { + StringBuilder sb = new StringBuilder(); + data.keySet().stream().sorted() + .filter(key -> StringUtils.isNotBlank(key) && StringUtils.isNotEmpty(data.get(key))) + .forEach(key -> { + sb.append(key); + sb.append("="); + sb.append(data.get(key)); + sb.append("&"); + }); + if (sb.length() > 0) { + sb.deleteCharAt(sb.length() - 1); + } + + return sb.toString(); + } + + + /** + * 获取参数 Map 的签名结果 + * https://mp.kuaishou.com/docs/develop/server/epay/appendix.html + * + * @param signParamsMap 含义见上述示例 + * @return 返回签名结果 + */ + public String calcSign(Map signParamsMap, String secret) { + // 去掉 value 为空的 + Map trimmedParamMap = signParamsMap.entrySet() + .stream() + .filter(item -> !Strings.isNullOrEmpty(item.getValue().toString())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + // 按照字母排序 + Map sortedParamMap = trimmedParamMap.entrySet() + .stream() + .sorted(Map.Entry.comparingByKey()) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (oldValue, newValue) -> oldValue, LinkedHashMap::new)); + + // 组装成待签名字符串。(注,引用了guava工具) + String paramStr = Joiner.on("&").withKeyValueSeparator("=").join(sortedParamMap.entrySet()); + String signStr = paramStr + secret; + + // 生成签名返回。(注,引用了commons-codec工具) + return DigestUtils.md5Hex(signStr); + } +} + 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 new file mode 100644 index 0000000..2022f05 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/utils/PayRestTemplateUtil.java @@ -0,0 +1,107 @@ +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; + } +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/vo/PayInOrderVo.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/vo/PayInOrderVo.java index 5c7ca59..fc1a2ea 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/vo/PayInOrderVo.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/vo/PayInOrderVo.java @@ -39,9 +39,12 @@ public class PayInOrderVo { @ApiModelProperty(value = "支付宝") private String outStr; - @ApiModelProperty(value = "订单号") - private String orderId; + @ApiModelProperty(value = "内部系统支付单号") + private String payId; - @ApiModelProperty(value = "调微信支付接口地址") - private String mwebUrl; + @ApiModelProperty(value = "第三方返回支付单号,抖音快手支付需要用到") + private String payOrderId; + + @ApiModelProperty(value = "快手、抖音支付") + private String orderToken; } 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 new file mode 100644 index 0000000..82a431f --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/DypayConfigMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + id, appid,salt, token, backurl, `status`, remark, create_time, update_time + + diff --git a/bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/KspayConfigMapper.xml b/bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/KspayConfigMapper.xml new file mode 100644 index 0000000..d417df9 --- /dev/null +++ b/bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/KspayConfigMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + id, appid, secret, backurl, `status`, remark, create_time, update_time + + diff --git a/bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/PayInfoMapper.xml b/bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/PayInfoMapper.xml index d1ec0e7..7adf47f 100644 --- a/bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/PayInfoMapper.xml +++ b/bnyer-services/bnyer-pay/src/main/resources/com/bnyer/pay/mapper/PayInfoMapper.xml @@ -11,7 +11,9 @@ + + @@ -30,7 +32,7 @@ - id, pay_id, order_id, pay_status,single_status,single_time, pay_type, pay_no, appid, goods_subject, goods_desc, + id, pay_id, order_id, pay_status,single_status,single_time, pay_type, pay_channel, pay_no, trade_no, appid, goods_subject, goods_desc, pay_amount, pay_time, scene_code, ip, third_code, third_msg, third_no, remark, create_time, update_time,sort,is_show @@ -38,9 +40,19 @@ update pay_pay_info + + set pay_no = #{payNo}, + + + set trade_no = #{tradeNo}, + + + set pay_time = STR_TO_DATE(#{payTime},'%Y%m%d%H%i%s'), + + + set pay_channel = #{payChannel}, + set order_status = 1001, - set pay_time = STR_TO_DATE(#{payTime},'%Y%m%d%H%i%s'), - set pay_no = #{payNo} set single_status = 1003, set single_time = now() where pay_id = #{payId}