经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » HTML/CSS » HTML5 » 查看文章
uni-app开发经验分享八: 实现微信APP支付的全过程详解
来源:cnblogs  作者:林恒  时间:2020/12/8 9:04:24  对本文有异议

背景

最近项目使用uni-app实现微信支付,把过程简单记录下,帮助那些刚刚基础uni-app,苦于文档的同学们。
整体来说实现过程和非uni-app的实现方式没有太大不同,难点就在于uni-app对于orderInfo的格式没有说明。

准备工作

  1. 申请了商户号,拿到了API秘钥。这个需要微信开发平台,相关的工作大家百度。
  2. 后面代码里用到的appid和秘钥之类需要实现申请号。
  3. 在uni-app manifest.json 配置sdk支付权限

 

 

 

前端代码

  1. onload阶段获取了可用支付列表,这里我们只用到了微信支付。
  2. requestPayment  

          a. getOrderInfo 获取到订单信息,主要是prepayid,对应统一下单api的返回值。

          b. uni.requestPayment发起支付,效果就是弹出微信支付框输入密码支付。第一个参数是“wxpay”,第二个参数就是OrderInfo.

  1. 前端代码很简单,重点是如何让后端返回OrderInfo以及OrderInfo的格式。

  

前端代码如下:

  1. <template>
  2. <view>
  3. <page-head :title="title"></page-head>
  4. <view class="uni-padding-wrap">
  5. <view style="background:#FFF; padding:50upx 0;">
  6. <view class="uni-hello-text uni-center">支付金额</text></view>
  7. <view class="uni-h1 uni-center uni-common-mt">
  8. <text class="rmbLogo"></text>
  9. <input class="price" type="digit" :value="price" maxlength="4" @input="priceChange" />
  10. </view>
  11. </view>
  12. <view class="uni-btn-v uni-common-mt">
  13. <!-- #ifdef APP-PLUS -->
  14. <template v-if="providerList.length > 0">
  15. <button v-for="(item,index) in providerList" :key="index" @click="requestPayment(item,index)"
  16. :loading="item.loading">{{item.name}}支付</button>
  17. </template>
  18. <!-- #endif -->
  19. </view>
  20. </view>
  21. </view>
  22. </view>
  23. </template>
  24. <script>
  25. export default {
  26. data() {
  27. return {
  28. title: 'request-payment',
  29. loading: false,
  30. price: 1,
  31. providerList: []
  32. }
  33. },
  34. onLoad: function() {
  35. // #ifdef APP-PLUS
  36. uni.getProvider({
  37. service: "payment",
  38. success: (e) => {
  39. console.log("payment success:" + JSON.stringify(e));
  40. let providerList = [];
  41. e.provider.map((value) => {
  42. switch (value) {
  43. case 'alipay':
  44. providerList.push({
  45. name: '支付宝',
  46. id: value,
  47. loading: false
  48. });
  49. break;
  50. case 'wxpay':
  51. providerList.push({
  52. name: '微信',
  53. id: value,
  54. loading: false
  55. });
  56. break;
  57. default:
  58. break;
  59. }
  60. })
  61. this.providerList = providerList;
  62. },
  63. fail: (e) => {
  64. console.log("获取支付通道失败:", e);
  65. }
  66. });
  67. // #endif
  68. },
  69. methods: {
  70. async requestPayment(e, index) {
  71. this.providerList[index].loading = true;
  72. let orderInfo = await this.getOrderInfo(e.id);
  73. console.log("得到订单信息", orderInfo);
  74. if (orderInfo.statusCode !== 200) {
  75. console.log("获得订单信息失败", orderInfo);
  76. uni.showModal({
  77. content: "获得订单信息失败",
  78. showCancel: false
  79. })
  80. return;
  81. }
  82. uni.requestPayment({
  83. provider: e.id,
  84. orderInfo: orderInfo.data.data,
  85. success: (e) => {
  86. console.log("success", e);
  87. uni.showToast({
  88. title: "感谢您的赞助!"
  89. })
  90. },
  91. fail: (e) => {
  92. console.log("fail", e);
  93. uni.showModal({
  94. content: "支付失败,原因为: " + e.errMsg,
  95. showCancel: false
  96. })
  97. },
  98. complete: () => {
  99. this.providerList[index].loading = false;
  100. }
  101. })
  102. },
  103. getOrderInfo(e) {
  104. let appid = "";
  105. // #ifdef APP-PLUS
  106. appid = plus.runtime.appid;
  107. // #endif
  108. let url = 'http://10.10.60.200:8070/sc-admin/sales/wx/prepay/?brokerId=shba01';
  109. return new Promise((res) => {
  110. uni.request({
  111. url: url,
  112. success: (result) => {
  113. res(result);
  114. },
  115. fail: (e) => {
  116. res(e);
  117. }
  118. })
  119. })
  120. },
  121. priceChange(e) {
  122. console.log(e.detail.value)
  123. this.price = e.detail.value;
  124. }
  125. }
  126. }
  127. </script>
  128.  
  129. <style>
  130. .rmbLogo {
  131. font-size: 40upx;
  132. }
  133.  
  134. button {
  135. background-color: #007aff;
  136. color: #ffffff;
  137. }
  138.  
  139. .uni-h1.uni-center {
  140. display: flex;
  141. flex-direction: row;
  142. justify-content: center;
  143. align-items: flex-end;
  144. }
  145.  
  146. .price {
  147. border-bottom: 1px solid #eee;
  148. width: 200upx;
  149. height: 80upx;
  150. padding-bottom: 4upx;
  151. }
  152.  
  153. .ipaPayBtn {
  154. margin-top: 30upx;
  155. }
  156. </style>

  

后端代码(springboot)

核心代码

  1. import com.alibaba.fastjson.JSONObject;
  2. import com.bian.common.core.domain.AjaxResult;
  3. import com.bian.common.utils.StringUtils;
  4. import com.bian.framework.config.jwt.AuthService;
  5. import com.bian.sales.entity.Constant;
  6. import com.bian.sales.entity.PayInfo;
  7. import com.bian.sales.service.IWxService;
  8. import com.bian.sales.util.*;
  9. import org.slf4j.Logger;
  10. import org.slf4j.LoggerFactory;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.stereotype.Service;
  13. import org.springframework.web.client.RestTemplate;
  14. import com.thoughtworks.xstream.XStream;
  15. import org.springframework.http.HttpEntity;
  16. import org.slf4j.Logger;
  17. import javax.servlet.http.HttpServletRequest;
  18. import java.util.*;
  19.  
  20. @Service
  21. public class WxServiceImpl implements IWxService
  22. {
  23. Logger logger = LoggerFactory.getLogger(WxServiceImpl.class);
  24.  
  25. @Override
  26. public AjaxResult goWeChatPay(String brokerId, HttpServletRequest request) throws Exception {
  27. String clientIP = CommonUtil.getClientIp(request);
  28. logger.error("openId: " + brokerId + ", clientIP: " + clientIP);
  29. String randomNonceStr = RandomUtils.generateMixString(32);
  30. Map<String, String> result = unifiedOrder(brokerId, clientIP, randomNonceStr);
  31. System.out.println(request.toString());
  32. if(StringUtils.isBlank(result.get("prepay_id"))) {
  33. return AjaxResult.error("支付错误");
  34. } else {
  35. logger.info("支付成功");
  36. Map <String,Object>jsonObject = new LinkedHashMap();
  37. String noncestr = RandomUtils.generateMixString(32);
  38. String prepayid = result.get("prepay_id");
  39. String timestamp = String.valueOf(new Date().getTime()/1000);
  40. jsonObject.put("appid",Constant.APP_ID);
  41. jsonObject.put("noncestr",noncestr);
  42. jsonObject.put("package","Sign=WXPay");
  43. jsonObject.put("partnerid",Constant.MCH_ID);
  44. jsonObject.put("prepayid",result.get("prepay_id"));
  45. jsonObject.put("timestamp",new Date().getTime()/1000);
  46. jsonObject.put("sign",getSignforPayment(noncestr,prepayid,timestamp ));
  47. return AjaxResult.success(jsonObject);
  48. }
  49. }
  50.  
  51. /**
  52. * @Function: 去支付
  53. * @author: YangXueFeng
  54. * @Date: 2019/6/14 16:50
  55. */
  56. /**
  57. * 调用统一下单接口
  58. * @param brokerId
  59. */
  60. private Map<String, String>
  61. (String brokerId, String clientIP, String randomNonceStr) {
  62.  
  63. try {
  64.  
  65. //生成预支付交易单,返回正确的预支付交易会话标识后再在APP里面调起支付
  66. String url = Constant.URL_UNIFIED_ORDER;
  67.  
  68. PayInfo payInfo = createPayInfo(brokerId, clientIP, randomNonceStr);
  69. String md5 = getSign(payInfo);
  70. payInfo.setSign(md5);
  71.  
  72. logger.error("md5 value: " + md5);
  73.  
  74. String xml = CommonUtil.payInfoToXML(payInfo);
  75. xml = xml.replace("__", "_").replace("<![CDATA[1]]>", "1");
  76. //xml = xml.replace("__", "_").replace("<![CDATA[", "").replace("]]>", "");
  77. logger.error(xml);
  78.  
  79. StringBuffer buffer = HttpUtil.httpsRequest(url, "POST", xml);
  80. logger.error("unifiedOrder request return body: \n" + buffer.toString());
  81. Map<String, String> result = CommonUtil.parseXml(buffer.toString());
  82.  
  83.  
  84. String return_code = result.get("return_code");
  85. if(StringUtils.isNotBlank(return_code) && return_code.equals("SUCCESS")) {
  86.  
  87. String return_msg = result.get("return_msg");
  88. if(StringUtils.isNotBlank(return_msg) && !return_msg.equals("OK")) {
  89. logger.error("统一下单错误!");
  90. return null;
  91. }
  92.  
  93. String prepay_Id = result.get("prepay_id");
  94. return result;
  95.  
  96. } else {
  97. return null;
  98. }
  99.  
  100. } catch (Exception e) {
  101. e.printStackTrace();
  102. }
  103.  
  104. return null;
  105. }
  106.  
  107. /**
  108. * 生成统一下单接口的请求参数
  109. * https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
  110. * @param brokerId
  111. * @param clientIP
  112. * @param randomNonceStr
  113. * @return
  114. */
  115. private PayInfo createPayInfo(String brokerId, String clientIP, String randomNonceStr) {
  116. clientIP ="222.72.148.34";
  117. Date date = new Date();
  118. String timeStart = TimeUtils.getFormatTime(date, Constant.TIME_FORMAT);
  119. String timeExpire = TimeUtils.getFormatTime(TimeUtils.addDay(date, Constant.TIME_EXPIRE), Constant.TIME_FORMAT);
  120.  
  121. String randomOrderId = CommonUtil.getRandomOrderId(); //生成随机商品订单号
  122.  
  123. PayInfo payInfo = new PayInfo();
  124. payInfo.setAppid(Constant.APP_ID);
  125. payInfo.setMch_id(Constant.MCH_ID);
  126. payInfo.setDevice_info("WEB");
  127. payInfo.setNonce_str(randomNonceStr);
  128. payInfo.setSign_type("MD5"); //默认即为MD5
  129. payInfo.setBody("必安glJSAPI支付测试");
  130. payInfo.setAttach("支付测试4luluteam");
  131. payInfo.setOut_trade_no(randomOrderId);
  132. payInfo.setTotal_fee(1);
  133. payInfo.setSpbill_create_ip(clientIP);
  134. payInfo.setTime_start(timeStart);
  135. payInfo.setTime_expire(timeExpire);
  136. payInfo.setNotify_url(Constant.URL_NOTIFY);
  137. payInfo.setTrade_type("APP");
  138. payInfo.setLimit_pay("no_credit");
  139. // payInfo.setOpenid(brokerId);
  140. return payInfo;
  141. }
  142.  
  143. private String getSign(PayInfo payInfo) throws Exception {
  144. StringBuffer sb = new StringBuffer();
  145. sb.append("appid=" + payInfo.getAppid())
  146. .append("&attach=" + payInfo.getAttach())
  147. .append("&body=" + payInfo.getBody())
  148. .append("&device_info=" + payInfo.getDevice_info())
  149. .append("&limit_pay=" + payInfo.getLimit_pay())
  150. .append("&mch_id=" + payInfo.getMch_id())
  151. .append("&nonce_str=" + payInfo.getNonce_str())
  152. .append("&notify_url=" + payInfo.getNotify_url())
  153. // .append("&openid=" + payInfo.getOpenid())
  154. .append("&out_trade_no=" + payInfo.getOut_trade_no())
  155. .append("&sign_type=" + payInfo.getSign_type())
  156. .append("&spbill_create_ip=" + payInfo.getSpbill_create_ip())
  157. .append("&time_expire=" + payInfo.getTime_expire())
  158. .append("&time_start=" + payInfo.getTime_start())
  159. .append("&total_fee=" + payInfo.getTotal_fee())
  160. .append("&trade_type=" + payInfo.getTrade_type())
  161. .append("&key=" + Constant.API_KEY);
  162.  
  163. System.out.println("排序后的拼接参数:" + sb.toString());
  164. return CommonUtil.getMD5(sb.toString().trim()).toUpperCase();
  165. }
  166.  
  167. private String getSignforPayment(String noncestr,String prepayid,String timestamp) throws Exception {
  168. StringBuffer sb = new StringBuffer();
  169. sb.append("appid=" +Constant.APP_ID)
  170. .append("&noncestr=" + noncestr)
  171. .append("&package=" + "Sign=WXPay")
  172. .append("&partnerid=" + Constant.MCH_ID)
  173. .append("&prepayid=" + prepayid)
  174. .append("&timestamp=" + timestamp)
  175. .append("&key=" + Constant.API_KEY);
  176.  
  177. System.out.println("排序后的拼接参数:" + sb.toString());
  178. return CommonUtil.getMD5(sb.toString().trim()).toUpperCase();
  179. }
  180.  
  181. }

  

代码说明
以上代码goWeChatPay从controller层跳转并返回结果给controller接口。所有过程参考微信官方文档的2个接口

  1. 统一下单接口后端
  2. 调起支付接口前端已实现

unifiedOrder对应了统一下单接口,看起来很复杂,其实就做了一件事就是拼接参数。拼接参数时涉及到了签名算法,理解这个算法是关键,建议花时间理解透彻这个算法。

createPayInfo,创建一个PayInfo的类,对应了提交的各个参数。
getSign,签名算法的具体实现,获得提交参数的sign。

以上完成了统一下单接口的过程,如果return_code返回“SUCCESS”,result_code返回OK,我们会获得prepay_id(预支付交易会话标识),到这里已经完成了后端内容。但为了使用uni-app我们需要按照如下格式返回给前端,

 

格式如下:

  1. {"data": {
  2. "appid": "wx0411fa6a39d61297",
  3. "noncestr": "5JigiIJicbq8hQI2",
  4. "package": "Sign=WXPay",
  5. "partnerid": "1230636401",
  6. "prepayid": "wx21204902147233e222e12d451613768000",
  7. "timestamp": 1571662142,
  8. "sign": "0E5C9B9B1C8D7497A234CCC3C721AB1F"
  9. },
  10. "statusCode": 200,
  11. "header": {
  12. "Content-Type": "text/plain;charset=UTF-8",
  13. "X-Android-Response-Source": "NETWORK 200",
  14. "Date": "Mon, 21 Oct 2019 12:49:02 GMT",
  15. "EagleId": "74cf71a315716621419605339e",
  16. "Vary": "[Accept-Encoding, Accept-Encoding]",
  17. "X-Android-Received-Millis": "1571662145735",
  18. "Timing-Allow-Origin": "*",
  19. "_": "HTTP/1.1 200 OK",
  20. "X-Android-Selected-Protocol": "http/1.1",
  21. "Connection": "keep-alive",
  22. "Via": "cache28.l2et15-1[208,0], kunlun5.cn1241[504,0]",
  23. "X-Android-Sent-Millis": "1571662145071",
  24. "Access-Control-Allow-Origin": "*",
  25. "Server": "Tengine",
  26. "Transfer-Encoding": "chunked"
  27. },
  28. "errMsg": "request:ok"
  29. }

  

重点是data部分,就是uni-app要求的OrderInfo的格式,getSignforPayment就是该部分的签名算法。

以上如果实行正确,应该就可以正常发起微信支付。

参考文档

https://blog.csdn.net/zhuoganliwanjin/article/details/81872215

原文链接:http://www.cnblogs.com/smileZAZ/p/14081365.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号