diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/config/YstBeanConfig.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/config/YstBeanConfig.java new file mode 100644 index 00000000..e5b4d151 --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/config/YstBeanConfig.java @@ -0,0 +1,23 @@ +package com.ruoyi.common.pay.yst.config; + +import com.ruoyi.common.pay.yst.util.YstClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author wjh + * 2024/8/1 + */ +@Configuration +public class YstBeanConfig { + + @Autowired + private YstConfig ystConfig; + + @Bean + public YstClient ystClient() throws Exception { + return new YstClient(ystConfig); + } + +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/config/YstConfig.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/config/YstConfig.java new file mode 100644 index 00000000..6f9fdb2c --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/config/YstConfig.java @@ -0,0 +1,48 @@ +package com.ruoyi.common.pay.yst.config; + +import com.ruoyi.common.pay.yst.util.YstClient; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +/** + * @author wjh + * 2024/8/1 + */ +@Component +@ConfigurationProperties(prefix = "yst") +@Data +public class YstConfig { + + // APP ID + private String appId; + + // 云商通二代分配的服务商应用ID + private String spAppId; + + // 秘钥 + private String secretKey; + + // 域名 + private String host; + + // 格式化 + private String format; + + // 字符集 + private String charset; + + // 签名方式 + private String signType; + + // API版本 + private String version; + + // 私钥 + private String privateKey; + + // 公钥 + private String publicKey; + +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/constants/YstApi.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/constants/YstApi.java new file mode 100644 index 00000000..ed3f586d --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/constants/YstApi.java @@ -0,0 +1,27 @@ +package com.ruoyi.common.pay.yst.constants; + +/** + * @author wjh + * 2024/8/1 + */ +public class YstApi { + + // 会员及账户类接口 + public static final String TM = "/yst-service-api/tm/handle"; + + // 交易类接口 + public static final String TX = "/yst-service-api/tx/handle"; + + // 交易结果查询类接口 + public static final String TQ = "/yst-service-api/tq/handle"; + + // 对账文件下载类接口 + public static final String DOWNLOAD = "/yst-service-api/file/download"; + + // 文件上传 + public static final String UPLOAD = "/yst-service-api/file/upload"; + + + + +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/constants/YstTransCode.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/constants/YstTransCode.java new file mode 100644 index 00000000..985e02aa --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/constants/YstTransCode.java @@ -0,0 +1,100 @@ +package com.ruoyi.common.pay.yst.constants; + +/** + * 云商通接口代码 + * @author wjh + * 2024/8/1 + */ +public class YstTransCode { + + // 企业会员实名开户 + public static final String ENTERPRISE_MEMBER_REAL_NAME = "1020"; + + // 会员资料补录 + public static final String MEMBER_INFO_SUPPLEMENT = "1022"; + + // 个人会员实名及绑卡(申请) + public static final String INDIVIDUAL_MEMBER_REAL_NAME_AND_BIND_CARD_APPLY = "1010"; + + // 个人会员实名及绑卡(确认) + public static final String INDIVIDUAL_MEMBER_REAL_NAME_AND_BIND_CARD_CONFIRM = "1011"; + + // 会员绑定手机号申请 + public static final String MEMBER_BIND_MOBILE_APPLY = "1030"; + + // 会员解绑手机号(原手机号)申请 + public static final String MEMBER_UNBIND_MOBILE_APPLY = "1031"; + + // 确认绑定/解绑手机号 + public static final String CONFIRM_BIND_UNBIND_MOBILE = "1032"; + + // 会员线上协议签约申请 + public static final String MEMBER_ONLINE_AGREEMENT_SIGN_APPLY = "1050"; + + // 线下协议文件上传 + public static final String OFFLINE_AGREEMENT_FILE_UPLOAD = "1051"; + + // 会员协议签约结果通知 + public static final String MEMBER_AGREEMENT_SIGN_RESULT_NOTIFY = "1052"; + + // 企业会员支付账户开户 + public static final String ENTERPRISE_MEMBER_PAYMENT_ACCOUNT_OPEN = "1025"; + + // 查询会员信息 + public static final String QUERY_MEMBER_INFO = "1027"; + + // 账户余额查询 + public static final String ACCOUNT_BALANCE_QUERY = "1023"; + + // 平台资金查询 + public static final String PLATFORM_FUND_QUERY = "1026"; + + // 会员绑定收银宝商户 + public static final String MEMBER_BIND_SHOUYINBAO_MERCHANT = "1024"; + + // 终端信息管理 + public static final String TERMINAL_INFO_MANAGEMENT = "4001"; + + // 消费申请 + public static final String CONSUMPTION_APPLY = "2085"; + + // 担保消费申请 + public static final String GUARANTEE_CONSUMPTION_APPLY = "2089"; + + // 单订单担保确认 + public static final String SINGLE_ORDER_GUARANTEE_CONFIRM = "2090"; + + // 单会员担保确认 + public static final String SINGLE_MEMBER_GUARANTEE_CONFIRM = "2091"; + + // 划款入账通知 + public static final String TRANSFER_INTO_ACCOUNT_NOTIFY = "2080"; + + // 转账申请 + public static final String TRANSFER_APPLY = "2084"; + + // 提现申请 + public static final String WITHDRAW_APPLY = "2290"; + + // 退款申请 + public static final String REFUND_APPLY = "2294"; + + // 订单关闭 + public static final String ORDER_CLOSE = "2295"; + + // 确认支付(后台+短信) + public static final String CONFIRM_PAYMENT_BACK_SMS = "3010"; + + // 订单状态查询 + public static final String ORDER_STATUS_QUERY = "3001"; + + // 订单详情查询 + public static final String ORDER_DETAILS_QUERY = "3002"; + + // 电子回单下载 + public static final String ELECTRONIC_RECEIPT_DOWNLOAD = "4003"; + + // 应用集合对账文件下载 + public static final String APPLICATION_SET_RECONCILIATION_FILE_DOWNLOAD = "4002"; + +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/BizParameter.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/BizParameter.java new file mode 100644 index 00000000..f8e8c79b --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/BizParameter.java @@ -0,0 +1,36 @@ +package com.ruoyi.common.pay.yst.domain; + + +import com.alibaba.fastjson2.JSONObject; + +import java.util.List; +import java.util.Map; + +/** + * 业务参数 + * + * @author gejunqing + * @version 1.0 + * @date 2024/1/11 + */ +public class BizParameter extends JSONObject +{ + public void addParam(String paramName, String paramValue) + { + this.put(paramName, paramValue); + } + + public void addParam(String paramName, int paramValue) + { + this.put(paramName, paramValue); + } + + public void addMapParam(String paramName, Map paramMap) + { + this.put(paramName, paramMap); + } + public void addListParam(String paramName, List paramValue) + { + this.put(paramName, paramValue); + } +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/YstRequest.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/YstRequest.java new file mode 100644 index 00000000..6a8d4bb2 --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/YstRequest.java @@ -0,0 +1,68 @@ +package com.ruoyi.common.pay.yst.domain; + +import com.alibaba.fastjson2.JSON; +import lombok.Data; + +/** + * 云商通通用请求数据结构 + * 字段 字段类型 必填 字段名称 说明 + * appId String 是 云商通二代分配的应用ID 如上送服务商应用ID,验证服务商证书 + * spAppId String 否 云商通二代分配的服务商应用ID + * transCode String 是 接口代码 + * format String 否 仅支持JSON json + * charset String 是 请求使用的编码格式,utf-8 UTF-8 + * signType String 是 商户生成签名字符串所使用的签名 + * 算法类型 比如:SM3withSM2 + * sign String 是 商户请求参数的签名串 + * transDate String 是 发送请求的日期,格式"yyyyMMdd" 20240202 + * transTime String 是 请求时间 141333 + * version String 是 调用的接口版本 1.0 + * bizData String 是 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档 + * + * @author wjh + * 2024/8/1 + */ +@Data +public class YstRequest { + + // 云商通二代分配的应用ID + private String appId; + + // 云商通二代分配的服务商应用ID + private String spAppId; + + // 接口代码 + private String transCode; + + // 仅支持JSON + private String format; + + // 请求使用的编码格式 + private String charset; + + // 商户生成签名字符串所使用的签名算法类型 + private String signType; + + // 签名串 + private String sign; + + // 发送请求的日期,格式"yyyyMMdd" + private String transDate; + + // 请求时间 + private String transTime; + + // 调用的接口版本 + private String version; + + // 请求参数的集合 + private String bizData; + + + @Override + public String toString() + { + return JSON.toJSONString(this); + } + +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/YstResponse.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/YstResponse.java new file mode 100644 index 00000000..f02451f6 --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/YstResponse.java @@ -0,0 +1,32 @@ +package com.ruoyi.common.pay.yst.domain; + +import lombok.Data; + +/** + * 字段 字段类型 必填 字段名称 说明 + * code String 是 调用结果返回码 00000-成功 + * msg String 是 调用结果返回码描述 + * sign String 是 商户请求参数的签名串 + * bizData String 是 返回参数的集合,最大长度不限,除公共参数外所有返回参数都必须放在这个参数中传递,具体参照各产品快速接入文档 + * + * @author wjh + * 2024/8/1 + */ +@Data +public class YstResponse { + // 调用结果返回码 + private String code; + + // 调用结果返回码描述 + private String msg; + + // 商户请求参数的签名串 + private String sign; + + // 返回参数的集合 + private T bizData; + + public boolean isSuccess() { + return this.code != null && this.code.equals("00000"); + } +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/bizRes/comsumptionApply/ChannelParamInfo.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/bizRes/comsumptionApply/ChannelParamInfo.java new file mode 100644 index 00000000..9667d53e --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/bizRes/comsumptionApply/ChannelParamInfo.java @@ -0,0 +1,86 @@ +package com.ruoyi.common.pay.yst.domain.bizRes.comsumptionApply; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +/** + * @author wjh + * 2024/8/2 + */ +@Data +public class ChannelParamInfo { + /** + * 收银宝渠道交易流水号。 + */ + private String chnlTradeCode; + + /** + * 收银宝渠道返回的交易类型,针对退款订单,透传退款订单落地的“渠道交易类型”。 + */ + private String chnlTransCode; + + /** + * 支付人账号,根据不同支付渠道返回不同的账号信息,如微信的openid,支付宝的user_id。 + */ + private String payAcctNo; + + /** + * 渠道实际交易金额,单位为分。 + */ + private Long chnlAmount; + + /** + * 渠道手续费,单位为分。 + */ + private Long chnlFee; + + /** + * 透传渠道活动参数,包含云闪付、微信、支付宝的活动参数。 + */ + private JSONObject chnlData; + + /** + * 收银宝接口交易完成时间。 + */ + private String chnlFinTime; + + /** + * 收银宝商户号。 + */ + private String chnlMchtNo; + + /** + * 卡类型,如借记卡、信用卡或其他。 + */ + private String acctType; + + /** + * 后侧渠道流水号,如支付宝订单号,微信交易单号。 + */ + private String chnlTrxid; + + /** + * 收付通银行流水号,透传收付通渠道的银行流水号。 + */ + private String sftChnlBankTradeCode; + + /** + * 终端编号。 + */ + private String termNo; + + /** + * 银行借贷标记,仅微信支付返回。 + */ + private String termAuthCode; + + /** + * 终端参考号,收银宝订单POS支付时有值。 + */ + private String termRefnum; + + /** + * 终端流水号,对应收银宝接口字段traceno。 + */ + private String traceNo; +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/bizRes/comsumptionApply/ConsumptionApplyRes.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/bizRes/comsumptionApply/ConsumptionApplyRes.java new file mode 100644 index 00000000..a490e349 --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/bizRes/comsumptionApply/ConsumptionApplyRes.java @@ -0,0 +1,41 @@ +package com.ruoyi.common.pay.yst.domain.bizRes.comsumptionApply; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +/** + * 消费申请响应 + * @author wjh + * 2024/8/2 + */ +@Data +public class ConsumptionApplyRes { + + // 订单状态,仅当交易验证方式为“0”时返回,表示订单是否成功 + private String result; + + // 订单失败时的错误信息 + private String respMsg; + + // 通联支付系统生成的唯一订单号 + private String respTraceNum; + + // 商户系统内部的订单号,用于商户识别订单 + private String reqTraceNum; + + // 扩展参数,用于传递额外的信息 + private String extendParams; + + // 渠道参数信息,包含支付详情,如扫码支付信息、JS支付串信息等 + private ChannelParamInfo channelParamInfo; + + // 前端支付参数信息,用于前端展示和支付操作 + private JSONObject chnlFrontParamInfo; + + // 业务返回码,用于标识业务处理的结果 + private String respCode; + + // 详细的业务返回说明,提供更多关于业务处理情况的信息 + private String respMsgDetailed; + +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/params/AddOrderParam.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/params/AddOrderParam.java new file mode 100644 index 00000000..dac8ce06 --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/domain/params/AddOrderParam.java @@ -0,0 +1,10 @@ +package com.ruoyi.common.pay.yst.domain.params; + +/** + * 消费申请 参数 + * @author wjh + * 2024/8/1 + */ +public class AddOrderParam { + +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/enums/YstRespCode.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/enums/YstRespCode.java new file mode 100644 index 00000000..dc420ba4 --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/enums/YstRespCode.java @@ -0,0 +1,18 @@ +package com.ruoyi.common.pay.yst.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author wjh + * 2024/8/1 + */ +@Getter +@AllArgsConstructor +public enum YstRespCode { + + SUCCESS("00000", "成功"); + + private final String code; + private final String msg; +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/enums/YstVerifyType.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/enums/YstVerifyType.java new file mode 100644 index 00000000..934bc68d --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/enums/YstVerifyType.java @@ -0,0 +1,21 @@ +package com.ruoyi.common.pay.yst.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 交易验证方式 + * @author wjh + * 2024/8/2 + */ +@Getter +@AllArgsConstructor +public enum YstVerifyType { + + NONE("0", "无验证"), + SMS("1", "短信验证码"), + ; + + private final String type; + private final String msg; +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/service/YstAccountService.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/service/YstAccountService.java new file mode 100644 index 00000000..f30bb240 --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/service/YstAccountService.java @@ -0,0 +1,9 @@ +package com.ruoyi.common.pay.yst.service; + +/** + * 云商通账户接口 + * @author wjh + * 2024/8/1 + */ +public interface YstAccountService { +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/service/YstPayService.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/service/YstPayService.java new file mode 100644 index 00000000..c8d4b13a --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/service/YstPayService.java @@ -0,0 +1,15 @@ +package com.ruoyi.common.pay.yst.service; + +/** + * 云商通支付服务 + * @author wjh + * 2024/8/1 + */ +public interface YstPayService { + + /** + * 调起支付 + */ + void pay() throws Exception; + +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/service/impl/YstAccountServiceImpl.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/service/impl/YstAccountServiceImpl.java new file mode 100644 index 00000000..0826092f --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/service/impl/YstAccountServiceImpl.java @@ -0,0 +1,12 @@ +package com.ruoyi.common.pay.yst.service.impl; + +import com.ruoyi.common.pay.yst.service.YstAccountService; +import org.springframework.stereotype.Service; + +/** + * @author wjh + * 2024/8/1 + */ +@Service +public class YstAccountServiceImpl implements YstAccountService { +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/service/impl/YstPayServiceImpl.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/service/impl/YstPayServiceImpl.java new file mode 100644 index 00000000..0d9b939a --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/service/impl/YstPayServiceImpl.java @@ -0,0 +1,150 @@ +package com.ruoyi.common.pay.yst.service.impl; + +import com.ruoyi.common.pay.yst.config.YstConfig; +import com.ruoyi.common.pay.yst.constants.YstApi; +import com.ruoyi.common.pay.yst.constants.YstTransCode; +import com.ruoyi.common.pay.yst.domain.BizParameter; +import com.ruoyi.common.pay.yst.domain.YstResponse; +import com.ruoyi.common.pay.yst.domain.bizRes.comsumptionApply.ConsumptionApplyRes; +import com.ruoyi.common.pay.yst.domain.params.AddOrderParam; +import com.ruoyi.common.pay.yst.service.YstPayService; +import com.ruoyi.common.pay.yst.util.SM4Utils; +import com.ruoyi.common.pay.yst.util.YstClient; +import com.ruoyi.common.utils.SnowFlakeUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.text.SimpleDateFormat; +import java.util.*; + +import static jdk.nashorn.internal.runtime.regexp.joni.Config.log; + +/** + * @author wjh + * 2024/8/1 + */ +@Service +@Slf4j +@RestController +@RequestMapping("/yst") +public class YstPayServiceImpl implements YstPayService { + + @Autowired + private YstClient ystClient; + + @Autowired + private YstConfig ystConfig; + + /** + * 调起支付 + */ + @Override + @GetMapping + public void pay() throws Exception { + + // 支付模式 + String bankCardNo= SM4Utils.encryptEcb(ystConfig.getSecretKey(), "6212261001029054530"); + //微信正扫 + Map SCAN_WEIXIN = new HashMap<>(); + SCAN_WEIXIN.put("SCAN_WEIXIN","{\"limitPay\":\"no_credit\"}"); + SCAN_WEIXIN.put("SCAN_WEIXIN","{\"vspCusid\":\"55058404816VQJW\"}"); +// SCAN_WEIXIN.put("SCAN_WEIXIN","{\"extendParams\":\"渠道扩展参数\"}"); +// SCAN_WEIXIN.put("SCAN_WEIXIN","{\"idNo\":\"\"}"); +// SCAN_WEIXIN.put("SCAN_WEIXIN","{\"name\":\"\"}"); +// SCAN_WEIXIN.put("SCAN_WEIXIN","{\"cardThype\":\"\"}"); + + + //收银宝快捷 + Map QUICKPAY_VSP = new HashMap<>(); + + System.out.println("{\"bankCardNo\":\""+bankCardNo+"\"}"); + QUICKPAY_VSP.put("QUICKPAY_VSP","{\"bankCardNo\":\""+bankCardNo+"\"}"); + + //收付通快捷 + Map QUICKPAY_SFT = new HashMap<>(); + System.out.println("{\"bankCardNo\":\""+bankCardNo+"\"}"); + QUICKPAY_SFT.put("QUICKPAY_SFT","{\"bankCardNo\":\""+bankCardNo+"\"}"); + + //银联正扫 + Map SCAN_UNIONPAY = new HashMap<>(); + SCAN_UNIONPAY.put("SCAN_UNIONPAY","{\"limitPay\":\"no_credit\"}"); + + //支付宝正扫 + Map SCAN_ALIPAY = new HashMap<>(); + SCAN_ALIPAY.put("SCAN_ALIPAY","{\"limitPay\":\"no_credit\"}"); + + //收银宝POS + Map ORDER_VSPPAY = new HashMap<>(); + ORDER_VSPPAY.put("ORDER_VSPPAY","{\"vspCusid\":\"6602900601500JK\"}"); + + //付款码支付 + Map CODEPAY_VSP = new HashMap<>(); + CODEPAY_VSP.put("CODEPAY_VSP","{\"vspCusid\":\"\"}"); + CODEPAY_VSP.put("CODEPAY_VSP","{\"authcode\":\"280310687633511560\"}"); + + //分账规则 + List sepDetail = new ArrayList(); + Map map1 = new HashMap<>(); + map1.put("signNum", "2705wxl00002"); + map1.put("amount", 1); + sepDetail.add(map1); + + + Map map2 = new HashMap<>(); + map2.put("signNum", "9665wxl202400004"); + map2.put("amount", 1); +// sepDetail.add(map2); + + + //订单过期时间 + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + final Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.MINUTE, 720); + final Date date = calendar.getTime(); + final String orderValidTime = sdf.format(date); + + // 组装参数 + BizParameter bizParameter = new BizParameter(); + bizParameter.addParam("signNum", "111111");//TESTWC10011 + bizParameter.addParam("receiverSignNum", "2705wxl00001"); + bizParameter.addParam("reqTraceNum", "wxl"+ SnowFlakeUtil.newId());//商户订单号 + bizParameter.addParam("orderAmount", 3);//订单金额 + bizParameter.addParam("payAmount", 2);//支付金额 + bizParameter.addParam("promotionAmount", 1);//营销金额 + bizParameter.addParam("couponAmount",1 );//平台抽佣金额 + bizParameter.addParam("verifyType", 1);//交易验证方式-0:无验证、1:短信验证码(默认-1:短信验证码) + + //支付模式 + bizParameter.addMapParam("payMode", SCAN_WEIXIN);//微信正扫 +// bizParameter.addMapParam("payMode", QUICKPAY_VSP);//收银宝快捷 +// bizParameter.addMapParam("payMode", QUICKPAY_SFT);//收付通快捷 +// bizParameter.addMapParam("payMode", SCAN_UNIONPAY);//银联正扫 +// bizParameter.addMapParam("payMode", SCAN_ALIPAY);//支付宝正扫 +// bizParameter.addMapParam("payMode", ORDER_VSPPAY);//收银宝POS +// bizParameter.addMapParam("payMode", CODEPAY_VSP);//付款码支付 + + bizParameter.addListParam("sepDetail", sepDetail);//分账规则 + bizParameter.addParam("reqsUrl", "http://www.baidu.com");//前台通知地址 + bizParameter.addParam("respUrl", "http://test.allinpay.com/open/testNotify");//后台通知地址 + bizParameter.addParam("orderValidTime", orderValidTime);//订单过期时间 +// bizParameter.addParam("goodsType", "goodsType");//商品类型 +// bizParameter.addParam("bizGoodsNo", "bizGoodsNo123456");//商户商品编号 + bizParameter.addParam("goodsName", "goodsName123456");//商品名称 + bizParameter.addParam("goodsDesc", "goodsDesc123456");//商品描述 + bizParameter.addParam("industryCode", "111111");//行业代码 + bizParameter.addParam("industryName", "222222");//行业名称 + bizParameter.addParam("summary", "摘要"); + bizParameter.addParam("extendParams", "{\"extend\":\"abcdefghiii\"}"); + + // 发送请求 + YstResponse res = ystClient.sendRequest(YstApi.TX, YstTransCode.CONSUMPTION_APPLY, bizParameter, ConsumptionApplyRes.class); + res.getBizData().getChannelParamInfo(); + // 打印响应结果 + log.info(res.getBizData().toString()); + + } +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/util/SM2Utils.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/util/SM2Utils.java new file mode 100644 index 00000000..1b6b94c1 --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/util/SM2Utils.java @@ -0,0 +1,92 @@ +package com.ruoyi.common.pay.yst.util; + +import com.alibaba.fastjson2.JSONObject; +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.util.encoders.Base64; + +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + +/** + * Created with IntelliJ IDEA. + * + * @author gejunqing + * @version 1.0 + * @date 2024/1/11 + */ +public class SM2Utils +{ + /** 算法常量:SM3withSM2 */ + public static final String ALGORITHM_SM3SM2_BCPROV = "SM3withSM2"; + + static + { + Security.addProvider(new BouncyCastleProvider()); + } + + public static String jsonMapToStr(JSONObject jsonObject) + { + String[] keys = jsonObject.keySet().toArray(new String[0]); + Arrays.sort(keys); + StringBuilder raw = new StringBuilder(); + for (String key : keys) + { + if (!StringUtils.isBlank(jsonObject.getString(key))) + { + raw.append(key).append("=").append(jsonObject.getString(key)).append("&"); + } + } + + if (raw.length() > 0) + { + raw.deleteCharAt(raw.length() - 1); + } + return raw.toString(); + } + + /** 从字符串读取私钥-目前支持PKCS8(keystr为BASE64格式) */ + public static PrivateKey privKeySM2FromBase64Str(String keystr) throws Exception + { + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(keystr))); + } + + /** 从字符串读取RSA公钥(keystr为BASE64格式) */ + public static PublicKey pubKeySM2FromBase64Str(String keystr) throws Exception + { + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + return keyFactory.generatePublic(new X509EncodedKeySpec(Base64.decode(keystr))); + } + + public static String sign(PrivateKey privateKey, String text) throws Exception + { + Signature signature = Signature.getInstance(ALGORITHM_SM3SM2_BCPROV, "BC"); + signature.initSign(privateKey); + byte[] plainText = text.getBytes(StandardCharsets.UTF_8); + signature.update(plainText); + byte[] signatureValue = signature.sign(); + return Base64.toBase64String(signatureValue); + } + + public static boolean verify(PublicKey publicKey, String text, String sign) throws Exception + { + if (isEmpty(sign)) + { + return false; + } + Signature signature = Signature.getInstance(ALGORITHM_SM3SM2_BCPROV, "BC"); + signature.initVerify(publicKey); + signature.update(text.getBytes(StandardCharsets.UTF_8)); + byte[] signed = Base64.decode(sign); + return signature.verify(signed); + } + + public static boolean isEmpty(String str) + { + return str == null || "".equals(str) || "".equals(str.trim()); + } +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/util/SM4Utils.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/util/SM4Utils.java new file mode 100644 index 00000000..fbf657e0 --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/util/SM4Utils.java @@ -0,0 +1,184 @@ +package com.ruoyi.common.pay.yst.util; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.spec.SecretKeySpec; +import java.security.*; +import java.util.Arrays; + +/** + * Created with IntelliJ IDEA. + * + * @author gejunqing + * @version 1.0 + * @date 2024/1/25 + */ +public class SM4Utils +{ + static + { + Security.addProvider(new BouncyCastleProvider()); + } + + private static final String ENCODING = "UTF-8"; + + public static final String ALGORITHM_NAME = "SM4"; + + // 加密算法/分组加密模式/分组填充方式 + // PKCS5Padding-以8个字节为一组进行分组加密 + // 定义分组加密模式使用:PKCS5Padding + public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding"; + // 128-32位16进制;256-64位16进制 + public static final int DEFAULT_KEY_SIZE = 128; + + /** + * 生成ECB暗号 + * + * @explain ECB模式(电子密码本模式:Electronic codebook) + * @param algorithmName 算法名称 + * @param mode 模式 + * @param key + * @return + * @throws Exception + */ + private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception + { + Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME); + Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME); + cipher.init(mode, sm4Key); + return cipher; + } + + /** + * 自动生成密钥 + * + * @explain + * @return + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + */ + public static byte[] generateKey() throws Exception + { + return generateKey(DEFAULT_KEY_SIZE); + } + + /** + * @explain + * @param keySize + * @return + * @throws Exception + */ + public static byte[] generateKey(int keySize) throws Exception + { + KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME); + kg.init(keySize, new SecureRandom()); + return kg.generateKey().getEncoded(); + } + + /** + * sm4加密 + * + * @explain 加密模式:ECB 密文长度不固定,会随着被加密字符串长度的变化而变化 + * @param hexKey 16进制密钥(忽略大小写) + * @param paramStr 待加密字符串 + * @return 返回16进制的加密字符串 + * @throws Exception + */ + public static String encryptEcb(String hexKey, String paramStr) throws Exception + { + String cipherText = ""; + // 16进制字符串-->byte[] + byte[] keyData = ByteUtils.fromHexString(hexKey); + // String-->byte[] + byte[] srcData = paramStr.getBytes(ENCODING); + // 加密后的数组 + byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData); + // byte[]-->hexString + cipherText = ByteUtils.toHexString(cipherArray); + return cipherText; + } + + /** + * 加密模式之Ecb + * + * @explain + * @param key + * @param data + * @return + * @throws Exception + */ + public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception + { + Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key); + return cipher.doFinal(data); + } + + /** + * sm4解密 + * + * @explain 解密模式:采用ECB + * @param hexKey 16进制密钥 + * @param cipherText 16进制的加密字符串(忽略大小写) + * @return 解密后的字符串 + * @throws Exception + */ + public static String decryptEcb(String hexKey, String cipherText) throws Exception + { + // 用于接收解密后的字符串 + String decryptStr = ""; + // hexString-->byte[] + byte[] keyData = ByteUtils.fromHexString(hexKey); + // hexString-->byte[] + byte[] cipherData = ByteUtils.fromHexString(cipherText); + // 解密 + byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData); + // byte[]-->String + decryptStr = new String(srcData, ENCODING); + return decryptStr; + } + + /** + * 解密 + * + * @explain + * @param key + * @param cipherText + * @return + * @throws Exception + */ + public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception + { + Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key); + return cipher.doFinal(cipherText); + } + + /** + * 校验加密前后的字符串是否为同一数据 + * + * @explain + * @param hexKey 16进制密钥(忽略大小写) + * @param cipherText 16进制加密后的字符串 + * @param paramStr 加密前的字符串 + * @return 是否为同一数据 + * @throws Exception + */ + public static boolean verifyEcb(String hexKey, String cipherText, String paramStr) throws Exception + { + // 用于接收校验结果 + boolean flag = false; + // hexString-->byte[] + byte[] keyData = ByteUtils.fromHexString(hexKey); + // 将16进制字符串转换成数组 + byte[] cipherData = ByteUtils.fromHexString(cipherText); + // 解密 + byte[] decryptData = decrypt_Ecb_Padding(keyData, cipherData); + // 将原字符串转换成byte[] + byte[] srcData = paramStr.getBytes(ENCODING); + // 判断2个数组是否一致 + flag = Arrays.equals(decryptData, srcData); + return flag; + } +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/util/TxTrustManager.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/util/TxTrustManager.java new file mode 100644 index 00000000..79f5b88e --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/util/TxTrustManager.java @@ -0,0 +1,67 @@ +package com.ruoyi.common.pay.yst.util; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** + * Created with IntelliJ IDEA. + * + * @author gejunqing + * @version 1.0 + * @date 2024/1/11 + */ +public class TxTrustManager implements X509TrustManager +{ + private static volatile TxTrustManager instance; + private SSLSocketFactory sslFactory; + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException + { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException + { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() + { + return new X509Certificate[0]; + } + + public SSLSocketFactory getSSLSocketFactory() + { + return this.sslFactory; + } + + private TxTrustManager() + { + } + + public static TxTrustManager instance() throws NoSuchAlgorithmException, KeyManagementException + { + if (instance == null) + { + synchronized (TxTrustManager.class) + { + if (instance == null) + { + instance = new TxTrustManager(); + SSLContext sc = SSLContext.getInstance("TLSv1.2"); + sc.init(null, new TxTrustManager[] { instance }, null); + instance.sslFactory = sc.getSocketFactory(); + } + } + } + return instance; + } +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/util/YstClient.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/util/YstClient.java new file mode 100644 index 00000000..75a8072d --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/yst/util/YstClient.java @@ -0,0 +1,166 @@ +package com.ruoyi.common.pay.yst.util; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.TypeReference; +import com.ruoyi.common.pay.yst.config.YstConfig; +import com.ruoyi.common.pay.yst.domain.BizParameter; +import com.ruoyi.common.pay.yst.domain.YstRequest; +import com.ruoyi.common.pay.yst.domain.YstResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import javax.net.ssl.HttpsURLConnection; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * 云商通请求客户端 + * @author wjh + * 2024/8/1 + */ +@Slf4j +public class YstClient { + private static final String YYYY_MM_DD = "yyyyMMdd"; + private static final String HH_MM_SS = "HHmmss"; + + private YstConfig config; + + private final PrivateKey privateKey; + + private final PublicKey tlPublicKey; + + public YstClient(YstConfig config) throws Exception + { + this.config = config; + this.privateKey = SM2Utils.privKeySM2FromBase64Str(config.getPrivateKey()); + this.tlPublicKey = SM2Utils.pubKeySM2FromBase64Str(config.getPublicKey()); + } + + /** + * 发送请求 + */ + public YstResponse sendRequest(String api, String transCode, BizParameter param, Class type) throws Exception + { + YstRequest request = assembleRequest(transCode, param.toString()); + log.info("request:{}", request); + String respStr = post(request.toString(), config.getHost() + api); + log.info("response:{}", respStr); + verify(respStr); + log.info("验签成功"); + YstResponse resp = JSON.parseObject(respStr, new TypeReference>() {}); + String bizDataStr = resp.getBizData() != null ? resp.getBizData().toString() : null; + if (StringUtils.isNotBlank(bizDataStr)) { + resp.setBizData(JSON.parseObject(bizDataStr, type)); + } + return resp; + } + + private void verify(String respStr) throws Exception + { + JSONObject map = JSON.parseObject(respStr); + String sign = (String) map.remove("sign"); + String signType = (String) map.remove("signType"); + String srcSignMsg = SM2Utils.jsonMapToStr(map); + if (!SM2Utils.verify(this.tlPublicKey, srcSignMsg, sign)) + { + throw new Exception("响应报文验签失败"); + } + } + + private YstRequest assembleRequest(String transCode, String param) throws Exception + { + YstRequest request = new YstRequest(); + request.setAppId(this.config.getAppId()); + request.setSpAppId(this.config.getSpAppId()); + request.setTransCode(transCode); + request.setFormat(this.config.getFormat()); + request.setCharset(this.config.getCharset()); + request.setTransDate(new SimpleDateFormat(YYYY_MM_DD).format(new Date())); + request.setTransTime(new SimpleDateFormat(HH_MM_SS).format(new Date())); + request.setVersion(this.config.getVersion()); + request.setBizData(param); + String signedValue = SM2Utils.jsonMapToStr((JSONObject) JSON.toJSON(request)); + log.info("待签名源串:{}", signedValue); + String sign = SM2Utils.sign(this.privateKey, signedValue); + request.setSignType(this.config.getSignType()); + request.setSign(sign); + return request; + } + + /** + * 发送请求 + * + * @param param + * @return + * @throws IOException + */ + private String post(String param, String url) throws IOException + { + if (url == null) { + throw new RuntimeException("url不允许为空"); + } + StringBuilder result = new StringBuilder(); + BufferedWriter writer = null; + BufferedReader reader = null; + + try + { + URL httpUrl = new URL(url); + HttpURLConnection connection = (HttpURLConnection) httpUrl.openConnection(); + if (connection instanceof HttpsURLConnection) + { + ((HttpsURLConnection) connection).setSSLSocketFactory(TxTrustManager.instance().getSSLSocketFactory()); + } + connection.setRequestProperty("Connection", "keep-alive"); + connection.setRequestProperty("Content-Type", "application/json;charset=utf-8"); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setDoInput(true); + writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8)); + writer.write(param); + writer.flush(); + // 响应 + reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)); + while (true) + { + String line = reader.readLine(); + if (line == null) + { + break; + } + result.append(line); + } + } + catch (Exception e2) + { + this.log.error(e2.getMessage(), e2); + } + finally + { + if (writer != null) + { + try + { + writer.close(); + } + catch (IOException e) + { + this.log.error(e.getMessage(), e); + } + } + if (reader != null) + { + reader.close(); + } + } + return result.toString(); + } + +} diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/device/mapper/DeviceMapper.xml b/smart-switch-service/src/main/java/com/ruoyi/ss/device/mapper/DeviceMapper.xml index 6431d399..d1b1b3a6 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/device/mapper/DeviceMapper.xml +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/device/mapper/DeviceMapper.xml @@ -380,7 +380,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" set store_id = null, user_id = null, device_name = '未命名', - activation_time = null + activation_time = null, + custom_picture = null where device_id = #{deviceId} diff --git a/smart-switch-web/src/main/resources/application-dev.yml b/smart-switch-web/src/main/resources/application-dev.yml index 21ae0d8e..9e02bd64 100644 --- a/smart-switch-web/src/main/resources/application-dev.yml +++ b/smart-switch-web/src/main/resources/application-dev.yml @@ -56,3 +56,26 @@ spring: max-active: 8 # #连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1ms + +# 云商通配置 +yst: + # appid + appId: 21762000921804636162 + # 云商通二代分配的服务商应用ID + spAppId: "" + # 域名 + host: http://116.228.64.55:28082 + # 格式化 + format: json + # 字符集 + charset: UTF-8 + # 签名方式 + signType: SM3withSM2 + # API版本 + version: 1.0 + # 秘钥 + secret-key: 878427523d3525e070298d44481b8d2e + # 私钥 + private-key: MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgiaZmB+feACtziE8SYjVZsaQwLNLRiyO8ebSupeoWIF2gCgYIKoEcz1UBgi2hRANCAATwEo0zq6KaB992PToWeJH52LmfS0sFovnB8/LMaoIAOTlFJtA3YgjWXKlO3KT+GqOCfCC4xE60isCr28tqy7hM + # 公钥 + public-key: MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEu9LNkJlyLtjJxtQWIGlcZ/hyHt5eZ7LEH1nfOiK1H9HsE1cMPu5KK5jZVTtAyc7lPMXixUMirf6A3tMbuMbgqg== diff --git a/smart-switch-web/src/main/resources/application-prod.yml b/smart-switch-web/src/main/resources/application-prod.yml index b246f1fe..5423cd77 100644 --- a/smart-switch-web/src/main/resources/application-prod.yml +++ b/smart-switch-web/src/main/resources/application-prod.yml @@ -56,3 +56,12 @@ spring: max-active: 8 # #连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1ms + +# 云商通配置 +yst: + # appid + appId: xx + # 云商通二代分配的服务商应用ID + spAppId: xx + # 域名 + host: https://ibsapi.allinpay.com