微信公众号支付全解析

简单介绍了微信公众号支付的申请、接入、使用、确认支付结果等相关流程

0 系列文章

系列一 微信App支付全解析
系列二 支付宝App支付全解析
系列三 微信公众号支付全解析
系列四 微信扫码支付全解析
系列五 支付宝即时到账支付全解析
系列六 微信退款全解析
系列七 支付宝退款全解析
系列八 支付宝开放平台支付更新升级全解析

1 申请

申请步骤直接参考官方文档

主要2个大块:

  1. 申请开通公众平台,创建应用
  2. 申请支付开通商户平台

全部申请通过后,获取支付必须的参数如下:

1.1 AppID和AppSecret

公众平台创建的应用唯一标识。
登录微信公众平台,进入应用详情可查看AppID和AppSecret。

Paste_Image.png

1.2 mch_id

微信支付申请完成之后,微信商户平台会给你的邮箱发通知邮件,里面包含开通支付的商户信息

Paste_Image.png

1.3 API秘钥

即商户支付秘钥,主要负责处理通信相关参数加密。登陆微信商户平台(账号密码在微信商户平台发来的邮件里)
点击左侧的「账户设置 - API 安全」(第一次登陆会让你安装操作证书,请先安装操作证书)。点击设置密钥,设置自己的密钥。

Paste_Image.png

1.4 商户证书

用于退款等一些需要证书验证的接口使用。在微信商户平台点击「账户中心 - API 安全」,点击「下载证书」

Paste_Image.png

证书下载后,打开压缩包会看到「apiclient_cert.pem」和「apiclient_key.pem」和rootca.pem证书。

2 接入流程

参考接入文档

主要几个步骤:

  1. 统一下单(放在服务端,需要加密参数)
  2. 生成支付参数(放在服务端,需要生成签名)
  3. 调用客户端SDK发起支付
  4. 服务端异步接收支付结果

2.1 统一下单

$appid = "";  //你的appid
$mch_id = "";  //商户id
$wx_api_key = "";    //商户api秘钥
$out_trade_no = "";  //自己业务系统生成的交易no,可以唯一标识
$client_ip = "";  //客户端ip
$notify_url = "";    //接收支付结果通知url
$openid = "";    //微信授权获得的openid

$UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";  //统一下单地址

$data = array();
$data['appid'] = $appid; 
$data['mch_id'] =$mch_id;
$data['nonce_str'] = randomStr(20);  //随机20位字符串
$data['body'] = "微信公众号支付测试";
$data['detail'] = "微信公众号支付测试detail";
$data['out_trade_no'] = $out_trade_no;    
$data['total_fee'] = 1;  //注意 单位是分
$data['spbill_create_ip'] = $client_ip;
$data['openid'] = $openid;
$data['notify_url'] = $notify_url;
$data['trade_type'] = "JSAPI";  //交易类型
$data['sign'] =sign($data, $wx_api_key);    //签名

//转为xml格式
$xml_str = arrayToXmlStr($data); 

//发送请求 使用封装好的curl_post
$result = curl_post($UNIFIED_ORDER_URL, $xml_str);

//解析得到的值
$get_data = simplexml_load_string($raw_data, 'SimpleXMLElement', LIBXML_NOCDATA);
$get_para = array();
$get_sign = "";
foreach ($get_data->children() as $child) 
{    
  if($child->getName() == 'sign') {        
    $get_sign = strval($child);    
  } else {        
    $get_para[strval($child->getName())] = strval($child);    
   }
}

if($get_para['return_code'] !== "SUCCESS") {
    //return code fail
}

//验证签名
if(!verifySign($get_sign, $get_para, $wx_api_key)) {
    //验证签名非法
}

//可以自行处理解析获得的参数
//todo...

一些函数:

/**
 * array转成xml str
 * @param $arr
 */
public static function arrayToXmlStr($arr) {    
  $xml_data = new \SimpleXMLElement("<xml></xml>");    
  Func::arrayToXml($arr, $xml_data);    
  return $xml_data->asXML();
}

/**
 * 生成指定长度的随机字符串(包含大写英文字母, 小写英文字母, 数字)
 * @param $length int 需要生成的字符串的长度
 * @return string 包含 大小写英文字母 和 数字 的随机字符串
 */
public static function randomStr($length){    
  //生成一个包含 大写英文字母, 小写英文字母, 数字 的数组    
  $arr = array_merge(range(0, 9), range('a', 'z'), range('A', 'Z'));    
  $str = '';    
  $arr_len = count($arr);    
  for ($i = 0; $i < $length; $i++)    {        
    $rand = mt_rand(0, $arr_len-1);        
    $str.=$arr[$rand];    
  }    
  return $str;
}

/**
 * 微信签名
 * @param $para mixed 带签名参数数组
 * @param $wx_key string wxkey
 */
public static function sign($para, $wx_key) {    
  $unsign_str = Func::createLinkString(Func::argSort($para)) . "&key=" . $wx_key;    
  $sign = strtoupper(md5($unsign_str));    
  return $sign;
}

/**
 * 微信签名验证
 * @param $sign
 * @param $para
 * @param $wx_key
 * @return false-验证失败 true-验证成功
 */
public static function verifySign($sign, $para, $wx_key) {    
  $unsign_str = Func::createLinkString(Func::argSort($para)) . "&key=" . $wx_key;    
  $sign_str = strtoupper(md5($unsign_str));    
  if($sign === $sign_str) {        
    return true;    
  }    
  return false;
}

2.2 生成支付参数

客户端需要的支付参数是带签名的,所以最好支付参数也在服务端生成后,jsondecode后传入客户端即可直接调用

//生成支付参数
$data = array();
$data['appId'] = $appid;
$data['timeStamp'] = time();
$data['nonceStr'] = randomStr(20);
$data['package'] = "prepay_id=$prepay_id";
$data['signType'] = "MD5";
$data['timestamp'] = time();
$data['sign'] =sign($data, $wx_api_key);

$pay_param = json_encode($data);

3. 调用支付

注:微信公众号发起支付的路径必须是在公众号支付-开发配置-公众号支付-支付授权目录中设置中填入的支付路径,而且不能是多级目录

Paste_Image.png

客户端js调用,封装了一个简单的js,可以直接调用:

//调用微信JS api 支付function 
jsApiCall(pay_param, callback){ 
  var pay_param_arr = eval("(" + pay_param + ")"); 
  WeixinJSBridge.invoke( 
    'getBrandWCPayRequest',{ 
      'appId':pay_param_arr.appId,   
      'timeStamp':pay_param_arr.timeStamp+"", 
      'nonceStr':pay_param_arr.nonceStr, 
      'package':pay_param_arr.package, 
      'signType':pay_param_arr.signType, 
      'paySign':pay_param_arr.paySign 
    }, 
    function(res){ 
      if(res.err_msg == "get_brand_wcpay_request:ok") { 
        //成功 
        callback("ok"); 
      } else if(res.err_msg == "get_brand_wcpay_request:cancel") {   
        //取消 
        callback("cancel"); 
      } else { 
        //失败 
        callback("fail", res.err_desc); 
      } 
    } );
  }

/** * 微信公众号支付
 * @param pay_param 服务端生成的pay_param
 */
function callWxJsPay(pay_param, callback){ 
  if (typeof WeixinJSBridge == "undefined"){ 
    if( document.addEventListener ){ 
      document.addEventListener('WeixinJSBridgeReady', jsApiCall, false); 
    }else if (document.attachEvent){ 
      document.attachEvent('WeixinJSBridgeReady', jsApiCall); 
      document.attachEvent('onWeixinJSBridgeReady', jsApiCall); 
    } 
  }else{ 
    jsApiCall(pay_param, callback); 
  }
}

调用示例:

<html>
<head> 
  <meta http-equiv="content-type" content="text/html;charset=utf-8"/> 
  <meta name="viewport" content="width=device-width, initial-scale=1"/> 
  <title>微信支付样例-支付</title> 
  <script type="text/javascript" src="../pay_h5_sdk/js/wx_jspay.js"></script> 
  <script type="text/javascript"> 
    function docallpay() { 
      var pay_param = document.getElementById('pay_param').value; 
      callWxJsPay(pay_param, callback); 
    } 
    function callback(state, msg) { 
      alert(state + " " + msg); 
    } 
  </script>
</head>

<body>
<br/>
<div align="center"> 
  输入支付参数:<br /><br /> 
  <textarea id="pay_param"></textarea> <br/><br />   
  <button style="width:210px; height:50px; border-radius: 15px;background-color:#FE6714; border:0px #FE6714 solid; cursor: pointer; color:white; font-size:16px;" type="button" onclick="docallpay()" >立即支付</button>
</div>
</body>
</html>

4 异步结果通知

注:尤其要注意通知结果验证成功后要能正确处理重复通知,放置多次发货造成资金损失

$raw_data = $GLOBALS["HTTP_RAW_POST_DATA"];

$get_data = simplexml_load_string($raw_data, 'SimpleXMLElement', LIBXML_NOCDATA);
$get_para = array();
$get_sign = "";
foreach ($get_data->children() as $child) 
{    
  if($child->getName() == 'sign') {        
    $get_sign = strval($child);    
  } else {        
    $get_para[strval($child->getName())] = strval($child);    
   }
}

if($get_para['return_code'] !== "SUCCESS") {
    //return code fail
    die("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
}

//验证签名
if(!verifySign($get_sign, $get_para, $wx_api_key)) {
    //验证签名非法
    //todo
    die("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
}

//在这其实通知已经接受成功 可以返回成功告诉微信不用再次通知了
echo("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");

//业务状态码判断
if ($get_para['result_code'] !== 'SUCCESS') {       //状态码错误
  //支付错误 更改订单状态 记录log等 
  //...
}

//支付成功 更改订单状态 记录log等 
//todo

5 其他

  1. 客户端收到同步支付结果后建议一段时间内轮询检查服务端,获取服务端的结果,支付最终状态以服务端为准

结尾

更多文章关注我的公众号


我的公众号
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容

  • 微信公众号支付全解析://www.greatytc.com/p/f1df7ed364c6 公众号支付笔记:...
    yuzhan550阅读 383评论 0 2
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,008评论 25 707
  • 选择恐惧症,密集恐惧症,社交恐惧症。 此时还有一种交作业恐惧症。 看图作文。随之而来的是考试恐惧症。曾经夜夜梦到考...
    槿篁阅读 243评论 0 0
  • 企业名称:深圳市慧友冠源科技有限公司。 组别:272期乐观四组 【日精进打卡82天】 【知~学习】 巜六项精进》读...
    江阳水阅读 226评论 0 0
  • 轻分享是AA组织的一场线上社群分享活动,旨在让创业者之间相互传递分享创业经验。采访100位独立创业者,将他们最拿手...
    洞天水月阅读 404评论 0 0