ThinkPHP接入PayPal支付

news/2024/12/26 4:43:07 标签: thinkphp, PayPal

ThinkPHP 5接入PayPal 支付,PayPal的流程是服务器请求Paypal的接口下单(需要传订单id/支付成功的重定向地址/支付失败的重定向地址),接会返回一个支付地址,项目服务器把地址返给用户,用户打开链接登录Paypal完成付款,然后Paypal给重定向到指定地址。

在paypal官网开通商户号,设置通知地址。

开通沙箱模式用于测试,后台会给沙箱模式生成商户账号和用户账号,请注意区分。

申请和开通网上有教程不在赘述。

具体实现步骤如下

1.安装包

composer require paypal/rest-api-sdk-php:*

2.具体实现代码

<?php

namespace app\api\controller;

use app\common\controller\Api;
use app\common\model\ShopOrder;
use PayPal\Api\Amount;
use PayPal\Api\Payer;
use PayPal\Api\Payment;
use PayPal\Api\Transaction;
use PayPal\Api\PaymentExecution;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Rest\ApiContext;
use PayPal\Api\RedirectUrls;
use think\Log;
class Paypal extends Api
{
    protected $noNeedLogin = ['*'];
    protected $noNeedRight = ['*'];

    private $apiContext;

    public function __construct()
    {
        parent::__construct();

        // 初始化PayPal API上下文
        $this->apiContext = new ApiContext(
            new OAuthTokenCredential(
                'AV8d**************************N-jbpRvV-K0_dLuEA5d8uodUowab6jdWtM',     // 客户端ID
                'EByrRAncAi*****************************RSqIRA'         // 客户端密钥
            )
        );
        $this->apiContext->setConfig([
            'mode' => 'sandbox',      // 或者 'live'
            // 其他配置...
        ]);
    }

    /**
     * 下单接口
     * @return \think\response\Json|void
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function createPaypalPaymentUrl()
    {
        // 获取前端传递的order_Id
        $orderId = input('post.order_id');

        // 查询订单信息(这里你需要根据自己的数据库结构进行查询)
        // 假设我们得到了一个包含订单详情的数组$orderInfo
        $orderInfo = ShopOrder::where('id', $orderId)->where('status', '1')->find();

        if (empty($orderInfo)) {
            $this->error('订单不存在或不是待支付状态');
        }

        // 设置Payer信息,表明付款人是通过PayPal付款
        $payer = new Payer();
        $payer->setPaymentMethod("paypal");

        // 设置交易金额
        $amount = new Amount();
        $amount->setCurrency("AUD")
            ->setTotal(number_format($orderInfo['actual_money'], 2, '.', ''));

        // 创建Transaction对象,并使用订单ID作为发票号
        $transaction = new Transaction();
        $transaction->setAmount($amount)
            ->setDescription("Slem order pay")  // 描述可选
            ->setInvoiceNumber($orderInfo->order_num);  // 使用订单order_num作为发票号

        // 创建RedirectUrls对象
        $redirectUrls = new RedirectUrls();
        $redirectUrls->setReturnUrl("https://*******.cn/api/paypal/paymentSuccess")  // 支付成功后的回调地址
        ->setCancelUrl("https://********/api/paypal/paymentCancel");  // 用户取消支付后的回调地址

        // 创建Payment对象
        $payment = new Payment();
        $payment->setIntent("sale")
            ->setPayer($payer)
            ->setRedirectUrls($redirectUrls)
            ->setTransactions([$transaction]);

        try {
            // 创建支付请求
            $payment->create($this->apiContext);

            // 获取approval_url,这是用户需要访问来完成支付的URL
            foreach ($payment->getLinks() as $link) {
                if ($link->getRel() == 'approval_url') {
                    // 返回支付链接给客户端
                    return json(['code' => 1, 'data' => $link->getHref()]);
//                    $data = ['status' => 'success', 'approval_url' => $link->getHref()];
//                    $this->success(__('SUccess'),$data);
                }
            }
        } catch (\Exception $ex) {
            // 输出详细的错误信息
            return json([
                'status' => 'error',
                'message' => 'Error creating PayPal payment: ' . $ex->getMessage(),
                'details' => $ex->getTraceAsString(),
                'response' => $payment->toArray()
            ]);
        }
    }

    /**
     * 支付成功跳转的页面
     * 建议前端出个html后台做渲染,本方法只为展示流程
     * @return \think\response\Json
     */
    public function paymentSuccess()
    {
        // 获取PayPal传递过来的参数
        $paymentId = input('get.paymentId');
        $payerId = input('get.PayerID');

        if (empty($paymentId) || empty($payerId)) {
            return json(['status' => 'error', 'message' => 'Missing payment ID or payer ID']);
        }

        try {
            // 获取支付信息
            $payment = Payment::get($paymentId, $this->apiContext);

            // 创建PaymentExecution对象并设置payer_id
            $execution = new PaymentExecution();
            $execution->setPayerId($payerId);

            // 执行支付请求
            $result = $payment->execute($execution, $this->apiContext);

            // 检查支付状态
            if ($result->getState() === 'approved') {
                // 使用发票号(即订单ID)来查找订单
                $invoiceNumber = $payment->getTransactions()[0]->getInvoiceNumber();
                $order = ShopOrder::where('order_num', $invoiceNumber)->find();

                if (!empty($order)) {
                    // 更新订单状态为已支付
                    $order->payment = 'paypal';
                    $order->status = '2';
                    $order->save();

                    // 你可以在这里添加更多的业务逻辑,比如发送确认邮件等

                    // 返回成功信息给前端
                    return json(['status' => 'success', 'message' => 'Payment successful']);
                } else {
                    return json(['status' => 'error', 'message' => 'Order not found']);
                }
            } else {
                return json(['status' => 'error', 'message' => 'Payment not approved']);
            }
        } catch (\Exception $ex) {
            // 错误处理
            Log::error('PayPal Error: ' . $ex->getMessage());
            return json([
                'status' => 'error',
                'message' => 'Error executing payment: ' . $ex->getMessage(),
                'details' => $ex->getTraceAsString()
            ]);
        }
    }

    /**
     * 支付取消跳转的页面
     * @return \think\response\Json
     */
    public function paymentCancel()
    {
        // 获取订单ID或其他相关信息(如果需要)
        $orderId = input('get.order_id'); // 如果PayPal回调包含order_id

        if (!empty($orderId)) {
            try {
                // 根据订单ID查找订单信息
                $order = ShopOrder::where('id', $orderId)->find();

                if (!empty($order)) {
                    // 你可以在这里添加更多的业务逻辑,比如记录取消原因、发送通知等

                    // 更新订单状态为已取消或保持不变,视业务需求而定
                    // 这里假设我们不改变订单状态,仅记录取消事件
                    Log::info("Payment cancelled for order ID: " . $orderId);

                    // 返回取消信息给前端
                    return json(['status' => 'info', 'message' => 'Payment cancelled.']);
                } else {
                    return json(['status' => 'error', 'message' => 'Order not found.']);
                }
            } catch (\Exception $ex) {
                // 错误处理
                Log::error('Error handling payment cancellation: ' . $ex->getMessage());
                return json([
                    'status' => 'error',
                    'message' => 'An error occurred while processing your request.',
                    'details' => $ex->getTraceAsString()
                ]);
            }
        } else {
            // 如果没有提供订单ID,则简单地告知用户支付已被取消
            return json(['status' => 'info', 'message' => 'Payment cancelled.']);
        }
    }
}


http://www.niftyadmin.cn/n/5799813.html

相关文章

项目练习:element-ui的valid表单验证功能用法

文章目录 一、情景说明二、代码实现 一、情景说明 一般表单提交的时候&#xff0c;都要对表单数据进行前段验证。 比如登陆表单提交。 二、代码实现 package.json "element-ui": "2.15.14",main.js 引用ElementUI import ElementUI from element-ui; i…

如何让Tplink路由器自身的IP网段 与交换机和电脑的IP网段 保持一致?

问题分析&#xff1a; 正常情况下&#xff0c;我的需求是&#xff1a;电脑又能上网&#xff0c;又需要与路由器处于同一局域网下&#xff08;串流Pico4 VR眼镜&#xff09;&#xff0c;所以&#xff0c;我是这么连接 交换机、路由器、电脑 的&#xff1a; 此时&#xff0c;登录…

4种使用带有阶段的前后控制图来衡量改进的方法

每个人都有自己喜欢的图形类型或可视化工具。我喜欢的是带有阶段的控制图&#xff0c;有时也被称为前后控制图&#xff08;我们之前写过前后控制图的文章&#xff09;。 简而言之&#xff0c;它们是帮助分析改进前后过程的控制图&#xff0c;不仅监视变化&#xff0c;而且监视…

react中使用ResizeObserver来观察元素的size变化

在 React 中使用 ResizeObserver 来观察元素的大小变化&#xff0c;可以通过创建一个自定义 Hook 来封装 ResizeObserver 的逻辑&#xff0c;并在组件中使用这个 Hook。以下是一个完整的示例&#xff0c;展示了如何在 React 中使用 ResizeObserver 来观察元素的大小变化。 自定…

Detected at node ‘truediv‘ defined at (most recent call last): Node: ‘truediv‘

目录 解决方法&#xff1a; tensorflow.python.framework.errors_impl.InternalError: Graph execution error tensorflow.python.framework.errors_impl.InternalError: Graph execution error: Detected at node truediv defined at (most recent call last): Node: truedi…

使用Grafana中按钮插件实现收发HTTP请求

最近项目中需要监控分布式集群的各项指标信息&#xff0c;需要用到PrometheusGrafana的技术栈实现对分布式集群的各个节点状态进行可视化显示&#xff0c;但是要求前端需要提供一个易用的接口让用户可以触发一些操作&#xff0c;例如负载高时进行负载均衡或弹性伸缩。网上找到的…

springboot481基于springboot社区老人健康信息管理系统(论文+源码)_kaic

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统社区老人健康信息管理系统信息管理难度大&#xff0c;容错…

高并发处理 --- Caffeine内存缓存库

目录 一.什么是Caffeine&#xff1f; 使用场景&#xff1a; 二.如何使用Caffeine&#xff1f; 1.导入依赖&#xff1a; 2.在java项目中使用&#xff1a; 三.对缓存项的驱逐&#xff1a; 1.容量驱逐&#xff08;Maximum Size&#xff09;&#xff1a; 2.过期驱逐&#xff…