一、微信支付

1、业务平台介绍:

(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="/tradePrecreate", method = RequestMethod.POST)@ResponseBodypublic 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);  //公众账号IDparamMap.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支付//第二步,签名,参数转xmlString 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 blocke.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 {// 执行getMethodint 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;//操作人idprivate 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");// // 支付完成时间,格式为yyyyMMddHHmmssString 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_IDString APP_ID = "商户APP_ID";// 商户RSA 私钥String APP_PRIVATE_KEY = "商户RSA私钥";// 请求方式 jsonString FORMAT = "json";// 编码格式,目前只支持UTF-8String 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 ";//生产环境由业务分配,测试环境可以使用99988877766static {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");//生产环境由业务分配,测试环境可以使用99988877766contentData.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.frontTransUrlString 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.backTransUrlMap<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"); //填写000000contentData.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.backTransUrlMap<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.云闪付的支付结果最好以商户后端结果为准。

Java对接微信、支付宝、银联第三方支付相关推荐

  1. 海关179对接微信/支付宝/通联支付及报关

    海关179查询时,需要上报四个参数 initalRequest:原始请求,跨境电商平台企业向支付企业发送的原始信息 initalResponse:原始响应,支付企业向跨境电商平台企业反馈的原始信息 p ...

  2. 个人免签支付如何对接微信支付宝?

    个人开发者个人站长在使用用是离不开支付渠道的,但如果用官方的支付接口,还需要准备企业资质等,大大增加了开发流程和时间成本,而个人免签支付就是一个很不错的选择,所以我们今天就来说说怎么用个人免签支付对接 ...

  3. Java对接微信支付实现微信APP支付

    Java对接微信实现微信APP支付 之前对接过第三方的支付方式,也有接入微信jsapi的支付方式,这次项目需求要求对接微信APP支付,找了很多,几乎都没有最新版的微信支付v3的对接相关的详细博客,真的 ...

  4. java对接微信支付收不到支付通知问题(亲身实践)

    问题描述: 用java对接微信支付时,统一下单接口正常.但是用户扫码付款成功后,设置用于回调的notify_url对应的接口并没有收到请求(这个url测试过,是正常的且外网能访问的). 由于官方文档没 ...

  5. Java对接微信支付(完整全流程)

    Java对接微信支付及支付回调通知的全流程 一.所用框架.对接微信支付我们技术组用的是payment框架,因为该框架已整合springboot因此很方便快捷 <dependency>< ...

  6. java实现微信支付宝等多个支付平台合一的二维码支付(SSM框架)

    点击上方[全栈开发者社区]→右上角[...]→[设为星标⭐] ♪ 点击上方绿标 收听java实现微信支付宝等多个支付平台合一的二维码支付(SSM框架) 整体思路: 实现微信支付宝等支付平台合多为一的二 ...

  7. 支付宝开发 ——第三方支付

    支付宝开发 第三方支付 在线支付 在线支付是指卖方与买方通过因特网上的电子商务网站进行交易时,银行为其提供网上资金结算服务的一种业务.它为企业和个人提供了一个安全.快捷.方便的电子商务应用环境和网上资 ...

  8. Java对接微信公众平台详解

    Java对接微信公众平台详解 1.公众平台概述 1.1 公众平台概述 1.2 入门指引 2.对接流程 2.1 接入概述 2.2 填写服务器配置 2.3 接口域名说明 2.4 获取Access toke ...

  9. 微信支付宝扫码支付聚合系统

    一.简介 微信支付宝扫码支付聚合系统,聚合了微信支付和支付宝的所有扫码支付模式.并支持微信支付服务商子商户模式.支持五种扫码技术. 二.主要功能 1. 微信扫码支付:包括扫码支付模式一.扫码支付模式二 ...

  10. Java接入微信native、jsapi支付

    Java接入微信native.jsapi支付 一.说明 本文示例使用的微信支付版本为V2版本,是旧版本,旧版本与新版本的接口不一,并不通用. 微信官方接口文档地址:https://pay.weixin ...

最新文章

  1. 第52章,bitmap图像处理(从零开始学android),第52章、Bitmap图像处理(从零开始学Android)...
  2. 为Delphi程序添加事件和事件处理器
  3. Python 学习之中的一个:在Mac OS X下基于Sublime Text搭建开发平台包括numpy,scipy
  4. 了解【Docker】从这里开始
  5. CF510C Fox And Names——拓扑排序练习
  6. 如何制作linux系统硬盘,教你制作Linux操作系统的Boot/Root盘
  7. 【转】XP/2000无法使用“缩略图查看”、右键无“设置桌面背景”选项问题详解...
  8. Docker - 实战TLS加密通讯
  9. java jmap mat_利用jmap和MAT等工具查看JVM运行时堆内存
  10. performancepoint里面建立数据源的时候,总是发生以下的报警(转的)我也遇到了这个问题...
  11. pyside6(1):Qt 资源系统和qrc文件使用
  12. gms2游戏移植linux,GMS卡刷包制作
  13. 海康GB28181协议服务器怎么配置,GB/T28181国标流媒体服务器在海康平台上进行级联配置步骤总结...
  14. socket函数send和recv函数
  15. 3D 旋转立方体的完整代码
  16. 高频信号发生器设计—电容三点式振荡电路
  17. 树莓派安装Ubuntu server无屏幕开机自动连接WIFI
  18. 计算机固态和机械什么意思,固态硬盘和机械硬盘有什么不同?详解固态硬盘和机械硬盘...
  19. 2021牛年大吉,红包敬上
  20. 这样解读PID算法,有点意思~

热门文章

  1. 数据分析入门——深入浅出数据分析
  2. 如何在msdn下载纯净文件
  3. 御剑仙侠服务器维护,御剑修仙放置挂机文字游戏
  4. 在python语言中、写文件的操作是_Python语言之详解文件操作
  5. 【国产化电脑】如何有效避免违规外联
  6. 软考数据库系统工程师教程知识点总结
  7. hadoop 学习心得
  8. codeproject的文章【一】
  9. java开发实例大全_java编程实例大全100例
  10. Cplex20.1版本bin包Linux安装过程