前言

公司最近在做微信小程序,被分配到做支付这一块,现在对这一块做一个简单的总结和梳理。

支付,对于购物来说,可以说是占据了十分重要的一块,毕竟能收到钱才是重点。

当然在开发之前,我们需要有下面这些东西:

  • appId
  • 密钥(小程序配置界面)
  • 商户号
  • api密钥(商家后台自己设置)

当然这些是不用我们自己申请的,公司会有人申请好,然后要什么跟这个人说,让他提供就可以了。

首先来看一下官方给出的业务流程时序图

这个图很清晰的表达了在小程序支付中的整个流程,每一步要做些什么。

一个完整的支付,一般情况下都是包含了下面三个主要的点;

  • 支付(正常是支付平台提供的h5页面让用户操作,主要是输密码)
  • 通知(用户完成一笔支付了,支付平台要通知商家支付结果,商家收到结果后进行一些相应的处理)
  • 查询(与第二点有点反过来的意思,商家自己主动去支付平台查询支付的结果,然后根据结果做相应的处理)

下面就重点来简单实现一下上面说的第一点,支付,也是可以进行下面两步的在大前提。

支付的简单实现

小程序的实现

简单起见,在index.wxml中添加一个输入框和一个button,绑定一下相应的事件,输入框主要是用于输入订单号,按钮用于模拟提交一个订单并发起支付。

<!--index.wxml-->
<view class="container">
<input type="text" bindinput="getOrderCode" style="border:1px solid #ccc;" />
<button bindtap="pay">立即支付</button>
</view>

然后在index.js中写上一小段代码,主要是处理上面按钮的点击事件。

Page({
data: {
txtOrderCode: ''
},
pay: function () {
var ordercode = this.data.txtOrderCode;
wx.login({
success: function (res) {
if (res.code) {
wx.request({
url: 'https://www.yourdomain.com/pay',
data: {
code: res.code,//要去换取openid的登录凭证
ordercode: ordercode
},
method: 'GET',
success: function (res) {
console.log(res.data)
wx.requestPayment({
timeStamp: res.data.timeStamp,
nonceStr: res.data.nonceStr,
package: res.data.package,
signType: 'MD5',
paySign: res.data.paySign,
success: function (res) {
// success
console.log(res);
},
fail: function (res) {
// fail
console.log(res);
},
complete: function (res) {
// complete
console.log(res);
}
})
}
})
} else {
console.log('获取用户登录态失败!' + res.errMsg)
}
}
});
},
getOrderCode: function (event) {
this.setData({
txtOrderCode: event.detail.value
});
}
})

可以看到,在这里Catcher先通过wx.login这个API先取到了登录的凭证code,并把这个凭证code做为请求参数用wx.request这个API发起一个网络请求。

在这个网络请求处理后会返回小程序支付所需要的相关参数。拿到这些参数后,再调用wx.requestPayment这个支付API,此时才算是真正的发起支付。

至此,小程序这边的事已经做完了,接下来就是要去处理接口那边的事了,其实接口要做的就是返回小程序需要的几个参数。但是要拿到这几个参数还是需要做不少事情的。

接口的实现

据悉最新版的Senparc.Weixin.MP已经支付了小程序相关的内容,但是公司用的版本还是比较低

并且近期也没有打算对这个组件进行升级。所以就从白纸一张开始了。

用的是mvc,所以这个小程序发起的网络请求会由下面的action的执行,里面的实现,每一步做了什么应该也已经很清晰了。

public ActionResult Pay(string code, string ordercode)
{
var paramter = new Parameters();
paramter.out_trade_no = ordercode; //使用登录凭证 code 获取 session_key 和 openid
var unifiedorderRes = GetOpenIdAndSessionKey(paramter.appid, paramter.secret, code); //反序列化session_key 和 openid成ChangeResponseEntity实体
var tmp = JsonConvert.DeserializeObject<ChangeResponseEntity>(unifiedorderRes); //统一下单的url和参数
var payUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
var param = GetUnifiedOrderParam(tmp.openid, paramter); //统一下单后拿到的xml结果
var payResXML = Helper.DoPost(param, payUrl);
var payRes = XDocument.Parse(payResXML);
var root = payRes.Element("xml"); //序列化相应参数返回给小程序
var res = GetPayRequestParam(root, paramter.appid, paramter.key);
return Json(res, JsonRequestBehavior.AllowGet);
}

由于只是一个演示的过程,不想这些数据经常以字符串的形式频繁出现在代码中,所以把相关的参数全部都放到了一个名为Parameters的类中(放到配置文件中也是可以的),除了订单号是从小程序传过来的,当然在实际中这是不合理的,毕竟像金额这些东西,不可能每次都是同一个!这点是要注意的。

下面先来看看这个Parameters类的定义:

public class Parameters
{
public string appid { get { return "申请的appid"; } }
public string mchid { get { return "申请的商户号"; } }
public string nonce { get { return Helper.GetNoncestr(); } }
public string notify_url { get { return "http://yourdomain.com/notifyurl"; } }
public string body { get { return "testpay"; } }
public string out_trade_no { get; set; }
public string spbill_create_ip { get { return "IP地址"; } }
public string total_fee { get { return "1"; } }
public string trade_type { get { return "JSAPI"; } }
public string key { get { return "在商家后台设置的密钥"; } }
public string secret { get { return "在配置小程序时的密钥"; } }
}

首先是获取到登录凭证后发起的这个网络请求。这个网络请求是决定了这次支付能否成功的第一步!

下面要做的是用登录凭证去换我们要的openid。

/// <summary>
/// 取openid和session_key
/// </summary>
/// <param name="appid"></param>
/// <param name="secret"></param>
/// <param name="js_code"></param>
/// <returns></returns>
private string GetOpenIdAndSessionKey(string appid, string secret, string js_code)
{
var url = string.Format("https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code"
, appid,secret,js_code);
var request = WebRequest.Create(url) as HttpWebRequest; var response = request.GetResponse();
var respStream = response.GetResponseStream(); var res = string.Empty;
using (var reader = new StreamReader(respStream, Encoding.UTF8))
{
res = reader.ReadToEnd();
}
return res;
}

要换取openid,就要向微信提供的地址发起一个网络请求,并在URL带上appid,secret和凭证code这三个参数。

然后就可以拿到一个下面形式的json字符串

{
"openid": "OPENID",
"session_key": "SESSIONKEY"
}

拿到之后自然就是要对这个字符串进行json的反序列化,这里用到了json.net这个包。

根据时序图,下面要调用统一下单这个接口了。

上面的代码,在统一下单这一块,又分为下面几个步骤

  1. 处理统一下单的参数(签名和组装xml)
  2. 发起POST请求
  3. 解析请求得到的结果

参数的处理:

具体规则参见:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3

/// <summary>
/// 取统一下单的请求参数
/// </summary>
/// <param name="openid"></param>
/// <param name="param"></param>
/// <returns></returns>
private string GetUnifiedOrderParam(string openid, Parameters param)
{
//参与统一下单签名的参数,除最后的key外,已经按参数名ASCII码从小到大排序
var unifiedorderSignParam = string.Format("appid={0}&body={1}&mch_id={2}&nonce_str={3}&notify_url={4}&openid={5}&out_trade_no={6}&spbill_create_ip={7}&total_fee={8}&trade_type={9}&key={10}"
, param.appid, param.body, param.mchid, param.nonce, param.notify_url
, openid, param.out_trade_no, param.spbill_create_ip, param.total_fee, param.trade_type, param.key);
//MD5
var unifiedorderSign = Helper.GetMD5(unifiedorderSignParam).ToUpper();
//构造统一下单的请求参数
return string.Format(@"<xml>
<appid>{0}</appid>
<body>{1}</body>
<mch_id>{2}</mch_id>
<nonce_str>{3}</nonce_str>
<notify_url>{4}</notify_url>
<openid>{5}</openid>
<out_trade_no>{6}</out_trade_no>
<spbill_create_ip>{7}</spbill_create_ip>
<total_fee>{8}</total_fee>
<trade_type>{9}</trade_type>
<sign>{10}</sign>
</xml>
", param.appid, param.body, param.mchid, param.nonce, param.notify_url, openid
, param.out_trade_no, param.spbill_create_ip, param.total_fee, param.trade_type, unifiedorderSign); }

这里要注意一点,由于我们的传的trade_type是JSAPI,所以这里必须是要加上openid进行处理的。

然后就是解析统一下单返回的XML了,说是解析,其实也就是要拿到我们需要的数据罢了。这里最后会得到一个小程序支付API需要的参数实体。

/// <summary>
/// 获取返回给小程序的支付参数
/// </summary>
/// <param name="root"></param>
/// <param name="appid"></param>
/// <param name="key"></param>
/// <returns></returns>
private PayRequesEntity GetPayRequestParam(XElement root,string appid,string key)
{
//当return_code 和result_code都为SUCCESS时才有我们要的prepay_id
if (root.Element("return_code").Value == "SUCCESS" && root.Element("result_code").Value == "SUCCESS")
{
var package = "prepay_id=" + root.Element("prepay_id").Value;
var nonceStr = Helper.GetNoncestr();
var signType = "MD5";
var timeStamp = Convert.ToInt64((DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds).ToString(); var paySignParam = string.Format("appId={0}&nonceStr={1}&package={2}&signType={3}&timeStamp={4}&key={5}",
appid, nonceStr, package, signType, timeStamp, key); var paySign = Helper.GetMD5(paySignParam).ToUpper(); var payEntity = new PayRequesEntity
{
package = package,
nonceStr = nonceStr,
paySign = paySign,
signType = signType,
timeStamp = timeStamp
};
return payEntity;
} return new PayRequesEntity();
}

支付参数实体对应的内容如下:

/// <summary>
/// 小程序支付需要的参数
/// </summary>
public class PayRequesEntity
{
/// <summary>
/// 时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间
/// </summary>
public string timeStamp { get; set; } /// <summary>
/// 随机字符串,长度为32个字符以下。
/// </summary>
public string nonceStr { get; set; } /// <summary>
/// 统一下单接口返回的 prepay_id 参数值
/// </summary>
public string package { get; set; } /// <summary>
/// 签名算法
/// </summary>
public string signType { get; set; } /// <summary>
/// 签名
/// </summary>
public string paySign { get; set; }
}

需要注意的是,这里的签名操作,一定是要配合appId,这也是Catcher在支付这一块踩的唯一的一个坑,所以提醒一下各位读者,希望能避开这个坑。

还有最后一步就是要返回一个序列化的对象给小程序,以供小程序使用。

到这里,后台接口也已经OK了,现在就用真机扫描二维码,点击立即支付按钮,此时就会弹出要你输入密码的框框,输入你的微信支付密码,如下所示:

然后就会提示支付成功,如下所示:

几秒钟之后就会收到微信支付发来的消息,通知你在什么时候支出了多少钱。

通知的简单说明

前面也提到了,通知是用户支付成功后,微信的服务器会向我们统一下单指定的notify_url发起一个异步的回调。

下面用伪代码来表示这一过程

public ActionResult Notify()
{
//1.获取微信通知的参数 //2.更新订单的相关状态 //3.返回一个xml格式的结果给微信服务器
var res = @"<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>"; return Content(res);
}

这里需要注意的是要处理好微信重复通知的情况!

查询的简单说明

通知和查询本质上都是想知道订单是否支付成功了。

它们的区别是:通知是微信主动通知商家; 查询是商家主动向微信发起查询;

这两个动作的主体是不一样的。

当微信能正常发起推送并且商家接收这个推送的服务器又没有挂的时候,查询的作用是微乎其微的。

当然,不可避免的会出现,微信不能正常发起推送或者商家的服务器挂了,这个时候查询的作用就变得很重要了!!

这个时候我们就要建交起一个定时作业来专门处理这种情况了,可以选择Quartz.NetHangfire等!

这个作业的内容具体如下:

public void QueryJob()
{
//1.找到要查询的订单号 //2.根据订单号和appId等内容向https://api.mch.weixin.qq.com/pay/orderquery这个地址发起网络请求 //3.拿到微信返回的结果 //4.根据结果进行相应的处理
}

至于多久执行一次这个作业,可能就要根据使用小程序进行购物的数量多不多来做一个大致的估计。

总结

小程序的支付还是算是比较简单,毕竟文档还算齐全,基本照着文档的提示就能把这个支付做好。

微信小程序支付简单小结与梳理的更多相关文章

  1. [转]微信小程序支付简单小结与梳理

    本文转自:https://www.cnblogs.com/onetwo/p/6667424.html 公司最近在做微信小程序,被分配到做支付这一块,现在对这一块做一个简单的总结和梳理. 支付,对于购物 ...

  2. 微信小程序支付步骤

    http://blog.csdn.net/wangsf789/article/details/53419781 最近开发微信小程序进入到支付阶段,一直以来从事App开发,所以支付流程还是熟记于心的.但 ...

  3. 微信小程序支付及退款流程详解

    微信小程序的支付和退款流程 近期在做微信小程序时,涉及到了小程序的支付和退款流程,所以也大概的将这方面的东西看了一个遍,就在这篇博客里总结一下. 首先说明一下,微信小程序支付的主要逻辑集中在后端,前端 ...

  4. 通俗易懂,C#如何安全、高效地玩转任何种类的内存之Span的脾气秉性(二)。 异步委托 微信小程序支付证书及SSL证书使用 SqlServer无备份下误删数据恢复 把list集合的内容写入到Xml中,通过XmlDocument方式写入Xml文件中 通过XDocument方式把List写入Xml文件

    通俗易懂,C#如何安全.高效地玩转任何种类的内存之Span的脾气秉性(二).   前言 读完上篇<通俗易懂,C#如何安全.高效地玩转任何种类的内存之Span的本质(一).>,相信大家对sp ...

  5. .NET Core 微信小程序支付——(统一下单)

    最近公司研发了几个电商小程序,还有一个核心的电商直播,只要是电商一般都会涉及到交易信息,离不开支付系统,这里我们统一实现小程序的支付流程(与服务号实现步骤一样). 目录1.开通小程序的支付能力2.商户 ...

  6. Asp.net Core 微信小程序支付

    最近要做一个微信小程序支付的功能 在网上找了一下 .net Core做微信支付的博客 和 demo 几乎没有 自己研究了好几天 参考了 很多 大牛的博客 勉强做出来了  因为参数都没有 比如 opid ...

  7. Java 后端微信小程序支付demo (网上说的坑里面基本上都有)

    Java 后端微信小程序支付 一.遇到的问题 1. 商户号该产品权限未开通,请前往商户平台>产品中心检查后重试 2.签名错误 3.已经调起微信统一下单接口,可以拿到预支付ID,但是前端支付的时候 ...

  8. php对接微信小程序支付

    前言:这里我就假装你已经注册了微信小程序,并且基本的配置都已经好了.注: 个人注册小程序不支持微信支付,所以我还是假装你是企业或者个体工商户的微信小程序,其他的商户号注册,二者绑定,授权,支付开通,就 ...

  9. 微信小程序支付遇到的坑

    1,微信公众号支付和微信小程序支付有差异 微信公众号:可以直接跳转走h5的微信支付 微信小程序:在测试环境.沙箱环境使用微信公众号的跳转支付没有问题,在线上存在支付异常 最后商讨的解决方法 openi ...

随机推荐

  1. Webpack学习系列(一)

    一:全局安装: npm install webpack -g (-g全局安装) npm init -y   (初始化参数) npm install webpack --save-dev  (安装在当前 ...

  2. JAVA集合一之集合简介(Collection,List,Set)

    在编写JAVA程序中,我们经常会遇到需要保存一组数据对象,此时,我们可以采用对象数组来进行多个对象的保存,但对象数组存在一个最大的问题即在于长度上的限制,如果说我们现在要保存一组对象,但是我们并知道数 ...

  3. 浅解.Net分布式锁的实现

    序言 我晚上有在公司多呆会儿的习惯,所以很多晚上我都是最后一个离开公司的.当然也有一些同事,跟我一样喜欢在公司多搞会儿.这篇文章就要从,去年年末一个多搞会的晚上说起,那是一个夜黑风高的晚上,公司应该没 ...

  4. java数组、泛型、集合在多态中的使用及对比

    我们在使用数组,泛型集合的过程中不可避免的会碰到多态,或者说什么情况下能如何使用父数组引用子数组(集合.泛型)呢? 数组在多态中的使用 元素为父类型的数组引用可指向元素为子类型的数组对象 当数组被调用 ...

  5. Python入门教程(1)

    人生苦短,我用Python! Python(英语发音:/ˈpaɪθən/), 是一种面向对象.解释型计算机程序设计语言,由Guido van Rossum于1989年底发明,第一个公开发行版发行于19 ...

  6. iOS 原生的 UIButton 点击事件是不允许带多参数的,唯一的一个参数就是默认UIButton本身 那么我们该怎么实现传递多个参数的点击事件呢?

    UIButton *btn = // create the button objc_setAssociatedObject(btn, "firstObject", someObje ...

  7. wap问答系统工作总结

    一直想找个锻炼自己的机会,但是又很恐慌,怕自己能力太差,把握不住机会,把事情弄糟. 终于,要做wap问答系统了,本来说是几个人一块儿做,我分析了下页面,发现共同的部分还是比较多的,有点想法,要不我接过 ...

  8. 巧用*_his表记录操作历史

    文章转载自「开发者圆桌」一个关于开发者入门.进阶.踩坑的微信公众号 许多OLTP应用的开发者都知道,一些重要的操作要记录操作历史,把操作前的数据备份到历史表,然后再执行相应的修改操作.这样可以获取某个 ...

  9. C++游戏服务器的性能优化

    以下只是某次项目的一次经历,最终并没有按照这样的方案来优化,但对思路方面确实是一个提高,所以记录在此. ------------------------------------------------ ...

  10. 测试不同格式下depth buffer的精度

    这篇文章主要是参考MJP的“Attack of The Depth Buffer”,测试不同格式下depth buffer的精度. 测试的depth buffer包含两类: 一是非线性的depth b ...