thinkphp5使用微信支付SDK

1、首先在微信公众平台下载微信支付的SKD,解压后将lib目录下的文件置于项目的extend目录下WxPay目录下

       image.png    

2、在项目中创建service目录,并在service目录下创建Pay.php,该目录可以根据自己的业务是否要,此处我把逻辑都写在在里面了,代码如下:

<?php
/**
 * Created by jinkuang
 * Email: jinkuanghqu@gmail.com
 * Date : 2018/3/16
 * Time : 11:40
 */

namespace app\api\service;


use app\lib\enum\OrderStatusEnum;
use app\lib\exception\OrderException;
use app\lib\exception\TokenException;
use think\Exception;
use app\api\service\Order as OrderService;
use app\api\model\Order as OrderModel;
use think\Loader;
use think\Log;
//引入sdk类
Loader::import('WxPay.WxPay',EXTEND_PATH,'.Api.php');

class Pay
{
    private $orderId;
    private $orderNo;

    public function __construct ($orderId)
    {
        if ($orderId) {
            throw new Exception('订单号不允许为NULL');
        }
        $this->orderId = $orderId;
    }

    public function pay()
    {
        //订单号可能不存在
        //订单号存在但是和当前用户不匹配
        //检测订单是否已经被检测
        $this->checkOrderValid();
        //进行库存检测
        $orderService = new OrderService();
        $status = $orderService->checkOrderStock($this->orderId);
        if (!$status['pass']) {
            return $status;
        }
        return $this->makeWxPreOrder($status['orderPrice']);

    }

    private function makeWxPreOrder($totalPrice)
    {
        $openid = Token::getCurrentTokenVar('openid');
        if (!$openid) {
            throw new TokenException();
        }
        //实例化统一下单类
        $wxOrderData = new \WxPayUnifiedOrder();
        //订单号
        $wxOrderData->SetOut_trade_no($this->orderNo);
        //调用类型
        $wxOrderData->SetTrade_type('JSAPI');
        //总价钱,把金额转为分为单位
        $wxOrderData->SetTotal_fee($totalPrice*100);
        //设置显示的名称
        $wxOrderData->SetBody('微信支付');
        //openid
        $wxOrderData->SetOpenid($openid);
        //填写微信异步通知的回调地址,一定要填写不然接收到通知
        $wxOrderData->SetNotify_url('');//回调地址
        return $this->getPaySignture($wxOrderData);
    }

    private function getPaySignture($wxOrderData)
    {
        $wxOrder = \WxPayApi::unifiedOrder($wxOrderData);
        if ($wxOrder['return_code'] != 'SUCCESS' || $wxOrder['result_code'] != 'SUCCESS') {
            Log::record($wxOrder,'error');
            Log::record('获取预支付订单失败','error');
        }
        //prepay_id向用户推送消息模板时需要用到
        $this->recordPreOrder($wxOrder);
        $signature = $this->sign($wxOrder);
        return $signature;
    }

    private function recordPreOrder($wxOrder)
    {
        OrderModel::where('id','=',$this->orderId)->update(['prepay_id'=>$wxOrder['prepay_id']]);
    }
        //生成签名
    private function sign($wxOrder)
    {
        $jsApiPayData = new \WxPayJsApiPay();
        $jsApiPayData->SetAppid(config('wx.app_id'));
        $jsApiPayData->SetTimeStamp((string)time());
        $rand = md5(rand(time().mt_rand(0,1000)));
        //随机字符串
        $jsApiPayData->SetNonceStr($rand);
        //特别要注意,此处一定要写成queryString的形式
        $jsApiPayData->SetPackage('prepay_id='.$wxOrder['prepay_id']);
        //加密类型
        $jsApiPayData->SetSignType('md5');
        //调用微信sdk中的方法生成签名
        $sign = $jsApiPayData->MakeSign();
        //把对象转换为数组
        $rawValues = $jsApiPayData->GetValues();
        $rawValues['paySign'] = $sign;
        unset($rawValues['appId']);

        return $rawValues;
    }

    /**
     * 检测订单的有效性
     * @return mixed true
     * @throws OrderException
     * @throws TokenException
     */
    private function checkOrderValid()
    {
        $order = OrderModel::where('id','=',$this->orderId)->find();
        //检测订单是否存在
        if (!$order) {
            throw new OrderException();
        }
        //检测订单是否和当前用户匹配
        if (Token::isValidOperate($order->user_id)) {
            throw new TokenException([
                'msg'=>'订单与用户不匹配',
                'errorCode'=>10003
            ]);
        }
        //检测订单是否是未支付状态
        if ($order->status != OrderStatusEnum::UNPAY) {
            throw new OrderException([
                'msg'=>'订单已支付过了',
                'errorCode'=>80003,
                'code'=>404
            ]);
        }
        $this->orderNo = $order->order_no;
        return true;

    }

}

注意:

1、要使用sdk一定要先引入sdk类:Loader::import('WxPay.WxPay',EXTEND_PATH,'.Api.php'); 

2、实例化的时候一定要加上反斜杠'\' 如:$wxOrderData = new \WxPayUnifiedOrder();

3、支付金额一定要转化为以'分'为单位:$wxOrderData->SetTotal_fee($totalPrice*100);

4、prepay_id向用户推送消息模板时需要用到,设置prepay_id时一定要写成queryString的形式:$jsApiPayData->SetPackage('prepay_id='.$wxOrder['prepay_id']);

3、在controller中进行调用

<?php
namespace app\api\controller\v1;

use app\api\controller\BaseController;
use app\api\service\Pay as PayService;
use app\api\service\WxNotify;
use app\api\validate\IDMustBePositiveInt;
use think\Controller;

class Pay extends BaseController
{

protected $beforeActionList = [
        'checkExclusiveScope' => ['only' => 'getPreOrder']
    ];
public function getPreOrder($id='')
    {
        (new IDMustBePositiveInt()) -> goCheck();
        $pay= new PayService($id);
        return $pay->pay();
    }

    public function redirectNotify()
    {
        //WxNotify是service目录下的WxNotify.php,WxNotify用于处理微信的异步通知结果的,单独创建WxNotify
        的目的就是为了能够很好的使用sdk中的封装好的方法的
        $notify = new WxNotify();
        $notify->handle();
    }
    
 }

4、在service目录下创建WxNotify.php ,WxNotify类继承sdk中的WxPayNotify,要继承就一定要先引入喽

<?php
namespace app\api\service;

use app\api\model\Order;
use app\api\model\Product;
use app\api\service\Order as OrderService;
use app\lib\enum\OrderStatusEnum;
use app\lib\order\OrderStatus;
use think\Db;
use think\Exception;
use think\Loader;
use think\Log;

Loader::import('WxPay.WxPay', EXTEND_PATH, '.Api.php');

class WxNotify extends \WxPayNotify
{
    public function NotifyProcess($data, &$msg)
    {
        if ($data['result_code'] == 'SUCCESS') {
            $orderNo = $data['out_trade_no'];
            Db::startTrans();
            try {
                $order = Order::where('order_no', '=', $orderNo)->lock(true)->find();
                if ($order->status == 1) {
                    $service = new OrderService();
                    $status = $service->checkOrderStock($order->id);
                    if ($status['pass']) {
                        $this->updateOrderStatus($order->id, true);
                        $this->reduceStock($status);
                    } else {
                        $this->updateOrderStatus($order->id, false);
                    }
                }
                Db::commit();
            } catch (Exception $ex) {
                Db::rollback();
                Log::error($ex);
                // 如果出现异常,向微信返回false,请求重新发送通知
                return false;
            }
        }
        return true;
    }


    private function reduceStock($status)
    {
        foreach ($status['pStatusArray'] as $singlePStatus) {
            Product::where('id', '=', $singlePStatus['id'])
                ->setDec('stock', $singlePStatus['count']);
        }
    }

    private function updateOrderStatus($orderID, $success)
    {
        $status = $success ? OrderStatusEnum::PAID : OrderStatusEnum::PAID_BUT_OUT_OF;
        Order::where('id', '=', $orderID)
            ->update(['status' => $status]);
    }
}

注意:此处应该修改sdk中的文件把WxPay.Notify.php引入到WxPay.Api.php文件中,不然会报错的

image.png