日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java开发微信支付实践

發布時間:2023/12/20 java 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java开发微信支付实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前提條件

1、認證的微信公眾號和微信小程序號(http://mp.weixin.qq.com/)

2、微信商戶號(http://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F)

3、帶有公網ip的VPS? 【端口映射用】,也可以使用其他方式映射

4、解析到公網ip的域名(公眾號里面用到JS接口安全域名和網頁授權域名)

5、映射知識參考這里:利用VPS服務器搭建一個FRP內網穿透服務和Web服務穿透

開發工具:微信開發者工具、Intellij?idea

注冊商戶:

(注冊過程比較繁瑣,自行百度,需要營業執照)

需要設置的地方在商戶平臺? 產品中心->AppID管理設置公眾號對應的AppID

然后去?產品中心->開發配置? 里面將支付目錄設置為項目訪問的根目錄即可

(文檔地址:http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3)

1.小程序里面的JSAPI支付

流程:

1、先下載官方的支付demo(http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1)

其中需要注意的地方是WXPay.java里面的這里:

默認加密方式用反了,需要修改成相反的方式。

2、賦值以下工具類和配置:

#######application.properties配置 ################ #小程序 keyAppID=wx*************** AppSecret=************************ #商戶id MchID=********* #商戶key(在微信支付商戶網站自定義的32個字符的mchkey) MchKey=************************ #終端ip,支持IPV4和IPV6兩種格式的IP地址。調用微信支付API的機器IP(這里提供的是VPS的公網ip,其他映射方式需要自己試驗) spbill_create_ip=*.*.*.* #異步接收微信支付結果通知的回調地址,通知url必須為外網可訪問的url,不能攜帶參數 notifyUrl=http://www.xxx.com/payment/receiveResultOfPay /** * @Description 統一返回類 **/public class ResultUtil implements Serializable {private String code;private String msg;private Object data;public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}public ResultUtil() {}public ResultUtil(String code, String msg, Object data) {this.code = code;this.msg = msg;this.data = data;}} /** * @Description 請求和返回的參數封裝 **/public class ResParamsUtil {public static JSONObject getBodyParamsJSON(httpervletRequest request) {BufferedReader br = null;try {br = request.getReader();String line = br.readLine();if (line == null) {return null;} else {StringBuilder ret = new StringBuilder();ret.append(line);while((line = br.readLine()) != null) {ret.append('\n').append(line);}return JSONObject.parseObject(ret.toString());}} catch (IOException var4) {throw new RuntimeException(var4);}}public static String getBodyParamsXML(httpervletRequest request) {BufferedReader br = null;try {br = request.getReader();String line = br.readLine();line= URLDecoder.decode(line,"UTF-8");if (line == null) {return null;} else {StringBuilder ret = new StringBuilder();ret.append(line);while((line = br.readLine()) != null) {ret.append('\n').append(line);}return ret.toString();}} catch (IOException var4) {throw new RuntimeException(var4);}}}

3、小程序端調用微信登錄接口,返回code后,再用code發送到后臺的接口,讓后臺去請求微信官方的接口換取帶有openid的認證session(即authsession)

/* app.js */ App({onLaunch: function () {this.startLogin();//調用登錄 },startLogin:function(){//登錄wx.login({timeout:30000,success: login_res =>{//發送請求到后臺wx.request({url: this.globalData.baseHost+'/user/getAuthSession',method:'POST',data:{formData:JSON.stringify({code:login_res.code,})},success:function(codesession_res){console.log("登錄Code換取Session結果:",codesession_res)if(codesession_res.data.code=='200'){wx.setStorage({ key: 'openId', data: codesession_res.data.data });//存儲到本地內存中}else{wx.setStorage({ key: 'opanId', data: null });}}}) }})}, globalData: {baseHost:'http://blogs.johngene.cn',}})

Java后臺的用code換取authsession接口:

import com.alibaba.fastjson.JSONObject; import com.hongtai.clientapi.utils.ResParamsUtil; import com.hongtai.clientapi.utils.ResultUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.httpervletRequest; @Controller@RequestMapping("/user")public class UserController {@Autowiredprivate RestTemplateBuilder restTemplateBuilder;@Value("${AppID}")private String appId; @Value("${AppSecret}")private String appSecret;/*** 根據前臺調用的登錄接口返回的code再次請求查詢帶有openid的authSession登錄信息**/@PostMapping("/getAuthSession")@ResponseBodypublic ResultUtil getAuthSession(httpervletRequest request){JSONObject formData= ResParamsUtil.getBodyParamsJSON(request).getJSONObject("formData");String code=formData.getString("code");if(code==null){return new ResultUtil("1","失敗",null);//返回自定義登錄態}String url="http://api.weixin.qq.com/sns/jscode2session?appid="+appId+"&secret="+appSecret+"&js_code="+code+"&grant_type=authorization_code";ResponseEntity responseEntity=restTemplateBuilder.build().getForEntity(url, String.class);//sessionJson帶有openid和session_key,安全起見session_key不可返回給小程序if(responseEntity.getStatusCodeValue()==200){JSONObject entityJSON=JSONObject.parseObject(responseEntity.getBody().toString());//獲取到的兩個有用的東西:openId和sessionKey,其中我只用到openIdString openId=entityJSON.getString("openid");String sessionKey=entityJSON.getString("session_key"); if(openId!=null){return new ResultUtil("200","登錄成功",openId);//返回自定義登錄態(openId理論上講應存在后臺,不返回給前端,只需要返回給前端一個自定義的userId,讓前端使用userId查詢openId)}else{return new ResultUtil("201","保存用戶信息失敗",null);//返回自定義登錄態}}else{return new ResultUtil("500","查詢的登錄信息失敗",null);//返回自定義登錄態}}}

4、調用支付的頁面index.wxml代碼:

<!-- index.wxml --> <view><button type="primary" bindtap="startPay" style="margin-bottom:15px;">調用支付</button> </view>

只有一個按鈕:

index.js代碼:

/* index.js */ const app = getApp() Page({//頁面的初始數據data: {},//自定義:支付按鈕事件startPay:function(){/*小程序支付開發流程:1、小程序內調用登錄接口,獲取到用戶的openid,api參見公共api【小程序登錄API】2、商戶server調用支付統一下單,api參見公共api【統一下單API】3、商戶server調用再次簽名,api參見公共api【再次簽名】4、商戶server接收支付通知,api參見公共api【支付結果通知API】5、商戶server查詢支付結果,api參見公共api【查詢訂單API】*/let openId=wx.getStorageSync("openId") || [];wx.request({url: app.globalData.baseHost + '/payment/doUnifiedOrder',header: {"Content-Type": "application/json;charset=UTF-8"},data: {formData:JSON.stringify({openId : openId})},method: 'POST',dataType: 'json',responseType: 'text',success: function(res) {console.log("服務端返回:",res);var c=res.data;wx.requestPayment({timeStamp: res.data.data.timeStamp,nonceStr: res.data.data.nonceStr,package: res.data.data.package,signType: res.data.data.signType,paySign: res.data.data.paySign,success(res) {console.log("統一下單接口成功:",res);},fail(res) {console.log("統一下單接口失敗:",res);}});},fail: function(res) {console.log("請求服務失敗:",res);},complete: function(res) {},});},//生命周期函數--監聽頁面加載onLoad: function (options) {},//生命周期函數--監聽頁面初次渲染完成onReady: function () {},//生命周期函數--監聽頁面顯示onShow: function () {},//生命周期函數--監聽頁面隱藏onHide: function () {},//生命周期函數--監聽頁面卸載onUnload: function () {},//頁面相關事件處理函數--監聽用戶下拉動作onPullDownRefresh: function () {},//頁面上拉觸底事件的處理函數onReachBottom: function () {},//用戶點擊右上角分享onShareAppMessage: function () {} })

自定義微信支付配置類MyConfig.java,繼承自微信支付API Demo 中的WxPayConfig.java

import java.io.ByteArrayInputStream; import java.io.InputStream;/*** 微信支付配置類*/ public class MyConfig extends WXPayConfig {private String appid;private String mch_id;private String mch_key;private byte[] certData;public MyConfig() throws Exception {}public MyConfig(String appid, String mch_id, String mch_key) {this.appid = appid;this.mch_id = mch_id;this.mch_key = mch_key;}@Overridepublic String getAppID() {return this.appid;}@Overridepublic String getMchID() {return this.mch_id;}@Overridepublic String getKey() {return this.mch_key;}@Overridepublic InputStream getCertStream() {ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);return certBis;}@Overridepublic int getHttpConnectTimeoutMs() {return 8000;}@Overridepublic int getHttpReadTimeoutMs() {return 10000;}@OverrideIWXPayDomain getWXPayDomain() {return new IWXPayDomain() {@Overridepublic void report(String domain, long elapsedTimeMillis, Exception ex) {}@Overridepublic DomainInfo getDomain(WXPayConfig config) {return new DomainInfo("api.mch.weixin.qq.com", false);}};} }

小程序JSAPI支付后臺接口WxPayController.java:

import com.alibaba.fastjson.JSONObject; import com.hongtai.clientapi.utils.ResParamsUtil; import com.hongtai.clientapi.utils.ResultUtil; import com.hongtai.clientapi.utils.wxpay.MyConfig; import com.hongtai.clientapi.utils.wxpay.WXPay; import com.hongtai.clientapi.utils.wxpay.WXPayConstants; import com.hongtai.clientapi.utils.wxpay.WXPayUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*;import javax.servlet.http.httpervletRequest; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map;/*** 小程序JSAPI支付**/ @Controller @RequestMapping("/payment") public class WxPayController {/**支付流程1、小程序內調用登錄接口,獲取到用戶的openid,api參見公共api【小程序登錄API】2、商戶server調用支付統一下單,api參見公共api【統一下單API】3、商戶server調用再次簽名,api參見公共api【再次簽名】4、商戶server接收支付通知,api參見公共api【支付結果通知API】5、商戶server查詢支付結果,api參見公共api【查詢訂單API】*/@Value("${AppID}")private String appid;@Value("${AppSecret}")private String appsecret;@Value("${MchID}")private String mch_id;@Value("${MchKey}")private String mch_key;@Value("${notifyUrl}")private String notifyUrl;@Value("${spbill_create_ip}")private String spbill_create_ip;/*** 后臺下單接口(里面自動調用了微信統一下單接口)**/@RequestMapping(value = "/doUnifiedOrder", method = RequestMethod.POST)@ResponseBodypublic ResultUtil doUnifiedOrder(httpervletRequest request) {JSONObject formData= ResParamsUtil.getBodyParamsJSON(request).getJSONObject("formData");String openId=formData.getString("openId");MyConfig config = null;WXPay wxpay =null;try {config = new MyConfig(appid,mch_id,mch_key);wxpay= new WXPay(config);} catch (Exception e) {e.printStackTrace();}//獲取客戶端的ip地址//獲取本機的ip地址InetAddress addr = null;try {addr = InetAddress.getLocalHost();} catch (UnknownHostException e) {e.printStackTrace();}String spbill_create_ip = addr.getHostAddress();//終端ip,支持IPV4和IPV6兩種格式的IP地址。調用微信支付API的機器IPint total_fee=1;//支付金額,需要使用字符串類型,否則后面的簽名會失敗,微信支付提交的金額是不能帶小數點的,且是以分為單位String body = "費用支付";//商品描述String out_trade_no= WXPayUtil.generateNonceStr();//商戶訂單號String nonceStr=WXPayUtil.generateNonceStr();//獲取隨機字符串//統一下單接口參數HashMap<String, String> data = new HashMap<>();data.put("body", body);//商品描述data.put("out_trade_no",out_trade_no);//商戶訂單號(隨機生成)data.put("total_fee", String.valueOf(total_fee));//支付金額data.put("spbill_create_ip", spbill_create_ip);//終端ip,支持IPV4和IPV6兩種格式的IP地址。調用微信支付API的機器IPdata.put("notify_url", notifyUrl);//異步接收微信支付結果通知的回調地址,通知url必須為外網可訪問的url,不能攜帶參數data.put("trade_type","JSAPI");//交易類型data.put("openid", openId);//用戶openid,trade_type=JSAPI,此參數必傳data.put("nonce_str",nonceStr);try {Map<String, String> rMap = wxpay.unifiedOrder(data);//調用統一下單apiSystem.out.println("統一下單接口返回: " + rMap);String return_code = rMap.get("return_code");String result_code = rMap.get("result_code");Long timeStamp = System.currentTimeMillis() / 1000;//獲取時間戳if ("SUCCESS".equals(return_code) && return_code.equals(result_code)) {Map resultMap=new HashMap();String prepayid = rMap.get("prepay_id");resultMap.put("appId",appid);//appId,再次簽名中的appId的i要大寫不能寫成appidresultMap.put("timeStamp", timeStamp + "");//這邊要將返回的時間戳轉化成字符串,不然小程序端調用wx.requestPayment方法會報簽名錯誤resultMap.put("nonceStr", nonceStr);//隨機字符串必須和統一下單接口使用的隨機字符串相同resultMap.put("package", "prepay_id="+prepayid);resultMap.put("signType", WXPayConstants.MD5);//MD5或者HMAC-SHA256String sign = WXPayUtil.generateSignature(resultMap, mch_key);//再次簽名,這個簽名用于小程序端調用wx.requesetPayment方法String xml=WXPayUtil.generateSignedXml(resultMap,mch_key);resultMap.put("paySign", sign);System.out.println("生成的簽名paySign : "+ sign);return new ResultUtil("200","",resultMap);}else{return new ResultUtil("500","",null);}} catch (Exception e) {e.printStackTrace();return new ResultUtil("500","",null);}}/*** @Author zj* @Description 接收成功后返回的支付結果* @Date 2020/07/17 15:13:01* @Param []* @return void**/@RequestMapping(value = "/receiveResultOfPay")@ResponseBodypublic void receiveResultOfPay(httpervletRequest request){System.out.println("支付成功!!!");String xmlData=ResParamsUtil.getBodyParamsXML(request);System.out.println(xmlData);Map<String,String> map=null;try {map=WXPayUtil.xmlToMap(xmlData);} catch (Exception e) {e.printStackTrace();}//此處map就是接收到的支付成功的結果,同樣的通知可能會多次,發送給商戶系統。商戶系統必須能夠正確處理重復的通知} }

2.公眾號里面的JSAPI支付

公眾號調用該支付與小程序調用支付的不同之處在于小程序直接可以調用登錄api獲取code然后利用code去后臺調用統一下單接口,而公眾號的網頁無法直接調用,需要通過JSSDK調用。

微信JSSDK開發文檔地址:http://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html

微信支付文檔地址:http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1

步驟如下:1、公眾號開發配置

需要點擊一下重置來獲取AppSecret和ip白名單,白名單需要設置成測試環境下的公網ip(百度ip地址即可查看公網ip)和服務器對應的ip(上線后用到)

2、網頁授權配置

在接口權限目錄中找到? 網頁服務->網頁授權->點擊后面的修改按鈕

3、關聯商戶號

關聯成功后可以看到如下界面:

?

進入重點:

思路

? 先獲取到微信的網頁授權,利用網頁授權接口返回的code和state,用后臺再次請求通過code換取網頁授權access_token接口,完成授權后重定向到支付頁面并將openid、appId等信息帶過去,支付頁面中用appid等信息獲取簽名后初始化JSSDK,請求后臺的支付接口,利用后臺的支付接口請求微信統一下單接口進行支付操作,后臺等待支付成功消息。

簡化思路

? 網頁授權:

第一步:用戶同意授權,獲取code

第二步:通過code換取網頁授權access_token

? 調用JSSDK:

直接初始化JSSDK

? 調用支付:

直接調用后臺支付接口,讓后臺完成操作

開始寫代碼:

工具類和上面小程序JSAPI支付里面的兩個工具類以外還有一個簽名用的工具類WxUtil.java

application.properties配置類如下:

#商戶id MchID=******** #商戶key MchKey=********#終端ip,支持IPV4和IPV6兩種格式的IP地址。調用微信支付API的機器IP(要與實際調用的機器相同) spbill_create_ip=*.*.*.*#異步接收微信支付結果通知的回調地址,通知url必須為外網可訪問的url,不能攜帶參數 notifyUrl=http://www.xxx.com/payment/receiveResultOfPay#公眾號Token token=**********#公眾號appid app_id=********** app_secret=**********

簽名工具類:WxUtil.java

import org.springframework.http.ResponseEntity;import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Iterator; import java.util.Map; import java.util.TreeMap;/** * @Description 公眾號的weixin工具類 * @Date 2020/07/28 15:05:25 * @Param * @return **/ public class WxUtils {/*** @Description 將字節轉換為十六進制字符串* @Date 2020/07/27 19:38:02* @Param [mByte]* @return java.lang.String**/public static String byteToHexStr(byte mByte) {char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };char[] tempArr = new char[2];tempArr[0] = Digit[(mByte >>> 4) & 0X0F];tempArr[1] = Digit[mByte & 0X0F];String s = new String(tempArr);return s;}/*** @Description 將數組中的內容進行字典排序也可以直接使用Arrays.sort(arr);* @Date 2020/07/27 19:37:40* @Param [a]* @return void**/public static void sort(String a[]) {for (int i = 0; i < a.length - 1; i++) {for (int j = i + 1; j < a.length; j++) {if (a[j].compareTo(a[i]) < 0) {String temp = a[i];a[i] = a[j];a[j] = temp;}}}}/*** @Description 將字節數組轉換為十六進制字符串* @Date 2020/07/27 19:38:39* @Param [byteArray]* @return java.lang.String**/public static String byteToStr(byte[] byteArray) {String strDigest = "";for (int i = 0; i < byteArray.length; i++) {strDigest += byteToHexStr(byteArray[i]);}return strDigest;}/*** @Description 轉換頁面的code為session信息(session中帶有openid等信息)* @Date 2020/07/29 17:45:38* @Param [code, appId, appSecret]* @return org.springframework.http.ResponseEntity**/public static ResponseEntity code2Session(String code,String appId,String appSecret){System.out.println("code2Session----appId:"+appId+",appSecret:"+appSecret);return HttpUtil.getFormRequest("http://api.weixin.qq.com/sns/oauth2/access_token?appid="+appId+"&secret="+appSecret+"&code="+code+"&grant_type=authorization_code");}/*** @Description 獲取JSSDKd 簽名* @Date 2020/07/29 17:46:52* @Param []* @return java.lang.String**/public static String getJsSDKSignature(String noncestr,String jsapi_ticket,Long timestamp,String url){/*簽名生成規則如下:參與簽名的字段包括:noncestr(隨機字符串),有效的jsapi_ticket, timestamp(時間戳),url(當前網頁的URL,不包含#及其后面部分) 。步驟如下:1)對所有待簽名參數按照字段名的ASCII 碼從小到大排序(字典序),2)使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串string1。這里需要注意的是所有參數名均為小寫字符。3)對string1作sha1加密,字段名和字段值都采用原始值,不進行URL 轉義注意事項簽名用的noncestr和timestamp必須與前端的wx.config中的nonceStr和timestamp相同。簽名用的url必須是調用JS接口頁面的完整URL。出于安全考慮,開發者必須在服務器端實現簽名的邏輯*///利用TreeMap 默認排序規則:按照key的字典順序來排序(升序)TreeMap<String,Object> treeMap=new TreeMap<>();treeMap.put("noncestr",noncestr);treeMap.put("jsapi_ticket",jsapi_ticket);treeMap.put("timestamp",timestamp);treeMap.put("url",url);StringBuffer stringBuffer=new StringBuffer();Iterator<Map.Entry<String, Object>> it = treeMap.entrySet().iterator();while(it.hasNext()) {Map.Entry<String, Object> entry = it.next();stringBuffer.append(entry.getKey()).append("=").append(entry.getValue()).append("&");}String string1=stringBuffer.substring(0,stringBuffer.length()-1);String signature = null;MessageDigest md=null;try {md = MessageDigest.getInstance("SHA-1");// 將字符串string1進行sha1加密byte[] digest = md.digest(string1.getBytes());signature = WxUtils.byteToStr(digest);} catch (NoSuchAlgorithmException e) {e.printStackTrace();}return signature;} }

其中用到的HttpUtil.java

import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.*; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate;import java.util.Map;/** * @Description Http工具類 * @Date 2020/07/03 10:50:55 * @Param * @return **/ @Component public class HttpUtil {private static RestTemplate getRestTemplate() {return new RestTemplateBuilder().build();}private static HttpHeaders getHttpHeaders(){return new HttpHeaders();}/*** @Description POST的方式發送json格式的請求* @Date 2020/07/03 10:43:32* @Param [url, params]* @return java.lang.String**/public static String postJsonRequest(String url, Map<String,Object> params){RestTemplate restTemplate=getRestTemplate();HttpHeaders httpHeaders=getHttpHeaders();httpHeaders.setContentType(MediaType.APPLICATION_JSON);HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(params, httpHeaders);ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity,String.class);return responseEntity.getBody();}/*** @Description POST的方式發送表單格式的請求* @Date 2020/07/03 10:49:37* @Param [url, params]* @return java.lang.String**/public static String postFormRequest(String url, MultiValueMap<String,Object> params){RestTemplate restTemplate=getRestTemplate();HttpHeaders httpHeaders=getHttpHeaders();httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(params, httpHeaders);ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity,String.class);return responseEntity.getBody();}/*** @Description get的方式發送表達格式的請求* @Date 2020/07/03 10:50:20* @Param [url]* @return java.lang.String**/public static ResponseEntity getFormRequest(String url){RestTemplate restTemplate = getRestTemplate();ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);return responseEntity;} }

程序入口類:IndexController.java

import com.alibaba.fastjson.JSONObject; import com.hongtai.driverapi.utils.WxUtils; import com.sun.deploy.net.URLEncoder; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.httpervletRequest; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays;/** * @Description 程序入口 * @Date 2020/08/31 17:19:38 **/ @Controller public class IndexController {@Value("${app_id}")private String app_id;@Value("${app_secret}")private String app_secret;/*** @Description 跳轉到授權頁* @Date 2020/07/28 15:40:27* @Param [model]* @return java.lang.String**/@RequestMapping("/")public String auth(Model model){model.addAttribute("app_id",app_id);System.out.println("app_id:"+app_id);try {String url="http://blogs.johngene.cn/authCallBack";String redirect_uri= URLEncoder.encode(url,"UTF-8");System.out.println("redirect_uri:"+redirect_uri);model.addAttribute("redirect_uri",redirect_uri);} catch (UnsupportedEncodingException e) {e.printStackTrace();}return "auth";}/*** @Description 跳轉到微信支付測試頁面* @Date 2020/07/19 18:15:25* @Param []* @return java.lang.String**/@RequestMapping("/authCallBack")public String authCallBack(String code, String state, httpervletRequest request){System.out.println("網頁授權獲得:code:"+code+",state:"+state);//code換取Session信息,應保留在服務器,需要時查詢,過期時重新獲取ResponseEntity responseEntity= WxUtils.code2Session(code,app_id,app_secret);String openid="";String access_token="";JSONObject entityJSON=null;if(responseEntity.getStatusCodeValue()==200) {entityJSON = JSONObject.parseObject(responseEntity.getBody().toString());access_token = entityJSON.getString("access_token");//這里的access_token是網頁的access_token并不是公眾號的基本access_tokenString expires_in = entityJSON.getString("expires_in");String refresh_token = entityJSON.getString("refresh_token");openid = entityJSON.getString("openid");String scope = entityJSON.getString("scope");System.out.println("根據網頁授權獲取的code再次請求換取的session信息,access_token:"+access_token+",expires_in:"+expires_in+"" +",refresh_token:"+refresh_token+",openid:"+openid+",scope:"+scope);}else if(responseEntity.getStatusCodeValue()==40029){System.out.println("網頁授權獲得的code無效!");}request.getSession().setAttribute("openid",openid);//attr.addAttribute("access_token",access_token);//網頁授權獲取的access_token只為進一步獲取用戶信息,別無他用return "redirect:/toWxPay";}/*** @Description 跳轉到微信JSAPI支付頁面* @Date 2020/07/28 23:01:38* @Param [code, state, model]* @return java.lang.String**/@RequestMapping("/toWxPay")public String wxPay(Model model,httpervletRequest request){String openid=request.getSession().getAttribute("openid")+"";model.addAttribute("openid",openid);model.addAttribute("app_id",app_id);System.out.println("已經重定向到了wx_pay頁面,openid:"+openid);return "wx_pay";}/*** @Description JS接口安全域名和網頁安全域名驗證* @Date 2020/07/26 09:50:37* @Param []* @return java.lang.String**/@RequestMapping("/MP_verify_xJwVNVyXJG2YvgOj.txt")@ResponseBodypublic String validateDomainName(){//根據MP_verify_xJwVNVyXJG2YvgOj.txt的文件內容返回,這里是按照文件內容造假,直接返回return "******";//這里需要復制出根據MP_verify_xJwVNVyXJG2YvgOj.txt文件內容,我用*號代替了}//----------------分割線,下面的是用來驗證服務器配置的,和支付無關-----------------@Value("${token}")private String token;/*** @Description 服務器配置對應的接口:檢驗微信公眾號Token的地址(接入微信后臺的驗證器)這里暫時用不到* signature:微信加密簽名,signature結合了開發者填寫的token參數和請求中的timestamp參數、nonce參數。* timestamp:時間戳* nonce:隨機數* echostr:隨機字符串* @Date 2020/07/27 19:17:49* @Param [signature, timestamp, nonce, echostr]* @return java.lang.String**/@RequestMapping("/checkToken")@ResponseBodypublic String checkToken(String signature,String timestamp,String nonce,String echostr){// 1)將token、timestamp、nonce三個參數進行字典序排序// 2)將三個參數字符串拼接成一個字符串進行sha1加密// 3)開發者獲得加密后的字符串可與signature對比,標識該請求來源于微信// 4)成功則將echostr原樣返回,失敗則不返回或返回其他String[] arr = new String[] { token, timestamp, nonce };// 將token、timestamp、nonce三個參數進行字典序排序Arrays.sort(arr);StringBuilder content = new StringBuilder();for (int i = 0; i < arr.length; i++) {content.append(arr[i]);}MessageDigest md;String tmpStr = null;try {md = MessageDigest.getInstance("SHA-1");// 將三個參數字符串拼接成一個字符串進行sha1加密byte[] digest = md.digest(content.toString().getBytes());tmpStr = WxUtils.byteToStr(digest);} catch (NoSuchAlgorithmException e) {e.printStackTrace();}// 將sha1加密后的字符串可與signature對比Boolean result=tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;System.out.println("Token驗證結果:"+result);//通過檢驗signature對請求進行校驗,若校驗成功則原樣返回echostr,表示接入成功,否則接入失敗if(result){return echostr;}else{return "";}} }

后臺支付接口和獲取初始化JSSDK所需要的signature簽名接口

import com.alibaba.fastjson.JSONObject; import com.github.wxpay.sdk.WXPayConstants; import com.hongtai.driverapi.service.WxTokenService; import com.hongtai.driverapi.utils.*; import com.hongtai.driverapi.utils.wxpay.MyConfig; import com.hongtai.driverapi.utils.wxpay.WXPay; import com.hongtai.driverapi.utils.wxpay.WXPayUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.httpervletRequest; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map;/** * 公眾號JSAPI支付 **/ @Controller @RequestMapping("/payment") public class WxPayOfficialController {/*1、商戶server調用統一下單接口請求訂單,api參見公共api【統一下單API】2、商戶server接收支付通知,api參見公共api【支付結果通知API】3、商戶server查詢支付結果,api參見公共api【查詢訂單API】*/@Value("${app_id}")private String app_id;@Value("${app_secret}")private String app_secret;@Value("${MchID}")private String mch_id;@Value("${MchKey}")private String mch_key;@Value("${notifyUrl}")private String notifyUrl;@Value("${spbill_create_ip}")private String spbill_create_ip;/*** @Description 公眾號統一下單接口* 注意:需要公眾號設置的地方:ip白名單、JS接口安全域名、網頁授權域名* @Date 2020/07/27 22:14:29**/@RequestMapping(value = "/doUnifiedOrder", method = RequestMethod.POST)@ResponseBodypublic ResultUtil doUnifiedOrder(httpervletRequest request) {JSONObject formData=ResParamsUtil.getBodyParamsJSON(request);String openid=formData.getString("openid");MyConfig config = null;WXPay wxpay =null;try {config = new MyConfig(app_id,mch_id,mch_key);wxpay= new WXPay(config);} catch (Exception e) {e.printStackTrace();}//獲取客戶端的ip地址//獲取本機的ip地址InetAddress addr = null;try {addr = InetAddress.getLocalHost();} catch (UnknownHostException e) {e.printStackTrace();}String spbill_create_ip = addr.getHostAddress();//終端ip,支持IPV4和IPV6兩種格式的IP地址。調用微信支付API的機器IP//支付金額,需要使用字符串類型,否則后面的簽名會失敗,微信支付提交的金額是不能帶小數點的,且是以分為單位int total_fee=1;//商品描述String body = "費用支付";//商戶訂單號String out_trade_no= WXPayUtil.generateNonceStr();String nonceStr=WXPayUtil.generateNonceStr();//統一下單接口參數HashMap<String, String> data = new HashMap<>();data.put("nonce_str",nonceStr);//隨機字符串data.put("body",body);//商品描述data.put("out_trade_no",out_trade_no);//商戶訂單號(隨機生成)data.put("total_fee", String.valueOf(total_fee));//支付金額data.put("spbill_create_ip", spbill_create_ip);//終端ip,支持IPV4和IPV6兩種格式的IP地址。調用微信支付API的機器IPdata.put("notify_url", notifyUrl);//異步接收微信支付結果通知的回調地址,通知url必須為外網可訪問的url,不能攜帶參數data.put("trade_type","JSAPI");//交易類型data.put("openid", openid);//用戶openid,trade_type=JSAPI,此參數必傳try {Map<String, String> rMap = wxpay.unifiedOrder(data);//調用統一下單apiSystem.out.println("統一下單接口返回: " + rMap);String return_code = rMap.get("return_code");String result_code = rMap.get("result_code");Long timeStamp = System.currentTimeMillis() / 1000;//獲取時間戳if ("SUCCESS".equals(return_code) && return_code.equals(result_code)) {Map resultMap=new HashMap();String prepayid = rMap.get("prepay_id");resultMap.put("appId",app_id);//appIdresultMap.put("timeStamp", timeStamp + "");//這邊要將返回的時間戳轉化成字符串,不然小程序端調用wx.requestPayment方法會報簽名錯誤resultMap.put("nonceStr", nonceStr);//隨機字符串必須和統一下單接口使用的隨機字符串相同resultMap.put("package", "prepay_id="+prepayid);resultMap.put("signType", WXPayConstants.MD5);//MD5或者HMAC-SHA256String sign = WXPayUtil.generateSignature(resultMap, mch_key);//再次簽名,這個簽名用于小程序端調用wx.requesetPayment方法String xml=WXPayUtil.generateSignedXml(resultMap,mch_key);resultMap.put("paySign", sign);System.out.println("生成的簽名paySign : "+ sign);return new ResultUtil("200","",resultMap);}else{return new ResultUtil("500","",null);}} catch (Exception e) {e.printStackTrace();return new ResultUtil("500","",null);}}/*** @Description 接收支付結果* @Date 2020/07/17 15:13:01* @Param []* @return void**/@RequestMapping(value = "/receiveResultOfPay")@ResponseBodypublic void receiveResultOfPay(httpervletRequest request){System.out.println("支付成功!!!");String xmlData=ResParamsUtil.getBodyParamsXML(request);System.out.println(xmlData);Map<String,String> map=null;try {map=WXPayUtil.xmlToMap(xmlData);} catch (Exception e) {e.printStackTrace();}//此處map就是接收到的支付成功的結果,同樣的通知可能會多次(經過測試為13次左右)發送給商戶系統。商戶系統必須能夠正確處理重復的通知}@Autowiredprivate WxTokenService wxAccessTokenService;/*** @Description 使用JSSDK前需要獲取的signature簽名數據* @Date 2020/07/29 16:00:03* @Param []**/@RequestMapping(value = "/getSignature")@ResponseBodypublic ResultUtil getSignature(String url){//url是頁面完整的url(請在當前頁面alert(location.href.split('#')[0])確認),包括'http(s)://'部分,以及'?'后面的GET參數部分,但不包括'#'hash后面的部分。if(url.indexOf("#")>0){url=url.split("#")[0];}Map<String,Object> map=new HashMap<>();JSONObject accessTokenJson=wxAccessTokenService.findAccessToken();String access_token=accessTokenJson.getString("access_token");JSONObject jsapiTicketJson=wxAccessTokenService.findJsAPITicket(access_token);String jsapi_ticket=jsapiTicketJson.getString("ticket");String noncestr=WXPayUtil.generateNonceStr();Long timestamp = System.currentTimeMillis() / 1000;//獲取時間戳String signature= WxUtils.getJsSDKSignature(noncestr,jsapi_ticket,timestamp,url);if(StringTools.isNotEmpty(signature)){map.put("timestamp",timestamp);map.put("noncestr",noncestr);map.put("signature",signature);//map.put("jsapi_ticket",jsapi_ticket);//map.put("url",url);return new ResultUtil("200","",map);}else{return new ResultUtil("500","",null);}} }

下面來寫頁面

第一個入口頁面代碼:auth.html?只是用來直接跳轉以獲取微信的授權,這里采用靜默授權snsapi_base,另一種授權snsapi_userinfo參考官網:http://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#0

<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head><meta charset="UTF-8"> </head> <body> <div id="app"></div> </body> <script>/* 直接跳轉到微信靜默授權 */let app_id='[[${app_id}]]';let redirect_uri="[[${redirect_uri}]]";let url='http://open.weixin.qq.com/connect/oauth2/authorize?' +'appid='+app_id+'&redirect_uri='+redirect_uri+'&response_type=code' +'&scope=snsapi_base' +'&state=STATE#wechat_redirect';console.log(url);window.location.href=url; </script> </html>

支付測試頁面:wx_pay.html

<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head><meta charset="UTF-8"><title>微信JSAPI支付測試</title><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0,user-scalable=no"><!-- mintui --><link rel="stylesheet" href="http://unpkg.com/mint-ui/lib/style.css"> </head> <body> <div id="app"><mt-button size="normal" type="primary" @click.native="startPay">發起支付</mt-button> </div> </body> <!-- jquery --> <script src="/js/jquery.js"></script> <!-- vue --> <script src="http://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!-- mintui --> <script src="http://unpkg.com/mint-ui/lib/index.js"></script> <!-- 引入js-sdk --> <script src="http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> <script>let vue=new Vue({el:'#app',data(){return {openid:'',appid:'',}},mounted(){this.openid="[[${openid}]]";this.appid="[[${app_id}]]";console.log("openid:",this.openid,",appid:"+this.appid);this.initJSSDK();},methods:{//初始化jssdkinitJSSDK(){let _this=this;$.ajax({//請求服務器進行簽名type:'post',dataType:'json',url:'/payment/getSignature',data:{url:window.location.href,},success:function(res){console.log("initJSSDK:",res);if(res.code==200){wx.config({debug: true, // 開啟調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時才會打印。appId: _this.appid, // 必填,公眾號的唯一標識timestamp: res.data.timestamp, // 必填,生成簽名的時間戳nonceStr: res.data.noncestr, // 必填,生成簽名的隨機串signature: res.data.signature,// 必填,簽名jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表});}else{}},error:function(res){console.error(res);}});},//發起支付startPay(){console.log("發起支付");let _this=this;$.ajax({//調用統一下單接口type:'post',dataType:'json',url:'/payment/doUnifiedOrder',data:JSON.stringify({openid : _this.openid}),success:function(res){console.log(res)if(res.code==200){wx.chooseWXPay({//開始支付timestamp: res.data.timeStamp, // 支付簽名時間戳,注意微信jssdk中的所有使用timestamp字段均為小寫。但最新版的支付后臺生成簽名使用的timeStamp字段名需大寫其中的S字符nonceStr: res.data.nonceStr, // 支付簽名隨機串,不長于 32 位package: res.data.package, // 統一支付接口返回的prepay_id參數值,提交格式如:prepay_id=\*\*\*)signType: 'MD5', // 簽名方式,默認為'SHA1',使用新版支付需傳入'MD5'paySign: res.data.paySign, // 支付簽名success: function (res) {// 支付成功后的回調函數console.log("支付成功:",res);}});}else{}},error:function(res){console.error(res)}});},}}) </script> </html>

最后用手機測試支付就可以了,有什么不正確的地方或者缺少的工具類歡迎到評論區提醒!

總結

以上是生活随笔為你收集整理的Java开发微信支付实践的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。