-
支付宝支付——统一wap和支付宝钱包回调
2016-12-02 13:48:51用过支付宝支付的都知道,支付宝支付有两个回调,一个是wap支付回调,一个是支付宝钱包回调,简单来说就是,一个是有支付宝app的回调,一个是没有安装支付宝的回调 /** * 处理钱包或者独立快捷app支付跳回商户app...一、前言
-
用过支付宝支付的都知道,支付宝支付有两个回调,一个是wap支付回调,一个是支付宝钱包回调,简单来说就是,一个是有支付宝app的回调,一个是没有安装支付宝的回调
/** * 处理钱包或者独立快捷app支付跳回商户app携带的支付结果Url * * @param resultUrl 支付结果url * @param completionBlock 支付结果回调 */ - (void)processOrderWithPaymentResult:(NSURL *)resultUrl standbyCallback:(CompletionBlock)completionBlock;
/** * 支付接口 * * @param orderStr 订单信息 * @param schemeStr 调用支付的app注册在info.plist中的scheme * @param compltionBlock 支付结果回调Block,用于wap支付结果回调(非跳转钱包支付) */ - (void)payOrder:(NSString *)orderStr fromScheme:(NSString *)schemeStr callback:(CompletionBlock)completionBlock;
二、问题
-
wap支付回调是在发起支付的方法里面,而支付宝钱包的回调是在AppDelegate里面实现的,那么问题就来了,如果我有支付宝app~我在当前的页面上发起支付,但回调却不在当前页面
三、解决方案
-
使用通知,在appDelegate的支付回调中发起一个通知,然后任意页面监听这个通知就可以处理
- 优点:
-
-
踩坑: 微信小程序支付流程(统一下单, 支付回调)
2018-07-30 16:15:55众所周知,微信小程序目前只能使用微信支付,而且微信小程序支付相对于app支付,h5支付都要简单一些,但是该支付文档对java这语言是非常不友好的,居然没有demo,网上虽说有很多博客,但是找了好多都是跑不通,乱七八糟的很...公司最近开发小程序,涉及到支付功能. 现在支付功能已经做完,特此记录一下自己踩坑经验:
众所周知,微信小程序目前只能使用微信支付, 而且微信小程序支付相对于app支付,h5支付都要简单一些,但是该支付文档对java这语言是非常不友好的,居然没有demo, 网上虽说有很多博客,但是找了好多都是跑不通, 乱七八糟的很多都跑不通, 以下 代码不是自己写的,大多都是这儿抄一点哪儿抄一顿,但是能跑通,亲测没毛病,如果有毛病可以留言交流.废话不多说,先说准备工作!对依赖不清楚或者回调失败的请访问: https://www.keppel.fun/articles/2019/03/06/1551844306979.html
并注意该链接页面最下面的说明
目录
1. 登录微信公众平台, 开通微信支付功能
这是准备工作的第一步, 确保小程序对应的支付功能已经开启
2. 登录微信商户平台
该步骤需要获取两个参数, 一个是商户号, 一个是支付秘钥, 如下图所示注意秘钥自己要保护好,相当于支付密码,每次签名都需要该参数, 该参数只能设置的时候看得见,其余的时候是没法看得见.所以要记好了!
3. 准备完毕, 小程序代码
微信小程序发起支付的请求到开发者服务器, 后台预下单返回一个prepay_id, 还有其他乱七八糟的参数.然后微信小程序调用支付方法进行支付, 最后微信服务器会发起回调函数到开发者服务器.先贴一段微信的测试代码,微信统一下单必须要openId参数:
//index.js //获取应用实例 var app = getApp() Page({ data: { motto: 'Hello World', userInfo: {} }, onLoad: function () { console.log('onLoad') }, // payoff: function(e){ var that = this; console.log('zhixingle') wx.login({ success: function(res) { wx.getUserInfo({ success: function(re) { that.getAddress(re,res.code); }, fail: function () { console.log('失败了') } }) } }); }, // 获取用户的收货地址 getAddress: function (re,code) { var that = this; wx.chooseAddress({ success: function (add) { that.getOpenId(re, add.userName,add.provinceName,add.cityName,add.countyName, add.detailInfo,add.telNumber,code); } }) }, //获取openid getOpenId: function(re, userName, provinceName, cityName, countryName, detailInfo, telNumber,code){ var that = this; wx.request({ // 开发者服务器地址 url: 'http://1d7a111.iok.la:10534/api/getUnionId', method: 'POST', header: { 'content-type': 'application/x-www-form-urlencoded' }, data: { encryptedData: re.encryptedData, iv: re.iv, code: code }, success: function(res) { var openId = res.data.userInfo.openId; var unionId = res.data.userInfo.unionId; // console.log('res>', openId); that.loginPlatform(userName, provinceName, cityName, countryName, detailInfo, telNumber,unionId, openId); } }) }, // 登录 loginPlatform: function (userName, provinceName, cityName, countryName, detailInfo, telNumber,unionId, openId) { var that = this; wx.request({ url: 'http://1d7a111.iok.la:12534/api/login/platform', method: 'POST', header: { 'content-type': 'application/json' }, data: { unionId: unionId, platform: 'WECHAT' }, success: function(res) { console.log('res', res.data.token); var token = res.data.token; that.xiadan(userName, provinceName, cityName, countryName, detailInfo, telNumber,openId, token); } }) }, //下单 xiadan: function (userName, provinceName, cityName, countryName, detailInfo, telNumber,openId, token){ console.log('openId', openId); console.log('token', token); var that = this; wx.request({ url: 'http://1d7a01111.iok.la:12534/api/v1/weixin/payment', method: 'POST', header: { 'content-type': 'application/json', 'authorization': 'Bearer ' + token }, data: { openId: openId, cfId: '5b32f553aff8ca411f839eb4', planId: '5b32f5cbaff8ca111f839eb7', quantity: 2, orderRemark: '说点啥备注好?', name: userName, phone: telNumber, province: provinceName, city: cityName, district: countryName, address: detailInfo }, success: function(res) { console.log('res>>>',res) that.requestPayment(res.data.data); } }) }, //申请支付 requestPayment: function(obj){ console.log('obj>>',obj) wx.requestPayment({ 'timeStamp': obj.timeStamp, 'nonceStr': obj.nonceStr, 'package': obj.package, 'signType': obj.signType, 'paySign': obj.paySign, 'success':function(res){ }, 'fail':function(res){ } }) } })
因为我这边需求是要获取微信用户的收货地址,然后unionId是登录获取token,openId是支付的必须参数,所以代码写的有点乱,不是专业的4. 后端统一下单和支付回调接口
import com.thoughtworks.xstream.XStream; import org.apache.log4j.Logger; import org.dom4j.DocumentException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map; /** * @Author: YFei * @Date: Created in 18:10 2018/7/25 * @Description: */ @RestController @RequestMapping(value = "/api/v1") public class WXAppletPayCtrl { private static final long serialVersionUID = 1L; private static final Logger L = Logger.getLogger(WXAppletPayCtrl.class); @Value("${wxapplet.config.weixinpay.notifyurl}") private String notify_url; //交易类型 private final String trade_type = "JSAPI"; //统一下单API接口链接 private final String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; /** * * @param request * @return * @throws UnsupportedEncodingException * @throws DocumentException */ @RequestMapping( value = "/weixin/payment", method = RequestMethod.POST ) public Map payment(@Valid @RequestBody NewWXOrderRequest request, HttpServletRequest httpServletRequest) { Map map = new HashMap(); String money = "10"; String title = "商品名字"; try { OrderInfo order = new OrderInfo(); order.setAppid(Configure.getAppID()); order.setMch_id(Configure.getMch_id()); order.setNonce_str(RandomStringGenerator.getRandomStringByLength(32)); order.setBody(title); order.setOut_trade_no(RandomStringGenerator.getRandomStringByLength(32)); order.setTotal_fee(Integer.parseInt(money)); // 该金钱其实10 是 0.1元 order.setSpbill_create_ip("127.0.0.1"); order.setNotify_url(notify_url); order.setTrade_type(trade_type); order.setOpenid(request.getOpenId()); order.setSign_type("MD5"); //生成签名 String sign = Signature.getSign(order); order.setSign(sign); String result = HttpRequest.sendPost(url, order); System.out.println(result); XStream xStream = new XStream(); xStream.alias("xml", OrderReturnInfo.class); OrderReturnInfo returnInfo = (OrderReturnInfo)xStream.fromXML(result); // 二次签名 if ("SUCCESS".equals(returnInfo.getReturn_code()) && returnInfo.getReturn_code().equals(returnInfo.getResult_code())) { SignInfo signInfo = new SignInfo(); signInfo.setAppId(Configure.getAppID()); long time = System.currentTimeMillis()/1000; signInfo.setTimeStamp(String.valueOf(time)); signInfo.setNonceStr(RandomStringGenerator.getRandomStringByLength(32)); signInfo.setRepay_id("prepay_id="+returnInfo.getPrepay_id()); signInfo.setSignType("MD5"); //生成签名 String sign1 = Signature.getSign(signInfo); Map payInfo = new HashMap(); payInfo.put("timeStamp", signInfo.getTimeStamp()); payInfo.put("nonceStr", signInfo.getNonceStr()); payInfo.put("package", signInfo.getRepay_id()); payInfo.put("signType", signInfo.getSignType()); payInfo.put("paySign", sign1); map.put("status", 200); map.put("msg", "统一下单成功!"); map.put("data", payInfo); // 此处可以写唤起支付前的业务逻辑 // 业务逻辑结束 return map; } map.put("status", 500); map.put("msg", "统一下单失败!"); map.put("data", null); return map; } catch (Exception e) { e.printStackTrace(); L.error("-------", e); } return null; } /** * 微信小程序支付成功回调函数 * @param request * @param response * @throws Exception */ @RequestMapping(value = "/weixin/callback") public void wxNotify(HttpServletRequest request,HttpServletResponse response) throws Exception{ BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream)request.getInputStream())); String line = null; StringBuilder sb = new StringBuilder(); while((line = br.readLine()) != null){ sb.append(line); } br.close(); //sb为微信返回的xml String notityXml = sb.toString(); String resXml = ""; System.out.println("接收到的报文:" + notityXml); Map map = PayUtil.doXMLParse(notityXml); String returnCode = (String) map.get("return_code"); if("SUCCESS".equals(returnCode)){ //验证签名是否正确 Map<String, String> validParams = PayUtil.paraFilter(map); //回调验签时需要去除sign和空值参数 String validStr = PayUtil.createLinkString(validParams);//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 String sign = PayUtil.sign(validStr, Configure.getKey(), "utf-8").toUpperCase();//拼装生成服务器端验证的签名 // 因为微信回调会有八次之多,所以当第一次回调成功了,那么我们就不再执行逻辑了 //根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等 if(sign.equals(map.get("sign"))){ /**此处添加自己的业务逻辑代码start**/ // bla bla bla.... /**此处添加自己的业务逻辑代码end**/ //通知微信服务器已经支付成功 resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; } else { System.out.println("微信支付回调失败!签名不一致"); } }else{ resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> "; } System.out.println(resXml); System.out.println("微信支付回调数据结束"); BufferedOutputStream out = new BufferedOutputStream( response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); } }
以上是统一下单的接口和微信支付成功的回调接口, 注意的如果springboot项目中的springsecurity,一定要注意放行回调地址, 否则回调会失败.5. 相关配置类以及工具类
先上一下签名类:import com.thoughtworks.xstream.annotations.XStreamAlias; import org.apache.log4j.Logger; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Map; /** * 签名 * @author zuoliangzhu * */ public class Signature { private static final Logger L = Logger.getLogger(Signature.class); /** * 签名算法 * @param o 要参与签名的数据对象 * @return 签名 * @throws IllegalAccessException */ public static String getSign(Object o) throws IllegalAccessException { ArrayList<String> list = new ArrayList<String>(); Class cls = o.getClass(); Field[] fields = cls.getDeclaredFields(); for (Field f : fields) { f.setAccessible(true); if (f.get(o) != null && f.get(o) != "") { String name = f.getName(); XStreamAlias anno = f.getAnnotation(XStreamAlias.class); if(anno != null) name = anno.value(); list.add(name + "=" + f.get(o) + "&"); } } int size = list.size(); String [] arrayToSort = list.toArray(new String[size]); Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER); StringBuilder sb = new StringBuilder(); for(int i = 0; i < size; i ++) { sb.append(arrayToSort[i]); } String result = sb.toString(); result += "key=" + Configure.getKey(); System.out.println("签名数据:"+result); result = MD5.MD5Encode(result).toUpperCase(); return result; } public static String getSign(Map<String,Object> map){ ArrayList<String> list = new ArrayList<String>(); for(Map.Entry<String,Object> entry:map.entrySet()){ if(entry.getValue()!=""){ list.add(entry.getKey() + "=" + entry.getValue() + "&"); } } int size = list.size(); String [] arrayToSort = list.toArray(new String[size]); Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER); StringBuilder sb = new StringBuilder(); for(int i = 0; i < size; i ++) { sb.append(arrayToSort[i]); } String result = sb.toString(); result += "key=" + Configure.getKey(); //Util.log("Sign Before MD5:" + result); result = MD5.MD5Encode(result).toUpperCase(); //Util.log("Sign Result:" + result); return result; } }
然后配置类:public class Configure { // 商户支付秘钥 private static String key = "xxxxxxNOBVmszxxxxxxxxxxxxxxxxxxx"; //小程序ID private static String appID = "wx42ebbFFFFFFFFFF"; //商户号 private static String mch_id = "1499111112"; // 小程序的secret private static String secret = "xxxxxxxxxxxxxxxxxxx"; public static String getSecret() { return secret; } public static void setSecret(String secret) { Configure.secret = secret; } public static String getKey() { return key; } public static void setKey(String key) { Configure.key = key; } public static String getAppID() { return appID; } public static void setAppID(String appID) { Configure.appID = appID; } public static String getMch_id() { return mch_id; } public static void setMch_id(String mch_id) { Configure.mch_id = mch_id; } }
然后工具类:
import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import javax.net.ssl.X509TrustManager; import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; public class HttpRequest { //连接超时时间,默认10秒 private static final int socketTimeout = 10000; //传输超时时间,默认30秒 private static final int connectTimeout = 30000; /** * post请求 * @throws IOException * @throws ClientProtocolException * @throws NoSuchAlgorithmException * @throws KeyStoreException * @throws KeyManagementException * @throws UnrecoverableKeyException */ public static String sendPost(String url, Object xmlObj) throws ClientProtocolException, IOException, UnrecoverableKeyException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException { HttpPost httpPost = new HttpPost(url); //解决XStream对出现双下划线的bug XStream xStreamForRequestPostData = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_"))); xStreamForRequestPostData.alias("xml", xmlObj.getClass()); //将要提交给API的数据对象转换成XML格式数据Post给API String postDataXML = xStreamForRequestPostData.toXML(xmlObj); //得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别 StringEntity postEntity = new StringEntity(postDataXML, "UTF-8"); httpPost.addHeader("Content-Type", "text/xml"); httpPost.setEntity(postEntity); //设置请求器的配置 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build(); httpPost.setConfig(requestConfig); HttpClient httpClient = HttpClients.createDefault(); HttpResponse response = httpClient.execute(httpPost); HttpEntity entity = response.getEntity(); String result = EntityUtils.toString(entity, "UTF-8"); return result; } /** * 自定义证书管理器,信任所有证书 * @author pc * */ public static class MyX509TrustManager implements X509TrustManager { @Override public void checkClientTrusted( java.security.cert.X509Certificate[] arg0, String arg1) throws CertificateException { // TODO Auto-generated method stub } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] arg0, String arg1) throws CertificateException { // TODO Auto-generated method stub } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { // TODO Auto-generated method stub return null; } } }
MD5加密工具:
import java.security.MessageDigest; /** * User: rizenguo * Date: 2014/10/23 * Time: 15:43 */ public class MD5 { private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; /** * 转换字节数组为16进制字串 * @param b 字节数组 * @return 16进制字串 */ public static String byteArrayToHexString(byte[] b) { StringBuilder resultSb = new StringBuilder(); for (byte aB : b) { resultSb.append(byteToHexString(aB)); } return resultSb.toString(); } /** * 转换byte到16进制 * @param b 要转换的byte * @return 16进制格式 */ private static String byteToHexString(byte b) { int n = b; if (n < 0) { n = 256 + n; } int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } /** * MD5编码 * @param origin 原始字符串 * @return 经过MD5加密之后的结果 */ public static String MD5Encode(String origin) { String resultString = null; try { resultString = origin; MessageDigest md = MessageDigest.getInstance("MD5"); resultString = byteArrayToHexString(md.digest(resultString.getBytes())); } catch (Exception e) { e.printStackTrace(); } return resultString; } }
预订单实体类:package com.xxxx.common.utils.wxUtil; /** * 预订单 * * @author zuoliangzhu * */ public class OrderInfo { private String appid;// 小程序ID private String mch_id;// 商户号 private String nonce_str;// 随机字符串 private String sign_type;//签名类型 private String sign;// 签名 private String body;// 商品描述 private String out_trade_no;// 商户订单号 private int total_fee;// 标价金额 ,单位为分 private String spbill_create_ip;// 终端IP private String notify_url;// 通知地址 private String trade_type;// 交易类型 private String openid;//用户标识 public String getSign_type() { return sign_type; } public void setSign_type(String sign_type) { this.sign_type = sign_type; } public String getOpenid() { return openid; } public void setOpenid(String openid) { this.openid = openid; } public String getAppid() { return appid; } public void setAppid(String appid) { this.appid = appid; } public String getMch_id() { return mch_id; } public void setMch_id(String mch_id) { this.mch_id = mch_id; } public String getNonce_str() { return nonce_str; } public void setNonce_str(String nonce_str) { this.nonce_str = nonce_str; } public String getSign() { return sign; } public void setSign(String sign) { this.sign = sign; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public String getOut_trade_no() { return out_trade_no; } public void setOut_trade_no(String out_trade_no) { this.out_trade_no = out_trade_no; } public int getTotal_fee() { return total_fee; } public void setTotal_fee(int total_fee) { this.total_fee = total_fee; } public String getSpbill_create_ip() { return spbill_create_ip; } public void setSpbill_create_ip(String spbill_create_ip) { this.spbill_create_ip = spbill_create_ip; } public String getNotify_url() { return notify_url; } public void setNotify_url(String notify_url) { this.notify_url = notify_url; } public String getTrade_type() { return trade_type; } public void setTrade_type(String trade_type) { this.trade_type = trade_type; } }
订单返回消息实体类package com.xxxx.common.utils.wxUtil; public class OrderReturnInfo { private String return_code; private String return_msg; private String result_code; private String appid; private String mch_id; private String nonce_str; private String sign; private String prepay_id; private String trade_type; public String getReturn_code() { return return_code; } public void setReturn_code(String return_code) { this.return_code = return_code; } public String getReturn_msg() { return return_msg; } public void setReturn_msg(String return_msg) { this.return_msg = return_msg; } public String getResult_code() { return result_code; } public void setResult_code(String result_code) { this.result_code = result_code; } public String getAppid() { return appid; } public void setAppid(String appid) { this.appid = appid; } public String getMch_id() { return mch_id; } public void setMch_id(String mch_id) { this.mch_id = mch_id; } public String getNonce_str() { return nonce_str; } public void setNonce_str(String nonce_str) { this.nonce_str = nonce_str; } public String getSign() { return sign; } public void setSign(String sign) { this.sign = sign; } public String getPrepay_id() { return prepay_id; } public void setPrepay_id(String prepay_id) { this.prepay_id = prepay_id; } public String getTrade_type() { return trade_type; } public void setTrade_type(String trade_type) { this.trade_type = trade_type; } }
随机数生成工具类:import java.util.Random; /** * 随机字符串生成 * @author zuoliangzhu * */ public class RandomStringGenerator { /** * 获取一定长度的随机字符串 * @param length 指定字符串长度 * @return 一定长度的字符串 */ public static String getRandomStringByLength(int length) { String base = "abcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < length; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } }
签名实体类:import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 签名信息 * @author zuoliangzhu * */ public class SignInfo { private String appId;//小程序ID private String timeStamp;//时间戳 private String nonceStr;//随机串 @XStreamAlias("package") private String repay_id; private String signType;//签名方式 public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getTimeStamp() { return timeStamp; } public void setTimeStamp(String timeStamp) { this.timeStamp = timeStamp; } public String getNonceStr() { return nonceStr; } public void setNonceStr(String nonceStr) { this.nonceStr = nonceStr; } public String getRepay_id() { return repay_id; } public void setRepay_id(String repay_id) { this.repay_id = repay_id; } public String getSignType() { return signType; } public void setSignType(String signType) { this.signType = signType; } }
最后有一些乱七八糟的配置文件:import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.security.SignatureException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.codec.digest.DigestUtils; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; public class PayUtil { /** * 签名字符串 * @param text 需要签名的字符串 * @param key 密钥 * @param input_charset 编码格式 * @return 签名结果 */ public static String sign(String text, String key, String input_charset) { text = text + "&key=" + key; return DigestUtils.md5Hex(getContentBytes(text, input_charset)); } /** * 签名字符串 * @param text 需要签名的字符串 * @param sign 签名结果 * @param key 密钥 * @param input_charset 编码格式 * @return 签名结果 */ public static boolean verify(String text, String sign, String key, String input_charset) { text = text + key; String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset)); if (mysign.equals(sign)) { return true; } else { return false; } } /** * @param content * @param charset * @return * @throws SignatureException * @throws UnsupportedEncodingException */ public static byte[] getContentBytes(String content, String charset) { if (charset == null || "".equals(charset)) { return content.getBytes(); } try { return content.getBytes(charset); } catch (UnsupportedEncodingException e) { throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); } } private static boolean isValidChar(char ch) { if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) return true; if ((ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f)) return true;// 简体中文汉字编码 return false; } /** * 除去数组中的空值和签名参数 * @param sArray 签名参数组 * @return 去掉空值与签名参数后的新签名参数组 */ public static Map<String, String> paraFilter(Map<String, String> sArray) { Map<String, String> result = new HashMap<String, String>(); if (sArray == null || sArray.size() <= 0) { return result; } for (String key : sArray.keySet()) { String value = sArray.get(key); if (value == null || value.equals("") || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")) { continue; } result.put(key, value); } return result; } /** * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串 * @param params 需要排序并参与字符拼接的参数组 * @return 拼接后字符串 */ public static String createLinkString(Map<String, String> params) { List<String> keys = new ArrayList<String>(params.keySet()); Collections.sort(keys); String prestr = ""; for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); String value = params.get(key); if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符 prestr = prestr + key + "=" + value; } else { prestr = prestr + key + "=" + value + "&"; } } return prestr; } /** * * @param requestUrl 请求地址 * @param requestMethod 请求方法 * @param outputStr 参数 */ public static String httpRequest(String requestUrl,String requestMethod,String outputStr) { // 创建SSLContext StringBuffer buffer = null; try { URL url = new URL(requestUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod(requestMethod); conn.setDoOutput(true); conn.setDoInput(true); conn.connect(); //往服务器端写内容 if (null != outputStr) { OutputStream os = conn.getOutputStream(); os.write(outputStr.getBytes("utf-8")); os.close(); } // 读取服务器端返回的内容 InputStream is = conn.getInputStream(); InputStreamReader isr = new InputStreamReader(is, "utf-8"); BufferedReader br = new BufferedReader(isr); buffer = new StringBuffer(); String line = null; while ((line = br.readLine()) != null) { buffer.append(line); } br.close(); }catch(Exception e){ e.printStackTrace(); } return buffer.toString(); } public static String urlEncodeUTF8(String source){ String result=source; try { result=java.net.URLEncoder.encode(source, "UTF-8"); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; } /** * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 * @param strxml * @return * @throws JDOMException * @throws IOException */ public static Map doXMLParse(String strxml) throws Exception { if(null == strxml || "".equals(strxml)) { return null; } Map m = new HashMap(); InputStream in = String2Inputstream(strxml); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while(it.hasNext()) { Element e = (Element) it.next(); String k = e.getName(); String v = ""; List children = e.getChildren(); if(children.isEmpty()) { v = e.getTextNormalize(); } else { v = getChildrenText(children); } m.put(k, v); } //关闭流 in.close(); return m; } /** * 获取子结点的xml * @param children * @return String */ public static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if(!children.isEmpty()) { Iterator it = children.iterator(); while(it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<" + name + ">"); if(!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } public static InputStream String2Inputstream(String str) { return new ByteArrayInputStream(str.getBytes()); } }
好了,就这些了,应该没漏了.. 抄了不少朋友的代码,大同小异
6. 最后还有些jar包说明一下
这里使用gradle管理jar包, 如果是maven自行转换
// https://mvnrepository.com/artifact/org.codehaus.xfire/xfire-core compile group: 'org.codehaus.xfire', name: 'xfire-core', version: '1.2.6' // https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk16 compile group: 'org.bouncycastle', name: 'bcprov-jdk16', version: '1.46' // https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream compile group: 'com.thoughtworks.xstream', name: 'xstream', version: '1.4.7' // https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.2' compile 'org.jdom:jdom2:2.0.5' // compile("org.springframework.boot:spring-boot-starter-undertow") compile('org.springframework.boot:spring-boot-starter-security')
另外还有application.yml文件里面说明一下, 有一个回调地址
wxapplet.config.weixinpay.notifyurl: http://localhost:8080/api/v1/weixin/wxappletpaycallback
-
微信支付统一下单与支付成功回调
2020-06-01 17:10:583、等待微信后台回调我们自己的回调接口 微信后台 接口文档 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1 统一下单工具类 /** * @param appKey 商户平台设置的密钥key * @param appid ...微信下单流程
1、统一下单生成预支付订单,获取对应参数
2、使用对应参数进行二次签名,返回给小程序进行发起支付
3、等待微信后台回调我们自己的回调接口微信后台 接口文档
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1统一下单工具类
/** * @param appKey 商户平台设置的密钥key * @param appid 应用id * @param openid 微信openid * @param mch_id 商户号id * @param body 商品描述 * @param out_trade_no 商户订单号 * @param total_fee 金额,单位:分 * @param spbill_create_ip 终端ip * @param notify_url 通知地址 * @param trade_type 交易类型,小程序:JSAPI * @return * @throws Exception */ public static UnifiedOrderResponse unifiedorder(String appKey, String appid, String openid, String mch_id, String body, String out_trade_no, Integer total_fee, String spbill_create_ip, String notify_url, String trade_type) throws Exception { SortedMap<String, String> ps = new TreeMap(); ps.put("appid", appid); ps.put("openid", openid); ps.put("mch_id", mch_id); ps.put("nonce_str", EncryptionUtils.getMD5String(System.currentTimeMillis() + out_trade_no)); //随机字符串 ps.put("body", body); ps.put("out_trade_no", out_trade_no); ps.put("total_fee", String.valueOf(total_fee)); ps.put("spbill_create_ip", spbill_create_ip); ps.put("notify_url", notify_url); ps.put("trade_type", trade_type); String sign = sign(ps, wxApiKey); ps.put("sign", sign); String xml = XmlUtil.mapToXml(ps); log.debug("unifiedorder xml:" + xml); try { RequestBody requestBody = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded; charset=utf-8"), xml); Response execute = OkHttpUtil.getInstance().post("https://api.mch.weixin.qq.com/pay/unifiedorder";, (Map)null, requestBody); int code = execute.code(); if (code == 200) { String result = execute.body().string(); log.debug("unifiedorder result:" + result); UnifiedOrderResponse response = (UnifiedOrderResponse)XmlUtil.xmlToBeanByJAXB(UnifiedOrderResponse.class, result); if (response == null) { throw new Exception("wx统一下单结果未知,返回数据解析错误,返回数据为:" + result); } else if (response.getReturn_code().equals("SUCCESS")) { return response; } else { throw new Exception("wx统一下单通信失败,原因如下:" + result); } } else { throw new Exception("wx统一下单失败,http 访问结果不是 200,返回结果为:" + execute.body().string()); } } catch (IOException var18) { throw new Exception("wx统一下单失败,http 访问错误:" + var18.getMessage()); } }try { RequestBody requestBody = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded; charset=utf-8"), xml); Response execute = OkHttpUtil.getInstance().post("https://api.mch.weixin.qq.com/pay/unifiedorder";, (Map)null, requestBody); int code = execute.code(); if (code == 200) { String result = execute.body().string(); log.debug("unifiedorder result:" + result); UnifiedOrderResponse response = (UnifiedOrderResponse)XmlUtil.xmlToBeanByJAXB(UnifiedOrderResponse.class, result); if (response == null) { throw new Exception("wx统一下单结果未知,返回数据解析错误,返回数据为:" + result); } else if (response.getReturn_code().equals("SUCCESS")) { return response; } else { throw new Exception("wx统一下单通信失败,原因如下:" + result); } } else { throw new Exception("wx统一下单失败,http 访问结果不是 200,返回结果为:" + execute.body().string()); } } catch (IOException var18) { throw new Exception("wx统一下单失败,http 访问错误:" + var18.getMessage()); } }
获取用户ip
获取ip import javax.servlet.http.HttpServletRequest; public class IpAddress { /** * 获取用户真实IP地址,不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址, * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值 * * @return ip */ public static String getIpAddress(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { if (ip.indexOf(",") != -1) { ip = ip.split(",")[0]; } } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Real-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } }
UnifiedOrderResponse
// 具体参数对照微信 api @Data public class UnifiedOrderResponse { private String return_code; private String return_msg; private String result_code; private String err_code; private String err_code_des; private String appid; private String mch_id; private String device_info; private String nonce_str; private String sign; private String trade_type; private String prepay_id; private String code_url; }
请求成功后 需要进行二次签名
/** * 设置统一下单返回给前端的参数 二次签名 * * @param unifiedorder * @return */ public WxparamVO getWxparamVO(UnifiedOrderResponse unifiedorder, Orders order, Integer realPaymentPrice) { String body = order.getProductFullName().split(";")[0]; WxparamVO wxparamVO = new WxparamVO(); wxparamVO.setPrepayId(unifiedorder.getPrepay_id());// 获取预支付交易id wxparamVO.setBody(body); // 显示支付内容 wxparamVO.setNonce_str(unifiedorder.getNonce_str()); // 随机字符串 Long timeStamp = System.currentTimeMillis() / 1000; SortedMap<String, String> ps = new TreeMap(); ps.put("appId", unifiedorder.getAppid()); ps.put("nonceStr", unifiedorder.getNonce_str()); ps.put("package", "prepay_id=" + unifiedorder.getPrepay_id()); ps.put("signType", "MD5"); ps.put("timeStamp", String.valueOf(timeStamp)); wxparamVO.setSign(sign(ps, WX_API_KEY)); // 签名 wxparamVO.setOut_trade_no(order.getOrderId());//订单id wxparamVO.setTotal_fee(realPaymentPrice);//支付价格 wxparamVO.setTimeStamp(String.valueOf(timeStamp)); log.info("正在请求支付,支付信息:order_id" + order.getOrderId() + " 支付金额:" + order.getRealPaymentPrice()); return wxparamVO; }
WxparamVO
@Data public class WxparamVO { @ApiModelProperty(value = "预支付交易会话id") private String prepayId; /*随机字符串,长度要求在32位以内*/ @ApiModelProperty(value = "随机字符串")`在这里插入代码片` private String nonce_str; /*签名*/ @ApiModelProperty(value = "签名") private String sign; /*商品描述*/ @ApiModelProperty(value = "商品描述") private String body; /*商户订单号*/ @ApiModelProperty(value = "商户订单号 也就是 订单id orderId") @JsonSerialize(using = ToStringSerializer.class) private Long out_trade_no; /*标价金额 默认分*/ @ApiModelProperty(value = "标价金额 默认分") private Integer total_fee; /*标价金额 默认分*/ @ApiModelProperty(value = "时间戳") private String timeStamp; }
微信回调通知
回调通知需要进行校验签名!!!
//controller 类 @ApiOperation(value = "微信支付成功回调", notes = "微信支付成功异步回调,修改订单信息") @PostMapping("/xxx-notify") public String orderNotify() throws Exception { return OrderService.orderNotify(); } // 实现类 // 实现类 public String orderNotify() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); //校验 订单 String xml = request2String(request); boolean flag = validateSign(xml, WxDataManager.WX_API_KEY); if(flag){ // TODO 业务 return success(); } return false(); } String fail() { return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml>"; } String success() { return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; }
request 转换为字符串
/** * 微信回调参数 转化为String的 xml * * @param request * @return */ public static String request2String(HttpServletRequest request) { String notifyData = ""; try { InputStream is = request.getInputStream(); try { BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); String line = null; while ((line = reader.readLine()) != null) { sb.append(line + "\n"); } notifyData = sb.toString(); } catch (IOException e) { e.printStackTrace(); } finally { is.close(); } } catch (Exception e) { log.error("获取回调数据异常:" + e.getMessage()); } return notifyData; }
校验sign签名 工具类
/** * 验证 签名sign * * @param xml * @param APPKEY 商户的app key * @return */ public static boolean validateSign(String xml, String APPKEY) { SortedMap<String, String> stringStringMap = xmlToMap(xml);// 微信返回来的xml 参数是 已经按照字典序排序了 String sign = stringStringMap.get("sign"); if (StringUtils.isEmpty(sign)) {//签名为空 return false; } String validSign = sign(stringStringMap, APPKEY); if (sign.equals(validSign)) { return true; } log.error("微信支付 签名错误"); return false; }
-
3.支付的回调统一处理
2016-07-29 23:23:13在支付宝、微信支付sdk接入的过程中,处理支付回调、退款回调等操作一直让人头痛。Payment通过依赖注入的方式将业务与支付回调行为进行分离。切底让客户端专注业务。简单方便的跳用。经过这一段时间,终于把支付宝支付相关接口完成了。文档将陆续更新。这里先把回调的文档补充完成。这样结合之前 提供的 支付宝即时到帐接口 才能形成一个闭环。完成一次完整的支付流程。
回调接口介绍
支付服务商(支付宝、微信、PayPal等)处理完支付数据后。会将处理的结果数据通过服务器主动通知的方式通知给商户网站。这些处理结果数据就是服务器异步通知参数。
简单来说,就是支付成功后,支付服务商告诉你,钱已经到账了,你应该把别个买的东西给别个了。
由于本次发布的接口主要都是与支付宝相关,因此本次回调的部分代码也主要先上支付宝的。微信等后期开发完成将更新相关文档。
针对不同的回调,我提供了统一的调用方式,让客户端专注自己的业务。不在处理支付相关的签名验证、来源验证的问题。下面代码是统一的回调处理代码
// 支付宝配置文件 $aliconfig = [ 'partner' => '2088xxxxx',// 请填写自己的支付宝账号信息 'md5_key' => 'xxxxxxx',// 此密码无效,请填写自己对应设置的值 'rsa_private_key' => dirname(__FILE__) . DIRECTORY_SEPARATOR . 'rsa_private_key.pem', "notify_url" => 'http://test.helei.com/pay-notify.html', "return_url" => 'http://test.helei.com/return-url.html', "time_expire" => '14', // 转款接口,必须配置以下两项 'account' => 'xxxxxxx@126.com', 'account_name' => 'xxxxxxxxxxxx', ]; // 微信配置文件 $wxconfig = [ 'app_id' => 'wxxxx', // 公众账号ID 'mch_id' => 'xxxx',// 商户id 'md5_key' => 'xxxxxx',// md5 秘钥 'notify_url' => 'http://test.helei.com/pay-notify.html', 'time_expire' => '14', // 涉及资金流动时,需要提供该文件 'cert_path' => dirname(__FILE__) . DIRECTORY_SEPARATOR . 'wx' . DIRECTORY_SEPARATOR . 'apiclient_cert.pem', 'key_path' => dirname(__FILE__) . DIRECTORY_SEPARATOR . 'wx' . DIRECTORY_SEPARATOR . 'apiclient_key.pem', ]; // 获取异步通知上下文 $notify = new NotifyContext(); // 客户端的业务逻辑类。处理如:订单更新 $callback = new TestNotify(); try { // 支付宝回调 //$notify->initNotify(Config::ALI, $aliconfig); // 微信回调 $notify->initNotify(Config::WEIXIN, $wxconfig); $notify->notify($callback); } catch (PayException $e) { echo $e->errorMessage();exit; }
客户端类
TestNotify
的注意事项 ,一定要继承PayNotifyInterface
这个类use Payment\Notify\PayNotifyInterface; class TestNotify implements PayNotifyInterface { /** * 客户端的业务逻辑, * @param array $data * @return bool 返回值一定是bool值 * @author helei */ public function notifyProcess(array $data) { // 一般支付的处理业务 1. 检查订单是否存在 2. 检查金额是否正确 3. 检查订单是否已经处理过(防止重复通知) 4. 更新订单 return true; } }
在
TestNotify::notifyProcess()
方法中,我注释了常规的订单处理流程。这个地方客户端可根据自己的情况进行适当调整。只是返回结果一定需要 布尔值现在的重点是
TestNotify::notifyProcess()
方法中的参数 $data 中具体包含的值。根据不同的回调业务,返回的值也不一样。具体请往下看。支付宝回调通知类型
支付宝的回调,与支付相关的对应三种类型。
- trade_status_sync 支付行为相关的异步通知
- batch_refund_notify 有密批量退款的异步通知
- batch_trans_notify 有密批量付款到支付宝账号的通知
trade_status_sync 异步通知返回数据
该回调发生在:移动支付、即时到帐、手机网站支付接口调用后,如果支付成功,支付宝会发起回调接口。
返回的对应数据如下表
参数 参数名 参数说明 必须 subject 商品名称 订单的关键字 是 body 商品描述 提交订单时的body值 是 amount 交易金额 本次订单总金额 是 channel 支付渠道 本处取值: ali 是 order_no 商户网站唯一订单号 商户生成的订单号,必须确保在系统中唯一 是 buyer_id 买家支付宝账户号 可以是Email或手机号码。 是 trade_state 交易状态 支付成功与否,可取值:success not_pay 是 transaction_id 支付宝交易号 支付宝系统中的交易流水号,可用于查询订单状态 是 time_end 交易付款时间 格式为2016-07-28 16:01:01 是 notify_time 通知时间 格式为2016-07-28 16:01:01 是 notify_type 通知类型 此处为:trade 表示交易 是 extra_param 商户自定义参数 仅即时到帐支持 否 传入到
TestNotify::notifyProcess()
中的数组,包括以上key。可根据以上内容进行业务处理。batch_refund_notify 异步通知返回数据
本类回调发生在操作退款后,支付宝会通知服务器是否退款成功。退款的界面,如下图:
输入支付密码后,就可完成退款。退款后支付宝会向商户服务器进行异步通知。他返回的数据如下:
参数 参数名 参数说明 必须 channel 渠道 本处取值: ali 是 refund_no 商户网站唯一退单号 商户生成的退单号,必须确保在系统中唯一 是 success_num 退款成功总数 0<= success_num<= 总退款笔数 是 notify_time 通知时间 格式为2016-07-28 16:01:01 是 notify_type 通知类型 此处为:trade 表示交易 是 batch_trans_notify 异步通知返回数据
该回调发生在批量付款后。返回的数据如下:
参数 参数名 参数说明 必须 channel 渠道 本处取值: ali 是 trans_no 商户网站唯一转账单号 商户生成的转账单号,必须确保在系统中唯一 是 pay_name 付款账号姓名 如果是个人为昵称,公司则为公司名称 是 pay_account 付款账号 邮箱或者手机号码 是 notify_time 通知时间 格式为2016-07-28 16:01:01 是 notify_type 通知类型 此处为:trade 表示交易 是 success 转账成功的详细信息 返回的是一个数组 是 fail 转账失败的详细信息 返回的是一个数组 是 备注:
- success返回的数组中,单条记录格式如下:
流水号^收款方账号^收款账号姓名^付款金额^成功标识(S)^成功原因(null)^支付宝内部流水号^完成时间。
- fail返回数据中的单条记录格式如下:
流水号^收款方账号^收款账号姓名^付款金额^失败标识(F)^失败原因^支付宝内部流水号^完成时间。
OK。到此支付宝相关的回调通知数据,已经全部处理完成。这里需要大家注意一个问题,上面的数据并不是完整的支付宝返回数据。大部分经过了我的简化,如果有需要的参数,没有返回可执行修改源码或者联系我。
微信支付回调
微信的回调代码部分与支付宝相同。不同的主要是返回值
参数 参数名 参数说明 必须 amount 订单金额 微信默认为分,此处以处理为元,两位小数 是 channel 支付渠道 可取之为:ali wx 此处为wx 是 order_no 商户订单号 商户系统的订单号,与请求一致。 是 buyer_id 用户标识 用户在商户appid下的唯一标识 是 trade_state 交易状态 表示交易成功,返回值:success 是 transaction_id 微信支付订单号 微信支付订单号 是 time_end 支付完成时间 支付完成时间,格式为yyyy-MM-dd HH:mm:ss 是 notify_time 异步通知发生时间 支付完成时间,格式为yyyy-MM-dd HH:mm:ss 是 notify_type 通知类型 此处为:trade 表示交易 是 extra_param 商户自定义参数 仅即时到帐支持 否 大家可以对比支付宝中异步通知的返回数据。对相关数据均做了命令统一、数据单位格式统一。方便再业务中进行统一处理。
-
支付宝统一下单回调方法(2)
2020-03-21 14:49:26注意:进行异步回调时一定要确认消息是否来自支付宝 public void payCompent(String out_trade_no, String trade_status, HttpServletRequest request, HttpServletResponse response) { ... -
微信支付统一下单支付结果回调
2020-04-02 18:22:12在用户支付完过后微信会调用我们给它的异步通知地址返回支付的结果,需要注意的是我们给的通知地址是可以进行外网访问的 我使用的一些工具类方法大多是从官方给的demo,下面是下载地址 ...好了上代码 ... -
Java 微信支付统一下单、支付回调、订单查询实现
2020-03-11 11:23:46【微信支付】H5支付开发文档 b. Java 后端微信支付demo 引入jar包 <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> ... -
微信统一下单发起微信支付和支付后回调
2020-03-12 11:36:47微信统一下单发起微信支付和支付后回调 引入微信支付jar <dependency> <groupId>org.jdom</groupId> <artifactId>jdom</artifactId> <version>1.1.3</version> ... -
微信支付-公众号支付,统一下单,调起微信支付,回调验证
2019-09-25 08:17:30参考:http://www.jianshu.com/p/a172a1b69fdd http://www.jianshu.com/p/1ae0ef652f63 http://www.jb51.net/article/76110.htm http://www.tangshuang.net/2359.html http://wyong.blog.51cto.com/1115465/1... -
[Spring boot]原生Feign完成微信支付全流程(一)统一下单支付与支付回调
2020-06-23 17:14:53微信支付统一下单前言难点依赖微信支付流程(常用)具体代码 前言 本系列主要介绍如何用原生的Feign实现微信的整个支付流程。本文先介绍支付的第一步统一下单。 难点 1.将Feign的入参的Bean自动转化为xml来请求。 ... -
微信支付回调工具类
2018-11-30 16:26:50对接微信支付统一下单接口时,下单支付成功后的回调工具类,相关的回调工具类使用,大家可以关注我的博客进入查看,有关键代码解析,配套使用更佳 -
微信回调 java_详解APP微信支付(java后台_统一下单和回调)
2021-02-12 14:25:09方法wxpay用于生成预支付订单信息方法notifyWeiXinPay用于微信支付成功后的回调, 注意: 在手机端使用微信支付成功后,微信服务器会根据提供的回调地址进行回调,parameterMap.put("notify_url", wxnotify);...