1. 程式人生 > >Java微信支付全教程demo【公眾號支付】

Java微信支付全教程demo【公眾號支付】

直入主題:

  1. 註冊微信公眾號、微信支付商戶號,並做好基礎配置(不解釋配置詳情,無非是獲取 appid,商戶號等)
  2. 微信支付介面程式碼
  3. 微信支付回撥介面程式碼
  4. 微信h5支付頁面喚起字元密碼介面完成支付   

1,寫程式碼之前準備工作

(1):利用開原始碼 weixin-java-tools來開發效率很高,免去了很多繁瑣的程式碼開發量;

搭建maven工程,引入:

  1. <!-- 微信支付 開始-->

  2. <dependency>

  3. <groupId>com.github.binarywang</groupId>

  4. <artifactId>weixin-java-pay</artifactId>

  5. <version>2.8.0</version>

  6. </dependency>

  7. <dependency>

  8. <groupId>com.github.binarywang</groupId>

  9. <artifactId>weixin-java-pay</artifactId>

  10. <version>2.8.0</version>

  11. <classifier>sources</classifier>

  12. </dependency>

  13. <dependency>

  14. <groupId>com.github.binarywang</groupId>

  15. <artifactId>weixin-java-mp</artifactId>

  16. <version>2.8.0</version>

  17. </dependency>

  18. <dependency>

  19. <groupId>com.github.binarywang</groupId>

  20. <artifactId>weixin-java-mp</artifactId>

  21. <version>2.8.0</version>

  22. <classifier>sources</classifier>

  23. </dependency>

  24. <dependency>

  25. <groupId>com.github.binarywang</groupId>

  26. <artifactId>weixin-java-common</artifactId>

  27. <version>2.8.0</version>

  28. </dependency>

  29. <dependency>

  30. <groupId>com.github.binarywang</groupId>

  31. <artifactId>weixin-java-common</artifactId>

  32. <version>2.8.0</version>

  33. <classifier>sources</classifier>

  34. </dependency>

  35. <!-- 微信支付 結束 -->


(2):微信支付開發介面需要 用到使用者openId引數,至於微信授權獲取使用者openId這裡不做解釋;

(3):獲得微信支付所要的配置檔案,

這裡我配置的有引數的都是必須要填寫的,其他的可以不寫,這裡WX_WEB_URL這個是你網站的網址在回撥的時候需要用到,我把這個地址配置到了配置檔案裡了。

  1. #微信對接配置

  2. WX_APPID=wx11231231231

  3. WX_APPSECRET=321321321321321321

  4. WX_TOKEN=

  5. WX_AESKEY=

  6. #微信支付商戶號

  7. WX_mchId=432432432

  8. #微信支付平臺商戶API金鑰

  9. WX_mchKey=fgfdfdewrewrwer432432

  10. #服務商模式下的子商戶公眾賬號ID

  11. WX_subAppId=

  12. #服務商模式下的子商戶號

  13. WX_subMchId=

  14. WX_keyPath=

  15. WX_WEB_URL=http://7cvyn3.natappfree.cc/zc

(4):這個是我們從微信平臺上獲取的配置檔案,還有兩個重要的授權地址是我們要在微信平臺上配置的,這個也是微信極為坑的一點

1:在微信公眾平臺——》許可權介面——》網頁授權獲取使用者基本資訊

必須填入外網域名並且要下載提示裡的.txt檔案,放到你網站的跟目錄下,可以通過網站直接訪問的路徑

這一步配置好後,還有一步就是微信支付的路徑配置,這裡最坑,我已在崩潰的邊緣,這就是支付授權目錄,注意這裡是目錄,不是僅僅是域名,後面要加你要掉起支付的html頁面的最後一個路徑要加/。當初我只寫了域名,然後怎麼都掉用起不來支付介面。坑了我好幾天,

到這一步,如果都沒有問,你基礎的配置就完成了,下面就到了擼程式碼的步驟了,由於我們用的是開源工具,程式碼量其實非常簡單,只需要一點配置就好。

2:擼程式碼,Java搞起來

2.1:先把微信配置檔案 通過spring寫到bean中;

  1. <!-- 微信基礎配置 -->

  2. <bean name="wxConfig" class="me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage">

  3. <property name="appId" value="${WX_APPID}" />

  4. <property name="secret" value="${WX_APPSECRET}" />

  5. </bean>

  6. <!-- 微信核心service注入 -->

  7. <bean id="wxMpService" class="me.chanjar.weixin.mp.api.impl.WxMpServiceImpl">

  8. <property name="wxMpConfigStorage" ref="wxConfig" />

  9. </bean>

  10. <!-- 微信支付配置 -->

  11. <bean name="wxPayConfig" class="com.github.binarywang.wxpay.config.WxPayConfig">

  12. <property name="appId" value="${WX_APPID}" />

  13. <property name="mchId" value="${WX_mchId}" />

  14. <property name="mchKey" value="${WX_mchKey}" />

  15. <property name="subAppId" value="${WX_subAppId}" />

  16. <property name="subMchId" value="${WX_subMchId}" />

  17. <property name="keyPath" value="${WX_keyPath}" />

  18. </bean>

  19. <!-- 微信支付service注入 -->

  20. <bean id="wxPayService" class="com.github.binarywang.wxpay.service.impl.WxPayServiceImpl">

  21. <property name="config" ref="wxPayConfig" />

  22. </bean>

2.2:直接進入重點,微信支付控制器,微信支付欲支付介面和回撥介面:相關工具類封裝在下面。

支付那我已經把業務和微信支付做了分離。

  1. /**

  2. * 微信對接控制器,微信支付

  3. * Project Name:zc_app_api

  4. * File Name:WxInitController.java

  5. * Package Name:com.zc.app.api.controller.weixin

  6. * Date:2017年9月30日下午3:10:19

  7. * @author 吉文劍

  8. */

  9. @Controller

  10. @RequestMapping("/wxPay/")

  11. public class WxPayController extends BaseController {

  12. private final Logger logger = LoggerFactory.getLogger("WxPayController");

  13. @Autowired

  14. private WxPayConfig payConfig;

  15. @Autowired

  16. private WxPayService payService;

  17. /**

  18. *微信公眾號支付,業務方法

  19. * @param response

  20. * @param request

  21. */

  22. @RequestMapping(value = "toPayInfo")

  23. public void getJSSDKPayInfo(HttpServletResponse response,

  24. HttpServletRequest request) {

  25. DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");

  26. String orderSubject = "停車繳費0.01元";//商品描述

  27. String merchantTradeNo = df.format(new Date());//商戶訂單號

  28. Integer totalAmount = 1;//訂單總金額,單位為分

  29. String goodsDesc = "萬達停車場下午1點-9點";

  30. String gooodsCode = "code"+merchantTradeNo;

  31. Map<String, Object> payData = getPayData( merchantTradeNo,

  32. orderSubject,

  33. totalAmount,

  34. goodsDesc,

  35. gooodsCode);

  36. resultPayData(response,payData);

  37. return;

  38. }

  39. /** 微信公眾號支付介面,通過引數生成網頁微信js支付引數,掉起支付介面必須引數

  40. * @param merchantTradeNo 商戶訂單號(必填)

  41. * @param orderSubject 訂單名稱(必填)

  42. * @param totalAmount 訂單金額,單位分(必填)

  43. * @param goodsDesc 商品描述(可空)

  44. * @param gooodsCode 商品編碼(可空)

  45. * Date:2017年12月4日上午11:04:04

  46. * @author 吉文劍

  47. */

  48. private Map<String, Object> getPayData(String merchantTradeNo,

  49. String orderSubject,

  50. Integer totalAmount,

  51. String goodsDesc,

  52. String gooodsCode){

  53. Map<String, Object> map = new HashMap<String, Object>();

  54. ConstantUtils instance = ConstantUtils.getInstance();

  55. ZuUser sessUser = (ZuUser) session.getAttribute(BaseController.SESSION_USER);

  56. if(null == sessUser || !StringUtils.isValid(sessUser.getOpenId())){

  57. map.put("result", false);

  58. return map;

  59. }

  60. WxPayUnifiedOrderRequest prepayInfo = WxPayUnifiedOrderRequest.newBuilder()

  61. .openid(sessUser.getOpenId())

  62. .outTradeNo(merchantTradeNo)

  63. .totalFee(totalAmount)

  64. .body(orderSubject)

  65. .tradeType(WeixinUtils.TRADE_TYPE)

  66. .spbillCreateIp(request.getRemoteAddr())

  67. .notifyURL(instance.getPropertyValue("WEB_URL")+WeixinUtils.NOTIFY_URL)

  68. .nonceStr(WeixinUtils.getNonceStr())

  69. .detail(goodsDesc)

  70. .productId(gooodsCode)

  71. .build();

  72. try {

  73. Map<String, String> payInfo = this.payService.getPayInfo(prepayInfo);

  74. map.put("result", true);

  75. map.put("data", payInfo);

  76. } catch (WxPayException e) {

  77. map.put("result", false);

  78. map.put("data", e.getErrCodeDes());

  79. this.logger.error(e.getErrCodeDes());

  80. e.printStackTrace();

  81. }

  82. return map;

  83. }

  84. /**

  85. * 微信通知支付結果的回撥地址,notifyCallback

  86. *

  87. * @param request

  88. * @param response

  89. */

  90. @RequestMapping(value = "notifyCallback")

  91. public void notifyCallback(HttpServletRequest request, HttpServletResponse response) {

  92. try {

  93. synchronized (this) {

  94. Map<String, String> kvm = XMLUtil.parseRequestXmlToMap(request);

  95. String orderCode = null;//回撥 支付訂單號

  96. String resultCode = null;//回撥支付是否成功狀態嗎

  97. String totalFee = null;//支付金額

  98. System.out.println("微信支付回撥引數:");

  99. System.out.println(kvm);

  100. if (SignUtils.checkSign(kvm, this.payConfig.getMchKey())) {

  101. orderCode = kvm.get("out_trade_no");

  102. resultCode = kvm.get("result_code");

  103. totalFee = kvm.get("total_fee");

  104. if ("SUCCESS".equals(resultCode)) {

  105. //TODO(user) 微信伺服器通知此回撥介面支付成功後,通知給業務系統做處理

  106. logger.info("out_trade_no: " + orderCode + " pay SUCCESS!");

  107. response.getWriter().write(WeixinUtils.WX_PAY_SUCCESS);

  108. } else {

  109. this.logger.error("out_trade_no: " + orderCode + " result_code is FAIL");

  110. response.getWriter().write(WeixinUtils.WX_PAY_FAIL);

  111. }

  112. } else {

  113. this.logger.error("out_trade_no: " + orderCode + " check signature FAIL");

  114. response.getWriter().write(WeixinUtils.WX_PAY_SIGN_FAIL);

  115. }

  116. if("SUCCESS".equals(resultCode)){

  117. //支付成功的業務邏輯

  118. //totalFee 要判斷支付金額是否等於訂單金額!!!

  119. System.out.println("支付成功:訂單號:"+orderCode+",支付金額:"+totalFee);

  120. }else{

  121. //支付失敗的業務邏輯

  122. System.out.println("微信支付 回撥 :*-************支付失敗");

  123. }

  124. }

  125. } catch (Exception e) {

  126. e.printStackTrace();

  127. }

  128. }

  129. /**

  130. * 客戶端返回JSON字串

  131. * @param response

  132. * @param object

  133. * @return

  134. */

  135. protected String resultPayData(HttpServletResponse response, Object object) {

  136. try {

  137. response.reset();

  138. response.setContentType("application/json");

  139. response.setCharacterEncoding("utf-8");

  140. //解決跨域問題

  141. response.setHeader("Access-Control-Allow-Origin", "*");

  142. response.getWriter().print(new Gson().toJson(object));

  143. return null;

  144. } catch (IOException e) {

  145. return null;

  146. }

  147. }

  148. }

2.3:自己整理了一個工具類,你們後面會用的到的,基本都是寫配置項,不會變的:

  1. /**

  2. * 微信通用工具類

  3. * Project Name:zc_app_api

  4. * File Name:WeixinUtils.java

  5. * Package Name:com.zc.app.api.utils

  6. * Date:2017年10月18日下午1:45:01

  7. * @author 吉文劍

  8. */

  9. public class WeixinUtils {

  10. /** 微信支付回撥支付成功,返回成功結果 */

  11. public static final String WX_PAY_SUCCESS = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[ok]]></return_msg></xml>";

  12. /** 微信支付回撥支付失敗,返回失敗結果 */

  13. public static final String WX_PAY_FAIL = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[result_code is FAIL]]></return_msg></xml>";

  14. /** 微信支付回撥支付sign驗證失敗,返回sign驗證失敗結果 */

  15. public static final String WX_PAY_SIGN_FAIL = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[check signature FAIL]]></return_msg></xml>";

  16. /** 微信支付回撥地址路徑 */

  17. public static final String NOTIFY_URL = "/wxPay/notifyCallback.do";

  18. /** 微信獲取微信使用者授權後用戶資訊 地址路徑 */

  19. public static final String OAUTH2_USERINFO_URL = "/wx/getOAuth2UserInfo.do";

  20. /** 微信官方api介面 */

  21. public static final String URL_OAUTH2 = "https://open.weixin.qq.com/connect/oauth2/authorize?";

  22. /** 獲取微信使用者資訊 */

  23. public static final String SCOPE = "snsapi_userinfo";

  24. /** 交易型別 :jsapi代表微信公眾號支付 */

  25. public static final String TRADE_TYPE = "JSAPI";

  26. /** 獲取微信openId URL */

  27. public String getWxOpenIdUrl(String toUrl){

  28. ConstantUtils instance = ConstantUtils.getInstance();

  29. StringBuffer url = new StringBuffer();

  30. url.append(URL_OAUTH2)

  31. .append("appid=").append(instance.getPropertyValue("WX_APPID"))

  32. .append("&redirect_uri=").append(instance.getPropertyValue("WX_WEB_URL") + OAUTH2_USERINFO_URL)

  33. .append("&response_type=code")

  34. .append("&scope=").append(SCOPE)

  35. .append("&state=").append(toUrl)

  36. .append("#wechat_redirect");

  37. return url.toString();

  38. }

  39. /**

  40. * 獲得微信支付隨機碼

  41. * @return

  42. * Date:2017年12月4日上午9:50:48

  43. * @author 吉文劍

  44. */

  45. public static String getNonceStr(){

  46. return UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();

  47. }

  48. }


2.4:其他相關工具類:

  1. /**

  2. * @ClassName: ConstantUtils

  3. * @Description: (讀取配置檔案的資訊,一些公共的屬性,引數配置在)

  4. * @author JiWenJian

  5. */

  6. public class ConstantUtils {

  7. private static final String FILEPATH = "/zc_app_api.properties";

  8. private static ConstantUtils instance;

  9. private ConstantUtils(){

  10. }

  11. public static ConstantUtils getInstance(){

  12. if(null == instance ){

  13. instance = new ConstantUtils();

  14. }

  15. return instance;

  16. }

  17. /**

  18. * @Description: (讀取檔案資訊)

  19. * @author JiWenJian

  20. * @date 2012-11-22 下午01:42:08

  21. * @param key

  22. * @return

  23. */

  24. public String getPropertyValue(String key) {

  25. Properties props = new Properties();

  26. try {

  27. InputStream in = getClass().getResourceAsStream(FILEPATH);

  28. props.load(in);

  29. return props.getProperty(key);

  30. } catch (Exception e) {

  31. e.printStackTrace();

  32. return null;

  33. }

  34. }

  35. /**

  36. * @Description: (讀取配置Integer數值資訊)

  37. * @author JiWenJian

  38. * @date 2012-11-22 下午01:42:08

  39. * @param key

  40. * @return

  41. */

  42. public Integer getPropertyIntegerValue(String key) {

  43. Properties props = new Properties();

  44. try {

  45. InputStream in = getClass().getResourceAsStream(FILEPATH);

  46. props.load(in);

  47. return Integer.parseInt(props.getProperty(key));

  48. } catch (Exception e) {

  49. e.printStackTrace();

  50. return null;

  51. }

  52. }

  53. }

  1. /**

  2. * xml解析成map物件

  3. */

  4. public class XMLUtil {

  5. /**

  6. * 將微信伺服器傳送的Request請求中Body的XML解析為Map

  7. *

  8. * @param request

  9. * @return

  10. * @throws Exception

  11. */

  12. public static Map<String, String> parseRequestXmlToMap(HttpServletRequest request) throws Exception {

  13. // 解析結果儲存在HashMap中

  14. Map<String, String> resultMap;

  15. InputStream inputStream = request.getInputStream();

  16. resultMap = parseInputStreamToMap(inputStream);

  17. return resultMap;

  18. }

  19. /**

  20. * 將輸入流中的XML解析為Map

  21. *

  22. * @param inputStream

  23. * @return

  24. * @throws DocumentException

  25. * @throws IOException

  26. */

  27. public static Map<String, String> parseInputStreamToMap(InputStream inputStream) throws DocumentException, IOException {

  28. // 解析結果儲存在HashMap中

  29. Map<String, String> map = new HashMap<String, String>();

  30. // 讀取輸入流

  31. SAXReader reader = new SAXReader();

  32. Document document = reader.read(inputStream);

  33. //得到xml根元素

  34. Element root = document.getRootElement();

  35. // 得到根元素的所有子節點

  36. List<Element> elementList = root.elements();

  37. //遍歷所有子節點

  38. for (Element e : elementList) {

  39. map.put(e.getName(), e.getText());

  40. }

  41. //釋放資源

  42. inputStream.close();

  43. return map;

  44. }

  45. /**

  46. * 將String型別的XML解析為Map

  47. *

  48. * @param str

  49. * @return

  50. * @throws Exception

  51. */

  52. public static Map<String, String> parseXmlStringToMap(String str) throws Exception {

  53. Map<String, String> resultMap;

  54. InputStream inputStream = new ByteArrayInputStream(str.getBytes("UTF-8"));

  55. resultMap = parseInputStreamToMap(inputStream);

  56. return resultMap;

  57. }

  58. }

3:下面到了前臺html頁面,只剩下最後一小步了,只要掉起微信支付密碼介面就大功告成了;

  1. <head>

  2. <script type="text/javascript" src="http://g.alicdn.com/sj/lib/jquery/dist/jquery.min.js"></script>

  3. <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>

  4. <script src="../js/common.js"></script>

  5. <script type="text/javascript">

  6. function toPay(){

  7. if (typeof WeixinJSBridge == "undefined"){

  8. if( document.addEventListener ){

  9. document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);

  10. }else if (document.attachEvent){

  11. document.attachEvent('WeixinJSBridgeReady', onBridgeReady);

  12. document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);

  13. }

  14. }else{

  15. onBridgeReady();

  16. }

  17. }

  18. function onBridgeReady(){

  19. $.ajax({

  20. type: "POST",

  21. url: DEF_GLOBAL_DOMAIN+"/wxPay/toPayInfo.do",

  22. success: function(data){

  23. console.log(data);

  24. WeixinJSBridge.invoke(

  25. 'getBrandWCPayRequest', {

  26. "appId" : data.data.appId,

  27. "timeStamp": data.data.timeStamp,

  28. "nonceStr" : data.data.nonceStr,

  29. "package" : data.data.package,

  30. "signType" : data.data.signType,

  31. "paySign" : data.data.paySign

  32. },function(res){

  33. $("#msgId").html(res.err_msg);

  34. if(res.err_msg == "get_brand_wcpay_request:ok"){

  35. $("#resId").html("支付成功");

  36. // location.href="weixinPayResult.html";//支付成功跳轉到指定頁面

  37. }else if(res.err_msg == "get_brand_wcpay_request:cancel"){

  38. $("#resId").html("支付取消");

  39. }else{

  40. $("#resId").html("支付失敗");

  41. }

  42. });

  43. }

  44. });

  45. }

  46. </script>

  47. </head>

  48. <body>

  49. <div class="content">

  50. <div class="form-area">

  51. <div class="inp">

  52. 支付0.01元

  53. </div>

  54. <button class="em-submit-st2" onclick="toPay()" >

  55. 確定支付

  56. </button>

  57. </div>

  58. 結果:

  59. <p/>

  60. <div id="resId"></div>

  61. 引數:

  62. <p/>

  63. <div id="invokeId"></div>

  64. <br/><p/>

  65. 返回:

  66. <p/>

  67. <div id="msgId"></div>

  68. </div>

  69. </body>

  70. </html>

到這裡整個微信支付就完成了,總結:難點在於微信平臺繁瑣的配置,很繁瑣,錯一個整個都跑不通,還以為是自己程式碼有問題,坑爹。程式碼層,感覺沒有什麼複雜量,就做一些配置就好了,至於微信老是喜歡用各種 簽名    sign  

這個工具已經給我們解決了,只要各個地方的配置沒有問題,其實就很簡單了。

整個執行流程 是 :

微信點選支付按鈕——》

       傳送ajax到支付請求控制器——》

       返回支付引數——》

       用支付引數,呼叫微信內嵌的掉起支付js方法,發起支付——》

      支付結果同步返回結果——》

      支付結果非同步傳送到後臺回撥控制器做結果處理

感謝同是天涯程式碼人,花這麼長時間來參讀本文章,加油把。。。