企業號微信支付 公眾號支付 H5調起支付API示例代碼 JSSDK C# .NET,
先看效果
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011818592203.jpg)
1.本文演示的是微信【企業號】的H5頁面微信支付
2.本項目基於開源微信框架WeiXinMPSDK開發:https://github.com/JeffreySu/WeiXinMPSDK 感謝作者蘇志巍的開源精神
一、准備部分
相關參數:
AppId:公眾號的唯一標識(登陸微信企業號後台 - 設置 - 賬號信息 - CorpID)
AppSecret:(微信企業號後台 - 設置 - 權限管理 - 新建一個擁有所有應用權限的普通管理組 - Secret)
Key:商戶API密鑰(登陸微信商戶後台 - 賬戶中心 - API安全 - API密鑰)
MchId:商戶ID(微信企業號後台 - 服務中心 - 微信支付 - 微信支付 -商戶信息 - 商戶號)
後台設置:
微信企業號後台 - 服務中心 - 微信支付 - 微信支付 - 開發配置 :
1.測試授權目錄,改成線上支付頁面的目錄(例:http://www.abc.com/wxpay/)
2.測試白名單,加上測試用戶的白名單,只有白名單用戶可以付款
二、代碼
前端:
使用微信支付先引入JSSDK:http://res.wx.qq.com/open/js/jweixin-1.0.0.js
頁面打開即初始化:
$.ajax({
type: "GET",
url: "/WxPay/GetPayConfig",
beforeSend: function () {
$("#btnPay").attr({ "disabled": "disabled" });//獲取到配置之前,禁止點擊付款按鈕
},
success: function (data) {
$("#btnPay").removeAttr("disabled");//獲取到配置,打開付款按鈕
wx.config({
debug: true, // 開啟調試模式,成功失敗都會有alert框
appId: data.appId, // 必填,公眾號的唯一標識
timestamp: data.timeStamp, // 必填,生成簽名的時間戳
nonceStr: data.nonceStr, // 必填,生成簽名的隨機串
signature: data.signature,// 必填,簽名
jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表
});
wx.ready(function () {
// config信息驗證後會執行ready方法,所有接口調用都必須在config接口獲得結果之後,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。
});
wx.error(function (res) {
// config信息驗證失敗會執行error函數,如簽名過期導致驗證失敗,具體錯誤信息可以打開config的debug模式查看,也可以在返回的res參數中查看,對於SPA可以在這裡更新簽名。
});
}
});
對應的後端代碼:
/// <summary>
/// 獲取微信支付配置
/// </summary>
/// <returns></returns>
[HttpGet]
public JsonResult GetPayConfig()
{
string timeStamp = Senparc.Weixin.MP.TenPayLib.TenPayUtil.GetTimestamp();
string nonceStr = Senparc.Weixin.MP.TenPayLib.TenPayUtil.GetNoncestr();
string signature = new Senparc.Weixin.MP.TenPayLib.RequestHandler(null).CreateMd5Sign();
return Json(new { appId = AppId, timeStamp = timeStamp, nonceStr = nonceStr, signature = signature }, JsonRequestBehavior.AllowGet);
}
用戶點擊支付觸發的函數(微信JSSDK的chooseWXPay函數):
function startWxPay() {
$.ajax({
type: "POST",
url: "/WxPay/GetPaySign",
data: { code: code, openid: openid },
beforeSend: function () {
$("#btnPay").attr({ "disabled": "disabled" });
},
success: function (res) {
$("#btnPay").removeAttr("disabled");
if (res.openid != null && res.openid != undefined && res.openid != "") {
window.localStorage.setItem("openid", res.openid);
}
wx.chooseWXPay({
timestamp: res.data.timeStamp, // 支付簽名時間戳
nonceStr: res.data.nonceStr, // 支付簽名隨機串,不長於32 位
package: res.data.package, // 統一支付接口返回的prepay_id參數值,提交格式如:prepay_id=***)
signType: "MD5", // 簽名方式,默認為'SHA1',使用新版支付需傳入'MD5'
paySign: res.data.paysign, // 支付簽名
success: function (res) {
//支付成功
},
cancel: function (res) {
//支付取消
}
});
}
});
}
對應的服務端代碼:
/// <summary>
/// 支付接口
/// </summary>
/// <param name="code"></param>
/// <param name="openid"></param>
/// <returns></returns>
[HttpPost]
public JsonResult GetPaySign(string code, string openid)
{
string body = "支付測試";//支付描述
string nonce_str = Senparc.Weixin.MP.TenPayLibV3.TenPayV3Util.GetNoncestr();
string notify_url = "http://" + HttpContext.Request.Url.Host + "/WxPay/PayNotifyUrl";//支付結果回調地址,不能帶參數(PayNotifyUrl回調裡能接到訂單號out_trade_no參數)
string out_trade_no = "WxPay_" + DateTime.Now.ToString("yyyyMMddHHmmssfff");//訂單號:32個字符內、不得重復
string spbill_create_ip = Request.UserHostAddress;//用戶端IP
int total_fee = 1;//訂單金額(單位:分),整數
string trade_type = "JSAPI";//JSAPI,NATIVE,APP,WAP
#region 獲取用戶微信OpenId
string openidExt = string.Empty;
if (string.IsNullOrEmpty(openid))
{
if (!Senparc.Weixin.QY.Containers.AccessTokenContainer.CheckRegistered(AppId))
{
Senparc.Weixin.QY.Containers.AccessTokenContainer.Register(AppId, AppSecret);
}
var accountToken = Senparc.Weixin.QY.Containers.AccessTokenContainer.GetToken(AppId, AppSecret);
var user = Senparc.Weixin.QY.AdvancedAPIs.OAuth2Api.GetUserId(accountToken, code);
var model = Senparc.Weixin.QY.CommonAPIs.CommonApi.ConvertToOpenId(accountToken, user.UserId);
openidExt = model.openid;
}
else
{
openidExt = openid;
}
#endregion
#region 調用統一支付接口獲得prepay_id(預支付交易會話標識)
Senparc.Weixin.MP.TenPayLibV3.RequestHandler packageReqHandler = new Senparc.Weixin.MP.TenPayLibV3.RequestHandler(null);
packageReqHandler.SetParameter("appid", AppId);
packageReqHandler.SetParameter("body", body);
packageReqHandler.SetParameter("mch_id", MchId);
packageReqHandler.SetParameter("nonce_str", nonce_str);
packageReqHandler.SetParameter("notify_url", notify_url);
packageReqHandler.SetParameter("openid", openidExt);
packageReqHandler.SetParameter("out_trade_no", out_trade_no);
packageReqHandler.SetParameter("spbill_create_ip", spbill_create_ip);
packageReqHandler.SetParameter("total_fee", total_fee.ToString());
packageReqHandler.SetParameter("trade_type", trade_type);
packageReqHandler.SetParameter("sign", packageReqHandler.CreateMd5Sign("key", Key));
string data = packageReqHandler.ParseXML();
#region 應該調SDK接口:var result = Senparc.Weixin.MP.AdvancedAPIs.TenPayV3.Unifiedorder(data); 但是總報錯
var urlFormat = "https://api.mch.weixin.qq.com/pay/unifiedorder";
var formDataBytes = data == null ? new byte[0] : Encoding.UTF8.GetBytes(data);
MemoryStream ms = new MemoryStream();
ms.Write(formDataBytes, 0, formDataBytes.Length);
ms.Seek(0, SeekOrigin.Begin);
var result = RequestUtility.HttpPost(urlFormat, null, ms);
#endregion
var res = System.Xml.Linq.XDocument.Parse(result);
string prepay_id = res.Element("xml").Element("prepay_id").Value;
#endregion
#region 支付參數
string timeStamp = Senparc.Weixin.MP.TenPayLibV3.TenPayV3Util.GetTimestamp();
nonce_str = Senparc.Weixin.MP.TenPayLibV3.TenPayV3Util.GetNoncestr();
Senparc.Weixin.MP.TenPayLibV3.RequestHandler paysignReqHandler = new Senparc.Weixin.MP.TenPayLibV3.RequestHandler(null);
paysignReqHandler.SetParameter("appId", AppId);
paysignReqHandler.SetParameter("timeStamp", timeStamp);
paysignReqHandler.SetParameter("nonceStr", nonce_str);
paysignReqHandler.SetParameter("package", string.Format("prepay_id={0}", prepay_id));
paysignReqHandler.SetParameter("signType", "MD5");
string paysign = paysignReqHandler.CreateMd5Sign("key", Key);
paysignReqHandler.SetParameter("paysign", paysign);
#endregion
return Json(new { data = paysignReqHandler.GetAllParameters(), openid = openidExt }, JsonRequestBehavior.AllowGet);
}
前端頁面全部代碼:
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011818592338.gif)
![]()
<!DOCTYPE html>
<html>
<head>
<title>企業號微信支付測試</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css">
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script type="text/javascript" src="http://cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
</head>
<body>
<input type="button" onclick="startWxPay()" class="btn btn-primary btn-lg btn-block" value="點擊付費(¥:0.01元)" id="btnPay" />
<script type="text/javascript">
var code = GetQueryString("code");
var openid = window.localStorage.getItem("openid");
$.ajax({
type: "GET",
url: "/WxPay/GetPayConfig",
beforeSend: function () {
$("#btnPay").attr({ "disabled": "disabled" });//獲取到配置之前,禁止點擊付款按鈕
},
success: function (data) {
$("#btnPay").removeAttr("disabled");//獲取到配置,打開付款按鈕
wx.config({
debug: true, // 開啟調試模式,成功失敗都會有alert框
appId: data.appId, // 必填,公眾號的唯一標識
timestamp: data.timeStamp, // 必填,生成簽名的時間戳
nonceStr: data.nonceStr, // 必填,生成簽名的隨機串
signature: data.signature,// 必填,簽名
jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表
});
wx.ready(function () {
// config信息驗證後會執行ready方法,所有接口調用都必須在config接口獲得結果之後,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。
});
wx.error(function (res) {
// config信息驗證失敗會執行error函數,如簽名過期導致驗證失敗,具體錯誤信息可以打開config的debug模式查看,也可以在返回的res參數中查看,對於SPA可以在這裡更新簽名。
});
}
});
function startWxPay() {
$.ajax({
type: "POST",
url: "/WxPay/GetPaySign",
data: { code: code, openid: openid },
beforeSend: function () {
$("#btnPay").attr({ "disabled": "disabled" });
},
success: function (res) {
$("#btnPay").removeAttr("disabled");
if (res.openid != null && res.openid != undefined && res.openid != "") {
window.localStorage.setItem("openid", res.openid);
}
wx.chooseWXPay({
timestamp: res.data.timeStamp, // 支付簽名時間戳
nonceStr: res.data.nonceStr, // 支付簽名隨機串,不長於32 位
package: res.data.package, // 統一支付接口返回的prepay_id參數值,提交格式如:prepay_id=***)
signType: "MD5", // 簽名方式,默認為'SHA1',使用新版支付需傳入'MD5'
paySign: res.data.paysign, // 支付簽名
success: function (res) {
//支付成功
},
cancel: function (res) {
//支付取消
}
});
}
});
}
function GetQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]); return null;
}
</script>
</body>
</html>
View Code
後端控制器全部代碼:
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017011818592338.gif)
![]()
using Senparc.Weixin.HttpUtility;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
namespace CNPCCMS.Controllers
{
public class WxPayController : Controller
{
private static string AppId = "改成你的AppId";
private static string AppSecret = "改成你的AppSecret";
private static string Key = "改成你的Key";
private static string MchId = "改成你的MchId";
/// <summary>
/// 獲取微信支付配置
/// </summary>
/// <returns></returns>
[HttpGet]
public JsonResult GetPayConfig()
{
string timeStamp = Senparc.Weixin.MP.TenPayLib.TenPayUtil.GetTimestamp();
string nonceStr = Senparc.Weixin.MP.TenPayLib.TenPayUtil.GetNoncestr();
string signature = new Senparc.Weixin.MP.TenPayLib.RequestHandler(null).CreateMd5Sign();
return Json(new { appId = AppId, timeStamp = timeStamp, nonceStr = nonceStr, signature = signature }, JsonRequestBehavior.AllowGet);
}
/// <summary>
/// 支付接口
/// </summary>
/// <param name="code"></param>
/// <param name="openid"></param>
/// <returns></returns>
[HttpPost]
public JsonResult GetPaySign(string code, string openid)
{
string body = "支付測試";//支付描述
string nonce_str = Senparc.Weixin.MP.TenPayLibV3.TenPayV3Util.GetNoncestr();
string notify_url = "http://" + HttpContext.Request.Url.Host + "/WxPay/PayNotifyUrl";//支付結果回調地址,不能帶參數(PayNotifyUrl回調裡能接到訂單號out_trade_no參數)
string out_trade_no = "WxPay_" + DateTime.Now.ToString("yyyyMMddHHmmssfff");//訂單號:32個字符內、不得重復
string spbill_create_ip = Request.UserHostAddress;//用戶端IP
int total_fee = 1;//訂單金額(單位:分),整數
string trade_type = "JSAPI";//JSAPI,NATIVE,APP,WAP
#region 獲取用戶微信OpenId
string openidExt = string.Empty;
if (string.IsNullOrEmpty(openid))
{
if (!Senparc.Weixin.QY.Containers.AccessTokenContainer.CheckRegistered(AppId))
{
Senparc.Weixin.QY.Containers.AccessTokenContainer.Register(AppId, AppSecret);
}
var accountToken = Senparc.Weixin.QY.Containers.AccessTokenContainer.GetToken(AppId, AppSecret);
var user = Senparc.Weixin.QY.AdvancedAPIs.OAuth2Api.GetUserId(accountToken, code);
var model = Senparc.Weixin.QY.CommonAPIs.CommonApi.ConvertToOpenId(accountToken, user.UserId);
openidExt = model.openid;
}
else
{
openidExt = openid;
}
#endregion
#region 調用統一支付接口獲得prepay_id(預支付交易會話標識)
Senparc.Weixin.MP.TenPayLibV3.RequestHandler packageReqHandler = new Senparc.Weixin.MP.TenPayLibV3.RequestHandler(null);
packageReqHandler.SetParameter("appid", AppId);
packageReqHandler.SetParameter("body", body);
packageReqHandler.SetParameter("mch_id", MchId);
packageReqHandler.SetParameter("nonce_str", nonce_str);
packageReqHandler.SetParameter("notify_url", notify_url);
packageReqHandler.SetParameter("openid", openidExt);
packageReqHandler.SetParameter("out_trade_no", out_trade_no);
packageReqHandler.SetParameter("spbill_create_ip", spbill_create_ip);
packageReqHandler.SetParameter("total_fee", total_fee.ToString());
packageReqHandler.SetParameter("trade_type", trade_type);
packageReqHandler.SetParameter("sign", packageReqHandler.CreateMd5Sign("key", Key));
string data = packageReqHandler.ParseXML();
#region 應該調SDK接口:var result = Senparc.Weixin.MP.AdvancedAPIs.TenPayV3.Unifiedorder(data); 但是總報錯
var urlFormat = "https://api.mch.weixin.qq.com/pay/unifiedorder";
var formDataBytes = data == null ? new byte[0] : Encoding.UTF8.GetBytes(data);
MemoryStream ms = new MemoryStream();
ms.Write(formDataBytes, 0, formDataBytes.Length);
ms.Seek(0, SeekOrigin.Begin);
var result = RequestUtility.HttpPost(urlFormat, null, ms);
#endregion
var res = System.Xml.Linq.XDocument.Parse(result);
string prepay_id = res.Element("xml").Element("prepay_id").Value;
#endregion
#region 支付參數
string timeStamp = Senparc.Weixin.MP.TenPayLibV3.TenPayV3Util.GetTimestamp();
nonce_str = Senparc.Weixin.MP.TenPayLibV3.TenPayV3Util.GetNoncestr();
Senparc.Weixin.MP.TenPayLibV3.RequestHandler paysignReqHandler = new Senparc.Weixin.MP.TenPayLibV3.RequestHandler(null);
paysignReqHandler.SetParameter("appId", AppId);
paysignReqHandler.SetParameter("timeStamp", timeStamp);
paysignReqHandler.SetParameter("nonceStr", nonce_str);
paysignReqHandler.SetParameter("package", string.Format("prepay_id={0}", prepay_id));
paysignReqHandler.SetParameter("signType", "MD5");
string paysign = paysignReqHandler.CreateMd5Sign("key", Key);
paysignReqHandler.SetParameter("paysign", paysign);
#endregion
return Json(new { data = paysignReqHandler.GetAllParameters(), openid = openidExt }, JsonRequestBehavior.AllowGet);
}
/// <summary>
/// 支付結果回調地址
/// </summary>
/// <returns></returns>
[HttpPost]
public ActionResult PayNotifyUrl()
{
Senparc.Weixin.MP.TenPayLibV3.ResponseHandler payNotifyRepHandler = new Senparc.Weixin.MP.TenPayLibV3.ResponseHandler(null);
payNotifyRepHandler.SetKey(Key);
string return_code = payNotifyRepHandler.GetParameter("return_code");
string return_msg = payNotifyRepHandler.GetParameter("return_msg");
string xml = string.Format(@"<xml><return_code><![CDATA[{0}]]></return_code><return_msg><![CDATA[{1}]]></return_msg></xml>", return_code, return_msg);
if (return_code.ToUpper() != "SUCCESS")
{
return Content(xml, "text/xml");
}
string out_trade_no = payNotifyRepHandler.GetParameter("out_trade_no");
//微信服務器可能會多次推送到本接口,這裡需要根據out_trade_no去查詢訂單是否處理,如果處理直接返回:return Content(xml, "text/xml"); 不跑下面代碼
//驗證請求是否從微信發過來(安全)
if (payNotifyRepHandler.IsTenpaySign())
{
}
else
{
}
return Content(xml, "text/xml");
}
}
}
View Code
三、關鍵點
1.支付頁面的鏈接不管是放到自定義菜單還是嵌到公眾號文章內,或是發到微信對話框讓用戶點擊支付,最終的鏈接是:https://open.weixin.qq.com/connect/oauth2/authorize?appid=這裡改成你的appid&redirect_uri=這裡是你支付頁面的地址&response_type=code&scope=SCOPE&state=1#wechat_redirect
這種約定的鏈接最終訪問的還是redirect_uri這個鏈接,同時微信回調這個鏈接還會帶上code和state兩個參數。code參數是當前用戶,state參數自定義,本文未用到。
2.後台GetPaySign方法同時接收code和openid是為了連續支付或者用戶取消了支付再次點擊支付設計的(每點一次支付就觸發一次後台GetPaySign方法),因為code是微信服務器回調帶的,只能用一次。所以第一次code傳到後台就調接口獲得用戶openid發回前端緩存起來,再起發起請求code和openid一起傳到後端,這個時候code無效了,但是openid可以用
3.微信支付回調接口(notify_url)不能帶參數,但是回調接口裡能從微信服務器推過來的xml文件裡提取出訂單號(out_trade_no),可以對其進行操作
爬蟲可恥,本文原始鏈接:http://www.cnblogs.com/oppoic/p/6132533.html
四、附錄
H5調起支付API:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
統一下單:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
微信JSSDK:https://mp.weixin.qq.com/wiki 微信網頁開發 - 微信JS-SDK說明文檔 - Ctrl+F搜索:chooseWXPay
OAuth驗證接口:http://qydev.weixin.qq.com/wiki/index.php?title=OAuth驗證接口
userid轉換成openid:http://qydev.weixin.qq.com/wiki/index.php?title=Userid與openid互換接口