资讯专栏INFORMATION COLUMN

支付开发填坑记之微信支付

zhunjiee / 3034人阅读

摘要:前者集成在中,后者主要是为微信用户提供了另一种支付方式需要在微信的内置浏览器中打开页面,再调起微信支付。步骤商户后台收到用户支付单,调用微信支付统一下单接口。拿到所有参数后,就可以在页面中发起微信支付的请求了。

微信支付,支持的支付方式比较多:有扫码支付,刷卡支付,APP支付和公众号支付。其中,APP和网站上最常用的就是APP支付和公众号支付。前者集成在APP中,后者主要是为微信用户提供了另一种支付方式(需要在微信的内置浏览器中打开页面,再调起微信支付)。

同样的,微信的APP支付和支付宝的APP支付也是很简单:

APP支付

商户系统和微信支付系统主要交互说明:

步骤1:用户在商户APP中选择商品,提交订单,选择微信支付。

步骤2:商户后台收到用户支付单,调用微信支付统一下单接口。参见【统一下单API】。

步骤3:统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。参与签名的字段名为appIdpartnerIdprepayIdnonceStrtimeStamppackage注意:package的值格式为Sign=WXPay

步骤4:商户APP调起微信支付。

步骤5:商户后台接收支付通知。

步骤6:商户后台查询支付结果。

这里主要的还是后台干活(获取 prepay_id,生成随机字符串 nonceStr 和时间戳 timeStampappIdpartnerId 均能在后台管理中查看。)

后台的步骤也很简洁,就是上述中的步骤1,2。

获取 prepayId

设置获取 prepayId 所需参数。

此处需要调用微信的统一下单接口。这个过程,官方文档已经写得十分之详细了,包括调用的接口API地址,需要传递的参数(必要和非必要的参数),还有返回结果也写得很清楚。

以下是我在实际项目开发中传入的参数。

签名。

签名都差不多,都是先将所有的带签名的参数进行字典排序。

ksort($data);

然后将参数以 {key}={value} 的组合形式,用 & 连接。

$a = array();
foreach ($data as $k => $v) {
    if ((string) $v === "") {
        continue;
    }
    $a[] = "{$k}={$v}";
}

$a = implode("&", $a);

最后拼上 &key={Your apiKey} ,然后对整串字符串进行MD5加密即可。

$sign = strtoupper(md5($a));

将拼好的数据,以 XML 的格式发送给微信,请求 prepayId

没错,就是要转成 XML 格式再发送。

但是,这个XML格式很简单,只需要进行简单的拼接即可:

public function arrayToXml(array $data)
{
    $xml = "";
    foreach ($data as $k => $v) {
        if (is_numeric($v)) {
            $xml .= "<{$k}>{$v}";
        } else {
            $xml .= "<{$k}>";
        }
    }
    $xml .= "";
    return $xml;
}

参数值用XML转义即可,CDATA标签用于说明数据不被XML解析器解析。。

然后请求统一下单API即可(url = https://api.mch.weixin.qq.com/pay/unifiedorder

$ch = curl_init();
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
$response = curl_exec($ch);
if (!$response) {
    throw new Exception("CURL Error: " . curl_errno($ch));
}
curl_close($ch);

请求回来的数据也为XML格式,只需要简单做下处理,转换成array即可:

public function xmlToArray($xml){
    return json_decode(json_encode(simplexml_load_string($xml, "SimpleXMLElement", LIBXML_NOCDATA)), true);
}

如果返回值中的 return_coderesult_code 都为 SUCCESS 的时候会返回 交易类型 trade_type 和 预支付交易会话标识 prepayId 。到这里,我们就可以获取到 prepayId

将获取的 prepayId 与其他参数拼接,返回给APP即可。

$params = array();
//  商户号
$params["appid"] = $this->config->appId;
//  时间戳
$params["timestamp"] = "" . time();
//  随机字符串
$params["noncestr"] = md5(uniqid(mt_rand(), true));
//  固定为 "Sign=WXPay"
$params["package"] = "Sign=WXPay";
//  步骤3获取的预支付交易会话标识
$params["prepayid"] = $prepayId;
//  合作伙伴id
$params["partnerid"] = $this->config->partnerId;
//  步骤2生成的签名。
$params["sign"] = $this->sign($params);

微信APP支付,后台需要干的活到这里就暂时结束了(因为还有支付成功后的异步通知商户后面再讲)


jsapi支付

下面就是web版的微信支付(公司项目是在微信浏览器内,选择微信支付后,在微信中调起的微信支付)

web版微信支付的步骤和APP的大同小异,也是现获取 prepayId ,再在页面中,调用jsapi进行支付。

但是,此处有2个坑

坑1: 支付时出现 appid and openid not match 的报错

原因非常的简单,就是支付时所获取的 openid 在并不属于支付的商户。
这个 openid 为微信用户在商户对应appid下的唯一标识。也就是说,必须根据支付的商户的 appid 去获取用户的 openid

因为业务逻辑需要,项目中用于微信登录用的公众号A用于支付的公众号B(其实还和开放平台用于APP支付的 appId 也是不一样的)是不一样的,虽然所获取unionid是一致,但是 openid 是不!一!样!的!所以,在获取 openid 时,需要使用当前支付时所用到的 appid 去请求用户的 openid ,同时,请求 openid 后的回调也必须是 支付商户 后台所设置好的回调地址,要不然就会报 redirect_uri 参数错误 的错误。

坑2: 参数名大小写不一致。

↑ APP支付的参数

↑ web支付的参数

仔细看看划横线的地方。没错,app中的参数的key全是小写,web支付中的key则为驼峰命名方式。而且,签名方式 signType 是必填的, 签名的字段也变成了 paySign,其中 package 的值也是不一样,APP支付是固定的值,web支付则为 prepayId,这也要注意。当然,官方文档也是很详细的说明,但是需要细心观察(所以说嘛,还是直接拷贝必填项最保险了2333)。

拿到所有参数后,就可以在页面中发起微信支付的请求了。

代码可以直接使用官方提供的js代码

function onBridgeReady(){
   WeixinJSBridge.invoke(
       "getBrandWCPayRequest", YOUR_PARAMS,
       function(res){     
           if(res.err_msg == "get_brand_wcpay_request:ok" ) {
               // success_callback
           }     // 使用以上方式判断前端返回,微信团队郑重提示: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();
}

其中 YOUR_PARAMS 是参数转换成json格式直接渲染至页面即可。

如果参数没错的话,那么就可以顺利的调起支付窗口了。

坑3: APP支付和jsapi支付不是同一个号

APP支付是在开放平台中申请下来的,appId和apiKey都是不一样的。而jsapi支付实质就是公众号支付,是在公众平台中申请得到的。所以,在这里,需要注意一下。


重要的来了,能体现后台的重要性的地方终于来了 ---

支付结果的异步通知

官方文档写得也很详细了(不得不说,微信的开发文档真的很清晰。很容易找到。就是没有详细的步骤区分)。

首先需要申明的是:异步通知的URL是必须能在公网访问的,而且,必须不能携带参数

也就是说,http://domain.com/payment/wxp... 是没问题的,但是 http://domain.com/payment/not... 这样的URL是不行的。如果要想达到这种效果,要不服务器(Nginx , Apache)进行rewrite,要不在notify.php 中,手动修改 $_GET 中的参数。

返回的数据,都是一致的:

这时候,商户后台拿到这些异步通知的数据进行简单的校验即可,然后修改商户中相应订单的支付状态。

校验返回码是否成功

$d = $this->xmlToArray(file_get_contents("php://input"));
if (empty($d)) {
    throw new Exception(__METHOD__);
}
if ($d["return_code"] != "SUCCESS") {
    throw new Exception($d["return_msg"]);
}
if ($d["result_code"] != "SUCCESS") {
    throw new Exception("[{$d["err_code"]}]{$d["err_code_des"]}");
}

对返回数据进行校验

和请求 prepayId 时处理数据的方式差不多,先取出签名 sign,然后除去签名后,进行字典排序,以 {key}={value} 的方式进行组合,并在最后加上 &key={apiKey}得到待校验字符串,最后,将待校验字符串进行MD5加密,和签名进行比较,若一致则校验成功,并且支付成功,然后后台做相应操作。

if (!$this->verify($d)) {
    throw new Exception("Invalid signature");
}

//  验证函数
if (empty($d["sign"])) {
    return false;
}
$sign = $d["sign"];
unset($d["sign"]);
return $sign == $this->sign($d);

微信退款

有支付肯定就会有退款。微信的退款操作也是很简单,而且退款速度非常快,测试时基本都是秒退。

但是退款是有注意事项的:

交易时间超过一年的订单无法提交退款;

微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。总退款金额不能超过用户实际支付金额。一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号

退款请求需要证书

【证书获取方式:】

微信支付接口中,涉及资金回滚的接口会使用到商户证书,包括退款、撤销接口。商家在申请微信支付成功后,收到的相应邮件后,可以按照指引下载API证书,也可以按照以下路径下载:微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->证书下载。

微信退款程序流程:

设置退款时得参数。

请求的参数有:

appid : 公众账号ID

mch_id : 商户号

nonce_str : 随机字符串

sign: 签名

transaction_id / out_trade_no :微信订单号 / 商户订单号 二者中传其中一个即可。

out_refund_no: 商户退款单号(由商户自行生成的唯一标识)

total_fee:订单金额(单位为分)

refund_fee:退款金额(单位为分),退款金额不能大于订单金额。

op_user_id:操作员帐号, 默认为商户号

签名还是老规矩(默认是MD5方式),先将所有参数进行字典排序,然后以$key=$value的形式用&字符拼接成字符串,最后将拼上 &key=YOUR_APIKEY 的待签名字符串进行MD5加密即可。

将参数列表转换成XML格式。

发送退款请求。

退款请求需要携带微信上下载的证书,请保证证书存放路径外网不能直接访问。

解析请求结果。

当返回的结果中, return_coderesult_code 均为 SUCCESS ,即为退款申请成功。更多返回结果,请移步至 官网

结尾

微信支付的官方开发文档其实算是很详细了,传递的参数,返回结果,如果判断是否成功,都写的很好。只是,开发中的逻辑过程需要自己慢慢摸索,理清思路后,开发起来其实都是很迅速的。

但是,开发微信支付时,需要留个心,需要将所有涉及到的微信后台提供的数据小心保存(比如AppSecret,一当忘记只能重置。)

祝各位开发过程顺利进行。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/22906.html

相关文章

  • 支付开发填坑记之支付

    摘要:原文地址支付支付步骤为获取支付宝的配置信息。将得到的数据请求支付宝客户端进行支付。端将拼接好的字符串拿去请求支付宝客户端即可调起支付宝进行支付。向支付宝申请新订单,获取支付。成功请求回来后,就可以向支付宝发出一次支付请求。 支付宝在所有支付方式中最好开发的了,因为文档比较清晰,而且开发起来也比较简单。因此,支付宝的坑是相对较少的。原文地址 APP支付 APP支付步骤为: 获取支付宝的...

    chanjarster 评论0 收藏0
  • 微信支付PHP SDK之微信公众号支付代码详解

    摘要:微信支付接口下载官方文档参考配置公众号信息我们先进行测试,所以先把测试授权目录和测试白名单添加上。在申请微信支付后发来的邮件中可以找到,则根据邮件提示拜访官方我们首先需要的是支付。 微信php支付接口demo下载https://pay.weixin.qq.com/wik... 官方文档参考https://pay.weixin.qq.com/wik... 1. 配置公众号信息showIm...

    jimhs 评论0 收藏0
  • 支付宝小程序爬坑记支付宝与微信小程序的区别。

    摘要:上线时间问题发布审核时间,微信小时内会审核完成,但是支付宝官方公示是上线审核需要三到五个工作日,据亲测,实际支付宝审核印版不会超过小时,但是支付宝的审核相比较微信真的很严格。 前言: 最近一个月接收一个支付宝小程序项目,并进行原生开发,现将遇到的问题,爬过的坑给大家进行分享,希望读者可以少走弯路,以下介绍的内容将从大方面到细节进行展开。 废话少言,直接开始步入正题 ①:上传、发布问...

    WilsonLiu95 评论0 收藏0
  • 使用vue开发微信公众号下SPA站点的填坑之旅

    摘要:原文见我的博客,点击进入使用开发微信公众号下站点的填坑之旅本文为我创业过程中,开发项目的填坑之旅。作为一个技术宅男,我的项目是做一个微信公众号,前后端全部自己搞定,不浪费国家一分钱。 原文见我的博客,点击进入使用vue开发微信公众号下SPA站点的填坑之旅 本文为我创业过程中,开发项目的填坑之旅。作为一个技术宅男,我的项目是做一个微信公众号,前后端全部自己搞定,不浪费国家一分钱^_^。 ...

    yeyan1996 评论0 收藏0
  • 使用 vue2.0 开发微信公众号下前后端分离的SPA站点的填坑之旅

    摘要:目前正在写一个微信公众号的小项目,记录一下遇到的问题和解决方法主要是前端。前端提交时使用,在后端再取出对应的微信支付看了下文档,以前是需要用唤起支付,而现在则是把微信内置到了微信的浏览器中。 目前正在写一个微信公众号的小项目,记录一下遇到的问题和解决方法(主要是前端)。内容持续更新中~ 主要实现 前后端分离前端为 SPA 单页面使用微信的JSSDK微信支付 技术方案 后端使用 php ...

    afishhhhh 评论0 收藏0

发表评论

0条评论

zhunjiee

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<