(1)微信"大众平台
微信"大众平台是微信公众账号申请入口和管理后台。商户可以在"大众年夜众平台提交基本资料、业务资料、财务资料申请开通微信支付功能。
(2) 微信开放平台
微信开放平台是商户APP接入微信支付开放接口的申请入口,通过此平台可申请微信APP支付。
(3) 微信商户平台
微信商户平台是微信支付干系的商户功能凑集,包括参数配置、支付数据查询与统计、在线退款、代金券或立减优惠运营等功能。
2、支付产品先容:
(1)付款码支付
付款码支付,即日常所说的被扫支付,这是一种纯用于线了局景的支付办法,由用户出示微信客户端内展示的付款二维码,商户利用扫码设备扫码后完成支付。
(2)Native原生支付
Native原生支付,即日常所说的扫码支付,商户根据微信支付协议格式天生的二维码,用户通过微信“扫一扫”扫描二维码后即进入付款确认界面,输入密码即完成支付。
(3) JSAPI网页支付
JSAPI网页支付,即日常所说的公众年夜众号支付,可在微信"大众年夜众号、朋友圈、谈天会话中点击页面链接,或者用微信“扫一扫”扫描页面地址二维码在微信中打开商户HTML5页面,在页面内下单完成支付。
(4) APP支付
APP支付是指商户已有的APP,通过对接微信支付API及SDK,实现从商户APP发起交易后跳转到微信APP,用户完成支付后跳回商户APP的场景。
(5) H5支付
H5支付是指在微信外打开的H5页面,通过对接微信支付API,实现拉起微信客户端,完成支付后跳回外部浏览器的能力。
(6) 小程序支付
小程序支付是指在商户既有的小程序内通过对接微信支付API,实现用户在小程序内完成交易的场景。
3、申请运用APPID
由于微信支付的产品体系全部搭载于微信的社交体系之上,以是直连商户或做事商商户接入微信支付之前,都须要有一个微信社交载体,该载体对应的ID即为APPID。
对付直连商户,该社交载体可以是"大众年夜众号,小程序或APP。而做事商的社交载体只能是"大众年夜众号。
如申请社交载体为"大众年夜众号,请前往公众年夜众平台申请(https://mp.weixin.qq.com)
如申请社交载体为小程序,请前往小程序平台申请 (https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/getstart.html#申请帐号)
如商户已拥有自己的APP,且希望该APP接入微信支付,请前往开放平台申请(https://open.weixin.qq.com/)
各种社交载体一旦申请成功后,可以登录对应平台查看账号信息以获取对应的appid。
4、申请商户MCHID
商户号申请平台申请MCHID(https://pay.weixin.qq.com)
申请成功后,会向做事商填写的联系邮箱下发关照邮件,内容包含申请成功的MCHID及其登录账号密码,请妥善保存。
把稳:一个MCHID只能对应一个结算币种,若须要利用多个币种收款,须要申请对应数量的MCHID。
5、绑定APPID及MCHID
APPID和MCHID全部申请完毕后,须要建立两者之间的绑定关系。在微信商户后台进行绑定。
6、设置支付API密钥
登录微信商户平台,在账户设置-API安全,设置API密钥。
7、微信扫码支付示例
7.1 扫码支付流程
7.2 扫码支付统一下单示例:
/
微信预创建订单,天生微信二维码
@return
@throws Exception
/
@RequestMapping(value=34;/tradePrecreate", method = RequestMethod.POST)
@ResponseBody
public void toWxPayPrecreate(PayWay payWay) throws Exception {
//自定义商户订单号:长度不能超过32位
String out_trade_no = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
//封装微信下单参数
SortedMap<String, String> paramMap = new TreeMap<>();
paramMap.put("appid", appid); //公众年夜众账号ID
paramMap.put("mch_id", mch_id); //商户号
String nonce_str= UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
paramMap.put("nonce_str", nonce_str ); //随机字符串
paramMap.put("body", payWay.getGoodsName()); // 商品描述
paramMap.put("out_trade_no", out_trade_no); //商户订单号,商户系统内部订单号,哀求32个字符内,只能是数字、大小写字母_-| 且在同一个商户号下唯一
paramMap.put("total_fee",String.valueOf(payWay.getAmount())); //标价金额,单位分
paramMap.put("spbill_create_ip", IPUtils.localIp());
paramMap.put("notify_url", notifyUrl); //自定义后台关照地址
paramMap.put("trade_type", "NATIVE"); //交易类型 JSAPI "大众年夜众号支付 NATIVE 扫码支付 APP APP支付
//第二步,署名,参数转xml
String requestXMl = WXPayUtil.generateSignedXml(paramMap, key, WXPayConstants.SignType.MD5);
try {
//发送要求(POST)(得到数据包ID)
String result = HttpXmlUtil._doPost("https://api.mch.weixin.qq.com/pay/unifiedorder",requestXMl);
log.info("微信预创建订单,返回数据:"+result);
// 将解析结果存储在HashMap中
Map map = WXPayUtil.xmlToMap(result);
String return_code = (String) map.get("return_code");//返回状态码
String return_msg = (String) map.get("return_msg");//返回状态码
String result_code = (String) map.get("result_code");//返回状态码
String err_code_des = (String) map.get("err_code_des");//返回状态码
String prepay_id = (String) map.get("prepay_id");//返回状态码
if("SUCCESS".equals(return_code)){
if("SUCCESS".equals(result_code)){
log.info("=====微信统一下单成功");
}else{
if(err_code_des!=null && !"".equals(err_code_des)){
log.error("微信预创建订单,返回非常提示:"+err_code_des);
}else{
log.error("微信下单失落败");
}
}
}else{
log.error("微信下单失落败");
}
} catch (Exception e) {
e.printStackTrace();
}
}
个中涉及到的工具类:
工具类IPUtils.java:
import java.net.;
import java.util.Enumeration;
import java.util.List;
public class IPUtils {
/
获取本机Ip
通过 获取系统所有的networkInterface网络接口 然后遍历 每个网络下的InterfaceAddress组。
得到符合 <code>InetAddress instanceof Inet4Address</code> 条件的一个IpV4地址
@return
/
@SuppressWarnings("rawtypes")
public static String localIp(){
String ip = null;
Enumeration allNetInterfaces;
try {
allNetInterfaces = NetworkInterface.getNetworkInterfaces();
while (allNetInterfaces.hasMoreElements()) {
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
List<InterfaceAddress> InterfaceAddress = netInterface.getInterfaceAddresses();
for (InterfaceAddress add : InterfaceAddress) {
InetAddress Ip = add.getAddress();
if (Ip != null && Ip instanceof Inet4Address) {
ip = Ip.getHostAddress();
}
}
}
} catch (SocketException e) {
// TODO Auto-generated catch block
e.getCause();
}
return ip;
}
}
工具类HttpXmlUtil.java:
import org.apache.commons.httpclient.;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.util.EntityUtils;
import java.io.;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/
此版本利用document 工具封装XML,办理发送短信内容包涵分外字符而涌现无法解析,如 短信为:“你好,<%&&&><<<>fds测试短信”
@author 编程侠Java
/
public class HttpXmlUtil {
/
实行一个HTTP GET要求,返回要求相应的HTML
@param url 要求的URL地址
@param params 要求的查询参数,可以为null
@return 返回要求相应的HTML
/
public static String doGet(String url,Map<String, Object> params) throws Exception {
// 布局HttpClient的实例
HttpClient httpClient = HttpClientFactory.getHttpClient();
if (params != null && !params.isEmpty()) {
List<org.apache.http.NameValuePair> pairs = new ArrayList<org.apache.http.NameValuePair>(params.size());
for (String key : params.keySet()) {
pairs.add(new org.apache.http.message.BasicNameValuePair(key, params.get(key).toString()));
}
url += "?" + EntityUtils.toString(new UrlEncodedFormEntity(pairs, "UTF-8"));
}
// 创建GET方法的实例
GetMethod getMethod = new GetMethod(url);
// 利用系统供应的默认的规复策略
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,new DefaultHttpMethodRetryHandler());
try {
// 实行getMethod
int statusCode = httpClient.executeMethod(getMethod);
if (statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: " + getMethod.getStatusLine());
}
// 读取内容
byte[] responseBody = getMethod.getResponseBody();
// 处理内容
return new String(responseBody,"UTF-8");
} catch (HttpException e) {
// 发生致命的非常,可能是协议不对或者返回的内容有问题
e.printStackTrace();
} catch (IOException e) {
// 发生网络非常
e.printStackTrace();
} finally {
// 开释连接
getMethod.releaseConnection();
}
return null;
}
/
实行一个HTTP POST要求,返回要求相应的XML
@param url 要求的URL地址
@param params 要求的查询参数,可以为null
@return 返回要求相应的XML
/
public static String _doPost(String url, String params) throws Exception {
HttpClient client = HttpClientFactory.getHttpClient();
PostMethod myPost = new PostMethod(url);
String responseString = null;
try {
myPost.setRequestEntity(new StringRequestEntity(params, "text/xml", "utf-8"));
int statusCode = client.executeMethod(myPost);
if (statusCode == HttpStatus.SC_OK) {
BufferedInputStream bis = new BufferedInputStream(myPost.getResponseBodyAsStream());
byte[] bytes = new byte[1024];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int count = 0;
while ((count = bis.read(bytes)) != -1) {
bos.write(bytes, 0, count);
}
byte[] strByte = bos.toByteArray();
responseString = new String(strByte, 0, strByte.length, "utf-8");
bos.close();
bis.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
myPost.releaseConnection();
}
return responseString;
}
}
其他的工具类如:WXPayUtil、WXPayConstants 均利用微信官方demo中的,sdk与demo下载地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1
7.3 扫码支付微信退款:
微信支付接口中,涉及资金回滚的接口会利用到API证书,包括退款、撤销接口等。可以在微信商户平台—》账户中央—》账户设置—》API安全,下载微信供应的证诗人成工具,填写商户号和商户名称,再把将软件天生的密钥字符串复制到微信商户平台,天生证书。
/
微信退款
@param tradeRefund
@return
/
public void toRefund(TradeRefund tradeRefund){
//退款订单号
String out_refund_no = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
HashMap<String, String> data = new HashMap();
data.put("out_trade_no", tradeRefund.getOrderID());
data.put("out_refund_no", out_refund_no);
data.put("total_fee", String.valueOf(tradeRefund.getTotalFee()));
data.put("refund_fee", String.valueOf(tradeRefund.getRefundAmt()));//退款金额,单位分
data.put("refund_fee_type", "CNY");
data.put("op_user_id", mch_id);
data.put("refund_desc", tradeRefund.getRefundDescribe());
data.put("notify_url", refundNotify);
try {
this.config = WXPayConfigImpl.getInstance();
this.wxpay = new WXPay(this.config);
Map<String, String> resp = this.wxpay.refund(data);
System.out.println(resp);
if (!"SUCCESS".equals(resp.get("return_code"))) {
log.error("微信退延接口调用失落败,返回相应信息:"+resp.get("return_msg"));
}else{
if ("SUCCESS".equals(resp.get("result_code"))) {
log.info("微信退款成功,返回相应信息:"+resp.get("return_msg"));
}else{
log.error("微信退款失落败,返回相应信息:"+resp.get("err_code_des"));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
涉及到的退款信息类TradeRefund.java:
public class TradeRefund {
private String Version;//版本
private String CharSet;//编码费那事
private String OrderID;//订单号
private String OperatorId;//操作人id
private short RefundFlag; //1:交易失落败退款、2:事务处理退款、3:非现金即时退款
private int RefundAmt;//退款金额
private String RefundDescribe;//订单备注
private int totalFee;//订单总金额
public String getVersion() {
return Version;
}
public void setVersion(String version) {
Version = version;
}
public String getCharSet() {
return CharSet;
}
public void setCharSet(String charSet) {
CharSet = charSet;
}
public String getOrderID() {
return OrderID;
}
public void setOrderID(String orderID) {
OrderID = orderID;
}
public String getOperatorId() {
return OperatorId;
}
public void setOperatorId(String operatorId) {
OperatorId = operatorId;
}
public short getRefundFlag() {
return RefundFlag;
}
public void setRefundFlag(short refundFlag) {
RefundFlag = refundFlag;
}
public int getRefundAmt() {
return RefundAmt;
}
public void setRefundAmt(int refundAmt) {
RefundAmt = refundAmt;
}
public String getRefundDescribe() {
return RefundDescribe;
}
public void setRefundDescribe(String refundDescribe) {
RefundDescribe = refundDescribe;
}
public int getTotalFee() {
return totalFee;
}
public void setTotalFee(int totalFee) {
this.totalFee = totalFee;
}
}
7.4 扫码支付回调:
微信支付完,会调用做事端后真个关照接口,返回支付信息,商户需在微信公众年夜众号后台配置回调地址,把稳:回调地址必须利用通过ICP备案的域名,不能是IP地址,并且链接不能带参数。
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
@Slf4j
@Controller
public class NotifyController {
private String key = ""; //这里填运用的key
/
异步接管微信扫码支付关照
支付结果通用关照文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8
@param request
@param response
@throws IOException
/
@ResponseBody
@RequestMapping(value = "payNotify", produces = "application/json;charset=UTF-8")
public void payNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
BufferedReader reader = null;
reader = request.getReader();
String line = "";
String xmlString = null;
StringBuffer inputString = new StringBuffer();
while ((line = reader.readLine()) != null) {
inputString.append(line);
}
xmlString = inputString.toString();
request.getReader().close();
Map<String, String> packageParams = WXPayUtil.xmlToMap(xmlString);
//判断署名是否精确
if (checkSign(packageParams)) {
String resXml = "";
if("SUCCESS".equals((String)packageParams.get("result_code"))){//支付成功
String appid = packageParams.get("appid");
String mch_id = packageParams.get("mch_id");
String openid = packageParams.get("openid");
String is_subscribe = packageParams.get("is_subscribe");
String out_trade_no = packageParams.get("out_trade_no");
String total_fee = packageParams.get("total_fee");
//交易类型
String trade_type = packageParams.get("trade_type");
//付款银行
String bank_type = packageParams.get("bank_type");
//现金支付金额
String cash_fee = packageParams.get("cash_fee");
// 微信支付订单号
String transactionId = packageParams.get("transaction_id");
// // 支付完成韶光,格式为yyyyMMddHHmmss
String time_end = packageParams.get("time_end");
//关照微信.异步确认成功.必写.不然会一贯关照后台.八次之后就认为交易失落败了.
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
//处理自己的而业务
} else {
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
} else{
log.error("======署名验证失落败");
}
}
/
署名验证
@param map
@return
/
private boolean checkSign(Map<String, String> map) {
String signFromAPIResponse = map.get("sign");
if (signFromAPIResponse == "" || signFromAPIResponse == null) {
log.info("=========API返回的数据署名数据不存在");
return false;
}
//清掉返回数据工具里面的Sign数据(不能把这个数据也加进去进行署名),然后用署名算法进行署名
map.put("sign", "");
//将API返回的数据根据用署名算法进行打算新的署名,用来跟API返回的署名进行比较
String signForAPIResponse = getSign(map);
if (!signForAPIResponse.equals(signFromAPIResponse)) {
//署名验不过,表示这个API返回的数据有可能已经被修改了
log.info("===========API返回的数据署名验证不通过");
return false;
}
log.info("===========sign署名验证通过");
return true;
}
public String getSign(Map<String, String> map) {
SortedMap<String, String> signParams = new TreeMap<String, String>();
for (Map.Entry<String, String> stringStringEntry : map.entrySet()) {
signParams.put(stringStringEntry.getKey(), stringStringEntry.getValue());
}
signParams.remove("sign");
String sign = null;
try {
sign = WXPayUtil.generateSignature(signParams, key);
} catch (Exception e) {
e.printStackTrace();
}
return sign;
}
}
7.5 退款回调:
private String key = ""; //这里填运用的key
/
退款结果关照
<p>
在申请退延接口中上传参数“notify_url”以开通该功能
如果链接无法访问,商户将无法吸收到微信关照。
关照url必须为直接可访问的url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”
<p>
当商户申请的退款有结果后,微信会把干系结果发送给商户,商户须要吸收处理,并返回应答。
对后台通深交互时,如果微信收到商户的应答不是成功或超时,微信认为关照失落败,微信会通过一定的策略定期重新发起关照,尽可能提高关照的成功率,但微信不担保关照终极能成功。
(关照频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)
把稳:同样的关照可能会多次发送给商户系统。商户系统必须能够精确处理重复的关照。
推举的做法是,当收到关照进行处理时,首先检讨对应业务数据的状态,判断该关照是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检讨和处理之前,要采取数据锁进行并发掌握,以避免函数重入造成的数据混乱。
特殊解释:退款结果对主要的数据进行了加密,商户须要用商户秘钥进行解密后才能得到结果关照的内容
@param request
@param response
@throws IOException
/
@ResponseBody
@RequestMapping(value = "refundNotify", produces = "application/json;charset=UTF-8")
public void refundNotify(HttpServletRequest request,HttpServletResponse response) throws Exception {
BufferedReader reader = null;
reader = request.getReader();
String line = "";
String xmlString = null;
StringBuffer inputString = new StringBuffer();
while ((line = reader.readLine()) != null) {
inputString.append(line);
}
xmlString = inputString.toString();
request.getReader().close();
log.info("===========异步接管微信退款回调关照:" + xmlString);
Map<String, String> notifyMapData = WXPayUtil.xmlToMap(xmlString);
String resXml = "";
if("SUCCESS".equals(notifyMapData.get("return_code"))){//退款成功
// 得到加密信息
String reqInfo = notifyMapData.get("req_info");
/
解密办法
解密步骤如下:
(1)对加密串A做base64解码,得到加密串B
(2)对商户key做md5,得到32位小写key ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )
(3)用key对加密串B做AES-256-ECB解密(PKCS7Padding)
/
// 进行AES解密 获取req_info中包含的干系信息(解密失落败会抛出非常)
String keyB = MD5.MD5Encode2(key, "UTF-8");
AESUtils util = new AESUtils(keyB); // 密钥
String refundDecryptedData = util.decryptData(reqInfo);
Map<String, String> aesMap = WXPayUtil.xmlToMap(refundDecryptedData);
/ 以下为返回的加密字段: /
//商户退款单号
String out_refund_no = aesMap.get("out_refund_no");
//退款状态:SUCCESS-退款成功、CHANGE-退款非常、REFUNDCLOSE—退款关闭
String refund_status = aesMap.get("refund_status");
//商户订单号
String out_trade_no = aesMap.get("out_trade_no");
//微信订单号
String transaction_id = aesMap.get("transaction_id");
//微信退款单号
String refund_id = aesMap.get("refund_id");
//订单总金额,单位为分,只能为整数
String total_fee = aesMap.get("total_fee");
//应结订单金额
String settlement_total_fee = aesMap.get("settlement_total_fee");
//申请退款金额,单位为分
String refund_fee = aesMap.get("refund_fee");
//退款金额,退款金额=申请退款金额-非充值代金券退款金额,退款金额<=申请退款金额
String settlement_refund_fee = aesMap.get("settlement_refund_fee");
String success_time = aesMap.get("success_time");
// 退款是否成功
if (!"SUCCESS".equals(refund_status)) {
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[退款失落败]]></return_msg>" + "</xml> ";
WXPayUtil.getLogger().error("========================refund:微信支付回调:退款失落败");
} else {
// 关照微信.异步确认成功.必写.不然会一贯关照后台.八次之后就认为交易失落败了.
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
WXPayUtil.getLogger().info("微信支付回调:退款成功");
}
} else {
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[退款非常]]></return_msg>" + "</xml> ";
log.error("==========微信扫码退款非常");
}
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
}
个中涉及到的工具类AESUtils.java
import com.cn.util.Base64Util;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
/
AES加解密
Created 编程侠Java
/
public class AESUtils {
/
密钥算法
/
private static final String ALGORITHM = "AES";
/
加解密算法/事情模式/添补办法
/
private static final String ALGORITHM_STR = "AES/ECB/PKCS5Padding";
/
SecretKeySpec类是KeySpec接口的实现类,用于构建秘密密钥规范
/
private static SecretKeySpec key;
public AESUtils(String hexKey) {
key = new SecretKeySpec(hexKey.getBytes(), ALGORITHM);
}
/
AES加密
@param data
@return
@throws Exception
/
public String encryptData(String data) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_STR); // 创建密码器
cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化
byte[] encrypted = cipher.doFinal(data.getBytes());
return Base64Util.encodeBytes(encrypted);
}
/
AES解密
@param base64Data
@return
@throws Exception
/
public static String decryptData(String base64Data) throws Exception{
Cipher cipher = Cipher.getInstance(ALGORITHM_STR);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] original = cipher.doFinal(Base64Util.decode(base64Data));
return new String(original);
}
}
二、支付宝支付
支付宝支付一样平常分为代扣支付、扫码支付等等。代扣支付,用户须要前辈行签约,常日通过商户APP跳转到支付宝APP进行签约,支付时拿用户在支付宝APP中签约时的协议号去扣款。
代扣做事须要在支付宝商户后台开通商户代扣能力。而支付宝二维码扫码支付,需在支付宝商家后台开通当面付能力。
(1)支付宝代扣支付
/
支付宝代扣支付
@param orderID 订单编号
@param transAmount 订单金额
@param userPayAccount 支付账号
@return
/
public JSONObject alipayWithhold(String orderID, String transAmount, String userPayAccount){
JSONObject result = new JSONObject();
String returnUrl= "";//自定义扣款同步关照接口
String notifyUrl= "";//自定义扣款异步关照接口
String actual_order_time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
//初始化要求类
AlipayTradePayRequest alipayRequest = new AlipayTradePayRequest();
//组装扣款参数
String bizContent = "{"
+ "\"out_trade_no\":\"" + orderID + "\","
+ "\"product_code\":\"GENERAL_WITHHOLDING\","
+ "\"total_amount\":\"" + transAmount + "\","
+ "\"subject\":\"扣款备注信息\","
+ "\"promo_params\":{" + "\"actual_order_time\":\"" + actual_order_time + "\"},"
+ "\"agreement_params\":{" + "\"agreement_no\":\"" + userPayAccount + "\"}"
+ "}";
alipayRequest.setBizContent(bizContent);
alipayRequest.setReturnUrl(returnUrl);
alipayRequest.setNotifyUrl(notifyUrl);
//sdk要求客户端,已将配置信息初始化
AlipayClient alipayClient = DefaultAlipayClientFactory.getAlipayClient();
try {
//由于是接口做事,利用exexcute方法获取到返回值
AlipayTradePayResponse alipayResponse = alipayClient.execute(alipayRequest);
if (alipayResponse.isSuccess()) {
if (alipayResponse.getCode().equals("10000")) {
result.put("code","SUCCESS");
result.put("msg","支付宝扣款成功");
} else {
result.put("code","FAIL");
result.put("msg","支付宝扣款失落败,"+alipayResponse.getSubMsg());
log.error("=====支付宝扣款失落败");
}
} else {
result.put("code","FAIL");
result.put("msg","支付宝扣款失落败,"+alipayResponse.getSubMsg());
log.error("=====支付宝接口调用失落败");
}
} catch (AlipayApiException e) {
e.printStackTrace();
if (e.getCause() instanceof java.security.spec.InvalidKeySpecException) {
result.put("code","FAIL");
result.put("msg","商户私钥格式禁绝确,请确认配置文件是否配置精确");
log.error("=====商户私钥格式禁绝确,请确认配置文件是否配置精确");
}
}
return result;
}
个中支付宝封装调用的工具类 DefaultAlipayClientFactory.java
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
/
@author 编程侠Java
@description: 支付宝公共要求参数拼接类
@date 2021/01/09 13:36
/
public class DefaultAlipayClientFactory {
private static AlipayClient alipayClient = null;
/
封装公共要求参数
@return AlipayClient
/
public static AlipayClient getAlipayClient() {
if(alipayClient != null){
return alipayClient;
}
// 网关
String URL = "https://openapi.alipay.com/gateway.do";
// 商户APP_ID
String APP_ID = "商户APP_ID";
// 商户RSA 私钥
String APP_PRIVATE_KEY = "商户RSA私钥";
// 要求办法 json
String FORMAT = "json";
// 编码格式,目前只支持UTF-8
String CHARSET = "UTF-8";
// 支付宝公钥
String ALIPAY_PUBLIC_KEY = "支付宝公钥";
// 署名办法
String SIGN_TYPE = "RSA2";
return new DefaultAlipayClient(URL, APP_ID, APP_PRIVATE_KEY, FORMAT, CHARSET, ALIPAY_PUBLIC_KEY, SIGN_TYPE);
}
}
代扣支付回调:
/
异步接管支付宝代扣关照
@param request
@param response
@throws IOException
/
@ResponseBody
@RequestMapping(value="/notifyUrl.htm")
public void notifyObject(HttpServletRequest request, HttpServletResponse response) throws IOException {
String charset = "UTF-8"; // 编码
String publicKey = "填写支付宝公钥"; //支付宝公钥
String singType = "RSA2"; //署名办法
Map<String, String> params = new HashMap<String, String>();
Map<String, String[]> requestParams = request.getParameterMap();
log.info("=======异步接管支付宝代扣渠道关照,要求参数:"+ JSON.toJSONString(requestParams));
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
try {
boolean validation = AlipaySignature.rsaCheckV1(params, publicKey, charset,singType);//验签
if (validation) {
//根据业务须要进行处理
String notify_type = params.get("notify_type");
if("dut_user_unsign".equals(notify_type)){//dut_user_unsign 解约
//处理解约业务
} else if("dut_user_sign".equals(notify_type)){//签约 绑定dut_user_sign
//处理签约业务
}else if("trade_status_sync".equals(notify_type)){//订单支付关照
String gmt_create = params.get("gmt_create");
String seller_email = params.get("seller_email");
String subject = params.get("subject");
String buyer_id = params.get("buyer_id");
String invoice_amount = params.get("invoice_amount");
String notify_id = params.get("notify_id");
String trade_status = params.get("trade_status");
String receipt_amount = params.get("receipt_amount");
String app_id = params.get("app_id");
String buyer_pay_amount = params.get("buyer_pay_amount");
String sign_type = params.get("sign_type");
String seller_id = params.get("seller_id");
String gmt_payment = params.get("gmt_payment");
String notify_time = params.get("notify_time");
String version = params.get("version");
String out_trade_no = params.get("out_trade_no");
String total_amount = params.get("total_amount");
String trade_no = params.get("trade_no");
String auth_app_id = params.get("auth_app_id");
String buyer_logon_id = params.get("buyer_logon_id");
String point_amount = params.get("point_amount");
//处理订单支付之后的业务
}
//给支付宝返回success,否则支付宝会连续多次发送
response.getOutputStream().write("success".getBytes());
response.flushBuffer();
}
} catch (AlipayApiException e) {
log.error("======异步接管支付宝代扣渠道关照,回调非常");
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/
同步返回处理
@param request
@param response
@throws IOException
/
@MyLog(value = "同步接管支付宝关照")
@ResponseBody
@RequestMapping(value="/returnUrl.htm")
public void returnObject(HttpServletRequest request,HttpServletResponse response) throws IOException {
notifyObject(request,response);
}
(2)支付宝扫码支付
把稳:支付宝支付,先要初始化加载zfbinfo.properties文件
static {
Configs.init("zfbinfo.properties");
}
private static AlipayTradeService tradeService = (AlipayTradeService)(new AlipayTradeServiceImpl.ClientBuilder()).build();
/
支付宝订单预创建
@param payWay
@return
@throws IOException
/
public void toPrecreate(PayWay payWay) throws IOException {
String outTradeNo = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); //订单号
String subject = payWay.getGoodsName();//商户名称
String totalAmount = (payWay.getAmount() 0.01D) + "";//订单金额
String undiscountableAmount = "0";
String sellerId = "";
String body = payWay.getGoodsName() + payWay.getNum() + "张共" + totalAmount + "元";
String operatorId = payWay.getOperatorId();
String storeId = String.valueOf(payWay.getStationID());
String terminalId = (payWay.getDevID().length() != 8) ? payWay.getDevID().substring(2, 10) : payWay.getDevID();
ExtendParams extendParams = new ExtendParams();
extendParams.setSysServiceProviderId("填写支付宝商户号");//商户号
String timeoutExpress = "2m";//支付宝二维码过期韶光
List<GoodsDetail> goodsDetailList = new ArrayList<>();
GoodsDetail goods1 = GoodsDetail.newInstance("扫码支付", payWay.getGoodsName(), payWay.getPrice(), Integer.valueOf(payWay.getNum()));
goodsDetailList.add(goods1);
AlipayTradePrecreateRequestBuilder builder = (new AlipayTradePrecreateRequestBuilder()).setSubject(subject)
.setTotalAmount(totalAmount).setOutTradeNo(outTradeNo).setUndiscountableAmount(undiscountableAmount)
.setSellerId(sellerId).setBody(body).setOperatorId(operatorId).setStoreId(storeId).setTerminalId(terminalId)
.setExtendParams(extendParams).setTimeoutExpress(timeoutExpress)
.setNotifyUrl("填写支付回调关照地址").setGoodsDetailList(goodsDetailList);
//发起向支付宝做事端预创建要求,并返回创建结果
AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
switch (result.getTradeStatus()) {
case SUCCESS:
log.info("订单号{" + outTradeNo + "}创建成功");
AlipayTradePrecreateResponse resp = result.getResponse();
this.dumpResponse(resp);
String path="D://alipay";
File folder = new File(path);
if (!folder.exists()) {
folder.setWritable(true);
folder.mkdirs();
}
String filePath = String.format(path +"/qr-%s.png", new Object[] { resp.getOutTradeNo() });
//将二维码保存到本地filePath目录
ZxingUtils.getQRCodeImge(result.getResponse().getQrCode(), 256, filePath);
case FAILED:
log.error("订单号: " + outTradeNo + ",支付宝下单失落败");
case UNKNOWN:
log.error("订单号: " + outTradeNo + ",预创建失落败,返回非常");
}
}
在支付宝商户后台配置回到地址,在支付宝处理完业务(比如签约、解约、支付等),用户回调吸收之后处理详细业务,吸收成功须要给支付宝返回success字符串,否则支付宝侧25小时以内完成8次关照(关照的间隔频率一样平常是4m,10m,10m,1h,2h,6h,15h)
/
异步接管支付宝扫码支付关照
@param request
@param response
@throws IOException
/
@ResponseBody
@RequestMapping(value="/facePayNotifyUrl.htm")
public void facePayNotifyUrl(HttpServletRequest request, HttpServletResponse response) throws IOException {
String charset = "UTF-8"; // 编码
String publicKey = "填写支付宝公钥"; //支付宝公钥
String singType = "RSA2"; //署名办法
Map<String, String> params = new HashMap<String, String>();
Map<String, String[]> requestParams = request.getParameterMap();
log.info("============异步接管支付宝扫码支付关照.要求参数:"+ JSON.toJSONString(requestParams));
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = iter.next();
String[] values = requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
try {
boolean validation = AlipaySignature.rsaCheckV1(params, publicKey, charset,singType);
if (validation) {
String gmt_create = params.get("gmt_create");
String seller_email = params.get("seller_email");
String subject = params.get("subject");
String buyer_id = params.get("buyer_id");
String invoice_amount = params.get("invoice_amount");
String notify_id = params.get("notify_id");
String trade_status = params.get("trade_status");
String receipt_amount = params.get("receipt_amount");
String app_id = params.get("app_id");
String buyer_pay_amount = params.get("buyer_pay_amount");
String sign_type = params.get("sign_type");
String seller_id = params.get("seller_id");
String gmt_payment = params.get("gmt_payment");
String notify_time = params.get("notify_time");
String version = params.get("version");
String out_trade_no = params.get("out_trade_no");
String total_amount = params.get("total_amount");
String trade_no = params.get("trade_no");
String auth_app_id = params.get("auth_app_id");
String buyer_logon_id = params.get("buyer_logon_id");
String point_amount = params.get("point_amount");
if("TRADE_SUCCESS".equals(trade_status)){
log.info("支付宝交易成功,自行处理业务");
}else{
log.error("支付宝交易失落败,排查缘故原由");
}
//给支付宝返回success,否则支付宝会连续多次发送
response.getOutputStream().write("success".getBytes());
response.flushBuffer();
}
} catch (AlipayApiException e) {
log.error("=======异步接管支付宝扫码支付关照,回调非常");
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
三、银联支付
我们比较常见的是银联签约免密支付、银联闪付、银联扫码支付等
(1)银联签约免密支付
银联签约免密支付一样平常在商家app内填写银行卡干系信息(姓名、手机号、银行卡号、证件号码等),跳转到银联页面进行签约,商户端不须要保存银行卡的干系信息,银联侧会返回签约后的token信息,支付时利用token去支付(拼接token参数),类比支付宝代扣拿签约时的协议号去支付。
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import com.cn.Controller.unionpay.sdk.;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.;
/
银联代扣支付逻辑:
APP原生页面填写用户姓名、手机号、身份证号码、银行卡号,点击确定调用APP后台接口获取银联签约跳转页面(后台调用银联侧接口天生银联签约跳转页面),跳转到银联签约(业务开通)页面(此页面是银联侧的页面),获取短信验证码确认开通;
若开通成功,银联后台关照商户(银联页面自动返回至商户页面),商户保存银联返回的银行卡号末4位数字与token的对应关系。
/
@Slf4j
@RequestMapping("/unionpay")
@Controller
public class UnionpayController {
private static String merId = "填写商户id";
private static String trId ="99988877766 ";//生产环境由业务分配,测试环境可以利用99988877766
static {
SDKConfig.getConfig().loadPropertiesFromSrc();
}
/
银联侧开通
商户APP内输入姓名、手机号、身份证号码、银行卡号(输入信息的页面是app自己的),输入完成调用APP后台做事,后台做事调用银联侧开通接口,获取到银联返回的自动跳转的Html表单给app,app去跳转(此时跳转后的页面是银联的)
@param req
@param resp
@throws ServletException
@throws IOException
/
@ResponseBody
@RequestMapping(value="/tokenOpenCard")
public void tokenOpenCard(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/html; charset="+ DemoBase.encoding);
String certifId = req.getParameter("idcard");//用户身份证号码
String customerNm=req.getParameter("username");//用户姓名
String phoneNo = req.getParameter("mobile");//用户手机号
String accNo = req.getParameter("bankNo");//用户银行卡号
Map<String, String> contentData = new HashMap<String, String>();
/银联全渠道系统,产品参数,除了encoding自行选择外其他不需修正/
contentData.put("version", DemoBase.version); //版本号
contentData.put("encoding", DemoBase.encoding); //字符集编码 可以利用UTF-8,GBK两种办法
contentData.put("signMethod", SDKConfig.getConfig().getSignMethod()); //署名方法
contentData.put("txnType", "79"); //交易类型 79:开通交易
contentData.put("txnSubType", "00"); //交易子类型 00-默认开通
contentData.put("bizType", "000902"); //业务类型 Token支付
contentData.put("channelType", "07"); //渠道类型07-PC
/商户接入参数/
contentData.put("merId", merId); //商户号码(本商户号码仅做为测试调通交易利用,该商户号配置了须要对敏感信息加密)测试时请改本钱身申请的商户号,【自己注册的测试777开头的商户号不支持代收产品】
contentData.put("accessType", "0"); //接入类型,商户接入固定填0,不需修正
contentData.put("orderId", DemoBase.getOrderId()); //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则
contentData.put("txnTime", DemoBase.getCurrentTime()); //订单发送韶光,格式为yyyyMMddHHmmss,必须取当前韶光,否则会报txnTime无效
contentData.put("accType", "01");
//生产环境由业务分配,测试环境可以利用99988877766
contentData.put("tokenPayData", "{trId="+trId+"&tokenType=01}");
//选送卡号、手机号、证件类型+证件号、姓名
Map<String,String> customerInfoMap = new HashMap<String,String>();
customerInfoMap.put("certifTp", "01");//证件类型
customerInfoMap.put("certifId", certifId);//证件号码
customerInfoMap.put("customerNm", customerNm);//姓名
customerInfoMap.put("phoneNo", phoneNo); //手机号
//如果商户号开通了【商户对敏感信息加密】的权限那么须要对 accNo,pin和phoneNo,cvn2,expired加密(如果这些上送的话),对敏感信息加密利用:
contentData.put("accNo", AcpService.encryptData(accNo, "UTF-8")); //银行卡号
contentData.put("encryptCertId",AcpService.getEncryptCertId()); //加密证书的certId,配置在acp_sdk.properties文件 acpsdk.encryptCert.path属性下
String customerInfoStr = AcpService.getCustomerInfoWithEncrypt(customerInfoMap,null,DemoBase.encoding);
contentData.put("customerInfo", customerInfoStr);
//前台关照地址 (需设置为外网能访问 http https均可),支付成功后的页面 点击“返回商户”的时候将异步关照报文post到该地址
//如果想要实现过几秒中自动跳转回商户页面权限,需联系银联业务申请开通自动返回商户权限
//注:如果开通失落败的“返回商户”按钮也是触发frontUrl地址,点击时是按照get方法返回的,没有关照数据返回商户
contentData.put("frontUrl", DemoBase.frontUrl);
//后台关照地址(需设置为【外网】能访问 http https均可),支付成功后银联会自动将异步关照报文post到商户上送的该地址,失落败的交易银联不会发送后台关照
//后台关照参数详见open.unionpay.com帮助中央 下载 产品接口规范 网关支付产品接口规范 消费交易 商户关照
//把稳:
// 1.需设置为外网能访问,否则收不到关照
// 2.http https均可
// 3.收单后台关照后须要10秒内返回http200或302状态码
// 4.如果银联关照做事器发送关照后10秒内未收到返回状态码或者应答码非http200,那么银联会间隔一段韶光再次发送。统共发送5次,每次的间隔韶光为0,1,2,4分钟。
// 5.后台关照地址如果上送了带有?的参数,例如:http://abc/web?a=b&c=d 在后台关照处理程序验证署名之前须要编写逻辑将这些字段去掉再验签,否则将会验签失落败
contentData.put("backUrl", DemoBase.backUrl);
// 订单超时时间。
// 超过此韶光后,除网银交易外,其他交易银联系统会谢绝受理,提示超时。 跳转银行网银交易如果超时后交易成功,会自动退款,大约5个事情日金额返还到持卡人账户。
// 此韶光建议取支付时的北京韶光加15分钟。
// 超过超时时间调查询接口应答origRespCode不是A6或者00的就可以判断为失落败。
contentData.put("payTimeout", new SimpleDateFormat("yyyyMMddHHmmss").format(new Date().getTime() + 15 60 1000));
/要求参数设置完毕,以下对要求参数进行署名并天生html表单,将表单写入浏览器跳转打开银联页面/
Map<String, String> reqData = AcpService.sign(contentData,DemoBase.encoding); //报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置精确即可。
String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl(); //获取要求银联的前台地址:对应属性文件acp_sdk.properties文件中的acpsdk.frontTransUrl
String html = AcpService.createAutoFormHtml(requestFrontUrl,reqData,DemoBase.encoding); //天生自动跳转的Html表单
LogUtil.writeLog("打印要求HTML,此为要求报文,为联调排查问题的依据:"+html);
//将天生的html写到浏览器中完成自动跳转打开银联支付页面;这里调用signData之后,将html写到浏览器跳转到银联页面之前均不能对html中的表单项的名称和值进行修正,如果修正会导致验签不通过
resp.getWriter().write(html);
}
/
前台关照
@param req
@param resp
@throws ServletException
@throws IOException
/
@ResponseBody
@RequestMapping(value="/fontNotify")
public void fontNotify(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String encoding = req.getParameter(SDKConstants.param_encoding);
// 获取银联关照做事器发送的后台关照参数
Map<String, String> reqParam = getAllRequestParam(req);
LogUtil.printRequestLog(reqParam);
Map<String, String> valideData = null;
if (null != reqParam && !reqParam.isEmpty()) {
Iterator<Map.Entry<String, String>> it = reqParam.entrySet().iterator();
valideData = new HashMap<String, String>(reqParam.size());
while (it.hasNext()) {
Map.Entry<String, String> e = it.next();
String key = (String) e.getKey();
String value = (String) e.getValue();
valideData.put(key, value);
}
}
log.info("===银联前台关照,要求参数:"+ JSON.toJSONString(valideData));
//主要!
验证署名前不要修正reqParam中的键值对的内容,否则会验签不过if (!AcpService.validate(valideData, encoding)) {
LogUtil.writeLog("验证署名结果[失落败].");
//验签失落败,需办理验签问题
} else {
//【注:为了安全验签成功才该当写商户的成功处理逻辑】交易成功,更新商户订单状态
LogUtil.writeLog("验证署名结果[成功],暂不处理详细业务");
/ 交易类型./
String txnType = valideData.get("txnType");
/ 接入类型,商户接入填0 ,不需修正(0:直连商户, 1: 收单机构 2:平台商户)/
String accessType = valideData.get("accessType");
/ 业务类型./
String bizType = valideData.get("bizType");
/ 应答码信息./
String respMsg = valideData.get("respMsg");
/ 署名方法./
String signMethod = valideData.get("signMethod");//署名方法
/ 署名公钥证书/
//String signPubKeyCert = valideData.get("signPubKeyCert");
/ 版本号./
String version = valideData.get("version");
//开通交易
if("79".equals(txnType)){
/ 持卡人信息./
String customerInfo = valideData.get("customerInfo");
/ 发卡机构代码./
String issInsCode = valideData.get("issInsCode");
/ 银行卡号./
String accNo = valideData.get("accNo");
/ token信息./
String tokenPayData = valideData.get("tokenPayData");
String phoneNo="";
if(null!=customerInfo){
Map<String,String> map = AcpService.parseCustomerInfo(customerInfo, "UTF-8");
log.info("customerInfo明文:"+map);
phoneNo = map.get("phoneNo");
}
//如果是配置了敏感信息加密,如果须要获取卡号的明文,可以按以下方法解密卡号
if(null!=accNo){
//返回的是银行卡号后四位
accNo = AcpService.decryptData(accNo, "UTF-8");
log.info("accNo明文:"+accNo);
}
if(null!=tokenPayData){
Map<String,String> tokenPayDataMap = SDKUtil.parseQString(tokenPayData.substring(1, tokenPayData.length() - 1));
log.info("tokenPayDataMap明文:"+tokenPayDataMap);
String token = tokenPayDataMap.get("token");//这样取
log.info("token值:"+token);
//处理自己的业务
}
}
}
//返回给银联做事器http 200 状态码
resp.getWriter().print("200");
}
/
获取要求参数中所有的信息
当商户上送frontUrl或backUrl地址中带有参数信息的时候,
这种办法会将url地址中的参数读到map中,会导多出来这些信息从而致验签失落败,这个时候可以自行修正过滤掉url中的参数或者利用getAllRequestParamStream方法。
@param request
@return
/
public static Map<String, String> getAllRequestParam(
final HttpServletRequest request) {
Map<String, String> res = new HashMap<String, String>();
Enumeration<?> temp = request.getParameterNames();
if (null != temp) {
while (temp.hasMoreElements()) {
String en = (String) temp.nextElement();
String value = request.getParameter(en);
res.put(en, value);
// 在报文上送时,如果字段的值为空,则不上送<下面的处理为在获取所有参数数据时,判断若值为空,则删除这个字段>
if (res.get(en) == null || "".equals(res.get(en))) {
res.remove(en);
}
}
}
return res;
}
}
个中涉及到的公共类DemoBase、AcpService、SDKConfig、SDKConstants、SDKUtil 等直接用官方供应的demo中的示例,demo下载地址:无跳转支付
银联代扣支付:
private static String merId = "填写商户id";
private static String trId ="99988877766 ";//生产环境由业务分配,测试环境可以利用99988877766
/
银联代扣
@param orderId 订单编号
@param transAmount 订单金额,单位分
@param token 支付账号
@return
/
public void unionpayWithhold(String orderId, String transAmount, String token) {
Map<String, String> contentData = new HashMap<String, String>();
/银联全渠道系统,产品参数,除了encoding自行选择外其他不需修正/
contentData.put("version", DemoBase.version); //版本号
contentData.put("encoding", DemoBase.encoding); //字符集编码 可以利用UTF-8,GBK两种办法
contentData.put("signMethod", SDKConfig.getConfig().getSignMethod()); //署名方法
contentData.put("txnType", "01"); //交易类型 01-消费
contentData.put("txnSubType", "01"); //交易子类型 01-消费
contentData.put("bizType", "000902"); //业务类型 Token支付
contentData.put("channelType", "07"); //渠道类型07-PC
/商户接入参数/
contentData.put("merId", merId); //商户号码(本商户号码仅做为测试调通交易利用,该商户号配置了须要对敏感信息加密)测试时请改本钱身申请的商户号,【自己注册的测试777开头的商户号不支持代收产品】
contentData.put("accessType", "0"); //接入类型,商户接入固定填0,不需修正
contentData.put("orderId", orderId); //商户订单号,如上送短信验证码,请填写获取验证码时一样的orderId,此处默认取demo演示页面通报的参数
contentData.put("txnTime", DemoBase.getCurrentTime()); //订单发送韶光,如上送短信验证码,请填写获取验证码时一样的txnTime,此处默认取demo演示页面通报的参数
contentData.put("currencyCode", "156"); //交易币种(境内商户一样平常是156 公民币)
contentData.put("txnAmt", transAmount); //交易金额,单位分,如上送短信验证码,请填写获取验证码时一样的txnAmt,此处默认取demo演示页面通报的参数
contentData.put("accType", "01"); //账号类型
//后台关照地址(需设置为【外网】能访问 http https均可),支付成功后银联会自动将异步关照报文post到商户上送的该地址,失落败的交易银联不会发送后台关照
//后台关照参数详见open.unionpay.com帮助中央 下载 产品接口规范 代收产品接口规范 代收交易 商户关照
//把稳:1.需设置为外网能访问,否则收不到关照
// 2.http https均可
// 3.收单后台关照后须要10秒内返回http200或302状态码
// 4.如果银联关照做事器发送关照后10秒内未收到返回状态码或者应答码非http200,那么银联会间隔一段韶光再次发送。统共发送5次,每次的间隔韶光为0,1,2,4分钟。
// 5.后台关照地址如果上送了带有?的参数,例如:http://abc/web?a=b&c=d 在后台关照处理程序验证署名之前须要编写逻辑将这些字段去掉再验签,否则将会验签失落败
contentData.put("backUrl", DemoBase.backUrl);
//消费:token号(从前台开通的后台关照中获取或者后台开通的返回报文中获取),验证码看业务配置(默认要短信验证码)。
contentData.put("tokenPayData", "{token="+token+"&trId="+trId+"}");
Map<String,String> customerInfoMap = new HashMap<String,String>();
customerInfoMap.put("smsCode", "111111"); //短信验证码
//customerInfoMap不送pin的话 该方法可以不送 卡号
String customerInfoStr = AcpService.getCustomerInfo(customerInfoMap,null,DemoBase.encoding);
contentData.put("customerInfo", customerInfoStr);
/对要求参数进行署名并发送http post要求,吸收同步应答报文/
Map<String, String> reqData = AcpService.sign(contentData,DemoBase.encoding);//报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置精确即可。
String requestBackUrl = SDKConfig.getConfig().getBackRequestUrl(); //交易要求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrl
Map<String, String> rspData = AcpService.post(reqData,requestBackUrl,DemoBase.encoding);//发送要求报文并接管同步应答(默认连接超时时间30秒,读取返回结果超时时间30秒);这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修正,如果修正会导致验签不通过
/对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->/
//应答码规范参考open.unionpay.com帮助中央 下载 产品接口规范 《平台接入接口规范-第5部分-附录》
StringBuffer parseStr = new StringBuffer("");
if(!rspData.isEmpty()){
if(AcpService.validate(rspData, DemoBase.encoding)){
LogUtil.writeLog("验证署名成功");
String respCode = rspData.get("respCode") ;
if(("00").equals(respCode)){
//交易已受理(不代表交易已成功),等待吸收后台关照更新订单状态,也可以主动发起 查询交易确定交易状态。
}else if(("03").equals(respCode)|| ("04").equals(respCode)|| ("05").equals(respCode)){
//后续需发起交易状态查询交易确定交易状态
}else{
//其他应答码为失落败请排查缘故原由
}
}else{
LogUtil.writeErrorLog("验证署名失落败");
//TODO 检讨验证署名失落败的缘故原由
}
}else{
//未返回精确的http状态
LogUtil.writeErrorLog("未获取到返回报文或返回http状态码非200");
}
}
(2)银联扫码支付
银联扫码支付与支付宝扫码支付类似,先要加载银联扫码支付配置文件acp_sdk.properties
private static String merId = "填写商户id";
static {
SDKConfig.getConfig().loadPropertiesFromSrc();
}
/
天生银联二维码,预创建订单
@return
@throws Exception
/
@RequestMapping(value="/tradePrecreate", method = RequestMethod.POST)
@ResponseBody
public void toBankPrecreate(PayWay payWay) throws Exception {
Map<String, String> contentData = new HashMap<String, String>();
/银联全渠道系统,产品参数,除了encoding自行选择外其他不需修正/
contentData.put("version", DemoBase.version); //版本号 全渠道默认值
contentData.put("encoding", DemoBase.encoding); //字符集编码 可以利用UTF-8,GBK两种办法
contentData.put("signMethod", SDKConfig.getConfig().getSignMethod()); //署名方法
contentData.put("txnType", "01"); //交易类型 01:消费
contentData.put("txnSubType", "07"); //交易子类 07:申请消费二维码
contentData.put("bizType", "000000"); //填写000000
contentData.put("channelType", "08"); //渠道类型 08手机
/商户接入参数/
contentData.put("merId", merId); //商户号码,请改本钱身申请的商户号或者open上注册得来的777商户号测试
contentData.put("accessType", "0"); //接入类型,商户接入填0 ,不需修正(0:直连商户, 1: 收单机构 2:平台商户)
String orderId = DemoBase.getOrderId();
contentData.put("orderId", orderId); //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则
contentData.put("txnTime", DemoBase.getCurrentTime());
contentData.put("txnAmt", String.valueOf(payWay.getAmount())); //交易金额 单位为分,不能带小数点
contentData.put("currencyCode", "156"); //境内商户固定 156 公民币
contentData.put("backUrl", DemoBase.backUrl);
/对要求参数进行署名并发送http post要求,吸收同步应答报文/
Map<String, String> reqData = AcpService.sign(contentData,DemoBase.encoding); //报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置精确即可。
String requestAppUrl = SDKConfig.getConfig().getBackRequestUrl(); //交易要求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrl
Map<String, String> rspData = AcpService.post(reqData,requestAppUrl,DemoBase.encoding); //发送要求报文并接管同步应答(默认连接超时时间30秒,读取返回结果超时时间30秒);这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修正,如果修正会导致验签不通过
/对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->/
JSONObject jsonObject = new JSONObject();
if (!rspData.isEmpty()) {
if(AcpService.validate(rspData, DemoBase.encoding)){
LogUtil.writeLog("验证署名成功");
String respCode = rspData.get("respCode");
if (("00").equals(respCode)) {
String path="D://unionpay";
File folder = new File(path);
if (!folder.exists()) {
folder.setWritable(true);
folder.mkdirs();
}
//根据web订单号,天生路径,天生二维码
String qrPath = String.format(path + "/qr-%s.png", orderId);
String qrCode =rspData.get("qrCode");
ZxingUtils.getQRCodeImge(qrCode, 256, qrPath);
} else {
//其他应答码为失落败请排查缘故原由或做失落败处理
}
} else {
//验证署名失落败
}
} else {
//未返回精确的http状态
}
}
个中涉及到的公共类DemoBase、AcpService、SDKConfig 直接用官方供应的demo中的示例,demo下载地址:在线网关支付
(3)银联闪付
a.客户选择云闪付支付,提交订单给商户后端,后端向银联后端要求tn(流水号);
b.商户后端要求到tn,返回给用户的客户端;
c.客户端将tn,schema,viewController和mode传入到银联SDK中,唤起云闪付app;
d.云闪付返回用户客户端,将支付结果传给客户端,同时商户后端也能收到银联后真个支付结果;
e.云闪付的支付结果最好以商户后端结果为准。