优化支付模块的代码,增加微信扫码支付的功能
This commit is contained in:
parent
623cdd23b0
commit
cac17af0fe
@ -11,24 +11,31 @@ public enum PayChannelEnum {
|
|||||||
/**
|
/**
|
||||||
* 微信支付
|
* 微信支付
|
||||||
*/
|
*/
|
||||||
WECHAT_JSAPI("WECHAT_JSAPI", "微信支付"),
|
WECHAT_JSAPI("WECHAT_JSAPI", "微信JSAPI支付", "WECHAT"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付宝支付
|
* 微信扫码支付
|
||||||
*/
|
*/
|
||||||
ALIPAY("ALIPAY", "支付宝支付"),
|
WECHAT_NATIVE("WECHAT_NATIVE", "微信扫码支付", "WECHAT"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 学豆支付
|
* 支付宝扫码支付
|
||||||
*/
|
*/
|
||||||
COIN("COIN", "学豆支付");
|
ALIPAY_QR("ALIPAY_QR", "支付宝扫码支付", "ALIPAY"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 钱包支付
|
||||||
|
*/
|
||||||
|
COIN("COIN", "钱包支付", "COIN");
|
||||||
|
|
||||||
private final String code;
|
private final String code;
|
||||||
private final String description;
|
private final String description;
|
||||||
|
private final String type;
|
||||||
|
|
||||||
PayChannelEnum(String code, String description) {
|
PayChannelEnum(String code, String description,String type) {
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -75,4 +75,9 @@ public interface CommonConstant {
|
|||||||
* 启用
|
* 启用
|
||||||
*/
|
*/
|
||||||
Integer ENABLE = 1;
|
Integer ENABLE = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信openId
|
||||||
|
*/
|
||||||
|
String WX_OPEN_ID = "openId";
|
||||||
}
|
}
|
||||||
@ -57,7 +57,9 @@
|
|||||||
|
|
||||||
<!-- 支付集成 -->
|
<!-- 支付集成 -->
|
||||||
<wechatpay-apiv3.version>0.2.17</wechatpay-apiv3.version>
|
<wechatpay-apiv3.version>0.2.17</wechatpay-apiv3.version>
|
||||||
<weixin-java.version>4.7.5.B</weixin-java.version>
|
<weixin-java.version>4.8.1.B</weixin-java.version>
|
||||||
|
<alipay-sdk-java.version>4.35.79.ALL</alipay-sdk-java.version>
|
||||||
|
<com.github.binarywang.version>4.8.1.B</com.github.binarywang.version>
|
||||||
<!-- 编解码 -->
|
<!-- 编解码 -->
|
||||||
<protobuf.version>4.30.2</protobuf.version>
|
<protobuf.version>4.30.2</protobuf.version>
|
||||||
|
|
||||||
@ -84,7 +86,7 @@
|
|||||||
|
|
||||||
<jackson.version>2.17.2</jackson.version>
|
<jackson.version>2.17.2</jackson.version>
|
||||||
<xxl-job.version>3.3.1</xxl-job.version>
|
<xxl-job.version>3.3.1</xxl-job.version>
|
||||||
<com.github.binarywang.version>4.7.5.B</com.github.binarywang.version>
|
|
||||||
|
|
||||||
<jetcache.version>2.7.7</jetcache.version>
|
<jetcache.version>2.7.7</jetcache.version>
|
||||||
<kryo.version>4.0.3</kryo.version>
|
<kryo.version>4.0.3</kryo.version>
|
||||||
@ -298,6 +300,18 @@
|
|||||||
<version>${weixin-java.version}</version>
|
<version>${weixin-java.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alipay.sdk</groupId>
|
||||||
|
<artifactId>alipay-sdk-java</artifactId>
|
||||||
|
<version>${alipay-sdk-java.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.dongliu</groupId>
|
<groupId>net.dongliu</groupId>
|
||||||
<artifactId>apk-parser</artifactId>
|
<artifactId>apk-parser</artifactId>
|
||||||
|
|||||||
@ -171,10 +171,10 @@ public class AppOrderServiceImpl implements IAppOrderService {
|
|||||||
settlementRespVO.setItems(goodRespList);
|
settlementRespVO.setItems(goodRespList);
|
||||||
settlementRespVO.setTotalCount(totalCount);
|
settlementRespVO.setTotalCount(totalCount);
|
||||||
if (PayChannelEnum.COIN.getCode().equalsIgnoreCase(orderSettlementReq.getChannelCode())) {
|
if (PayChannelEnum.COIN.getCode().equalsIgnoreCase(orderSettlementReq.getChannelCode())) {
|
||||||
settlementRespVO.setTotalPrice(totalPrices);
|
|
||||||
} else {
|
|
||||||
CoinToAmountRespDTO coinToAmountRespDTO = accountServiceApi.convertCoinToAmount(String.valueOf(totalPrices));
|
CoinToAmountRespDTO coinToAmountRespDTO = accountServiceApi.convertCoinToAmount(String.valueOf(totalPrices));
|
||||||
settlementRespVO.setTotalPrice(new BigDecimal(coinToAmountRespDTO.getAmount()));
|
settlementRespVO.setTotalPrice(new BigDecimal(coinToAmountRespDTO.getAmount()));
|
||||||
|
} else {
|
||||||
|
settlementRespVO.setTotalPrice(totalPrices);
|
||||||
}
|
}
|
||||||
log.info("订单结算完成,用户使用[{}]支付的总价格: {}, 总数量: {}", orderSettlementReq.getChannelCode(), totalPrices, totalCount);
|
log.info("订单结算完成,用户使用[{}]支付的总价格: {}, 总数量: {}", orderSettlementReq.getChannelCode(), totalPrices, totalCount);
|
||||||
return settlementRespVO;
|
return settlementRespVO;
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
package com.seer.teach.pay.entity;
|
package com.seer.teach.pay.entity;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.seer.teach.common.entity.BaseEntity;
|
import com.seer.teach.common.entity.BaseEntity;
|
||||||
import java.io.Serializable;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@ -22,8 +19,6 @@ import lombok.Setter;
|
|||||||
@TableName("pay_channel")
|
@TableName("pay_channel")
|
||||||
public class PayChannelEntity extends BaseEntity {
|
public class PayChannelEntity extends BaseEntity {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渠道编码(WECHAT,ALIPAY)
|
* 渠道编码(WECHAT,ALIPAY)
|
||||||
*/
|
*/
|
||||||
@ -42,6 +37,18 @@ public class PayChannelEntity extends BaseEntity {
|
|||||||
@TableField("`status`")
|
@TableField("`status`")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* appId
|
||||||
|
*/
|
||||||
|
@TableField("app_id")
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回调域名
|
||||||
|
*/
|
||||||
|
@TableField("notify_domain")
|
||||||
|
private String notifyDomain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付配置(JSON格式)
|
* 支付配置(JSON格式)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import com.seer.teach.common.ResultBean;
|
|||||||
import com.seer.teach.common.annotation.LogPrint;
|
import com.seer.teach.common.annotation.LogPrint;
|
||||||
import com.seer.teach.pay.admin.pay.controller.req.AdminPayChannelSaveReq;
|
import com.seer.teach.pay.admin.pay.controller.req.AdminPayChannelSaveReq;
|
||||||
import com.seer.teach.pay.admin.pay.controller.req.AdminPayChannelUpdateReq;
|
import com.seer.teach.pay.admin.pay.controller.req.AdminPayChannelUpdateReq;
|
||||||
|
import com.seer.teach.pay.admin.pay.controller.resp.PayChannelCodeResp;
|
||||||
import com.seer.teach.pay.admin.pay.controller.resp.PayChannelResp;
|
import com.seer.teach.pay.admin.pay.controller.resp.PayChannelResp;
|
||||||
import com.seer.teach.pay.admin.pay.service.AdminPayChannelService;
|
import com.seer.teach.pay.admin.pay.service.AdminPayChannelService;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@ -25,6 +26,13 @@ public class AdminPayChannelController {
|
|||||||
|
|
||||||
private final AdminPayChannelService payChannelService;
|
private final AdminPayChannelService payChannelService;
|
||||||
|
|
||||||
|
@GetMapping("/code/list")
|
||||||
|
@Operation(summary = "获取支付渠道编码列表")
|
||||||
|
@LogPrint
|
||||||
|
@SaCheckPermission("admin:pay:channel:list")
|
||||||
|
public ResultBean<List<PayChannelCodeResp>> getPayChannelCodeList(){
|
||||||
|
return ResultBean.success(payChannelService.getPayChannelCodeList());
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@Operation(summary = "获取支付渠道列表")
|
@Operation(summary = "获取支付渠道列表")
|
||||||
|
|||||||
@ -32,6 +32,20 @@ public class AdminPayChannelSaveReq {
|
|||||||
@Schema(description = "是否启用(0-禁用,1-启用)", example = "1")
|
@Schema(description = "是否启用(0-禁用,1-启用)", example = "1")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用ID
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "应用ID不能为空")
|
||||||
|
@Schema(description = "应用ID", example = "wx1234567890")
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回调域名
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "回调域名不能为空")
|
||||||
|
@Schema(description = "回调域名", example = "https://www.seer.com")
|
||||||
|
private String notifyDomain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付配置(JSON格式)
|
* 支付配置(JSON格式)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.seer.teach.pay.admin.pay.controller.req;
|
package com.seer.teach.pay.admin.pay.controller.req;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -33,6 +34,20 @@ public class AdminPayChannelUpdateReq {
|
|||||||
@Schema(description = "支付配置(JSON对象)")
|
@Schema(description = "支付配置(JSON对象)")
|
||||||
private Map<String, String> config;
|
private Map<String, String> config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用ID
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "应用ID不能为空")
|
||||||
|
@Schema(description = "应用ID", example = "wx1234567890")
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回调域名
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "回调域名不能为空")
|
||||||
|
@Schema(description = "回调域名", example = "https://www.seer.com")
|
||||||
|
private String notifyDomain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 排序
|
* 排序
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
package com.seer.teach.pay.admin.pay.controller.resp;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@ToString
|
||||||
|
@Schema(description = "支付渠道编码响应类")
|
||||||
|
public class PayChannelCodeResp {
|
||||||
|
|
||||||
|
@Schema(description = "支付渠道编码")
|
||||||
|
private String channelCode;
|
||||||
|
|
||||||
|
@Schema(description = "支付渠道名称")
|
||||||
|
private String channelName;
|
||||||
|
|
||||||
|
@Schema(description = "支付渠道类型")
|
||||||
|
private String type;
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
package com.seer.teach.pay.admin.pay.controller.resp;
|
package com.seer.teach.pay.admin.pay.controller.resp;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -27,6 +26,18 @@ public class PayChannelResp {
|
|||||||
|
|
||||||
@Schema(description = "支付渠道状态,0:禁用, 1:启用")
|
@Schema(description = "支付渠道状态,0:禁用, 1:启用")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
/**
|
||||||
|
* 应用ID
|
||||||
|
*/
|
||||||
|
@Schema(description = "应用ID", example = "wx1234567890")
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回调域名
|
||||||
|
*/
|
||||||
|
@Schema(description = "回调域名", example = "https://www.seer.com")
|
||||||
|
private String notifyDomain;
|
||||||
|
|
||||||
|
|
||||||
@Schema(description = "支付渠道配置")
|
@Schema(description = "支付渠道配置")
|
||||||
private Map<String, String> payInfo;
|
private Map<String, String> payInfo;
|
||||||
|
|||||||
@ -4,10 +4,11 @@ package com.seer.teach.pay.admin.pay.service;
|
|||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.seer.teach.common.enums.ResultCodeEnum;
|
import com.seer.teach.common.enums.ResultCodeEnum;
|
||||||
import com.seer.teach.common.utils.AESUtils;
|
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
||||||
import com.seer.teach.common.utils.AssertUtils;
|
import com.seer.teach.common.utils.AssertUtils;
|
||||||
import com.seer.teach.pay.admin.pay.controller.req.AdminPayChannelSaveReq;
|
import com.seer.teach.pay.admin.pay.controller.req.AdminPayChannelSaveReq;
|
||||||
import com.seer.teach.pay.admin.pay.controller.req.AdminPayChannelUpdateReq;
|
import com.seer.teach.pay.admin.pay.controller.req.AdminPayChannelUpdateReq;
|
||||||
|
import com.seer.teach.pay.admin.pay.controller.resp.PayChannelCodeResp;
|
||||||
import com.seer.teach.pay.admin.pay.controller.resp.PayChannelResp;
|
import com.seer.teach.pay.admin.pay.controller.resp.PayChannelResp;
|
||||||
import com.seer.teach.pay.admin.pay.convert.PayChannelConvert;
|
import com.seer.teach.pay.admin.pay.convert.PayChannelConvert;
|
||||||
import com.seer.teach.pay.admin.pay.util.PayConfigEncryptUtils;
|
import com.seer.teach.pay.admin.pay.util.PayConfigEncryptUtils;
|
||||||
@ -17,9 +18,9 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.net.URLEncoder;
|
import java.util.Arrays;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.util.List;
|
||||||
import java.util.*;
|
import java.util.Map;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -43,9 +44,7 @@ public class AdminPayChannelService {
|
|||||||
* @param params
|
* @param params
|
||||||
*/
|
*/
|
||||||
public void addPayChannel(AdminPayChannelSaveReq params) {
|
public void addPayChannel(AdminPayChannelSaveReq params) {
|
||||||
PayChannelEntity one = payChannelService.getOne(new LambdaQueryWrapper<PayChannelEntity>()
|
PayChannelEntity one = payChannelService.getOne(new LambdaQueryWrapper<PayChannelEntity>().eq(PayChannelEntity::getChannelCode, params.getChannelCode()));
|
||||||
.eq(PayChannelEntity::getChannelCode, params.getChannelCode())
|
|
||||||
);
|
|
||||||
AssertUtils.isNull(one, ResultCodeEnum.PAY_CHANNEL_ALREADY_EXISTS);
|
AssertUtils.isNull(one, ResultCodeEnum.PAY_CHANNEL_ALREADY_EXISTS);
|
||||||
// 获取配置信息并加密
|
// 获取配置信息并加密
|
||||||
Map<String, String> encryptedConfig = PayConfigEncryptUtils.encryptConfig(params.getConfig());
|
Map<String, String> encryptedConfig = PayConfigEncryptUtils.encryptConfig(params.getConfig());
|
||||||
@ -105,4 +104,12 @@ public class AdminPayChannelService {
|
|||||||
resp.setPayInfo(decrypted);
|
resp.setPayInfo(decrypted);
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取支付渠道编码列表
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<PayChannelCodeResp> getPayChannelCodeList() {
|
||||||
|
return Arrays.stream(PayChannelEnum.values()).map( payChannelEnum -> new PayChannelCodeResp(payChannelEnum.getCode(), payChannelEnum.getDescription(), payChannelEnum.getType()) ).toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -5,11 +5,8 @@ spring:
|
|||||||
active: dev
|
active: dev
|
||||||
main:
|
main:
|
||||||
allow-bean-definition-overriding: true
|
allow-bean-definition-overriding: true
|
||||||
mvc:
|
|
||||||
pathmatch:
|
|
||||||
matching-strategy: ant_path_matcher
|
|
||||||
flyway:
|
flyway:
|
||||||
enabled: true
|
enabled: false
|
||||||
locations: classpath:db/mysql
|
locations: classpath:db/mysql
|
||||||
baseline-on-migrate: true
|
baseline-on-migrate: true
|
||||||
clean-disable: true
|
clean-disable: true
|
||||||
@ -20,6 +17,7 @@ spring:
|
|||||||
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml
|
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml
|
||||||
- optional:nacos:shared-database.yaml
|
- optional:nacos:shared-database.yaml
|
||||||
- optional:nacos:shared-redis.yaml
|
- optional:nacos:shared-redis.yaml
|
||||||
|
- optional:nacos:shared-sa-token.yaml
|
||||||
cloud:
|
cloud:
|
||||||
nacos:
|
nacos:
|
||||||
discovery:
|
discovery:
|
||||||
|
|||||||
@ -4,6 +4,8 @@ CREATE TABLE `pay_channel` (
|
|||||||
`channel_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL COMMENT '渠道编码(WECHAT,ALIPAY)',
|
`channel_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL COMMENT '渠道编码(WECHAT,ALIPAY)',
|
||||||
`channel_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL COMMENT '渠道名称(微信、支付宝)',
|
`channel_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL COMMENT '渠道名称(微信、支付宝)',
|
||||||
`status` int NULL DEFAULT NULL COMMENT '是否启用(0-禁用,1-启用)',
|
`status` int NULL DEFAULT NULL COMMENT '是否启用(0-禁用,1-启用)',
|
||||||
|
`app_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL COMMENT 'appId',
|
||||||
|
`notify_domain` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL COMMENT '回调域名,如:https://api.example.com',
|
||||||
`config_json` JSON NOT NULL COMMENT '支付配置(JSON格式)',
|
`config_json` JSON NOT NULL COMMENT '支付配置(JSON格式)',
|
||||||
`sort` int NULL DEFAULT NULL COMMENT '排序',
|
`sort` int NULL DEFAULT NULL COMMENT '排序',
|
||||||
`deleted` tinyint(1) NOT NULL default 0 COMMENT '逻辑删除',
|
`deleted` tinyint(1) NOT NULL default 0 COMMENT '逻辑删除',
|
||||||
|
|||||||
@ -56,6 +56,24 @@
|
|||||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.binarywang</groupId>
|
||||||
|
<artifactId>weixin-java-pay</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 三方云服务相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alipay.sdk</groupId>
|
||||||
|
<artifactId>alipay-sdk-java</artifactId>
|
||||||
|
<version>4.35.79.ALL</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-all</artifactId>
|
<artifactId>hutool-all</artifactId>
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
package com.seer.teach.pay.app.client;
|
package com.seer.teach.pay.app.client;
|
||||||
|
|
||||||
import com.seer.teach.common.enums.ResultCodeEnum;
|
import com.seer.teach.common.enums.ResultCodeEnum;
|
||||||
import com.seer.teach.common.enums.pay.PayStatusEnum;
|
|
||||||
import com.seer.teach.common.exception.CommonException;
|
import com.seer.teach.common.exception.CommonException;
|
||||||
import com.seer.teach.common.utils.AssertUtils;
|
|
||||||
import com.seer.teach.pay.app.client.convert.PayClientConvert;
|
import com.seer.teach.pay.app.client.convert.PayClientConvert;
|
||||||
import com.seer.teach.pay.app.client.dto.PayOrderCallBackRespDTO;
|
import com.seer.teach.pay.app.client.dto.PayOrderCallBackRespDTO;
|
||||||
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
||||||
@ -11,12 +9,7 @@ import com.seer.teach.pay.app.client.dto.PayOrderRespDTO;
|
|||||||
import com.seer.teach.pay.app.client.dto.PayRefundReqDTO;
|
import com.seer.teach.pay.app.client.dto.PayRefundReqDTO;
|
||||||
import com.seer.teach.pay.app.client.dto.RefundNotificationRespDTO;
|
import com.seer.teach.pay.app.client.dto.RefundNotificationRespDTO;
|
||||||
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
||||||
import com.seer.teach.pay.entity.PayOrderEntity;
|
|
||||||
import com.seer.teach.pay.entity.PayOrderExtensionEntity;
|
|
||||||
import com.seer.teach.pay.service.IPayOrderExtensionService;
|
|
||||||
import com.seer.teach.pay.service.IPayOrderService;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -28,33 +21,21 @@ import java.util.Optional;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class AbstractPayClient implements PayClient {
|
public abstract class AbstractPayClient implements PayClient {
|
||||||
|
|
||||||
@Autowired
|
protected final String channelCode;
|
||||||
protected IPayOrderService payOrderService;
|
|
||||||
|
|
||||||
@Autowired
|
protected final String config;
|
||||||
protected IPayOrderExtensionService payOrderExtensionService;
|
|
||||||
|
|
||||||
|
|
||||||
|
public AbstractPayClient(String channelCode, String config) {
|
||||||
|
this.channelCode = channelCode;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PayOrderRespDTO payOrder(PayOrderReqDTO payOrderReqDTO) {
|
public PayOrderRespDTO payOrder(PayOrderReqDTO payOrderReqDTO) {
|
||||||
log.info("开始支付订单,渠道:{},支付订单号:{}", getChannel(), payOrderReqDTO.getPayOrderSn());
|
log.info("开始支付订单,渠道:{},支付订单号:{}", getChannel(), payOrderReqDTO.getPayOrderSn());
|
||||||
|
|
||||||
Object extrasRequest = getPrepayRequestParam(payOrderReqDTO);
|
PayOrderRespDTO payOrderRespDTO = doPayOrder(payOrderReqDTO);
|
||||||
|
|
||||||
PayOrderRespDTO payOrderRespDTO = doPayOrder(payOrderReqDTO, extrasRequest);
|
|
||||||
|
|
||||||
// 查找或创建支付订单扩展信息
|
|
||||||
Optional<PayOrderExtensionEntity> extension = payOrderExtensionService.getOneByOrderId(payOrderReqDTO.getParOrderId());
|
|
||||||
|
|
||||||
// 如果扩展信息不存在,则创建
|
|
||||||
if (extension.isPresent()) {
|
|
||||||
log.info("支付订单扩展信息不存在,创建新的扩展信息");
|
|
||||||
updatePayOrderExtension(extension.get(), payOrderRespDTO);
|
|
||||||
} else {
|
|
||||||
PayOrderExtensionEntity orderExtension = createPayOrderExtension(payOrderReqDTO);
|
|
||||||
boolean extensionSaved = payOrderExtensionService.save(orderExtension);
|
|
||||||
log.info("保存支付订单扩展信息结果:{}", extensionSaved);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("支付订单创建成功,订单号:{}", payOrderReqDTO.getParOrderId());
|
log.info("支付订单创建成功,订单号:{}", payOrderReqDTO.getParOrderId());
|
||||||
return payOrderRespDTO;
|
return payOrderRespDTO;
|
||||||
@ -63,66 +44,26 @@ public abstract class AbstractPayClient implements PayClient {
|
|||||||
@Override
|
@Override
|
||||||
public PayOrderRespDTO handlePayCallback(Map<String, String> headers, Map<String, String> params, String body) {
|
public PayOrderRespDTO handlePayCallback(Map<String, String> headers, Map<String, String> params, String body) {
|
||||||
log.info("开始处理支付回调,渠道:{}", getChannel());
|
log.info("开始处理支付回调,渠道:{}", getChannel());
|
||||||
// 解析支付回调
|
|
||||||
PayOrderCallBackRespDTO payOrderCallBackRespDTO = parseOrderNotify(headers, params, body);
|
PayOrderCallBackRespDTO payOrderCallBackRespDTO = parseOrderNotify(headers, params, body);
|
||||||
|
|
||||||
PayOrderEntity payOrder = payOrderService.getOrderByOrderSn(payOrderCallBackRespDTO.getPayOrderSn());
|
|
||||||
AssertUtils.notNull(payOrder, ResultCodeEnum.PAY_ORDER_IS_NOT_EXIST);
|
|
||||||
|
|
||||||
if (PayStatusEnum.SUCCESS.getCode() == payOrderCallBackRespDTO.getPayStatus().getCode()) {
|
|
||||||
log.info("该笔支付已处理,订单号:{}", payOrderCallBackRespDTO.getPayOrderSn());
|
|
||||||
throw new CommonException(ResultCodeEnum.PAY_ORDER_IS_PROCESSED);
|
|
||||||
}
|
|
||||||
return PayClientConvert.INSTANCE.convert(payOrderCallBackRespDTO);
|
return PayClientConvert.INSTANCE.convert(payOrderCallBackRespDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建支付订单扩展信息
|
|
||||||
*/
|
|
||||||
private PayOrderExtensionEntity createPayOrderExtension(PayOrderReqDTO payOrderReqDTO) {
|
|
||||||
PayOrderExtensionEntity extension = new PayOrderExtensionEntity();
|
|
||||||
extension.setOrderId(payOrderReqDTO.getParOrderId());
|
|
||||||
extension.setMerchantOrderSn(payOrderReqDTO.getMerchantOrderSn());
|
|
||||||
return extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新支付订单扩展信息
|
|
||||||
*/
|
|
||||||
private void updatePayOrderExtension(PayOrderExtensionEntity extension, PayOrderRespDTO payOrderRespDTO) {
|
|
||||||
if (payOrderRespDTO.getPrepayId() != null || payOrderRespDTO.getPayParams() != null) {
|
|
||||||
// 将 payParams 对象转换为 JSON 字符串存储
|
|
||||||
String channelExtras = null;
|
|
||||||
if (payOrderRespDTO.getPayParams() != null) {
|
|
||||||
try {
|
|
||||||
channelExtras = cn.hutool.json.JSONUtil.toJsonStr(payOrderRespDTO.getPayParams());
|
|
||||||
} catch (Exception e) {
|
|
||||||
channelExtras = payOrderRespDTO.getPayParams().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
extension.setChannelExtras(channelExtras);
|
|
||||||
payOrderExtensionService.updateById(extension);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract PayOrderCallBackRespDTO parseOrderNotify(Map<String, String> headers, Map<String, String> params, String body);
|
protected abstract PayOrderCallBackRespDTO parseOrderNotify(Map<String, String> headers, Map<String, String> params, String body);
|
||||||
|
|
||||||
protected abstract Object getPrepayRequestParam(PayOrderReqDTO payOrderReq);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 具体支付渠道实现创建预支付订单
|
* 具体支付渠道实现创建预支付订单
|
||||||
*/
|
*/
|
||||||
protected abstract PayOrderRespDTO doPayOrder(PayOrderReqDTO payOrderReqDTO , Object extrasRequest);
|
protected abstract PayOrderRespDTO doPayOrder(PayOrderReqDTO payOrderReqDTO);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 具体支付渠道实现查询支付状态
|
* 具体支付渠道实现查询支付状态
|
||||||
*/
|
*/
|
||||||
protected abstract PayOrderEntity doQueryPayStatus(String orderSn);
|
protected abstract PayOrderRespDTO doQueryPayStatus(String orderSn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 具体支付渠道实现关闭支付订单
|
* 具体支付渠道实现关闭支付订单
|
||||||
*/
|
*/
|
||||||
protected abstract boolean doClosePayOrder(String orderSn);
|
protected abstract String doClosePayOrder(String orderSn);
|
||||||
|
|
||||||
|
|
||||||
protected abstract RefundSubmitRespDTO doRefundOrder(PayRefundReqDTO payRefundReqDTO);
|
protected abstract RefundSubmitRespDTO doRefundOrder(PayRefundReqDTO payRefundReqDTO);
|
||||||
@ -136,12 +77,12 @@ public abstract class AbstractPayClient implements PayClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PayOrderEntity queryPayStatus(String orderSn) {
|
public PayOrderRespDTO queryPayStatus(String orderSn) {
|
||||||
return doQueryPayStatus(orderSn);
|
return doQueryPayStatus(orderSn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean closePayOrder(String orderSn) {
|
public String closePayOrder(String orderSn) {
|
||||||
return doClosePayOrder(orderSn);
|
return doClosePayOrder(orderSn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,10 @@ package com.seer.teach.pay.app.client;
|
|||||||
|
|
||||||
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
||||||
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
||||||
import com.seer.teach.pay.app.client.dto.RefundNotificationRespDTO;
|
|
||||||
import com.seer.teach.pay.app.pay.controller.req.OrderPayReq;
|
|
||||||
import com.seer.teach.pay.app.client.dto.PayOrderRespDTO;
|
import com.seer.teach.pay.app.client.dto.PayOrderRespDTO;
|
||||||
import com.seer.teach.pay.app.client.dto.PayRefundReqDTO;
|
import com.seer.teach.pay.app.client.dto.PayRefundReqDTO;
|
||||||
|
import com.seer.teach.pay.app.client.dto.RefundNotificationRespDTO;
|
||||||
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
||||||
import com.seer.teach.pay.entity.PayOrderEntity;
|
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -21,9 +19,8 @@ public interface PayClient {
|
|||||||
/**
|
/**
|
||||||
* 初始化支付客户端
|
* 初始化支付客户端
|
||||||
*
|
*
|
||||||
* @param configJson 配置信息
|
|
||||||
*/
|
*/
|
||||||
void init(String configJson);
|
void init();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取支付渠道编码
|
* 获取支付渠道编码
|
||||||
@ -43,7 +40,7 @@ public interface PayClient {
|
|||||||
* @param orderSn 订单号
|
* @param orderSn 订单号
|
||||||
* @return 支付订单信息
|
* @return 支付订单信息
|
||||||
*/
|
*/
|
||||||
PayOrderEntity queryPayStatus(String orderSn);
|
PayOrderRespDTO queryPayStatus(String orderSn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理支付回调
|
* 处理支付回调
|
||||||
@ -59,7 +56,7 @@ public interface PayClient {
|
|||||||
* @param orderSn 订单号
|
* @param orderSn 订单号
|
||||||
* @return 是否关闭成功
|
* @return 是否关闭成功
|
||||||
*/
|
*/
|
||||||
boolean closePayOrder(String orderSn);
|
String closePayOrder(String orderSn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断是否是异步回调
|
* 判断是否是异步回调
|
||||||
|
|||||||
@ -0,0 +1,57 @@
|
|||||||
|
package com.seer.teach.pay.app.client;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
||||||
|
import com.seer.teach.pay.app.client.alipay.AbstractAlipayPayClient;
|
||||||
|
import com.seer.teach.pay.app.client.alipay.AlipayQrPayClient;
|
||||||
|
import com.seer.teach.pay.app.client.coin.CoinPayClient;
|
||||||
|
import com.seer.teach.pay.app.client.wechat.WechatJsApiPayClient;
|
||||||
|
import com.seer.teach.pay.app.client.wechat.WechatNativePayClient;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付策略工厂,根据支付渠道编码获取对应的支付策略
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class PayClientFactory implements InitializingBean {
|
||||||
|
|
||||||
|
private static final Map<PayChannelEnum, PayClient> CLIENT_MAP = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final Map<PayChannelEnum, Class<? extends PayClient>> clientClass = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据支付渠道编码获取支付策略
|
||||||
|
*
|
||||||
|
* @param channel 支付渠道编码(如:WECHAT、ALIPAY)
|
||||||
|
* @return 支付策略
|
||||||
|
*/
|
||||||
|
public PayClient getPayClient(PayChannelEnum channel, String configJson) {
|
||||||
|
PayClient client = CLIENT_MAP.get(channel);
|
||||||
|
if (Objects.isNull(client)) {
|
||||||
|
Class<? extends PayClient> clazz = clientClass.get(channel);
|
||||||
|
client = ReflectUtil.newInstance(clazz, configJson);
|
||||||
|
client.init();
|
||||||
|
CLIENT_MAP.put(channel, client);
|
||||||
|
}
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
clientClass.put(PayChannelEnum.ALIPAY_QR, AlipayQrPayClient.class);
|
||||||
|
|
||||||
|
clientClass.put(PayChannelEnum.WECHAT_NATIVE, WechatNativePayClient.class);
|
||||||
|
clientClass.put(PayChannelEnum.WECHAT_JSAPI, WechatJsApiPayClient.class);
|
||||||
|
|
||||||
|
clientClass.put(PayChannelEnum.COIN, CoinPayClient.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,138 @@
|
|||||||
|
package com.seer.teach.pay.app.client.alipay;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.alipay.api.AlipayApiException;
|
||||||
|
import com.alipay.api.AlipayConfig;
|
||||||
|
import com.alipay.api.DefaultAlipayClient;
|
||||||
|
import com.alipay.api.domain.AlipayTradeRefundModel;
|
||||||
|
import com.alipay.api.internal.util.AlipaySignature;
|
||||||
|
import com.alipay.api.internal.util.AntCertificationUtil;
|
||||||
|
import com.alipay.api.internal.util.codec.Base64;
|
||||||
|
import com.alipay.api.request.AlipayTradeRefundRequest;
|
||||||
|
import com.alipay.api.response.AlipayTradeRefundResponse;
|
||||||
|
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
||||||
|
import com.seer.teach.common.enums.pay.PayStatusEnum;
|
||||||
|
import com.seer.teach.pay.app.client.AbstractPayClient;
|
||||||
|
import com.seer.teach.pay.app.client.dto.PayOrderCallBackRespDTO;
|
||||||
|
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
||||||
|
import com.seer.teach.pay.app.client.dto.PayOrderRespDTO;
|
||||||
|
import com.seer.teach.pay.app.client.dto.PayRefundReqDTO;
|
||||||
|
import com.seer.teach.pay.app.client.dto.RefundNotificationRespDTO;
|
||||||
|
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝支付策略实现
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AbstractAlipayPayClient extends AbstractPayClient {
|
||||||
|
|
||||||
|
protected DefaultAlipayClient client;
|
||||||
|
|
||||||
|
public AbstractAlipayPayClient(String channelCode, String configJson) {
|
||||||
|
super(channelCode, configJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
AlipayConfig alipayConfig = JSONUtil.toBean(config, AlipayConfig.class);
|
||||||
|
try {
|
||||||
|
this.client = new DefaultAlipayClient(alipayConfig);
|
||||||
|
} catch (AlipayApiException e) {
|
||||||
|
log.error("支付宝初始化失败", e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAsyncCallback() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RefundSubmitRespDTO doRefundOrder(PayRefundReqDTO payRefundReqDTO) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<RefundNotificationRespDTO> parseRefundNotify(Map<String, String> headers, Map<String, String> params, String body) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RefundSubmitRespDTO doQueryRefundStatus(String refundSn) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PayOrderCallBackRespDTO parseOrderNotify(Map<String, String> headers, Map<String, String> params, String body) {
|
||||||
|
// 额外说明:支付宝不仅仅支付成功会回调,再各种触发支付单数据变化时,都会进行回调,所以这里 status 的解析会写的比较复杂
|
||||||
|
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
|
||||||
|
PayStatusEnum status = parseStatus(bodyObj.get("trade_status"));
|
||||||
|
// 特殊逻辑: 支付宝没有退款成功的状态,所以,如果有退款金额,我们认为是退款成功
|
||||||
|
if (MapUtil.getDouble(bodyObj, "refund_fee", 0D) > 0) {
|
||||||
|
status = PayStatusEnum.WECHAT_REFUND_SUCCESS;
|
||||||
|
}
|
||||||
|
PayOrderCallBackRespDTO payOrderCallBackRespDTO = new PayOrderCallBackRespDTO();
|
||||||
|
payOrderCallBackRespDTO.setPayStatus(status);
|
||||||
|
payOrderCallBackRespDTO.setPayOrderSn(bodyObj.get("out_trade_no"));
|
||||||
|
payOrderCallBackRespDTO.setPayOrderId(MapUtil.getInt(bodyObj, "trade_no"));
|
||||||
|
payOrderCallBackRespDTO.setPaySuccessTime(parseTime(bodyObj.get("gmt_payment")));
|
||||||
|
payOrderCallBackRespDTO.setResponse(body);
|
||||||
|
return payOrderCallBackRespDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PayOrderRespDTO doQueryPayStatus(String orderSn) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String doClosePayOrder(String orderSn) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static PayStatusEnum parseStatus(String tradeStatus) {
|
||||||
|
if(Objects.equals("WAIT_BUYER_PAY", tradeStatus)){
|
||||||
|
return PayStatusEnum.PENDING;
|
||||||
|
}
|
||||||
|
if(Objects.equals("TRADE_FINISHED", tradeStatus) || Objects.equals("TRADE_SUCCESS", tradeStatus)){
|
||||||
|
return PayStatusEnum.SUCCESS;
|
||||||
|
}
|
||||||
|
if(Objects.equals("TRADE_CLOSED", tradeStatus)){
|
||||||
|
return PayStatusEnum.WECHAT_REFUND_CLOSED;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String formatAmount(Integer amount) {
|
||||||
|
return String.valueOf(amount / 100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String formatTime(LocalDateTime time) {
|
||||||
|
return LocalDateTimeUtil.format(time, NORM_DATETIME_FORMATTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LocalDateTime parseTime(String str) {
|
||||||
|
return LocalDateTimeUtil.parse(str, NORM_DATETIME_FORMATTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,158 +0,0 @@
|
|||||||
package com.seer.teach.pay.app.client.alipay;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
||||||
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
|
||||||
import com.seer.teach.common.enums.pay.PayStatusEnum;
|
|
||||||
import com.seer.teach.common.utils.MoneyUtil;
|
|
||||||
import com.seer.teach.pay.app.client.AbstractPayClient;
|
|
||||||
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
|
||||||
import com.seer.teach.pay.app.client.dto.RefundNotificationRespDTO;
|
|
||||||
import com.seer.teach.pay.app.client.dto.PayOrderCallBackRespDTO;
|
|
||||||
import com.seer.teach.pay.app.client.dto.PayOrderRespDTO;
|
|
||||||
import com.seer.teach.pay.app.client.dto.PayRefundReqDTO;
|
|
||||||
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
|
||||||
import com.seer.teach.pay.entity.PayOrderEntity;
|
|
||||||
import com.seer.teach.pay.entity.PayOrderExtensionEntity;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 支付宝支付策略实现
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
public class AlipayPayClient extends AbstractPayClient {
|
|
||||||
|
|
||||||
|
|
||||||
public AlipayPayClient() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取支付渠道的编码
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public PayChannelEnum getChannel() {
|
|
||||||
return PayChannelEnum.ALIPAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAsyncCallback() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RefundSubmitRespDTO doRefundOrder(PayRefundReqDTO payRefundReqDTO) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<RefundNotificationRespDTO> parseRefundNotify(Map<String, String> headers, Map<String, String> params, String body) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RefundSubmitRespDTO doQueryRefundStatus(String refundSn) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(String configJson) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PayOrderCallBackRespDTO parseOrderNotify(Map<String, String> headers, Map<String, String> params, String body) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object getPrepayRequestParam(PayOrderReqDTO payOrder) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PayOrderRespDTO doPayOrder(PayOrderReqDTO payOrder, Object extrasRequest) {
|
|
||||||
// TODO: 实现支付宝预支付逻辑
|
|
||||||
// 1. 调用支付宝SDK创建预支付订单
|
|
||||||
// 2. 获取支付宝返回的支付参数
|
|
||||||
PayOrderRespDTO response = new PayOrderRespDTO();
|
|
||||||
response.setChannelCode(payOrder.getChannelCode());
|
|
||||||
response.setStatus(PayStatusEnum.PENDING.getCode());
|
|
||||||
// 支付宝通常返回支付字符串或二维码
|
|
||||||
Map<String, Object> payParams = new HashMap<>();
|
|
||||||
payParams.put("payString", "支付宝支付参数字符串");
|
|
||||||
response.setPayParams(payParams); // 设置为对象格式
|
|
||||||
response.setPrepayId(null); // 支付宝没有 prepayId 概念
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PayOrderEntity doQueryPayStatus(String orderSn) {
|
|
||||||
log.info("查询支付宝支付状态,订单号:{}", orderSn);
|
|
||||||
|
|
||||||
PayOrderEntity payOrder = payOrderService.getOne(
|
|
||||||
new LambdaQueryWrapper<PayOrderEntity>()
|
|
||||||
.eq(PayOrderEntity::getOrderSn, orderSn)
|
|
||||||
.eq(PayOrderEntity::getChannelCode, getChannel())
|
|
||||||
);
|
|
||||||
|
|
||||||
if (payOrder == null) {
|
|
||||||
log.warn("支付订单不存在,订单号:{}", orderSn);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 调用支付宝查询订单接口获取最新状态
|
|
||||||
|
|
||||||
return payOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean doClosePayOrder(String orderSn) {
|
|
||||||
log.info("关闭支付宝支付订单,订单号:{}", orderSn);
|
|
||||||
|
|
||||||
try {
|
|
||||||
PayOrderEntity payOrder = payOrderService.getOne(
|
|
||||||
new LambdaQueryWrapper<PayOrderEntity>()
|
|
||||||
.eq(PayOrderEntity::getOrderSn, orderSn)
|
|
||||||
.eq(PayOrderEntity::getChannelCode, getChannel())
|
|
||||||
);
|
|
||||||
|
|
||||||
if (payOrder == null) {
|
|
||||||
log.warn("支付订单不存在,订单号:{}", orderSn);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新订单状态为已取消
|
|
||||||
boolean updateResult = payOrderService.update(
|
|
||||||
new LambdaUpdateWrapper<PayOrderEntity>()
|
|
||||||
.set(PayOrderEntity::getStatus, PayStatusEnum.CANCEL.getCode())
|
|
||||||
.eq(PayOrderEntity::getId, payOrder.getId())
|
|
||||||
);
|
|
||||||
|
|
||||||
// 更新扩展表状态
|
|
||||||
if (updateResult) {
|
|
||||||
payOrderExtensionService.update(
|
|
||||||
new LambdaUpdateWrapper<PayOrderExtensionEntity>()
|
|
||||||
.eq(PayOrderExtensionEntity::getOrderId, payOrder.getId())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 调用支付宝关闭订单接口
|
|
||||||
|
|
||||||
log.info("关闭支付宝支付订单结果:{},订单号:{}", updateResult, orderSn);
|
|
||||||
return updateResult;
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("关闭支付宝支付订单失败,订单号:{}", orderSn, e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.seer.teach.pay.app.client.alipay;
|
||||||
|
|
||||||
|
import com.alipay.api.AlipayApiException;
|
||||||
|
import com.alipay.api.domain.AlipayTradePrecreateModel;
|
||||||
|
import com.alipay.api.request.AlipayTradePrecreateRequest;
|
||||||
|
import com.alipay.api.response.AlipayTradePrecreateResponse;
|
||||||
|
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
||||||
|
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
||||||
|
import com.seer.teach.pay.app.client.dto.PayOrderRespDTO;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class AlipayQrPayClient extends AbstractAlipayPayClient{
|
||||||
|
|
||||||
|
|
||||||
|
public AlipayQrPayClient(String configJson) {
|
||||||
|
super(PayChannelEnum.ALIPAY_QR.getCode(), configJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PayOrderRespDTO doPayOrder(PayOrderReqDTO payOrderReqDTO) {
|
||||||
|
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
|
||||||
|
model.setOutTradeNo(payOrderReqDTO.getPayOrderSn());
|
||||||
|
model.setSubject(payOrderReqDTO.getDescription());
|
||||||
|
model.setTotalAmount(formatAmount(payOrderReqDTO.getTotalAmount()));
|
||||||
|
model.setProductCode("FACE_TO_FACE_PAYMENT");
|
||||||
|
|
||||||
|
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
|
||||||
|
request.setBizModel(model);
|
||||||
|
request.setNotifyUrl(payOrderReqDTO.getNotifyUrl());
|
||||||
|
request.setReturnUrl(payOrderReqDTO.getReturnUrl());
|
||||||
|
|
||||||
|
try {
|
||||||
|
AlipayTradePrecreateResponse response = client.execute(request);
|
||||||
|
|
||||||
|
PayOrderRespDTO payOrderRespDTO = new PayOrderRespDTO();
|
||||||
|
payOrderRespDTO.setChannelCode(channelCode);
|
||||||
|
payOrderRespDTO.setPayOrderSn(payOrderReqDTO.getPayOrderSn());
|
||||||
|
payOrderRespDTO.setQrCodeUrl(response.getQrCode());
|
||||||
|
payOrderRespDTO.setStatus(response.isSuccess() ? 0 : 2);
|
||||||
|
payOrderRespDTO.setResponse(response.getBody());
|
||||||
|
return payOrderRespDTO;
|
||||||
|
} catch (AlipayApiException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PayChannelEnum getChannel() {
|
||||||
|
return PayChannelEnum.ALIPAY_QR;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,6 @@
|
|||||||
package com.seer.teach.pay.app.client.coin;
|
package com.seer.teach.pay.app.client.coin;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
||||||
import com.seer.teach.common.enums.ResultCodeEnum;
|
import com.seer.teach.common.enums.ResultCodeEnum;
|
||||||
import com.seer.teach.common.enums.TradeTypeEnum;
|
import com.seer.teach.common.enums.TradeTypeEnum;
|
||||||
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
||||||
@ -18,30 +17,39 @@ import com.seer.teach.pay.app.client.dto.RefundNotificationRespDTO;
|
|||||||
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
||||||
import com.seer.teach.pay.entity.PayCoinAccountEntity;
|
import com.seer.teach.pay.entity.PayCoinAccountEntity;
|
||||||
import com.seer.teach.pay.entity.PayCoinAccountTradeLogEntity;
|
import com.seer.teach.pay.entity.PayCoinAccountTradeLogEntity;
|
||||||
import com.seer.teach.pay.entity.PayOrderEntity;
|
|
||||||
import com.seer.teach.pay.entity.PayOrderExtensionEntity;
|
|
||||||
import com.seer.teach.pay.service.IPayCoinAccountService;
|
import com.seer.teach.pay.service.IPayCoinAccountService;
|
||||||
import com.seer.teach.pay.service.IPayCoinAccountTradeLogService;
|
import com.seer.teach.pay.service.IPayCoinAccountTradeLogService;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 学豆支付策略实现
|
* 钱包支付实现
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class CoinPayClient extends AbstractPayClient {
|
public class CoinPayClient extends AbstractPayClient {
|
||||||
|
|
||||||
private final IPayCoinAccountService payConAccountService;
|
private IPayCoinAccountService payConAccountService;
|
||||||
|
|
||||||
private final IPayCoinAccountTradeLogService userCoinAccountTradeLogService;
|
private IPayCoinAccountTradeLogService userCoinAccountTradeLogService;
|
||||||
|
|
||||||
|
public CoinPayClient(String config) {
|
||||||
|
super(PayChannelEnum.COIN.getCode(), config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
if(Objects.isNull(payConAccountService)){
|
||||||
|
payConAccountService = SpringUtil.getBean(IPayCoinAccountService.class);
|
||||||
|
}
|
||||||
|
if(Objects.isNull(userCoinAccountTradeLogService)){
|
||||||
|
userCoinAccountTradeLogService = SpringUtil.getBean(IPayCoinAccountTradeLogService.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取支付渠道的编码
|
* 获取支付渠道的编码
|
||||||
@ -61,11 +69,11 @@ public class CoinPayClient extends AbstractPayClient {
|
|||||||
@Override
|
@Override
|
||||||
protected RefundSubmitRespDTO doRefundOrder(PayRefundReqDTO payRefundReqDTO) {
|
protected RefundSubmitRespDTO doRefundOrder(PayRefundReqDTO payRefundReqDTO) {
|
||||||
Integer userId = payRefundReqDTO.getUserId();
|
Integer userId = payRefundReqDTO.getUserId();
|
||||||
Long refundAmount = payRefundReqDTO.getRefundPrice();
|
Integer refundAmount = payRefundReqDTO.getRefundPrice();
|
||||||
BigDecimal refundPrice = BigDecimal.valueOf(refundAmount);
|
BigDecimal refundPrice = BigDecimal.valueOf(refundAmount);
|
||||||
PayCoinAccountEntity beforeAccount = payConAccountService.getByUserId(userId);
|
PayCoinAccountEntity beforeAccount = payConAccountService.getByUserId(userId);
|
||||||
|
|
||||||
// 增加用户学豆余额
|
// 增加用户余额
|
||||||
boolean addBalanceResult = payConAccountService.addBalance(userId, beforeAccount.getId(), refundPrice);
|
boolean addBalanceResult = payConAccountService.addBalance(userId, beforeAccount.getId(), refundPrice);
|
||||||
log.info("确认退款的增加余额结果:{}", addBalanceResult);
|
log.info("确认退款的增加余额结果:{}", addBalanceResult);
|
||||||
|
|
||||||
@ -107,31 +115,25 @@ public class CoinPayClient extends AbstractPayClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(String configJson) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PayOrderCallBackRespDTO parseOrderNotify(Map<String, String> headers, Map<String, String> params, String body) {
|
public PayOrderCallBackRespDTO parseOrderNotify(Map<String, String> headers, Map<String, String> params, String body) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object getPrepayRequestParam(PayOrderReqDTO payOrderReqDTO) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PayOrderRespDTO doPayOrder(PayOrderReqDTO payOrder, Object extrasRequest) {
|
protected PayOrderRespDTO doPayOrder(PayOrderReqDTO payOrder) {
|
||||||
try {
|
try {
|
||||||
log.info("开始创建学豆支付订单,订单号:{},用户ID:{},支付学豆:{},",
|
log.info("开始创建支付订单,订单号:{},用户ID:{},支付:{},",
|
||||||
payOrder.getPayOrderSn(), payOrder.getUserId(), payOrder.getTotalCoin());
|
payOrder.getPayOrderSn(), payOrder.getUserId(), payOrder.getTotalCoin());
|
||||||
|
|
||||||
Integer userId = payOrder.getUserId();
|
Integer userId = payOrder.getUserId();
|
||||||
BigDecimal totalCoin = payOrder.getTotalCoin();
|
BigDecimal totalCoin = payOrder.getTotalCoin();
|
||||||
|
|
||||||
// 获取用户学豆账户
|
// 获取用户账户
|
||||||
PayCoinAccountEntity coinAccount = payConAccountService.getByUserId(userId);
|
PayCoinAccountEntity coinAccount = payConAccountService.getByUserId(userId);
|
||||||
AssertUtils.notNull(coinAccount, ResultCodeEnum.USER_NOT_FOUND);
|
AssertUtils.notNull(coinAccount, ResultCodeEnum.USER_NOT_FOUND);
|
||||||
|
|
||||||
@ -140,20 +142,20 @@ public class CoinPayClient extends AbstractPayClient {
|
|||||||
throw new CommonException(ResultCodeEnum.USER_COIN_LOCK_ERROR);
|
throw new CommonException(ResultCodeEnum.USER_COIN_LOCK_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查学豆余额是否充足
|
// 检查余额是否充足
|
||||||
BigDecimal availableBalance = coinAccount.getAvailableBalance();
|
BigDecimal availableBalance = coinAccount.getAvailableBalance();
|
||||||
if (availableBalance == null || availableBalance.compareTo(totalCoin) < 0) {
|
if (availableBalance == null || availableBalance.compareTo(totalCoin) < 0) {
|
||||||
log.warn("学豆余额不足,用户ID:{},可用余额:{},需要支付:{}", userId, availableBalance, totalCoin);
|
log.warn("余额不足,用户ID:{},可用余额:{},需要支付:{}", userId, availableBalance, totalCoin);
|
||||||
throw new CommonException(ResultCodeEnum.USER_NOT_ENOUGH_COIN,
|
throw new CommonException(ResultCodeEnum.USER_NOT_ENOUGH_COIN,
|
||||||
String.format("学豆余额不足,可用学豆余额:%s,需要支付学豆:%s", availableBalance, totalCoin));
|
String.format("余额不足,可用余额:%s,需要支付:%s", availableBalance, totalCoin));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 扣减学豆余额
|
// 扣减余额
|
||||||
boolean deductResult = payConAccountService.deductBalance(userId, coinAccount.getId(), totalCoin);
|
boolean deductResult = payConAccountService.deductBalance(userId, coinAccount.getId(), totalCoin);
|
||||||
if (!deductResult) {
|
if (!deductResult) {
|
||||||
log.error("扣减学豆余额失败,用户ID:{},账户ID:{},扣减学豆:{}",
|
log.error("扣减余额失败,用户ID:{},账户ID:{},扣减:{}",
|
||||||
userId, coinAccount.getId(), totalCoin);
|
userId, coinAccount.getId(), totalCoin);
|
||||||
throw new CommonException(ResultCodeEnum.USER_COIN_PAY_ERROR, "学豆扣减失败");
|
throw new CommonException(ResultCodeEnum.USER_COIN_PAY_ERROR, "扣减失败");
|
||||||
}else {
|
}else {
|
||||||
// 添加用户账户流水
|
// 添加用户账户流水
|
||||||
PayCoinAccountEntity afterAccount = payConAccountService.getByUserId(userId);
|
PayCoinAccountEntity afterAccount = payConAccountService.getByUserId(userId);
|
||||||
@ -174,70 +176,35 @@ public class CoinPayClient extends AbstractPayClient {
|
|||||||
response.setStatus(PayStatusEnum.SUCCESS.getCode());
|
response.setStatus(PayStatusEnum.SUCCESS.getCode());
|
||||||
response.setPayOrderSn(payOrder.getPayOrderSn());
|
response.setPayOrderSn(payOrder.getPayOrderSn());
|
||||||
response.setPaySuccessTime(LocalDateTime.now());
|
response.setPaySuccessTime(LocalDateTime.now());
|
||||||
log.info("学豆支付订单完成,订单号:{},支付学豆:{}", payOrder.getPayOrderSn(), payOrder.getTotalCoin());
|
log.info("支付订单完成,订单号:{},支付:{}", payOrder.getPayOrderSn(), payOrder.getTotalCoin());
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("创建学豆支付订单失败,订单号:{},支付学豆:{}", payOrder.getPayOrderSn(), payOrder.getTotalCoin(), e);
|
log.error("创建支付订单失败,订单号:{},支付:{}", payOrder.getPayOrderSn(), payOrder.getTotalCoin(), e);
|
||||||
throw new CommonException(ResultCodeEnum.USER_COIN_PAY_ERROR, e.getMessage());
|
throw new CommonException(ResultCodeEnum.USER_COIN_PAY_ERROR, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询学豆支付状态
|
* 查询支付状态
|
||||||
*
|
*
|
||||||
* @param orderSn 订单号
|
* @param orderSn 订单号
|
||||||
* @return 支付订单
|
* @return 支付订单
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected PayOrderEntity doQueryPayStatus(String orderSn) {
|
protected PayOrderRespDTO doQueryPayStatus(String orderSn) {
|
||||||
log.info("查询学豆支付状态,订单号:{}", orderSn);
|
log.info("查询支付状态,订单号:{}", orderSn);
|
||||||
PayOrderEntity payOrder = payOrderService.getOne(
|
return null;
|
||||||
new LambdaQueryWrapper<PayOrderEntity>()
|
|
||||||
.eq(PayOrderEntity::getOrderSn, orderSn)
|
|
||||||
.eq(PayOrderEntity::getChannelCode, getChannel()));
|
|
||||||
AssertUtils.notNull(payOrder, ResultCodeEnum.ORDER_NOT_FOUND);
|
|
||||||
return payOrder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭学豆支付订单
|
* 关闭支付订单
|
||||||
*
|
*
|
||||||
* @param orderSn 订单号
|
* @param orderSn 订单号
|
||||||
* @return 关闭结果
|
* @return 关闭结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean doClosePayOrder(String orderSn) {
|
protected String doClosePayOrder(String orderSn) {
|
||||||
try {
|
return "";
|
||||||
log.info("关闭学豆支付订单,订单号:{}", orderSn);
|
|
||||||
// 查询订单
|
|
||||||
PayOrderEntity payOrder = payOrderService.getOne(
|
|
||||||
new LambdaQueryWrapper<PayOrderEntity>()
|
|
||||||
.eq(PayOrderEntity::getOrderSn, orderSn)
|
|
||||||
.eq(PayOrderEntity::getChannelCode, getChannel()));
|
|
||||||
AssertUtils.notNull(payOrder, ResultCodeEnum.ORDER_NOT_FOUND);
|
|
||||||
|
|
||||||
// 学豆支付订单是待支付状态才能关闭
|
|
||||||
if (PayStatusEnum.PENDING.getCode() != payOrder.getStatus()) {
|
|
||||||
log.warn("学豆支付订单状态不允许关闭,订单号:{},当前状态:{}", orderSn, payOrder.getStatus());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 更新订单状态为已取消
|
|
||||||
boolean updateResult = payOrderService.update(
|
|
||||||
new LambdaUpdateWrapper<PayOrderEntity>()
|
|
||||||
.set(PayOrderEntity::getStatus, PayStatusEnum.CANCEL.getCode())
|
|
||||||
.eq(PayOrderEntity::getId, payOrder.getId()));
|
|
||||||
// 更新扩展表
|
|
||||||
if (updateResult) {
|
|
||||||
payOrderExtensionService.update(
|
|
||||||
new LambdaUpdateWrapper<PayOrderExtensionEntity>()
|
|
||||||
.eq(PayOrderExtensionEntity::getOrderId, payOrder.getId()));
|
|
||||||
}
|
|
||||||
log.info("关闭学豆支付订单结果:{},订单号:{}", updateResult, orderSn);
|
|
||||||
return updateResult;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("关闭学豆支付订单失败,订单号:{}", orderSn, e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,11 +50,14 @@ public interface PayClientConvert {
|
|||||||
* @param payReq
|
* @param payReq
|
||||||
* @return PayOrderReqDTO
|
* @return PayOrderReqDTO
|
||||||
*/
|
*/
|
||||||
@Mapping(source = "payOrderEntity.subject", target = "description")
|
@Mapping(source = "payOrderEntity.subject", target = "subject")
|
||||||
@Mapping(source = "payReq.channelCode", target = "channelCode")
|
@Mapping(source = "payReq.channelCode", target = "channelCode")
|
||||||
@Mapping(source = "payOrderEntity.merchantOrderSn", target = "merchantOrderSn")
|
@Mapping(source = "payOrderEntity.merchantOrderSn", target = "merchantOrderSn")
|
||||||
@Mapping(source = "payOrderEntity.id", target = "parOrderId")
|
@Mapping(source = "payOrderEntity.id", target = "parOrderId")
|
||||||
@Mapping(source = "payOrderEntity.orderSn", target = "payOrderSn")
|
@Mapping(source = "payOrderEntity.orderSn", target = "payOrderSn")
|
||||||
|
@Mapping(source = "payOrderEntity.body", target = "description")
|
||||||
|
@Mapping(source = "payOrderEntity.totalAmount", target = "totalAmount")
|
||||||
|
@Mapping(source = "payOrderEntity.expireTime", target = "expireTime")
|
||||||
PayOrderReqDTO convert2PayOrderReqDTO(PayOrderEntity payOrderEntity,OrderPayReq payReq);
|
PayOrderReqDTO convert2PayOrderReqDTO(PayOrderEntity payOrderEntity,OrderPayReq payReq);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -42,4 +42,13 @@ public class PayOrderCallBackRespDTO {
|
|||||||
* 订单来源
|
* 订单来源
|
||||||
*/
|
*/
|
||||||
private Integer orderSource;
|
private Integer orderSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用渠道的错误码
|
||||||
|
*/
|
||||||
|
private String channelErrorCode;
|
||||||
|
/**
|
||||||
|
* 调用渠道报错时,错误信息
|
||||||
|
*/
|
||||||
|
private String channelErrorMsg;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
package com.seer.teach.pay.app.client.dto;
|
package com.seer.teach.pay.app.client.dto;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付订单的请求信息
|
* 支付订单的请求信息
|
||||||
@ -15,6 +19,11 @@ public class PayOrderReqDTO {
|
|||||||
*/
|
*/
|
||||||
private Integer userId;
|
private Integer userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户IP
|
||||||
|
*/
|
||||||
|
private String userIp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付订单SN
|
* 支付订单SN
|
||||||
*/
|
*/
|
||||||
@ -43,7 +52,12 @@ public class PayOrderReqDTO {
|
|||||||
/**
|
/**
|
||||||
* 实际支付订单总金额,单位为分,整型,必须大于0
|
* 实际支付订单总金额,单位为分,整型,必须大于0
|
||||||
*/
|
*/
|
||||||
private Long totalAmount;
|
private Integer totalAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品
|
||||||
|
*/
|
||||||
|
private String subject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 【商品描述】商品信息描述
|
* 【商品描述】商品信息描述
|
||||||
@ -54,4 +68,21 @@ public class PayOrderReqDTO {
|
|||||||
* 货币类型 说明:CNY:人民币
|
* 货币类型 说明:CNY:人民币
|
||||||
*/
|
*/
|
||||||
private String currency;
|
private String currency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付过期时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime expireTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付结果的 notify 回调地址
|
||||||
|
*/
|
||||||
|
private String notifyUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付结果的 return 回调地址,支付宝使用
|
||||||
|
*/
|
||||||
|
private String returnUrl;
|
||||||
|
|
||||||
|
private Map<String, String> channelExtras = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,9 +15,6 @@ public class PayOrderRespDTO {
|
|||||||
@Schema(description = "支付渠道编码")
|
@Schema(description = "支付渠道编码")
|
||||||
private String channelCode;
|
private String channelCode;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/******** jsAPI 预支付参数 *************/
|
|
||||||
@Schema(description = "支付参数(对象格式)")
|
@Schema(description = "支付参数(对象格式)")
|
||||||
private Object payParams;
|
private Object payParams;
|
||||||
|
|
||||||
@ -25,9 +22,6 @@ public class PayOrderRespDTO {
|
|||||||
private String prepayId;
|
private String prepayId;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/******** 回调参数 *************/
|
|
||||||
|
|
||||||
@Schema(description = "支付状态(0-待支付,1-支付成功,2-支付失败,3-申请退款,4-取消支付)")
|
@Schema(description = "支付状态(0-待支付,1-支付成功,2-支付失败,3-申请退款,4-取消支付)")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
@ -44,4 +38,7 @@ public class PayOrderRespDTO {
|
|||||||
private LocalDateTime paySuccessTime;
|
private LocalDateTime paySuccessTime;
|
||||||
|
|
||||||
private String response;
|
private String response;
|
||||||
|
|
||||||
|
@Schema(description = "二维码地址")
|
||||||
|
private String qrCodeUrl;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,12 +33,12 @@ public class PayRefundReqDTO {
|
|||||||
*
|
*
|
||||||
* 目前微信支付在退款的时候,必须传递该字段
|
* 目前微信支付在退款的时候,必须传递该字段
|
||||||
*/
|
*/
|
||||||
private Long payPrice;
|
private Integer payPrice;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 退款金额,单位:分
|
* 退款金额,单位:分
|
||||||
*/
|
*/
|
||||||
private Long refundPrice;
|
private Integer refundPrice;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户ID
|
* 用户ID
|
||||||
|
|||||||
@ -1,72 +0,0 @@
|
|||||||
package com.seer.teach.pay.app.client.factory;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
||||||
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
|
||||||
import com.seer.teach.common.enums.ResultCodeEnum;
|
|
||||||
import com.seer.teach.common.utils.AssertUtils;
|
|
||||||
import com.seer.teach.pay.app.client.PayClient;
|
|
||||||
import com.seer.teach.pay.entity.PayChannelEntity;
|
|
||||||
import com.seer.teach.pay.service.IPayChannelService;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 支付策略工厂,根据支付渠道编码获取对应的支付策略
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class PayClientFactory {
|
|
||||||
|
|
||||||
private final List<PayClient> payStrategies;
|
|
||||||
|
|
||||||
private final IPayChannelService payChannelService;
|
|
||||||
|
|
||||||
private static final Map<PayChannelEnum, PayClient> STRATEGY_MAP = new HashMap<>();
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void init() {
|
|
||||||
for (PayClient strategy : payStrategies) {
|
|
||||||
STRATEGY_MAP.put(strategy.getChannel(), strategy);
|
|
||||||
log.info("注册支付渠道:{} -> {}", strategy.getChannel(), strategy.getClass().getSimpleName());
|
|
||||||
}
|
|
||||||
List<PayChannelEntity> channels = payChannelService.list(new LambdaUpdateWrapper<>(PayChannelEntity.class).eq(PayChannelEntity::getStatus, 1));
|
|
||||||
AssertUtils.notEmpty(channels, ResultCodeEnum.PAY_CHANNEL_IS_NOT_EXIST);
|
|
||||||
Integer count = 0;
|
|
||||||
for (PayChannelEntity channel : channels) {
|
|
||||||
PayClient strategy = STRATEGY_MAP.get(PayChannelEnum.fromCode(channel.getChannelCode()));
|
|
||||||
if(Objects.nonNull(strategy) && StringUtils.hasText(channel.getConfigJson())){
|
|
||||||
strategy.init(channel.getConfigJson());
|
|
||||||
log.info("支付渠道:{} -> {}", channel.getChannelCode(), strategy.getClass().getSimpleName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("支付策略工厂初始化完成,共注册{}个策略", count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据支付渠道编码获取支付策略
|
|
||||||
* @param channel 支付渠道编码(如:WECHAT、ALIPAY)
|
|
||||||
* @return 支付策略
|
|
||||||
*/
|
|
||||||
public PayClient getPayClient(PayChannelEnum channel) {
|
|
||||||
PayClient strategy = STRATEGY_MAP.get(channel);
|
|
||||||
AssertUtils.notNull(strategy, ResultCodeEnum.PAY_CHANNEL_NOT_SUPPORTED);
|
|
||||||
return strategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PayClient getPayClient(String channelCode) {
|
|
||||||
PayChannelEnum channel = PayChannelEnum.fromCode(channelCode);
|
|
||||||
AssertUtils.notNull(channel, ResultCodeEnum.PAY_CHANNEL_IS_NOT_EXIST);
|
|
||||||
PayClient strategy = STRATEGY_MAP.get(channel);
|
|
||||||
AssertUtils.notNull(strategy, ResultCodeEnum.PAY_CHANNEL_NOT_SUPPORTED);
|
|
||||||
return strategy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,346 @@
|
|||||||
|
package com.seer.teach.pay.app.client.wechat;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||||
|
import cn.hutool.core.date.TemporalAccessorUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
|
||||||
|
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
|
||||||
|
import com.github.binarywang.wxpay.bean.request.WxPayOrderQueryV3Request;
|
||||||
|
import com.github.binarywang.wxpay.bean.request.WxPayRefundQueryV3Request;
|
||||||
|
import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
|
||||||
|
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
|
||||||
|
import com.github.binarywang.wxpay.bean.result.WxPayOrderCloseResult;
|
||||||
|
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
|
||||||
|
import com.github.binarywang.wxpay.bean.result.WxPayRefundQueryV3Result;
|
||||||
|
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
|
||||||
|
import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult;
|
||||||
|
import com.github.binarywang.wxpay.config.WxPayConfig;
|
||||||
|
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||||
|
import com.github.binarywang.wxpay.service.WxPayService;
|
||||||
|
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
|
||||||
|
import com.seer.teach.common.enums.ResultCodeEnum;
|
||||||
|
import com.seer.teach.common.enums.pay.PayStatusEnum;
|
||||||
|
import com.seer.teach.common.enums.pay.RefundStatusEnum;
|
||||||
|
import com.seer.teach.common.exception.CommonException;
|
||||||
|
import com.seer.teach.common.utils.DateUtils;
|
||||||
|
import com.seer.teach.pay.app.client.AbstractPayClient;
|
||||||
|
import com.seer.teach.pay.app.client.dto.PayOrderCallBackRespDTO;
|
||||||
|
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
||||||
|
import com.seer.teach.pay.app.client.dto.PayOrderRespDTO;
|
||||||
|
import com.seer.teach.pay.app.client.dto.PayRefundReqDTO;
|
||||||
|
import com.seer.teach.pay.app.client.dto.RefundNotificationRespDTO;
|
||||||
|
import com.seer.teach.pay.app.util.ConfigDecryptUtil;
|
||||||
|
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static cn.hutool.core.date.DatePattern.UTC_WITH_XXX_OFFSET_PATTERN;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AbstractWxPayClient extends AbstractPayClient {
|
||||||
|
|
||||||
|
|
||||||
|
protected WxPayService client;
|
||||||
|
protected String notifyUrl;
|
||||||
|
|
||||||
|
protected String refundNotifyUrl;
|
||||||
|
|
||||||
|
public AbstractWxPayClient(String channelCode, String configJson) {
|
||||||
|
super(channelCode, configJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
* @param tradeType
|
||||||
|
*/
|
||||||
|
protected void doInit(String tradeType) {
|
||||||
|
WxPayConfig wxPayConfig = new WxPayConfig();
|
||||||
|
wxPayConfig.setTradeType(tradeType);
|
||||||
|
JSONObject channelConfig = JSONUtil.parseObj(config);
|
||||||
|
// 从渠道配置获取必要参数,并进行解密处理
|
||||||
|
String appId = channelConfig.getStr("appId");
|
||||||
|
String mchId = channelConfig.getStr("mchId");
|
||||||
|
String apiV3Key = channelConfig.getStr("apiV3Key");
|
||||||
|
String publicKeyId = channelConfig.getStr("publicKeyId");
|
||||||
|
String merchantSerialNumber = channelConfig.getStr("merchantSerialNumber");
|
||||||
|
String privateKeyPem = channelConfig.getStr("privateKeyPem");
|
||||||
|
String publicKeyPem = channelConfig.getStr("publicKeyPem");
|
||||||
|
this.notifyUrl = channelConfig.getStr("jsPayNotifyUrl");
|
||||||
|
this.refundNotifyUrl = channelConfig.getStr("jsRefundNotifyUrl");
|
||||||
|
// 使用安全解密方法处理敏感配置参数
|
||||||
|
apiV3Key = ConfigDecryptUtil.safeDecrypt(apiV3Key);
|
||||||
|
publicKeyId = ConfigDecryptUtil.safeDecrypt(publicKeyId);
|
||||||
|
merchantSerialNumber = ConfigDecryptUtil.safeDecrypt(merchantSerialNumber);
|
||||||
|
// 对PEM格式证书使用特殊处理
|
||||||
|
privateKeyPem = cleanPemContent(ConfigDecryptUtil.safeDecrypt(privateKeyPem, true));
|
||||||
|
publicKeyPem = cleanPemContent(ConfigDecryptUtil.safeDecrypt(publicKeyPem, true));
|
||||||
|
|
||||||
|
wxPayConfig.setAppId(ConfigDecryptUtil.safeDecrypt(appId));
|
||||||
|
wxPayConfig.setMchId(ConfigDecryptUtil.safeDecrypt(mchId));
|
||||||
|
wxPayConfig.setApiV3Key(ConfigDecryptUtil.safeDecrypt(apiV3Key));
|
||||||
|
wxPayConfig.setCertSerialNo(merchantSerialNumber);
|
||||||
|
|
||||||
|
wxPayConfig.setPublicKeyId(ConfigDecryptUtil.safeDecrypt(publicKeyId));
|
||||||
|
wxPayConfig.setPublicKeyString(publicKeyPem);
|
||||||
|
|
||||||
|
wxPayConfig.setPrivateCertString(privateKeyPem);
|
||||||
|
|
||||||
|
wxPayConfig.setNotifyUrl(notifyUrl);
|
||||||
|
wxPayConfig.setRefundNotifyUrl(refundNotifyUrl);
|
||||||
|
|
||||||
|
client = new WxPayServiceImpl();
|
||||||
|
client.setConfig(wxPayConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PayOrderCallBackRespDTO parseOrderNotify(Map<String, String> headers, Map<String, String> params, String body) {
|
||||||
|
SignatureHeader signatureHeader = getRequestHeader(headers);
|
||||||
|
TransferBillsNotifyResult response = null;
|
||||||
|
try {
|
||||||
|
response = client.getTransferService().parseTransferBillsNotifyResult(body, signatureHeader);
|
||||||
|
|
||||||
|
TransferBillsNotifyResult.DecryptNotifyResult result = response.getResult();
|
||||||
|
|
||||||
|
PayOrderCallBackRespDTO payOrderCallBackRespDTO = new PayOrderCallBackRespDTO();
|
||||||
|
payOrderCallBackRespDTO.setPayOrderSn(result.getOutBillNo());
|
||||||
|
payOrderCallBackRespDTO.setPayStatus(PayStatusEnum.fromWxOrderCode(result.getState()));
|
||||||
|
payOrderCallBackRespDTO.setPaySuccessTime(DateUtils.parseStringToLocalDateTime(result.getUpdateTime()));
|
||||||
|
payOrderCallBackRespDTO.setTransactionId(result.getTransferBillNo());
|
||||||
|
payOrderCallBackRespDTO.setChannelErrorCode(result.getState());
|
||||||
|
payOrderCallBackRespDTO.setChannelErrorMsg(result.getFailReason());
|
||||||
|
payOrderCallBackRespDTO.setResponse(JSONUtil.toJsonStr(result));
|
||||||
|
return payOrderCallBackRespDTO;
|
||||||
|
} catch (WxPayException e) {
|
||||||
|
log.error("微信回调失败", e);
|
||||||
|
throw new CommonException(ResultCodeEnum.WX_PREPAY_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PayOrderRespDTO doQueryPayStatus(String orderSn) {
|
||||||
|
WxPayOrderQueryV3Request request = new WxPayOrderQueryV3Request()
|
||||||
|
.setOutTradeNo(orderSn);
|
||||||
|
try {
|
||||||
|
WxPayOrderQueryV3Result response = client.queryOrderV3(request);
|
||||||
|
PayOrderRespDTO payOrderRespDTO = new PayOrderRespDTO();
|
||||||
|
payOrderRespDTO.setChannelCode(channelCode);
|
||||||
|
payOrderRespDTO.setPayOrderSn(orderSn);
|
||||||
|
payOrderRespDTO.setStatus(parseStatus(response.getTradeState()));
|
||||||
|
payOrderRespDTO.setPaySuccessTime(parseDateV3(response.getSuccessTime()));
|
||||||
|
payOrderRespDTO.setTransactionId(response.getTransactionId());
|
||||||
|
payOrderRespDTO.setResponse(JSONUtil.toJsonStr(response));
|
||||||
|
return payOrderRespDTO;
|
||||||
|
} catch (WxPayException e) {
|
||||||
|
log.error("微信查询订单失败", e);
|
||||||
|
throw new CommonException(ResultCodeEnum.WX_PREPAY_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RefundSubmitRespDTO doRefundOrder(PayRefundReqDTO reqDTO) {
|
||||||
|
WxPayRefundV3Request request = new WxPayRefundV3Request()
|
||||||
|
.setOutTradeNo(reqDTO.getOutTradeNo())
|
||||||
|
.setOutRefundNo(reqDTO.getOutRefundNo())
|
||||||
|
.setAmount(new WxPayRefundV3Request.Amount().setRefund(reqDTO.getRefundPrice())
|
||||||
|
.setTotal(reqDTO.getPayPrice()).setCurrency("CNY"))
|
||||||
|
.setReason(reqDTO.getReason())
|
||||||
|
.setNotifyUrl(this.refundNotifyUrl);
|
||||||
|
try {
|
||||||
|
WxPayRefundV3Result response = client.refundV3(request);
|
||||||
|
|
||||||
|
RefundSubmitRespDTO refundSubmitRespDTO = new RefundSubmitRespDTO();
|
||||||
|
refundSubmitRespDTO.setRefundId(response.getRefundId());
|
||||||
|
refundSubmitRespDTO.setTransactionId(response.getTransactionId());
|
||||||
|
refundSubmitRespDTO.setPayOrderSn(reqDTO.getOutTradeNo());
|
||||||
|
refundSubmitRespDTO.setRefundOrderSn(reqDTO.getOutRefundNo());
|
||||||
|
refundSubmitRespDTO.setMerchantRefundSn(response.getOutRefundNo());
|
||||||
|
refundSubmitRespDTO.setSuccessTime(parseDateV3(response.getSuccessTime()));
|
||||||
|
refundSubmitRespDTO.setStatus(PayStatusEnum.SUCCESS.getCode());
|
||||||
|
refundSubmitRespDTO.setRefundResJson(JSONUtil.toJsonStr(response));
|
||||||
|
|
||||||
|
String status = response.getStatus();
|
||||||
|
if (status.equals("PROCESSING")) {
|
||||||
|
refundSubmitRespDTO.setStatus(RefundStatusEnum.PENDING_WECHAT_REFUND.getCode());
|
||||||
|
} else if (status.equals("SUCCESS")) {
|
||||||
|
refundSubmitRespDTO.setStatus(RefundStatusEnum.REFUND_SUCCESS.getCode());
|
||||||
|
} else {
|
||||||
|
refundSubmitRespDTO.setStatus(RefundStatusEnum.WECHAT_REFUND_FAILED.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
return refundSubmitRespDTO;
|
||||||
|
} catch (WxPayException e) {
|
||||||
|
throw new CommonException(ResultCodeEnum.REFUND_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Optional<RefundNotificationRespDTO> parseRefundNotify(Map<String, String> headers, Map<String, String> params, String body) {
|
||||||
|
SignatureHeader signatureHeader = getRequestHeader(headers);
|
||||||
|
try {
|
||||||
|
WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, signatureHeader);
|
||||||
|
WxPayRefundNotifyV3Result.DecryptNotifyResult result = response.getResult();
|
||||||
|
|
||||||
|
RefundNotificationRespDTO refundNotificationRespDTO = new RefundNotificationRespDTO();
|
||||||
|
refundNotificationRespDTO.setRefundId(result.getRefundId());
|
||||||
|
refundNotificationRespDTO.setTransactionId(result.getTransactionId());
|
||||||
|
refundNotificationRespDTO.setSuccessTime(parseDateV3(result.getSuccessTime()));
|
||||||
|
refundNotificationRespDTO.setRefundStatus(RefundStatusEnum.fromWxCode(result.getRefundStatus()));
|
||||||
|
|
||||||
|
return Optional.of(refundNotificationRespDTO);
|
||||||
|
} catch (WxPayException e) {
|
||||||
|
throw new CommonException(ResultCodeEnum.REFUND_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RefundSubmitRespDTO doQueryRefundStatus(String refundSn) {
|
||||||
|
WxPayRefundQueryV3Request request = new WxPayRefundQueryV3Request();
|
||||||
|
request.setOutRefundNo(refundSn);
|
||||||
|
try {
|
||||||
|
WxPayRefundQueryV3Result response = client.refundQueryV3(request);
|
||||||
|
|
||||||
|
RefundStatusEnum refundStatusEnum = RefundStatusEnum.fromWxCode(response.getStatus());
|
||||||
|
|
||||||
|
RefundSubmitRespDTO refundSubmitRespDTO = new RefundSubmitRespDTO();
|
||||||
|
refundSubmitRespDTO.setRefundId(response.getRefundId());
|
||||||
|
refundSubmitRespDTO.setTransactionId(response.getTransactionId());
|
||||||
|
refundSubmitRespDTO.setPayOrderSn(response.getOutTradeNo());
|
||||||
|
refundSubmitRespDTO.setRefundOrderSn(refundSn);
|
||||||
|
refundSubmitRespDTO.setMerchantRefundSn(response.getOutRefundNo());
|
||||||
|
refundSubmitRespDTO.setSuccessTime(parseDateV3(response.getSuccessTime()));
|
||||||
|
refundSubmitRespDTO.setStatus(refundStatusEnum.getCode());
|
||||||
|
refundSubmitRespDTO.setRefundResJson(JSONUtil.toJsonStr(response));
|
||||||
|
return refundSubmitRespDTO;
|
||||||
|
} catch (WxPayException e) {
|
||||||
|
throw new CommonException(ResultCodeEnum.REFUND_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String doClosePayOrder(String orderSn) {
|
||||||
|
try {
|
||||||
|
WxPayOrderCloseResult closeResult = client.closeOrder(orderSn);
|
||||||
|
return closeResult.getResultMsg();
|
||||||
|
} catch (WxPayException e) {
|
||||||
|
log.error("微信关闭订单失败", e);
|
||||||
|
throw new CommonException(ResultCodeEnum.REFUND_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WxPayUnifiedOrderV3Request buildPayRequestV3(PayOrderReqDTO reqDTO) {
|
||||||
|
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
||||||
|
request.setOutTradeNo(reqDTO.getPayOrderSn());
|
||||||
|
request.setDescription(reqDTO.getDescription());
|
||||||
|
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getTotalAmount())); // 单位分
|
||||||
|
request.setTimeExpire(formatDateV3(reqDTO.getExpireTime()));
|
||||||
|
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
|
||||||
|
request.setNotifyUrl(getNotifyUrl());
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String formatDateV3(LocalDateTime time) {
|
||||||
|
return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), UTC_WITH_XXX_OFFSET_PATTERN);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LocalDateTime parseDateV3(String time) {
|
||||||
|
return LocalDateTimeUtil.parse(time, UTC_WITH_XXX_OFFSET_PATTERN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SignatureHeader getRequestHeader(Map<String, String> headers) {
|
||||||
|
return SignatureHeader.builder()
|
||||||
|
.signature(getHeaderValue(headers, "Wechatpay-Signature", "wechatpay-signature"))
|
||||||
|
.nonce(getHeaderValue(headers, "Wechatpay-Nonce", "wechatpay-nonce"))
|
||||||
|
.serial(getHeaderValue(headers, "Wechatpay-Serial", "wechatpay-serial"))
|
||||||
|
.timeStamp(getHeaderValue(headers, "Wechatpay-Timestamp", "wechatpay-timestamp"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHeaderValue(Map<String, String> headers, String capitalizedKey, String lowercaseKey) {
|
||||||
|
String value = headers.get(capitalizedKey);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return headers.get(lowercaseKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected String getNotifyUrl() {
|
||||||
|
return notifyUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Integer parseStatus(String tradeState) {
|
||||||
|
switch (tradeState) {
|
||||||
|
case "NOTPAY":
|
||||||
|
case "USERPAYING":
|
||||||
|
return PayStatusEnum.PENDING.getCode();
|
||||||
|
case "SUCCESS":
|
||||||
|
return PayStatusEnum.SUCCESS.getCode();
|
||||||
|
case "REFUND":
|
||||||
|
return PayStatusEnum.WECHAT_REFUND_SUCCESS.getCode();
|
||||||
|
case "CLOSED":
|
||||||
|
case "REVOKED":
|
||||||
|
case "PAYERROR":
|
||||||
|
return PayStatusEnum.PAY_FAILED.getCode();
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(StrUtil.format("未知的支付状态({})", tradeState));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理PEM内容,确保符合微信SDK要求
|
||||||
|
*
|
||||||
|
* @param pemContent PEM内容
|
||||||
|
* @return 清理后的PEM内容
|
||||||
|
*/
|
||||||
|
protected String cleanPemContent(String pemContent) {
|
||||||
|
if (pemContent == null || pemContent.isEmpty()) {
|
||||||
|
return pemContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 移除所有反斜杠
|
||||||
|
pemContent = pemContent.replace("\\", "");
|
||||||
|
|
||||||
|
// 确保正确的换行符
|
||||||
|
pemContent = pemContent.replace("\\n", "\n")
|
||||||
|
.replace("\r\n", "\n")
|
||||||
|
.replace("\r", "\n");
|
||||||
|
|
||||||
|
// 移除多余的空白字符,但要确保BEGIN和END标记格式正确
|
||||||
|
pemContent = pemContent.trim();
|
||||||
|
|
||||||
|
// 确保BEGIN和END标记格式正确(包含正确的空格)
|
||||||
|
pemContent = pemContent.replace("-----BEGINPRIVATEKEY-----", "-----BEGIN PRIVATE KEY-----")
|
||||||
|
.replace("-----ENDPRIVATEKEY-----", "-----END PRIVATE KEY-----")
|
||||||
|
.replace("-----BEGINPUBLICKEY-----", "-----BEGIN PUBLIC KEY-----")
|
||||||
|
.replace("-----ENDPUBLICKEY-----", "-----END PUBLIC KEY-----");
|
||||||
|
|
||||||
|
// 确保BEGIN和END标记独立成行
|
||||||
|
if (pemContent.contains("-----BEGIN")) {
|
||||||
|
// 先标准化标记格式
|
||||||
|
pemContent = pemContent.replaceAll("-----BEGIN PRIVATE KEY-----", "\n-----BEGIN PRIVATE KEY-----\n")
|
||||||
|
.replaceAll("-----END PRIVATE KEY-----", "\n-----END PRIVATE KEY-----\n")
|
||||||
|
.replaceAll("-----BEGIN PUBLIC KEY-----", "\n-----BEGIN PUBLIC KEY-----\n")
|
||||||
|
.replaceAll("-----END PUBLIC KEY-----", "\n-----END PUBLIC KEY-----\n")
|
||||||
|
.replaceAll("\n+", "\n") // 将多个连续换行符合并为一个
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// 确保标记前后有换行符
|
||||||
|
pemContent = "\n" + pemContent + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("PEM内容清理完成,长度: {}", pemContent.length());
|
||||||
|
return pemContent;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("PEM内容清理异常: {}", e.getMessage());
|
||||||
|
return pemContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,48 +1,35 @@
|
|||||||
package com.seer.teach.pay.app.client.wechat;
|
package com.seer.teach.pay.app.client.wechat;
|
||||||
|
|
||||||
import cn.hutool.json.JSONUtil;
|
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
|
||||||
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
||||||
import com.seer.teach.common.utils.DateUtils;
|
import com.seer.teach.common.constants.CommonConstant;
|
||||||
import com.seer.teach.common.enums.pay.PayStatusEnum;
|
|
||||||
import com.seer.teach.common.enums.ResultCodeEnum;
|
import com.seer.teach.common.enums.ResultCodeEnum;
|
||||||
|
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
||||||
|
import com.seer.teach.common.enums.pay.PayStatusEnum;
|
||||||
import com.seer.teach.common.exception.CommonException;
|
import com.seer.teach.common.exception.CommonException;
|
||||||
import com.seer.teach.common.utils.AssertUtils;
|
|
||||||
import com.seer.teach.common.utils.MoneyUtil;
|
import com.seer.teach.common.utils.MoneyUtil;
|
||||||
import com.seer.teach.pay.app.client.AbstractPayClient;
|
|
||||||
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
||||||
import com.seer.teach.pay.app.client.dto.RefundNotificationRespDTO;
|
|
||||||
import com.seer.teach.pay.app.client.wechat.core.WechatPlayClient;
|
|
||||||
import com.seer.teach.pay.app.client.wechat.core.resp.WechatPrepayTransactionResp;
|
|
||||||
import com.seer.teach.pay.app.client.dto.PayOrderCallBackRespDTO;
|
|
||||||
import com.seer.teach.pay.app.client.dto.PayOrderRespDTO;
|
import com.seer.teach.pay.app.client.dto.PayOrderRespDTO;
|
||||||
import com.seer.teach.pay.app.client.dto.PayRefundReqDTO;
|
|
||||||
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
|
||||||
import com.seer.teach.pay.entity.PayOrderEntity;
|
|
||||||
import com.seer.teach.pay.entity.PayOrderExtensionEntity;
|
|
||||||
import com.seer.teach.user.api.UserInfoServiceApi;
|
|
||||||
import com.seer.teach.user.api.dto.UserAuthDTO;
|
|
||||||
import com.wechat.pay.java.service.payments.model.Transaction;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.Objects;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 微信支付策略实现
|
* 微信支付策略实现
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
public class WechatJsApiPayClient extends AbstractWxPayClient {
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class WechatJsApiPayClient extends AbstractPayClient {
|
|
||||||
|
|
||||||
private WechatPlayClient wechatPlayClient;
|
public WechatJsApiPayClient(String configJson){
|
||||||
|
super(PayChannelEnum.WECHAT_JSAPI.getCode(), configJson);
|
||||||
|
}
|
||||||
|
|
||||||
private final UserInfoServiceApi userInfoServiceApi;
|
@Override
|
||||||
|
public void init() {
|
||||||
|
super.doInit(WxPayConstants.TradeType.JSAPI);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取支付渠道的编码
|
* 获取支付渠道的编码
|
||||||
@ -60,75 +47,55 @@ public class WechatJsApiPayClient extends AbstractPayClient {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(String configJson) {
|
|
||||||
wechatPlayClient = new WechatPlayClient(configJson);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object getPrepayRequestParam(PayOrderReqDTO payOrder) {
|
|
||||||
UserAuthDTO userAuthDTO = userInfoServiceApi.getUserAuthByUserIdAndAppId(payOrder.getUserId(), wechatPlayClient.getAppId());
|
|
||||||
return userAuthDTO.getOpenId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建支付订单
|
* 创建支付订单
|
||||||
*
|
*
|
||||||
* @param payOrder 支付订单实体
|
* @param reqDTO 支付订单实体
|
||||||
* @return 创建的支付订单结果
|
* @return 创建的支付订单结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected PayOrderRespDTO doPayOrder(PayOrderReqDTO payOrder, Object extrasRequest) {
|
protected PayOrderRespDTO doPayOrder(PayOrderReqDTO reqDTO) {
|
||||||
try {
|
try {
|
||||||
// 调用微信预支付接口时传入配置
|
// 调用微信预支付接口时传入配置
|
||||||
log.info("准备调用微信预支付接口");
|
log.info("准备调用微信预支付接口");
|
||||||
WechatPrepayTransactionResp wechatResp = wechatPlayClient.prepayPay(payOrder, extrasRequest);
|
WxPayUnifiedOrderV3Request request = buildPayRequestV3(reqDTO);
|
||||||
|
|
||||||
|
WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
|
||||||
|
payer.setOpenid(reqDTO.getChannelExtras().get(CommonConstant.WX_OPEN_ID));
|
||||||
|
WxPayUnifiedOrderV3Result.JsapiResult jsapiResult = client.createOrderV3(TradeTypeEnum.JSAPI, request);
|
||||||
|
|
||||||
log.info("微信预支付接口调用完成");
|
log.info("微信预支付接口调用完成");
|
||||||
// 检查微信预支付响应是否为空
|
// 检查微信预支付响应是否为空
|
||||||
if (wechatResp == null) {
|
if (Objects.isNull(jsapiResult)) {
|
||||||
log.error("微信预支付接口返回空响应,订单号:{}", payOrder.getPayOrderSn());
|
log.error("微信预支付接口返回空响应,订单号:{}", reqDTO.getPayOrderSn());
|
||||||
throw new CommonException(ResultCodeEnum.WX_PREPAY_ERROR, "微信预支付接口返回空响应");
|
throw new CommonException(ResultCodeEnum.WX_PREPAY_ERROR, "微信预支付接口返回空响应");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("微信预支付接口调用成功,响应数据:{}", wechatResp);
|
log.info("微信预支付接口调用成功,响应数据:{}", jsapiResult);
|
||||||
|
|
||||||
// 检查响应中的关键字段
|
|
||||||
if (wechatResp.getPackageVal() == null || wechatResp.getPackageVal().isEmpty()) {
|
|
||||||
log.error("微信预支付响应中packageVal为空,订单号:{}", payOrder.getPayOrderSn());
|
|
||||||
throw new CommonException(ResultCodeEnum.WX_PREPAY_ERROR, "微信预支付响应中packageVal为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构造返回结果
|
// 构造返回结果
|
||||||
PayOrderRespDTO response = new PayOrderRespDTO();
|
PayOrderRespDTO response = new PayOrderRespDTO();
|
||||||
response.setChannelCode(payOrder.getChannelCode());
|
response.setChannelCode(reqDTO.getChannelCode());
|
||||||
response.setStatus(PayStatusEnum.PENDING.getCode());
|
response.setStatus(PayStatusEnum.PENDING.getCode());
|
||||||
|
|
||||||
// 构造微信支付参数
|
response.setPayParams(jsapiResult);
|
||||||
Map<String, Object> payParams = new HashMap<>();
|
|
||||||
payParams.put("appId", wechatResp.getAppId());
|
|
||||||
payParams.put("timeStamp", wechatResp.getTimeStamp());
|
|
||||||
payParams.put("nonceStr", wechatResp.getNonceStr());
|
|
||||||
payParams.put("package", wechatResp.getPackageVal());
|
|
||||||
payParams.put("signType", wechatResp.getSignType());
|
|
||||||
payParams.put("paySign", wechatResp.getPaySign());
|
|
||||||
response.setPayParams(payParams);
|
|
||||||
|
|
||||||
// 从 packageVal 中提取 prepayId
|
// 从 packageVal 中提取 prepayId
|
||||||
String prepayId = extractPrepayIdFromPackage(wechatResp.getPackageVal());
|
String prepayId = extractPrepayIdFromPackage(jsapiResult.getPackageValue());
|
||||||
response.setPrepayId(prepayId);
|
response.setPrepayId(prepayId);
|
||||||
|
|
||||||
log.info("微信预支付订单创建成功,订单号:{},支付金额:{}元,prepayId:{}",
|
log.info("微信预支付订单创建成功,订单号:{},支付金额:{}元,prepayId:{}",
|
||||||
payOrder.getPayOrderSn(), MoneyUtil.centToYuan(payOrder.getTotalAmount()), prepayId);
|
reqDTO.getPayOrderSn(), MoneyUtil.centToYuan(reqDTO.getTotalAmount()), prepayId);
|
||||||
|
|
||||||
// 验证关键字段是否成功设置
|
// 验证关键字段是否成功设置
|
||||||
if (response.getPrepayId() == null || response.getPrepayId().isEmpty()) {
|
if (response.getPrepayId() == null || response.getPrepayId().isEmpty()) {
|
||||||
log.error("prepayId为空,订单号:{}", payOrder.getPayOrderSn());
|
log.error("prepayId为空,订单号:{}", reqDTO.getPayOrderSn());
|
||||||
throw new CommonException(ResultCodeEnum.WX_PREPAY_ERROR, "prepayId为空");
|
throw new CommonException(ResultCodeEnum.WX_PREPAY_ERROR, "prepayId为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.getPayParams() == null) {
|
if (response.getPayParams() == null) {
|
||||||
log.error("payParams为空,订单号:{}", payOrder.getPayOrderSn());
|
log.error("payParams为空,订单号:{}", reqDTO.getPayOrderSn());
|
||||||
throw new CommonException(ResultCodeEnum.WX_PREPAY_ERROR, "payParams为空");
|
throw new CommonException(ResultCodeEnum.WX_PREPAY_ERROR, "payParams为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,96 +103,11 @@ public class WechatJsApiPayClient extends AbstractPayClient {
|
|||||||
return response;
|
return response;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("创建微信预支付订单失败,订单号:{},支付金额:{}元",
|
log.error("创建微信预支付订单失败,订单号:{},支付金额:{}元",
|
||||||
payOrder.getPayOrderSn(), MoneyUtil.centToYuan(payOrder.getTotalAmount()), e);
|
reqDTO.getPayOrderSn(), MoneyUtil.centToYuan(reqDTO.getTotalAmount()), e);
|
||||||
throw new CommonException(ResultCodeEnum.WX_PREPAY_ERROR, "微信预支付失败: " + e.getMessage());
|
throw new CommonException(ResultCodeEnum.WX_PREPAY_ERROR, "微信预支付失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询支付状态
|
|
||||||
*
|
|
||||||
* @param orderSn 订单号
|
|
||||||
* @return 支付订单
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected PayOrderEntity doQueryPayStatus(String orderSn) {
|
|
||||||
log.info("查询微信支付状态,订单号:{}", orderSn);
|
|
||||||
PayOrderEntity payOrder = payOrderService.getOne(
|
|
||||||
new LambdaQueryWrapper<PayOrderEntity>()
|
|
||||||
.eq(PayOrderEntity::getOrderSn, orderSn)
|
|
||||||
.eq(PayOrderEntity::getChannelCode, getChannel())
|
|
||||||
);
|
|
||||||
if (payOrder == null) {
|
|
||||||
log.warn("支付订单不存在,订单号:{}", orderSn);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (Integer.valueOf(PayStatusEnum.SUCCESS.getCode()).equals(payOrder.getStatus()) ||
|
|
||||||
Integer.valueOf(PayStatusEnum.PAY_FAILED.getCode()).equals(payOrder.getStatus())) {
|
|
||||||
return payOrder;
|
|
||||||
}
|
|
||||||
return payOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PayOrderCallBackRespDTO parseOrderNotify(Map<String, String> headers, Map<String, String> params, String body) {
|
|
||||||
Transaction transaction = wechatPlayClient.parseOrderNotifyV3(headers, body).orElseThrow(() -> new CommonException(ResultCodeEnum.WX_PREPAY_ERROR));
|
|
||||||
PayOrderCallBackRespDTO payOrderCallBackRespDTO = new PayOrderCallBackRespDTO();
|
|
||||||
payOrderCallBackRespDTO.setPayOrderSn(transaction.getOutTradeNo());
|
|
||||||
payOrderCallBackRespDTO.setPayStatus(PayStatusEnum.fromWxOrderCode(transaction.getTradeState().name()));
|
|
||||||
payOrderCallBackRespDTO.setPaySuccessTime(DateUtils.parseStringToLocalDateTime(transaction.getSuccessTime()));
|
|
||||||
payOrderCallBackRespDTO.setTransactionId(transaction.getTransactionId());
|
|
||||||
payOrderCallBackRespDTO.setResponse(JSONUtil.toJsonStr(transaction));
|
|
||||||
return payOrderCallBackRespDTO;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭支付订单
|
|
||||||
*
|
|
||||||
* @param orderSn 订单号
|
|
||||||
* @return 处理结果
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected boolean doClosePayOrder(String orderSn) {
|
|
||||||
try {
|
|
||||||
log.info("关闭微信支付订单,订单号:{}", orderSn);
|
|
||||||
// 查询订单
|
|
||||||
PayOrderEntity payOrder = payOrderService.getOne(
|
|
||||||
new LambdaQueryWrapper<PayOrderEntity>()
|
|
||||||
.eq(PayOrderEntity::getOrderSn, orderSn)
|
|
||||||
.eq(PayOrderEntity::getChannelCode, getChannel()));
|
|
||||||
AssertUtils.notNull(payOrder, ResultCodeEnum.ORDER_NOT_FOUND);
|
|
||||||
// 只有待支付状态的订单才能关闭
|
|
||||||
if (!Integer.valueOf(PayStatusEnum.PENDING.getCode()).equals(payOrder.getStatus())) {
|
|
||||||
log.warn("订单状态不允许关闭,订单号:{},当前状态:{}", orderSn, payOrder.getStatus());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 更新订单状态为已取消
|
|
||||||
boolean updateResult = payOrderService.update(
|
|
||||||
new LambdaUpdateWrapper<PayOrderEntity>()
|
|
||||||
.set(PayOrderEntity::getStatus, PayStatusEnum.CANCEL.getCode())
|
|
||||||
.eq(PayOrderEntity::getId, payOrder.getId()));
|
|
||||||
// 更新扩展表
|
|
||||||
if (updateResult) {
|
|
||||||
payOrderExtensionService.update(
|
|
||||||
new LambdaUpdateWrapper<PayOrderExtensionEntity>()
|
|
||||||
.eq(PayOrderExtensionEntity::getOrderId, payOrder.getId()));
|
|
||||||
}
|
|
||||||
// 调用微信关闭订单接口
|
|
||||||
try {
|
|
||||||
// 获取支付渠道配置
|
|
||||||
boolean closeResult = wechatPlayClient.closeOrderWithConfig(orderSn);
|
|
||||||
log.info("调用微信关闭订单接口结果:{},订单号:{}", closeResult, orderSn);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("调用微信关闭订单接口失败,订单号:{},错误信息:{}", orderSn, e.getMessage(), e);
|
|
||||||
}
|
|
||||||
log.info("关闭微信支付订单结果:{},订单号:{}", updateResult, orderSn);
|
|
||||||
return updateResult;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("关闭微信支付订单失败,订单号:{}", orderSn, e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从 packageVal 中提取 prepayId
|
* 从 packageVal 中提取 prepayId
|
||||||
@ -242,62 +124,4 @@ public class WechatJsApiPayClient extends AbstractPayClient {
|
|||||||
log.warn("无法从 packageVal 中提取 prepayId:{}", packageVal);
|
log.warn("无法从 packageVal 中提取 prepayId:{}", packageVal);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected RefundSubmitRespDTO doRefundOrder(PayRefundReqDTO payRefundReqDTO) {
|
|
||||||
String outTradeNo = payRefundReqDTO.getOutTradeNo();
|
|
||||||
String refundReason = payRefundReqDTO.getReason();
|
|
||||||
Long refundAmount = payRefundReqDTO.getRefundPrice();
|
|
||||||
Long totalAmount = payRefundReqDTO.getPayPrice();
|
|
||||||
String refundSn = payRefundReqDTO.getOutRefundNo();
|
|
||||||
try {
|
|
||||||
log.info("开始处理微信退款订单:{}", payRefundReqDTO);
|
|
||||||
//调用微信客户端提交退款
|
|
||||||
RefundSubmitRespDTO wechatResp = wechatPlayClient.submitRefund(outTradeNo, refundReason, refundAmount, totalAmount, refundSn);
|
|
||||||
log.info("微信退款接口调用完成,订单号:{},退款单号:{}", outTradeNo, refundSn);
|
|
||||||
return wechatResp;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("处理微信退款订单失败,订单号:{},退款单号:{}", outTradeNo, refundSn, e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询退款状态
|
|
||||||
*
|
|
||||||
* @param refundSn 退款单号
|
|
||||||
* @return 退款订单信息
|
|
||||||
*/
|
|
||||||
public RefundSubmitRespDTO doQueryRefundStatus(String refundSn) {
|
|
||||||
try {
|
|
||||||
log.info("查询微信退款状态,退款单号:{}", refundSn);
|
|
||||||
|
|
||||||
// 调用微信查询退款接口
|
|
||||||
RefundSubmitRespDTO wechatResp = wechatPlayClient.queryRefund(refundSn);
|
|
||||||
|
|
||||||
log.info("微信查询退款接口调用完成,退款单号:{}", refundSn);
|
|
||||||
|
|
||||||
return wechatResp;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("查询微信退款状态失败,退款单号:{}", refundSn, e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理退款回调
|
|
||||||
*
|
|
||||||
* @param headers 回调请求头
|
|
||||||
* @param params 回调请求参数
|
|
||||||
* @param body 回调请求体
|
|
||||||
* @return RefundSubmitRespDTO
|
|
||||||
*/
|
|
||||||
public Optional<RefundNotificationRespDTO> parseRefundNotify(Map<String, String> headers, Map<String, String> params, String body) {
|
|
||||||
try {
|
|
||||||
log.info("开始处理微信退款回调");
|
|
||||||
return wechatPlayClient.parseRefundNotifyV3(headers, body);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("处理微信退款回调失败", e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
package com.seer.teach.pay.app.client.wechat;
|
||||||
|
|
||||||
|
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
|
||||||
|
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
|
||||||
|
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
||||||
|
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||||
|
import com.seer.teach.common.enums.ResultCodeEnum;
|
||||||
|
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
||||||
|
import com.seer.teach.common.enums.pay.PayStatusEnum;
|
||||||
|
import com.seer.teach.common.exception.CommonException;
|
||||||
|
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
||||||
|
import com.seer.teach.pay.app.client.dto.PayOrderRespDTO;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class WechatNativePayClient extends AbstractWxPayClient {
|
||||||
|
|
||||||
|
public WechatNativePayClient(String configJson) {
|
||||||
|
super(PayChannelEnum.WECHAT_NATIVE.getCode(), configJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
super.doInit(WxPayConstants.TradeType.NATIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PayOrderRespDTO doPayOrder(PayOrderReqDTO payOrderReqDTO) {
|
||||||
|
WxPayUnifiedOrderV3Request request = buildPayRequestV3(payOrderReqDTO);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String response = client.createOrderV3(TradeTypeEnum.NATIVE, request);
|
||||||
|
|
||||||
|
if(StringUtils.isNotEmpty(response)){
|
||||||
|
PayOrderRespDTO payOrderRespDTO = new PayOrderRespDTO();
|
||||||
|
payOrderRespDTO.setChannelCode(payOrderReqDTO.getChannelCode());
|
||||||
|
payOrderRespDTO.setPayOrderSn(payOrderReqDTO.getPayOrderSn());
|
||||||
|
payOrderRespDTO.setStatus(PayStatusEnum.PENDING.getCode());
|
||||||
|
payOrderRespDTO.setQrCodeUrl(response);
|
||||||
|
return payOrderRespDTO;
|
||||||
|
}
|
||||||
|
throw new CommonException(ResultCodeEnum.WX_PREPAY_ERROR, "微信二维码预支付失败:");
|
||||||
|
} catch (WxPayException e) {
|
||||||
|
throw new CommonException(ResultCodeEnum.WX_PREPAY_ERROR, "微信二维码预支付失败:");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PayChannelEnum getChannel() {
|
||||||
|
return PayChannelEnum.WECHAT_NATIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAsyncCallback() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,44 +1,69 @@
|
|||||||
package com.seer.teach.pay.app.client.wechat.core;
|
package com.seer.teach.pay.app.client.wechat.core;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.TemporalAccessorUtil;
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.json.JSONObject;
|
import cn.hutool.json.JSONObject;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.seer.teach.common.config.WxConfig;
|
||||||
import com.seer.teach.common.enums.ResultCodeEnum;
|
import com.seer.teach.common.enums.ResultCodeEnum;
|
||||||
import com.seer.teach.common.enums.pay.RefundStatusEnum;
|
import com.seer.teach.common.enums.pay.RefundStatusEnum;
|
||||||
import com.seer.teach.common.exception.CommonException;
|
import com.seer.teach.common.exception.CommonException;
|
||||||
import com.seer.teach.common.config.WxConfig;
|
|
||||||
import com.seer.teach.common.utils.DateUtils;
|
import com.seer.teach.common.utils.DateUtils;
|
||||||
|
import com.seer.teach.pay.app.client.convert.PayClientConvert;
|
||||||
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
||||||
import com.seer.teach.pay.app.client.dto.RefundNotificationRespDTO;
|
import com.seer.teach.pay.app.client.dto.RefundNotificationRespDTO;
|
||||||
import com.seer.teach.pay.app.client.convert.PayClientConvert;
|
|
||||||
import com.seer.teach.pay.app.client.wechat.core.resp.WechatPayOrderResp;
|
import com.seer.teach.pay.app.client.wechat.core.resp.WechatPayOrderResp;
|
||||||
import com.seer.teach.pay.app.client.wechat.core.resp.WechatPrepayTransactionResp;
|
import com.seer.teach.pay.app.client.wechat.core.resp.WechatPrepayTransactionResp;
|
||||||
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
|
||||||
import com.seer.teach.pay.app.client.wechat.core.resp.WechatTransferDetailResp;
|
import com.seer.teach.pay.app.client.wechat.core.resp.WechatTransferDetailResp;
|
||||||
import com.seer.teach.pay.app.client.wechat.core.resp.WechatTransferQueryResp;
|
import com.seer.teach.pay.app.client.wechat.core.resp.WechatTransferQueryResp;
|
||||||
import com.seer.teach.pay.app.client.wechat.core.resp.WechatTransferResp;
|
import com.seer.teach.pay.app.client.wechat.core.resp.WechatTransferResp;
|
||||||
import com.seer.teach.pay.app.util.ConfigDecryptUtil;
|
import com.seer.teach.pay.app.util.ConfigDecryptUtil;
|
||||||
import com.seer.teach.pay.entity.PayOrderEntity;
|
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
||||||
import com.wechat.pay.java.core.Config;
|
import com.wechat.pay.java.core.Config;
|
||||||
import com.wechat.pay.java.core.RSAPublicKeyConfig;
|
import com.wechat.pay.java.core.RSAPublicKeyConfig;
|
||||||
import com.wechat.pay.java.core.notification.NotificationConfig;
|
import com.wechat.pay.java.core.notification.NotificationConfig;
|
||||||
import com.wechat.pay.java.core.notification.NotificationParser;
|
import com.wechat.pay.java.core.notification.NotificationParser;
|
||||||
import com.wechat.pay.java.core.notification.RequestParam;
|
import com.wechat.pay.java.core.notification.RequestParam;
|
||||||
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
|
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
|
||||||
import com.wechat.pay.java.service.payments.jsapi.model.*;
|
|
||||||
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
|
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
|
||||||
|
import com.wechat.pay.java.service.payments.jsapi.model.CloseOrderRequest;
|
||||||
|
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
|
||||||
|
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
|
||||||
|
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
|
||||||
import com.wechat.pay.java.service.payments.model.Transaction;
|
import com.wechat.pay.java.service.payments.model.Transaction;
|
||||||
|
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
|
||||||
|
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
|
||||||
import com.wechat.pay.java.service.refund.RefundService;
|
import com.wechat.pay.java.service.refund.RefundService;
|
||||||
import com.wechat.pay.java.service.refund.model.*;
|
import com.wechat.pay.java.service.refund.model.AmountReq;
|
||||||
|
import com.wechat.pay.java.service.refund.model.CreateRequest;
|
||||||
|
import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest;
|
||||||
|
import com.wechat.pay.java.service.refund.model.Refund;
|
||||||
|
import com.wechat.pay.java.service.refund.model.RefundNotification;
|
||||||
import com.wechat.pay.java.service.transferbatch.TransferBatchService;
|
import com.wechat.pay.java.service.transferbatch.TransferBatchService;
|
||||||
import com.wechat.pay.java.service.transferbatch.model.*;
|
import com.wechat.pay.java.service.transferbatch.model.GetTransferBatchByOutNoRequest;
|
||||||
|
import com.wechat.pay.java.service.transferbatch.model.GetTransferDetailByOutNoRequest;
|
||||||
|
import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferRequest;
|
||||||
|
import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferResponse;
|
||||||
|
import com.wechat.pay.java.service.transferbatch.model.TransferBatchEntity;
|
||||||
|
import com.wechat.pay.java.service.transferbatch.model.TransferBatchGet;
|
||||||
|
import com.wechat.pay.java.service.transferbatch.model.TransferDetailCompact;
|
||||||
|
import com.wechat.pay.java.service.transferbatch.model.TransferDetailEntity;
|
||||||
|
import com.wechat.pay.java.service.transferbatch.model.TransferDetailInput;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
|
||||||
import java.util.*;
|
import java.time.ZoneId;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static cn.hutool.core.date.DatePattern.UTC_WITH_XXX_OFFSET_PATTERN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 微信支付客户端
|
* 微信支付客户端
|
||||||
@ -64,6 +89,8 @@ public class WechatPlayClient {
|
|||||||
|
|
||||||
private ApplicationContext applicationContext;
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
private NativePayService nativePayService;
|
||||||
|
|
||||||
private String configJson;
|
private String configJson;
|
||||||
|
|
||||||
private String appId;
|
private String appId;
|
||||||
@ -107,8 +134,12 @@ public class WechatPlayClient {
|
|||||||
.merchantSerialNumber(merchantSerialNumber)
|
.merchantSerialNumber(merchantSerialNumber)
|
||||||
.apiV3Key(apiV3Key)
|
.apiV3Key(apiV3Key)
|
||||||
.build();
|
.build();
|
||||||
// 初始化证书服务
|
|
||||||
jsapiServiceExtension = new JsapiServiceExtension.Builder().config(config).build();
|
jsapiServiceExtension = new JsapiServiceExtension.Builder().config(config).build();
|
||||||
|
|
||||||
|
|
||||||
|
nativePayService = new NativePayService.Builder().config(config).build();
|
||||||
|
|
||||||
NotificationConfig notificationConfig = new RSAPublicKeyConfig.Builder()
|
NotificationConfig notificationConfig = new RSAPublicKeyConfig.Builder()
|
||||||
.merchantId(mchId)
|
.merchantId(mchId)
|
||||||
.privateKey(privateKeyPem)
|
.privateKey(privateKeyPem)
|
||||||
@ -147,7 +178,7 @@ public class WechatPlayClient {
|
|||||||
public WechatPrepayTransactionResp prepayPay(PayOrderReqDTO payOrder, Object extrasRequest) {
|
public WechatPrepayTransactionResp prepayPay(PayOrderReqDTO payOrder, Object extrasRequest) {
|
||||||
String sn = payOrder.getPayOrderSn();
|
String sn = payOrder.getPayOrderSn();
|
||||||
Integer userId = payOrder.getUserId();
|
Integer userId = payOrder.getUserId();
|
||||||
Long totalAmount = payOrder.getTotalAmount();
|
Integer totalAmount = payOrder.getTotalAmount();
|
||||||
log.info("开始处理微信预支付请求,订单号: {}, 用户ID: {}, 支付金额: {}分", sn, userId, totalAmount);
|
log.info("开始处理微信预支付请求,订单号: {}, 用户ID: {}, 支付金额: {}分", sn, userId, totalAmount);
|
||||||
|
|
||||||
// 构建支付请求
|
// 构建支付请求
|
||||||
@ -201,6 +232,35 @@ public class WechatPlayClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预支付,获取二维码地址(使用默认配置)
|
||||||
|
* <p>
|
||||||
|
* NATIVE支付场景,商户调用该接口在微信支付下单,生成用于调起支付的二维码,
|
||||||
|
* 获取二维码地址。
|
||||||
|
* <see><a href='https://pay.weixin.qq.com/doc/v3/merchant/4012791877'>微信NATIVE下单</a></see>
|
||||||
|
*
|
||||||
|
* @param payOrder 支付订单信息
|
||||||
|
* @param extrasRequest 额外参数
|
||||||
|
* @return 二维码地址
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public Optional<String> nativePrepayPay(PayOrderReqDTO payOrder, Object extrasRequest){
|
||||||
|
com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest request = new com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest();
|
||||||
|
request.setOutTradeNo(payOrder.getPayOrderSn());
|
||||||
|
request.setDescription(payOrder.getDescription());
|
||||||
|
request.setNotifyUrl(jsPayNotifyUrl);
|
||||||
|
com.wechat.pay.java.service.payments.nativepay.model.Amount amount = new com.wechat.pay.java.service.payments.nativepay.model.Amount();
|
||||||
|
amount.setTotal(payOrder.getTotalAmount().intValue());
|
||||||
|
amount.setCurrency("CNY");
|
||||||
|
request.setAmount(amount);
|
||||||
|
request.setTimeExpire(TemporalAccessorUtil.format(payOrder.getExpireTime().atZone(ZoneId.systemDefault()), UTC_WITH_XXX_OFFSET_PATTERN));
|
||||||
|
|
||||||
|
PrepayResponse prepay = nativePayService.prepay(request);
|
||||||
|
if(Objects.nonNull(prepay)){
|
||||||
|
return Optional.of(prepay.getCodeUrl());
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清理PEM内容,确保符合微信SDK要求
|
* 清理PEM内容,确保符合微信SDK要求
|
||||||
|
|||||||
@ -53,18 +53,16 @@ public class AppPayOrderController {
|
|||||||
@SaCheckPermission("app:order:status")
|
@SaCheckPermission("app:order:status")
|
||||||
public ResultBean<AppPayOrderStatusResp> queryPayStatus(
|
public ResultBean<AppPayOrderStatusResp> queryPayStatus(
|
||||||
@Parameter(description = "订单号") @PathVariable("orderSn") String orderSn) {
|
@Parameter(description = "订单号") @PathVariable("orderSn") String orderSn) {
|
||||||
PayOrderEntity payOrder = appPayOrderService.queryPayStatus(orderSn);
|
AppPayOrderStatusResp payOrder = appPayOrderService.queryPayStatus(orderSn);
|
||||||
AppPayOrderStatusResp result = AppPayOrderConvert.INSTANCE.convertOne(payOrder);
|
return ResultBean.success(payOrder);
|
||||||
return ResultBean.success(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Operation(summary = "关闭支付订单")
|
@Operation(summary = "关闭支付订单")
|
||||||
@PostMapping("/close/{orderSn}")
|
@PostMapping("/close/{orderSn}")
|
||||||
@SaCheckPermission("app:order:close")
|
@SaCheckPermission("app:order:close")
|
||||||
public ResultBean<Boolean> closePayOrder(
|
public ResultBean<String> closePayOrder(
|
||||||
@Parameter(description = "订单号", required = true) @PathVariable("orderSn") String orderSn) {
|
@Parameter(description = "订单号", required = true) @PathVariable("orderSn") String orderSn) {
|
||||||
Boolean result = appPayOrderService.closePayOrder(orderSn);
|
return ResultBean.success(appPayOrderService.closePayOrder(orderSn));
|
||||||
return ResultBean.success(result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8,6 +8,7 @@ import lombok.Setter;
|
|||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import org.hibernate.validator.constraints.URL;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ -22,4 +23,8 @@ public class OrderPayReq {
|
|||||||
|
|
||||||
@Schema(description = "商户订单编号")
|
@Schema(description = "商户订单编号")
|
||||||
private String merchantOrderSn;
|
private String merchantOrderSn;
|
||||||
|
|
||||||
|
@Schema(description = "回跳地址")
|
||||||
|
@URL(message = "回跳地址的格式必须是 URL")
|
||||||
|
private String returnUrl;
|
||||||
}
|
}
|
||||||
@ -17,4 +17,7 @@ public class AppPayOrderResp {
|
|||||||
|
|
||||||
@Schema(description = "支付订单号")
|
@Schema(description = "支付订单号")
|
||||||
private String payOrderSn;
|
private String payOrderSn;
|
||||||
|
|
||||||
|
@Schema(description = "二维码地址")
|
||||||
|
private String qrCodeUrl;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.seer.teach.pay.app.pay.service;
|
|||||||
|
|
||||||
import com.seer.teach.pay.app.pay.controller.req.OrderPayReq;
|
import com.seer.teach.pay.app.pay.controller.req.OrderPayReq;
|
||||||
import com.seer.teach.pay.app.pay.controller.resp.AppPayOrderResp;
|
import com.seer.teach.pay.app.pay.controller.resp.AppPayOrderResp;
|
||||||
|
import com.seer.teach.pay.app.pay.controller.resp.AppPayOrderStatusResp;
|
||||||
import com.seer.teach.pay.entity.PayOrderEntity;
|
import com.seer.teach.pay.entity.PayOrderEntity;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -30,12 +31,12 @@ public interface IAppPayOrderService {
|
|||||||
* @param orderSn 订单号
|
* @param orderSn 订单号
|
||||||
* @return 支付订单信息
|
* @return 支付订单信息
|
||||||
*/
|
*/
|
||||||
PayOrderEntity queryPayStatus(String orderSn);
|
AppPayOrderStatusResp queryPayStatus(String orderSn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭支付订单
|
* 关闭支付订单
|
||||||
* @param orderSn
|
* @param orderSn
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
Boolean closePayOrder(String orderSn);
|
String closePayOrder(String orderSn);
|
||||||
}
|
}
|
||||||
@ -1,28 +1,34 @@
|
|||||||
package com.seer.teach.pay.app.pay.service.impl;
|
package com.seer.teach.pay.app.pay.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.nacos.shaded.com.google.common.collect.Maps;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
import com.seer.teach.common.constants.CommonConstant;
|
||||||
import com.seer.teach.common.enums.pay.PayStatusEnum;
|
|
||||||
import com.seer.teach.common.enums.ResultCodeEnum;
|
import com.seer.teach.common.enums.ResultCodeEnum;
|
||||||
|
import com.seer.teach.common.enums.pay.PayChannelEnum;
|
||||||
import com.seer.teach.common.enums.pay.PayOrderSourceEnum;
|
import com.seer.teach.common.enums.pay.PayOrderSourceEnum;
|
||||||
|
import com.seer.teach.common.enums.pay.PayStatusEnum;
|
||||||
import com.seer.teach.common.utils.AssertUtils;
|
import com.seer.teach.common.utils.AssertUtils;
|
||||||
|
import com.seer.teach.pay.app.client.PayClient;
|
||||||
|
import com.seer.teach.pay.app.client.PayClientFactory;
|
||||||
import com.seer.teach.pay.app.client.convert.PayClientConvert;
|
import com.seer.teach.pay.app.client.convert.PayClientConvert;
|
||||||
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
import com.seer.teach.pay.app.client.dto.PayOrderReqDTO;
|
||||||
import com.seer.teach.pay.app.pay.controller.req.OrderPayReq;
|
|
||||||
import com.seer.teach.pay.app.client.dto.PayOrderRespDTO;
|
import com.seer.teach.pay.app.client.dto.PayOrderRespDTO;
|
||||||
import com.seer.teach.pay.app.pay.controller.resp.AppPayOrderResp;
|
|
||||||
import com.seer.teach.pay.app.pay.convert.AppPayOrderConvert;
|
|
||||||
import com.seer.teach.pay.app.pay.service.IAppPayOrderService;
|
|
||||||
import com.seer.teach.pay.app.notify.service.OrderPayNotifyHandler;
|
import com.seer.teach.pay.app.notify.service.OrderPayNotifyHandler;
|
||||||
import com.seer.teach.pay.app.notify.service.OrderProcessStrategyFactory;
|
import com.seer.teach.pay.app.notify.service.OrderProcessStrategyFactory;
|
||||||
import com.seer.teach.pay.app.client.PayClient;
|
import com.seer.teach.pay.app.pay.controller.req.OrderPayReq;
|
||||||
import com.seer.teach.pay.app.client.factory.PayClientFactory;
|
import com.seer.teach.pay.app.pay.controller.resp.AppPayOrderResp;
|
||||||
|
import com.seer.teach.pay.app.pay.controller.resp.AppPayOrderStatusResp;
|
||||||
|
import com.seer.teach.pay.app.pay.convert.AppPayOrderConvert;
|
||||||
|
import com.seer.teach.pay.app.pay.service.IAppPayOrderService;
|
||||||
import com.seer.teach.pay.entity.PayChannelEntity;
|
import com.seer.teach.pay.entity.PayChannelEntity;
|
||||||
import com.seer.teach.pay.entity.PayOrderEntity;
|
import com.seer.teach.pay.entity.PayOrderEntity;
|
||||||
import com.seer.teach.pay.service.IPayChannelService;
|
import com.seer.teach.pay.service.IPayChannelService;
|
||||||
import com.seer.teach.pay.service.IPayOrderService;
|
import com.seer.teach.pay.service.IPayOrderService;
|
||||||
|
import com.seer.teach.user.api.UserInfoServiceApi;
|
||||||
|
import com.seer.teach.user.api.dto.UserAuthDTO;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -41,6 +47,12 @@ public class AppPayOrderServiceImpl implements IAppPayOrderService {
|
|||||||
|
|
||||||
private final IPayChannelService payChannelService;
|
private final IPayChannelService payChannelService;
|
||||||
|
|
||||||
|
private final UserInfoServiceApi userInfoServiceApi;
|
||||||
|
|
||||||
|
@Value("${server.servlet.context-path:}")
|
||||||
|
private String contextPath;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AppPayOrderResp payOrder(OrderPayReq orderPayReq) {
|
public AppPayOrderResp payOrder(OrderPayReq orderPayReq) {
|
||||||
log.info("开始处理支付订单,渠道:{},商户订单号:{}", orderPayReq.getChannelCode(), orderPayReq.getMerchantOrderSn());
|
log.info("开始处理支付订单,渠道:{},商户订单号:{}", orderPayReq.getChannelCode(), orderPayReq.getMerchantOrderSn());
|
||||||
@ -58,30 +70,44 @@ public class AppPayOrderServiceImpl implements IAppPayOrderService {
|
|||||||
AssertUtils.isTrue(payOrderEntity.getStatus() == PayStatusEnum.PENDING.getCode() || payOrderEntity.getStatus() == PayStatusEnum.PAY_FAILED.getCode(), ResultCodeEnum.PAY_ORDER_IS_PROCESSED);
|
AssertUtils.isTrue(payOrderEntity.getStatus() == PayStatusEnum.PENDING.getCode() || payOrderEntity.getStatus() == PayStatusEnum.PAY_FAILED.getCode(), ResultCodeEnum.PAY_ORDER_IS_PROCESSED);
|
||||||
|
|
||||||
// 4.获取对应的支付客户端
|
// 4.获取对应的支付客户端
|
||||||
PayClient payCLient = payClientFactory.getPayClient(orderPayReq.getChannelCode());
|
PayClient payClient = payClientFactory.getPayClient(PayChannelEnum.fromCode(channelCode), payChannel.getConfigJson());
|
||||||
|
|
||||||
// 5,执行支付订单
|
// 5,设置支付客户端的参数
|
||||||
PayOrderReqDTO payOrderReqDTO = PayClientConvert.INSTANCE.convert2PayOrderReqDTO(payOrderEntity, orderPayReq);
|
PayOrderReqDTO payOrderReqDTO = PayClientConvert.INSTANCE.convert2PayOrderReqDTO(payOrderEntity, orderPayReq);
|
||||||
PayOrderRespDTO response = payCLient.payOrder(payOrderReqDTO);
|
if( PayChannelEnum.WECHAT_JSAPI.getCode().equalsIgnoreCase(channelCode)){
|
||||||
|
UserAuthDTO userAuthDTO = userInfoServiceApi.getUserAuthByUserIdAndAppId(payOrderEntity.getUserId(), payChannel.getAppId());
|
||||||
|
if(Objects.nonNull(userAuthDTO)){
|
||||||
|
Map<String, String> channelExtras = payOrderReqDTO.getChannelExtras() == null ? Maps.newHashMap() : payOrderReqDTO.getChannelExtras();
|
||||||
|
channelExtras.put(CommonConstant.WX_OPEN_ID, userAuthDTO.getOpenId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
payOrderReqDTO.setNotifyUrl(getNotifyUrl(payChannel));
|
||||||
|
payOrderReqDTO.setReturnUrl(orderPayReq.getReturnUrl());
|
||||||
|
// 6,执行支付
|
||||||
|
PayOrderRespDTO response = payClient.payOrder(payOrderReqDTO);
|
||||||
response.setChannelId(payChannel.getId());
|
response.setChannelId(payChannel.getId());
|
||||||
|
|
||||||
if (!payCLient.isAsyncCallback()) {
|
if (!payClient.isAsyncCallback()) {
|
||||||
handlePayResult(response);
|
handlePayResult(response);
|
||||||
}
|
}
|
||||||
log.info("支付订单处理完成,订单号:{}", orderPayReq.getMerchantOrderSn());
|
log.info("支付订单处理完成,订单号:{}", orderPayReq.getMerchantOrderSn());
|
||||||
return AppPayOrderConvert.INSTANCE.convertOne(response);
|
return AppPayOrderConvert.INSTANCE.convertOne(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getNotifyUrl(PayChannelEntity payChannel) {
|
||||||
|
return payChannel.getNotifyDomain() + contextPath + "/app/callback/pay/" + payChannel.getChannelCode();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean payCallback(String channelCode, Map<String, String> headers, Map<String, String> params, String body) {
|
public boolean payCallback(String channelCode, Map<String, String> headers, Map<String, String> params, String body) {
|
||||||
log.info("开始处理统一支付回调,渠道编码:{}", channelCode);
|
log.info("开始处理统一支付回调,渠道编码:{}", channelCode);
|
||||||
try {
|
try {
|
||||||
// 根据渠道编码获取对应的支付策略
|
// 根据渠道编码获取对应的支付策略
|
||||||
PayChannelEnum channel = PayChannelEnum.fromCode(channelCode);
|
PayChannelEntity payChannel = payChannelService.checkChannelByChannelCode(channelCode);
|
||||||
|
|
||||||
AssertUtils.notNull(channel, ResultCodeEnum.PAY_CHANNEL_IS_NOT_EXIST);
|
AssertUtils.notNull(payChannel, ResultCodeEnum.PAY_CHANNEL_IS_NOT_EXIST);
|
||||||
|
|
||||||
PayClient payClient = payClientFactory.getPayClient(channel);
|
PayClient payClient = payClientFactory.getPayClient(PayChannelEnum.fromCode(channelCode), payChannel.getConfigJson());
|
||||||
// 处理支付回调
|
// 处理支付回调
|
||||||
PayOrderRespDTO response = payClient.handlePayCallback(headers, params, body);
|
PayOrderRespDTO response = payClient.handlePayCallback(headers, params, body);
|
||||||
|
|
||||||
@ -128,11 +154,20 @@ public class AppPayOrderServiceImpl implements IAppPayOrderService {
|
|||||||
* @return 支付订单
|
* @return 支付订单
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public PayOrderEntity queryPayStatus(String orderSn) {
|
public AppPayOrderStatusResp queryPayStatus(String orderSn) {
|
||||||
log.info("查询支付状态,订单号:{}", orderSn);
|
log.info("查询支付状态,订单号:{}", orderSn);
|
||||||
// 根据订单号查询支付订单,获取支付渠道编码
|
PayOrderEntity payOrder = payOrderService.getOrderByOrderSn(orderSn);
|
||||||
PayClient payCLient = getPayStrategy(orderSn);
|
if (Objects.isNull(payOrder)) {
|
||||||
return payCLient.queryPayStatus(orderSn);
|
return null;
|
||||||
|
}
|
||||||
|
AppPayOrderStatusResp appPayOrderStatusResp = AppPayOrderConvert.INSTANCE.convertOne(payOrder);
|
||||||
|
|
||||||
|
PayClient payCLient = getPayClient(orderSn);
|
||||||
|
PayOrderRespDTO payOrderRespDTO = payCLient.queryPayStatus(orderSn);
|
||||||
|
if (Objects.nonNull(payOrderRespDTO)) {
|
||||||
|
appPayOrderStatusResp.setStatus(payOrderRespDTO.getStatus());
|
||||||
|
}
|
||||||
|
return appPayOrderStatusResp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -142,25 +177,27 @@ public class AppPayOrderServiceImpl implements IAppPayOrderService {
|
|||||||
* @return 关闭结果
|
* @return 关闭结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Boolean closePayOrder(String orderSn) {
|
public String closePayOrder(String orderSn) {
|
||||||
log.info("关闭支付状态,订单号:{}", orderSn);
|
log.info("关闭支付状态,订单号:{}", orderSn);
|
||||||
PayClient payCLient = getPayStrategy(orderSn);
|
PayClient payCLient = getPayClient(orderSn);
|
||||||
return payCLient.closePayOrder(orderSn);
|
return payCLient.closePayOrder(orderSn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据订单号获取支付策略
|
* 根据订单号获取支付客户端
|
||||||
* 提取公共方法
|
* 提取公共方法
|
||||||
*
|
*
|
||||||
* @param orderSn 订单号
|
* @param orderSn 订单号
|
||||||
* @return 支付策略
|
* @return 支付客户端
|
||||||
*/
|
*/
|
||||||
private PayClient getPayStrategy(String orderSn) {
|
private PayClient getPayClient(String orderSn) {
|
||||||
PayOrderEntity payOrder = payOrderService.getOne(new LambdaQueryWrapper<PayOrderEntity>()
|
PayOrderEntity payOrder = payOrderService.getOne(new LambdaQueryWrapper<PayOrderEntity>()
|
||||||
.eq(PayOrderEntity::getOrderSn, orderSn));
|
.eq(PayOrderEntity::getOrderSn, orderSn));
|
||||||
AssertUtils.notNull(payOrder, ResultCodeEnum.ORDER_NOT_FOUND);
|
AssertUtils.notNull(payOrder, ResultCodeEnum.ORDER_NOT_FOUND);
|
||||||
String channelCode = payOrder.getChannelCode();
|
String channelCode = payOrder.getChannelCode();
|
||||||
|
|
||||||
|
PayChannelEntity payChannel = payChannelService.checkChannelByChannelCode(channelCode);
|
||||||
log.info("订单号:{},支付渠道:{}", orderSn, channelCode);
|
log.info("订单号:{},支付渠道:{}", orderSn, channelCode);
|
||||||
return payClientFactory.getPayClient(channelCode);
|
return payClientFactory.getPayClient(PayChannelEnum.fromCode(channelCode), payChannel.getConfigJson());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,7 +4,6 @@ import com.seer.teach.common.enums.pay.PayChannelEnum;
|
|||||||
import com.seer.teach.common.enums.ResultCodeEnum;
|
import com.seer.teach.common.enums.ResultCodeEnum;
|
||||||
import com.seer.teach.common.enums.pay.PayOrderSourceEnum;
|
import com.seer.teach.common.enums.pay.PayOrderSourceEnum;
|
||||||
import com.seer.teach.common.enums.pay.RefundStatusEnum;
|
import com.seer.teach.common.enums.pay.RefundStatusEnum;
|
||||||
import com.seer.teach.common.enums.pay.RefundTypeEnum;
|
|
||||||
import com.seer.teach.common.exception.CommonException;
|
import com.seer.teach.common.exception.CommonException;
|
||||||
import com.seer.teach.common.utils.AssertUtils;
|
import com.seer.teach.common.utils.AssertUtils;
|
||||||
import com.seer.teach.common.utils.OrderIdGenerator;
|
import com.seer.teach.common.utils.OrderIdGenerator;
|
||||||
@ -12,15 +11,17 @@ import com.seer.teach.pay.api.refund.dto.RefundOrderRequestDTO;
|
|||||||
import com.seer.teach.pay.app.client.PayClient;
|
import com.seer.teach.pay.app.client.PayClient;
|
||||||
import com.seer.teach.pay.app.client.convert.PayClientConvert;
|
import com.seer.teach.pay.app.client.convert.PayClientConvert;
|
||||||
import com.seer.teach.pay.app.client.dto.RefundNotificationRespDTO;
|
import com.seer.teach.pay.app.client.dto.RefundNotificationRespDTO;
|
||||||
import com.seer.teach.pay.app.client.factory.PayClientFactory;
|
import com.seer.teach.pay.app.client.PayClientFactory;
|
||||||
import com.seer.teach.pay.app.notify.service.OrderPayNotifyHandler;
|
import com.seer.teach.pay.app.notify.service.OrderPayNotifyHandler;
|
||||||
import com.seer.teach.pay.app.notify.service.OrderProcessStrategyFactory;
|
import com.seer.teach.pay.app.notify.service.OrderProcessStrategyFactory;
|
||||||
import com.seer.teach.pay.app.client.dto.PayRefundReqDTO;
|
import com.seer.teach.pay.app.client.dto.PayRefundReqDTO;
|
||||||
import com.seer.teach.pay.app.refund.service.IAppRefundOrderService;
|
import com.seer.teach.pay.app.refund.service.IAppRefundOrderService;
|
||||||
import com.seer.teach.pay.app.refund.service.dto.RefundResultDTO;
|
import com.seer.teach.pay.app.refund.service.dto.RefundResultDTO;
|
||||||
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
import com.seer.teach.pay.dto.RefundSubmitRespDTO;
|
||||||
|
import com.seer.teach.pay.entity.PayChannelEntity;
|
||||||
import com.seer.teach.pay.entity.PayOrderEntity;
|
import com.seer.teach.pay.entity.PayOrderEntity;
|
||||||
import com.seer.teach.pay.entity.RefundOrderEntity;
|
import com.seer.teach.pay.entity.RefundOrderEntity;
|
||||||
|
import com.seer.teach.pay.service.IPayChannelService;
|
||||||
import com.seer.teach.pay.service.IPayOrderService;
|
import com.seer.teach.pay.service.IPayOrderService;
|
||||||
import com.seer.teach.pay.service.IRefundOrderService;
|
import com.seer.teach.pay.service.IRefundOrderService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -43,6 +44,8 @@ public class AppRefundOrderServiceImpl implements IAppRefundOrderService {
|
|||||||
|
|
||||||
private final IPayOrderService payOrderService;
|
private final IPayOrderService payOrderService;
|
||||||
|
|
||||||
|
private final IPayChannelService payChannelService;
|
||||||
|
|
||||||
private final IRefundOrderService refundOrderService;
|
private final IRefundOrderService refundOrderService;
|
||||||
|
|
||||||
private final OrderProcessStrategyFactory orderProcessStrategyFactory;
|
private final OrderProcessStrategyFactory orderProcessStrategyFactory;
|
||||||
@ -94,7 +97,10 @@ public class AppRefundOrderServiceImpl implements IAppRefundOrderService {
|
|||||||
PayChannelEnum channel = PayChannelEnum.fromCode(channelCode);
|
PayChannelEnum channel = PayChannelEnum.fromCode(channelCode);
|
||||||
log.info("获取支付渠道结果:{}", channel);
|
log.info("获取支付渠道结果:{}", channel);
|
||||||
AssertUtils.notNull(channel, ResultCodeEnum.PAY_CHANNEL_NOT_SUPPORTED);
|
AssertUtils.notNull(channel, ResultCodeEnum.PAY_CHANNEL_NOT_SUPPORTED);
|
||||||
PayClient payClient = payClientFactory.getPayClient(channel);
|
|
||||||
|
PayChannelEntity payChannel = payChannelService.checkChannelByChannelCode(channelCode);
|
||||||
|
|
||||||
|
PayClient payClient = payClientFactory.getPayClient(channel,payChannel.getConfigJson());
|
||||||
log.info("获取支付客户端完成:{}", payClient != null);
|
log.info("获取支付客户端完成:{}", payClient != null);
|
||||||
|
|
||||||
// 执行退款订单
|
// 执行退款订单
|
||||||
@ -103,8 +109,8 @@ public class AppRefundOrderServiceImpl implements IAppRefundOrderService {
|
|||||||
.outTradeNo(payOrder.getOrderSn())
|
.outTradeNo(payOrder.getOrderSn())
|
||||||
.outRefundNo(refundSn)
|
.outRefundNo(refundSn)
|
||||||
.reason(refundRequest.getRefundReason())
|
.reason(refundRequest.getRefundReason())
|
||||||
.payPrice(refundRequest.getTotalAmount())
|
.payPrice(refundRequest.getTotalAmount().intValue())
|
||||||
.refundPrice(refundRequest.getRefundAmount())
|
.refundPrice(refundRequest.getRefundAmount().intValue())
|
||||||
.userId(refundRequest.getUserId())
|
.userId(refundRequest.getUserId())
|
||||||
.build();
|
.build();
|
||||||
log.info("构建退款请求参数完成,请求参数:{}", payRefundReqDTO);
|
log.info("构建退款请求参数完成,请求参数:{}", payRefundReqDTO);
|
||||||
@ -140,7 +146,7 @@ public class AppRefundOrderServiceImpl implements IAppRefundOrderService {
|
|||||||
log.info("查询退款状态,退款单号:{}", refundSn);
|
log.info("查询退款状态,退款单号:{}", refundSn);
|
||||||
try {
|
try {
|
||||||
// 根据退款单号查询支付订单,获取支付渠道编码
|
// 根据退款单号查询支付订单,获取支付渠道编码
|
||||||
PayClient payClient = getPayStrategy(refundSn);
|
PayClient payClient = getPayClient(refundSn);
|
||||||
log.info("获取支付客户端完成,退款单号:{}", refundSn);
|
log.info("获取支付客户端完成,退款单号:{}", refundSn);
|
||||||
|
|
||||||
RefundSubmitRespDTO result = payClient.doQueryRefundStatus(refundSn);
|
RefundSubmitRespDTO result = payClient.doQueryRefundStatus(refundSn);
|
||||||
@ -157,12 +163,14 @@ public class AppRefundOrderServiceImpl implements IAppRefundOrderService {
|
|||||||
public boolean refundCallback(String channelCode, Map<String, String> headers, Map<String, String> params, String body) {
|
public boolean refundCallback(String channelCode, Map<String, String> headers, Map<String, String> params, String body) {
|
||||||
log.info("开始处理统一退款回调,渠道编码:{}", channelCode);
|
log.info("开始处理统一退款回调,渠道编码:{}", channelCode);
|
||||||
try {
|
try {
|
||||||
// 根据渠道编码获取对应的支付策略
|
// 根据渠道编码获取对应的支付客户端
|
||||||
PayChannelEnum channel = PayChannelEnum.fromCode(channelCode);
|
PayChannelEnum channel = PayChannelEnum.fromCode(channelCode);
|
||||||
log.info("获取的支付策略:{}", channel);
|
log.info("获取的支付客户端:{}", channel);
|
||||||
AssertUtils.notNull(channel, ResultCodeEnum.PAY_CHANNEL_NOT_SUPPORTED);
|
AssertUtils.notNull(channel, ResultCodeEnum.PAY_CHANNEL_NOT_SUPPORTED);
|
||||||
|
|
||||||
PayClient payClient = payClientFactory.getPayClient(channel);
|
PayChannelEntity payChannel = payChannelService.checkChannelByChannelCode(channelCode);
|
||||||
|
|
||||||
|
PayClient payClient = payClientFactory.getPayClient(channel,payChannel.getConfigJson());
|
||||||
log.info("获取支付客户端完成,渠道编码:{}", channelCode);
|
log.info("获取支付客户端完成,渠道编码:{}", channelCode);
|
||||||
|
|
||||||
// 处理退款回调
|
// 处理退款回调
|
||||||
@ -249,13 +257,13 @@ public class AppRefundOrderServiceImpl implements IAppRefundOrderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据退款单号获取支付策略
|
* 根据退款单号获取支付客户端
|
||||||
*
|
*
|
||||||
* @param refundSn 退款单号
|
* @param refundSn 退款单号
|
||||||
* @return 支付策略
|
* @return 支付客户端
|
||||||
*/
|
*/
|
||||||
private PayClient getPayStrategy(String refundSn) {
|
private PayClient getPayClient(String refundSn) {
|
||||||
log.info("开始获取支付策略,退款单号:{}", refundSn);
|
log.info("开始获取支付客户端,退款单号:{}", refundSn);
|
||||||
try {
|
try {
|
||||||
// 根据退款单号查询退款订单
|
// 根据退款单号查询退款订单
|
||||||
RefundOrderEntity refundOrder = refundOrderService.getRefundOrderByRefundSn(refundSn);
|
RefundOrderEntity refundOrder = refundOrderService.getRefundOrderByRefundSn(refundSn);
|
||||||
@ -268,11 +276,12 @@ public class AppRefundOrderServiceImpl implements IAppRefundOrderService {
|
|||||||
|
|
||||||
PayChannelEnum channel = PayChannelEnum.fromCode(channelCode);
|
PayChannelEnum channel = PayChannelEnum.fromCode(channelCode);
|
||||||
log.info("支付渠道枚举转换结果:{}", channel);
|
log.info("支付渠道枚举转换结果:{}", channel);
|
||||||
PayClient result = payClientFactory.getPayClient(channel);
|
PayChannelEntity payChannel = payChannelService.checkChannelByChannelCode(channelCode);
|
||||||
log.info("获取支付策略完成,退款单号:{},支付策略:{}", refundSn, result != null);
|
|
||||||
return result;
|
PayClient payClient = payClientFactory.getPayClient(channel,payChannel.getConfigJson());
|
||||||
|
return payClient;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("获取支付策略发生异常,退款单号:{}", refundSn, e);
|
log.error("获取支付客户端发生异常,退款单号:{}", refundSn, e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,16 @@
|
|||||||
<artifactId>wechatpay-java</artifactId>
|
<artifactId>wechatpay-java</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.binarywang</groupId>
|
||||||
|
<artifactId>weixin-java-pay</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alipay.sdk</groupId>
|
||||||
|
<artifactId>alipay-sdk-java</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.retry</groupId>
|
<groupId>org.springframework.retry</groupId>
|
||||||
<artifactId>spring-retry</artifactId>
|
<artifactId>spring-retry</artifactId>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user