微信公众号内支付文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
下面来说说开发过程中需要注意的地方:
1.微信支付接口老版和旧版不兼容,一旦在微信公众号平台微信支付升级了支付接口,那么用老版支付接口开发的程序都会不好用。。所以升级一定要谨慎。。
(我们公司的公众号h5微信支付突然有一天不好用了,于是开始疯狂的找bug,结果找了半天发现是不知道谁把支付接口给升级了,,没办法只能用新接口重新开发,真的是坑的一逼)
2.开发工程中一定要仔细阅读文档的各个字眼,, 一点错误可能就导致无法支付。。
下面举几个栗子:
参数名字一定要和文档中的完全一致
----------------------------------------------------------------------------------------------- 分割线
----------------------------------------------------------------------------------------------- 分割线
这里一定要格外注意,一个步骤出错都会导致报错签名错误。
----------------------------------------------------------------------------------------------- 分割线
注意是10位的。。位数错误也会导致支付报错。
注意新版的只支持MD5加密 ,老版的好像还可以用sha1加密方式,新版的不行了
这个在统一下单接口中,作用是微信支付成功后会带上一些参数回调该地址,来通知支付结果等信息,回调地址一定要填写正确。
----------------------------------------------------------------------------------------------- 分割线
下面附上部分代码:
/// <summary>
/// 签名工具类
/// </summary>
public class RequestHandler
{
public RequestHandler(HttpContextBase httpContext)
{
parameters = new Hashtable();
this.httpContext = httpContext;
}
/** 密钥 */
private string key;
protected HttpContextBase httpContext;
/** 请求的参数 */
protected Hashtable parameters;
/** debug信息 */
private string debugInfo;
/** 初始化函数 */
public virtual void init()
{
setKey("商户平台设置的密钥key");
}
/** 获取debug信息 */
public String getDebugInfo()
{
return debugInfo;
}
/** 获取密钥 */
public String getKey()
{
return key;
}
/** 设置密钥 */
public void setKey(string key)
{
this.key = key;
}
/** 设置参数值 */
public void setParameter(string parameter, string parameterValue)
{
if (!string.IsNullOrEmpty(parameter))
{
if (parameters.Contains(parameter))
{
parameters.Remove(parameter);
}
parameters.Add(parameter, parameterValue);
}
}
//创建md5加密Sign,规则是:ASCII码从小到大排序(字典序),遇到空值的参数不参加签名。
public virtual string CreateMd5Sign()
{
StringBuilder sb = new StringBuilder();
ArrayList akeys = new ArrayList(parameters.Keys);
akeys.Sort(); //排序
foreach (string k in akeys)
{
string v = (string)parameters[k];
if (null != v && "".CompareTo(v) != 0 && "sign".CompareTo(k) != 0 && "key".CompareTo(k) != 0)
{
sb.Append(k + "=" + v + "&");
}
}
//sign=除sign参数外的所有不为空值的发送参数+key ,在MD5加密(key为商户平台设置的密钥)
sb.Append("key=" + this.getKey());
string sign = MD5Util.GetMD5(sb.ToString(), getCharset()).ToUpper();
//debug信息 可记录到日志方便查看
this.setDebugInfo(sb.ToString() + " => sign:" + sign);
return sign;
}
//输出XML
public string parseXML()
{
StringBuilder sb = new StringBuilder();
sb.Append("<xml>");
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 + "><![CDATA[" + v + "]]></" + k + ">");
}
}
sb.Append("</xml>");
return sb.ToString();
}
/// <summary>
/// 把XML数据转换为SortedDictionary<string, string>集合
/// </summary>
/// <param name="strxml"></param>
/// <returns></returns>
public SortedDictionary<string, string> GetInfoFromXml(string xmlstring)
{
SortedDictionary<string, string> sParams = new SortedDictionary<string, string>();
try
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlstring);
XmlElement root = doc.DocumentElement;
int len = root.ChildNodes.Count;
for (int i = 0; i < len; i++)
{
string name = root.ChildNodes[i].Name;
if (!sParams.ContainsKey(name))
{
sParams.Add(name.Trim(), root.ChildNodes[i].InnerText.Trim());
}
}
}
catch { }
return sParams;
}
/** 设置debug信息 */
public void setDebugInfo(String debugInfo)
{
this.debugInfo = debugInfo;
}
public Hashtable getAllParameters()
{
return this.parameters;
}
protected virtual string getCharset()
{
return this.httpContext.Request.ContentEncoding.BodyName;
}
}
private String packageValue = string.Empty;
/// <summary>
/// 获取支付接口需要的Package
/// </summary>
/// <returns></returns>
private string GetPackageByRequest(string orderId, float totalPrice, string remarks)
{
string open_id = ""; //统一下单接口需要的openid,获取方式可看文档
//创建支付应答对象
RequestHandler packageReqHandler = new RequestHandler(HttpContext);
//初始化
packageReqHandler.init();
//设置统一下单参数
packageReqHandler.setParameter("appid", appid);
packageReqHandler.setParameter("trade_type", "JSAPI");
packageReqHandler.setParameter("body", remarks); //商品描述
packageReqHandler.setParameter("mch_id","123456"); //商户号
packageReqHandler.setParameter("nonce_str", TenpayUtil.Noncestr);
packageReqHandler.setParameter("out_trade_no", orderId); //商家订单号
packageReqHandler.setParameter("total_fee", (1*100).ToString()); //商品金额,以分为单位(money * 100).ToString()
packageReqHandler.setParameter("fee_type", "CNY"); //币种,CNY人民币
packageReqHandler.setParameter("notify_url", "http://wwww.xxx.com/xxxx"); //接收微信支付结果通知的URL
packageReqHandler.setParameter("spbill_create_ip", Request.UserHostAddress); //用户的公网ip,不是商户服务器IP
packageReqHandler.setParameter("openid", open_id);
packageReqHandler.setParameter("sign_type", "MD5");
packageReqHandler.setParameter("sign", packageReqHandler.CreateMd5Sign()); //签名
packageValue = getPrepayId(packageReqHandler);
if (!string.IsNullOrEmpty(packageValue))
{
packageValue = "prepay_id=" + packageValue;
}
return packageValue;
}
/// <summary>
/// 获取prepay_id
/// </summary>
public string getPrepayId(RequestHandler packageReqHandler)
{
string UnifiedPayUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //统一下单接口链接
string prepay_id = "";
string post_data = packageReqHandler.parseXML(); //把发送参数转换为xml格式
string request_data = CommHelper.PostUrl(UnifiedPayUrl, post_data); //发送post请求
SortedDictionary<string, string> requestXML = packageReqHandler.GetInfoFromXml(request_data);
foreach (KeyValuePair<string, string> k in requestXML)
{
if (k.Key == "prepay_id")
{
prepay_id = k.Value;
break;
}
}
return prepay_id;
}
/// <summary>
/// 设置支付接口需要的参数发送到前台使用(我这里是前台ajax请求获取)
/// </summary>
public JsonResult WeixinPay()
{
dynamic model = new ExpandoObject();
string sign = Request["sign"]; //加密验证,保证程序安全性
if (!sign.Equals("自己设置的加密字符串"))
{
model.Error = "验证失败";
return Json(model, JsonRequestBehavior.AllowGet);
}
model.appId = "appid";
model.timeStamp = GetTimestamp();
model.nonceStr = GetNoncestr();
model.packageValue = GetPackageByRequest("商户订单号", "商品金额", "商品描述");
model.signType = "MD5";
model.paySign = GetSign(model.nonceStr, model.timeStamp);
return Json(model, JsonRequestBehavior.AllowGet);
}
/// <summary>
/// 支付Sign
/// </summary>
/// <returns></returns>
private string GetSign(string nonceStr, string timeStamp)
{
//设置支付接口参数
RequestHandler paySignReqHandler = new RequestHandler(HttpContext);
paySignReqHandler.setParameter("appId", "appid");
paySignReqHandler.setParameter("nonceStr", nonceStr);
paySignReqHandler.setParameter("timeStamp", timeStamp);
paySignReqHandler.setParameter("package", packageValue);
paySignReqHandler.setParameter("signType", "MD5");
paySignReqHandler.init();
string paySign = paySignReqHandler.CreateMd5Sign();
//获取debug信息可纪录到日志方便查看
string paySignDebuginfo = paySignReqHandler.getDebugInfo();
return paySign;
}
/// <summary>
/// 获取10位的时间戳
/// </summary>
/// <returns></returns>
private static string GetTimestamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
string timeStamp = Convert.ToInt64(ts.TotalSeconds).ToString();
return timeStamp;
}
/// <summary>
/// 获取随机字符串
/// </summary>
/// <returns></returns>
private static string GetNoncestr()
{
Random random = new Random();
string nonceStr = MD5Util.GetMD5(random.Next(1000).ToString(), "GBK");
return nonceStr;
}
public static string GetMD5(string encypStr, string charset)
{
string retStr;
MD5CryptoServiceProvider m5 = new MD5CryptoServiceProvider();
//创建md5对象
byte[] inputBye;
byte[] outputBye;
//使用GB2312编码方式把字符串转化为字节数组.
try
{
inputBye = Encoding.GetEncoding(charset).GetBytes(encypStr);
}
catch (Exception ex)
{
inputBye = Encoding.GetEncoding("GB2312").GetBytes(encypStr);
}
outputBye = m5.ComputeHash(inputBye);
retStr = System.BitConverter.ToString(outputBye);
retStr = retStr.Replace("-", "").ToUpper();
return retStr;
}
注意不要忘了微信支付结果通知回调的接口,文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
开发工程中需要注意安全问题,比如可在传输、获取参数时采用验证加密sign的方式,已保证程序的安全!