勇敢向前 發表於 2020-12-3 17:06:00

uni-app开发经验分享八: 实现微信APP支付的全过程详解

<h2>背景</h2>
<p>最近项目使用uni-app实现微信支付,把过程简单记录下,帮助那些刚刚基础uni-app,苦于文档的同学们。<br>整体来说实现过程和非uni-app的实现方式没有太大不同,难点就在于uni-app对于orderInfo的格式没有说明。</p>
<h2>准备工作</h2>
<ol>
<li>申请了商户号,拿到了API秘钥。这个需要微信开发平台,相关的工作大家百度。</li>
<li>后面代码里用到的appid和秘钥之类需要实现申请号。</li>
<li>在uni-app manifest.json 配置sdk支付权限</li>
</ol>
<p><img src="https://img2020.cnblogs.com/blog/2149129/202012/2149129-20201203170047818-769369869.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2>前端代码</h2>
<ol>
<li>onload阶段获取了可用支付列表,这里我们只用到了微信支付。</li>
<li>requestPayment&nbsp;&nbsp;</li>
</ol>
<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; a. getOrderInfo 获取到订单信息,主要是prepayid,对应统一下单api的返回值。</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b. uni.requestPayment发起支付,效果就是弹出微信支付框输入密码支付。第一个参数是“wxpay”,第二个参数就是OrderInfo.</p>
<div class="cnblogs_Highlighter">
<pre class="brush:css;gutter:true;">前端代码很简单,重点是如何让后端返回OrderInfo以及OrderInfo的格式。
</pre>
</div>
<p>  </p>
<p>前端代码如下:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:css;gutter:true;">&lt;template&gt;
    &lt;view&gt;
      &lt;page-head :title="title"&gt;&lt;/page-head&gt;
      &lt;view class="uni-padding-wrap"&gt;
            &lt;view style="background:#FFF; padding:50upx 0;"&gt;
                &lt;view class="uni-hello-text uni-center"&gt;支付金额&lt;/text&gt;&lt;/view&gt;
                &lt;view class="uni-h1 uni-center uni-common-mt"&gt;
                  &lt;text class="rmbLogo"&gt;¥&lt;/text&gt;
                  &lt;input class="price" type="digit" :value="price" maxlength="4" @input="priceChange" /&gt;
                &lt;/view&gt;
            &lt;/view&gt;
            &lt;view class="uni-btn-v uni-common-mt"&gt;
                &lt;!-- #ifdef APP-PLUS --&gt;
                &lt;template v-if="providerList.length &gt; 0"&gt;
                  &lt;button v-for="(item,index) in providerList" :key="index" @click="requestPayment(item,index)"
                        :loading="item.loading"&gt;{{item.name}}支付&lt;/button&gt;
                &lt;/template&gt;
                &lt;!-- #endif --&gt;
            &lt;/view&gt;
      &lt;/view&gt;
    &lt;/view&gt;
    &lt;/view&gt;
&lt;/template&gt;
&lt;script&gt;
    export default {
      data() {
            return {
                title: 'request-payment',
                loading: false,
                price: 1,
                providerList: []
            }
      },
      onLoad: function() {
            // #ifdef APP-PLUS
            uni.getProvider({
                service: "payment",
                success: (e) =&gt; {
                  console.log("payment success:" + JSON.stringify(e));
                  let providerList = [];
                  e.provider.map((value) =&gt; {
                        switch (value) {
                            case 'alipay':
                              providerList.push({
                                    name: '支付宝',
                                    id: value,
                                    loading: false
                              });
                              break;
                            case 'wxpay':
                              providerList.push({
                                    name: '微信',
                                    id: value,
                                    loading: false
                              });
                              break;
                            default:
                              break;
                        }
                  })
                  this.providerList = providerList;
                },
                fail: (e) =&gt; {
                  console.log("获取支付通道失败:", e);
                }
            });
            // #endif
      },
      methods: {
            async requestPayment(e, index) {
                this.providerList.loading = true;
                let orderInfo = await this.getOrderInfo(e.id);
                console.log("得到订单信息", orderInfo);
                if (orderInfo.statusCode !== 200) {
                  console.log("获得订单信息失败", orderInfo);
                  uni.showModal({
                        content: "获得订单信息失败",
                        showCancel: false
                  })
                  return;
                }
                uni.requestPayment({
                  provider: e.id,
                  orderInfo: orderInfo.data.data,
                  success: (e) =&gt; {
                        console.log("success", e);
                        uni.showToast({
                            title: "感谢您的赞助!"
                        })
                  },
                  fail: (e) =&gt; {
                        console.log("fail", e);
                        uni.showModal({
                            content: "支付失败,原因为: " + e.errMsg,
                            showCancel: false
                        })
                  },
                  complete: () =&gt; {
                        this.providerList.loading = false;
                  }
                })
            },
            getOrderInfo(e) {
                let appid = "";
                // #ifdef APP-PLUS
                appid = plus.runtime.appid;
                // #endif
                let url = 'http://10.10.60.200:8070/sc-admin/sales/wx/prepay/?brokerId=shba01';
                return new Promise((res) =&gt; {
                  uni.request({
                        url: url,
                        success: (result) =&gt; {
                            res(result);
                        },
                        fail: (e) =&gt; {
                            res(e);
                        }
                  })
                })
            },
            priceChange(e) {
                console.log(e.detail.value)
                this.price = e.detail.value;
            }
      }
    }
&lt;/script&gt;

&lt;style&gt;
    .rmbLogo {
      font-size: 40upx;
    }

    button {
      background-color: #007aff;
      color: #ffffff;
    }

    .uni-h1.uni-center {
      display: flex;
      flex-direction: row;
      justify-content: center;
      align-items: flex-end;
    }

    .price {
      border-bottom: 1px solid #eee;
      width: 200upx;
      height: 80upx;
      padding-bottom: 4upx;
    }

    .ipaPayBtn {
      margin-top: 30upx;
    }
&lt;/style&gt;
</pre>
</div>
<p>  </p>
<h2>后端代码(springboot)</h2>
<p>核心代码</p>
<div class="cnblogs_Highlighter">
<pre class="brush:css;gutter:true;">import com.alibaba.fastjson.JSONObject;
import com.bian.common.core.domain.AjaxResult;
import com.bian.common.utils.StringUtils;
import com.bian.framework.config.jwt.AuthService;
import com.bian.sales.entity.Constant;
import com.bian.sales.entity.PayInfo;
import com.bian.sales.service.IWxService;
import com.bian.sales.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.thoughtworks.xstream.XStream;
import org.springframework.http.HttpEntity;
import org.slf4j.Logger;
import javax.servlet.http.HttpServletRequest;
import java.util.*;

@Service
public class WxServiceImpl implements IWxService
{
    Logger logger = LoggerFactory.getLogger(WxServiceImpl.class);

    @Override
    public AjaxResult goWeChatPay(String brokerId, HttpServletRequest request) throws Exception {
      String clientIP = CommonUtil.getClientIp(request);
      logger.error("openId: " + brokerId + ", clientIP: " + clientIP);
      String randomNonceStr = RandomUtils.generateMixString(32);
      Map&lt;String, String&gt; result = unifiedOrder(brokerId, clientIP, randomNonceStr);
      System.out.println(request.toString());
      if(StringUtils.isBlank(result.get("prepay_id"))) {
         return AjaxResult.error("支付错误");
      } else {
            logger.info("支付成功");
            Map &lt;String,Object&gt;jsonObject = new LinkedHashMap();
            String noncestr = RandomUtils.generateMixString(32);
            String prepayid = result.get("prepay_id");
            String timestamp = String.valueOf(new Date().getTime()/1000);
            jsonObject.put("appid",Constant.APP_ID);
            jsonObject.put("noncestr",noncestr);
            jsonObject.put("package","Sign=WXPay");
            jsonObject.put("partnerid",Constant.MCH_ID);
            jsonObject.put("prepayid",result.get("prepay_id"));
            jsonObject.put("timestamp",new Date().getTime()/1000);
            jsonObject.put("sign",getSignforPayment(noncestr,prepayid,timestamp ));
            return AjaxResult.success(jsonObject);
      }
    }

    /**
   * @Function: 去支付
   * @author:   YangXueFeng
   * @Date:   2019/6/14 16:50
   */
    /**
   * 调用统一下单接口
   * @param brokerId
   */
    private Map&lt;String, String&gt;
    (String brokerId, String clientIP, String randomNonceStr) {

      try {

            //生成预支付交易单,返回正确的预支付交易会话标识后再在APP里面调起支付
            String url = Constant.URL_UNIFIED_ORDER;

            PayInfo payInfo = createPayInfo(brokerId, clientIP, randomNonceStr);
            String md5 = getSign(payInfo);
            payInfo.setSign(md5);

            logger.error("md5 value: " + md5);

            String xml = CommonUtil.payInfoToXML(payInfo);
            xml = xml.replace("__", "_").replace("&lt;!]&gt;", "1");
            //xml = xml.replace("__", "_").replace("&lt;!]&gt;", "");
            logger.error(xml);

            StringBuffer buffer = HttpUtil.httpsRequest(url, "POST", xml);
            logger.error("unifiedOrder request return body: \n" + buffer.toString());
            Map&lt;String, String&gt; result = CommonUtil.parseXml(buffer.toString());


            String return_code = result.get("return_code");
            if(StringUtils.isNotBlank(return_code) &amp;&amp; return_code.equals("SUCCESS")) {

                String return_msg = result.get("return_msg");
                if(StringUtils.isNotBlank(return_msg) &amp;&amp; !return_msg.equals("OK")) {
                  logger.error("统一下单错误!");
                  return null;
                }

                String prepay_Id = result.get("prepay_id");
                return result;

            } else {
                return null;
            }

      } catch (Exception e) {
            e.printStackTrace();
      }

      return null;
    }

    /**
   * 生成统一下单接口的请求参数
   * https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
   * @param brokerId
   * @param clientIP
   * @param randomNonceStr
   * @return
   */
    private PayInfo createPayInfo(String brokerId, String clientIP, String randomNonceStr) {
      clientIP ="222.72.148.34";
      Date date = new Date();
      String timeStart = TimeUtils.getFormatTime(date, Constant.TIME_FORMAT);
      String timeExpire = TimeUtils.getFormatTime(TimeUtils.addDay(date, Constant.TIME_EXPIRE), Constant.TIME_FORMAT);

      String randomOrderId = CommonUtil.getRandomOrderId(); //生成随机商品订单号

      PayInfo payInfo = new PayInfo();
      payInfo.setAppid(Constant.APP_ID);
      payInfo.setMch_id(Constant.MCH_ID);
      payInfo.setDevice_info("WEB");
      payInfo.setNonce_str(randomNonceStr);
      payInfo.setSign_type("MD5");//默认即为MD5
      payInfo.setBody("必安glJSAPI支付测试");
      payInfo.setAttach("支付测试4luluteam");
      payInfo.setOut_trade_no(randomOrderId);
      payInfo.setTotal_fee(1);
      payInfo.setSpbill_create_ip(clientIP);
      payInfo.setTime_start(timeStart);
      payInfo.setTime_expire(timeExpire);
      payInfo.setNotify_url(Constant.URL_NOTIFY);
      payInfo.setTrade_type("APP");
      payInfo.setLimit_pay("no_credit");
//      payInfo.setOpenid(brokerId);
      return payInfo;
    }

    private String getSign(PayInfo payInfo) throws Exception {
      StringBuffer sb = new StringBuffer();
      sb.append("appid=" + payInfo.getAppid())
                .append("&amp;attach=" + payInfo.getAttach())
                .append("&amp;body=" + payInfo.getBody())
                .append("&amp;device_info=" + payInfo.getDevice_info())
                .append("&amp;limit_pay=" + payInfo.getLimit_pay())
                .append("&amp;mch_id=" + payInfo.getMch_id())
                .append("&amp;nonce_str=" + payInfo.getNonce_str())
                .append("&amp;notify_url=" + payInfo.getNotify_url())
//                .append("&amp;openid=" + payInfo.getOpenid())
                .append("&amp;out_trade_no=" + payInfo.getOut_trade_no())
                .append("&amp;sign_type=" + payInfo.getSign_type())
                .append("&amp;spbill_create_ip=" + payInfo.getSpbill_create_ip())
                .append("&amp;time_expire=" + payInfo.getTime_expire())
                .append("&amp;time_start=" + payInfo.getTime_start())
                .append("&amp;total_fee=" + payInfo.getTotal_fee())
                .append("&amp;trade_type=" + payInfo.getTrade_type())
                .append("&amp;key=" + Constant.API_KEY);

      System.out.println("排序后的拼接参数:" + sb.toString());
      return CommonUtil.getMD5(sb.toString().trim()).toUpperCase();
    }

    private String getSignforPayment(String noncestr,String prepayid,String timestamp) throws Exception {
      StringBuffer sb = new StringBuffer();
      sb.append("appid=" +Constant.APP_ID)
                .append("&amp;noncestr=" + noncestr)
                .append("&amp;package=" + "Sign=WXPay")
                .append("&amp;partnerid=" + Constant.MCH_ID)
                .append("&amp;prepayid=" + prepayid)
                .append("&amp;timestamp=" + timestamp)
                .append("&amp;key=" + Constant.API_KEY);

      System.out.println("排序后的拼接参数:" + sb.toString());
      return CommonUtil.getMD5(sb.toString().trim()).toUpperCase();
    }

}
</pre>
</div>
<p>  </p>
<p>代码说明<br>以上代码goWeChatPay从controller层跳转并返回结果给controller接口。所有过程参考微信官方文档的2个接口</p>
<ol>
<li>统一下单接口后端</li>
<li>调起支付接口前端已实现</li>

</ol>
<p>unifiedOrder对应了统一下单接口,看起来很复杂,其实就做了一件事就是拼接参数。拼接参数时涉及到了签名算法,理解这个算法是关键,建议花时间理解透彻这个算法。</p>
<p>createPayInfo,创建一个PayInfo的类,对应了提交的各个参数。<br>getSign,签名算法的具体实现,获得提交参数的sign。</p>
<p>以上完成了统一下单接口的过程,如果return_code返回“SUCCESS”,result_code返回OK,我们会获得prepay_id(预支付交易会话标识),到这里已经完成了后端内容。但为了使用uni-app我们需要按照如下格式返回给前端,</p>
<p>&nbsp;</p>
<p>格式如下:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:css;gutter:true;">{"data": {
                "appid": "wx0411fa6a39d61297",
                "noncestr": "5JigiIJicbq8hQI2",
                "package": "Sign=WXPay",
                "partnerid": "1230636401",
                "prepayid": "wx21204902147233e222e12d451613768000",
                "timestamp": 1571662142,
                "sign": "0E5C9B9B1C8D7497A234CCC3C721AB1F"
        },
        "statusCode": 200,
        "header": {
                "Content-Type": "text/plain;charset=UTF-8",
                "X-Android-Response-Source": "NETWORK 200",
                "Date": "Mon, 21 Oct 2019 12:49:02 GMT",
                "EagleId": "74cf71a315716621419605339e",
                "Vary": "",
                "X-Android-Received-Millis": "1571662145735",
                "Timing-Allow-Origin": "*",
                "_": "HTTP/1.1 200 OK",
                "X-Android-Selected-Protocol": "http/1.1",
                "Connection": "keep-alive",
                "Via": "cache28.l2et15-1, kunlun5.cn1241",
                "X-Android-Sent-Millis": "1571662145071",
                "Access-Control-Allow-Origin": "*",
                "Server": "Tengine",
                "Transfer-Encoding": "chunked"
        },
        "errMsg": "request:ok"
}
</pre>
</div>
<p>  </p>
<p>重点是data部分,就是uni-app要求的OrderInfo的格式,getSignforPayment就是该部分的签名算法。</p>
<p>以上如果实行正确,应该就可以正常发起微信支付。</p>
<h2>参考文档</h2>
<p>https://blog.csdn.net/zhuoganliwanjin/article/details/81872215</p><br><br>
来源:https://www.cnblogs.com/smileZAZ/p/14081365.html
頁: [1]
查看完整版本: uni-app开发经验分享八: 实现微信APP支付的全过程详解