微信支付(composer require overtrue/laravel-wechat)

汝跃
2023-12-01

以JSAPI支付为例

一、参照文档引入 composer 包 overtrue/laravel-wechat

二、商户开发配置自行到微信官网获取 商户后台

三、具体代码如下

<?php

namespace App\Http\Controllers\H5\Verification;

use EasyWeChat\Factory;
use App\Model\Agent\Agent;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Model\Advert\PlatformConfig;
use App\Model\OfflineClassroom\ClassroomAppiesModel;
use App\Model\OfflineClassroom\ClassroomPayHistoryModel;
use App\Model\OfflineClassroom\ClassroomRefundHistoryModel;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

use function EasyWeChat\Kernel\Support\generate_sign;

class PayController extends Controller
{
    protected $request;
    protected $agent;
    protected $appiesModel;
    protected $platformConfig;
    protected $historyModel;
    protected $refundHistoryModel;

    public function __construct(Request $request, Agent $agent, ClassroomAppiesModel $appiesModel, PlatformConfig $platformConfig, ClassroomPayHistoryModel $historyModel, ClassroomRefundHistoryModel $refundHistoryModel)
    {
        parent::__construct($request);
        $this->request = $request;
        $this->agent = $agent;
        $this->appiesModel = $appiesModel;
        $this->platformConfig = $platformConfig;
        $this->historyModel = $historyModel;
        $this->refundHistoryModel = $refundHistoryModel;
    }

    /**
     * 支付配置
     * @return string[]
     */
    public function config()
    {
        $server = $this->request->server();
        return [
            // 必要配置
            'app_id' => env('WECHAT_OFFICIAL_ACCOUNT_APPID', 'wx0b019484714*****'),
            'mch_id' => env('WECHAT_MCH_ID', '******'),//商户id
            'key' => env('WECHAT_API_SECRET_KEY', 'p32u23u4jk5l******8sdf378sjdf'), //API 密钥

            // 如需使用敏感接口(如退款、发送红包等)需要配置 API 证书路径(登录商户平台下载 API 证书)
            'cert_path' => $server['DOCUMENT_ROOT'] . '/wxpay/apiclient_cert.pem',    // XXX: 绝对路径!!!!
            'key_path' => $server['DOCUMENT_ROOT'] . '/wxpay/apiclient_key.pem',      // XXX: 绝对路径!!!!
            'notify_url' => env('APP_URL', 'https://lhdev.com') . '/mapi/wechat_pay_notify',    //支付回调地址(必须为https)

            //'sandbox' => true, // 设置为 false 或注释则关闭沙箱模式
        ];
    }

    /**
     * 统一下单 唤起微信支付
     * @return mixed
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function wechat_pay()
    {
        $order_no = request('order_no');//订单编号
        if (empty($order_no)) {
            return restful(['message' => '订单编号不能为空', 'code' => 201]);
        }
        $data = $this->get_data();
        if ($data['code'] == 201) {
            return restful($data);
        }

        $app = Factory::payment($this->config());
        $result = $app->order->unify([
            'body' => '课程押金',
            'out_trade_no' => $order_no,
            'total_fee' => $data['total_fee'],
            'trade_type' => 'JSAPI', // 请对应换成你的支付方式对应的值类型
            'openid' => $data['openid'],
        ]);
        //成功生成统一下单的订单,那么进行二次签名
        if ($result['return_code'] === 'SUCCESS' && !empty($result['prepay_id'])) {
            // 二次签名的参数必须与下面相同
            $params = [
                'appId' => $result['appid'],
                'timeStamp' => time(),
                'nonceStr' => $result['nonce_str'],
                'package' => 'prepay_id=' . $result['prepay_id'],
                'signType' => 'MD5',
            ];

            $params['paySign'] = generate_sign($params, $this->config()['key']);

            return restful(['message' => 'OK', 'data' => $params]);
        } else {
            return restful(['message' => '错误', 'data' => $result, 'code' => 201]);
        }
    }

    /**
     * 支付回调
     * @throws \EasyWeChat\Kernel\Exceptions\Exception
     */
    public function wechat_pay_notify()
    {
        try {
            DB::beginTransaction();
            $app = Factory::payment($this->config());
            $response = $app->handlePaidNotify(function ($message, $fail) {
                // 使用通知里的 "微信支付订单号" 或者 "商户订单号" 去自己的数据库找到订单
                $order = $this->appiesModel->where(['order_no' => $message['out_trade_no']])->first();

                // 如果订单不存在 或者 订单已经支付过了
                if (!$order || $order->pay_at) {
                    return true; // 告诉微信,我已经处理完了,订单没找到,别再通知我了
                }

                //建议在这里调用微信的【订单查询】接口查一下该笔订单的情况,确认是已经支付
                if ($message['return_code'] === 'SUCCESS') {
                    // 用户是否支付成功
                    if ($message['result_code'] === 'SUCCESS') {

                        $order->pay_status = 1;
                        $order->amount = $message['total_fee'];
                        $order->pay_at = date('Y-m-d H:i:s'); // 更新支付时间为当前时间

                        // 用户支付失败
                    } elseif ($message['result_code'] === 'FAIL') {
                        $order->pay_status = 4;
                    }
                } else {
                    return true;
                    // return $fail('通信失败,请稍后再通知我');
                }

                $order->save(); // 保存订单

                $this->payment_history($message, $order->agent_id);//记录支付数据

                DB::commit();
                return true; //返回处理完成
            });

            $response->send(); //return $response;

        } catch (\Exception $e) {
            DB::rollBack();
            return false;
        }
    }

    /**
     * 用户端回调查询支付结果
     * @return mixed
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     */
    public function check_pay_results()
    {
        $order_no = request('order_no');//订单编号
        if (empty($order_no)) {
            return restful(['message' => '订单编号不能为空', 'code' => 201]);
        }

        $app = Factory::payment($this->config());
        $result = $app->order->queryByOutTradeNumber($order_no);//系统内的订单编号
        //$result = $app->order->queryByTransactionId("4200001133202108190483451692");//微信返回的订单编号。

        if ($result['result_code'] === 'SUCCESS') {
            return restful(['message' => '支付成功', 'data' => $result]);
        }

        return restful(['message' => '支付失败', 'data' => $result, 'code' => 201]);
    }

    /**
     * 获取用户open_id和配置的报名金额
     * @return array
     */
    public function get_data()
    {
        $agent_id = resolve('agent')['id'];
        $agent = $this->agent->where('id', $agent_id)->first(['id', 'weixin_openid']);

        if (empty($agent->weixin_openid)) {
            return ['message' => '微信未授权无法支付', 'code' => 201];
        }
        $data = $this->platformConfig->first(['id', 'deposit']);
        $total_fee = $data ? bcmul($data->deposit, 100) : 100;
        return [
            'code' => 200,
            'openid' => $agent->weixin_openid,
            'total_fee' => (int)$total_fee //报名金额(分)
        ];
    }

    /**
     * 记录支付数据
     * @param array $data
     * @param $agent_id
     */
    public function payment_history($data = [], $agent_id)
    {
        $time = time();
        $payment = [
            'agent_id' => $agent_id,
            'trade_type' => $data['trade_type'],
            'transaction_id' => $data['transaction_id'],
            'order_no' => $data['out_trade_no'],
            'total_fee' => $data['total_fee'],
            'info' => json_encode($data),
            'created_at' => $time,
            'updated_at' => $time,
        ];
        $this->historyModel->create($payment);
    }

    /**
     * 退款操作
     * @return array
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     */
    public function refund()
    {
        $id = request('id');//报名id
        if (empty($id)) {
            return ['message' => '参数错误', 'code' => 201];
        }
        $order = $this->appiesModel->with(['history'])->find($id);

        if (empty($order)) {
            return ['message' => '报名记录不存在', 'code' => 201];
        }
        if ($order->pay_status != 1) {
            return ['message' => '支付状态错误不可退款(未支付或已退款)', 'code' => 201];
        }
        if (empty($order->history)) {
            return ['message' => '没有支付记录退款失败', 'code' => 201];
        }
        if (empty($order->amount)) {
            return ['message' => '金额有误退款失败', 'code' => 201];
        }
        $data = $order->toArray()['history'];
        $refund_no = 'TK' . time() . mt_rand(10000, 99999);//退单编号

        $other['refund_desc'] = '押金退还';
        $other['notify_url'] = env('APP_URL', 'https://lhdev.com') . '/mapi/wechat_funded_notify'; //退款回调地址

        $app = Factory::payment($this->config());
        $result = $app->refund->byTransactionId($data['transaction_id'], $refund_no, $data['total_fee'], $data['total_fee'], $other);

        if ($result['return_code'] === 'SUCCESS' && !empty($result['out_trade_no'])) {
            //修改订单为退款中
            $this->appiesModel->where(['order_no' => $result['out_trade_no']])->update(['pay_status' => 3]);

            return ['message' => '操作成功', 'data' => $result];
        }

        return ['message' => $result['err_code_des'], 'data' => $result, 'code' => 201];
    }

    /**
     * 退款通知
     * @throws \EasyWeChat\Kernel\Exceptions\Exception
     */
    public function wechat_funded_notify()
    {
        $app = Factory::payment($this->config());
        $response = $app->handleRefundedNotify(function ($message, $reqInfo, $fail) {
            // $reqInfo 为 message['req_info'] 解密后的信息
            //Log::info($reqInfo);

            return $this->refund_history($reqInfo);

            //return true; // 返回 true 告诉微信“我已处理完成”
            // 或返回错误原因 $fail('参数格式校验错误');
        });

        $response->send();
    }

    /**
     * 记录退款日志
     * @param array $data
     * @return bool
     */
    public function refund_history($data = [])
    {
        try {
            DB::beginTransaction();
            if (!empty($data) && $data['refund_status'] === 'SUCCESS') {
                //修改订单状态
                $up_data = ['pay_status' => 2, 'refund_at' => date('Y-m-d H:i:s')];
                $this->appiesModel->where(['order_no' => $data['out_trade_no']])->update($up_data);

                $time = time();
                $refund = [
                    'refund_id' => $data['refund_id'],
                    'refund_no' => $data['out_refund_no'],
                    'transaction_id' => $data['transaction_id'],
                    'order_no' => $data['out_trade_no'],
                    'total_fee' => $data['total_fee'],
                    'refund_fee' => $data['refund_fee'],
                    'info' => json_encode($data),
                    'created_at' => $time,
                    'updated_at' => $time,
                ];

                //记录退款日志
                $this->refundHistoryModel->create($refund);

                DB::commit();
                return true;
            }

        } catch (\Exception $e) {
            DB::rollBack();
            return false;
        }
    }

}

四、更多内容请参考EasyWeChat官方文档

 类似资料: