公众号支付有2种支付方式:
JS API 支付:是指用户打开图文消息或者扫描二维码,在微信内置浏览器打开网页进行的支付。商户网页前端通过使用微信提供的 JS API,调用微信支付模块。这种方式,适合
需要在商户网页进行选购下单的购买流程。
Native(原生)支付:是指商户组成符合 Native(原生)支付规则的 URL 链接,用户可通过点击该链接或者扫描对应的二维码直接进入微信支付模块(微信客户端界面),即可
进行支付。这种方式,适合无需选购直接支付的购买流程。
以上两种方式最大的区别是:是否需要经过网页调用!
交互原理:
客户是想做一个手机端的商城,所以选择JS API支付,通过网页调取微信支付。。
楼主第一次看过文档后,什么“支付签名(paySign)”啊,什么“package包”啊等等一些参数,反正似懂非懂的样子。楼主这人比较懒,不然也不 会注册3年了,也就写了这么篇不入流的文章,楼主想啊像这些个大公司开放个啥接口的肯定会有demo。楼主的运气还真不错(PS:楼主可是为福利事业贡献 了不少哦,咋就没这运气?),还真让楼主找到了,那心情肯定是比不上中500W的,嘿嘿。
打开一看(楼主心里想TX你是多缺美工!),“提交”两字这么大,点一下试试,没反应?这不科学啊!立马找原因,当看到这句:“微信支付,是基于微信客户端提供的支付服务功能” ,楼主**(已和谐)了!放网站上用微信打开,这次有反应了!微信友情的提示我:“功能未授权”。。 这次知道是啥原因了,找客户沟通,原来合同快递还没到,保证金5W没交(现在只要2W了)。。
N天之后。。。。
这下测试能成功了,该怎么跟网站结合呢?不会直接把参数改了,然后让客户点提交吧?楼主想想这用户体验也太差了吧,客户肯定不认同。这问题纠结了楼主几 天,百撕不得骑姐。有一天突然企业QQ一个讨论组的图标老闪,我就纳闷我没加过讨论组啊,打开正想喷(实际也真没喷过,能拉进讨论组都是好友,不过问题解 不出来的时候确实很烦)的时候,人物列表那个头像怎么那么熟呢?那不我客户吗?瞬间一盆凉水从头到脚彻底没脾气了!A(TX商务人员):“你们的微信支付 做好了没?”,我:“还没呢。”,A:”有什么困难吗?”,我:“有,有一些地方不明白。”,A:”我帮你安排个技术支持吧?”,楼主欣喜若狂啊,从看到 这行信息到打出“好的”发出去绝对是没有超过1秒。。
之后楼主一帆风顺了,如鱼得水了,同事妹纸也变的水灵多了。。
<——————————————————————-吐 槽结束,华丽的分割线 —————————————————————–>
以下是.NET版本的微信支付:
MD5Util类:
using System; using System.Security.Cryptography; using System.Text; namespace tenpayApp { ////// MD5Util 的摘要说明。 /// public class MD5Util { public MD5Util() { // // TODO: 在此处添加构造函数逻辑 // } /** 获取大写的MD5签名结果 */ 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; } } }
RequestHandler类:
using System; using System.Collections; using System.Text; using System.Web; using System.Xml; using System.Security.Cryptography; using System.Collections.Generic; using System.Text.RegularExpressions; namespace tenpayApp { /** '签名工具类 ============================================================================///'api说明: 'init(); '初始化函数,默认给一些参数赋值。 'setKey(key_)'设置商户密钥 'createMd5Sign(signParams);字典生成Md5签名 'genPackage(packageParams);获取package包 'createSHA1Sign(signParams);创建签名SHA1 'parseXML();输出xml 'getDebugInfo(),获取debug信息 * * ============================================================================ */ public class RequestHandler { public RequestHandler(HttpContext httpContext) { parameters = new Hashtable(); this.httpContext = httpContext; } /** 密钥 */ private string key; protected HttpContext httpContext; /** 请求的参数 */ protected Hashtable parameters; /** debug信息 */ private string debugInfo; /** 初始化函数 */ public virtual void init() { } /** 获取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 (parameter != null && parameter != "") { if (parameters.Contains(parameter)) { parameters.Remove(parameter); } parameters.Add(parameter, parameterValue); } } //获取package带参数的签名包 public string getRequestURL() { this.createSign(); 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 && "key".CompareTo(k) != 0) { sb.Append(k + "=" + TenpayUtil.UrlEncode(v, getCharset()) + "&"); } } //去掉最后一个& if(sb.Length > 0) { sb.Remove(sb.Length-1, 1); } return sb.ToString(); } //创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。 protected virtual void createSign() { 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 + "&"); } } sb.Append("key=" + this.getKey()); string sign = MD5Util.GetMD5(sb.ToString(), getCharset()).ToUpper(); this.setParameter("sign", sign); //debug信息 this.setDebugInfo(sb.ToString() + " => sign:" + sign); } //创建package签名 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 && "".CompareTo(v) != 0) { sb.Append(k + "=" + v + "&"); } } string sign = MD5Util.GetMD5(sb.ToString(), getCharset()).ToLower(); this.setParameter("sign", sign); return sign; } //创建sha1签名 public string createSHA1Sign() { 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) { if(sb.Length==0) { sb.Append(k + "=" + v); } else{ sb.Append("&" + k + "=" + v); } } } string paySign = SHA1Util.getSha1(sb.ToString()).ToString().ToLower(); //debug信息 this.setDebugInfo(sb.ToString() + " => sign:" + paySign); return paySign; } //输出XML public string parseXML() { 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(); } /** 设置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; } } }
ResponseHandler类:
using System; using System.Collections; using System.Collections.Specialized; using System.Text; using System.Web; using System.Xml; namespace tenpayApp { /** '============================================================================ 'api说明: 'getKey()/setKey(),获取/设置密钥 'getParameter()/setParameter(),获取/设置参数值 'getAllParameters(),获取所有参数 'isTenpaySign(),是否正确的签名,true:是 false:否 'isWXsign(),是否正确的签名,true:是 false:否 ' * isWXsignfeedback判断微信维权签名 ' *getDebugInfo(),获取debug信息 '============================================================================ */ public class ResponseHandler { // 密钥 private string key; // appkey private string appkey; //xmlMap private Hashtable xmlMap; // 应答的参数 protected Hashtable parameters; //debug信息 private string debugInfo; //原始内容 protected string content; private string charset = "gb2312"; //参与签名的参数列表 private static string SignField = "appid,appkey,timestamp,openid,noncestr,issubscribe"; protected HttpContext httpContext; //初始化函数 public virtual void init() { } //获取页面提交的get和post参数 public ResponseHandler(HttpContext httpContext) { parameters = new Hashtable(); xmlMap = new Hashtable(); this.httpContext = httpContext; NameValueCollection collection; //post data if (this.httpContext.Request.HttpMethod == "POST") { collection = this.httpContext.Request.Form; foreach (string k in collection) { string v = (string)collection[k]; this.setParameter(k, v); } } //query string collection = this.httpContext.Request.QueryString; foreach (string k in collection) { string v = (string)collection[k]; this.setParameter(k, v); } if (this.httpContext.Request.InputStream.Length > 0) { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(this.httpContext.Request.InputStream); XmlNode root = xmlDoc.SelectSingleNode("xml"); XmlNodeList xnl = root.ChildNodes; foreach (XmlNode xnf in xnl) { xmlMap.Add(xnf.Name, xnf.InnerText); } } } /** 获取密钥 */ public string getKey() { return key;} /** 设置密钥 */ public void setKey(string key, string appkey) { this.key = key; this.appkey = appkey; } /** 获取参数值 */ public string getParameter(string parameter) { string s = (string)parameters[parameter]; return (null == s) ? "" : s; } /** 设置参数值 */ public void setParameter(string parameter,string parameterValue) { if(parameter != null && parameter != "") { if(parameters.Contains(parameter)) { parameters.Remove(parameter); } parameters.Add(parameter,parameterValue); } } /** 是否财付通签名,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。 * @return boolean */ public virtual Boolean isTenpaySign() { 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 + "&"); } } sb.Append("key=" + this.getKey()); string sign = MD5Util.GetMD5(sb.ToString(), getCharset()).ToLower(); this.setDebugInfo(sb.ToString() + " => sign:" + sign); //debug信息 return getParameter("sign").ToLower().Equals(sign); } //判断微信签名 public virtual Boolean isWXsign() { StringBuilder sb = new StringBuilder(); Hashtable signMap = new Hashtable(); foreach (string k in xmlMap.Keys) { if (k != "SignMethod" && k != "AppSignature") { signMap.Add(k.ToLower(), xmlMap[k]); } } signMap.Add("appkey", this.appkey); ArrayList akeys = new ArrayList(signMap.Keys); akeys.Sort(); foreach (string k in akeys) { string v = (string)signMap[k]; if (sb.Length == 0) { sb.Append(k + "=" + v); } else { sb.Append("&" + k + "=" + v); } } string sign = SHA1Util.getSha1(sb.ToString()).ToString().ToLower(); this.setDebugInfo(sb.ToString() + " => SHA1 sign:" + sign); return sign.Equals(xmlMap["AppSignature"]); } //判断微信维权签名 public virtual Boolean isWXsignfeedback() { StringBuilder sb = new StringBuilder(); Hashtable signMap = new Hashtable(); foreach (string k in xmlMap.Keys) { if (SignField.IndexOf(k.ToLower()) != -1) { signMap.Add(k.ToLower(), xmlMap[k]); } } signMap.Add("appkey", this.appkey); ArrayList akeys = new ArrayList(signMap.Keys); akeys.Sort(); foreach (string k in akeys) { string v = (string)signMap[k]; if ( sb.Length == 0 ) { sb.Append(k + "=" + v); } else { sb.Append("&" + k + "=" + v); } } string sign = SHA1Util.getSha1(sb.ToString()).ToString().ToLower(); this.setDebugInfo(sb.ToString() + " => SHA1 sign:" + sign); return sign.Equals( xmlMap["AppSignature"] ); } /** 获取debug信息 */ public string getDebugInfo() { return debugInfo;} /** 设置debug信息 */ protected void setDebugInfo(String debugInfo) { this.debugInfo = debugInfo;} protected virtual string getCharset() { return this.httpContext.Request.ContentEncoding.BodyName; } } }
SHA1Util:
using System; using System.Collections.Generic; using System.Text; using System.Security.Cryptography; namespace tenpayApp { class SHA1Util { public static String getSha1(String str) { //建立SHA1对象 SHA1 sha = new SHA1CryptoServiceProvider(); //将mystr转换成byte[] ASCIIEncoding enc = new ASCIIEncoding(); byte[] dataToHash = enc.GetBytes(str); //Hash运算 byte[] dataHashed = sha.ComputeHash(dataToHash); //将运算结果转换成string string hash = BitConverter.ToString(dataHashed).Replace("-", ""); return hash; } } }
TenpayUtil:
using System; using System.Text; using System.Web; namespace tenpayApp { ////// TenpayUtil 的摘要说明。 /// 配置文件 /// public class TenpayUtil { public static string tenpay = "1"; public static string partner = ""; //商户号 public static string key = ""; //密钥 public static string appid = "";//appid public static string appkey = "";//paysignkey(非appkey) public static string tenpay_notify = "http://localhost/payNotifyUrl.aspx"; //支付完成后的回调处理页面,*替换成notify_url.asp所在路径 public TenpayUtil() { } public static string getNoncestr() { Random random = new Random(); return MD5Util.GetMD5(random.Next(1000).ToString(), "GBK"); } public static string getTimestamp() { TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); return Convert.ToInt64(ts.TotalSeconds).ToString(); } /** 对字符串进行URL编码 */ public static string UrlEncode(string instr, string charset) { //return instr; if(instr == null || instr.Trim() == "") return ""; else { string res; try { res = HttpUtility.UrlEncode(instr,Encoding.GetEncoding(charset)); } catch (Exception ex) { res = HttpUtility.UrlEncode(instr,Encoding.GetEncoding("GB2312")); } return res; } } /** 对字符串进行URL解码 */ public static string UrlDecode(string instr, string charset) { if(instr == null || instr.Trim() == "") return ""; else { string res; try { res = HttpUtility.UrlDecode(instr,Encoding.GetEncoding(charset)); } catch (Exception ex) { res = HttpUtility.UrlDecode(instr,Encoding.GetEncoding("GB2312")); } return res; } } /** 取时间戳生成随即数,替换交易单号中的后10位流水号 */ public static UInt32 UnixStamp() { TimeSpan ts = DateTime.Now - TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); return Convert.ToUInt32(ts.TotalSeconds); } /** 取随机数 */ public static string BuildRandomStr(int length) { Random rand = new Random(); int num = rand.Next(); string str = num.ToString(); if(str.Length > length) { str = str.Substring(0,length); } else if(str.Length < length) { int n = length - str.Length; while(n > 0) { str.Insert(0, "0"); n--; } } return str; } } }
页面代码:
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Collections; using tenpayApp; //================================= //JSAPI支付 //================================= public partial class _Default : System.Web.UI.Page { public String appId = TenpayUtil.appid; public String timeStamp = ""; public String nonceStr = ""; public String packageValue = ""; public String paySign = ""; protected void Page_Load(object sender, EventArgs e) { string sp_billno = Request["order_no"]; //当前时间 yyyyMMdd string date = DateTime.Now.ToString("yyyyMMdd"); if (null == sp_billno) { //生成订单10位序列号,此处用时间和随机数生成,商户根据自己调整,保证唯一 sp_billno = DateTime.Now.ToString("HHmmss") + TenpayUtil.BuildRandomStr(4); } else { sp_billno = Request["order_no"].ToString(); } sp_billno = TenpayUtil.partner + sp_billno; //创建支付应答对象 RequestHandler packageReqHandler = new RequestHandler(Context); //初始化 packageReqHandler.init(); //设置package订单参数 packageReqHandler.setParameter("partner", TenpayUtil.partner); //商户号 packageReqHandler.setParameter("fee_type", "1"); //币种,1人民币 packageReqHandler.setParameter("input_charset", "GBK"); packageReqHandler.setParameter("out_trade_no", sp_billno); //商家订单号 packageReqHandler.setParameter("total_fee", "1"); //商品金额,以分为单位(money * 100).ToString() packageReqHandler.setParameter("notify_url", TenpayUtil.tenpay_notify); //接收财付通通知的URL packageReqHandler.setParameter("body", "JSAPIdemo"); //商品描述 packageReqHandler.setParameter("spbill_create_ip", Page.Request.UserHostAddress); //用户的公网ip,不是商户服务器IP //获取package包 packageValue = packageReqHandler.getRequestURL(); //调起微信支付签名 timeStamp = TenpayUtil.getTimestamp(); nonceStr = TenpayUtil.getNoncestr(); //设置支付参数 RequestHandler paySignReqHandler = new RequestHandler(Context); paySignReqHandler.setParameter("appid", appId); paySignReqHandler.setParameter("appkey", TenpayUtil.appkey); paySignReqHandler.setParameter("noncestr", nonceStr); paySignReqHandler.setParameter("timestamp", timeStamp); paySignReqHandler.setParameter("package", packageValue); paySign = paySignReqHandler.createSHA1Sign(); //获取debug信息,建议把请求和debug信息写入日志,方便定位问题 //string pakcageDebuginfo = packageReqHandler.getDebugInfo(); //Response.Write("
pakcageDebuginfo:" + pakcageDebuginfo + "
"); //string paySignDebuginfo = paySignReqHandler.getDebugInfo(); //Response.Write("
paySignDebuginfo:" + paySignDebuginfo + "
"); } }