mirror of
https://gitee.com/dromara/dax-pay.git
synced 2025-09-03 02:56:20 +00:00
feat 云闪付对接调试, 重写SDK中一份调用逻辑
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
- [ ] 增加定时同步退款中的退款订单
|
||||
|
||||
2.0.x 版本内容
|
||||
- [ ] 首页驾驶舱功能: 各通道收入和支付情况
|
||||
- [ ] 增加各类日志记录,例如钱包的各项操作
|
||||
- [ ] 支付流程涉及异步支付时, 更换支付方式需要控制预防客户重复付款
|
||||
- [ ] 增加撤销功能,用于处理线下支付订单的情况
|
||||
|
@@ -0,0 +1,57 @@
|
||||
package cn.bootx.platform.daxpay.admin.controller.channel;
|
||||
|
||||
import cn.bootx.platform.common.core.rest.Res;
|
||||
import cn.bootx.platform.common.core.rest.ResResult;
|
||||
import cn.bootx.platform.common.core.rest.dto.LabelValue;
|
||||
import cn.bootx.platform.daxpay.service.core.channel.union.service.UnionPayConfigService;
|
||||
import cn.bootx.platform.daxpay.service.dto.channel.union.UnionPayConfigDto;
|
||||
import cn.bootx.platform.daxpay.service.param.channel.union.UnionPayConfigParam;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 云闪付配置
|
||||
* @author xxm
|
||||
* @since 2024/3/9
|
||||
*/
|
||||
@Tag(name = "云闪付配置")
|
||||
@RestController
|
||||
@RequestMapping("/union/pay/config")
|
||||
@RequiredArgsConstructor
|
||||
public class UnionPayConfigController {
|
||||
|
||||
private final UnionPayConfigService unionPayConfigService;
|
||||
|
||||
@Operation(summary = "获取配置")
|
||||
@GetMapping("/getConfig")
|
||||
public ResResult<UnionPayConfigDto> getConfig() {
|
||||
return Res.ok(unionPayConfigService.getConfig().toDto());
|
||||
}
|
||||
|
||||
@Operation(summary = "更新")
|
||||
@PostMapping("/update")
|
||||
public ResResult<Void> update(@RequestBody UnionPayConfigParam param) {
|
||||
unionPayConfigService.update(param);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "支持的支付方式")
|
||||
@GetMapping("/findPayWays")
|
||||
public ResResult<List<LabelValue>> findPayWays() {
|
||||
return Res.ok(unionPayConfigService.findPayWays());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Operation(summary = "读取证书文件内容")
|
||||
@PostMapping("/toBase64")
|
||||
public ResResult<String> toBase64(MultipartFile file){
|
||||
return Res.ok(Base64.encode(file.getBytes()));
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package cn.bootx.platform.daxpay.admin.controller.channel;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 云闪付控制器
|
||||
* @author xxm
|
||||
* @since 2024/3/9
|
||||
*/
|
||||
@Tag(name = "云闪付控制器")
|
||||
@RestController
|
||||
@RequestMapping("/union/pay")
|
||||
@RequiredArgsConstructor
|
||||
public class UnionPayController {
|
||||
}
|
@@ -28,7 +28,7 @@ public enum PaySyncStatusEnum {
|
||||
* 所以查询为了区分,增加一个未知的状态, 用于处理这种特殊情况, 然后根据业务需要,关闭订单或者进行其他操作
|
||||
*/
|
||||
NOT_FOUND_UNKNOWN("pay_not_found_unknown","交易不存在(特殊)"),
|
||||
/** 不属于网关同步过来的状态, 需要手动设置, 处理本地订单到了超时时间, 但是网关和本地都未关闭, 需要触发关闭相关处理 */
|
||||
/** 本地订单到了超时时间, 但是网关和本地都未关闭, 需要触发关闭相关处理, 可以进行手动设置 */
|
||||
TIMEOUT("pay_timeout", "支付超时");
|
||||
|
||||
/** 编码 */
|
||||
|
@@ -36,4 +36,10 @@ public class PayReturnController {
|
||||
public ModelAndView wechat(){
|
||||
return null;
|
||||
}
|
||||
|
||||
@Operation(summary = "云闪付同步通知")
|
||||
@GetMapping("/union")
|
||||
public ModelAndView union(){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -18,26 +18,26 @@ public interface UnionPayCode {
|
||||
String RESULT_CODE = "result_code";
|
||||
|
||||
/** 网关订单号 */
|
||||
String TRANSACTION_ID = "transaction_id";
|
||||
String QUERY_ID = "queryId";
|
||||
|
||||
/** 第三方订单号 */
|
||||
String OUT_TRANSACTION_ID = "out_transaction_id";
|
||||
String ORDER_ID = "orderId";
|
||||
|
||||
/** 退款ID */
|
||||
String REFUND_ID = "refund_id";
|
||||
|
||||
|
||||
/**
|
||||
* 支付完成时间
|
||||
* 订单发送时间
|
||||
* 格式: yyyyMMddHHmmss
|
||||
*/
|
||||
String TIME_END = "time_end";
|
||||
String TXN_TIME = "txnTime";
|
||||
|
||||
/** 支付结果 */
|
||||
String PAY_RESULT = "pay_result";
|
||||
|
||||
/** 总金额 */
|
||||
String TOTAL_FEE = "total_fee";
|
||||
String TOTAL_FEE = "settleAmt";
|
||||
|
||||
/** 交易状态 */
|
||||
String TRADE_STATE = "trade_state";
|
||||
|
@@ -0,0 +1,39 @@
|
||||
package cn.bootx.platform.daxpay.service.code;
|
||||
|
||||
import cn.bootx.platform.daxpay.code.PayWayEnum;
|
||||
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 云闪付支付方式
|
||||
* @author xxm
|
||||
* @since 2024/3/9
|
||||
*/
|
||||
@UtilityClass
|
||||
public class UnionPayWay {
|
||||
|
||||
// 支付方式
|
||||
private static final List<PayWayEnum> PAY_WAYS = Arrays.asList(PayWayEnum.WAP, PayWayEnum.APP, PayWayEnum.WEB,
|
||||
PayWayEnum.QRCODE, PayWayEnum.BARCODE);
|
||||
|
||||
/**
|
||||
* 根据编码获取
|
||||
*/
|
||||
public PayWayEnum findByCode(String code) {
|
||||
return PAY_WAYS.stream()
|
||||
.filter(e -> Objects.equals(code, e.getCode()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new PayFailureException("不存在的支付方式"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支持的支付方式
|
||||
*/
|
||||
public List<PayWayEnum> getPayWays() {
|
||||
return PAY_WAYS;
|
||||
}
|
||||
}
|
@@ -53,7 +53,7 @@ public class UnionPayConfig extends MpBaseEntity implements EntityBaseFunction<U
|
||||
* @see UnionPaySignTypeEnum
|
||||
*/
|
||||
@DbColumn(comment = "签名类型")
|
||||
public String signType;
|
||||
private String signType;
|
||||
|
||||
/**
|
||||
* 是否为证书签名
|
||||
|
@@ -89,15 +89,15 @@ public class UnionPayCallbackService extends AbsCallbackStrategy {
|
||||
Map<String, String> callbackParam = callbackInfo.getCallbackParam();
|
||||
|
||||
// 网关订单号
|
||||
callbackInfo.setGatewayOrderNo(callbackParam.get(TRANSACTION_ID));
|
||||
callbackInfo.setGatewayOrderNo(callbackParam.get(QUERY_ID));
|
||||
// 支付订单ID
|
||||
callbackInfo.setOrderId(Long.valueOf(callbackParam.get(OUT_TRANSACTION_ID)));
|
||||
callbackInfo.setOrderId(Long.valueOf(callbackParam.get(ORDER_ID)));
|
||||
// 支付结果
|
||||
PayStatusEnum payStatus = WxPayKit.codeIsOk(callbackParam.get(PAY_RESULT)) ? PayStatusEnum.SUCCESS : PayStatusEnum.FAIL;
|
||||
callbackInfo.setGatewayStatus(payStatus.getCode());
|
||||
// 支付金额
|
||||
callbackInfo.setAmount(callbackParam.get(TOTAL_FEE));
|
||||
String timeEnd = callbackParam.get(TIME_END);
|
||||
String timeEnd = callbackParam.get(TXN_TIME);
|
||||
if (StrUtil.isNotBlank(timeEnd)) {
|
||||
LocalDateTime time = LocalDateTimeUtil.parse(timeEnd, DatePattern.PURE_DATETIME_PATTERN);
|
||||
callbackInfo.setFinishTime(time);
|
||||
|
@@ -1,20 +1,12 @@
|
||||
package cn.bootx.platform.daxpay.service.core.channel.union.service;
|
||||
|
||||
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
|
||||
import cn.bootx.platform.daxpay.service.code.UnionPayCode;
|
||||
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
|
||||
import cn.bootx.platform.daxpay.service.sdk.union.api.UnionPayKit;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.egzosn.pay.common.bean.AssistOrder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.bootx.platform.daxpay.service.code.UnionPayCode.*;
|
||||
|
||||
/**
|
||||
* 云闪付支付关闭
|
||||
* @author xxm
|
||||
@@ -29,30 +21,7 @@ public class UnionPayCloseService {
|
||||
* 关闭订单
|
||||
*/
|
||||
public void close(PayOrder payOrder, UnionPayKit unionPayKit) {
|
||||
AssistOrder closeOrder = new AssistOrder();
|
||||
|
||||
closeOrder.setOutTradeNo(String.valueOf(payOrder.getId()));
|
||||
|
||||
Map<String, Object> result = unionPayKit.close(closeOrder);
|
||||
|
||||
throw new PayFailureException("云闪付没有关闭订单功能!");
|
||||
// this.verifyErrorMsg(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证错误信息
|
||||
*/
|
||||
private void verifyErrorMsg(Map<String, String> result) {
|
||||
String status = result.get(UnionPayCode.STATUS);
|
||||
String returnCode = result.get(UnionPayCode.RESULT_CODE);
|
||||
|
||||
// 判断查询是否成功
|
||||
if (!(Objects.equals(SUCCESS, status) && Objects.equals(SUCCESS, returnCode))){
|
||||
String errorMsg = result.get(ERR_MSG);
|
||||
if (StrUtil.isBlank(errorMsg)) {
|
||||
errorMsg = result.get(MESSAGE);
|
||||
}
|
||||
log.error("订单关闭失败 {}", errorMsg);
|
||||
throw new PayFailureException(errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,14 +4,16 @@ import cn.bootx.platform.common.core.exception.DataNotExistException;
|
||||
import cn.bootx.platform.common.core.rest.dto.LabelValue;
|
||||
import cn.bootx.platform.daxpay.code.PayChannelEnum;
|
||||
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
|
||||
import cn.bootx.platform.daxpay.service.code.AliPayWay;
|
||||
import cn.bootx.platform.daxpay.service.code.UnionPayWay;
|
||||
import cn.bootx.platform.daxpay.service.core.channel.union.dao.UnionPayConfigManager;
|
||||
import cn.bootx.platform.daxpay.service.core.channel.union.entity.UnionPayConfig;
|
||||
import cn.bootx.platform.daxpay.service.core.system.config.service.PayChannelConfigService;
|
||||
import cn.bootx.platform.daxpay.service.param.channel.alipay.AliPayConfigParam;
|
||||
import cn.bootx.platform.daxpay.service.param.channel.union.UnionPayConfigParam;
|
||||
import cn.bootx.platform.daxpay.service.sdk.union.api.UnionPayKit;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.copier.CopyOptions;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import com.egzosn.pay.common.bean.CertStoreType;
|
||||
import com.egzosn.pay.common.http.HttpConfigStorage;
|
||||
import com.egzosn.pay.union.api.UnionPayConfigStorage;
|
||||
@@ -20,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -42,7 +45,7 @@ public class UnionPayConfigService {
|
||||
* 修改
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(AliPayConfigParam param) {
|
||||
public void update(UnionPayConfigParam param) {
|
||||
UnionPayConfig unionPayConfig = unionPayConfigManager.findById(ID).orElseThrow(() -> new DataNotExistException("支付宝配置不存在"));
|
||||
// 启用或停用
|
||||
if (!Objects.equals(param.getEnable(), unionPayConfig.getEnable())){
|
||||
@@ -54,10 +57,10 @@ public class UnionPayConfigService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝支持支付方式
|
||||
* 云闪付支持支付方式
|
||||
*/
|
||||
public List<LabelValue> findPayWays() {
|
||||
return AliPayWay.getPayWays()
|
||||
return UnionPayWay.getPayWays()
|
||||
.stream()
|
||||
.map(e -> new LabelValue(e.getName(),e.getCode()))
|
||||
.collect(Collectors.toList());
|
||||
@@ -67,7 +70,7 @@ public class UnionPayConfigService {
|
||||
* 获取支付配置
|
||||
*/
|
||||
public UnionPayConfig getConfig(){
|
||||
return unionPayConfigManager.findById(ID).orElseThrow(() -> new DataNotExistException("支付宝配置不存在"));
|
||||
return unionPayConfigManager.findById(ID).orElseThrow(() -> new DataNotExistException("云闪付支付配置不存在"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,32 +90,35 @@ public class UnionPayConfigService {
|
||||
*/
|
||||
public UnionPayKit initPayService(UnionPayConfig config){
|
||||
UnionPayConfigStorage unionPayConfigStorage = new UnionPayConfigStorage();
|
||||
unionPayConfigStorage.setInputCharset(CharsetUtil.UTF_8);
|
||||
// 商户号
|
||||
unionPayConfigStorage.setMerId(config.getMachId());
|
||||
//是否为证书签名
|
||||
unionPayConfigStorage.setCertSign(config.isCertSign());
|
||||
|
||||
//中级证书 证书字符串信息
|
||||
unionPayConfigStorage.setAcpMiddleCert(config.getAcpMiddleCert());
|
||||
//根证书路径 证书字符串信息
|
||||
unionPayConfigStorage.setAcpRootCert(config.getAcpRootCert());
|
||||
// 私钥证书路径 证书字符串信息
|
||||
unionPayConfigStorage.setKeyPrivateCert(config.getKeyPrivateCert());
|
||||
//中级证书 流
|
||||
unionPayConfigStorage.setAcpMiddleCert(new ByteArrayInputStream(Base64.decode(config.getAcpMiddleCert())));
|
||||
//根证书 流
|
||||
unionPayConfigStorage.setAcpRootCert(new ByteArrayInputStream(Base64.decode(config.getAcpRootCert())));
|
||||
// 私钥证书 流
|
||||
unionPayConfigStorage.setKeyPrivateCert(new ByteArrayInputStream(Base64.decode(config.getKeyPrivateCert())));
|
||||
|
||||
//私钥证书对应的密码 私钥证书对应的密码
|
||||
unionPayConfigStorage.setKeyPrivateCertPwd(config.getKeyPrivateCertPwd());
|
||||
//设置证书对应的存储方式,证书字符串信息
|
||||
unionPayConfigStorage.setCertStoreType(CertStoreType.STR);
|
||||
unionPayConfigStorage.setCertStoreType(CertStoreType.INPUT_STREAM);
|
||||
|
||||
// 回调地址
|
||||
unionPayConfigStorage.setNotifyUrl(config.getNotifyUrl());
|
||||
// 同步回调可不填
|
||||
unionPayConfigStorage.setReturnUrl(config.getReturnUrl());
|
||||
unionPayConfigStorage.setSignType(config.signType);
|
||||
unionPayConfigStorage.setSignType(config.getSignType());
|
||||
//是否为测试账号,沙箱环境
|
||||
unionPayConfigStorage.setTest(config.isSandbox());
|
||||
|
||||
// 网络请求配置
|
||||
HttpConfigStorage httpConfigStorage = new HttpConfigStorage();
|
||||
httpConfigStorage.setCertStoreType(CertStoreType.STR);
|
||||
httpConfigStorage.setCertStoreType(CertStoreType.INPUT_STREAM);
|
||||
//最大连接数
|
||||
httpConfigStorage.setMaxTotal(20);
|
||||
//默认的每个路由的最大连接数
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package cn.bootx.platform.daxpay.service.core.channel.union.service;
|
||||
|
||||
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
|
||||
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
|
||||
import cn.bootx.platform.daxpay.code.RefundSyncStatusEnum;
|
||||
import cn.bootx.platform.daxpay.service.code.UnionPayCode;
|
||||
@@ -9,13 +10,18 @@ import cn.bootx.platform.daxpay.service.core.payment.sync.result.PayGatewaySyncR
|
||||
import cn.bootx.platform.daxpay.service.core.payment.sync.result.RefundGatewaySyncResult;
|
||||
import cn.bootx.platform.daxpay.service.sdk.union.api.UnionPayKit;
|
||||
import cn.bootx.platform.daxpay.service.sdk.union.bean.UnionRefundOrder;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.egzosn.pay.common.bean.AssistOrder;
|
||||
import com.egzosn.pay.common.bean.NoticeParams;
|
||||
import com.egzosn.pay.union.bean.SDKConstants;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -39,33 +45,42 @@ public class UnionPaySyncService {
|
||||
|
||||
AssistOrder query = new AssistOrder();
|
||||
query.setOutTradeNo(String.valueOf(order.getId()));
|
||||
|
||||
Map<String, Object> result = unionPayKit.query(query);
|
||||
|
||||
syncResult.setSyncInfo(JSONUtil.toJsonStr(result));
|
||||
if (!unionPayKit.verify(new NoticeParams(result))) {
|
||||
log.warn("查询云闪付订单验签失败:{}", result);
|
||||
return syncResult.setErrorMsg("查询订单验签失败");
|
||||
}
|
||||
|
||||
// String status = result.get(STATUS);
|
||||
// String returnCode = result.get(RESULT_CODE);
|
||||
//
|
||||
// // 判断查询是否成功
|
||||
// if (!(Objects.equals(SUCCESS, status) && Objects.equals(SUCCESS, returnCode))){
|
||||
// log.warn("查询云闪付订单失败:{}", result);
|
||||
// return syncResult;
|
||||
// }
|
||||
//
|
||||
// // 设置微信支付网关订单号
|
||||
// syncResult.setGatewayOrderNo(result.get(TRANSACTION_ID));
|
||||
// // 查询到订单的状态
|
||||
// String tradeStatus = result.get(TRADE_STATE);
|
||||
// // 支付完成
|
||||
// if (Objects.equals(tradeStatus, SUCCESS)) {
|
||||
// String timeEnd = result.get(TIME_END);
|
||||
// LocalDateTime time = LocalDateTimeUtil.parse(timeEnd, DatePattern.PURE_DATETIME_PATTERN);
|
||||
// return syncResult.setPayTime(time).setSyncStatus(PaySyncStatusEnum.SUCCESS);
|
||||
// }
|
||||
// // 待支付
|
||||
// if (Objects.equals(tradeStatus, TRADE_NOT_PAY)) {
|
||||
// return syncResult.setSyncStatus(PaySyncStatusEnum.PROGRESS);
|
||||
// }
|
||||
// 查询失败
|
||||
String resultCode = MapUtil.getStr(result, SDKConstants.param_respCode);
|
||||
if (!SDKConstants.OK_RESP_CODE.equals(resultCode)) {
|
||||
log.warn("查询云闪付订单失败:{}", result);
|
||||
return syncResult.setErrorMsg(MapUtil.getStr(result, SDKConstants.param_respMsg));
|
||||
}
|
||||
String origRespCode = MapUtil.getStr(result, SDKConstants.param_origRespCode);
|
||||
|
||||
// 查询流水号, 相当于网关订单号
|
||||
|
||||
|
||||
// 成功
|
||||
if (Objects.equals(origRespCode, SDKConstants.OK_RESP_CODE)) {
|
||||
String queryId = MapUtil.getStr(result, QUERY_ID);
|
||||
String timeEnd = MapUtil.getStr(result, TXN_TIME);
|
||||
LocalDateTime time = LocalDateTimeUtil.parse(timeEnd, DatePattern.PURE_DATETIME_PATTERN);
|
||||
return syncResult.setGatewayOrderNo(queryId).setPayTime(time).setSyncStatus(PaySyncStatusEnum.SUCCESS);
|
||||
}
|
||||
// 支付超时 交易不在受理时间范围内
|
||||
if (Objects.equals(origRespCode, "39")) {
|
||||
return syncResult.setSyncStatus(PaySyncStatusEnum.TIMEOUT)
|
||||
.setErrorMsg(MapUtil.getStr(result, SDKConstants.param_origRespMsg));
|
||||
}
|
||||
|
||||
// 待支付
|
||||
if (Objects.equals(origRespCode, "05")) {
|
||||
return syncResult.setSyncStatus(PaySyncStatusEnum.PROGRESS);
|
||||
}
|
||||
//
|
||||
// // 已退款/退款中
|
||||
// if (Objects.equals(tradeStatus, TRADE_REFUND)) {
|
||||
|
@@ -1,11 +1,15 @@
|
||||
package cn.bootx.platform.daxpay.service.dto.channel.union;
|
||||
|
||||
import cn.bootx.platform.common.core.rest.dto.BaseDto;
|
||||
import cn.bootx.platform.daxpay.service.code.UnionPaySignTypeEnum;
|
||||
import cn.bootx.platform.starter.data.perm.sensitive.SensitiveInfo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author xxm
|
||||
* @since 2022/3/11
|
||||
@@ -16,4 +20,90 @@ import lombok.experimental.Accessors;
|
||||
@Schema(title = "云闪付配置")
|
||||
public class UnionPayConfigDto extends BaseDto {
|
||||
|
||||
/** 商户号 */
|
||||
@Schema(description = "商户号")
|
||||
private String machId;
|
||||
|
||||
/** 是否启用, 只影响支付和退款操作 */
|
||||
@Schema(description = "是否启用")
|
||||
private Boolean enable;
|
||||
|
||||
|
||||
/**
|
||||
* 商户收款账号
|
||||
*/
|
||||
@Schema(description = "商户收款账号")
|
||||
private String seller;
|
||||
|
||||
/**
|
||||
* 签名类型
|
||||
* @see UnionPaySignTypeEnum
|
||||
*/
|
||||
@Schema(description = "签名类型")
|
||||
public String signType;
|
||||
|
||||
/**
|
||||
* 是否为证书签名
|
||||
*/
|
||||
@Schema(description = "是否为证书签名")
|
||||
private boolean certSign;
|
||||
|
||||
/**
|
||||
* 应用私钥证书 字符串
|
||||
*/
|
||||
@SensitiveInfo(value = SensitiveInfo.SensitiveType.OTHER, front = 15)
|
||||
@Schema(description = "应用私钥证书")
|
||||
private String keyPrivateCert;
|
||||
/**
|
||||
* 私钥证书对应的密码
|
||||
*/
|
||||
@SensitiveInfo(value = SensitiveInfo.SensitiveType.PASSWORD)
|
||||
@Schema(description = "私钥证书对应的密码")
|
||||
private String keyPrivateCertPwd;
|
||||
|
||||
/**
|
||||
* 中级证书
|
||||
*/
|
||||
@SensitiveInfo(value = SensitiveInfo.SensitiveType.OTHER, front = 15)
|
||||
@Schema(description = "中级证书")
|
||||
private String acpMiddleCert;
|
||||
/**
|
||||
* 根证书
|
||||
*/
|
||||
@SensitiveInfo(value = SensitiveInfo.SensitiveType.OTHER, front = 15)
|
||||
@Schema(description = "根证书")
|
||||
private String acpRootCert;
|
||||
|
||||
/** 是否沙箱环境 */
|
||||
@Schema(description = "是否沙箱环境")
|
||||
private boolean sandbox;
|
||||
|
||||
/** 支付网关地址 */
|
||||
@Schema(description = "支付网关地址")
|
||||
private String serverUrl;
|
||||
|
||||
/**
|
||||
* 服务器异步通知页面路径, 需要填写本网关服务的地址, 不可以直接填写业务系统的地址
|
||||
* 1. 需http://或者https://格式的完整路径,
|
||||
* 2. 不能加?id=123这类自定义参数,必须外网可以正常访问
|
||||
* 3. 消息顺序 银联网关 -> 本网关进行处理 -> 发送消息通知业务系统
|
||||
*/
|
||||
@Schema(description = "异步通知路径")
|
||||
private String notifyUrl;
|
||||
|
||||
/**
|
||||
* 服务器同步通知页面路径, 需要填写本网关服务的地址, 不可以直接填写业务系统的地址
|
||||
* 1. 需http://或者https://格式的完整路径,
|
||||
* 2. 不能加?id=123这类自定义参数,必须外网可以正常访问
|
||||
* 3. 消息顺序 银联网关 -> 本网关进行处理 -> 重定向到业务系统中
|
||||
*/
|
||||
@Schema(description = "同步通知页面路径")
|
||||
private String returnUrl;
|
||||
|
||||
/** 可用支付方式 */
|
||||
@Schema(description = "可用支付方式")
|
||||
private List<String> payWays;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
||||
|
@@ -0,0 +1,97 @@
|
||||
package cn.bootx.platform.daxpay.service.param.channel.union;
|
||||
|
||||
import cn.bootx.platform.daxpay.service.code.UnionPaySignTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 云闪付支付配置参数
|
||||
* @author xxm
|
||||
* @since 2024/3/9
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "云闪付支付配置参数")
|
||||
public class UnionPayConfigParam {
|
||||
|
||||
/** 商户号 */
|
||||
@Schema(description = "商户号")
|
||||
private String machId;
|
||||
|
||||
/** 是否启用, 只影响支付和退款操作 */
|
||||
@Schema(description = "是否启用")
|
||||
private Boolean enable;
|
||||
|
||||
/**
|
||||
* 商户收款账号
|
||||
*/
|
||||
@Schema(description = "商户收款账号")
|
||||
private String seller;
|
||||
|
||||
/**
|
||||
* 签名类型
|
||||
* @see UnionPaySignTypeEnum
|
||||
*/
|
||||
@Schema(description = "签名类型")
|
||||
public String signType;
|
||||
|
||||
/**
|
||||
* 是否为证书签名
|
||||
*/
|
||||
@Schema(description = "是否为证书签名")
|
||||
private boolean certSign;
|
||||
|
||||
/**
|
||||
* 应用私钥证书
|
||||
*/
|
||||
@Schema(description = "应用私钥证书")
|
||||
private String keyPrivateCert;
|
||||
/**
|
||||
* 私钥证书对应的密码
|
||||
*/
|
||||
@Schema(description = "私钥证书对应的密码")
|
||||
private String keyPrivateCertPwd;
|
||||
|
||||
/**
|
||||
* 中级证书
|
||||
*/
|
||||
@Schema(description = "中级证书")
|
||||
private String acpMiddleCert;
|
||||
/**
|
||||
* 根证书
|
||||
*/
|
||||
@Schema(description = "根证书")
|
||||
private String acpRootCert;
|
||||
|
||||
/** 是否沙箱环境 */
|
||||
@Schema(description = "是否沙箱环境")
|
||||
private boolean sandbox;
|
||||
|
||||
/**
|
||||
* 服务器异步通知页面路径, 需要填写本网关服务的地址, 不可以直接填写业务系统的地址
|
||||
* 1. 需http://或者https://格式的完整路径,
|
||||
* 2. 不能加?id=123这类自定义参数,必须外网可以正常访问
|
||||
* 3. 消息顺序 银联网关 -> 本网关进行处理 -> 发送消息通知业务系统
|
||||
*/
|
||||
@Schema(description = "异步通知路径")
|
||||
private String notifyUrl;
|
||||
|
||||
/**
|
||||
* 服务器同步通知页面路径, 需要填写本网关服务的地址, 不可以直接填写业务系统的地址
|
||||
* 1. 需http://或者https://格式的完整路径,
|
||||
* 2. 不能加?id=123这类自定义参数,必须外网可以正常访问
|
||||
* 3. 消息顺序 银联网关 -> 本网关进行处理 -> 重定向到业务系统中
|
||||
*/
|
||||
@Schema(description = "同步通知页面路径")
|
||||
private String returnUrl;
|
||||
|
||||
/** 可用支付方式 */
|
||||
@Schema(description = "可用支付方式")
|
||||
private List<String> payWays;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
@@ -1,8 +1,32 @@
|
||||
package cn.bootx.platform.daxpay.service.sdk.union.api;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.egzosn.pay.common.bean.*;
|
||||
import com.egzosn.pay.common.bean.outbuilder.PayTextOutMessage;
|
||||
import com.egzosn.pay.common.bean.result.PayException;
|
||||
import com.egzosn.pay.common.exception.PayErrorException;
|
||||
import com.egzosn.pay.common.http.HttpConfigStorage;
|
||||
import com.egzosn.pay.common.http.UriVariables;
|
||||
import com.egzosn.pay.common.util.DateUtils;
|
||||
import com.egzosn.pay.common.util.Util;
|
||||
import com.egzosn.pay.common.util.sign.CertDescriptor;
|
||||
import com.egzosn.pay.common.util.sign.SignTextUtils;
|
||||
import com.egzosn.pay.common.util.sign.SignUtils;
|
||||
import com.egzosn.pay.common.util.sign.encrypt.RSA;
|
||||
import com.egzosn.pay.common.util.sign.encrypt.RSA2;
|
||||
import com.egzosn.pay.common.util.str.StringUtils;
|
||||
import com.egzosn.pay.union.api.UnionPayConfigStorage;
|
||||
import com.egzosn.pay.union.api.UnionPayService;
|
||||
import com.egzosn.pay.union.bean.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.*;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 云闪付支付服务类重命名, 避免与系统中类名冲突
|
||||
@@ -10,6 +34,29 @@ import com.egzosn.pay.union.api.UnionPayService;
|
||||
* @since 2024/3/8
|
||||
*/
|
||||
public class UnionPayKit extends UnionPayService {
|
||||
/**
|
||||
* 测试域名
|
||||
*/
|
||||
private static final String TEST_BASE_DOMAIN = "test.95516.com";
|
||||
/**
|
||||
* 正式域名
|
||||
*/
|
||||
private static final String RELEASE_BASE_DOMAIN = "95516.com";
|
||||
/**
|
||||
* 交易请求地址
|
||||
*/
|
||||
private static final String FRONT_TRANS_URL = "https://gateway.%s/gateway/api/frontTransReq.do";
|
||||
private static final String BACK_TRANS_URL = "https://gateway.%s/gateway/api/backTransReq.do";
|
||||
private static final String SINGLE_QUERY_URL = "https://gateway.%s/gateway/api/queryTrans.do";
|
||||
private static final String BATCH_TRANS_URL = "https://gateway.%s/gateway/api/batchTrans.do";
|
||||
private static final String FILE_TRANS_URL = "https://filedownload.%s/";
|
||||
private static final String APP_TRANS_URL = "https://gateway.%s/gateway/api/appTransReq.do";
|
||||
private static final String CARD_TRANS_URL = "https://gateway.%s/gateway/api/cardTransReq.do";
|
||||
/**
|
||||
* 证书解释器
|
||||
*/
|
||||
private volatile CertDescriptor certDescriptor;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
@@ -22,4 +69,675 @@ public class UnionPayKit extends UnionPayService {
|
||||
public UnionPayKit(UnionPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) {
|
||||
super(payConfigStorage, configStorage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置支付配置
|
||||
*
|
||||
* @param payConfigStorage 支付配置
|
||||
*/
|
||||
@Override
|
||||
public UnionPayService setPayConfigStorage(UnionPayConfigStorage payConfigStorage) {
|
||||
this.payConfigStorage = payConfigStorage;
|
||||
if (null != certDescriptor) {
|
||||
return this;
|
||||
}
|
||||
try {
|
||||
certDescriptor = new CertDescriptor();
|
||||
certDescriptor.initPrivateSignCert(payConfigStorage.getKeyPrivateCertInputStream(), payConfigStorage.getKeyPrivateCertPwd(), "PKCS12");
|
||||
certDescriptor.initPublicCert(payConfigStorage.getAcpMiddleCertInputStream());
|
||||
certDescriptor.initRootCert(payConfigStorage.getAcpRootCertInputStream());
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error("", e);
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付请求地址
|
||||
*
|
||||
* @param transactionType 交易类型
|
||||
* @return 请求地址
|
||||
*/
|
||||
@Override
|
||||
public String getReqUrl(TransactionType transactionType) {
|
||||
return (payConfigStorage.isTest() ? TEST_BASE_DOMAIN : RELEASE_BASE_DOMAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据是否为沙箱环境进行获取请求地址
|
||||
*
|
||||
* @return 请求地址
|
||||
*/
|
||||
public String getReqUrl() {
|
||||
return getReqUrl(null);
|
||||
}
|
||||
|
||||
public String getFrontTransUrl() {
|
||||
return String.format(FRONT_TRANS_URL, getReqUrl());
|
||||
}
|
||||
|
||||
public String getBackTransUrl() {
|
||||
return String.format(BACK_TRANS_URL, getReqUrl());
|
||||
}
|
||||
|
||||
public String getAppTransUrl() {
|
||||
return String.format(APP_TRANS_URL, getReqUrl());
|
||||
}
|
||||
|
||||
public String getSingleQueryUrl() {
|
||||
return String.format(SINGLE_QUERY_URL, getReqUrl());
|
||||
}
|
||||
|
||||
|
||||
public String getFileTransUrl() {
|
||||
return String.format(FILE_TRANS_URL, getReqUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台通知地址
|
||||
*
|
||||
* @param parameters 预订单信息
|
||||
* @param order 订单
|
||||
* @return 预订单信息
|
||||
*/
|
||||
private Map<String, Object> initNotifyUrl(Map<String, Object> parameters, AssistOrder order) {
|
||||
//后台通知地址
|
||||
OrderParaStructure.loadParameters(parameters, SDKConstants.param_backUrl, payConfigStorage.getNotifyUrl());
|
||||
OrderParaStructure.loadParameters(parameters, SDKConstants.param_backUrl, order.getNotifyUrl());
|
||||
OrderParaStructure.loadParameters(parameters, SDKConstants.param_backUrl, order);
|
||||
return parameters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改
|
||||
*
|
||||
* @return 返回参数集合
|
||||
*/
|
||||
private Map<String, Object> getCommonParam() {
|
||||
Map<String, Object> params = new TreeMap<>();
|
||||
UnionPayConfigStorage configStorage = payConfigStorage;
|
||||
//银联接口版本
|
||||
params.put(SDKConstants.param_version, configStorage.getVersion());
|
||||
//编码方式
|
||||
params.put(SDKConstants.param_encoding, payConfigStorage.getInputCharset().toUpperCase());
|
||||
//商户代码
|
||||
params.put(SDKConstants.param_merId, payConfigStorage.getPid());
|
||||
|
||||
//订单发送时间
|
||||
params.put(SDKConstants.param_txnTime, DateUtils.formatDate(new Date(), DateUtils.YYYYMMDDHHMMSS));
|
||||
//后台通知地址
|
||||
params.put(SDKConstants.param_backUrl, payConfigStorage.getNotifyUrl());
|
||||
|
||||
//交易币种
|
||||
params.put(SDKConstants.param_currencyCode, "156");
|
||||
//接入类型,商户接入填0 ,不需修改(0:直连商户, 1: 收单机构 2:平台商户)
|
||||
params.put(SDKConstants.param_accessType, configStorage.getAccessType());
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 回调校验
|
||||
*
|
||||
* @param result 回调回来的参数集
|
||||
* @return 签名校验 true通过
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public boolean verify(Map<String, Object> result) {
|
||||
|
||||
|
||||
return verify(new NoticeParams(result));
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调校验
|
||||
*
|
||||
* @param noticeParams 回调回来的参数集
|
||||
* @return 签名校验 true通过
|
||||
*/
|
||||
@Override
|
||||
public boolean verify(NoticeParams noticeParams) {
|
||||
final Map<String, Object> result = noticeParams.getBody();
|
||||
if (null == result || result.get(SDKConstants.param_signature) == null) {
|
||||
LOG.debug("银联支付验签异常:params:" + result);
|
||||
return false;
|
||||
}
|
||||
return this.signVerify(result, (String) result.get(SDKConstants.param_signature));
|
||||
}
|
||||
|
||||
/**
|
||||
* 签名校验
|
||||
*
|
||||
* @param params 参数集
|
||||
* @param sign 签名原文
|
||||
* @return 签名校验 true通过
|
||||
*/
|
||||
public boolean signVerify(Map<String, Object> params, String sign) {
|
||||
SignUtils signUtils = SignUtils.valueOf(payConfigStorage.getSignType());
|
||||
|
||||
String data = SignTextUtils.parameterText(params, "&", "signature");
|
||||
switch (signUtils) {
|
||||
case RSA:
|
||||
data = SignUtils.SHA1.createSign(data, "", payConfigStorage.getInputCharset());
|
||||
return RSA.verify(data, sign, verifyCertificate(genCertificateByStr((String) params.get(SDKConstants.param_signPubKeyCert))).getPublicKey(), payConfigStorage.getInputCharset());
|
||||
case RSA2:
|
||||
data = SignUtils.SHA256.createSign(data, "", payConfigStorage.getInputCharset());
|
||||
return RSA2.verify(data, sign, verifyCertificate(genCertificateByStr((String) params.get(SDKConstants.param_signPubKeyCert))).getPublicKey(), payConfigStorage.getInputCharset());
|
||||
case SHA1:
|
||||
case SHA256:
|
||||
case SM3:
|
||||
String before = signUtils.createSign(payConfigStorage.getKeyPublic(), "", payConfigStorage.getInputCharset());
|
||||
return signUtils.verify(data, sign, "&" + before, payConfigStorage.getInputCharset());
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 订单超时时间。
|
||||
* 超过此时间后,除网银交易外,其他交易银联系统会拒绝受理,提示超时。 跳转银行网银交易如果超时后交易成功,会自动退款,大约5个工作日金额返还到持卡人账户。
|
||||
* 此时间建议取支付时的北京时间加15分钟。
|
||||
* 超过超时时间调查询接口应答origRespCode不是A6或者00的就可以判断为失败。
|
||||
*
|
||||
* @param expirationTime 超时时间
|
||||
* @return 具体的时间字符串
|
||||
*/
|
||||
private String getPayTimeout(Date expirationTime) {
|
||||
//
|
||||
if (null != expirationTime) {
|
||||
return DateUtils.formatDate(expirationTime, DateUtils.YYYYMMDDHHMMSS);
|
||||
}
|
||||
return DateUtils.formatDate(new Timestamp(System.currentTimeMillis() + 30 * 60 * 1000), DateUtils.YYYYMMDDHHMMSS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回创建的订单信息
|
||||
*
|
||||
* @param order 支付订单
|
||||
* @return 订单信息
|
||||
* @see PayOrder 支付订单信息
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> orderInfo(PayOrder order) {
|
||||
Map<String, Object> params = this.getCommonParam();
|
||||
|
||||
UnionTransactionType type = (UnionTransactionType) order.getTransactionType();
|
||||
initNotifyUrl(params, order);
|
||||
|
||||
//设置交易类型相关的参数
|
||||
type.convertMap(params);
|
||||
|
||||
params.put(SDKConstants.param_orderId, order.getOutTradeNo());
|
||||
|
||||
if (StringUtils.isNotEmpty(order.getAddition())) {
|
||||
params.put(SDKConstants.param_reqReserved, order.getAddition());
|
||||
}
|
||||
switch (type) {
|
||||
case WAP:
|
||||
case WEB:
|
||||
//todo PCwap网关跳转支付特殊用法.txt
|
||||
case B2B:
|
||||
params.put(SDKConstants.param_txnAmt, Util.conversionCentAmount(order.getPrice()));
|
||||
params.put("orderDesc", order.getSubject());
|
||||
params.put(SDKConstants.param_payTimeout, getPayTimeout(order.getExpirationTime()));
|
||||
|
||||
params.put(SDKConstants.param_frontUrl, payConfigStorage.getReturnUrl());
|
||||
break;
|
||||
case CONSUME:
|
||||
params.put(SDKConstants.param_txnAmt, Util.conversionCentAmount(order.getPrice()));
|
||||
params.put(SDKConstants.param_qrNo, order.getAuthCode());
|
||||
break;
|
||||
case APPLY_QR_CODE:
|
||||
if (null != order.getPrice()) {
|
||||
params.put(SDKConstants.param_txnAmt, Util.conversionCentAmount(order.getPrice()));
|
||||
}
|
||||
params.put(SDKConstants.param_payTimeout, getPayTimeout(order.getExpirationTime()));
|
||||
break;
|
||||
default:
|
||||
params.put(SDKConstants.param_txnAmt, Util.conversionCentAmount(order.getPrice()));
|
||||
params.put(SDKConstants.param_payTimeout, getPayTimeout(order.getExpirationTime()));
|
||||
params.put("orderDesc", order.getSubject());
|
||||
}
|
||||
params.putAll(order.getAttrs());
|
||||
params = preOrderHandler(params, order);
|
||||
return setSign(params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成并设置签名
|
||||
*
|
||||
* @param parameters 请求参数
|
||||
* @return 请求参数
|
||||
*/
|
||||
private Map<String, Object> setSign(Map<String, Object> parameters) {
|
||||
|
||||
SignUtils signUtils = SignUtils.valueOf(payConfigStorage.getSignType());
|
||||
|
||||
String signStr;
|
||||
switch (signUtils) {
|
||||
case RSA:
|
||||
parameters.put(SDKConstants.param_signMethod, SDKConstants.SIGNMETHOD_RSA);
|
||||
parameters.put(SDKConstants.param_certId, certDescriptor.getSignCertId());
|
||||
signStr = SignUtils.SHA1.createSign(SignTextUtils.parameterText(parameters, "&", "signature"), "", payConfigStorage.getInputCharset());
|
||||
parameters.put(SDKConstants.param_signature, RSA.sign(signStr, certDescriptor.getSignCertPrivateKey(payConfigStorage.getKeyPrivateCertPwd()), payConfigStorage.getInputCharset()));
|
||||
break;
|
||||
case RSA2:
|
||||
parameters.put(SDKConstants.param_signMethod, SDKConstants.SIGNMETHOD_RSA);
|
||||
parameters.put(SDKConstants.param_certId, certDescriptor.getSignCertId());
|
||||
signStr = SignUtils.SHA256.createSign(SignTextUtils.parameterText(parameters, "&", "signature"), "", payConfigStorage.getInputCharset());
|
||||
parameters.put(SDKConstants.param_signature, RSA2.sign(signStr, certDescriptor.getSignCertPrivateKey(payConfigStorage.getKeyPrivateCertPwd()), payConfigStorage.getInputCharset()));
|
||||
break;
|
||||
case SHA1:
|
||||
case SHA256:
|
||||
case SM3:
|
||||
String key = payConfigStorage.getKeyPrivate();
|
||||
signStr = SignTextUtils.parameterText(parameters, "&", "signature");
|
||||
key = signUtils.createSign(key, "", payConfigStorage.getInputCharset()) + "&";
|
||||
parameters.put(SDKConstants.param_signature, signUtils.createSign(signStr, key, payConfigStorage.getInputCharset()));
|
||||
break;
|
||||
default:
|
||||
throw new PayErrorException(new PayException("sign fail", "未找到的签名类型"));
|
||||
}
|
||||
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证证书链
|
||||
*
|
||||
* @param cert 需要验证的证书
|
||||
*/
|
||||
private X509Certificate verifyCertificate(X509Certificate cert) {
|
||||
try {
|
||||
cert.checkValidity();//验证有效期
|
||||
X509Certificate middleCert = certDescriptor.getPublicCert();
|
||||
X509Certificate rootCert = certDescriptor.getRootCert();
|
||||
|
||||
X509CertSelector selector = new X509CertSelector();
|
||||
selector.setCertificate(cert);
|
||||
|
||||
Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
|
||||
trustAnchors.add(new TrustAnchor(rootCert, null));
|
||||
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
|
||||
|
||||
Set<X509Certificate> intermediateCerts = new HashSet<X509Certificate>();
|
||||
intermediateCerts.add(rootCert);
|
||||
intermediateCerts.add(middleCert);
|
||||
intermediateCerts.add(cert);
|
||||
|
||||
pkixParams.setRevocationEnabled(false);
|
||||
|
||||
CertStore intermediateCertStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(intermediateCerts));
|
||||
pkixParams.addCertStore(intermediateCertStore);
|
||||
|
||||
CertPathBuilder builder = CertPathBuilder.getInstance("PKIX");
|
||||
|
||||
/*PKIXCertPathBuilderResult result = (PKIXCertPathBuilderResult)*/
|
||||
builder.build(pkixParams);
|
||||
return cert;
|
||||
}
|
||||
catch (java.security.cert.CertPathBuilderException e) {
|
||||
LOG.error("verify certificate chain fail.", e);
|
||||
}
|
||||
catch (CertificateExpiredException e) {
|
||||
LOG.error("", e);
|
||||
}
|
||||
catch (GeneralSecurityException e) {
|
||||
LOG.error("", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送订单
|
||||
*
|
||||
* @param order 发起支付的订单信息
|
||||
* @return 返回支付结果
|
||||
*/
|
||||
|
||||
public JSONObject postOrder(PayOrder order, String url) {
|
||||
Map<String, Object> params = orderInfo(order);
|
||||
String responseStr = getHttpRequestTemplate().postForObject(url, params, String.class);
|
||||
JSONObject response = UriVariables.getParametersToMap(responseStr);
|
||||
if (response.isEmpty()) {
|
||||
throw new PayErrorException(new PayException("failure", "响应内容有误!", responseStr));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toPay(PayOrder order) {
|
||||
|
||||
if (null == order.getTransactionType()) {
|
||||
order.setTransactionType(UnionTransactionType.WEB);
|
||||
}
|
||||
else if (UnionTransactionType.WEB != order.getTransactionType() && UnionTransactionType.WAP != order.getTransactionType() && UnionTransactionType.B2B != order.getTransactionType()) {
|
||||
throw new PayErrorException(new PayException("-1", "错误的交易类型:" + order.getTransactionType()));
|
||||
}
|
||||
|
||||
return super.toPay(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输出二维码,用户返回给支付端,
|
||||
*
|
||||
* @param order 发起支付的订单信息
|
||||
* @return 返回图片信息,支付时需要的
|
||||
*/
|
||||
@Override
|
||||
public String getQrPay(PayOrder order) {
|
||||
order.setTransactionType(UnionTransactionType.APPLY_QR_CODE);
|
||||
JSONObject response = postOrder(order, getBackTransUrl());
|
||||
if (this.verify(response)) {
|
||||
if (SDKConstants.OK_RESP_CODE.equals(response.get(SDKConstants.param_respCode))) {
|
||||
//成功
|
||||
return (String) response.get(SDKConstants.param_qrCode);
|
||||
}
|
||||
throw new PayErrorException(new PayException((String) response.get(SDKConstants.param_respCode), (String) response.get(SDKConstants.param_respMsg), response.toJSONString()));
|
||||
}
|
||||
throw new PayErrorException(new PayException("failure", "验证签名失败", response.toJSONString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷卡付,pos主动扫码付款(条码付)
|
||||
*
|
||||
* @param order 发起支付的订单信息
|
||||
* @return 返回支付结果
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> microPay(PayOrder order) {
|
||||
order.setTransactionType(UnionTransactionType.CONSUME);
|
||||
JSONObject response = postOrder(order, getBackTransUrl());
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将字符串转换为X509Certificate对象.
|
||||
*
|
||||
* @param x509CertString 证书串
|
||||
* @return X509Certificate
|
||||
*/
|
||||
public static X509Certificate genCertificateByStr(String x509CertString) {
|
||||
X509Certificate x509Cert = null;
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
InputStream tIn = new ByteArrayInputStream(x509CertString.getBytes("ISO-8859-1"));
|
||||
x509Cert = (X509Certificate) cf.generateCertificate(tIn);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new PayErrorException(new PayException("证书加载失败", "gen certificate error:" + e.getLocalizedMessage()));
|
||||
}
|
||||
return x509Cert;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输出消息,用户返回给支付端
|
||||
*
|
||||
* @param code 状态
|
||||
* @param message 消息
|
||||
* @return 返回输出消息
|
||||
*/
|
||||
@Override
|
||||
public PayOutMessage getPayOutMessage(String code, String message) {
|
||||
return PayTextOutMessage.TEXT().content(code.toLowerCase()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取成功输出消息,用户返回给支付端
|
||||
* 主要用于拦截器中返回
|
||||
*
|
||||
* @param payMessage 支付回调消息
|
||||
* @return 返回输出消息
|
||||
*/
|
||||
@Override
|
||||
public PayOutMessage successPayOutMessage(PayMessage payMessage) {
|
||||
return getPayOutMessage("ok", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能:生成自动跳转的Html表单
|
||||
*
|
||||
* @param orderInfo 发起支付的订单信息
|
||||
* @param method 请求方式 "post" "get",
|
||||
* @return 生成自动跳转的Html表单返回给支付端, 针对于PC端
|
||||
* @see MethodType 请求类型
|
||||
*/
|
||||
@Override
|
||||
public String buildRequest(Map<String, Object> orderInfo, MethodType method) {
|
||||
StringBuffer sf = new StringBuffer();
|
||||
sf.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + payConfigStorage.getInputCharset() + "\"/></head><body>");
|
||||
sf.append("<form id = \"pay_form\" action=\"" + getFrontTransUrl() + "\" method=\"post\">");
|
||||
if (null != orderInfo && 0 != orderInfo.size()) {
|
||||
for (Map.Entry<String, Object> entry : orderInfo.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
sf.append("<input type=\"hidden\" name=\"" + key + "\" id=\"" + key + "\" value=\"" + value + "\"/>");
|
||||
}
|
||||
}
|
||||
sf.append("</form>");
|
||||
sf.append("</body>");
|
||||
sf.append("<script type=\"text/javascript\">");
|
||||
sf.append("document.all.pay_form.submit();");
|
||||
sf.append("</script>");
|
||||
sf.append("</html>");
|
||||
return sf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能:将订单信息进行签名并提交请求
|
||||
* 业务范围:手机支付控件(含安卓Pay)
|
||||
*
|
||||
* @param order 订单信息
|
||||
* @return 成功:返回支付结果 失败:返回
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> app(PayOrder order) {
|
||||
if (null == order.getTransactionType()) {
|
||||
order.setTransactionType(UnionTransactionType.APP);
|
||||
}
|
||||
JSONObject response = postOrder(order, getAppTransUrl());
|
||||
if (this.verify(response)) {
|
||||
if (SDKConstants.OK_RESP_CODE.equals(response.get(SDKConstants.param_respCode))) {
|
||||
// //成功,获取tn号
|
||||
// String tn = (String)response.get(SDKConstants.param_tn);
|
||||
// //TODO
|
||||
return response;
|
||||
}
|
||||
throw new PayErrorException(new PayException((String) response.get(SDKConstants.param_respCode), (String) response.get(SDKConstants.param_respMsg), response.toJSONString()));
|
||||
}
|
||||
throw new PayErrorException(new PayException("failure", "验证签名失败", response.toJSONString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 交易查询接口
|
||||
*
|
||||
* @param tradeNo 支付平台订单号
|
||||
* @param outTradeNo 商户单号
|
||||
* @return 返回查询回来的结果集,支付方原值返回
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> query(String tradeNo, String outTradeNo) {
|
||||
return query(new AssistOrder(tradeNo, outTradeNo));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 交易查询接口
|
||||
*
|
||||
* @param assistOrder 查询条件
|
||||
* @return 返回查询回来的结果集,支付方原值返回
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> query(AssistOrder assistOrder) {
|
||||
Map<String, Object> params = this.getCommonParam();
|
||||
UnionTransactionType.QUERY.convertMap(params);
|
||||
params.put(SDKConstants.param_orderId, assistOrder.getOutTradeNo());
|
||||
this.setSign(params);
|
||||
String responseStr = getHttpRequestTemplate().postForObject(this.getSingleQueryUrl(), params, String.class);
|
||||
return UriVariables.getParametersToMap(responseStr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 消费撤销/退货接口
|
||||
*
|
||||
* @param origQryId 原交易查询流水号.
|
||||
* @param orderId 退款单号
|
||||
* @param refundAmount 退款金额
|
||||
* @param type UnionTransactionType.REFUND 或者UnionTransactionType.CONSUME_UNDO
|
||||
* @return 返回支付方申请退款后的结果
|
||||
*/
|
||||
public UnionRefundResult unionRefundOrConsumeUndo(String origQryId, String orderId, BigDecimal refundAmount, UnionTransactionType type) {
|
||||
return unionRefundOrConsumeUndo(new RefundOrder(orderId, origQryId, refundAmount), type);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 消费撤销/退货接口
|
||||
*
|
||||
* @param refundOrder 退款订单信息
|
||||
* @param type UnionTransactionType.REFUND 或者UnionTransactionType.CONSUME_UNDO
|
||||
* @return 返回支付方申请退款后的结果
|
||||
*/
|
||||
public UnionRefundResult unionRefundOrConsumeUndo(RefundOrder refundOrder, UnionTransactionType type) {
|
||||
Map<String, Object> params = this.getCommonParam();
|
||||
type.convertMap(params);
|
||||
params.put(SDKConstants.param_orderId, refundOrder.getRefundNo());
|
||||
params.put(SDKConstants.param_txnAmt, Util.conversionCentAmount(refundOrder.getRefundAmount()));
|
||||
params.put(SDKConstants.param_origQryId, refundOrder.getTradeNo());
|
||||
params.putAll(refundOrder.getAttrs());
|
||||
this.setSign(params);
|
||||
String responseStr = getHttpRequestTemplate().postForObject(this.getBackTransUrl(), params, String.class);
|
||||
JSONObject response = UriVariables.getParametersToMap(responseStr);
|
||||
|
||||
if (this.verify(new NoticeParams(response))) {
|
||||
final UnionRefundResult refundResult = UnionRefundResult.create(response);
|
||||
if (SDKConstants.OK_RESP_CODE.equals(refundResult.getRespCode())) {
|
||||
return refundResult;
|
||||
|
||||
}
|
||||
throw new PayErrorException(new PayException(response.getString(SDKConstants.param_respCode), response.getString(SDKConstants.param_respMsg), response.toJSONString()));
|
||||
}
|
||||
throw new PayErrorException(new PayException("failure", "验证签名失败", response.toJSONString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 交易关闭接口
|
||||
* 使用冲正接口
|
||||
*
|
||||
* @param tradeNo 支付平台订单号
|
||||
* @param outTradeNo 商户单号
|
||||
* @return 返回支付方交易关闭后的结果
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> close(String tradeNo, String outTradeNo) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* 交易关闭接口
|
||||
* TODO 这个是冲正接口, 后续进行迁移
|
||||
*
|
||||
* @param assistOrder 关闭订单
|
||||
* @return 返回支付方交易关闭后的结果
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> close(AssistOrder assistOrder) {
|
||||
Map<String, Object> params = this.getCommonParam();
|
||||
//交易类型
|
||||
params.put(SDKConstants.param_txnType, "99");
|
||||
//交易子类
|
||||
params.put(SDKConstants.param_txnSubType, "01");
|
||||
//业务类型
|
||||
params.put(SDKConstants.param_bizType,"000000");
|
||||
//渠道类型
|
||||
params.put(SDKConstants.param_channelType,"08");
|
||||
// 订单号
|
||||
params.put(SDKConstants.param_orderId, assistOrder.getOutTradeNo());
|
||||
this.setSign(params);
|
||||
String responseStr = getHttpRequestTemplate().postForObject(this.getBackTransUrl(), params, String.class);
|
||||
return UriVariables.getParametersToMap(responseStr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnionRefundResult refund(RefundOrder refundOrder) {
|
||||
return unionRefundOrConsumeUndo(refundOrder, UnionTransactionType.REFUND);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询退款
|
||||
*
|
||||
* @param refundOrder 退款订单单号信息
|
||||
* @return 返回支付方查询退款后的结果
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> refundquery(RefundOrder refundOrder) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载对账单
|
||||
*
|
||||
* @param billDate 账单时间
|
||||
* @param fileType 文件类型 文件类型,一般商户填写00即可
|
||||
* @return 返回fileContent 请自行将数据落地
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> downloadBill(Date billDate, String fileType) {
|
||||
return downloadBill(billDate, new UnionPayBillType(fileType));
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载对账单
|
||||
*
|
||||
* @param billDate 账单时间
|
||||
* @param billType 账单类型
|
||||
* @return 返回fileContent 请自行将数据落地
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> downloadBill(Date billDate, BillType billType) {
|
||||
|
||||
Map<String, Object> params = this.getCommonParam();
|
||||
UnionTransactionType.FILE_TRANSFER.convertMap(params);
|
||||
|
||||
params.put(SDKConstants.param_settleDate, DateUtils.formatDate(billDate, DateUtils.MMDD));
|
||||
params.put(SDKConstants.param_fileType, billType.getFileType());
|
||||
params.remove(SDKConstants.param_backUrl);
|
||||
params.remove(SDKConstants.param_currencyCode);
|
||||
this.setSign(params);
|
||||
String responseStr = getHttpRequestTemplate().postForObject(this.getFileTransUrl(), params, String.class);
|
||||
JSONObject response = UriVariables.getParametersToMap(responseStr);
|
||||
if (this.verify(response)) {
|
||||
if (SDKConstants.OK_RESP_CODE.equals(response.get(SDKConstants.param_respCode))) {
|
||||
return response;
|
||||
|
||||
}
|
||||
throw new PayErrorException(new PayException(response.get(SDKConstants.param_respCode).toString(), response.get(SDKConstants.param_respMsg).toString(), response.toString()));
|
||||
|
||||
}
|
||||
throw new PayErrorException(new PayException("failure", "验证签名失败", response.toString()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建消息
|
||||
*
|
||||
* @param message 支付平台返回的消息
|
||||
* @return 支付消息对象
|
||||
*/
|
||||
@Override
|
||||
public PayMessage createMessage(Map<String, Object> message) {
|
||||
return UnionPayMessage.create(message);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user