朋友叫我幫忙開發個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);
}
}
}