| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541 |
- 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 VERSION = "12";
- private static final String SIGN_TYPE = "SM2";
- private static final String APP_ID = "00375513";
- private static final String CUS_ID = "660222053994DLK";
-
- // SM2密钥对
- private static final String SM2_PRIVATE_KEY = System.getenv("TL_SM2_PRIVATE_KEY");
-
- // 通联公钥(用于验签,实际使用时需要从通联获取)
- private static final String ALLINPAY_PUBLIC_KEY = System.getenv("TL_SM2_PUBLIC_KEY");
- // 生产环境配置
- private static final String BASE_URL = "https://syb.allinpay.com";
- private static final String H5_PAY_URL = BASE_URL + "/apiweb/h5unionpay/unionorder";
- private static final String QUERY_URL = BASE_URL + "/apiweb/unitorder/query";
-
- private static final SM2 sm2 = SmUtil.sm2(SM2_PRIVATE_KEY, ALLINPAY_PUBLIC_KEY);
-
- /**
- * H5收银台支付
- *
- * @param trxamt 交易金额(分)
- * @param reqsn 商户订单号
- * @param body 订单标题
- * @param remark 订单备注信息
- * @param notify_url 服务器异步通知页面路径
- * @param returl 页面跳转同步通知页面路径
- * @param charset 参数字符编码集
- * @param validtime 有效时间(分钟)
- * @param limit_pay 支付限制
- * @param truename 付款人真实姓名
- * @param idno 证件号
- * @return 支付结果
- */
- public static PaymentResult h5Pay(Long trxamt, String reqsn, String body, String remark,
- String notify_url, String returl, String charset,
- Integer validtime, String limit_pay, String truename, String idno) {
- try {
- // 1. 构建请求参数
- Map<String, String> params = new TreeMap<>();
- params.put("cusid", CUS_ID);
- params.put("appid", APP_ID);
- params.put("version", VERSION);
- params.put("trxamt", String.valueOf(trxamt));
- params.put("signtype", SIGN_TYPE);
- params.put("randomstr", RandomUtil.randomString(8));
- params.put("reqsn", reqsn);
- params.put("body", body);
- params.put("remark", remark);
- params.put("returl", returl);
- // 添加可选参数
- if (StrKit.notBlank(returl)) params.put("returl", returl);
- if (StrKit.notBlank(charset)) params.put("charset", charset);
- if (validtime != null) params.put("validtime", String.valueOf(validtime));
- if (StrKit.notBlank(limit_pay)) params.put("limit_pay", limit_pay);
- if (StrKit.notBlank(truename)) params.put("truename", truename);
- if (StrKit.notBlank(idno)) params.put("idno", idno);
- // 2. 生成签名
- String sign = generateSign(params);
- params.put("sign", sign);
- // 3. 发送请求
- String response = sendPostRequest(H5_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<String, String> 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<String, String> notifyParams) {
- try {
- System.out.println("收到异步通知: " + notifyParams);
-
- // 1. 验证签名
- String sign = notifyParams.get("sign");
- if (sign == null || sign.trim().isEmpty()) {
- return NotifyResult.error("签名为空");
- }
-
- // 构建验签参数
- Map<String, String> verifyParams = new TreeMap<>();
- for (Map.Entry<String, String> 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<String, String> 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<String, String> params) {
- StringBuilder sb = new StringBuilder();
- boolean first = true;
-
- for (Map.Entry<String, String> 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<String, String> params) throws Exception {
- // 构建表单参数
- StringBuilder formData = new StringBuilder();
- boolean first = true;
- for (Map.Entry<String, String> entry : params.entrySet()) {
- if (!first) {
- formData.append("&");
- }
- formData.append(entry.getKey()).append("=").append(entry.getValue());
- first = false;
- }
-
- // 设置请求头
- Map<String, String> headers = new HashMap<>();
- headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
- String finalUrl = url + "?" + formData;
-
- // 使用JFinal的HttpKit发送请求
- String response = HttpKit.post(finalUrl, null, null, 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<String, String> 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 = "book";
- String remark = "SDKbook";
- String notify_url = "https://dlsh-nft.com.cn/api/hello/";
-
- System.out.println("测试订单号: " + reqsn);
- System.out.println("交易金额: " + trxamt + "分");
-
- // 测试1: H5收银台支付
- System.out.println("\n=== 测试H5收银台支付 ===");
- PaymentResult payResult = h5Pay(trxamt, reqsn, body, remark, notify_url,
- "https://dlsh-nft.com.cn", "UTF-8",
- 30, 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<String, String> 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=== 测试完成 ===");
- }
- }
|