一、前言
1、大概思路
- 1、查找官方文档:百度搜索
微信现金红包接口
,找到页面 微信支付 - 现金红包 - 产品说明 - 2、根据文档接口,组装请求参数:微信支付 - 现金红包 - 发放红包接口
- 3、使用参数去请求接口,处理接口返回的数据:微信支付 - 现金红包 - 安全规范
2、注意
注意
:这篇文章只是针对单个接口,编写PHP的实现方法。而不是从零教你怎么把微信接口相关数据JDK
,证书
,...
整合到我们的项目中。- 这里为了演示,把自定义的处理数据方法都写到
Test.php
中了,实际中,请把这些公共方法,放到extend
下的Wechat\WechatPay.php
中,WechatPay.php
为自定义的文件 - 他山之石:微信小程序PHP 微信支付接口调用
3、特别注意
- 接口请求参数
total_amount
的单位是分
- 微信接口
可能会变动
,可能会致使以下PHP代码失效,请以最新的官方文档为准
二、代码
- Test.php
<?php
namespace app\index\controller;
use think\Exception;
use think\facade\Env;
class Test {
public function __construct() {
//header('Access-Control-Allow-Origin:*'); //支持跨域请求
}
/**
* 入口方法
* @throws Exception
*/
public function reward() {
$money = 1; //奖励红包金额:单位-元
$openid = 'sadfsadfsd'; //用户的openid
list($requestString, $responseString, $message) = self::sendMoney($money, $openid);
if ($message) {
echo <<<EOF
【请求数据】<br/>
{$requestString}<br/><br/>
【响应数据】<br/>
{$responseString}<br/><br/>
【错误提示】<br/>
{$message}
EOF;
} else {
echo "操作成功~";
}
}
/**
* 微信发放红包接口文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3
* 组装数据,调用微信接口(请注意:这里的参数请根据自己的实际情况来改动,我这里写的是个Demo,仅供参考~)
* @param $money 红包金额:单位-元 (微信接口的金额单位为:分,这里要注意一下)
* @param $openid 用户的openid
* @return array 结果数据
* @throws Exception 抛出异常
*/
public function sendMoney($money, $openid) {
$amount = $money * 100; //红包金额:需要转成微信需要的单位-分
//组装接口请求数据:就按照文档的参数顺序来
$params = [
'nonce_str' => self::getRandStr(), //随机字符串,不长于32位
//'sign' => '', //签名
'mch_billno' => self::getUniqidStr(), //商户订单号(每个订单号必须唯一。取值范围:0~9,a~z,A~Z)
'mch_id' => '100***98', //商户号-微信支付分配的商户号
'wxappid' => 'wx8888888888888888', //公众账号appid-微信分配的公众账号ID(企业号corpid即为此appId)。在微信开放平台(open.weixin.qq.com)申请的移动应用appid无法使用该接口。
'send_name' => '商户名称', //商户名称-红包发送者名称(注意:敏感词会被转义成字符*)
're_openid' => $openid, //用户openid-接受红包的用户openid
'total_amount' => $amount, //付款金额:单位-分
'total_num' => 1, //红包发放总人数:total_num=1
'wishing' => '感谢您参加活动,祝您生活愉快!', //红包祝福语(注意:敏感词会被转义成字符*)
'client_ip' => $_SERVER['SERVER_ADDR'], //Ip地址-调用接口的机器Ip地址
'act_name' => "促销节", //活动名称(注意:敏感词会被转义成字符*)
'remark' => "参加活动【促销节】,平台奖励红包", //备注-备注信息
//'scene_id' => 0, //场景id-否: 红包金额大于200或者小于1元时,请求参数scene_id必传,参数说明见下文。
//'risk_info' => 0, //活动信息-否 在数据示例中,这个参数是没有 <![CDATA[]] 的,需要注意一下
];
//发放红包使用场景,红包金额大于200或者小于1元时必传:PRODUCT_1:商品促销,PRODUCT_2:抽奖,PRODUCT_3:虚拟物品兑奖,PRODUCT_4:企业内部福利,PRODUCT_5:渠道分润,PRODUCT_6:保险回馈,PRODUCT_7:彩票派奖,PRODUCT_8:税务刮奖,
if (($money < 1) || ($money > 200)) {
$params['scene_id'] = 'PRODUCT_1';
}
//签名生成
$params['sign'] = self::getSign($params, '21b3*******943');
//请求接口
$message = '';
$xml = self::arrayToXml($params);
$url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack'; //微信发放红包API地址
$response = self::curlPostXml($xml, $url, true);
$responseRet = self::xmlToArray($response);
if ($responseRet['result_code'] == 'FAIL') {
$message = !empty($responseRet['err_code_des']) ? $responseRet['err_code_des'] : '接口异常';
}
//我这里记录一下接口的(请求数据、返回数据):各位可以把这个数据存文件或者存数据表,用于上线后问题排查
$requestString = json_encode($params, JSON_UNESCAPED_UNICODE);
$responseString = json_encode($responseRet, JSON_UNESCAPED_UNICODE);
return [$requestString, $responseString, $message];
}
/**
* post方式传xml数据请求接口
* 由于curl_setopt设置实在是太多了:这个方法中可能不是很完善,大家实际运用中发现了问题再添加curl_setopt相关设置
* @param $xml 请求的XML数据
* @param $url 请求的地址
* @param bool $certCheck 是否需要证书校验:false-否; true-是
* @param array $header 请求头设置
* @param int $second 超时时间设置(默认30秒)
* @return bool|string 返回的结果数据
* @throws Exception 接口请求异常提示
*/
public function curlPostXml($xml, $url, $certCheck = false, $header = [], $second = 30) {
//开启句柄
$ch = curl_init();
//超时时间
curl_setopt($ch,CURLOPT_TIMEOUT,$second);
curl_setopt($ch,CURLOPT_RETURNTRANSFER, 1);
//这里设置代理,如果有的话
//curl_setopt($ch,CURLOPT_PROXY, '11.***.**.11');
//curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);
//是否传入证书
//apiclient_cert.p12是商户证书文件,除PHP外的开发均使用此证书文件。
//咱们PHP使用这两个证书哈:apiclient_cert.pem apiclient_key.pem
//微信支付证书文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3
if ($certCheck) {
//请注意:这2个文件隐私性是极高的,我们linux中应该设置这2个文件夹权限为只读,不能修改,也不能下载。
curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM'); //默认格式为PEM,可以注释
//绝对地址可使用 dirname(__DIR__)打印,如果不是绝对地址会报 58 错误
curl_setopt($ch,CURLOPT_SSLCERT, Env::get('extend_path') .'Wechat/cert/apiclient_cert.pem');
curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLKEY, Env::get('extend_path') .'Wechat/cert/apiclient_key.pem');
}
//设置请求头
if ($header) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
curl_setopt($ch,CURLOPT_POST, 1); //POST请求:第二个值可以传int类型的:1,也可以传bool类型的:true
curl_setopt($ch,CURLOPT_POSTFIELDS, $xml);
$data = curl_exec($ch); //去请求接口
if ($data) {
curl_close($ch); //关闭句柄
return $data;
} else {
$error = curl_errno($ch); //获取错误码
curl_close($ch); //关闭句柄
throw new Exception("请求异常, errorCode:{$error}"); //如果证书地址错误,可能会报58错误
}
}
/**
* 根据微信的规则,计算出sign参数的值
* @param $array 待处理的参数
* @param $key 秘钥
* @return string 结果数据
*/
public function getSign($array, $key) {
//1、把数组按照键值升序
ksort($array);
//2、数组数据拼接成 a=1&b=2&c=3 形式
$string = self::getUrlParam($array);
//3、在string后加入key参数
$string = $string . "&key=" . $key;
//4、md5加密
$string = md5($string);
//5、字符串转为大写并返回
return strtoupper($string);
}
/**
* 格式化参数为url参数:把数组数据处理成:a=1&b=2&c=3格式
* @param $array 待处理的参保时
* @return string 处理完的参数
*/
public function getUrlParam($array) {
$string = "";
foreach ($array as $key => $value) {
if ($value && !is_array($value) && ($key != 'sign')) { //值存在并且不是数组;并且键名不为sign
$string .= "{$key}={$value}&";
}
}
if ($string) { //去除最右边的特殊字符&
$string = rtrim($string, '&');
}
return $string;
}
/**
* 创建随机字符串
* @param $length
* @return string
*/
public function getRandStr($length = 16) {
$string = "";
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; //随机字符
for ($i = 0; $i < $length; $i++) {
$string .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $string;
}
/**
* 创建随机唯一字符串
* @param string $prefix 自定义前缀:WR-wechatReward
* @param int $length 随机的字符串长度
* @return string
*/
public function getUniqidStr($prefix = 'WR', $length = 4) {
$string = "";
for($i = 0; $i < $length; $i++) {
$string .= mt_rand(0, 9);
}
return $prefix . uniqid() . $string;
}
/**
* xml字符串转数组
* @param $xml
* @return mixed
* @throws Exception
*/
public function xmlToArray($xml) {
if (empty($xml)) {
throw new Exception('xml数据不能为空');
}
//禁止引用外部xml实体 防XXE注入
libxml_disable_entity_loader(true);
$xmlString = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
$val = json_decode(json_encode($xmlString), true);
return $val;
}
/**
* 数组转XML字符串(包含CDATA)
* @param $array
* @return string
*/
public function arrayToXml($array) {
$str = "<xml>";
foreach ($array as $key => $val) {
$str .= "<{$key}><![CDATA[{$val}]]></{$key}>";
}
$str .= "</xml>";
return $str;
}
/*--------------------------------------------------------*/
/*-------------------- 其他自定义方法 ----------------------*/
/*--------------------------------------------------------*/
/**
* 数组转XML字符串(不包含CDATA)
* @param $array
* @return string
*/
public function arrayToXmlNotCDATA($array) {
$str = "<xml>";
foreach ($array as $key => $val) {
$str.="<{$key}>{$val}</{$key}>";
}
$str .= "</xml>";
return $str;
}
}
三、结果打印
打印截图
如果校验了证书,但是证书地址错误,则报错如下:
请求异常, errorCode:58