Skip to content

Commit d41c65c

Browse files
committed
Implemented PayPal Checkout payment method
1 parent ad112ad commit d41c65c

File tree

7 files changed

+264
-108
lines changed

7 files changed

+264
-108
lines changed

system/modules/isotope/config/config.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@
194194
\Isotope\Model\Payment::registerModelType('payone', 'Isotope\Model\Payment\Payone');
195195
\Isotope\Model\Payment::registerModelType('paypal', 'Isotope\Model\Payment\Paypal');
196196
\Isotope\Model\Payment::registerModelType('paypal_plus', 'Isotope\Model\Payment\PaypalPlus');
197+
\Isotope\Model\Payment::registerModelType('paypal_checkout', 'Isotope\Model\Payment\PaypalCheckout');
197198
\Isotope\Model\Payment::registerModelType('postfinance', 'Isotope\Model\Payment\Postfinance');
198199
\Isotope\Model\Payment::registerModelType('quickpay', 'Isotope\Model\Payment\QuickPay');
199200
\Isotope\Model\Payment::registerModelType('saferpay', 'Isotope\Model\Payment\Saferpay');

system/modules/isotope/dca/tl_iso_payment.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
'paybyway' => '{type_legend},name,label,type;{note_legend:hide},note;{config_legend},new_order_status,quantity_mode,minimum_quantity,maximum_quantity,minimum_total,maximum_total,countries,shipping_modules,product_types,product_types_condition,config_ids;{gateway_legend},paybyway_merchant_id,paybyway_private_key;{price_legend:hide},price,tax_class;{expert_legend:hide},guests,protected;{enabled_legend},enabled,debug,logging',
104104
'paypal' => '{type_legend},name,label,type;{note_legend:hide},note;{config_legend},new_order_status,quantity_mode,minimum_quantity,maximum_quantity,minimum_total,maximum_total,countries,shipping_modules,product_types,product_types_condition,config_ids;{gateway_legend},paypal_account;{price_legend:hide},price,tax_class;{expert_legend:hide},guests,protected;{enabled_legend},enabled,debug,logging',
105105
'paypal_plus' => '{type_legend},name,label,type;{note_legend:hide},note;{config_legend},new_order_status,quantity_mode,minimum_quantity,maximum_quantity,minimum_total,maximum_total,countries,shipping_modules,product_types,product_types_condition,config_ids;{gateway_legend},paypal_client,paypal_secret;{price_legend:hide},price,tax_class;{expert_legend:hide},guests,protected;{enabled_legend},enabled,debug,logging',
106+
'paypal_checkout' => '{type_legend},name,label,type;{note_legend:hide},note;{config_legend},new_order_status,quantity_mode,minimum_quantity,maximum_quantity,minimum_total,maximum_total,countries,shipping_modules,product_types,product_types_condition,config_ids;{gateway_legend},paypal_client,paypal_secret;{price_legend:hide},price,tax_class;{expert_legend:hide},guests,protected;{enabled_legend},enabled,debug,logging',
106107
'postfinance' => '{type_legend},name,label,type;{note_legend:hide},note;{config_legend},new_order_status,quantity_mode,minimum_quantity,maximum_quantity,minimum_total,maximum_total,countries,shipping_modules,product_types,product_types_condition,config_ids;{gateway_legend},psp_pspid,psp_http_method,psp_hash_method,psp_hash_in,psp_hash_out,psp_dynamic_template,psp_payment_method;{price_legend:hide},price,tax_class;{expert_legend:hide},guests,protected;{enabled_legend},enabled,debug,logging',
107108
'viveum' => '{type_legend},name,label,type;{note_legend:hide},note;{config_legend},new_order_status,quantity_mode,minimum_quantity,maximum_quantity,minimum_total,maximum_total,countries,shipping_modules,product_types,product_types_condition,config_ids;{gateway_legend},psp_pspid,psp_http_method,psp_hash_method,psp_hash_in,psp_hash_out,psp_dynamic_template,psp_payment_method;{price_legend:hide},price,tax_class;{expert_legend:hide},guests,protected;{enabled_legend},enabled,debug,logging',
108109
'datatrans' => '{type_legend},name,label,type;{note_legend:hide},note;{config_legend},new_order_status,trans_type,quantity_mode,minimum_quantity,maximum_quantity,minimum_total,maximum_total,countries,shipping_modules,product_types,product_types_condition,config_ids;{gateway_legend},datatrans_id,datatrans_sign,datatrans_hash_method,datatrans_hash_convert;{price_legend:hide},price,tax_class;{expert_legend:hide},guests,protected;{enabled_legend},enabled,debug,logging',

system/modules/isotope/languages/en/default.xlf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,12 @@
710710
<trans-unit id="MODEL.tl_iso_payment.paypal_plus.1">
711711
<source>Paypal PLUS is currently only available in Germany.</source>
712712
</trans-unit>
713+
<trans-unit id="MODEL.tl_iso_payment.paypal_checkout.0">
714+
<source>PayPal Checkout</source>
715+
</trans-unit>
716+
<trans-unit id="MODEL.tl_iso_payment.paypal_checkout.1">
717+
<source>Checkout is the latest PayPal payment method.</source>
718+
</trans-unit>
713719
<trans-unit id="MODEL.tl_iso_payment.postfinance.0">
714720
<source>PostFinance</source>
715721
</trans-unit>

system/modules/isotope/library/Isotope/Model/Payment/PaypalApi.php

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<?php
22

3+
/**
4+
* @noinspection PhpPossiblePolymorphicInvocationInspection
5+
* @noinspection UnsupportedStringOffsetOperationsInspection
6+
*/
7+
38
/*
49
* Isotope eCommerce for Contao Open Source CMS
510
*
@@ -11,11 +16,13 @@
1116

1217
namespace Isotope\Model\Payment;
1318

19+
use Contao\Date;
1420
use Contao\Environment;
1521
use Contao\StringUtil;
1622
use Isotope\Interfaces\IsotopeProductCollection;
1723
use Isotope\Interfaces\IsotopePurchasableCollection;
1824
use Isotope\Model\Payment;
25+
use Isotope\Model\ProductCollection\Order;
1926
use Isotope\Module\Checkout;
2027
use Symfony\Component\HttpClient\HttpClient;
2128
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
@@ -227,6 +234,109 @@ protected function storeHistory(IsotopeProductCollection $collection, array $pay
227234
$collection->save();
228235
}
229236

237+
/**
238+
* {@inheritdoc}
239+
*/
240+
public function backendInterface($orderId)
241+
{
242+
if (($objOrder = Order::findByPk($orderId)) === null) {
243+
return parent::backendInterface($orderId);
244+
}
245+
246+
$arrPayment = StringUtil::deserialize($objOrder->payment_data, true);
247+
248+
if (!\is_array($arrPayment['PAYPAL_HISTORY']) || empty($arrPayment['PAYPAL_HISTORY'])) {
249+
return parent::backendInterface($orderId);
250+
}
251+
252+
$strBuffer = '
253+
<div id="tl_buttons">
254+
<a href="' . ampersand(str_replace('&key=payment', '', Environment::get('request'))) . '" class="header_back" title="' . StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['backBT']) . '">' . $GLOBALS['TL_LANG']['MSC']['backBT'] . '</a>
255+
</div>';
256+
257+
foreach ($arrPayment['PAYPAL_HISTORY'] as $response) {
258+
if ($response['intent'] === 'sale'
259+
&& $response['state'] === 'approved'
260+
&& isset($response['transactions'][0]['related_resources'][0]['sale']['id'])
261+
) {
262+
$saleId = $response['transactions'][0]['related_resources'][0]['sale']['id'];
263+
264+
$strBuffer .= '
265+
<div class="maintenance_inactive">
266+
<h2 class="sub_headline">' . $this->name . ' (' . $GLOBALS['TL_LANG']['MODEL']['tl_iso_payment'][$this->type][0] . ')' . '</h2>
267+
<div class="tl_tbox">
268+
<p><strong>' . sprintf($GLOBALS['TL_LANG']['MSC']['paypalTransaction'], $saleId) . '</strong></p>
269+
<p>' . $GLOBALS['TL_LANG']['MSC']['paypalTransactionOnline'] .'</p>
270+
<a class="tl_submit" href="https://www.paypal.com/activity/payment/' . $saleId . '" target="_blank">' . $GLOBALS['TL_LANG']['MSC']['paypalTransactionButton'] . '</a>
271+
</div>
272+
</div>';
273+
274+
break;
275+
}
276+
}
277+
278+
foreach (array_reverse($arrPayment['PAYPAL_HISTORY']) as $transaction) {
279+
if (isset($transaction['create_time'])) {
280+
$dateCreated = Date::parse(
281+
$GLOBALS['TL_CONFIG']['datimFormat'],
282+
strtotime($transaction['create_time'])
283+
);
284+
} else {
285+
$dateCreated = '<i>UNKNOWN</i>';
286+
}
287+
288+
$strBuffer .= '
289+
<div class="maintenance_inactive">
290+
<h2 class="sub_headline">' . sprintf($GLOBALS['TL_LANG']['MSC']['paypalTransactionDetails'], $dateCreated) . '</h2>
291+
<table class="tl_show">
292+
<tbody>
293+
';
294+
295+
$render = function($k, $v, &$i) use (&$strBuffer) {
296+
$strBuffer .= '
297+
<tr>
298+
<td' . ($i % 2 ? '' : ' class="tl_bg"') . ' style="width:auto"><span class="tl_label">' . $k . ': </span></td>
299+
<td' . ($i % 2 ? '' : ' class="tl_bg"') . '>' . $v . '</td>
300+
</tr>';
301+
302+
++$i;
303+
};
304+
305+
$loop = function($data, $loop, $i=0) use ($render, &$strBuffer) {
306+
foreach ($data as $k => $v) {
307+
if (\in_array($k, ['potential_payer_info', 'links', 'create_time'], true)) {
308+
continue;
309+
}
310+
311+
if (\is_array($v)) {
312+
$strBuffer .= '
313+
<tr>
314+
<td' . ($i % 2 ? '' : ' class="tl_bg"') . ' style="width:auto"><span class="tl_label">' . $k . ': </span></td>
315+
<td' . ($i % 2 ? '' : ' class="tl_bg"') . '>
316+
<table class="tl_show" style="border:1px solid #d0d0d2; background:#fff"><tbody>';
317+
318+
$i++;
319+
$loop($v, $loop, (int) $i % 2);
320+
321+
$strBuffer .= '</td></tbody></table></tr>';
322+
323+
continue;
324+
}
325+
326+
$render($k, $v, $i);
327+
}
328+
};
329+
330+
$loop($transaction, $loop);
331+
332+
$strBuffer .= '
333+
</tbody></table>
334+
</div>';
335+
}
336+
337+
return $strBuffer;
338+
}
339+
230340
/**
231341
* @return array|null
232342
*/
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
/*
4+
* Isotope eCommerce for Contao Open Source CMS
5+
*
6+
* Copyright (C) 2009 - 2019 terminal42 gmbh & Isotope eCommerce Workgroup
7+
*
8+
* @link https://isotopeecommerce.org
9+
* @license https://opensource.org/licenses/lgpl-3.0.html
10+
*/
11+
12+
namespace Isotope\Model\Payment;
13+
14+
use Contao\Input;
15+
use Contao\Module;
16+
use Contao\System;
17+
use Haste\Util\Url;
18+
use Isotope\Interfaces\IsotopeProductCollection;
19+
use Isotope\Interfaces\IsotopePurchasableCollection;
20+
use Isotope\Module\Checkout;
21+
use Isotope\Template;
22+
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
23+
24+
class PaypalCheckout extends PaypalApi
25+
{
26+
public function isAvailable(): bool
27+
{
28+
if (!in_array($this->currency, ['AUD', 'BRL', 'CAD', 'CZK', 'DKK', 'EUR', 'HKD', 'HUF', 'ILS', 'JPY', 'MYR', 'MXN', 'TWD', 'NZD', 'NOK', 'PHP', 'PLN', 'GBP', 'RUB', 'SGD', 'SEK', 'CHF', 'THB', 'USD'])) {
29+
return false;
30+
}
31+
32+
return parent::isAvailable();
33+
}
34+
35+
public function checkoutForm(IsotopeProductCollection $objOrder, \Module $objModule)
36+
{
37+
if (!$objOrder instanceof IsotopePurchasableCollection) {
38+
System::log('Product collection ID "' . $objOrder->getId() . '" is not purchasable', __METHOD__, TL_ERROR);
39+
Checkout::redirectToStep(Checkout::STEP_COMPLETE, $objOrder);
40+
}
41+
42+
try {
43+
$response = $this->createPayment($objOrder);
44+
} catch (TransportExceptionInterface $e) {
45+
System::log('PayPayl payment failed. See paypal.log for more information.', __METHOD__, TL_ERROR);
46+
Checkout::redirectToStep(Checkout::STEP_FAILED);
47+
}
48+
49+
$this->debugLog($response->getContent(false));
50+
51+
if (201 === $response->getStatusCode()) {
52+
$paypalData = $response->toArray();
53+
$this->storePayment($objOrder, $paypalData);
54+
$this->storeHistory($objOrder, $paypalData);
55+
56+
$this->patchPayment($objOrder, $paypalData['id']);
57+
58+
foreach ($paypalData['links'] as $link) {
59+
if ('approval_url' === $link['rel']) {
60+
$template = new Template('iso_payment_paypal_paypal');
61+
$template->setData($this->arrData);
62+
63+
$template->client_id = $this->paypal_client;
64+
$template->currency = $objOrder->getCurrency();
65+
66+
parse_str(parse_url($link['href'], PHP_URL_QUERY), $params);
67+
$template->token = $params['token'];
68+
69+
$successUrl = Checkout::generateUrlForStep(Checkout::STEP_COMPLETE, $objOrder, null, true);
70+
$successUrl = Url::addQueryString('paymentID=__paymentID__', $successUrl);
71+
$successUrl = Url::addQueryString('payerID=__payerID__', $successUrl);
72+
$template->success_url = $successUrl;
73+
74+
$template->cancel_url = Checkout::generateUrlForStep(Checkout::STEP_FAILED, null, null, true);
75+
76+
return $template->parse();
77+
}
78+
}
79+
}
80+
81+
return false;
82+
}
83+
84+
/**
85+
* @inheritdoc
86+
*/
87+
public function processPayment(IsotopeProductCollection $objOrder, Module $objModule)
88+
{
89+
if (!$objOrder instanceof IsotopePurchasableCollection) {
90+
System::log('Product collection ID "' . $objOrder->getId() . '" is not purchasable', __METHOD__, TL_ERROR);
91+
return false;
92+
}
93+
94+
$paypalData = $this->retrievePayment($objOrder);
95+
96+
if (0 === \count($paypalData)
97+
|| Input::get('paymentID') !== $paypalData['id']
98+
|| 'created' !== $paypalData['state']
99+
) {
100+
return false;
101+
}
102+
103+
try {
104+
$response = $this->executePayment($paypalData['id'], Input::get('payerID'));
105+
} catch (TransportExceptionInterface $e) {
106+
return false;
107+
}
108+
109+
$this->debugLog($response->getContent(false));
110+
111+
if (200 !== $response->getStatusCode()) {
112+
return false;
113+
}
114+
115+
$this->storeHistory($objOrder, $response->toArray());
116+
117+
$objOrder->checkout();
118+
$objOrder->setDatePaid(time());
119+
$objOrder->updateOrderStatus($this->new_order_status);
120+
$objOrder->save();
121+
122+
return true;
123+
}
124+
}

0 commit comments

Comments
 (0)