Core3.1 微信v3 JSAPI支付

1、前言

  “小魏呀,这个微信支付还要多久?”,“快了快了老板,就等着最后一步了。。。”,“搞快点哈,就等着上线呢”,“………” 

因公司业务需要微信支付,以前没弄过花了几天时间写了一个微信v3的JSAPI支付,我滴个乖乖,差点今年小孩的奶粉就没了,还好弄出来了。在这里面各种踩坑,在这里记录一下,我开发的是微信公众号上面拉起微信支付。后台是Core3.1的接口,前端用的是Vue。后面是部署在CentOS上面的

2、写代码之前的准备

 你必须要有一个非个人性质的公众号(服务号),还有一个微信商户号。服务号申请地址微信商户号申请地址,具体的根据网站申请中按人家要求来就行了。个人建议把申请下来的公众号里面的appid 、appsecret,微信商户平台,商户号等数据保存在数据库中。

3、公众号、商户号配置

    1)、公众号JS安全域名

登录公众号在左手边菜单:公众号设置—->功能设置——>JS安全域名—–>设置。在里面可以连写5个域名下载文件上传到服务器上面 域名要经过ICP备案。可以访问到上面说的那个文件就可以了。

core3.1Api 发布后你放根目录是访问不到的,在configure里面加上访问静态文件 app.UseStaticFiles();然后在根目录建一个文件夹wwwroot 吧域名验证需要的txt文件丢进去  我是这么搞点。暂时没有想到其他骚操作

这里有人要问了 这个设置了是干嘛的,以前我也不知道是干嘛的哈哈,总有一颗好奇的心想知道。现在想想个人理解这个JS安全域名就是一个验证的机制吧。这里设置了加上微信服务号也有一个类似的,后面就可以调用JSAPI支付了。

    2)、

这个紧接着在JS安全域名后面  跟着设置一下就可以了 我部署在CentOS上面 看一下文件夹目录,还有一个文件夹里面是是p12文件 后面会提到

 

这个网页授权意思就是后面要获取到用户的OpenId的时候 要通过这个域名授权。我们就能获取到用户的信息,授权登录这些配置。后面图上还有一个HHhhjZj的文件这个是商户号上面设置的。

    3)、微信商户号设置

在微信商户平台上面选择产品中心—->开发配置,这里面设置支付目录。我这里是设置的一个 ,我也不是申请商户号的人 也没有这个权限 。上面的界面跟上面两步骤差不多就不啰嗦了。

 

   4)、微信商户号的key,V3key设置

这里不再重复 参考微信开发文档  微信JSAPI开发接入前准备 //pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_1.shtml 慢慢来哦,设置秘钥 我倒是自己想着(阿猫阿狗888666)AMAG66688这种来拼够32位就可以了哈哈。

4、Core3.1后端代码 详解

前面这些弄好了只算搭好了环境 下面开始撸码。微信支付的逻辑就是,获取用户的OpenId——->统一下单获取payId————–>拉起微信支付————>支付回调接口写逻辑 

下面官网的

 

 

 

 

 

 

 

参考文档  JSAPI支付

   1)、封装微信请求类

这里我单独封装了一个微信支付的请求类。因为调用v3支付必须要符合APIV3接口规则 ,具体的在微信官方文档看

using App.Common.Base;
using Microsoft.AspNetCore.Hosting;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace App.Common.HttpHelper
{
    /// <summary>
    /// 请求类封装   别忘了 调用的时候添加 services.AddHttpClient() 调用都要加参数,实体类返回没有超时时间 
    /// 容器要添加
    /// </summary>
    public class HttpClientFactoryHelper
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private IWebHostEnvironment _webHostEnvironment;
        public HttpClientFactoryHelper(IHttpClientFactory httpClientFactory, IWebHostEnvironment webHostEnvironment)
        {
            _httpClientFactory = httpClientFactory;
            _webHostEnvironment = webHostEnvironment;
        }
       
        public void SaveLog(string text)
        {
            string thisTime = DateTime.Now.ToString("yyyyMMdd");
            var path = _webHostEnvironment.ContentRootPath + $"/ApiInterfaceErrorLog/";//绝对路径
            string dirPath = Path.Combine(path, thisTime + "/");//绝对径路 储存文件路径的文件夹
            if (!Directory.Exists(dirPath))//查看文件夹是否存在
                Directory.CreateDirectory(dirPath);
            string splitLine = "============================下一条==============================";
            string timeNow = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            using StreamWriter file = new StreamWriter(dirPath + thisTime + ".txt", true);
            file.WriteLine(timeNow+text);
            file.WriteLine(splitLine);
        }
        #region //微信支付请求
        /// <summary>
        /// 微信请求Post
        /// </summary>
        /// <param name="url">地址</param>
        /// <param name="requestString">参数</param>
        /// <param name="privateKey">私有秘钥 p12文件</param>
        /// <param name="merchantId">商户号</param>
        /// <param name="serialNo">商户证书号</param>
        /// <returns></returns>
        public async Task<string> WeChatPostAsync(string url,string requestString, string privateKey, string merchantId, string serialNo)
        {
            try
            {
                var client = _httpClientFactory.CreateClient();
                var requestContent = new StringContent(requestString);
                requestContent.Headers.ContentType.MediaType = "application/json";
                var auth = BuildAuthAsync(url, requestString, privateKey, merchantId, serialNo,"POST");
                string value = $"WECHATPAY2-SHA256-RSA2048 {auth}";
                client.DefaultRequestHeaders.Add("Authorization", value);
                client.DefaultRequestHeaders.Add("Accept", "application/json");
                client.DefaultRequestHeaders.Add("User-Agent", "WOW64");
                client.Timeout = TimeSpan.FromSeconds(60);
                var response = await client.PostAsync(url, requestContent);
                if (response.IsSuccessStatusCode)
                {
                    var result = await response.Content.ReadAsStringAsync();
                    return result;
                }
                else
                {
                    return $"接口【{url}】请求错误,错误代码{response.StatusCode},错误原因{response.ReasonPhrase}具体的话========================================\n{JsonConvert.SerializeObject(response)}";
                }
            }
            catch(Exception ex)
            {
                SaveLog($"接口【{DateTime.Now +url}】请求错误,错误代码{ex.Message}具体=============================================/n{ex.StackTrace}");
                return ex.Message + ex.StackTrace;
            }
        }
        /// <summary>
        /// 微信请求
        /// </summary>
        /// <param name="url">地址</param>
        /// <param name="requestString">参数</param>
        /// <param name="privateKey">私有秘钥 p12文件</param>
        /// <param name="merchantId">商户号</param>
        /// <param name="serialNo">商户证书号</param>
        /// <param name="method">Get或者Post</param>
        /// <returns></returns>
        public async Task<string> WeChatGetAsync(string url, string privateKey, string merchantId, string serialNo)
        {
            try
            {
                var client = _httpClientFactory.CreateClient();
                var auth = BuildAuthAsync(url, "", privateKey, merchantId, serialNo,"GET");
                string value = $"WECHATPAY2-SHA256-RSA2048 {auth}";
                client.DefaultRequestHeaders.Add("Authorization", value);
                client.DefaultRequestHeaders.Add("Accept", "*/*");
                client.DefaultRequestHeaders.Add("User-Agent", "WOW64");
                client.Timeout = TimeSpan.FromSeconds(60);
                var response = await client.GetAsync(url);
                if (response.IsSuccessStatusCode)
                {
                    var result = await response.Content.ReadAsStringAsync();
                    return result;
                }
                else
                {
                    return $"接口【{url}】请求错误,错误代码{response.StatusCode},错误原因{response.ReasonPhrase}";
                }
            }
            catch (Exception ex)
            {
                SaveLog($"接口【{DateTime.Now + url}】请求错误,错误代码{ex.Message}具体=============================================/n{ex.StackTrace}");
                return ex.Message+ ex.StackTrace;
            }
        }
        protected string BuildAuthAsync(string url,string jsonParame ,string privateKey, string merchantId,string serialNo,string method="")
        {
            string body = jsonParame;
            var uri = new Uri(url);
            var urlPath = uri.PathAndQuery;
            var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
            string nonce = Path.GetRandomFileName();

            string message = $"{method}\n{urlPath}\n{timestamp}\n{nonce}\n{body}\n";
            //string signature = Sign(message, privateKey);
            var path = _webHostEnvironment.WebRootPath + "/arsjkll/apiclient_cert.p12";
            string signature = Sign(message,path, "1601849569");
            return $"mchid=\"{merchantId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{serialNo}\",signature=\"{signature}\"";
        }
        /// <summary>
        /// 签名(CentOs 不支持 换了下面的)
        /// </summary>
        /// <param name="message">签名内容</param>
        /// <param name="privateKey">秘钥</param>
        /// <returns></returns>
        public string Sign(string message,string privateKey)
        {
            byte[] keyData = Convert.FromBase64String(privateKey);
            using CngKey cngKey = CngKey.Import(keyData, CngKeyBlobFormat.Pkcs8PrivateBlob);
            using RSACng rsa = new RSACng(cngKey);
            byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
            return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
        }
        /// <summary>
        /// 获取签名证书私钥
        /// </summary>
        /// <param name="priKeyFile">证书文件路径</param>
        /// <param name="keyPwd">密码</param>
        /// <returns></returns>
        private static RSA GetPrivateKey(string priKeyFile, string keyPwd)
        {
            var pc = new X509Certificate2(priKeyFile, keyPwd, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
            return (RSA)pc.PrivateKey;
        }
        /// <summary>
        //// <summary>
        /// 根据证书签名数据   后面要做成配置在数据库中
        /// </summary>
        /// <param name="data">要签名的数据</param>
        /// <param name="certPah">证书路径</param>
        /// <param name="certPwd">密码</param>
        /// <returns></returns>
        public string Sign(string data, string certPah, string certPwd)
        {
            var rsa = GetPrivateKey(certPah, certPwd);

            var rsaClear = new RSACryptoServiceProvider();

            var paras = rsa.ExportParameters(true);
            rsaClear.ImportParameters(paras);

            var signData = rsa.SignData(Encoding.UTF8.GetBytes(data), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
            return Convert.ToBase64String(signData);
        }
        #endregion

    }
    public class CustomerHttpException : Exception
    {
        public CustomerHttpException() : base()
        { }
        public CustomerHttpException(string message) : base(message)
        {
        }
    }
    public class ReturnData
    {
        /// <summary>
        /// 返回码
        /// </summary>
        public int Code { get; set; }
        /// <summary>
        /// 消息
        /// </summary>
        public string Msg { get; set; }
        /// <summary>
        /// 是否成功
        /// </summary>
        public bool IsSuccess { get; set; }
        /// <summary>
        /// 返回数据
        /// </summary>
        public object Data { get; set; }
        /// <summary>
        /// 成功默认
        /// </summary>
        /// <param name="data"></param>
        /// <param name="msg"></param>
        public static ReturnData ToSuccess(object data, string msg = "sussess")
        {
            var result = new ReturnData
            {
                Code = (int)HttpStatusCode.OK,
                IsSuccess = true,
                Msg = msg,
                Data = data
            };
            return result;

        }
        public static ReturnData ToFail(string msg)
        {
            var result = new ReturnData
            {
                Code = (int)HttpStatusCode.BadRequest,
                IsSuccess = false,
                Msg = msg
            };
            return result;
        }
        /// <summary>
        /// 异常
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="data"></param>
        public static ReturnData ToError(Exception ex, object data = null)
        {
            var result = new ReturnData
            {
                Code = (int)HttpStatusCode.InternalServerError,
                IsSuccess = false,
                Msg = "异常" + ex.Message,
                Data = data
            };
            return result;
        }
        /// <summary>
        /// 未经授权
        /// </summary>
        /// <param name="data"></param>
        public static ReturnData ToNoToken(object data = null)
        {
            var result = new ReturnData
            {
                Code = (int)HttpStatusCode.Forbidden,
                IsSuccess = false,
                Msg = "未经授权不能访问",
                Data = data
            };
            return result;
        }
        public static ReturnData Instance(object data, string msg, int code)
        {
            var result = new ReturnData
            {
                Code = code,
                IsSuccess = false,
                Msg = msg,
                Data = data
            };
            return result;

        }
    }
}

Sign签名官方给的只能在IIS上面运行 那是通过直接用私钥签名,我在CentOS上面不行

 以前在IIS上面也是 但是这个只要配置IIS应用程序池,把加载用户配置文件改成true就可以了。CentOS上面就不行了。后来我还是把p12文件放在了跟验证域名的那个位置,通过读取文件获取私钥。这个问题搞了我2天。。。不能跨平台。或者是我配置不对,后面有时间在研究。

    2)、获取用户的OpenId 

在用户统一下单的时候需要用户的OpenId就是这个用户在这个公众号下面的一个身份号码,关没关注获取了就不会变,所以我就是没调用统一下单之前就获取了保存在数据库中。统一下单的时候直接调用就可以了 参考连接  公众号网页授权

        private const string AuthorUrl = "//open.weixin.qq.com/connect/oauth2/authorize?";
        private const string Oauth = "//api.weixin.qq.com/sns/oauth2/access_token?";
        private const string GetUserInfo = "//api.weixin.qq.com/sns/userinfo?";
        public string GetAuthorizeUrl(string appId, string redirectUrl, string state = "state", string scope = "snsapi_userinfo", string responseType = "code")
        {
            redirectUrl = HttpUtility.UrlEncode(redirectUrl, System.Text.Encoding.UTF8);
            object[] args = new object[] { appId, redirectUrl, responseType, scope, state };
            return string.Format(AuthorUrl + "appid={0}&redirect_uri={1}&response_type={2}&scope={3}&state={4}#wechat_redirect", args);
        }
        private string GetOpenIdUrl(string appId, string secret, string code, string grantType = "authorization_code")
        {
            object[] args = new object[] { appId, secret, code, grantType };
            string requestUri = string.Format(Oauth + "appid={0}&secret={1}&code={2}&grant_type={3}", args);
            return requestUri;
        }
        public GetOpenIdDto GetOpenid(string appId, string secret, string code, string grantType = "authorization_code")
        {
            string requestUri = GetOpenIdUrl(appId, secret, code, grantType);
            var responseStr = _httpClientFactoryHelper.GetAsync(requestUri, null, 120).Result;
            var obj = JsonConvert.DeserializeObject<GetOpenIdDto>(responseStr);
            var getUserInfoUrl = GetUserInfo + $"access_token={obj.Access_Token}&openid={obj.OpenId}&lang=zh_CN";
            var responseUser = _httpClientFactoryHelper.GetAsync(getUserInfoUrl, null, 120).Result;
            SaveLog("OpenDetails", responseUser);
            var objUser = JsonConvert.DeserializeObject<GetOpenIdDto>(responseUser);
           
            
            return objUser;
        }

 

上面GetOpenId就是下面Api这里调用的

下面的接口地址就是/api/WeChatPay/SaveHospPatirntOpenId

 /// <summary>
        /// 储存用户所在公众号下面的OpenId
        /// </summary>
        /// <param name="hospCode">医院|公众号编码</param>
        /// <param name="userId">用户Id(登录的那个)</param>
        /// <param name="code">微信服务器返回的code不用填</param>
        /// <returns>跳转的returnUrl</returns>
        [HttpGet]
        public IActionResult SaveHospPatirntOpenId(string hospCode, int userId, string code = "")
        {
            var modelOpenId = _weChatPayService.IsSaveHospPatientOpenId(hospCode, userId);
            var model = _weChatPayService.GetHospInfo(hospCode);
            var modelNew = _weChatPayService.GetHospNewInfo(hospCode);
           // var path = _configuration.GetValue<string>("ReturnUrl");
            var returnUrl = $"//打码/#/subSite?hospCode={model.HospCode}&hospId={modelNew.Id}";
            //var returnUrl = HttpUtility.UrlEncode(returnUrltarget, System.Text.Encoding.UTF8);
            if (modelOpenId != null)
                return Redirect(returnUrl);
            else
            {
               
                if (string.IsNullOrEmpty(code))
                {
                    var redirectUrl = _weChatPayService.GetAuthorizeUrl(model.WxAppid, $"//打码/WeChatPay/SaveHospPatirntOpenId?hospCode={hospCode}&userId={userId}");
                    return Redirect(redirectUrl);
                }
                else
                {
                    //根据code和微信参数得到openid
                    var openIdModel = _weChatPayService.GetOpenid(model.WxAppid, model.WxAppsecret, code);
                    //业务处理保存到数据库
                    var modelOId=_weChatPayService.HospPatirntOpenIdSaveAsync(hospCode, userId, openIdModel).Result;
                    
                    return Redirect(returnUrl);
                }
            }
        }

 

我这里的逻辑就是获取过了直接数据库获取没有获取过的微信授权获取。这里 如果用户没有授权实际上这个接口要访问2次的 第一次code没有值,第二次微信授权后通过redirect_uri带着code回来就获取到了用户的OpenId信息。

    3)、统一下单

                    var path = RequestUrl.TRANSACTIONS;////api.mch.weixin.qq.com/v3/pay/transactions/jsapi
                    var timeOut = DateTime.Now.AddMinutes(10);
                    var address = $"{model.Address}/api/WeChatPay/NotifySuccess";//这个是微信支付状态返回发到你的接口上的地址
                    var reqDto = new
                    {
                        appid = model.WxAppid,
                        mchid = model.WxMchid,
                        description = req.Desc,
                        out_trade_no = troNo,
                        //time_expire = timeOut,
                        attach = regOrderModel.RegId.ToString(),
                        notify_url = address,
                        amount = new
                        {
                            total = req.Total,
                            currency = "CNY"
                        },
                        payer = new
                        {
                            openid = modelOpenId.OpenId
                        }
                    };
                    var reqJson = JsonConvert.SerializeObject(reqDto);
                   
                    var ret = _httpClientFactoryHelper.WeChatPostAsync(path, reqJson, model.PrivateKey, model.WxMchid, model.CardNo).Result;
                    var result = JsonConvert.DeserializeObject<JsApiResult>(ret);
                    if (!string.IsNullOrEmpty(result.Prepay_Id))
                    {
                        //时间戳
                        DateTimeOffset dto = new DateTimeOffset(DateTime.Now);
                        var unixTime = dto.ToUnixTimeSeconds().ToString();
                        //随机数
                        var nonMun = Guid.NewGuid().ToString("N").Substring(0, 30);
                        var pck = "prepay_id=" + result.Prepay_Id;
                        //签名
                        string message = $"{model.WxAppid}\n{unixTime}\n{nonMun}\n{pck}\n";
                        var pathfile = _webHostEnvironment.WebRootPath + "/arsjkll/apiclient_cert.p12";
                        string keyRsa = _httpClientFactoryHelper.Sign(message, pathfile, "密码咯");
                        //var keyRsa = _httpClientFactoryHelper.Sign(message, model.PrivateKey);
                        //构建JSAPI拉取支付的参数 匿名参数
                        var requestParam = new
                        {
                            appId = model.WxAppid,
                            timeStamp = unixTime,
                            nonceStr = nonMun,
                            package = pck,
                            signType = "RSA",
                            paySign = keyRsa
                        };
                        return Result.ToSuccess(requestParam);
                    }
                    else
                    {
                        return Result.ToFail("prepay_id获取失败+++++++" + ret);
                    }

 

 上面统一下单获取到prepay_id在构造JSAPI拉取微信支付的参数返回到前端。签名上面有代码就不贴了。

    4)、支付回调接口

 public Result NotifySuccess(NotifyDto ret)
        {
            SaveLog("NotifyParame", JsonConvert.SerializeObject(ret));
            //ResourceASC
            if (ret.Event_type == "TRANSACTION.SUCCESS")//支付成功
            {

                //解密数据报文
                var dataJson = AesGcmHelper.AesGcmDecrypt(ret.Resource.Associated_data, ret.Resource.Nonce, ret.Resource.Ciphertext);
                //转换对象接受
                var data = JsonConvert.DeserializeObject<ResourceASC>(dataJson);
                //获取当前订单记录实体
              //自己的业务逻辑
            }
            else
            {
                SaveLog("NotifyFaile", JsonConvert.SerializeObject(ret));
            }
            return Result.ToSuccess("");
        }
/// <summary>
    /// 支付结果回调接收参数
    /// </summary>
    public class NotifyDto
    {
        /// <summary>
        /// 通知ID通知的唯一ID  
        /// 示例值:EV-2018022511223320873
        /// </summary>
        public string Id { get; set; }
        /// <summary>
        /// 通知创建时间  通知创建的时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss.表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示北京时间2015年05月20日13点29分35秒。
        /// 示例值:2015-05-20T13:29:35+08:00
        /// </summary>
        public string Create_time { get; set; }
        /// <summary>
        /// 通知类型  通知的类型,支付成功通知的类型为TRANSACTION.SUCCESS
        /// 示例值:TRANSACTION.SUCCESS
        /// </summary>
        public string Event_type { get; set; }
        /// <summary>
        /// 通知数据类型  通知的资源数据类型,支付成功通知为encrypt-resource
        /// 示例值:encrypt-resource
        /// </summary>
        public string Resource_type { get; set; }
        /// <summary>
        /// 通知数据 通知资源数据
        /// json格式,见示例
        /// </summary>
        public Resource Resource { get; set; }
        /// <summary>
        /// 回调摘要 
        /// 示例值:支付成功
        /// </summary>
        public string Summary { get; set; }
    }

 解密类

 public class AesGcmHelper
    {
        private static string ALGORITHM = "AES/GCM/NoPadding";
        private static int TAG_LENGTH_BIT = 128;
        private static int NONCE_LENGTH_BYTE = 12;
        private static string AES_KEY = "v3秘钥";

        public static string AesGcmDecrypt(string associatedData, string nonce, string ciphertext)
        {
            GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine());
            AeadParameters aeadParameters = new AeadParameters(
                new KeyParameter(Encoding.UTF8.GetBytes(AES_KEY)),
                128,
                Encoding.UTF8.GetBytes(nonce),
                Encoding.UTF8.GetBytes(associatedData));
            gcmBlockCipher.Init(false, aeadParameters);

            byte[] data = Convert.FromBase64String(ciphertext);
            byte[] plaintext = new byte[gcmBlockCipher.GetOutputSize(data.Length)];
            int length = gcmBlockCipher.ProcessBytes(data, 0, data.Length, plaintext, 0);
            gcmBlockCipher.DoFinal(plaintext, length);
            return Encoding.UTF8.GetString(plaintext);
        }
    }

 

支付返回的我保存了日志分享出来看看

 

之前一直没有调通,花了一天时间 找到了原因 统一下单的时候 里面不是谣传一个参数  支付金钱 total  我携程decmial类型了赋值 ,注意这里一定要int  单位是分数。支付提示 (系统繁忙、请稍后再试),一般这个错误就是参数不对,类型一定要跟官网对应起来

    5)、Vue前端调用拉起支付

<template>
  <div>
    <van-nav-bar
      left-text="返回"
      left-arrow
      @click-left="clickLeft"
      title="支付订单"
    />

    <div class="figure">
      <div>支付金额</div>
      <div class="money"><span>{{ total }}</span>
      </div>
      <div style="font-size: 14px">
        请在
        <van-count-down
          :time="countDownTime"
          format="mm分ss秒"
          style="display: inline"
          @finish="countDownFinish"
        />
        内支付完成,超时后订单自动取消
      </div>
    </div>

    <van-radio-group v-model="radio">
      <van-cell-group>
        <van-cell v-for="el in payType" @click="chkCheck(el)">
          <template #right-icon>
            <svg class="icon" aria-hidden="true">
              <use :xlink:href="el.icon"></use>
            </svg>
            <span style="width: 80px; margin-left: 15px">{{ el.title }}</span>
            <van-radio style="margin-left: 60%" :name="el.name" />
          </template>
        </van-cell>
      </van-cell-group>
    </van-radio-group>

    <div style="margin: 16px">
      <van-button
        round
        block
        type="info"
        native-type="submit"
        @click="onSubmit"
        :disabled="!btnIsable"
      >
        {{ btnText }}
      </van-button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      // cardNo: '',
      // desc: '',
      // hospCode: '',
      // pointId: '',//号点Id
      total: '',//支付费用
      countDownTime: 5 * 60 * 1000,//支付剩余毫秒数
      radio: '1',
      appId: '',
      timeStamp: '',
      nonceStr: '',
      package: '',
      signType: '',
      paySign: '',
      btnText: '立即支付',
      btnIsable: true,//支付按钮是否可用,true:可用 false:不可用
      payType: [{
        icon: '#icon-weixinzhifu',
        title: '微信支付',
        name: '1',
      }],
    }
  },
  methods: {
    onSubmit() {
      let vm = this;
      let obj = {
        "appId": vm.appId,//公众号名称
        "timeStamp": vm.timeStamp,//时间戳,自1970年以来的秒数
        "nonceStr": vm.nonceStr,//随机串
        "package": vm.package,
        "signType": vm.signType,
        "paySign": vm.paySign //签名 
      };

      function onBridgeReady() {
        WeixinJSBridge.invoke('getBrandWCPayRequest', obj,
          function (res) {
            if (res.err_msg == "get_brand_wcpay_request:ok") {
              // 使用以上方式判断前端返回,微信团队郑重提示:
              //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
              vm.btnIsable = false;
              vm.closePage('支付成功');//后期跳转到挂号记录页面
            }
          });
      }
      if (typeof WeixinJSBridge == "undefined") {
        if (document.addEventListener) {
          document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
        } else if (document.attachEvent) {
          document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
          document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
        }
      } else {
        onBridgeReady();
      }
    },
    chkCheck: (el) => {
      return el.name;
    },
    countDownFinish() {
      //倒计时结束销号,不提交支付
      this.btnIsable = false;
      this.closePage('支付超时');
    },
    clickLeft() {
      this.$dialog.confirm({
        title: '提示',
        message: '支付尚未完成,是否继续支付',
      })
        .then(() => {
          // on confirm
        })
        .catch(() => {
          this.$router.go(-1);
        });
    },
    getBaseData() {
      // 获得支付信息
      let vm = this;
      let p = vm.$route.params;
      if (JSON.stringify(p) != "{}") {
        this.appId = p.appId;
        this.total = p.fee;
        this.nonceStr = p.nonceStr;
        this.package = p.package;
        this.paySign = p.paySign;
        this.countDownTime = p.paymentDeadline - vm.$moment().valueOf();
        this.signType = p.signType;
        this.timeStamp = p.timeStamp;
      } else {
        this.closePage('无效请求');
      }
    },
    closePage(text, num = 5, route = 'home') {
      this.btnIsable = false;
      let lock = setInterval(() => {
        num--;
        this.btnText = `${text},${num}秒后关闭`;
        if (num == 0) {
          clearInterval(lock);
          this.$router.push({ path: route });
        }
      }, 1000);
    },
  },
  mounted() {
    this.getBaseData();
  }
}
</script>

<style>
.figure {
  background: #fff;
  text-align: center;
  margin: 10px auto;
  padding: 10px 0;
  color: gray;
}
.money {
  color: orange;
  font-size: 16px;
  margin: 15px auto;
}
.money span {
  font-size: 36px;
}
</style>

5、总结

纸上得来终觉浅,看着微信官网的,v3支付只有 爪哇 跟 派森 的sdk 。NET的还是自己来。里面的各种术语花里花哨的感觉 哈哈。可能是现在的我心里太多的浮躁了,还是要慢慢静下心来看。本文出处魏杨杨博客园

原文链接我自己贴上//www.cnblogs.com/w5942066/p/14313946.html

做个内心阳光的人。不忧伤,不心急。坚强、向上,靠近阳光,成为更好的自己,你不需要别人过多的称赞,因为你自己知道自己有多好。内心的强大,永远胜过外表的浮华。