Files
kupshop/class/payments/class.PayPalPlus.php
2025-08-02 16:30:27 +02:00

393 lines
16 KiB
PHP

<?php
declare(strict_types=1);
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use Symfony\Component\Routing\Router;
require_once 'class.PayPal.php';
/**
* Dependencies: `composer require paypal/rest-api-sdk-php=^1.14`
* Example config: $cfg['Modules']['payments']['PayPal'] = [
* 'clientID' => 'client id',
* 'secret' => 'client secret',
* webProfileID - create temporary (3 hours): symfony kupshop:paypal-create-web-profile
* or permanent profile: symfony kupshop:paypal-create-web-profile --permanent
* optional image path as an argument: symfony kupshop:paypal-create-web-profile --permanent templates/images/logo.png
* 'webProfileID' => 'web profile id',
* 'mode' => 'sandbox' OR 'live',
* 'enableLog' => false,
* ].
*/
class PayPalPlus extends PayPal
{
public static $name = 'PayPal Plus platební brána';
protected $templateCart = 'payment.PayPalPlus.cart.tpl';
protected $templateOrderView = 'payment.PayPalPlus.orderView.tpl';
protected $templateInit = 'payment.PayPalPlus.init.tpl';
public $class = 'PayPalPlus';
/** Vytáhne existující, nebo vytvoří novou platbu */
public function getPayment()
{
$cart = ServiceContainer::getService(\KupShop\OrderingBundle\Cart::class);
// Preferuj ID z objednavky
if (isset($this->order) && ($this->order->getData('paypalplus')['id'] ?? false)) {
$payment = $this->getPayPalPayment($this->getApiContext(), $this->order->getData('paypalplus')['id']);
} elseif ($cart->getData('paypalplus') && $cart->getData('paypalplus')['id']) {
$payment = $this->getPayPalPayment($this->getApiContext(), $cart->getData('paypalplus')['id']);
} else {
$payment = $this->createPayPalPayment();
$cart->setData('paypalplus', ['id' => $payment->getId(), 'token' => $payment->getToken()]);
}
return $payment;
}
public function getPaymentUrl()
{
if (empty($this->order)) {
throw new \KupShop\OrderingBundle\Exception\PaymentException(translate('sameDevicePayment', 'payment'));
}
return path('kupshop_content_orders_order',
['id' => $this->order->id, 'cf' => $this->order->getSecurityCode(), 'status' => 1, 'immediate_pay' => 1]);
}
/** Pošle patch request pokud není platba aktualizovana - nema security code */
public function makeSurePaymentIsUpdated()
{
$payment = $this->getPayment();
if (empty($payment->getTransactions()[0]->getCustom())) {
return $this->updatePayment();
}
return $payment;
}
/** Vytvoří payment s dummy daty - protože se musí tvořit už v košíku */
protected function createPayPalPayment($useOriginalCurrency = true, $totalPrice = 0): PayPal\Api\Payment
{
$apiContext = $this->getApiContext();
$dbcfg = Settings::getDefault();
if ($this->order && $remainingPayment = roundPrice($this->order->getRemainingPayment())->asFloat() <= 0) {
$this->success(translate('paymentSuccess', 'payment'));
}
$payer = new \PayPal\Api\Payer();
$payer->setPaymentMethod('paypal');
$amount = new \PayPal\Api\Amount();
$amount->setCurrency('EUR')->setTotal('2.00');
$item1 = new \PayPal\Api\Item();
$item1->setName('Payment for order '.($this->order ? $this->order->order_no : '').' on '.$dbcfg->shop_firm_name)
->setCurrency('EUR')
->setQuantity(1)
->setPrice($totalPrice);
$itemList = new \PayPal\Api\ItemList();
$itemList->setItems([$item1]);
$transaction = new \PayPal\Api\Transaction();
$transaction->setAmount($amount)
->setDescription('Payment for order')
->setInvoiceNumber('123456')
->setItemList($itemList);
$redirectUrls = new \PayPal\Api\RedirectUrls();
$redirectUrls->setReturnUrl(
path('kupshop_ordering_payment_legacypayment',
array_merge([
'step' => 2,
'class' => $this->class,
], []),
Router::ABSOLUTE_URL)
)->setCancelUrl(
path('kupshop_ordering_payment_legacypayment',
array_merge([
'step' => 4,
'class' => $this->class,
], []),
Router::ABSOLUTE_URL)
);
$payment = new \PayPal\Api\Payment();
$payment->setIntent('sale')
->setPayer($payer)
->setTransactions([$transaction])
->setRedirectUrls($redirectUrls);
try {
$payment->create($apiContext);
} catch (PayPal\Exception\PayPalConnectionException $e) {
$data = $e->getData();
if ($data) {
$data = json_decode($data, true);
}
if (($data['name'] ?? false) === 'VALIDATION_ERROR') {
$this->error(translate('PayPalConnectionException', 'paypal'));
ServiceContainer::getService('logger')->error('PayPal exception: '.print_r($e->getData(), true),
['file' => __FILE__, 'line' => __LINE__]);
} else {
throw $e;
}
}
return $payment;
}
/** Funkce pro patch paymentu reálnými daty */
public function updatePayment(): PayPal\Api\Payment
{
$payment = $this->getPayment();
if ($useOriginalCurrency = in_array($this->order->currency, $this->allowedCurrencies)) {
$totalPrice = roundPrice($this->order->getRemainingPayment())->asFloat();
} else {
$totalPrice = $this->order->convertPriceToEUR(
roundPrice($this->order->getRemainingPayment())->asFloat()
);
}
$patchAmount = new \PayPal\Api\Patch();
$patchAddress = new \PayPal\Api\Patch();
$patchPayer = new \PayPal\Api\Patch();
$patchItems = new \PayPal\Api\Patch();
$patchCustom = new \PayPal\Api\Patch();
$patchInvoiceNo = new \PayPal\Api\Patch();
$amount = new \PayPal\Api\Amount();
$amount->setCurrency($useOriginalCurrency ? ($this->order ? $this->order->currency : '') : 'EUR')
->setTotal($totalPrice);
$patchAmount->setOp('replace')
->setPath('/transactions/0/amount')
->setValue($amount);
$payerDeliveryAddress = new \PayPal\Api\ShippingAddress();
$payerDeliveryAddress->setState($this->order->delivery_state ?? '')
->setCountryCode($this->order->delivery_country ?? '')
->setCity($this->order->delivery_city ?? '')
->setLine1($this->order->delivery_street ?? '')
->setLine2($this->order->delivery_custom_address ?? '')
->setPostalCode($this->order->delivery_zip ?? '')
->setRecipientName($this->order->delivery_name.' '.$this->order->delivery_surname);
$patchAddress->setOp('add')
->setPath('/transactions/0/item_list/shipping_address')
->setValue($payerDeliveryAddress);
$payerInfoAddress = new \PayPal\Api\Address();
$payerInfoAddress->setState($this->order->invoice_state ?? '')
->setCountryCode($this->order->invoice_country ?? '')
->setCity($this->order->invoice_city ?? '')
->setLine1($this->order->invoice_street ?? '')
->setLine2($this->order->invoice_custom_address ?? '')
->setPostalCode($this->order->invoice_zip ?? '');
$payerInfo = new \PayPal\Api\PayerInfo();
$payerInfo->setEmail($this->order ? $this->order->getUserEmail() : '')
->setBillingAddress($payerInfoAddress)
->setFirstName($this->order->invoice_name ?? '')
->setLastName($this->order->invoice_surname ?? '');
$patchPayer->setOp('add')
->setPath('/payer/payer_info')
->setValue($payerInfo);
$items = [];
$itemList = new \PayPal\Api\ItemList();
foreach ($this->order->fetchItems() as $orderItem) {
$item = new \PayPal\Api\Item();
$item->setName($orderItem['descr'])
->setCurrency($useOriginalCurrency ? $this->order->currency ?? '' : 'EUR')
->setQuantity($orderItem['pieces'])
->setPrice($orderItem['piece_price']['value_with_vat']->asFloat());
$items[] = $item;
}
$itemList->setItems($items);
$patchItems->setOp('replace')
->setPath('/transactions/0/item_list')
->setValue($itemList);
$patchCustom->setOp('add')
->setPath('/transactions/0/custom')
->setValue($this->order->getSecurityCode());
$patchInvoiceNo->setOp('add')
->setPath('/transactions/0/invoice_number')
->setValue($this->order->order_no);
$patchRequest = new \PayPal\Api\PatchRequest();
$patchRequest->setPatches([
$patchAddress,
$patchAmount,
$patchPayer,
$patchItems,
$patchCustom,
$patchInvoiceNo,
]);
try {
$payment->update($patchRequest, $this->getApiContext());
} catch (Exception $ex) {
ServiceContainer::getService('logger')->error('PayPal exception: '.$ex->getMessage(), ['file' => __FILE__, 'line' => __LINE__]);
if ($ex instanceof \PayPal\Exception\PayPalConnectionException) {
ServiceContainer::getService('logger')->error('PayPal exception: '.print_r($ex->getData(), true),
['file' => __FILE__, 'line' => __LINE__]);
}
$this->error('storno');
}
return $payment;
}
public function processStep_1()
{
// $this->error('');
}
public function processStep_2()
{
ServiceContainer::getService('logger')->error('PayPal response: '.print_r([$_GET, $_POST, $_SERVER], true),
['file' => __FILE__, 'line' => __LINE__]);
$payPalPaymentID = getVal('paymentId', null, false);
$apiContext = $this->getApiContext();
$payment = $this->getPayPalPayment($apiContext, $payPalPaymentID);
$error = false;
if ($payment) {
$this->orderId = $this->selectSQL('orders',
['order_no' => $payment->getTransactions()[0]->getInvoiceNumber()],
['id'])->fetchOne();
$this->order = new Order();
$this->order->createFromDB($this->orderId);
if ($this->order->getSecurityCode() == $payment->getTransactions()[0]->getCustom()) {
// Při platbě paypal umožňuje změnit adresu doručení....
// $address = $payment->getTransactions()[0]->getItemList()->getShippingAddress()->toArray();
// $address = $payment->toArray();
// if (isset($address['payer']['payer_info']['first_name']) && isset($address['payer']['payer_info']['last_name'])) {
// // $this->order->updateSQL('orders', [
// // 'delivery_name' => $address['payer']['payer_info']['first_name'],
// // 'delivery_surname' => $address['payer']['payer_info']['last_name'],
// // 'delivery_street' => $address['payer']['payer_info']['shipping_address']['line1'],
// // 'delivery_city' => $address['payer']['payer_info']['shipping_address']['city'],
// // 'delivery_zip' => $address['payer']['payer_info']['shipping_address']['postal_code'],
// // 'delivery_country' => $address['payer']['payer_info']['shipping_address']['country_code'],
// // 'delivery_phone' => $address['payer']['payer_info']['phone'] ?? '',
// // 'delivery_state' => $address['payer']['payer_info']['shipping_address']['state'],
// // ], ['id' => $this->orderId]);
//
// $name = explode(' ', $address['recipient_name']);
// $firstname = $name[0] ?? '';
// unset($name[0]);
// $lastname = implode(' ', $name);
// $this->order->updateSQL('orders', [
// 'delivery_name' => $firstname,
// 'delivery_surname' => $lastname ?? '',
// 'delivery_street' => $address['line1'],
// 'delivery_city' => $address['city'],
// 'delivery_zip' => $address['postal_code'],
// 'delivery_country' => $address['country_code'],
// 'delivery_phone' => $address['payer']['payer_info']['phone'] ?? '',
// 'delivery_state' => $address['payer']['payer_info']['shipping_address']['state'],
// ], ['id' => $this->orderId]);
// }
$this->createPayment(
$payment->getId(),
$payment->getTransactions()[0]->getAmount()->getTotal(),
['paymentClass' => self::class]
);
$this->processPayment($payment);
$paymentInstructions = $payment->getPaymentInstruction();
if ($paymentInstructions) {
$paypalData = $this->order->getData('paypalplus');
$paypalData['payment_instruction'] = $paymentInstructions->toArray();
$this->order->setData('paypalplus', $paypalData);
}
$this->step(3, 'wait', ['paymentId' => $payment->getId()]);
} else {
$error = true;
}
} else {
$error = true;
}
if ($error) {
$this->error(translate('payment_status_check_error', 'payment'));
}
}
public function processStep_3()
{
$apiContext = $this->getApiContext();
$payPalPaymentID = getVal('paymentId', null, false);
if (!$payPalPaymentID) {
$kupshopPayment = $this->getPendingPayment();
if ($kupshopPayment) {
// use already created payment
$payPalPaymentID = $kupshopPayment['decoded_data']->session;
} else {
$this->error('storno');
}
}
$this->processPayment($this->getPayPalPayment($apiContext, $payPalPaymentID));
}
public function processStep_4()
{
$token = getVal('token', null, false);
if (!$token) {
$this->error('storno');
}
$payPalId = sqlQueryBuilder()
->select("id, json_extract(note_admin, '$.paypalplus.id') as paypalID")
->from('orders')
->where("json_extract(note_admin, '$.paypalplus.token') = :token")
->setParameter('token', $token)
->execute()
->fetchAssociative();
if (!$payPalId) {
$this->error('storno');
}
$this->orderId = $payPalId['id'];
$this->order = new \Order();
$this->order->createFromDB($this->orderId);
$this->error(translate('payment_storno', 'payment'));
}
public static function getSettingsConfiguration(): array
{
return [
'fields' => [
'clientID' => [
'title' => 'clientID',
'type' => 'text',
],
'secret' => [
'title' => 'secret',
'type' => 'text',
],
'mode' => [
'title' => 'Režim',
'type' => 'select',
'options' => ['live' => 'Produkční režim', 'sandbox' => 'Testovaci režim'],
],
],
];
}
}