package common.utils.tl; import cn.hutool.core.util.RandomUtil; import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.asymmetric.SM2; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.jfinal.kit.HttpKit; import com.jfinal.kit.StrKit; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.Base64; /** * 通联支付SDK * 支持H5收银台支付功能 * * @author skyff * @date 2024 */ public class AllinpaySDK { // 生产环境配置 private static final String BASE_URL = "https://vsp.allinpay.com"; private static final String UNIFIED_PAY_URL = BASE_URL + "/apiweb/unitorder/pay"; private static final String QUERY_URL = "https://syb.allinpay.com/apiweb/unitorder/query"; // 商户配置信息(测试用静态配置) private static final String APP_ID = "00000051"; private static final String CUS_ID = "990581007426001"; private static final String VERSION = "11"; private static final String SIGN_TYPE = "SM2"; // SM2密钥对(测试用) private static final String SM2_PRIVATE_KEY = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgNqz1EieIP8QVzV7vEmx5e8f7XN7/MIzoeXgEinxcG0agCgYIKoEcz1UBgi2hRANCAAQNfkEgaCQ4cdZ4aD2LWMcnkk5LALQfL05oY8x8XQDIyUM44N15YcTwtFNvHYgyeNRa93vlEUutp935n6rp4yuf"; // 通联公钥(用于验签,实际使用时需要从通联获取) private static final String ALLINPAY_PUBLIC_KEY = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEDX5BIGgkOHHWeGg9i1jHJ5JOSwC0Hy9OaGPMfF0AyMlDOODdeWHE8LRTbx2IMnjUWvd75RFLrae9+Z+q6eMrnw=="; private static final SM2 sm2 = SmUtil.sm2(SM2_PRIVATE_KEY, ALLINPAY_PUBLIC_KEY); /** * 统一支付接口 * * @param trxamt 交易金额(分) * @param reqsn 商户交易单号 * @param paytype 交易方式 * @param body 订单标题 * @param remark 备注 * @param acct 支付平台用户标识 * @param notify_url 交易结果通知地址 * @param limit_pay 支付限制 * @param idno 证件号 * @param truename 付款人真实姓名 * @param sub_appid 微信子appid * @return 支付结果 */ public static PaymentResult unifiedPay(Long trxamt, String reqsn, String paytype, String body, String remark, String acct, String notify_url, String limit_pay, String idno, String truename, String sub_appid) { try { // 1. 构建请求参数 Map params = new TreeMap<>(); params.put("appid", APP_ID); params.put("cusid", CUS_ID); params.put("version", VERSION); params.put("signtype", SIGN_TYPE); params.put("randomstr", RandomUtil.randomString(8)); params.put("trxamt", String.valueOf(trxamt)); params.put("reqsn", reqsn); params.put("paytype", paytype); params.put("body", body); params.put("remark", remark); params.put("notify_url", notify_url); // 添加可选参数 if (StrKit.notBlank(acct)) params.put("acct", acct); if (StrKit.notBlank(limit_pay)) params.put("limit_pay", limit_pay); if (StrKit.notBlank(idno)) params.put("idno", idno); if (StrKit.notBlank(truename)) params.put("truename", truename); if (StrKit.notBlank(sub_appid)) params.put("sub_appid", sub_appid); // 2. 生成签名 String sign = generateSign(params); params.put("sign", sign); // 3. 发送请求 String response = sendPostRequest(UNIFIED_PAY_URL, params); // 4. 解析响应 return parsePaymentResponse(response); } catch (Exception e) { e.printStackTrace(); return PaymentResult.error("支付请求失败: " + e.getMessage()); } } /** * 查询支付结果 * * @param reqsn 商户交易单号 * @return 查询结果 */ public static QueryResult queryPayment(String reqsn) { try { // 1. 构建请求参数 Map params = new TreeMap<>(); params.put("appid", APP_ID); params.put("cusid", CUS_ID); params.put("version", VERSION); params.put("signtype", SIGN_TYPE); params.put("randomstr", RandomUtil.randomString(8)); params.put("reqsn", reqsn); // 2. 生成签名 String sign = generateSign(params); params.put("sign", sign); // 3. 发送请求 String response = sendPostRequest(QUERY_URL, params); // 4. 解析响应 return parseQueryResponse(response); } catch (Exception e) { e.printStackTrace(); return QueryResult.error("查询失败: " + e.getMessage()); } } /** * 处理异步通知 * * @param notifyParams 通知参数 * @return 处理结果 */ public static NotifyResult handleNotify(Map notifyParams) { try { System.out.println("收到异步通知: " + notifyParams); // 1. 验证签名 String sign = notifyParams.get("sign"); if (sign == null || sign.trim().isEmpty()) { return NotifyResult.error("签名为空"); } // 构建验签参数 Map verifyParams = new TreeMap<>(); for (Map.Entry entry : notifyParams.entrySet()) { if (!"sign".equals(entry.getKey()) && entry.getValue() != null) { verifyParams.put(entry.getKey(), entry.getValue()); } } String signString = buildSignString(verifyParams); System.out.println("通知验签字符串: " + signString); // 使用通联公钥验签 boolean verifyResult = sm2.verify(signString.getBytes(StandardCharsets.UTF_8), sign.getBytes(StandardCharsets.UTF_8), APP_ID.getBytes(StandardCharsets.UTF_8)); if (!verifyResult) { return NotifyResult.error("签名验证失败"); } // 2. 解析通知内容 String reqsn = notifyParams.get("reqsn"); String trxstatus = notifyParams.get("trxstatus"); String trxid = notifyParams.get("trxid"); String trxamt = notifyParams.get("trxamt"); return NotifyResult.success(reqsn, trxstatus, trxid, trxamt, "通知处理成功"); } catch (Exception e) { e.printStackTrace(); return NotifyResult.error("通知处理失败: " + e.getMessage()); } } /** * 生成SM2签名 */ private static String generateSign(Map params) throws Exception { // 1. 排序并拼接参数(排除sign字段) String signString = buildSignString(params); System.out.println("签名字符串: " + signString); // 2. 使用SM2私钥签名 byte[] signBytes = sm2.sign(signString.getBytes(StandardCharsets.UTF_8), APP_ID.getBytes(StandardCharsets.UTF_8)); String sign = Base64.getEncoder().encodeToString(signBytes); System.out.println("生成的签名: " + sign); return sign; } /** * 构建签名字符串 */ private static String buildSignString(Map params) { StringBuilder sb = new StringBuilder(); boolean first = true; for (Map.Entry entry : params.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); // 排除sign字段和空值 if (!"sign".equals(key) && value != null && !value.trim().isEmpty()) { if (!first) { sb.append("&"); } sb.append(key).append("=").append(value); first = false; } } return sb.toString(); } /** * 发送POST请求 */ private static String sendPostRequest(String url, Map params) throws Exception { // 构建表单参数 StringBuilder formData = new StringBuilder(); boolean first = true; for (Map.Entry entry : params.entrySet()) { if (!first) { formData.append("&"); } formData.append(entry.getKey()).append("=").append(entry.getValue()); first = false; } // 设置请求头 Map headers = new HashMap<>(); headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); // 使用JFinal的HttpKit发送请求 String response = HttpKit.post(url, null, formData.toString(), headers); if (StrKit.isBlank(response)) { throw new RuntimeException("API响应为空"); } return response; } /** * 解析支付响应 */ private static PaymentResult parsePaymentResponse(String response) { try { System.out.println("支付响应: " + response); JSONObject jsonResponse = JSONUtil.parseObj(response); // 验证响应签名 if (!verifyResponseSign(jsonResponse)) { return PaymentResult.error("响应签名验证失败"); } String retcode = jsonResponse.getStr("retcode"); String retmsg = jsonResponse.getStr("retmsg"); if ("SUCCESS".equals(retcode)) { String payUrl = jsonResponse.getStr("payinfo"); return PaymentResult.success(payUrl, "支付链接生成成功"); } else { return PaymentResult.error("支付失败: " + retmsg); } } catch (Exception e) { e.printStackTrace(); return PaymentResult.error("响应解析失败: " + e.getMessage()); } } /** * 解析查询响应 */ private static QueryResult parseQueryResponse(String response) { try { System.out.println("查询响应: " + response); JSONObject jsonResponse = JSONUtil.parseObj(response); // 验证响应签名 if (!verifyResponseSign(jsonResponse)) { return QueryResult.error("响应签名验证失败"); } String retcode = jsonResponse.getStr("retcode"); String retmsg = jsonResponse.getStr("retmsg"); if ("SUCCESS".equals(retcode)) { String trxstatus = jsonResponse.getStr("trxstatus"); String trxid = jsonResponse.getStr("trxid"); String trxamt = jsonResponse.getStr("trxamt"); return QueryResult.success(trxstatus, trxid, trxamt, "查询成功"); } else { return QueryResult.error("查询失败: " + retmsg); } } catch (Exception e) { e.printStackTrace(); return QueryResult.error("响应解析失败: " + e.getMessage()); } } /** * 验证响应签名 */ private static boolean verifyResponseSign(JSONObject response) { try { String sign = response.getStr("sign"); if (sign == null || sign.trim().isEmpty()) { return false; } // 构建验签字符串 Map params = new TreeMap<>(); for (String key : response.keySet()) { if (!"sign".equals(key)) { Object value = response.get(key); if (value != null) { params.put(key, value.toString()); } } } String signString = buildSignString(params); System.out.println("验签字符串: " + signString); // 使用通联公钥验签 boolean result = sm2.verify(signString.getBytes(StandardCharsets.UTF_8), Base64.getDecoder().decode(sign), APP_ID.getBytes(StandardCharsets.UTF_8)); System.out.println("验签结果: " + result); return result; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 支付结果类 */ public static class PaymentResult { private boolean success; private String payUrl; private String message; private String errorCode; private PaymentResult(boolean success, String payUrl, String message, String errorCode) { this.success = success; this.payUrl = payUrl; this.message = message; this.errorCode = errorCode; } public static PaymentResult success(String payUrl, String message) { return new PaymentResult(true, payUrl, message, null); } public static PaymentResult error(String message) { return new PaymentResult(false, null, message, "ERROR"); } // Getters public boolean isSuccess() { return success; } public String getPayUrl() { return payUrl; } public String getMessage() { return message; } public String getErrorCode() { return errorCode; } @Override public String toString() { return "PaymentResult{" + "success=" + success + ", payUrl='" + payUrl + '\'' + ", message='" + message + '\'' + ", errorCode='" + errorCode + '\'' + '}'; } } /** * 查询结果类 */ public static class QueryResult { private boolean success; private String trxstatus; private String trxid; private String trxamt; private String message; private String errorCode; private QueryResult(boolean success, String trxstatus, String trxid, String trxamt, String message, String errorCode) { this.success = success; this.trxstatus = trxstatus; this.trxid = trxid; this.trxamt = trxamt; this.message = message; this.errorCode = errorCode; } public static QueryResult success(String trxstatus, String trxid, String trxamt, String message) { return new QueryResult(true, trxstatus, trxid, trxamt, message, null); } public static QueryResult error(String message) { return new QueryResult(false, null, null, null, message, "ERROR"); } // Getters public boolean isSuccess() { return success; } public String getTrxstatus() { return trxstatus; } public String getTrxid() { return trxid; } public String getTrxamt() { return trxamt; } public String getMessage() { return message; } public String getErrorCode() { return errorCode; } @Override public String toString() { return "QueryResult{" + "success=" + success + ", trxstatus='" + trxstatus + '\'' + ", trxid='" + trxid + '\'' + ", trxamt='" + trxamt + '\'' + ", message='" + message + '\'' + ", errorCode='" + errorCode + '\'' + '}'; } } /** * 通知结果类 */ public static class NotifyResult { private boolean success; private String reqsn; private String trxstatus; private String trxid; private String trxamt; private String message; private String errorCode; private NotifyResult(boolean success, String reqsn, String trxstatus, String trxid, String trxamt, String message, String errorCode) { this.success = success; this.reqsn = reqsn; this.trxstatus = trxstatus; this.trxid = trxid; this.trxamt = trxamt; this.message = message; this.errorCode = errorCode; } public static NotifyResult success(String reqsn, String trxstatus, String trxid, String trxamt, String message) { return new NotifyResult(true, reqsn, trxstatus, trxid, trxamt, message, null); } public static NotifyResult error(String message) { return new NotifyResult(false, null, null, null, null, message, "ERROR"); } // Getters public boolean isSuccess() { return success; } public String getReqsn() { return reqsn; } public String getTrxstatus() { return trxstatus; } public String getTrxid() { return trxid; } public String getTrxamt() { return trxamt; } public String getMessage() { return message; } public String getErrorCode() { return errorCode; } @Override public String toString() { return "NotifyResult{" + "success=" + success + ", reqsn='" + reqsn + '\'' + ", trxstatus='" + trxstatus + '\'' + ", trxid='" + trxid + '\'' + ", trxamt='" + trxamt + '\'' + ", message='" + message + '\'' + ", errorCode='" + errorCode + '\'' + '}'; } } /** * 测试主方法 */ public static void main(String[] args) { System.out.println("=== 通联支付SDK测试 ==="); // 测试参数 Long trxamt = 100L; // 1元(分为单位) String reqsn = "TEST" + System.currentTimeMillis(); // 商户订单号 String body = "测试商品"; String remark = "SDK测试订单"; String notify_url = "https://your-domain.com/api/payment/callback"; System.out.println("测试订单号: " + reqsn); System.out.println("交易金额: " + trxamt + "分"); // 测试1: 统一支付 System.out.println("\n=== 测试统一支付 ==="); PaymentResult payResult = unifiedPay(trxamt, reqsn, "W02", body, remark, "o9o43wz9sV5x8P-MA2b02mtYw4Co", notify_url, null, null, null, null); System.out.println("支付结果: " + payResult); if (payResult.isSuccess()) { System.out.println("支付链接: " + payResult.getPayUrl()); System.out.println("请在浏览器中打开上述链接完成支付"); // 测试2: 查询支付结果 System.out.println("\n=== 测试查询支付结果 ==="); QueryResult queryResult = queryPayment(reqsn); System.out.println("查询结果: " + queryResult); } else { System.out.println("支付失败: " + payResult.getMessage()); } // 测试3: 模拟异步通知处理 System.out.println("\n=== 测试异步通知处理 ==="); Map notifyParams = new HashMap<>(); notifyParams.put("reqsn", reqsn); notifyParams.put("trxstatus", "0000"); notifyParams.put("trxid", "TL" + System.currentTimeMillis()); notifyParams.put("trxamt", String.valueOf(trxamt)); notifyParams.put("sign", "test_sign"); // 实际使用时这里应该是真实的签名 NotifyResult notifyResult = handleNotify(notifyParams); System.out.println("通知处理结果: " + notifyResult); System.out.println("\n=== 测试完成 ==="); } }