朋友叫我帮忙开发个demo,让他可以在此基础上进一步开发, 因此研究了一下微信支付。 经了解,api v3没有.net的sdk, 后来下载了v2的.net sdk后发现并不能正常使用, 因此选择了第三方库来实现。
选择的库也是最有名气的senparc。经了解调试以后发现senparc所谓的Tenpay.V3类竟然对应的是官方的api v2。 因此才有了本文这个标题。
基本信息了解
- 安装senparc时除了安装基础库,还要单独安装它的tenpay。
- 网上senparc的使用教程都讲的不是很清楚,有一篇虽讲清楚了但是没有很好的发挥全部功能(作者吃饱了还自己计算签名?)
- 同支付宝一样,微信支付也是先下单(生成预订单id)再调起支付(需要之前生成的预订单id和计算签名)。只不过前端代码有些区别,而且阿里有完整可用的sdk。
- senparc的文档:
http://doc.weixin.senparc.com/html/N_Senparc_Weixin_TenPay_V3.htm
- 官方说v2可以用md5加密。 而v3只能是RSA。
付款的实现
- 这次给他写的demo里,没有分离前后端,而是直接通过code页生成前端代码,aspx页就直接运行它了。
- 是我自己用的话,应该是封装一个api负责返回prepay_id、paySign等参数给前端页面用。 前端页面在下文。
- 用到的几个核心方法都是senparc的, 参数见文档
首先是后端
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Senparc.Weixin.TenPay.V3;
public partial class wxpay_xiadan_Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
//商品信息和购买人
string body = Request.QueryString["body"];
string outTradeNo = Request.QueryString["outTradeNo"];
string _totalFee = Request.QueryString["totalFee"];
int totalFee = Convert.ToInt32(_totalFee);
string openid = Request.QueryString["openid"];
//通知地址(腾讯会调用这个url,通过这个url来更新我方的订单信息)
string notifyUrl = "https://***********";
//计算签名的信息
string appId = "wx9b*******";
string mchId = "1608*****";
string ts = ((DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000) + "";
string key = "z***********************";
string nonceStr = "e61463f8efa94090b1f366cccfbbb444";
//准备预订单的请求数据
TenPayV3UnifiedorderRequestData unifiedorderRequestData = new TenPayV3UnifiedorderRequestData(
appId,
mchId,
body,
outTradeNo,
totalFee,
Request.ServerVariables["REMOTE_ADDR"],
notifyUrl,
0,
openid,
key,
nonceStr,
null,
null,
null,
null,
null,
"CNY",
null,
null,
false,
null
);
//请求下预订单
UnifiedorderResult unifiedorderResult = TenPayV3.Unifiedorder(
unifiedorderRequestData,
10000
);
// Response.Write("<br>prepay_id: " + unifiedorderResult.prepay_id);
// Response.Write("<br>prepay_id: " + unifiedorderResult.err_code + unifiedorderResult.err_code_des);
//下预订单失败
if (!String.IsNullOrEmpty(unifiedorderResult.err_code))
{
tishi.Text=("发生错误: " + unifiedorderResult.err_code);
}
else //下预订单成功
{
tishi.Text = "请在弹出窗口中完成付款。 完成后请返回上一页。";
//生成签名
string paySign = TenPayV3.GetJsPaySign(
appId,
ts,
nonceStr,
"prepay_id=" + unifiedorderResult.prepay_id,
key,
"md5"
);
//把生成签名的原参数(除了key)和算出的签名给前端。 前端自己再拼js代码来拉起付款。
vars.Text = @"
<script>
var appId='" + appId + @"';
var ts='" + ts + @"';
var nonceStr='" + nonceStr + @"';
var prepay_id='" + unifiedorderResult.prepay_id + @"';
var paySign='" + paySign + @"';
</script>
";
}
}
}
然后是前端(注意很多代码是腾讯规定必须这么写的,比如WeixinJSBridge.invoke这块
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="wxpay_xiadan_Default" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<asp:Literal id="vars" runat="server" />
<script>
function onBridgeReady() {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId": appId, //公众号ID,由商户传入
"timeStamp": ts, //时间戳,自1970年以来的秒数
"nonceStr": nonceStr, //随机串
"package": "prepay_id=" + prepay_id,
"signType": "RSA", //微信签名方式:
"paySign": paySign //微信签名
},
function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
});
}
function startPay() {
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();
}
}
startPay();
</script>
<body>
<form id="form1" runat="server">
<div>
<asp:Literal id="tishi" runat="server" />
</div>
</form>
</body>
</html>
接收通知
- v2接收到的通知是xml格式,而v3是json
- 除了格式区别, 还有就是v3的json中一部分是加密的。而v2只需验签,内容不加密。
- BouncyCastle暂时忽略,是给v3解密数据用的
- 这份通知接收的代码, 原型的senparc官方demo(比如怎么验签和判断是否订单付款是否成功,以及最后返回xml给微信)
具体代码
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;
using System.Data.SqlClient;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Dapper;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using System.Configuration;
using Senparc.Weixin.TenPay.V3;
public partial class yyt_wxpay_Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
try
{
ResponseHandler resHandler = new ResponseHandler(null);
string return_code = resHandler.GetParameter("return_code");
string return_msg = resHandler.GetParameter("return_msg");
string key = "z8r***************";
bool paySuccess = false;
resHandler.SetKey(key);
//验证请求是否从微信发过来(安全)并设定成功或失败的标识
if (resHandler.IsTenpaySign() && return_code.ToUpper() == "SUCCESS")
{
paySuccess = true;
}
else
{
paySuccess = false;
rt.Text="验签失败";
}
if (paySuccess)
{
#region 记录到数据库
try
{
//此处写更新自己订单数据的代码
//返回给腾讯看
string xml = string.Format(@"<xml>
<return_code><![CDATA[{0}]]></return_code>
<return_msg><![CDATA[{1}]]></return_msg>
</xml>", return_code, return_msg);
rt.Text = xml;
}
catch(Exception err)
{
rt.Text = err.ToString();
}
#endregion
}
else
{
}
}
catch (Exception ex)
{
rt.Text=ex.ToString();
}
}
//这个class备用,v3需要它来解密数据
public class AesGcm
{
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 = "z8*************";
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);
}
}
}