首页 分享 通联支付API集成(适用于SpringBoot)

通联支付API集成(适用于SpringBoot)

来源:花匠小妙招 时间:2024-12-03 18:20

目标:

学习如何使用Java与通联支付API进行交互

实现一个简单的支付下单和查询订单状态的示例

所需材料:

通联支付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 {

public static <T> T json2Obj(String jsonstr, Class<T> cls) {

JSONObject jo = JSONObject.fromObject(jsonstr);

T obj = (T) JSONObject.toBean(jo, cls);

return obj;

}

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);

}

}

public static boolean isEmpty(String s) {

if (s == null || "".equals(s.trim()))

return true;

return false;

}

public static String getValidatecode(int n) {

Random random = new Random();

String sRand = "";

n = n == 0 ? 4 : n;

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)) {

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 {

params.remove("sign");

if ("MD5".equals(signType)) {

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"));

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));

}

}

IDE(如IntelliJ IDEA或Eclipse)

JDK 8 或更高版本

需要的maven

<dependency>

<groupId>com.alibaba.fastjson2</groupId>

<artifactId>fastjson2</artifactId>

<version>2.0.43</version>

</dependency>

<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>

 辅助类 通联支付需要的请求数据格式(Allinpay.java)

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;

private String paytype;

private String fqnum;

private String reqsn;

private String charset;

private String returl;

private String notify_url;

private String body;

private String remark;

private String randomstr;

private String validtime;

private String limit_pay;

private String signtype;

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;

}

 H5支付下单代码

private final String order = "https://syb.allinpay.com/apiweb/h5unionpay/unionorder";

private final String rsaPrivateKey = "xxx";

@SneakyThrows

@Override

public String h5Pay(PayLoad payLoad) {

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));

}

@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>";

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));

}

@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));

}

@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));

}

@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));

}

@SneakyThrows

@GetMapping("refund/{outTradeNo}")

public Map<String, String> refund(@PathVariable Long outTradeNo) {

return handleResult(payService.refund(new PayLoad().setOrderId(outTradeNo.toString())));

}

提示: 可以和上面的接口进行整合处理

其中签名需要用到的转map方法 

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;

}

 其中map转路径参数方法

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();

}

其中通联返回验签代码

@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

相关知识

中航军工:关于通联支付系统升级的通知
最佳免费花品种识别API推荐与使用指南
基于springboot实现的对人社区交流平台(计算机毕设交流案例)
web支付基础教程
花瓣支付(深圳)有限公司官网
【毕业设计】基于SpringBoot + Vue的摄影跟拍预定系统
【SpringBoot项目
Springboot花店管理系统i6v19
小米花花草草传感器API:让你的植物监测更智能
【OpenAI】第一节(OpenAI API)获取OpenAI API KEY的两种方式,开发者必看全方面教程!

网址: 通联支付API集成(适用于SpringBoot) https://www.huajiangbk.com/newsview849407.html

所属分类:花卉
上一篇: java中如何实现支付宝接口
下一篇: 金融支付系统中的支付渠道与接口集

推荐分享