微信支付有两种使用场景,一种是可以在微信之外的浏览器(如UC浏览器、手机自带浏览器等)中使用,在微信外部唤醒微信进行支付;还有一种是在微信自带内置浏览器中使用,比如微信公众号里边的支付、给客户在微信上发了一个支付链接等这类使用场景,这里主要说的是后一种“JSAPI支付”(官方文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1),下面的内容默认你已经有商户平台和微信公众号了。
直接调用微信的内置函数,就能直接唤起微信支付,难点在于微信支付需要的参数的获取,下面是官方文档,我们需要的就是这6个参数(这6个参数的拼写是大小写敏感的):
appId:就是微信公众号的appid
timeStamp:时间戳(秒)
nonceStr:随机串,这个随意,随机一个字符串就行了
package:这个里边存储的是微信支付订单的订单号,这是一个难点,也是这6个参数里边唯一一个需要从微信官方获取的参数,这个参数的值是"prepay_id=u802345jgfjsdfgsdg",是一个键值对,其中“u802345jgfjsdfgsdg888”是订单号,这个是微信统一下单接口返回的,统一下单接口在具体实现流程里边说。
signType:参数签名方式,就用MD5吧,与你进行签名运算的参数保持一致
paySign:参数签名,前面五个参数和值先排序(参数名按ASCII码从小到大),然后进行拼接,组成的字符串进行MD5运算或者HMAC-SHA256运算的结果(微信参数签名算法文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3)
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"wx24******", //公众号名称,由商户传入
"timeStamp":"1395712654", //时间戳,自1970年以来的秒数
"nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串
"package":"prepay_id=u802345jgfjsdfgsd",
"signType":"MD5", //微信签名方式:
"paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ){
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
});
}
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();
}
1.1商户平台(pay.weixin.qq.com)设置JSAPI支付支付目录
即调用上面内置js函数的页面目录,设置路径:商户平台-->产品中心-->开发配置。比如调用JSAPI支付的页面路径是www.abc.com/a/b/c/pay.html,你要设置的支付授权目录是www.abc.com/a/b/c/,设置www.abc.com/a/是错误的,即页面地址里边最后一个反斜杠之前的内容。
1.2在微信公众号(mp.weixin.qq.com)里边配置能获取用户openid的域名
jsapi支付需要获取用户的openid,需要在微信统一下单接口中使用,设置路径:公众号设置-->功能设置-->网页授权域名。用哪个域名获取的openid就用设置哪个域名。
大致流程是打开特定格式的url,微信回调后会在页面上添加一个code参数,然后用code参数获取openid。scope可以使用snsapi_base模式,这种不用用户手动授权,页面跳转无感知,缺点是只能获取到openid,不过用户其他信息此处也不用获取。程序上此处不再实现,这也是微信开发的基础操作了(官方文档:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html)。
封装商户id、公众号id、订单信息等数据,然后调用统一下单接口,注意在JSAPI支付中openid是必填的,统一接口的参数最好用SortedDictionary,可以自动对参数按照参数名进行排序,后续签名方便,统一下单接口方法如下(官方文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)
string key = "123123在你的商户平台上设置";
SortedDictionary strdata = new SortedDictionary();
strdata.Add("appid", appid);//微信公众账号ID
strdata.Add("mch_id", mch_id);//商户平台上的商户号
strdata.Add("nonce_str", nonce_str);//随机字符串
strdata.Add("body", body);//商品描述,支付的时候显示
strdata.Add("out_trade_no", out_trade_no);//订单号,自己生成,可同时在数据库添加订单数据
strdata.Add("total_fee", total_fee);//总金额,单位是分,如果是元则换算成分(乘100)
strdata.Add("spbill_create_ip", spbill_create_ip);//终端ip
strdata.Add("notify_url", notify_url);//微信异步通知的url链接
strdata.Add("trade_type", "JSAPI");//jsapi交易类型
strdata.Add("product_id", product_id);//商品ID,trade_type=NATIVE时的必填参数
strdata.Add("openid", openid);//上一步获取到的用户的openid,trade_type=JSAPI时的必填参数
strdata.Add("sign_type", "MD5");//参数的签名方法
string strparam = ToUrl(strdata, key);//拼接参数成字符串
string sign = GetWxSign(strparam);//对字典数据进行MD5签名
strdata.Add("sign", sign);//签名
string retData = HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder", parseXML(strdata));
辅助函数:
///
/// 拼接字典的参数和数值
///
///
///
///
public string ToUrl(SortedDictionary m_values, string key)
{
string buff = "";
foreach (KeyValuePair pair in m_values)
{
if (pair.Value.ToString() != "")
{
buff += pair.Key + "=" + pair.Value + "&";
}
}
buff += "key=" + key;
return buff;
}
///
/// 获取变量签名
///
/// 参数按英文字母排序后的字符串
///
public string GetWxSign(string data)
{
//MD5加密
var md5 = MD5.Create();
var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(data));
var sb = new StringBuilder();
foreach (byte b in bs)
{
sb.Append(b.ToString("x2"));
}
string sign = sb.ToString().ToUpper();
return sign;
}
///
/// post 参数 转换成xml
///
///
///
public string parseXML(SortedDictionary parameters)
{
StringBuilder sb = new StringBuilder();
sb.Append("");
foreach (string k in parameters.Keys)
{
string v = (string)parameters[k];
if (Regex.IsMatch(v, @"^[0-9.]$"))
{
sb.Append("<" + k + ">" + v + "" + k + ">");
}
else
{
sb.Append("<" + k + ">" + k + ">");
}
}
sb.Append(" ");
return sb.ToString();
}
///
/// HTTP POST方式请求数据
///
/// URL.
/// POST的数据
///
public string HttpPost(string url, string param)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.Accept = "*/*";
request.Timeout = 15000;
request.AllowAutoRedirect = false;
StreamWriter requestStream = null;
WebResponse response = null;
string responseStr = null;
try
{
requestStream = new StreamWriter(request.GetRequestStream(), Encoding.UTF8);
requestStream.Write(param);
requestStream.Close();
response = request.GetResponse();
if (response != null)
{
StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
responseStr = reader.ReadToEnd();
reader.Close();
}
}
catch (Exception ex)
{
return ex.Message;
}
finally
{
request = null;
requestStream = null;
response = null;
}
return responseStr;
}
到这里,我们终于获取到了从返回的数据中可以获取到prepay_id(预支付交易会话标识,也可以理解成微信的预支付订单id),也就凑齐了JSAPI的6个参数,这6个参数里边还有个小问题,就是paySign是另外5个参数的签名运算,这里还在后端进行吧,还是使用MD5。
注意预支付订单参数的拼接方式,另外这是后端代码,最终的结果是一个字符串,js调用这个参数的时候注意将字符串转化为json格式,转化方法不再赘述。
SortedDictionary m_values = new SortedDictionary();
m_values.Add("appId", appId);//微信公众号的appid
m_values.Add("timeStamp", timeStamp);//时间戳
m_values.Add("nonceStr", nonce_str);//随机字符串
m_values.Add("package", "prepay_id=" +prepay_id);//预支付订单,注意参数的拼接
m_values.Add("signType", "MD5");//签名算法
string wxdataStr = ToUrl(m_values, key);//拼接参数字符串
string paysign = GetWxSign(wxdataStr);//计算参数字符串的MD签名
m_values.Add("paySign", paysign);
string json = JsonConvert.SerializeObject(m_values);//转化成json格式
本人第4步的实现是通过异步调取的,前端JS实现如下:
var wxData;
$.ajax({
type: "post",
async: false,
url: '/ashx/WeiXinPay',
data: { "method": "wxpaycreate" },
success: function (data) {
if (data && data != "") {
wxData = JSON.parse(data);
console.log(wxData);
var agent = navigator.userAgent.toLowerCase();
var indexStart = agent.indexOf("micromessenger/");
var version = agent.substr(indexStart + 1, 1);
if (parseInt(version) < 5) {
alert('您的微信版本低于5.0,无法使用微信支付!');
return false;
}
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();
}
}
}
});
function onBridgeReady() {
WeixinJSBridge.invoke(
'getBrandWCPayRequest', wxData,
function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
alert('支付成功,返回订单列表!');
window.location.href = "onlinepay.html?forumid=" + localStorage.getItem("forumid") + "&companyid=" + localStorage.getItem("companyid") + "&ispay=1";
} else if (res.err_msg == 'get_brand_wcpay_request:cancel') {
alert('取消支付!');
} else {
alert("支付失败!");
}
});
}
到这里如果提示支付成功,则表示钱已经到你的微信账户了,但还少一步。目前只是前端提示支付成功,数据库里边的订单表中,订单的支付状态还没变,这就要用到统一下单接口里边的notify_url参数了,这个你设置的接收微信的异步通知接口,如果支付成功,微信会请求你这个接口,下面就是最后一步,修改订单的支付状态。
notify_url页面接收到的数据的处理流程
// 接收从微信后台POST过来的数据
System.IO.Stream s = Request.InputStream;
StringBuilder builder = new StringBuilder();
int count = 0;
byte[] buffer = new byte[1024];
while ((count = s.Read(buffer, 0, 1024)) > 0)
{
builder.Append(Encoding.UTF8.GetString(buffer, 0, count));
}
s.Flush();
s.Close();
s.Dispose();
if (builder.ToString().Length > 0)
{
XmlDocument xmldoc = new XmlDocument();
xmldoc.LoadXml(builder.ToString());//返回的数据转换成XML对象
string result_code = xmldoc.SelectSingleNode("xml").SelectSingleNode("result_code").InnerText;
if (result_code == "SUCCESS")
{
//支付成功,根据微信返回的订单号修改数据库的订单状态
string trade_no= xmldoc.SelectSingleNode("xml").SelectSingleNode("out_trade_no").InnerText;//统一下单接口中你设置的订单号
string wxorderid = xmldoc.SelectSingleNode("xml").SelectSingleNode("transaction_id").InnerText;//微信支付订单的订单号
}
else
{
//支付失败,做好支付失败的日志记录
}
}
else
{
//数据异常,添加日志记录
}
其实微信支付这块,比较麻烦的还是参数传递、校验这块,微信的“接口签名校验工具”(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1)是个好东西。检查下必填项是否全部填写,再用工具校验下你生成的数据跟校验工具生成的数据是否一致,参数没问题,一般也就没有其他的问题了