赞
踩
学习如何使用Java与通联支付API进行交互
实现一个简单的支付下单和查询订单状态的示例
官方文档https://aipboss.allinpay.com/know/devhelp/main.php?pid=38#mid=313
通联支付加签代码SybUtil
- package com.allinpay.common;
-
- import net.sf.json.JSONObject;
- import org.apache.tomcat.util.codec.binary.Base64;
-
- import java.security.*;
- import java.security.spec.PKCS8EncodedKeySpec;
- import java.security.spec.X509EncodedKeySpec;
- import java.util.Map;
- import java.util.Random;
- import java.util.TreeMap;
-
-
- @SuppressWarnings("all")
- public class SybUtil {
- /**
- * js转化为实体
- *
- * @param <T>
- * @param jsonstr
- * @param cls
- * @return
- */
- public static <T> T json2Obj(String jsonstr, Class<T> cls) {
- JSONObject jo = JSONObject.fromObject(jsonstr);
- T obj = (T) JSONObject.toBean(jo, cls);
- return obj;
- }
-
- /**
- * md5
- *
- * @param b
- * @return
- */
- public static String md5(byte[] b) {
- try {
- MessageDigest md = MessageDigest.getInstance("MD5");
- md.reset();
- md.update(b);
- byte[] hash = md.digest();
- StringBuffer outStrBuf = new StringBuffer(32);
- for (int i = 0; i < hash.length; i++) {
- int v = hash[i] & 0xFF;
- if (v < 16) {
- outStrBuf.append('0');
- }
- outStrBuf.append(Integer.toString(v, 16).toLowerCase());
- }
- return outStrBuf.toString();
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- return new String(b);
- }
- }
-
- /**
- * 判断字符串是否为空
- *
- * @param s
- * @return
- */
- public static boolean isEmpty(String s) {
- if (s == null || "".equals(s.trim()))
- return true;
- return false;
- }
-
- /**
- * 生成随机码
- *
- * @param n
- * @return
- */
- public static String getValidatecode(int n) {
- Random random = new Random();
- String sRand = "";
- n = n == 0 ? 4 : n;// default 4
- for (int i = 0; i < n; i++) {
- String rand = String.valueOf(random.nextInt(10));
- sRand += rand;
- }
- return sRand;
- }
-
-
-
- public static boolean validSign(TreeMap<String, String> param,
- String appkey, String signType) throws Exception {
- if (param != null && !param.isEmpty()) {
- if (!param.containsKey("sign"))
- return false;
- String sign = param.remove("sign");
- if ("MD5".equals(signType)) {// 如果是md5则需要把md5的key加入到排序
- param.put("key", appkey);
- }
- StringBuilder sb = new StringBuilder();
- for (Map.Entry<String, String> entry : param.entrySet()) {
- if (entry.getValue() != null && entry.getValue().length() > 0) {
- sb.append(entry.getKey()).append("=")
- .append(entry.getValue()).append("&");
- }
- }
- if (sb.length() > 0) {
- sb.deleteCharAt(sb.length() - 1);
- }
- if ("MD5".equals(signType)) {
- return sign.toLowerCase().equals(
- md5(sb.toString().getBytes("UTF-8")).toLowerCase());
- } else {
- return rsaVerifyPublickey(sb.toString(), sign, appkey, "UTF-8");
- }
- }
- return false;
- }
-
- public static boolean rsaVerifyPublickey(String content, String sign,
- String publicKey, String charset) throws Exception {
- try {
- PublicKey pubKey = getPublicKeyFromX509("RSA",
- Base64.decodeBase64(publicKey.getBytes()));
- return rsaVerifyPublickey(content, sign, pubKey, charset);
- } catch (Exception e) {
- e.printStackTrace();
- throw new Exception("RSAcontent = " + content + ",sign=" + sign
- + ",charset = " + charset, e);
- }
- }
-
- public static boolean rsaVerifyPublickey(String content, String sign,
- PublicKey pubKey, String charset) throws Exception {
- try {
- java.security.Signature signature = java.security.Signature
- .getInstance("SHA1WithRSA");
-
- signature.initVerify(pubKey);
-
- if (charset == null || "".equals(charset)) {
- signature.update(content.getBytes());
- } else {
- signature.update(content.getBytes(charset));
- }
-
- return signature.verify(Base64.decodeBase64(sign.getBytes()));
- } catch (Exception e) {
- throw e;
- }
- }
- public static String unionSign(TreeMap<String, String> params,String appkey,
- String signType) throws Exception {
- // TODO Auto-generated method stub
-
- params.remove("sign");
- if ("MD5".equals(signType)) {// 如果是md5则需要把md5的key加入到排序
- params.put("key", appkey);
- }
- StringBuilder sb = new StringBuilder();
- for (Map.Entry<String, String> entry : params.entrySet()) {
- if (entry.getValue() != null && entry.getValue().length() > 0) {
- sb.append(entry.getKey()).append("=").append(entry.getValue())
- .append("&");
- }
- }
- if (sb.length() > 0) {
- sb.deleteCharAt(sb.length() - 1);
- }
- String sign = "";
- if ("MD5".equals(signType)) {
- System.out.println(sb.toString());
- sign = md5(sb.toString().getBytes("UTF-8"));// 记得是md5编码的加签
- params.remove("key");
- } else {
- sign = rsaSign(sb.toString(), appkey, "UTF-8");
- }
- return sign;
- }
-
- public static String rsaSign(String content, String privateKey,
- String charset) throws Exception {
- PrivateKey priKey = getPrivateKeyFromPKCS8("RSA",
- Base64.decodeBase64(privateKey.getBytes()));
- return rsaSign(content, priKey, charset);
- }
-
- public static String rsaSign(String content, byte[] privateKey,
- String charset) throws Exception {
- PrivateKey priKey = getPrivateKeyFromPKCS8("RSA", privateKey);
- return rsaSign(content, priKey, charset);
- }
-
- public static String rsaSign(String content, PrivateKey priKey,
- String charset) throws Exception {
- java.security.Signature signature = java.security.Signature
- .getInstance("SHA1WithRSA");
- signature.initSign(priKey);
- if (charset == null || "".equals(charset)) {
- signature.update(content.getBytes());
- } else {
- signature.update(content.getBytes(charset));
- }
- byte[] signed = signature.sign();
-
- return new String(Base64.encodeBase64(signed));
- }
-
- public static PrivateKey getPrivateKeyFromPKCS8(String algorithm,
- byte[] encodedKey) throws Exception {
-
- KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
-
- return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
- }
-
- public static PublicKey getPublicKeyFromX509(String algorithm,
- byte[] encodedKey) throws Exception {
- KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
-
- return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
- }
- }
- <dependency>
- <groupId>com.alibaba.fastjson2</groupId>
- <artifactId>fastjson2</artifactId>
- <version>2.0.43</version>
- </dependency>
-
- <!-- SybUtil文件需要用到 -->
- <dependency>
- <groupId>net.sf.json-lib</groupId>
- <artifactId>json-lib</artifactId>
- <version>2.4</version>
- <classifier>jdk15</classifier>
- </dependency>
-
- <dependency>
- <groupId>cn.hutool</groupId>
- <artifactId>hutool-all</artifactId>
- <version>5.8.20</version>
- </dependency>
-
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- package com.allinpay.pojo;
-
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- import lombok.experimental.Accessors;
-
- import java.math.BigDecimal;
-
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- @Accessors(chain = true)
- public class Allinpay {
-
- private String cusid;
-
- private String appid;
-
- private int version;
-
- private BigDecimal trxamt;
-
- /*
- 当指定为F02时,交易仅限分期交易。
- 分期交易金额必须大于500元。
- */
- // ?
- private String paytype;
-
- /*
- 3 花呗分期3期
- 6 花呗分期6期
- 12 花呗分期12期
- 3-cc 支付宝信用卡分期3期
- 6-cc 支付宝信用卡分期6期
- 12-cc 支付宝信用卡分期12期
- 暂只支持支付宝花呗分期,支付宝信用卡分期,仅支持A01/A02
- */
- // 可空参数
- private String fqnum;
-
- //订单号,商户唯一订单号
- private String reqsn;
-
- /*
- 商户网站使用的编码格式,支持
- UTF-8、GBK
- 跟商户网站的编码一致
- */
- //
- private String charset;
-
- /*
- 必须为https协议地址,且不允许带参数
- 页面跳转同步通知页面路径
- */
- private String returl;
-
- // 异步通知地址
- // ?
- private String notify_url;
-
- // 商品描述,长度最大100
- private String body;
-
-
- //? 通知会原样带上, 订单备注信息
- private String remark;
-
- // 随机字符串,自己生成,最大32位
- private String randomstr;
-
- //订单有效时间,以分为单位,默认为15
- private String validtime;
-
- // ? 支付限制(no_credit--指定不能使用信用卡支付)
- private String limit_pay;
-
- // 签名类型,目前支持RSA2和RSA
- private String signtype;
-
- // 签名 32位
- private String sign;
-
- // 关闭订单的时候需要
- private String oldreqsn;
-
-
-
- }
2. 由于本案列采用了多商家,所以暂时把配置也建成了一个类(PayConfig.java)
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- import lombok.experimental.Accessors;
-
- @AllArgsConstructor
- @NoArgsConstructor
- @Data
- @Accessors(chain = true)
- public class PayConfig {
-
- private String cusid;
-
- private String appid;
-
- private String privateKey;
-
- }
3.本案列设置了多方支付方式,所以还有一个支付载荷(PayLoad.java)
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- import lombok.experimental.Accessors;
-
- import java.math.BigDecimal;
-
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- @Accessors(chain = true)
- public class PayLoad {
-
- private String orderId;
-
- private BigDecimal amount;
-
- private String payConfigKey;
-
- private String remark;
-
- private String title;
-
- private int payType;
-
- private String returnUrl;
-
- private String notifyUrl;
-
-
- }
- //这个是PayService的Impl实现层
- private final String order = "https://syb.allinpay.com/apiweb/h5unionpay/unionorder";
- private final String rsaPrivateKey = "xxx";
- @SneakyThrows
- @Override
- public String h5Pay(PayLoad payLoad) {
- // String payConfigKey = payLoad.getPayConfigKey();
- PayConfig payConfig = new PayConfig()
- .setAppid("xxx")
- .setCusid("xxx")
- .setPrivateKey(rsaPrivateKey);
- Allinpay allinpay = new Allinpay()
- .setSigntype("RSA")
- .setTrxamt(new BigDecimal(2))
- .setReqsn(payLoad.getOrderId())
- .setRandomstr(SybUtil.getValidatecode(8))
- .setBody(payLoad.getTitle())
- .setRemark(payLoad.getRemark())
- .setCharset("UTF-8")
- .setAppid(payConfig.getAppid())
- .setCusid(payConfig.getCusid())
- .setReturl(payLoad.getReturnUrl());
- allinpay.setSign(URLEncoder.encode(SybUtil.unionSign(objectToTreeMap(allinpay), payConfig.getPrivateKey() , "RSA"), StandardCharsets.UTF_8));
- return order + "?" + treeMapToUrlParams(objectToTreeMap(allinpay));
- }
- //Controller代码
- @SneakyThrows
- @GetMapping("pay")
- public String pay(HttpServletResponse response) {
- String paymentUrl = (String) payService.h5Pay(new PayLoad()
- .setPayType(2)
- .setPayConfigKey("")
- .setReturnUrl("https://blog.csdn.net")
- .setOrderId(IdUtil.getSnowflakeNextIdStr())
- .setAmount(new BigDecimal(2))
- .setRemark("测试支付备注")
- .setTitle("测试H5支付")
- );
- String htmlResponse = "<!DOCTYPE html><html><head></head><body>"
- + "<script>window.location.href='" + paymentUrl + "'</script>"
- + "</body></html>";
-
- // 设置响应内容类型为HTML
- response.setContentType("text/html;charset=UTF-8");
- return htmlResponse;
- }
-
- // 关闭接口
- private final String close = "https://vsp.allinpay.com/apiweb/tranx/close";
-
- @SneakyThrows
- @Override
- public String closeOrder(PayLoad payLoad) {
- PayConfig payConfig = new PayConfig()
- .setAppid("xxx")
- .setCusid("xxxx")
- .setPrivateKey(rsaPrivateKey);
- Allinpay allinpay = new Allinpay()
- .setCusid(payConfig.getCusid())
- .setAppid(payConfig.getAppid())
- .setRandomstr(SybUtil.getValidatecode(8))
- .setVersion(11)
- .setOldreqsn(payLoad.getOrderId())
- .setSigntype("RSA");
- allinpay.setSign(SybUtil.unionSign(objectToTreeMap(allinpay), rsaPrivateKey, "RSA"));
- return HttpUtil.post(close, BeanUtil.beanToMap(allinpay));
- }
-
- /**
- * 关闭订单
- * @param outTradeNo 下单的订单号,也就是你支付下单类Allinpay的reqsn
- * @return {@link String}
- ***/
- @SneakyThrows
- @GetMapping("closeOrder/{outTradeNo}")
- public Map<String,String> closeOrder(@PathVariable Long outTradeNo) {
- return handleResult(payService.closeOrder(new PayLoad().setOrderId(outTradeNo.toString())));
- }
- // 查询接口
- private final String query = "https://vsp.allinpay.com/apiweb/tranx/query";
-
- @SneakyThrows
- @Override
- public String query(PayLoad payLoad) {
- PayConfig payConfig = new PayConfig()
- .setAppid("xxx")
- .setCusid("xxxx")
- .setPrivateKey(rsaPrivateKey);
- Allinpay allinpay = new Allinpay()
- .setCusid(payConfig.getCusid())
- .setAppid(payConfig.getAppid())
- .setRandomstr(SybUtil.getValidatecode(8))
- .setVersion(11)
- .setReqsn(payLoad.getOrderId())
- .setSigntype("RSA");
- allinpay.setSign(SybUtil.unionSign(objectToTreeMap(allinpay), rsaPrivateKey, "RSA"));
- return HttpUtil.post(query, BeanUtil.beanToMap(allinpay));
- }
-
-
- /**
- * 查询订单
- * @param outTradeNo 下单的订单号
- * @return {@link String}
- ***/
- @SneakyThrows
- @GetMapping("query/{outTradeNo}")
- public Map<String, String> query(@PathVariable Long outTradeNo) {
- return handleResult(payService.query(new PayLoad().setOrderId(outTradeNo.toString())));
- }
- // 取消当天交易退款接口
- private final String cancelDayUrl = "https://vsp.allinpay.com/apiweb/tranx/cancel";
-
- @SneakyThrows
- @Override
- public String cancelDay(PayLoad payLoad) {
- PayConfig payConfig = new PayConfig()
- .setAppid("xxx")
- .setCusid("xxxx")
- .setPrivateKey(rsaPrivateKey);
- Allinpay allinpay = new Allinpay()
- .setCusid(payConfig.getCusid())
- .setAppid(payConfig.getAppid())
- .setRandomstr(SybUtil.getValidatecode(8))
- .setVersion(11)
- .setTrxamt(new BigDecimal(2))
- .setReqsn(payLoad.getOrderId())
- .setOldreqsn(payLoad.getOrderId())
- .setSigntype("RSA");
- allinpay.setSign(SybUtil.unionSign(objectToTreeMap(allinpay), rsaPrivateKey, "RSA"));
- return HttpUtil.post(cancelDayUrl, BeanUtil.beanToMap(allinpay));
- }
-
-
- /**
- * 退款接口,只能退今天的,全额退款,实时返回退款结果
- * @return {@link String}
- */
- @SneakyThrows
- @GetMapping("cancelDay/{outTradeNo}")
- public Map<String, String> cancelDay(@PathVariable Long outTradeNo) {
- return handleResult(payService.cancelDay(new PayLoad().setOrderId(outTradeNo.toString())));
- }
-
- // 退款接口
- private final String refundUrl = "https://vsp.allinpay.com/apiweb/tranx/refund";
-
- @SneakyThrows
- @Override
- public String refund(PayLoad payLoad) {
- PayConfig payConfig = new PayConfig()
- .setAppid("xxx")
- .setCusid("xxxx")
- .setPrivateKey(rsaPrivateKey);
- Allinpay allinpay = new Allinpay()
- .setCusid(payConfig.getCusid())
- .setAppid(payConfig.getAppid())
- .setRandomstr(SybUtil.getValidatecode(8))
- .setVersion(11)
- .setTrxamt(new BigDecimal(2))
- .setReqsn(payLoad.getOrderId())
- .setOldreqsn(payLoad.getOrderId())
- .setSigntype("RSA");
- allinpay.setSign(SybUtil.unionSign(objectToTreeMap(allinpay), rsaPrivateKey, "RSA"));
- return HttpUtil.post(refundUrl, BeanUtil.beanToMap(allinpay));
- }
-
- // 可以和上面的接口进行整合处理
-
- /**
- * 退款接口,可以退部分
- * @return {@link String}
- */
- @SneakyThrows
- @GetMapping("refund/{outTradeNo}")
- public Map<String, String> refund(@PathVariable Long outTradeNo) {
- return handleResult(payService.refund(new PayLoad().setOrderId(outTradeNo.toString())));
- }
提示: 可以和上面的接口进行整合处理
- private TreeMap<String, String> objectToTreeMap(Object obj) {
- TreeMap<String, String> treeMap = new TreeMap<>();
- Class<?> clazz = obj.getClass();
- while (clazz != null) {
- for (Field field : clazz.getDeclaredFields()) {
- field.setAccessible(true);
- try {
- Object fieldValue = field.get(obj);
- if (fieldValue != null) {
- treeMap.put(field.getName(), fieldValue.toString());
- }
- } catch (IllegalAccessException e) {
- log.error("Error accessing field " + field.getName() + ": " + e.getMessage());
- }
- }
- clazz = clazz.getSuperclass();
- }
- return treeMap;
- }
- private static String treeMapToUrlParams(TreeMap<String, String> treeMap) {
- StringBuilder sb = new StringBuilder();
- for (Map.Entry<String, String> entry : treeMap.entrySet()) {
- String key = entry.getKey();
- String value = entry.getValue();
- if (sb.length() > 0) {
- sb.append("&");
- }
- sb.append(key).append("=").append(value);
- }
- return sb.toString();
- }
- /**
- * 验签
- * @param result
- * @return {@link Map}<{@link String},{@link String}>
- * @throws Exception
- */
- @SuppressWarnings({ "rawtypes", "all" })
- public static Map<String,String> handleResult(String result) throws Exception{
- Map map = SybUtil.json2Obj(result, Map.class);
- if(map == null){
- throw new Exception("返回数据错误");
- }
- if("SUCCESS".equals(map.get("retcode"))) {
- TreeMap tmap = new TreeMap();
- tmap.putAll(map);
- if(SybUtil.validSign(
- tmap,
- "xxxxx",
- "RSA")
- ){
- return map;
- }else{
- throw new Exception("验证签名失败");
- }
- // 验签成功,返回数据
- }else{
- throw new Exception(map.get("retmsg").toString());
- }
- }
提示:如果不考虑多商户,多支付通道,可以再方法中Allinpay类直接填参数请求,不用通过PayLoad和PayConfig类
git clone https://gitee.com/byte1026/allin-pay.git
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。