hakito/ cakephp-paypal-checkout
CakePHP 的 PayPalCheckout 插件
v1.3
2024-01-15 19:47 UTC
Requires
- php: >=8.1
- cakephp/cakephp: ^5.0
- guzzlehttp/guzzle: ^7.4.5
Requires (Dev)
- phpunit/phpunit: ^10.1
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') ?>¤cy=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');