hakito/

cakephp-paypal-checkout

CakePHP 的 PayPalCheckout 插件

安装次数: 91

依赖者: 0

建议者: 0

安全: 0

类型:cakephp-plugin

v1.3 2024-01-15 19:47 UTC

This package is auto-updated.

Last update: 2024-09-18 08:11:59 UTC


README

此插件可用于 PayPal Checkout API 的后端部分。PayPal Checkout API

安装

您可以使用 composer 将此插件安装到您的 CakePHP 应用程序中。

安装 composer 包的推荐方式是

composer require hakito/paypal-checkout

加载插件

在您的 plugins.php 中添加

'PayPalCheckout' => [
    'routes' => true
],

配置

在您的 app.php 中您需要设置 api 凭证

'PayPalCheckout' => [
    //'Mode' => 'sandbox', // optional set the current mode
    'ClientId' => 'CLIENT ID',
    'ClientSecret' => 'CLIENT SECRET'
],

对于一些基本的日志记录,您可以将此添加到 Log 部分

'PayPalCheckout' => [
    'className' => FileLog::class,
    'path' => LOGS,
    'file' => 'PayPalCheckout',
    'scopes' => ['PayPalCheckout'],
    'levels' => ['warning', 'error', 'critical', 'alert', 'emergency', 'info'],
]

使用

根据 API 文档实现客户端。示例

<?php

use Cake\Core\Configure;
?>
<script src="https://www.paypal.com/sdk/js?client-id=<?= Configure::read('PayPalCheckout.ClientId') ?>&currency=EUR&locale=de_AT"></script>
<script lang="javascript" type="text/javascript">
window.paypal
  .Buttons({
    style: {
        layout: 'horizontal',
        label: 'buynow'
    },
    async createOrder() {
      try {
        const response = await fetch("/paypal-checkout/Orders/create", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
        });

        const orderData = await response.json();

        if (orderData.abort) // example for handling your own data from the controller
        {
          if (orderData.abort == 'already paid')
          {
            window.location.reload();
          }
          else
            throw new Error(orderData.abort);
        }

        if (orderData.id) {
          return orderData.id;
        } else {
          const errorDetail = orderData?.details?.[0];
          const errorMessage = errorDetail
            ? `${errorDetail.issue} ${errorDetail.description} (${orderData.debug_id})`
            : JSON.stringify(orderData);

          throw new Error(errorMessage);
        }
      } catch (error) {
        console.error(error);
        resultMessage(`Could not initiate PayPal Checkout...<br><br>${error}`);
      }
    },
    async onApprove(data, actions) {
      try {
        const response = await fetch(`/paypal-checkout/Orders/capture/${data.orderID}`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
        });

        const orderData = await response.json();

        // Three cases to handle:
        //   (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
        //   (2) Other non-recoverable errors -> Show a failure message
        //   (3) Successful transaction -> Show confirmation or thank you message

        const errorDetail = orderData?.details?.[0];

        if (errorDetail?.issue === "INSTRUMENT_DECLINED") {
          // (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
          // recoverable state, per https://developer.paypal.com/docs/checkout/standard/customize/handle-funding-failures/
          return actions.restart();
        } else if (errorDetail) {
          // (2) Other non-recoverable errors -> Show a failure message
          throw new Error(`${errorDetail.description} (${orderData.debug_id})`);
        } else if (!orderData.purchase_units) {
          throw new Error(JSON.stringify(orderData));
        } else {
          // (3) Successful transaction -> Show confirmation or thank you message
          // Or go to another URL:  actions.redirect('thank_you.html');
          console.log('redirecting');
          actions.redirect(orderData.redirectSuccessUrl);
        }
      } catch (error) {
        console.error(error);
        resultMessage(
          `Sorry, your transaction could not be processed...<br><br>${error}`,
        );
      }
    },
  })
  .render("#paypal-button-container");

// Example function to show a result to the user. Your site's UI library can be used instead.
function resultMessage(message) {
  const container = document.querySelector("#result-message");
  container.innerHTML = message;
}

</script>

事件处理

您必须在您的应用程序中处理两个事件

EventManager::instance()
    ->on('PayPalCheckout.CreateOrder', OrdersController::createPayPalOrder(...))
    ->on('PayPalCheckout.CaptureOrder', PaymentCallbacks::capturePayPalOrder(...));

创建订单事件

当构建支付请求时触发。您可以在其中设置订单项目和金额。

此函数必须返回一个包含订单数据的数组。您可以自己构建数组或使用 OrderBuilder。

public static function createPayPalOrder(Event $event)
{
    // Optional you can also send back custom data and handle it on the client side
    if (ORDER_ALREADY_AID)
        return ['abort' => ['already paid']];

    $purchaseUnitBuilder = new PurchaseUnitBuilder(new AmountBuilder('USD', '123.56'));
    $purchaseUnitBuilder->ReferenceId('YOUR ORDER'); // optionally set your order id here

    return (new OrderBuilder())
        ->add($purchaseUnitBuilder)
        ->Build();
}

捕获订单事件

当支付完成时触发。

public static function capturePayPalOrder(Event $event, $args)
{
    $data = $event->getData('body');
    if ($data->status != 'COMPLETED')
    {
        Log::error("Captured PayPal checkout payment is not COMPLETED but $data->status");
        return;
    }
    $purchaseUnit = $data->purchase_units[0];
    $orderId = $purchaseUnit->reference_id;
    // Capture ID for issuing refunds
    $captureId = $purchaseUnit->payments->captures[0]->id;
    // DO YOUR PAYMENT HANDLING HERE ($orderId, $captureId);
    return ['redirectSuccessUrl' => 'http://payment-success.example.com'];
}

退款支付

您可以在控制器中完成支付的全额退款。

$this->loadComponent('PayPalCheckout.Checkout', Configure::read('PayPalCheckout'));
$this->Checkout->refundPayment('CAPTURE_ID_OF_YOUR_ORDER');