虽然之前对于接入第三方支付有比较多的经验,但是这次接入环迅h5支付还是花了2天半的时间才整入到项目中,遇到了一个坑耽搁了一天半,下面讲一下接入注意事项
demo启动效果访问地址:http://localhost:8080/accentry/
h5发起支付后会返回html代码,该代码可以在浏览器直接加载---------手机号登录------------------添加银行卡------------开通快捷支付-----------再次发送验证码进行付款-------付款成功后环迅服务器会同步回调以及异步回调
这会直接上接入代码了:发起h5网关支付类
AccEntryConfig/CollBackResponse/FunctionUtils/MerVerify/Request/SSLClient/SynchronousResponse/Utils这些类在demo中直接复制过来
package com.api.ips;
import java.net.URLEncoder;
import java.util.LinkedHashMap;
import org.junit.Test;
import com.alibaba.fastjson.JSONObject;
/**
* 环迅支付工具
*
* @author leehao
* @date 2019-09-25
*/
public class IpsPayUtils {
/***
* 环迅h5支付
*
* @param commodity 商品
* @param describe 描述
* @param payCode 订单号
* @param orderPrice 金额(以元为单位)
* @return
*/
public static String hxPay(String commodity, String describe, String payCode, String orderPrice) {
String ts = AccEntryConfig.date;// AccEntryConfig.date;//时间戳
String nonceStr = Utils.getRandomString(32);// 随机字符串
String trxDtTm = AccEntryConfig.date;// 商户日期
// 1.加密data
JSONObject dt = new JSONObject(new LinkedHashMap());
dt.put("platCode", "");// 平台编码默认1001
dt.put("account", AccEntryConfig.accCode);// 账户号
dt.put("trxId", payCode);// 商户订单号
dt.put("trxDtTm", ts);// 订单日期
dt.put("expireDtTm", "");// 订单截止时间
dt.put("trxCcyCd", "156");// 币种枚举,默认 156
dt.put("trxAmt", orderPrice);// 单位元
dt.put("productType", "");// 产品类型
dt.put("limitPayType", "");// 限制支付方式
dt.put("lang", AccEntryConfig.lang);// 默认为zh
dt.put("pageUrl", AccEntryConfig.successURL);// 页面回调地址
dt.put("notifyUrl", AccEntryConfig.s2sURL);// 后台回调地址
dt.put("attach", commodity);// 商品介绍
dt.put("goodsDesc", describe);// 商品内容
String ds = dt.toString();
// 1
// System.out.println("加密前data: " + ds);
String signData = MerVerify.encryptAES(AccEntryConfig.AES_KEY, ds);
// System.out.println("加密后signData: " + signData);
// 2
Request accEntryRequest = new Request(AccEntryConfig.version, AccEntryConfig.date, AccEntryConfig.merCode,
nonceStr, AccEntryConfig.format, AccEntryConfig.encryptType, signData, AccEntryConfig.signType);
String object = Utils.getParamStr(Utils.objectToMap(accEntryRequest));
// System.out.println("加密前object: " + object);
String signObject = MerVerify.signSHA256withRSA(AccEntryConfig.SHA256_PRIVATE_KEY, object);
// System.out.println("加密后signObject: " + signObject);
JSONObject retObj = new JSONObject();
retObj.put("version", AccEntryConfig.version);
retObj.put("ts", ts);
retObj.put("merCode", AccEntryConfig.merCode);
retObj.put("nonceStr", nonceStr);
retObj.put("format", AccEntryConfig.format);
retObj.put("encryptType", AccEntryConfig.encryptType);
retObj.put("signType", AccEntryConfig.signType);
retObj.put("account", AccEntryConfig.accCode);
retObj.put("trxId", payCode);
retObj.put("trxDtTm", trxDtTm);
retObj.put("trxAmt", orderPrice);
retObj.put("data", signData);
retObj.put("sign", signObject);
String response = null;
try {
response = Utils.postSubmit(AccEntryConfig.ipsURL, retObj);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "10001";
}
return response;
}
这里是发起请求接口以及支付回调(同步-异步 验签解析data中的数据)
确认交易成功后必须响应环迅支付服务器"ipscheckok"否则会多次回调接口
package com.api.ips;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
@RestController
public class pay {
@GetMapping("pay")
public String payh() {
String hxPay = IpsPayUtils.hxPay("苹果", "虾皮苹果", "798484848444474", "0.01");
System.out.println(hxPay);
return hxPay;
}
/**
* 同步回调
*
* @param request
* @param response
* @throws UnsupportedEncodingException
*/
@RequestMapping("asynchroHuanxin")
public void asynchroHuanxin(HttpServletRequest request, HttpServletResponse response)
throws UnsupportedEncodingException {
JSONObject map = Utils.getJsonParam(request);
System.out.println("同步回调:" + map.toString());
String data = map.getString("data");
String version = map.getString("version");
String merCode = map.getString("merCode");
String ts = map.getString("ts");
String nonceStr = map.getString("nonceStr");
String format = map.getString("format");
String encryptType = map.getString("encryptType");
String signType = map.getString("signType");
String notifyType = map.getString("notifyType");
String sign = map.getString("sign");
String verifyObject = null;
String decryptData = null;
boolean verifyResult = false;
try {
if (!Utils.isBlank(sign)) {
CollBackResponse backResponse = new CollBackResponse(version,ts,merCode,nonceStr,
format,encryptType,data,signType,notifyType);
verifyObject = Utils.getParamStr(Utils.objectToMap(backResponse));
verifyResult = MerVerify.verifySHA256withRSA(AccEntryConfig.SHA256_PUBLIC_KEY,
verifyObject, sign);
if (verifyResult) {
decryptData = MerVerify.decryptAES(AccEntryConfig.AES_KEY, data);
System.out.println("----:"+decryptData);
JSONObject jo = JSONObject.parseObject(new String(decryptData));
String trxId = jo.getString("trxId");//商户订单号
String trxStatus = jo.getString("trxStatus");//订单状态
String trxResultMsg = jo.getString("trxResultMsg");//订单消息
String ipsTrxId = jo.getString("ipsTrxId");//IPS订单流水号
if (trxStatus.equals("SUCCESS")) {
//交易成功
System.out.println("交易成功");
}
if (trxStatus.equals("FAIL")) {
//交易失败
System.out.println("交易失败");
}
if (trxStatus.equals("PROCESSING")) {
//交易处理中
System.out.println("交易处理中");
}
}
}
} catch (Throwable ex) {
ex.printStackTrace();
}
}
/**
* 异步回调
*
* @param request
* @param response
*/
@RequestMapping("synchroHuanxin")
public String synchroHuanxin(HttpServletRequest request, HttpServletResponse response) {
JSONObject map = Utils.getJsonParam(request);
System.out.println("异步回调:" + map.toString());
String data = map.getString("data");
String version = map.getString("version");
String merCode = map.getString("merCode");
String ts = map.getString("ts");
String nonceStr = map.getString("nonceStr");
String format = map.getString("format");
String encryptType = map.getString("encryptType");
String signType = map.getString("signType");
String notifyType = map.getString("notifyType");
String sign = map.getString("sign");
String verifyObject = null;
String decryptData = null;
boolean verifyResult = false;
try {
if (!Utils.isBlank(sign)) {
CollBackResponse backResponse = new CollBackResponse(version,ts,merCode,nonceStr,
format,encryptType,data,signType,notifyType);
verifyObject = Utils.getParamStr(Utils.objectToMap(backResponse));
verifyResult = MerVerify.verifySHA256withRSA(AccEntryConfig.SHA256_PUBLIC_KEY,
verifyObject, sign);
if (verifyResult) {
decryptData = MerVerify.decryptAES(AccEntryConfig.AES_KEY, data);
System.out.println("----:"+decryptData);
JSONObject jo = JSONObject.parseObject(new String(decryptData));
String trxId = jo.getString("trxId");//商户订单号
String trxStatus = jo.getString("trxStatus");//订单状态
String trxResultMsg = jo.getString("trxResultMsg");//订单消息
String ipsTrxId = jo.getString("ipsTrxId");//IPS订单流水号
if (trxStatus.equals("SUCCESS")) {
//交易成功
System.out.println("交易成功");
return "ipscheckok";
}
if (trxStatus.equals("FAIL")) {
//交易失败
System.out.println("交易失败");
}
if (trxStatus.equals("PROCESSING")) {
//交易处理中
System.out.println("交易处理中");
}
}
}
} catch (Throwable ex) {
ex.printStackTrace();
}
return null;
}
}
所需的jar包在demo全部复制过来即可,但是不支持maven从网上仓库引入依赖,会导致无法加载私钥。目前我是直接把jar包添加到项目中,大家可以尝试本地安装jar再引入依赖
这里是支付成功后环迅服务器回调接口传过来的参数值,订单状态以及订单信息都在data中。需要解密
验签需要把所有参数通过文档方式加密一遍与sign相对比
同步回调:
{"merCode":"178767",
"notifyType":"1",
"data":"83b8203f20b6a9aabb79a9c3735ae75f8a3bd1c1b99e8972b4146297ccd2ed17a0948434480fd47f270a41093800b02e6bf426bacd846bc88b4671a89a51af3e8c0bf611d7e744d0b8b293e47aeae983597d02a98f8b90a0c5f9c738906d625c27fd52b39d7bcf05171c960ad535a26569d2579bd1ee6f921395e6258064067298694b8d80269276459cf4703c4c21a22d5fe9803fa7ce38d650cba8131504c0e3e965de980ba8525d452507b735db5f91ec6ac9ffbcd5555d22ddcdfbed1a2e486493de9bac03367857923f6effa9fbffe6d26dde4cdb7e818ab4dd3020c1acab938d42811e81c606ef2f462015f03b89e4f805eb0df182407bc68ed12e5f510785e1c5d324e756ef4c85b4ba63635773782a75d3559ec519e59a4a7814c942b09757c8f6d07d9c21c6bf67c92f13387a481410cc28a6208f3d5e5996fa0195a822abe5c6303e2439a56a4e443a19c72a9f652eb97112c3551a87e38741e5edf36f3fdbdf94222b52be63ad81e075da898700e380e5d44509a7ba59305ec995c8e61c3746fa1b7cd8ba1b3ba72969a9",
"sign":"3ff5680aed7a5667a19015567efd86d9b6734191746be3e31c095c629ee9763e63e58acccbe01c706f2e6d431f20d9fcca6b9bb3e58afb621a0bc53300ba0523b35b745d1fb414eeab7202f7de786ad6a8d8ad3a229f00db78eefe5de43d15a68df5291a681264384733c6bd71dfee53fafdec9f5eace2104d5aa7c34de0eba412fe5b6ad803442a85009f510494f6430645f1ec42a31500979f0fa610f042c4c5af3020472c970cc2a362dcc55afb2bf5fd2418f86a8288714d238df6a166d8a339f2f8e17ea2bbaf8a2e171efc71001256858e48bce7140101e3ebfde0485dd2f3eb22df94528af9fa0be390bb0b7e5c5987088324b3e6ac6f6a93b806e268",
"signType":"RSA2",
"version":"2.0.0",
"encryptType":"AES",
"nonceStr":"5668d6f03c6b4e0c8f3e28acc86473ef",
"ts":"20191024115045"}
----:{"account":"1787670106","trxId":"88988998989444","trxDtTm":"20191024","trxCcyCd":"156","trxAmt":"0.01","payAmt":"0.01","payFee":"0","feeWho":"2","payType":"1501","cardType":"1","trxStatus":"SUCCESS","trxResultMsg":"支付成功","attach":"苹果","ipsTrxId":"BO20191024115016004120","bkTrxId":"2019102442104710419284230202905","ipsTrxDtTm":"20191024115045","thdTrxId":"","extInfo":"thrPayInfo="}
----:苹果
异步回调:{
"merCode":"178767",
"notifyType":"1",
"data":"83b8203f20b6a9aabb79a9c3735ae75f8a3bd1c1b99e8972b4146297ccd2ed17a0948434480fd47f270a41093800b02e6bf426bacd846bc88b4671a89a51af3e8c0bf611d7e744d0b8b293e47aeae983597d02a98f8b90a0c5f9c738906d625c27fd52b39d7bcf05171c960ad535a26569d2579bd1ee6f921395e6258064067298694b8d80269276459cf4703c4c21a22d5fe9803fa7ce38d650cba8131504c0e3e965de980ba8525d452507b735db5f91ec6ac9ffbcd5555d22ddcdfbed1a2e486493de9bac03367857923f6effa9fbffe6d26dde4cdb7e818ab4dd3020c1acab938d42811e81c606ef2f462015f03b89e4f805eb0df182407bc68ed12e5f510785e1c5d324e756ef4c85b4ba63635773782a75d3559ec519e59a4a7814c942b09757c8f6d07d9c21c6bf67c92f13387a481410cc28a6208f3d5e5996fa0195a822abe5c6303e2439a56a4e443a19c72a9f652eb97112c3551a87e38741e5edf36f3fdbdf94222b52be63ad81e075da898700e380e5d44509a7ba59305ec995c8e61c3746fa1b7cd8ba1b3ba72969a9",
"sign":"29f4050533302b29cd5d13e85e89390cdb8bab95d84c77cfe2ee1fcb4c0d5d0948431460afa98e36875a406af212328c79f1c054f5d000ea579b7959e7933cf5e01fca7245bfa1d1ecefe6f30350e25870329a435c4e9828fca71e6f66949e831bee93c1cd6bbc3583a206996dfbb0e1e8bc0f3a78ff94589cc8e8db009e3a597a457b17a74e63a387bbfdb93c3e7624f0cfc313c0a307a7fdba1af7ec199c49fe9c54e811d50ce111de22fa9de86f1ea4c36e8de152cb8d2351fbd97fdb1494e7295b92a285adb1c9716fc8d3d66d45cdcaa98174801ce6bb3267e16a00a05181145e7c58d933e5be0ab880977acba63a7e1ee61cc5b836da92f0e5e363e177",
"signType":"RSA2",
"version":"2.0.0",
"nonceStr":"889df20d9ccd4b3385d7cc139da36f52",
"encryptType":"AES",
"ts":"20191024115045"}
支付回调必须要做域名穿透,大家可是用花生壳或者https://natapp.cn/member/dashborad这两者,花生壳前期可以免费试用,但是得实名认证,域名不会发生改变,natapp可以一直使用但是每次使用的时候分配的域名都不一致
说实话,环迅支付文档是我遇到最难搞的了。像个gui一样~~~~~