APP接入微信APP支付的ThinkPHP5的实现

chapter8_3_1.png 

在ThinkPHP extend目录 创建 lib/wechatAppPay.php

<?php
namespace lib;
class wechatAppPay
{
//接口API URL前缀
const API_URL_PREFIX = 'https://api.mch.weixin.qq.com';
//下单地址URL
const UNIFIEDORDER_URL = "/pay/unifiedorder";
//查询订单URL
const ORDERQUERY_URL = "/pay/orderquery";
//关闭订单URL
const CLOSEORDER_URL = "/pay/closeorder";
//公众账号ID
private $appid;
//商户号
private $mch_id;
//随机字符串
private $nonce_str;
//签名
private $sign;
//商品描述
private $body;
//商户订单号
private $out_trade_no;
//支付总金额
private $total_fee;
//终端IP
private $spbill_create_ip;
//支付结果回调通知地址
private $notify_url;
//交易类型
private $trade_type;
//支付密钥
private $key;
//证书路径
private $SSLCERT_PATH;
private $SSLKEY_PATH;
//所有参数
private $params = array();
public function __construct($appid, $mch_id, $notify_url, $key)
{
$this->appid = $appid;
$this->mch_id = $mch_id;
$this->notify_url = $notify_url;
$this->key = $key;
}
/**
 * 下单方法
 * @param $params 下单参数
 */
public function unifiedOrder($params)
{
$this->body = $params['body'];
$this->out_trade_no = $params['out_trade_no'];
$this->total_fee = $params['total_fee'];
$this->trade_type = $params['trade_type'];
$this->nonce_str = $this->genRandomString();
$this->spbill_create_ip = $_SERVER['REMOTE_ADDR'];
$this->params['appid'] = $this->appid;
$this->params['mch_id'] = $this->mch_id;
$this->params['nonce_str'] = $this->nonce_str;
$this->params['body'] = $this->body;
$this->params['out_trade_no'] = $this->out_trade_no;
$this->params['total_fee'] = $this->total_fee;
$this->params['spbill_create_ip'] = $this->spbill_create_ip;
$this->params['notify_url'] = $this->notify_url;
$this->params['trade_type'] = $this->trade_type;
//获取签名数据
$this->sign = $this->MakeSign($this->params);
$this->params['sign'] = $this->sign;
$xml = $this->data_to_xml($this->params);
$response = $this->postXmlCurl($xml, self::API_URL_PREFIX . self::UNIFIEDORDER_URL);
//var_dump($response);exit();
if (!$response) {
return false;
}
$result = $this->xml_to_data($response);
if (!empty($result['result_code']) && !empty($result['err_code'])) {
$result['err_msg'] = $this->error_code($result['err_code']);
}
return $result;
}
/**
 * 查询订单信息
 * @param $out_trade_no 订单号
 * @returnarray
 */
public function orderQuery($out_trade_no)
{
$this->params['appid'] = $this->appid;
$this->params['mch_id'] = $this->mch_id;
$this->params['nonce_str'] = $this->genRandomString();
$this->params['out_trade_no'] = $out_trade_no;
//获取签名数据
$this->sign = $this->MakeSign($this->params);
$this->params['sign'] = $this->sign;
$xml = $this->data_to_xml($this->params);
$response = $this->postXmlCurl($xml, self::API_URL_PREFIX . self::ORDERQUERY_URL);
if (!$response) {
return false;
}
$result = $this->xml_to_data($response);
if (!empty($result['result_code']) && !empty($result['err_code'])) {
$result['err_msg'] = $this->error_code($result['err_code']);
}
return $result;
}
/**
 * 关闭订单
 * @param $out_trade_no 订单号
 * @returnarray
 */
public function closeOrder($out_trade_no)
{
$this->params['appid'] = $this->appid;
$this->params['mch_id'] = $this->mch_id;
$this->params['nonce_str'] = $this->genRandomString();
$this->params['out_trade_no'] = $out_trade_no;
//获取签名数据
$this->sign = $this->MakeSign($this->params);
$this->params['sign'] = $this->sign;
$xml = $this->data_to_xml($this->params);
$response = $this->postXmlCurl($xml, self::API_URL_PREFIX . self::CLOSEORDER_URL);
if (!$response) {
return false;
}
$result = $this->xml_to_data($response);
return $result;
}
/**
 *
 * 获取支付结果通知数据
 * return array
 */
public function getNotifyData()
{
//获取通知的数据
$xml = $GLOBALS['HTTP_RAW_POST_DATA'];
$data = array();
if (empty($xml)) {
return false;
}
$data = $this->xml_to_data($xml);
if (!empty($data['return_code'])) {
if ($data['return_code'] == 'FAIL') {
return false;
}
}
return $data;
}
/**
 * 接收通知成功后应答输出XML数据
 * @paramstring $xml
 */
public function replyNotify()
{
$data['return_code'] = 'SUCCESS';
$data['return_msg'] = 'OK';
$xml = $this->data_to_xml($data);
echo $xml;
die();
}
/**
 * 生成APP端支付参数
 * @param $prepayid 预支付id
 */
public function getAppPayParams($prepayid)
{
$data['appid'] = $this->appid;
$data['partnerid'] = $this->mch_id;
$data['prepayid'] = $prepayid;
$data['package'] = 'Sign=WXPay';
$data['noncestr'] = $this->genRandomString();
$data['timestamp'] = time();
$data['sign'] = $this->MakeSign($data);
return $data;
}
/**
 * 生成签名
 * @return 签名
 */
public function MakeSign($params)
{
//签名步骤一:按字典序排序数组参数
ksort($params);
$string = $this->ToUrlParams($params);
//签名步骤二:在string后加入KEY
$string = $string . "&key=" . $this->key;
//签名步骤三:MD5加密
$string = md5($string);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
/**
 * 将参数拼接为url: key=value&key=value
 * @param $params
 * @returnstring
 */
public function ToUrlParams($params)
{
$string = '';
if (!empty($params)) {
$array = array();
foreach ($params as $key => $value) {
$array[] = $key . '=' . $value;
}
$string = implode("&", $array);
}
return $string;
}
/**
 * 输出xml字符
 * @param $params 参数名称
 * return string 返回组装的xml
 **/
public function data_to_xml($params)
{
if (!is_array($params) || count($params) <= 0) {
return false;
}
$xml = "<xml>";
foreach ($params as $key => $val) {
// if (is_numeric($val)) {
$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
// } else {
//     $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
// }
}
$xml .= "</xml>";
return $xml;
}
/**
 * 将xml转为array
 * @paramstring $xml
 * return array
 */
public function xml_to_data($xml)
{
if (!$xml) {
return false;
}
//将XML转为array
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $data;
}
/**
 * 获取毫秒级别的时间戳
 */
private static function getMillisecond()
{
//获取毫秒的时间戳
$time = explode(" ", microtime());
$time = $time[1] . ($time[0] * 1000);
$time2 = explode(".", $time);
$time = $time2[0];
return $time;
}
/**
 * 产生一个指定长度的随机字符串,并返回给用户
 * @paramtype $len 产生字符串的长度
 * @returnstring 随机字符串
 */
private function genRandomString($len = 32)
{
$chars = array(
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G",
"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
"S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2",
"3", "4", "5", "6", "7", "8", "9",
);
$charsLen = count($chars) - 1;
// 将数组打乱
shuffle($chars);
$output = "";
for ($i = 0; $i < $len; $i++) {
$output .= $chars[mt_rand(0, $charsLen)];
}
return $output;
}
/**
 * 以post方式提交xml到对应的接口url
 *
 * @paramstring $xml 需要post的xml数据
 * @paramstring $url url
 * @parambool $useCert 是否需要证书,默认不需要
 * @paramint $second url执行超时时间,默认30s
 * @throwsWxPayException
 */
private function postXmlCurl($xml, $url, $useCert = false, $second = 30)
{
$curl = curl_init();
        // 启动一个CURL会话
        curl_setopt($curl, CURLOPT_URL, $url);
        // 要访问的地址
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        // 跳过证书检查
        //curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
        //curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); // 从证书中检查SSL加密算法是否存在
        //curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
        //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
        curl_setopt($curl, CURLOPT_AUTOREFERER, 1);
        // 自动设置Referer
        curl_setopt($curl, CURLOPT_POST, 1);
        // 发送一个常规的Post请求
        curl_setopt($curl, CURLOPT_POSTFIELDS, $xml);
        // Post提交的数据包
        curl_setopt($curl, CURLOPT_TIMEOUT, 30);
        // 设置超时限制防止死循环
        curl_setopt($curl, CURLOPT_HEADER, 0);
        // 显示返回的Header区域内容
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        // 获取的信息以文件流的形式返回
        $tmpInfo = curl_exec($curl);
        // 执行操作
        if (curl_errno($curl)) {
            echo 'Errno' . curl_error($curl);
            //捕抓异常
        }
        curl_close($curl);
        // 关闭CURL会话
        return $tmpInfo;
        // 返回数据,json格式
}
/**
 * 错误代码
 * @param $code 服务器输出的错误代码
 * return string
 */
public function error_code($code)
{
$errList = array(
'NOAUTH' => '商户未开通此接口权限',
'NOTENOUGH' => '用户帐号余额不足',
'ORDERNOTEXIST' => '订单号不存在',
'ORDERPAID' => '商户订单已支付,无需重复操作',
'ORDERCLOSED' => '当前订单已关闭,无法支付',
'SYSTEMERROR' => '系统错误!系统超时',
'APPID_NOT_EXIST' => '参数中缺少APPID',
'MCHID_NOT_EXIST' => '参数中缺少MCHID',
'APPID_MCHID_NOT_MATCH' => 'appid和mch_id不匹配',
'LACK_PARAMS' => '缺少必要的请求参数',
'OUT_TRADE_NO_USED' => '同一笔交易不能多次提交',
'SIGNERROR' => '参数签名结果不正确',
'XML_FORMAT_ERROR' => 'XML格式错误',
'REQUIRE_POST_METHOD' => '未使用post传递参数 ',
'POST_DATA_EMPTY' => 'post数据不能为空',
'NOT_UTF8' => '未使用指定编码格式',
);
if (array_key_exists($code, $errList)) {
return $errList[$code];
}
}
}

 使用:统一下单与回调接口实现

<?php
namespace app\api\controller;
use app\common\controller\Api;
use think\Db;
/**
 * 示例接口
 */
class Order extends Api
{
//如果$noNeedLogin为空表示所有接口都需要登录才能请求
//如果$noNeedRight为空表示所有接口都需要验证权限才能请求
//如果接口已经设置无需登录,那也就无需鉴权了
//
// 无需登录的接口,*表示全部
protected $noNeedLogin = '*';
// 无需鉴权的接口,*表示全部
protected $noNeedRight = '*';
/**
     * 生成订单号
     *
     */
public function get_order_no()
{
$yCode = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J');
$orderSn = $yCode[intval(date('Y')) - 2018] . strtoupper(dechex(date('m'))) . date('d') . substr(time(), -5) . substr(microtime(), 2, 5) . sprintf('%02d', rand(0, 99));
return $orderSn;
}
/**
     * 生成订单
     *
     * @paramstring $mobile 手机号
     * @paramstring $event 事件名称userId:用户id  goodsId:商品id  trade_type:支付类型,wxpay,alipay amount:金额 body:商品描述 attach:附件参数
     */
public function order_create()
{
if (!$this->request->request('userId') || !$this->request->request('goodsId') || !$this->request->request('trade_type') || !$this->request->request('amount')) {
$ret = array(
'code' => 200,
'msg' => "参数错误",
);
echo json_encode($ret);
exit();
}
$orderInfo = array(
'orderSn' => $this->get_order_no(),
'userId' => $this->request->request('userId'),
'goodsId' => $this->request->request('goodsId'),
'payType' => $this->request->request('trade_type'),
'amount' => $this->request->request('amount'),
'create_time' => date('Y-m-d H:i:s'),
'result' => '',
'status' => 0,
);
//生成订单入库
//$result = Db::table('GameOrderSn')->insert($orderInfo);
$result = 1;
if ($result) {
//1.统一下单方法
$appid = 'appid';
$mch_id = '店铺id';
$notify_url = '回调地址';
$key = '秘钥key';
$wechatAppPay = new \lib\wechatAppPay($appid, $mch_id, $notify_url, $key);
$params['body'] = $this->request->request('des') ? $this->request->request('des') : ''; //商品描述
$params['attach'] = $this->request->request('attach') ? $this->request->request('attach') : '测试'; //商品描述
$params['out_trade_no'] = $orderInfo['orderSn']; //自定义的订单号
$params['total_fee'] = $orderInfo['amount']; //订单金额 只能为整数 单位为分
$params['trade_type'] = 'APP'; //交易类型 JSAPI | NATIVE | APP | WAP
$result = $wechatAppPay->unifiedOrder($params);
// print_r($result); // result中就是返回的各种信息信息,成功的情况下也包含很重要的prepay_id
//2.创建APP端预支付参数
/** @varTYPE_NAME $result */
$data = @$wechatAppPay->getAppPayParams($result['prepay_id']);
// 根据上行取得的支付参数请求支付即可
// print_r($data);
exit(json_encode($data));
// } else {
//     $ret = array(
//         'code' => 500,
//     );
// }
// echo json_encode($ret);
}
}
/**
     *微信回调地址接口
     *
     *
     *
     */
public function notify()
{
//接收微信xml格式信息
$xmlData = file_get_contents('php://input');
//  //转换为数组
//测试用例
//$array = json_decode($xmlData,TRUE);
$array = $this->XMLDataParse($xmlData);
//   var_dump($this -> verifySign($array));
//对微信返回的签名进行验证是否正确
if (!$this->verifySign($array)) {
exit('<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名不正确]]></return_msg></xml>');
}
//写支付记录,更新订单表
//to do...
$orderSn = $array['out_trade_no']; //商户订单号
$order = array(
'paySn' => $array['transaction_id'], //微信支付订单号
'status' => 1,
'pay_time' => date('Y-m-d H:i:s'),
);
//查看订单状态,为已支付则完成流程
$orderInfo = Db::table('GameOrderSn')->where('orderSn', $orderSn)->find();
if ($orderInfo['status'] == 1) {
exit('<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>');
}
// 启动事务
Db::startTrans();
try {
$userId = $orderInfo['userId'];
$goodsId=$orderInfo['goodsId'];
$amount=$orderInfo['amount'];
Db::table('GameOrderSn')->where('orderSn', $orderSn)->update($order); //更新订单信息
//查询商品,为已支付则完成流程
$goodsInfo = Db::table('ShopInfoList')->where('ItemID', $goodsId)->find();
//计算购买道具数据量
$nums=($amount/$goodsInfo['Price'])*$goodsInfo['GoodsNum'];
//更新用户道具
Db::table('GameScoreInfo')->where('UserID', $userId)->setInc('InsureScore',$nums);
Db::commit();
//处理购买后的业务逻辑.通知充值结果到服务器
$codeM = 13;
$codeS = 1;
$this->send_msg($userId,$codeM,$codeS);
} catch (\Exception $e) {
// 回滚事务
Db::rollback();
}
//下面是处理完逻辑,返回给微信服务器的
exit('<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>');
}
/**
     * 通知充值结果到服务器
     * @param $params
     * @returnstring
     */
public function send_msg($userId,$codeM,$codeS)
{
}