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

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

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

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

APP支付

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

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

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

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

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

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

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

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

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

  1. 获取 prepayId :

    1. 设置获取 prepayId 所需参数。

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

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

    1. 签名。

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

    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));
    
  2. 将拼好的数据,以 XML 的格式发送给微信,请求 prepayId

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

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

    public functionarrayToXml(array $data)
    {
        $xml = "<xml>";
        foreach ($data as $k => $v) {
            if (is_numeric($v)) {
                $xml .= "<{$k}>{$v}</{$k}>";
            } else {
                $xml .= "<{$k}><![CDATA[{$v}]]></{$k}>";
            }
        }
        $xml .= "</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 functionxmlToArray($xml){
        return json_decode(json_encode(simplexml_load_string($xml, ‘SimpleXMLElement‘, LIBXML_NOCDATA)), true);
    }
    

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

  3. 将获取的 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代码

functiononBridgeReady(){
   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/wxpay/notify.php 是没问题的,但是 http://domain.com/payment/notify.php?payment_code=wxpay 这样的URL是不行的。如果要想达到这种效果,要不服务器(Nginx , Apache)进行rewrite,要不在notify.php 中,手动修改 $_GET 中的参数。

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

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

  1. 校验返回码是否成功

    $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‘]}");
    }
    
  2. 对返回数据进行校验

    和请求 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);
    

微信退款

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

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

  1. 交易时间超过 一年 的订单无法提交退款;
  2. 微信支付退款支持单笔交易分多次退款,多次退款需要提交 原支付订单的商户订单号 和设置 不同的退款单号 。总退款金额不能超过用户实际支付金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号 。
  3. 退款请求需要证书 。

【证书获取方式:】

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

微信退款程序流程:

  1. 设置退款时得参数。

    请求的参数有:

    1. appid : 公众账号ID
    2. mch_id : 商户号
    3. nonce_str : 随机字符串
    4. sign: 签名
    5. transaction_id / out_trade_no :微信订单号 / 商户订单号 二者中传其中一个即可。
    6. out_refund_no: 商户退款单号(由商户自行生成的唯一标识)
    7. total_fee:订单金额(单位为分)
    8. refund_fee:退款金额(单位为分),退款金额不能大于订单金额。
    9. op_user_id:操作员帐号, 默认为商户号

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

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

  3. 发送退款请求。

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

  4. 解析请求结果。

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

结尾

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

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

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

时间: 2024-10-29 19:10:44

支付开发填坑记之微信支付的相关文章

Java Web 开发填坑记- 如何正确的下载 Eclipse

一直以来,做 Java web 开发都是用 eclipse , 可是到 eclipse 官网一看,我的天 http://www.eclipse.org/downloads/eclipse-packages/ 那么多应该下载哪一个?这是一个问题? 其实 eclipse 为每一种开发者,都提供了不同的版本.

Android 项目开发填坑记 - 获取系统语言(兼容7.0)

如果移动端访问不佳,请访问–> Github版 关键词:Android7.0 .系统语言 .顺序不一致 获取系统当前语言是一个比较常用的功能,在 Android 7.0 系统上旧函数获取到的当前系统语言并不正确,或者说从 Android 7.0 起,Android 系统语言的规则变了. 背景 下面是未适配 Android 7.0 的代码: // 获取 Locale 的方式有二 Locale locale = getResources().getConfiguration().locale; Lo

Android项目开发填坑记-so文件引发的攻坚战

原文地址 http://blog.csdn.net/ys743276112/article/details/50903905 java.lang.UnsatisfiedLinkError 解决方法 主要内容 安装包在只编译了armeabi,没有x86.arm64-v8a,是如何运行在各种处理器的手机上的? https://www.zhihu.com/question/36893314/answer/69467752 arm64-v8a是可以向下兼容的,但前提是你的项目里面没有arm64-v8a的

UiAutomator2.0升级填坑记

UiAutomator2.0升级填坑记 SkySeraph May. 28th 2017 Email:[email protected] 更多精彩请直接访问SkySeraph个人站点:www.skyseraph.com 啰嗦 Google Android Developers 在2015年3月就发布了UiAutomator 2.0版本(下文简称U2),而公司的核心产品中用到还是UiAutomator老版本(下文简称U1),业界用U2的也不是很多,虽然有诸多问题和不便(如高版本OS中不支持Remo

minikube windows hyperx填坑记

minikube windows hyperx填坑记 安装了一天半,还是没行,先放弃 开始 minikube start --vm-driver=hyperv --hyperv-virtual-switch=k8svswitch --cpus=4 --memory=4096 --docker-env HTTP_PROXY=http://192.168.31.77:1080 --docker-env HTTPS_PROXY=http://192.168.31.77:1080 --docker-en

android调用微信支付,填坑

关于android调用微信支付,在网上基本是骂声一片.在于官方文档,对于许多问题都没有描述.我罗列一下我遇到的麻烦,供大家参考 首先想要获取微信支付功能,必须在微信开放平台,申请app 支付能力,根据官方文档,按照要求,完成后 官方会提供 appid .appsercert两个重要参数.以及自己设置的应用签名 一:官方文档 https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1 根据官方文档,app需要先调用预支付接口获得 p

微信支付接口开发之---微信支付之JSSDK(公众号支付)步骤

1.准备 1.1.公众号为服务号,开通微信支付功能 1.2.为了方便调试微信后台的回调URL(必须为外网),我用了nat123软件来做一个映射 1.3.官方微信开发的示例WxPayApi(.net版本) 2.业务流程图 3.步骤 3.1.用户访问商户的链接,商户链接地址调用[网页授权获取用户信息]接口获取用户的openid和access_token 参考:网页授权获取用户基本信息 3.1.1.第一步,用户同意授权,获取code,调用接口如下 https://open.weixin.qq.com/

微信支付开发教程-静态链接Native原生支付开发

微信支付现在分为v2版和v3版,2014年9月10号之前申请的为v2版,之后申请的为v3版.V3版的微信支付没有paySignKey参数.v2的相关介绍请参考方倍工作室的其他文章.本文介绍的为微信支付v3. 一.静态链接二维码生成 静态链接二维码的生成过程中,核心是sign签名.下面介绍sign签名过程. 1. 参数准备 唯一要确定的参数是产品的ID号.其他的是支付参数或者由程序生成.程序生成的部分如下. $this->parameters["appid"] = WxPayCon

微信小程序开发填坑指南V1

近期用了一星期的时间,开发了一个小程序.小程序名称是:小特Jarvis,取自钢铁侠的管家. 后台采用C#编写,WebAPI接口.其实开发时间并不多,小程序本身提供的API,相比公众号的API来说,已经封装了好多东西,我们只负责简单调用即可.而且,提供的开发工具也很方便,开发环境和VisualStudio很类似,包括快捷键(不知道Java的开发员是不是也有这感觉?) 好了说重点.今天是个总结,把这一星期开发时遇到的坑整理下,希望其他人遇到时能有个参考.其实开发的坑不多,部署的坑最多.开始咯 1,多