H5開發app之微信支付

        前言,微信支付根據實際情況可以分爲如下圖中的幾種支付方式:

        但是,如果用H5開發的app,對於登錄或者支付,到底屬於H5登錄/支付還是屬於app登錄/支付,申請的時候需要申請移動端的還是網頁端的,到現在還沒搞清楚,不過就目前實踐的結果來看,在微信開放平臺,申請移動應用,然後開通微信登錄和支付權限,可以實現用H5技術開發的app中的微信登錄和支付。

        言歸正傳,當前實現微信支付,所使用的是用H5+vue做的頁面,用HBuilder打包成app,實現效果是在app中點支付時,調起微信客戶端輸入密碼實現支付功能。在這個過程中如果想用H5做的app調起微信,離不開一個叫HTML5 plus的一個東西,具體這個是啥看連接:https://www.cnblogs.com/gzhjj/p/11903773.html,找到這個纔是實現當前功能的最佳選擇,其中支付用到的是plus.payment方法,後面會具體說到。

 1、首先說下,用plus.payment實現微信支付的整體流程:

        (1)、app發起支付請求給自己服務器

        (2)、自己服務器封裝相應的數據內容請求微信服務器下單(即獲取下單數據)

        (3)、自己服務器將從微信服務器獲取的下單數據返回給app

        (4)、app拿到下單數據通過plus.payment中的方法向微信服務端發起請求,請求成功後會調起微信客戶端輸入密碼進行支付。

2、前後端代碼實現流程及講解:

        (1)、前端主要使用vue框架,支付功能實現主要基於html5+中的plus.payment的plus.payment.getChannels和plus.payment.request兩個方法實現,前者主要獲取支付通道,後者向微信客戶端發起支付請求。plus.payment的具體簡介詳見官網:http://www.html5plus.org/doc/zh_cn/payment.html

具體代碼如下:

            a. 點擊支付按鈕,選擇微信支付:

<div class="zyy_payBox tab">
    <div class="WeChat opt" @click="wxPay">
        <input class="magic-radio" type="radio" name="radio" id="r1" value="option1" checked="">
        <label for="r1"><img src="../../assets/images/WeChat.png" >微信支付</label>
    </div>
    <div class="Alipay opt" @click="aliPay">
        <input class="magic-radio" type="radio" name="radio" id="r2" value="option2">
        <label for="r2"><img src="../../assets/images/Alipay.png" >支付寶支付</label>
    </div>
</div>

        b. 點擊微信支付按鈕,調用如下方法

wxPay(){
    var _self = this;
    //設備信息加載完成,先獲取微信的支付通道
    var payChanel;
	  plus.payment.getChannels(function(channels) {
	   for (var i in channels) {
	    if (channels[i].id == "wxpay") {
          payChanel = channels[i];
	    }
	   }
	  }, function(e) {
	   alert("獲取支付通道失敗:" + e.message);
    });
    //請求後臺服務器,通過後臺服務器向微信客戶端請求下單數據
    toWxpay({ "orderId": this.orderId}).then(res => {
      //_self.$toast("後臺返回數據:"+JSON.stringify(res.data.resp));
	  //後臺獲取下單數據,前臺封裝成json格式傳給request方法去微信服務端發起支付請求,請求成功後會調起微信客戶端輸入密碼進行支付操作
	   plus.payment.request(payChanel, JSON.stringify(res.data.resp), function(result) {
	   //支付成功,自己業務邏輯的處理部分
        _self.$toast(JSON.stringify("付費成功"));
        updateOrderStatus({ "orderId": this.orderId, "orderStatus": 'order_paid_noShipped' }).then(res => {//支付成功,待發貨
          _self.$toast(res.data.errmsg);
          this.$router.push({
            path: "/mall/myorder",
            query: {
              orderType: "order_paid_noShipped",
              index:2
            }
          });
        });
	   }, function(e) {
	    _self.$toast("付費失敗");
      });
    })
},

        (2)、通過前端的toWxpay({ "orderId": this.orderId}).方法請求後臺服務,後臺服務調用微信提供的sdk中的接口方法去獲取下單數據,主要後臺代碼如下:

           a.  引入官方的sdk  jar包,包中提供接口去請求微信服務端獲取下單數據

<!--微信支付 -->
		<dependency>
			<groupId>com.github.wxpay</groupId>
			<artifactId>wxpay-sdk</artifactId>
			<version>0.0.3</version>
		</dependency>

            b. 封裝請求微信服務端下單的請求數據,然後向微信服務到發起請求,獲取下單數據,代碼如下:

    @RequestMapping(value = "/toWxpay")
	@ResponseBody
	public Object toWxpay(@RequestParam(value = "orderId") String orderId, HttpServletRequest request) {
		Orders order = ordersService.selectById(orderId);
		BigDecimal totalFee = new BigDecimal(order.getOrderTotalPrice()).multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_DOWN);
		try {
            //加載證書文件
            //證書文件爲申請微信支付時微信支付方給提供的安全證書文件即API證書
			WxPaymentUtils config = new WxPaymentUtils();
			WXPay wxpay = new WXPay(config);
            //-------開始 封裝調用微信下單接口的接口參數------
			Map<String, String> data = new HashMap<String, String>();
			// 商品描述交易字段格式根據不同的應用場景按照以下格式:
			// APP——需傳入應用市場上的APP名字-實際商品名稱,天天愛消除-遊戲充值。
			data.put("body", SystemProperties.getProperty(SystemProperties.APP_NAME)+"-"+order.getProductBaseInfoModelList().get(0).getProductName());
			// 商戶系統內部訂單號,要求32個字符內,只能是數字、大小寫字母_-|*且在同一個商戶號下唯一。詳見商戶訂單號
			data.put("out_trade_no", orderId);
			// 訂單總金額,單位爲分,詳見支付金額
			data.put("total_fee", totalFee.toString());
			// 支持IPV4和IPV6兩種格式的IP地址。調用微信支付API的機器IP
			data.put("spbill_create_ip", IpUtil.getIp(request));//"123.12.12.123"
			// 接收微信支付異步通知回調地址,通知url必須爲直接可訪問的url,不能攜帶參數。
			data.put("notify_url", "http://ceshi.nice.cdsfs.fsd");
			// 支付類型,此處指定爲掃碼支付 NATIVE APP
			data.put("trade_type", "APP");
            //--------結束 封裝調用微信下單接口的接口參數------

            //調用下單接口,請求微信服務端
			Map<String, String> resp = wxpay.unifiedOrder(data);
			String returnCode = resp.get("return_code");
			String resultCode = resp.get("result_code");
			System.out.println(resp);
			// 字段在return_code 和result_code都爲SUCCESS的時候爲下單成功
			if (StringUtils.equals(returnCode, "SUCCESS") && StringUtils.equals(resultCode, "SUCCESS")) {
                
				resp.put("timestamp", (System.currentTimeMillis() / 1000) + "");
				resp.put("nonce_str", WXPayUtil.generateNonceStr());
                //------開始  獲取並封裝微信下單數據--------
				HashMap<String, String> map = new HashMap<>();
				map.put("appid", resp.get("appid"));
				// 商戶號
				map.put("partnerid", resp.get("mch_id"));
				map.put("prepayid", resp.get("prepay_id"));
				map.put("package", "Sign=WXPay");
				map.put("noncestr", resp.get("nonce_str"));
				map.put("timestamp", resp.get("timestamp"));
                //SystemProperties.getProperty(SystemProperties.WX_KEY)爲申請微信支付中的API密鑰
				String sign = WXPayUtil.generateSignature(map, SystemProperties.getProperty(SystemProperties.WX_KEY),
						WXPayConstants.SignType.MD5);
				resp.put("sign", sign);
				resp.put("accountType", "微信");
				
				map.put("sign", sign);
				logger.info("【統一下單API生成訂單信息】:{}=", map);
				//--------結束 封裝獲取的微信下單數據------
				Map<String, Object> obj = new HashedMap<String, Object>();
				obj.put("resp", map);
				obj.put("errno", 0);
				obj.put("errmsg", "成功");
				return obj;
			} else {
				return null;
			}
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("【請求微信統一下單API失敗】{}=", e.getMessage());
			return null;
		}
	}

上圖代碼中的WxPaymentUtils工具方法代碼 如下:

public class WxPaymentUtils implements WXPayConfig {
	private static Logger log = LoggerFactory.getLogger(WxPaymentUtils.class);
	private static final String NOTIFY_URL = "";

	private byte[] certData;

	/**
	 * 加載證書文件
	 */
	public WxPaymentUtils() throws Exception {
		InputStream certStream1 = this.getClass().getResourceAsStream("apiclient_cert.p12");
		log.info("certStream1=="+certStream1);
		File file = new File("apiclient_cert.p12");
		InputStream certStream = new FileInputStream(file);
		log.info("certStream=="+certStream);
		this.certData = new byte[certStream.available()];
		certStream.read(this.certData);
		certStream.close();
	}

	/**
	 * 微信應用appid
	 */
	@Override
	public String getAppID() {
		return SystemProperties.getProperty(SystemProperties.WX_APPID);
	}

	/**
	 * 微信商戶id
	 */
	@Override
	public String getMchID() {
		return SystemProperties.getProperty(SystemProperties.WX_MCHID);
	}

	/**
	 * 微信後臺配置的密鑰
	 */
	@Override
	public String getKey() {
		return SystemProperties.getProperty(SystemProperties.WX_KEY);
	}

	@Override
	public InputStream getCertStream() {
		ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
		return certBis;
	}

	@Override
	public int getHttpConnectTimeoutMs() {
		return 8000;
	}

	@Override
	public int getHttpReadTimeoutMs() {
		return 10000;
	}
}

3、開發中遇到的問題及注意點

    (1)、問題1,在封裝請求參數沒問題的情況下,調用微信接口wxpay.unifiedOrder(data)時報如下錯誤:

javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:208)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1959)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1916)
        at sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1899)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1420)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1397)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:153)
        at com.github.wxpay.sdk.WXPay.requestWithoutCert(WXPay.java:119)
        at com.github.wxpay.sdk.WXPay.unifiedOrder(WXPay.java:347)
        at com.github.wxpay.sdk.WXPay.unifiedOrder(WXPay.java:326)
        at com.cnki.controller.productOrdersClientController.toWxpay(productOrdersClientController.java:394)
        at com.cnki.controller.productOrdersClientController$$FastClassBySpringCGLIB$$d544c361.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
        at com.cnki.controller.productOrdersClientController$$EnhancerBySpringCGLIB$$dc5db038.toWxpay(<generated>)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:877)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:112)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
        at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
        at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
        at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
        at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
        at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
        at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
        at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
        at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
        at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
        at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
        at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
        at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
        at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
        at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:90)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:155)
        at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:123)
        at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:800)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1471)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:90)
        at sun.security.validator.Validator.getInstance(Validator.java:179)
        at sun.security.ssl.X509TrustManagerImpl.getValidator(X509TrustManagerImpl.java:312)
        at sun.security.ssl.X509TrustManagerImpl.checkTrustedInit(X509TrustManagerImpl.java:171)
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:184)
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1496)
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1026)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:961)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1072)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413)
        ... 98 more

解決方案,通過上網多方查詢後發現,是由於jdk版本引起的,使用open jdk時會報錯,轉換成Oracle jdks時,解決該問題,具體原因未查出,查看open jdk和Oracle jdk的區別後發現應該Oracle jdk和open的一些差別導致的,具體待後去查看源碼分析。

    (2)問題2,wxpay.unifiedOrder(data)中的封裝參數data中的回調地址參數notify_url,如何使用,目前仍不清楚,待研究中,有大佬知道的可以解答下。

    (3)注意點1,wxpay.unifiedOrder(data)請求微信服務器後返回經過處理返回給前臺plus.payment.request的參數具體內容及格式如下示例:

{
 package=Sign=WXPay,
 appid=wxc412651111111111,
 sign=1DA476960152F554A0643030C2F11111,
 partnerid=1601811111, 
 prepayid=wx03103856571156eefba34c60ce0d111111,
 noncestr=e1eaeb111fc44a55b04d95a864d0e2c2,
 timestamp=1606963132
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章