feat 云闪付条码支付/APP支付

This commit is contained in:
bootx
2024-03-11 23:41:19 +08:00
parent 3c2deabfdf
commit b1de4e83f8
12 changed files with 113 additions and 57 deletions

View File

@@ -20,6 +20,7 @@
- [ ] 支付流程涉及异步支付时, 更换支付方式需要控制预防客户重复付款
- [ ] 增加撤销功能,用于处理线下支付订单的情况
- [ ] 数据库表进行规则, 字段设置长度, 增加索引
- [ ] 回调处理可以配置延迟时间,
**任务池**
- [x] 支付SDK编写

View File

@@ -3,10 +3,12 @@ package cn.bootx.platform.daxpay.gateway.controller;
import cn.bootx.platform.common.core.annotation.IgnoreAuth;
import cn.bootx.platform.daxpay.service.core.payment.notice.service.PayReturnService;
import cn.bootx.platform.daxpay.service.param.channel.alipay.AliPayReturnParam;
import cn.bootx.platform.daxpay.service.param.channel.union.UnionPayReturnParam;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
@@ -38,8 +40,9 @@ public class PayReturnController {
}
@Operation(summary = "云闪付同步通知")
@GetMapping("/union")
public ModelAndView union(){
return null;
@PostMapping("/union")
public ModelAndView union(UnionPayReturnParam param){
String url = payReturnService.union(param);
return new ModelAndView("redirect:" + url);
}
}

View File

@@ -10,12 +10,15 @@ import com.egzosn.pay.union.bean.SDKConstants;
public interface UnionPayCode {
/** 成功状态 */
String SUCCESS = "0";
/** 状态 00表示成功 */
/** 应答码 00表示成功 */
String RESP_CODE = SDKConstants.param_respCode;
/** 原交易应答码 00表示成功 */
String RESP_ORIG_CODE = SDKConstants.param_origRespCode;
/** 应答码信息 */
String RESP_MSG = SDKConstants.param_respMsg;
/** 业务结果 00表示成功 */
String RESP_SUCCESS = SDKConstants.OK_RESP_CODE;
@@ -28,8 +31,8 @@ public interface UnionPayCode {
/** 第三方订单号(本地订单号) */
String ORDER_ID = "orderId";
/** 退款ID */
String REFUND_ID = "refund_id";
/** APP支付 银联订单号 */
String PAY_APP_TN = SDKConstants.param_tn;
/** 交易类型 支付 */
String TXN_TYPE_PAY = "01";

View File

@@ -110,7 +110,7 @@ public class UnionPayCallbackService extends AbsCallbackStrategy {
@Override
public void resolveRefundData() {
// 云闪付需要延迟半秒再进行处理, 不然会出现业务未处理完, 但回调已经到达的情况
ThreadUtil.sleep(300);
ThreadUtil.sleep(100);
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
Map<String, String> callbackParam = callbackInfo.getCallbackParam();

View File

@@ -5,6 +5,7 @@ import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.channel.UnionPayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import cn.bootx.platform.daxpay.service.code.AliPayWay;
import cn.bootx.platform.daxpay.service.code.UnionPayCode;
import cn.bootx.platform.daxpay.service.common.context.PayLocal;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.channel.union.entity.UnionPayConfig;
@@ -13,6 +14,8 @@ import cn.bootx.platform.daxpay.service.sdk.union.api.UnionPayKit;
import cn.bootx.platform.daxpay.service.sdk.union.bean.UnionPayOrder;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import com.egzosn.pay.common.bean.NoticeParams;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -20,6 +23,7 @@ import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
@@ -89,8 +93,17 @@ public class UnionPayService {
unionPayOrder.setPrice(amount);
unionPayOrder.setExpirationTime(expiredTime);
Map<String, Object> app = unionPayKit.app(unionPayOrder);
return null;
Map<String, Object> result = unionPayKit.app(unionPayOrder);
String resultCode = MapUtil.getStr(result, UnionPayCode.RESP_CODE);
// 支付失败
if (!(Objects.equals(resultCode, UnionPayCode.RESP_SUCCESS))) {
log.warn("云闪付支付失败:{}", result);
String errMsg = MapUtil.getStr(result, UnionPayCode.RESP_MSG);
throw new PayFailureException(errMsg);
}
return MapUtil.getStr(result, UnionPayCode.PAY_APP_TN);
}
/**
@@ -100,31 +113,11 @@ public class UnionPayService {
Date expiredTime = DateUtil.date(payOrder.getExpiredTime());
UnionPayOrder unionPayOrder = new UnionPayOrder();
unionPayOrder.setOutTradeNo(String.valueOf(payOrder.getId()));
unionPayOrder.setSubject(payOrder.getTitle());
unionPayOrder.setPrice(amount);
unionPayOrder.setExpirationTime(expiredTime);
return unionPayKit.getQrPay(unionPayOrder);
// Map<String, String> params = UnifiedOrderModel.builder()
// .service(ServiceEnum.NATIVE.toString())
// .mch_id(unionPayKit.getMachId())
// .out_trade_no(String.valueOf(payOrder.getId()))
// .body(payOrder.getTitle())
// .total_fee(amount)
// .time_expire(PayUtil.getUnionExpiredTime(payOrder.getExpiredTime()))
// .mch_create_ip("127.0.0.1")
// .notify_url(unionPayKit.getNotifyUrl())
// .nonce_str(WxPayKit.generateStr())
// .build()
// .createSign(unionPayKit.getAppKey(), SignType.MD5);
// String xmlResult = UnionPayApi.execution(unionPayKit.getServerUrl(), params);
// Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
// this.verifyErrorMsg(result);
// return result.get("code_url");
}
/**
@@ -134,28 +127,26 @@ public class UnionPayService {
Date expiredTime = DateUtil.date(payOrder.getExpiredTime());
UnionPayOrder unionPayOrder = new UnionPayOrder();
unionPayOrder.setAuthCode(authCode);
unionPayOrder.setOutTradeNo(String.valueOf(payOrder.getId()));
unionPayOrder.setSubject(payOrder.getTitle());
unionPayOrder.setPrice(amount);
unionPayOrder.setExpirationTime(expiredTime);
Map<String, Object> stringObjectMap = unionPayKit.microPay(unionPayOrder);
Map<String, Object> result = unionPayKit.microPay(unionPayOrder);
// Map<String, String> params = MicroPayModel.builder()
// .service(ServiceEnum.MICRO_PAY.toString())
// .mch_id(unionPayKit.getMachId())
// .out_trade_no(WxPayKit.generateStr())
// .body(payOrder.getTitle())
// .total_fee(amount)
// .op_device_id("daxpay")
// .mch_create_ip("127.0.0.1")
// .auth_code(authCode)
// .nonce_str(WxPayKit.generateStr())
// .build()
// .createSign(unionPayKit.getAppKey(), SignType.MD5);
//
// String xmlResult = UnionPayApi.execution(unionPayKit.getServerUrl(), params);
if (!unionPayKit.verify(new NoticeParams(result))) {
log.warn("云闪付支付验签失败:{}", result);
throw new PayFailureException("云闪付支付验签失败");
}
String resultCode = MapUtil.getStr(result, UnionPayCode.RESP_CODE);
// 支付失败
if (!(Objects.equals(resultCode, UnionPayCode.RESP_SUCCESS))) {
log.warn("云闪付支付失败:{}", result);
String errMsg = MapUtil.getStr(result, UnionPayCode.RESP_MSG);
throw new PayFailureException(errMsg);
}
}
}

View File

@@ -43,7 +43,6 @@ public class UnionPaySyncService {
PayGatewaySyncResult syncResult = new PayGatewaySyncResult().setSyncStatus(PaySyncStatusEnum.FAIL);
AssistOrder query = new AssistOrder();
// query.setOutTradeNo("1766811070348001280");
query.setOutTradeNo(String.valueOf(order.getId()));
Map<String, Object> result = unionPayKit.query(query);
@@ -87,6 +86,11 @@ public class UnionPaySyncService {
return syncResult.setSyncStatus(PaySyncStatusEnum.PROGRESS);
}
// 二维码支付无效
if (Objects.equals(origRespCode, "86")) {
return syncResult.setSyncStatus(PaySyncStatusEnum.CLOSED).setErrorMsg("支付二维码无效");
}
return syncResult;
}

View File

@@ -65,7 +65,6 @@ public class RefundCallbackService {
RefundRepairResult repair = reflectionService.repair(refundOrder, RefundRepairWayEnum.REFUND_SUCCESS);
callbackInfo.setPayRepairNo(repair.getRepairNo());
} else {
// 设置退款订单完成时间
RefundRepairResult repair = reflectionService.repair(refundOrder, RefundRepairWayEnum.REFUND_FAIL);
callbackInfo.setPayRepairNo(repair.getRepairNo());
}

View File

@@ -7,6 +7,7 @@ import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrderExtra;
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderQueryService;
import cn.bootx.platform.daxpay.service.core.system.config.service.PlatformConfigService;
import cn.bootx.platform.daxpay.service.param.channel.alipay.AliPayReturnParam;
import cn.bootx.platform.daxpay.service.param.channel.union.UnionPayReturnParam;
import cn.hutool.core.net.URLEncodeUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
@@ -51,4 +52,23 @@ public class PayReturnService {
// 跳转到默认页
return StrUtil.format("{}/result/success?msg={}", properties.getFrontH5Url(), URLEncodeUtil.encode("支付成功..."));
}
public String union(UnionPayReturnParam param) {
PayOrderExtra payOrderExtra = payOrderExtraManager.findById(param.getOrderNo()).orElse(null);
PayOrder prOrder = payOrderQueryService.findById(Long.valueOf(param.getOrderNo())).orElse(null);
if (Objects.isNull(payOrderExtra) || Objects.isNull(prOrder)){
return StrUtil.format("{}/result/error?msg={}", properties.getFrontH5Url(), URLEncodeUtil.encode("支付订单有问题,请排查"));
}
// 如果同步跳转参数为空, 获取系统配置地址, 系统配置如果也为空, 则返回默认地址
String returnUrl = payOrderExtra.getReturnUrl();
if (StrUtil.isBlank(returnUrl)){
returnUrl = platformConfigService.getConfig().getReturnUrl();
}
if (StrUtil.isNotBlank(returnUrl)){
return StrUtil.format("{}?paymentId={}&businessNo={}", payOrderExtra.getReturnUrl(),prOrder.getId(),prOrder.getBusinessNo());
}
// 跳转到默认页
return StrUtil.format("{}/result/success?msg={}", properties.getFrontH5Url(), URLEncodeUtil.encode("支付成功..."));
}
}

View File

@@ -1,5 +1,6 @@
package cn.bootx.platform.daxpay.service.core.payment.refund.service;
import cn.bootx.platform.common.core.annotation.CountTime;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.common.core.exception.RepetitiveOperationException;
import cn.bootx.platform.common.core.function.CollectorsFunction;
@@ -67,6 +68,7 @@ public class RefundService {
/**
* 支付退款
*/
@CountTime
@Transactional(rollbackFor = Exception.class )
public RefundResult refund(RefundParam param){
return this.refundAdapter(param,false);
@@ -75,6 +77,7 @@ public class RefundService {
/**
* 简单退款
*/
@CountTime
@Transactional(rollbackFor = Exception.class )
public RefundResult simpleRefund(SimpleRefundParam param){
ValidationUtil.validateParam(param);
@@ -273,7 +276,7 @@ public class RefundService {
this.successHandler(refundOrder, refundChannelOrders, payOrder);
}
catch (Exception e) {
// 5. 失败处理, 所有记录都会回滚, 可以调用重新
// 5. 失败处理, 所有记录都会回滚, 可以调用退款重试
PaymentContextLocal.get().getRefundInfo().setStatus(RefundStatusEnum.FAIL);
this.errorHandler(refundOrder);
throw e;
@@ -289,13 +292,10 @@ public class RefundService {
int refundableBalance = refundOrder.getRefundableBalance();
// 设置支付订单状态
if (refundInfo.getStatus() == RefundStatusEnum.PROGRESS) {
// 设置为退款中
payOrder.setStatus(PayStatusEnum.REFUNDING.getCode());
} else if (refundableBalance == 0) {
// 退款完成
payOrder.setStatus(PayStatusEnum.REFUNDED.getCode());
} else {
// 部分退款
payOrder.setStatus(PayStatusEnum.PARTIAL_REFUND.getCode());
}
payOrderService.updateById(payOrder);

View File

@@ -74,7 +74,6 @@ public class PaySyncService {
}
// 如果不是异步支付, 直接返回返回
if (!payOrder.isAsyncPay()){
// return new SyncResult().setSuccess(false).setRepair(false).setErrorMsg("订单没有异步支付方式,不需要同步");
throw new PayFailureException("订单没有异步支付方式,不需要同步");
}
// 执行订单同步逻辑

View File

@@ -6,13 +6,13 @@ import lombok.Data;
import lombok.experimental.Accessors;
/**
* 微信同步回调参数
* 支付宝同步回调参数
* @author xxm
* @since 2024/2/11
*/
@Data
@Accessors(chain = true)
@Schema(title = "微信同步回调参数")
@Schema(title = "支付宝同步回调参数")
public class AliPayReturnParam {
/**

View File

@@ -0,0 +1,36 @@
package cn.bootx.platform.daxpay.service.param.channel.union;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 云闪付同步回调参数
* @author xxm
* @since 2024/2/11
*/
@Data
@Accessors(chain = true)
@Schema(title = "云闪付同步回调参数")
public class UnionPayReturnParam {
private String orderNo;
private String signature;
private String merName;
private String settleDate;
private String certId;
private String voucherNum;
private String version;
private String settleKey;
private String termId;
private String origReqType;
private String qrNo;
private String reqReserved;
private String reqType;
private String origRespMsg;
private String comInfo;
private String merId;
private String merCatCode;
private String currencyCode;
private String origRespCode;
private String txnAmt;
}