最近负责的一些项目开发,都用到了微信支付(微信公众号支付、微信H5支付、微信扫码支付)。在开发的过程中,在调试支付的过程中,或多或少都遇到了一些问题,今天总结下,分享,留存。代码在文章结尾处,有需要的同学可以下载看下。

先说注意的第一点,所有支付的第一步都是请求统一下单,统一下单,统一下单,请求URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder。

再说一个微信官方提供的一个很重要的工具,微信支付接口签名校验工具(网址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1),此工具旨在帮助开发者检测调用【微信支付接口API】时发送的请求参数中生成的签名是否正确,提交相关信息后可获得签名校验结果。特别实用!特别实用!特别实用!签名只要正确了,一切就OK了!

第一部分 微信公众号支付

微信公众号支付需要配置的参数有:APPID(微信公众号开发者ID)、APPSECRET(微信公众号开发者密码)、MCHID(商户ID)、KEY(商户密钥)。

微信公众号支付应用的场景是在微信内部的H5环境中使用的支付方式。因为要通过网页授权获取用户的OpenId,所以必须要配置网页授权域名。同时要配置JS接口安全域名。

JsApiConfig.cs

 using System.Web;
using System.Text;
using System.IO;
using System.Net;
using System;
using System.Xml;
using System.Collections.Generic;
using Gwbnsh.Common; namespace Gwbnsh.API.Payment.wxpay
{
public class JsApiConfig
{
#region 字段
private string partner = string.Empty;
private string key = string.Empty;
private string appid = string.Empty;
private string appsecret = string.Empty;
private string redirect_url = string.Empty;
private string notify_url = string.Empty;
#endregion public JsApiConfig(int site_payment_id)
{
Model.site_payment model = new BLL.site_payment().GetModel(site_payment_id); //站点支付方式
if (model != null)
{
Model.payment payModel = new BLL.payment().GetModel(model.payment_id); //支付平台
Model.sites siteModel = new BLL.sites().GetModel(model.site_id); //站点配置
Model.sysconfig sysConfig = new BLL.sysconfig().loadConfig(); //系统配置 partner = model.key1; //商户号(必须配置)
key = model.key2; //商户支付密钥,参考开户邮件设置(必须配置)
appid = model.key3; //绑定支付的APPID(必须配置)
appsecret = model.key4; //公众帐号secert(仅JSAPI支付的时候需要配置) //获取用户的OPENID回调地址及登录后的回调地址
redirect_url = "http://m.gwbnsh.net.cn/hd/SellPhone" + payModel.return_url;
notify_url = "http://m.gwbnsh.net.cn/hd/SellPhone" + payModel.notify_url;
}
}
} #region 属性
/// <summary>
/// 商户号(必须配置)
/// </summary>
public string Partner
{
get { return partner; }
set { partner = value; }
} /// <summary>
/// 获取或设交易安全校验码
/// </summary>
public string Key
{
get { return key; }
set { key = value; }
} /// <summary>
/// 绑定支付的APPID(必须配置)
/// </summary>
public string AppId
{
get { return appid; }
set { appid = value; }
} /// <summary>
/// 公众帐号secert(仅JSAPI支付的时候需要配置)
/// </summary>
public string AppSecret
{
get { return appsecret; }
set { appsecret = value; }
} /// <summary>
/// 获取用户的OPENID回调地址
/// </summary>
public string Redirect_url
{
get { return redirect_url; }
} /// <summary>
/// 获取服务器异步通知页面路径
/// </summary>
public string Notify_url
{
get { return notify_url; }
} #endregion
}
}

JsApiConfig.cs

JsApiPay.cs

 using System;
using System.Collections.Generic;
using System.Web;
using System.Net;
using System.IO;
using System.Text;
using Gwbnsh.Common; namespace Gwbnsh.API.Payment.wxpay
{
public class JsApiPay
{
/**
*
* 测速上报
* @param string interface_url 接口URL
* @param int timeCost 接口耗时
* @param WxPayData inputObj参数数组
*/
public static void ReportCostTime(int paymentId, string interface_url, int timeCost, WxPayData inputObj)
{
//如果仅失败上报
if (inputObj.IsSet("return_code") && inputObj.GetValue("return_code").ToString() == "SUCCESS" &&
inputObj.IsSet("result_code") && inputObj.GetValue("result_code").ToString() == "SUCCESS")
{
return;
} //上报逻辑
WxPayData data = new WxPayData();
data.SetValue("interface_url", interface_url);
data.SetValue("execute_time_", timeCost);
//返回状态码
if (inputObj.IsSet("return_code"))
{
data.SetValue("return_code", inputObj.GetValue("return_code"));
}
//返回信息
if (inputObj.IsSet("return_msg"))
{
data.SetValue("return_msg", inputObj.GetValue("return_msg"));
}
//业务结果
if (inputObj.IsSet("result_code"))
{
data.SetValue("result_code", inputObj.GetValue("result_code"));
}
//错误代码
if (inputObj.IsSet("err_code"))
{
data.SetValue("err_code", inputObj.GetValue("err_code"));
}
//错误代码描述
if (inputObj.IsSet("err_code_des"))
{
data.SetValue("err_code_des", inputObj.GetValue("err_code_des"));
}
//商户订单号
if (inputObj.IsSet("out_trade_no"))
{
data.SetValue("out_trade_no", inputObj.GetValue("out_trade_no"));
}
//设备号
if (inputObj.IsSet("device_info"))
{
data.SetValue("device_info", inputObj.GetValue("device_info"));
} try
{
Report(paymentId, data);
}
catch (WxPayException ex)
{
//不做任何处理
}
} /**
*
* 测速上报接口实现
* @param WxPayData inputObj 提交给测速上报接口的参数
* @param int timeOut 测速上报接口超时时间
* @throws WxPayException
* @return 成功时返回测速上报接口返回的结果,其他抛异常
*/
public static WxPayData Report(int paymentId, WxPayData inputObj, int timeOut = )
{
JsApiConfig jsApiConfig = new JsApiConfig(paymentId);
string url = "https://api.mch.weixin.qq.com/payitil/report";
//检测必填参数
if (!inputObj.IsSet("interface_url"))
{
throw new WxPayException("接口URL,缺少必填参数interface_url!");
}
if (!inputObj.IsSet("return_code"))
{
throw new WxPayException("返回状态码,缺少必填参数return_code!");
}
if (!inputObj.IsSet("result_code"))
{
throw new WxPayException("业务结果,缺少必填参数result_code!");
}
if (!inputObj.IsSet("user_ip"))
{
throw new WxPayException("访问接口IP,缺少必填参数user_ip!");
}
if (!inputObj.IsSet("execute_time_"))
{
throw new WxPayException("接口耗时,缺少必填参数execute_time_!");
} inputObj.SetValue("appid", jsApiConfig.AppId);//公众账号ID
inputObj.SetValue("mch_id", jsApiConfig.Partner);//商户号
inputObj.SetValue("user_ip", DTRequest.GetIP());//终端ip
inputObj.SetValue("time", DateTime.Now.ToString("yyyyMMddHHmmss"));//商户上报时间
inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
inputObj.SetValue("sign", inputObj.MakeSign(jsApiConfig.Key));//签名
string xml = inputObj.ToXml(); string response = HttpService.Post(xml, url, false, timeOut); WxPayData result = new WxPayData();
result.FromXml(response, jsApiConfig.Key);
return result;
} /**
* 生成时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数
* @return 时间戳
*/
public static string GenerateTimeStamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(, , , , , , );
return Convert.ToInt64(ts.TotalSeconds).ToString();
} /**
* 生成随机串,随机串包含字母或数字
* @return 随机串
*/
public static string GenerateNonceStr()
{
return Guid.NewGuid().ToString().Replace("-", "");
} /// <summary>
/// 接收从微信支付后台发送过来的数据暂不验证签名
/// </summary>
/// <returns>微信支付后台返回的数据</returns>
public static WxPayData GetNotifyData()
{
//接收从微信后台POST过来的数据
System.IO.Stream s = HttpContext.Current.Request.InputStream;
int count = ;
byte[] buffer = new byte[];
StringBuilder builder = new StringBuilder();
while ((count = s.Read(buffer, , )) > )
{
builder.Append(Encoding.UTF8.GetString(buffer, , count));
}
s.Flush();
s.Close();
s.Dispose(); //转换数据格式并验证签名
WxPayData data = new WxPayData();
try
{
data.FromXml(builder.ToString());
}
catch (WxPayException ex)
{
//若有错误,则立即返回结果给微信支付后台
WxPayData res = new WxPayData();
res.SetValue("return_code", "FAIL");
res.SetValue("return_msg", ex.Message);
HttpContext.Current.Response.Write(res.ToXml());
HttpContext.Current.Response.End();
} return data;
} /**
*
* 查询订单
* @param WxPayData inputObj 提交给查询订单API的参数
* @param int timeOut 超时时间
* @throws WxPayException
* @return 成功时返回订单查询结果,其他抛异常
*/
public static WxPayData OrderQuery(int paymentId, WxPayData inputObj, int timeOut = )
{
string sendUrl = "https://api.mch.weixin.qq.com/pay/orderquery";
//检测必填参数
if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id"))
{
throw new WxPayException("订单查询接口中,out_trade_no、transaction_id至少填一个!");
}
JsApiConfig jsApiConfig = new JsApiConfig(paymentId);
inputObj.SetValue("appid", jsApiConfig.AppId);//公众账号ID
inputObj.SetValue("mch_id", jsApiConfig.Partner);//商户号
inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
inputObj.SetValue("sign", inputObj.MakeSign(jsApiConfig.Key));//签名
string xml = inputObj.ToXml();
var startTime = DateTime.Now; //开始时间
string response = HttpService.Post(xml, sendUrl, false, timeOut);//调用HTTP通信接口提交数据
var endTime = DateTime.Now; //结束时间
int timeCost = (int)((endTime - startTime).TotalMilliseconds); //计算所用时间
//将xml格式的数据转化为对象以返回
WxPayData result = new WxPayData();
result.FromXml(response, jsApiConfig.Key);
ReportCostTime(paymentId, sendUrl, timeCost, result);//测速上报
return result;
} }
}

JsApiPay.cs

第二部分 微信H5支付

微信H5支付是微信官方2017年上半年刚刚对外开放的支付模式,它主要应用于在手机网站在移动浏览器(非微信环境)调用微信支付的场景。

注意:微信H5支付需要在微信支付商户平台单独申请开通,否则无法使用。

微信H5支付的流程比较简单,就是拼接请求的xml数据,进行统一下单,获取到支付的mweb_url,然后请求这个url网址就行。

H5Config.cs

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace Gwbnsh.API.Payment.wxpay
{
/// <summary>
/// 移动端非微信浏览器支付
/// </summary>
public class H5Config
{
#region 字段
private string partner = string.Empty;
private string key = string.Empty;
private string appid = string.Empty;
private string notify_url = string.Empty;
#endregion public H5Config(int site_payment_id)
{
Model.site_payment model = new BLL.site_payment().GetModel(site_payment_id); //站点支付方式
if (model != null)
{
Model.payment payModel = new BLL.payment().GetModel(model.payment_id); //支付平台
Model.sites siteModel = new BLL.sites().GetModel(model.site_id); //站点配置
Model.sysconfig sysConfig = new BLL.sysconfig().loadConfig(); //系统配置 partner = model.key1; //商户号(必须配置)
key = model.key2; //商户支付密钥,参考开户邮件设置(必须配置)
appid = model.key3; //绑定支付的APPID(必须配置)
notify_url = "";
}
} #region 属性
/// <summary>
/// 商户号(必须配置)
/// </summary>
public string Partner
{
get { return partner; }
set { partner = value; }
} /// <summary>
/// 获取或设交易安全校验码
/// </summary>
public string Key
{
get { return key; }
set { key = value; }
} /// <summary>
/// 绑定支付的APPID(必须配置)
/// </summary>
public string AppId
{
get { return appid; }
set { appid = value; }
} /// <summary>
/// 获取服务器异步通知页面路径
/// </summary>
public string Notify_url
{
get { return notify_url; }
} #endregion
}
}

H5Config.cs

H5Pay.cs

 using Gwbnsh.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web; namespace Gwbnsh.API.Payment.wxpay
{
public class H5Pay
{
/**
*
* 测速上报
* @param string interface_url 接口URL
* @param int timeCost 接口耗时
* @param WxPayData inputObj参数数组
*/
public static void ReportCostTime(int paymentId, string interface_url, int timeCost, WxPayData inputObj)
{
//如果仅失败上报
if (inputObj.IsSet("return_code") && inputObj.GetValue("return_code").ToString() == "SUCCESS" &&
inputObj.IsSet("result_code") && inputObj.GetValue("result_code").ToString() == "SUCCESS")
{
return;
} //上报逻辑
WxPayData data = new WxPayData();
data.SetValue("interface_url", interface_url);
data.SetValue("execute_time_", timeCost);
//返回状态码
if (inputObj.IsSet("return_code"))
{
data.SetValue("return_code", inputObj.GetValue("return_code"));
}
//返回信息
if (inputObj.IsSet("return_msg"))
{
data.SetValue("return_msg", inputObj.GetValue("return_msg"));
}
//业务结果
if (inputObj.IsSet("result_code"))
{
data.SetValue("result_code", inputObj.GetValue("result_code"));
}
//错误代码
if (inputObj.IsSet("err_code"))
{
data.SetValue("err_code", inputObj.GetValue("err_code"));
}
//错误代码描述
if (inputObj.IsSet("err_code_des"))
{
data.SetValue("err_code_des", inputObj.GetValue("err_code_des"));
}
//商户订单号
if (inputObj.IsSet("out_trade_no"))
{
data.SetValue("out_trade_no", inputObj.GetValue("out_trade_no"));
}
//设备号
if (inputObj.IsSet("device_info"))
{
data.SetValue("device_info", inputObj.GetValue("device_info"));
} try
{
Report(paymentId, data);
}
catch (WxPayException ex)
{
//不做任何处理
}
} /**
*
* 测速上报接口实现
* @param WxPayData inputObj 提交给测速上报接口的参数
* @param int timeOut 测速上报接口超时时间
* @throws WxPayException
* @return 成功时返回测速上报接口返回的结果,其他抛异常
*/
public static WxPayData Report(int paymentId, WxPayData inputObj, int timeOut = )
{
H5Config h5Config = new H5Config(paymentId);
string url = "https://api.mch.weixin.qq.com/payitil/report";
//检测必填参数
if (!inputObj.IsSet("interface_url"))
{
throw new WxPayException("接口URL,缺少必填参数interface_url!");
}
if (!inputObj.IsSet("return_code"))
{
throw new WxPayException("返回状态码,缺少必填参数return_code!");
}
if (!inputObj.IsSet("result_code"))
{
throw new WxPayException("业务结果,缺少必填参数result_code!");
}
if (!inputObj.IsSet("user_ip"))
{
throw new WxPayException("访问接口IP,缺少必填参数user_ip!");
}
if (!inputObj.IsSet("execute_time_"))
{
throw new WxPayException("接口耗时,缺少必填参数execute_time_!");
} inputObj.SetValue("appid", h5Config.AppId);//公众账号ID
inputObj.SetValue("mch_id", h5Config.Partner);//商户号
inputObj.SetValue("user_ip", DTRequest.GetIP());//终端ip
inputObj.SetValue("time", DateTime.Now.ToString("yyyyMMddHHmmss"));//商户上报时间
inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
inputObj.SetValue("sign", inputObj.MakeSign(h5Config.Key));//签名
string xml = inputObj.ToXml(); string response = HttpService.Post(xml, url, false, timeOut); WxPayData result = new WxPayData();
result.FromXml(response, h5Config.Key);
return result;
} /**
* 生成时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数
* @return 时间戳
*/
public static string GenerateTimeStamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(, , , , , , );
return Convert.ToInt64(ts.TotalSeconds).ToString();
} /**
* 生成随机串,随机串包含字母或数字
* @return 随机串
*/
public static string GenerateNonceStr()
{
return Guid.NewGuid().ToString().Replace("-", "");
}
/// <summary>
/// 接收从微信支付后台发送过来的数据未验证签名
/// </summary>
/// <returns>微信支付后台返回的数据</returns>
public static WxPayData GetNotifyData()
{
//接收从微信后台POST过来的数据
System.IO.Stream s = HttpContext.Current.Request.InputStream;
int count = ;
byte[] buffer = new byte[];
StringBuilder builder = new StringBuilder();
while ((count = s.Read(buffer, , )) > )
{
builder.Append(Encoding.UTF8.GetString(buffer, , count));
}
s.Flush();
s.Close();
s.Dispose(); //转换数据格式暂不验证签名
WxPayData data = new WxPayData();
try
{
data.FromXml(builder.ToString());
}
catch (WxPayException ex)
{
//若签名错误,则立即返回结果给微信支付后台
WxPayData res = new WxPayData();
res.SetValue("return_code", "FAIL");
res.SetValue("return_msg", ex.Message);
HttpContext.Current.Response.Write(res.ToXml());
HttpContext.Current.Response.End();
} return data;
} /**
*
* 查询订单
* @param WxPayData inputObj 提交给查询订单API的参数
* @param int timeOut 超时时间
* @throws WxPayException
* @return 成功时返回订单查询结果,其他抛异常
*/
public static WxPayData OrderQuery(int paymentId, WxPayData inputObj, int timeOut = )
{
string sendUrl = "https://api.mch.weixin.qq.com/pay/orderquery";
//检测必填参数
if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id"))
{
throw new WxPayException("订单查询接口中,out_trade_no、transaction_id至少填一个!");
}
H5Config h5Config = new H5Config(paymentId);
inputObj.SetValue("appid", h5Config.AppId);//公众账号ID
inputObj.SetValue("mch_id", h5Config.Partner);//商户号
inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
inputObj.SetValue("sign", inputObj.MakeSign(h5Config.Key));//签名
string xml = inputObj.ToXml();
var startTime = DateTime.Now; //开始时间
string response = HttpService.Post(xml, sendUrl, false, timeOut);//调用HTTP通信接口提交数据
var endTime = DateTime.Now; //结束时间
int timeCost = (int)((endTime - startTime).TotalMilliseconds); //计算所用时间
//将xml格式的数据转化为对象以返回
WxPayData result = new WxPayData();
result.FromXml(response, h5Config.Key);
ReportCostTime(paymentId, sendUrl, timeCost, result);//测速上报
return result;
} }
}

H5Pay.cs

第三部分 微信扫码支付

微信扫码支付一般应用的场景是PC端电脑支付。微信扫码支付可分为两种模式,根据支付场景选择相应模式。一般情况下的PC端扫码支付选择的是模式二,需要注意的是模式二无回调函数。

【模式一】商户后台系统根据微信支付规则链接生成二维码,链接中带固定参数productid(可定义为产品标识或订单号)。用户扫码后,微信支付系统将productid和用户唯一标识(openid)回调商户后台系统(需要设置支付回调URL),商户后台系统根据productid生成支付交易,最后微信支付系统发起用户支付流程。

【模式二】商户后台系统调用微信支付【统一下单API】生成预付交易,将接口返回的链接生成二维码,用户扫码后输入密码完成支付交易。注意:该模式的预付单有效期为2小时,过期后无法支付。

微信扫码支付最友好的解决方案就是支付完成之后通过JS设置监听函数,通过该函数完成跳转。可参考的代码如下:

NativeConfig.cs

 using System.Web;
using System.Text;
using System.IO;
using System.Net;
using System;
using System.Xml;
using System.Collections.Generic;
using Gwbnsh.Common; namespace Gwbnsh.API.Payment.wxpay
{
public class NativeConfig
{
#region 字段
private string partner = string.Empty;
private string key = string.Empty;
private string appid = string.Empty;
private string notify_url = string.Empty;
#endregion public NativeConfig(int site_payment_id)
{
Model.site_payment model = new BLL.site_payment().GetModel(site_payment_id); //站点支付方式
if (model != null)
{
Model.payment payModel = new BLL.payment().GetModel(model.payment_id); //支付平台
Model.sites siteModel = new BLL.sites().GetModel(model.site_id); //站点配置
Model.sysconfig sysConfig = new BLL.sysconfig().loadConfig(); //系统配置 partner = model.key1; //商户号(必须配置)
key = model.key2; //商户支付密钥,参考开户邮件设置(必须配置)
appid = model.key3; //绑定支付的APPID(必须配置)
notify_url = "";
}
} #region 属性
/// <summary>
/// 商户号(必须配置)
/// </summary>
public string Partner
{
get { return partner; }
set { partner = value; }
} /// <summary>
/// 获取或设交易安全校验码
/// </summary>
public string Key
{
get { return key; }
set { key = value; }
} /// <summary>
/// 绑定支付的APPID(必须配置)
/// </summary>
public string AppId
{
get { return appid; }
set { appid = value; }
} /// <summary>
/// 获取服务器异步通知页面路径
/// </summary>
public string Notify_url
{
get { return notify_url; }
} #endregion
}
}

NativeConfig.cs

NativePay.cs

 using System;
using System.Collections.Generic;
using System.Web;
using System.Net;
using System.IO;
using System.Text;
using Gwbnsh.Common; namespace Gwbnsh.API.Payment.wxpay
{
public class NativePay
{
/**
*
* 测速上报
* @param string interface_url 接口URL
* @param int timeCost 接口耗时
* @param WxPayData inputObj参数数组
*/
public static void ReportCostTime(int paymentId, string interface_url, int timeCost, WxPayData inputObj)
{
//如果仅失败上报
if (inputObj.IsSet("return_code") && inputObj.GetValue("return_code").ToString() == "SUCCESS" &&
inputObj.IsSet("result_code") && inputObj.GetValue("result_code").ToString() == "SUCCESS")
{
return;
} //上报逻辑
WxPayData data = new WxPayData();
data.SetValue("interface_url", interface_url);
data.SetValue("execute_time_", timeCost);
//返回状态码
if (inputObj.IsSet("return_code"))
{
data.SetValue("return_code", inputObj.GetValue("return_code"));
}
//返回信息
if (inputObj.IsSet("return_msg"))
{
data.SetValue("return_msg", inputObj.GetValue("return_msg"));
}
//业务结果
if (inputObj.IsSet("result_code"))
{
data.SetValue("result_code", inputObj.GetValue("result_code"));
}
//错误代码
if (inputObj.IsSet("err_code"))
{
data.SetValue("err_code", inputObj.GetValue("err_code"));
}
//错误代码描述
if (inputObj.IsSet("err_code_des"))
{
data.SetValue("err_code_des", inputObj.GetValue("err_code_des"));
}
//商户订单号
if (inputObj.IsSet("out_trade_no"))
{
data.SetValue("out_trade_no", inputObj.GetValue("out_trade_no"));
}
//设备号
if (inputObj.IsSet("device_info"))
{
data.SetValue("device_info", inputObj.GetValue("device_info"));
} try
{
Report(paymentId, data);
}
catch (WxPayException ex)
{
//不做任何处理
}
} /**
*
* 测速上报接口实现
* @param WxPayData inputObj 提交给测速上报接口的参数
* @param int timeOut 测速上报接口超时时间
* @throws WxPayException
* @return 成功时返回测速上报接口返回的结果,其他抛异常
*/
public static WxPayData Report(int paymentId, WxPayData inputObj, int timeOut = )
{
NativeConfig nativeConfig = new NativeConfig(paymentId);
string url = "https://api.mch.weixin.qq.com/payitil/report";
//检测必填参数
if (!inputObj.IsSet("interface_url"))
{
throw new WxPayException("接口URL,缺少必填参数interface_url!");
}
if (!inputObj.IsSet("return_code"))
{
throw new WxPayException("返回状态码,缺少必填参数return_code!");
}
if (!inputObj.IsSet("result_code"))
{
throw new WxPayException("业务结果,缺少必填参数result_code!");
}
if (!inputObj.IsSet("user_ip"))
{
throw new WxPayException("访问接口IP,缺少必填参数user_ip!");
}
if (!inputObj.IsSet("execute_time_"))
{
throw new WxPayException("接口耗时,缺少必填参数execute_time_!");
} inputObj.SetValue("appid", nativeConfig.AppId);//公众账号ID
inputObj.SetValue("mch_id", nativeConfig.Partner);//商户号
inputObj.SetValue("user_ip", DTRequest.GetIP());//终端ip
inputObj.SetValue("time", DateTime.Now.ToString("yyyyMMddHHmmss"));//商户上报时间
inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
inputObj.SetValue("sign", inputObj.MakeSign(nativeConfig.Key));//签名
string xml = inputObj.ToXml(); string response = HttpService.Post(xml, url, false, timeOut); WxPayData result = new WxPayData();
result.FromXml(response, nativeConfig.Key);
return result;
} /**
* 生成时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数
* @return 时间戳
*/
public static string GenerateTimeStamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(, , , , , , );
return Convert.ToInt64(ts.TotalSeconds).ToString();
} /**
* 生成随机串,随机串包含字母或数字
* @return 随机串
*/
public static string GenerateNonceStr()
{
return Guid.NewGuid().ToString().Replace("-", "");
}
/// <summary>
/// 接收从微信支付后台发送过来的数据未验证签名
/// </summary>
/// <returns>微信支付后台返回的数据</returns>
public static WxPayData GetNotifyData()
{
//接收从微信后台POST过来的数据
System.IO.Stream s = HttpContext.Current.Request.InputStream;
int count = ;
byte[] buffer = new byte[];
StringBuilder builder = new StringBuilder();
while ((count = s.Read(buffer, , )) > )
{
builder.Append(Encoding.UTF8.GetString(buffer, , count));
}
s.Flush();
s.Close();
s.Dispose(); //转换数据格式暂不验证签名
WxPayData data = new WxPayData();
try
{
data.FromXml(builder.ToString());
}
catch (WxPayException ex)
{
//若签名错误,则立即返回结果给微信支付后台
WxPayData res = new WxPayData();
res.SetValue("return_code", "FAIL");
res.SetValue("return_msg", ex.Message);
HttpContext.Current.Response.Write(res.ToXml());
HttpContext.Current.Response.End();
} return data;
} /**
*
* 查询订单
* @param WxPayData inputObj 提交给查询订单API的参数
* @param int timeOut 超时时间
* @throws WxPayException
* @return 成功时返回订单查询结果,其他抛异常
*/
public static WxPayData OrderQuery(int paymentId, WxPayData inputObj, int timeOut = )
{
string sendUrl = "https://api.mch.weixin.qq.com/pay/orderquery";
//检测必填参数
if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id"))
{
throw new WxPayException("订单查询接口中,out_trade_no、transaction_id至少填一个!");
}
NativeConfig nativeConfig = new NativeConfig(paymentId);
inputObj.SetValue("appid", nativeConfig.AppId);//公众账号ID
inputObj.SetValue("mch_id", nativeConfig.Partner);//商户号
inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
inputObj.SetValue("sign", inputObj.MakeSign(nativeConfig.Key));//签名
string xml = inputObj.ToXml();
var startTime = DateTime.Now; //开始时间
string response = HttpService.Post(xml, sendUrl, false, timeOut);//调用HTTP通信接口提交数据
var endTime = DateTime.Now; //结束时间
int timeCost = (int)((endTime - startTime).TotalMilliseconds); //计算所用时间
//将xml格式的数据转化为对象以返回
WxPayData result = new WxPayData();
result.FromXml(response, nativeConfig.Key);
ReportCostTime(paymentId, sendUrl, timeCost, result);//测速上报
return result;
} }
}

NativePay.cs

以下为扫码支付、H5支付以及公众号支付需要用到的共同类:

HttpService.cs

 using System;
using System.Collections.Generic;
using System.Web;
using System.Net;
using System.IO;
using System.Text;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates; namespace Gwbnsh.API.Payment.wxpay
{
/// <summary>
/// http连接基础类,负责底层的http通信
/// </summary>
public class HttpService
{
public static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
//直接确认,否则打不开
return true;
} public static string Post(string xml, string url, bool isUseCert, int timeout)
{
System.GC.Collect();//垃圾回收,回收没有正常关闭的http连接 string result = "";//返回结果 HttpWebRequest request = null;
HttpWebResponse response = null;
Stream reqStream = null; try
{
//设置最大连接数
ServicePointManager.DefaultConnectionLimit = ;
//设置https验证方式
if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
{
ServicePointManager.ServerCertificateValidationCallback =
new RemoteCertificateValidationCallback(CheckValidationResult);
} /***************************************************************
* 下面设置HttpWebRequest的相关属性
* ************************************************************/
request = (HttpWebRequest)WebRequest.Create(url); request.Method = "POST";
request.Timeout = timeout * ; //设置代理服务器
/*WebProxy proxy = new WebProxy(); //定义一个网关对象
proxy.Address = new Uri(WxPayConfig.PROXY_URL); //网关服务器端口:端口
request.Proxy = proxy;*/ //设置POST的数据类型和长度
request.ContentType = "text/xml";
byte[] data = System.Text.Encoding.UTF8.GetBytes(xml);
request.ContentLength = data.Length; //是否使用证书
/*if (isUseCert)
{
string path = HttpContext.Current.Request.PhysicalApplicationPath;
X509Certificate2 cert = new X509Certificate2(path + WxPayConfig.SSLCERT_PATH, WxPayConfig.SSLCERT_PASSWORD);
request.ClientCertificates.Add(cert);
}*/ //往服务器写入数据
reqStream = request.GetRequestStream();
reqStream.Write(data, , data.Length);
reqStream.Close(); //获取服务端返回
response = (HttpWebResponse)request.GetResponse(); //获取服务端返回数据
StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
result = sr.ReadToEnd().Trim();
sr.Close();
}
catch (System.Threading.ThreadAbortException e)
{
System.Threading.Thread.ResetAbort();
}
catch (WebException e)
{
throw new WxPayException(e.ToString());
}
catch (Exception e)
{
throw new WxPayException(e.ToString());
}
finally
{
//关闭连接和流
if (response != null)
{
response.Close();
}
if (request != null)
{
request.Abort();
}
}
return result;
} /// <summary>
/// 处理http GET请求,返回数据
/// </summary>
/// <param name="url">请求的url地址</param>
/// <returns>http GET成功后返回的数据,失败抛WebException异常</returns>
public static string Get(string url)
{
System.GC.Collect();
string result = ""; HttpWebRequest request = null;
HttpWebResponse response = null; //请求url以获取数据
try
{
//设置最大连接数
ServicePointManager.DefaultConnectionLimit = ;
//设置https验证方式
if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
{
ServicePointManager.ServerCertificateValidationCallback =
new RemoteCertificateValidationCallback(CheckValidationResult);
} /***************************************************************
* 下面设置HttpWebRequest的相关属性
* ************************************************************/
request = (HttpWebRequest)WebRequest.Create(url); request.Method = "GET"; //设置代理
/*WebProxy proxy = new WebProxy();
proxy.Address = new Uri(WxPayConfig.PROXY_URL);
request.Proxy = proxy;*/ //获取服务器返回
response = (HttpWebResponse)request.GetResponse(); //获取HTTP返回数据
StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
result = sr.ReadToEnd().Trim();
sr.Close();
}
catch (System.Threading.ThreadAbortException e)
{
System.Threading.Thread.ResetAbort();
}
catch (WebException e)
{
throw new WxPayException(e.ToString());
}
catch (Exception e)
{
throw new WxPayException(e.ToString());
}
finally
{
//关闭连接和流
if (response != null)
{
response.Close();
}
if (request != null)
{
request.Abort();
}
}
return result;
}
}
}

HttpService.cs

WxPayData.cs

 using System;
using System.Collections.Generic;
using System.Web;
using System.Xml;
using System.Security.Cryptography;
using System.Text;
using Gwbnsh.Common; namespace Gwbnsh.API.Payment.wxpay
{
/// <summary>
/// 微信支付协议接口数据类,所有的API接口通信都依赖这个数据结构,
/// 在调用接口之前先填充各个字段的值,然后进行接口通信,
/// 这样设计的好处是可扩展性强,用户可随意对协议进行更改而不用重新设计数据结构,
/// 还可以随意组合出不同的协议数据包,不用为每个协议设计一个数据包结构
/// </summary>
public class WxPayData
{
public WxPayData()
{ } //采用排序的Dictionary的好处是方便对数据包进行签名,不用再签名之前再做一次排序
private SortedDictionary<string, object> m_values = new SortedDictionary<string, object>(); /**
* 设置某个字段的值
* @param key 字段名
* @param value 字段值
*/
public void SetValue(string key, object value)
{
m_values[key] = value;
} /**
* 根据字段名获取某个字段的值
* @param key 字段名
* @return key对应的字段值
*/
public object GetValue(string key)
{
object o = null;
m_values.TryGetValue(key, out o);
return o;
} /**
* 判断某个字段是否已设置
* @param key 字段名
* @return 若字段key已被设置,则返回true,否则返回false
*/
public bool IsSet(string key)
{
object o = null;
m_values.TryGetValue(key, out o);
if (null != o)
return true;
else
return false;
} /**
* @将Dictionary转成xml
* @return 经转换得到的xml串
* @throws WxPayException
**/
public string ToXml()
{
//数据为空时不能转化为xml格式
if ( == m_values.Count)
{
throw new WxPayException("WxPayData数据为空!");
} string xml = "<xml>";
foreach (KeyValuePair<string, object> pair in m_values)
{
//字段值不能为null,会影响后续流程
if (pair.Value == null)
{
throw new WxPayException("WxPayData内部含有值为null的字段!");
} if (pair.Value.GetType() == typeof(int))
{
xml += "<" + pair.Key + ">" + pair.Value + "</" + pair.Key + ">";
}
else if (pair.Value.GetType() == typeof(string))
{
xml += "<" + pair.Key + ">" + "<![CDATA[" + pair.Value + "]]></" + pair.Key + ">";
}
else//除了string和int类型不能含有其他数据类型
{
throw new WxPayException("WxPayData字段数据类型错误!");
}
}
xml += "</xml>";
return xml;
} /**
* @接收从微信后台POST过来的数据(未验证签名)
* @return 经转换得到的Dictionary
* @throws WxPayException
*/
public SortedDictionary<string, object> GetRequest()
{
//接收从微信后台POST过来的数据
System.IO.Stream s = HttpContext.Current.Request.InputStream;
int count = ;
byte[] buffer = new byte[];
StringBuilder builder = new StringBuilder();
while ((count = s.Read(buffer, , )) > )
{
builder.Append(Encoding.UTF8.GetString(buffer, , count));
}
s.Flush();
s.Close();
s.Dispose(); if (string.IsNullOrEmpty(builder.ToString()))
{
throw new WxPayException("将空的xml串转换为WxPayData不合法!");
} XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(builder.ToString());
XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>
XmlNodeList nodes = xmlNode.ChildNodes;
foreach (XmlNode xn in nodes)
{
XmlElement xe = (XmlElement)xn;
m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
} return m_values;
} /**
* @将xml转为WxPayData对象并返回对象内部的数据
* @param string 待转换的xml串
* @return 经转换得到的Dictionary
* @throws WxPayException
*/
public SortedDictionary<string, object> FromXml(string xml, string key)
{
if (string.IsNullOrEmpty(xml))
{
throw new WxPayException("将空的xml串转换为WxPayData不合法!");
} XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xml);
XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>
XmlNodeList nodes = xmlNode.ChildNodes;
foreach (XmlNode xn in nodes)
{
XmlElement xe = (XmlElement)xn;
m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
} try
{
//2015-06-29 错误是没有签名
if (m_values["return_code"] != "SUCCESS")
{
return m_values;
}
CheckSign(key);//验证签名,不通过会抛异常
}
catch (Exception ex)
{
throw new WxPayException(ex.Message);
} return m_values;
}
/**
* @将xml转为WxPayData对象并返回对象内部的数据(未验证签名)
* @param string 待转换的xml串
* @return 经转换得到的Dictionary
* @throws WxPayException
*/
public SortedDictionary<string, object> FromXml(string xml)
{
if (string.IsNullOrEmpty(xml))
{
throw new WxPayException("将空的xml串转换为WxPayData不合法!");
} XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xml);
XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>
XmlNodeList nodes = xmlNode.ChildNodes;
foreach (XmlNode xn in nodes)
{
XmlElement xe = (XmlElement)xn;
m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
} try
{
//2015-06-29 错误是没有签名
if (m_values["return_code"] != "SUCCESS")
{
return m_values;
}
}
catch (Exception ex)
{
throw new WxPayException(ex.Message);
} return m_values;
}
/**
* @Dictionary格式转化成url参数格式
* @ return url格式串, 该串不包含sign字段值
*/
public string ToUrl()
{
string buff = "";
foreach (KeyValuePair<string, object> pair in m_values)
{
if (pair.Value == null)
{
throw new WxPayException("WxPayData内部含有值为null的字段!");
} if (pair.Key != "sign" && pair.Value.ToString() != "")
{
buff += pair.Key + "=" + pair.Value + "&";
}
}
buff = buff.Trim('&');
return buff;
} /**
* @Dictionary格式化成Json
* @return json串数据
*/
public string ToJson()
{
string jsonStr = JsonHelper.ObjectToJSON(m_values);
return jsonStr;
} /**
* @values格式化成能在Web页面上显示的结果(因为web页面上不能直接输出xml格式的字符串)
*/
public string ToPrintStr()
{
string str = "";
foreach (KeyValuePair<string, object> pair in m_values)
{
if (pair.Value == null)
{
throw new WxPayException("WxPayData内部含有值为null的字段!");
} str += string.Format("{0}={1}<br>", pair.Key, pair.Value.ToString());
}
return str;
} /**
* @生成签名,详见签名生成算法
* @return 签名, sign字段不参加签名
*/
public string MakeSign(string key)
{
//转url格式
string str = ToUrl();
//在string后加入API KEY
str += "&key=" + key;
//MD5加密
var md5 = MD5.Create();
var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
var sb = new StringBuilder();
foreach (byte b in bs)
{
sb.Append(b.ToString("x2"));
}
//所有字符转为大写
return sb.ToString().ToUpper();
} /**
*
* 检测签名是否正确
* 正确返回true,错误抛异常
*/
public bool CheckSign(string key)
{
//如果没有设置签名,则跳过检测
if (!IsSet("sign"))
{
throw new WxPayException("WxPayData签名存在但不合法!");
}
//如果设置了签名但是签名为空,则抛异常
else if (GetValue("sign") == null || GetValue("sign").ToString() == "")
{
throw new WxPayException("WxPayData签名存在但不合法!");
} //获取接收到的签名
string return_sign = GetValue("sign").ToString(); //在本地计算新的签名
string cal_sign = MakeSign(key); if (cal_sign == return_sign)
{
return true;
} throw new WxPayException("WxPayData签名验证错误!");
} /**
* @获取Dictionary
*/
public SortedDictionary<string, object> GetValues()
{
return m_values;
}
}
}

WxPayData.cs

WxPayException.cs

 using System;
using System.Collections.Generic;
using System.Web; namespace Gwbnsh.API.Payment.wxpay
{
public class WxPayException : Exception
{
public WxPayException(string msg) : base(msg)
{ }
}
}

WxPayException.cs

最后,总结一下上述几种支付方式需要注意的点。

1. 所有的支付参数都需要到微信支付商户平台(pay.weixin.qq.com)配置参数。

2. 微信公众号支付、微信扫码支付需要在微信公众号里面申请开通;H5支付需要在微信商户平台申请开通。

3. 仅有公众号支付和扫码支付一模式需配置支付域名,H5无需配置域名,但是使用的网站域名和申请时填写的要一致。

4. 所有使用JS API方式发起支付请求的链接地址,都必须在当前页面所配置的支付授权目录之下。

5. 当公众平台接到扫码支付请求时,会回调当前页面所配置的支付回调链接传递订单信息。

以下为源码,包含aspx页面文件和详细使用说明:下载

C#版微信公众号支付|微信H5支付|微信扫码支付问题汇总及解决方案总结的更多相关文章

  1. Java开发微信公众号(一)---初识微信公众号以及环境搭建

    ps:1.开发语言使用Java springMvc+Mybaits+spring maven实现 2.使用微信接口测试账号进行本地测试 https://mp.weixin.qq.com/debug/c ...

  2. Vue — 微信公众号内置h5支付相关

    首先,在公众号后台配置h5页面地址 开发流程 1.通过配置h5地址,获取code.再通过code,获取openid getOpenid(){ let url = 'https://open.weixi ...

  3. 微信公众号内唤起h5支付详解

    1.调用统一下单的接口URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder 2.调用统一下单必传参数: appid:需要进行支付功能的公众号的app ...

  4. 微信公众号开发之H5页面跳转到指定的小程序

    前言: 最近公司有一个这样的需要,需要从我们在现有的公众号H5页面中加一个跳转到第三方小程序的按钮.之前只知道小程序之间是可以相互跳转的,今天查阅了下微信开发文档原来现在H5网页也支持小程序之间的跳转 ...

  5. 微信公众号开发《三》微信JS-SDK之地理位置的获取,集成百度地图实现在线地图搜索

    本次讲解微信开发第三篇:获取用户地址位置信息,是非常常用的功能,特别是服务行业公众号,尤为需要该功能,本次讲解的就是如何调用微信JS-SDK接口,获取用户位置信息,并结合百度地铁,实现在线地图搜索,与 ...

  6. 微信公众号开发《三》微信JS-SDK之地理位置的获取与在线导航,集成百度地图实现在线地图搜索

    本次讲解微信开发第三篇:获取用户地址位置信息,是非常常用的功能,特别是服务行业公众号,尤为需要该功能,本次讲解的就是如何调用微信JS-SDK接口,获取用户位置信息,并结合百度地铁,实现在线地图搜索,与 ...

  7. .NET微信公众号开发-1.0初始微信公众号

    一.前言 微信公众号是开发者或商家在微信公众平台上申请的应用账号,该帐号与QQ账号互通,通过公众号,商家可在微信平台上实现和特定群体的文字.图片.语音.视频的全方位沟通.互动 .形成了一 种主流的线上 ...

  8. 微信公众号生成带参数的二维码asp源码下载

    晚上闲着没事,一个朋友联系,让帮忙写一个微信公众号利用asp生成带参数的二维码,别人扫了后如果已经关注过该公众号的,则直接进入公众号里,如果没关注则提示关注,关注后自动把该微信用户资料获取到并且保存入 ...

  9. 微信公众号开发--.Net Core实现微信消息加解密

    1:准备工作 进入微信公众号后台设置微信服务器配置参数(注意:Token和EncodingAESKey必须和微信服务器验证参数保持一致,不然验证不会通过). 2:基本配置 设置为安全模式 3.代码实现 ...

  10. 3.微信公众号开发:配置与微信公众平台服务器交互的URL接口地址

    微信开发基本原理: 1.首先有3个对象 分别是微信用户端 微信公众平台服务器 开发者服务器(也就是放自己代码的服务器) 三者间互相交互 2.微信公众平台服务器 充当中间者角色 (以被动回复消息为例) ...

随机推荐

  1. swiper 父级元素display:none 之bug

    问题描述: 同一个页面,点击底部tab按钮切换div的显示与隐藏,点击到第四个页面时 轮播图总是不动,出bug function start(){ var mySwiper = new Swiper( ...

  2. 20165214 2018-2019-2 《网络对抗技术》Exp1+ 逆向进阶 Week4

    <网络对抗技术>Exp2 PC平台逆向破解之"MAL_简单后门" Week4 一.实验内容 Task1 自己编写一个64位shellcode.参考shellcode指导 ...

  3. 个人洛谷账号地址——https://www.luogu.org/space/show?uid=181909 附上NOIP查分系统

    个人洛谷地址:       https://www.luogu.org/space/show?uid=181909 NOPI查分地址:      http://bytew.net/OIer/

  4. makefile笔记10 - makefile 函数库文件

    函数库文件也就是对 Object 文件(程序编译的中间文件)的打包文件.在 Unix 下,一般是由命令"ar"来完成打包工作. 一.函数库文件的成员 一个函数库文件由多个文件组成. ...

  5. nginx 添加代理

    1 确认安装路径 ps aux | grep nginx 2.进入配置目录 3.使用vi编辑配置文件 如果是新增,可以参考其他配置,5yy复制相应行,p粘贴,然后修改内容后:wq保存退出 4.验证配置 ...

  6. Mysql 设置远程连接

    一.问题分析 有时候使用数据库远程连接工具连接MySQL的时候总是连接不上,确认过账号密码正确,端口telnet端口又是通的. Navicat Premium报错如下: 1130 - Host '19 ...

  7. SHELL输出带颜色字体

    输出特效格式控制:\033[0m  关闭所有属性  \033[1m   设置高亮度  \03[4m   下划线  \033[5m   闪烁  \033[7m   反显  \033[8m   消隐  \ ...

  8. Galera Cluster——一种新型的高一致性MySQL集群架构

    原文链接:https://www.sohu.com/a/147032902_505779,最近被分配定位mysql的问题,学习下. 1. 何谓Galera Cluster 何谓Galera Clust ...

  9. dubbo could not get local host ip address will use 127.0.0.1 instead 异常处理

    dubbo could not get local host ip address will use 127.0.0.1 instead 查看hostname localhost:spring wls ...

  10. django(channel)到 ubuntu

    1.准备工作 删除各app/migrations/下的以数字开头的数据库同步日志文件: 假设你使用的是Pycharm,我们要生成环境包: pip freeze > requirements.tx ...