Skip to content

Commit 039b6b1

Browse files
authored
Merge pull request #1151 from mollie/PIPRES-539/captures-api
PIPRES-539 captures api
2 parents e41b97f + 271b280 commit 039b6b1

File tree

190 files changed

+1749
-70483
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

190 files changed

+1749
-70483
lines changed

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,8 @@ run-ps-unit-tests:
103103

104104
create-env:
105105
echo "SENTRY_ENV='$(env)'" > .env
106+
107+
rebuild-js:
108+
cd views/js && npm i --legacy-peer-deps
109+
cd views/js && npm install html-webpack-plugin@4.5.2 --legacy-peer-deps
110+
cd views/js && npm run build

controllers/admin/AdminMollieAjaxController.php

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,16 @@
1616
use Mollie\Provider\CreditCardLogoProvider;
1717
use Mollie\Provider\TaxCalculatorProvider;
1818
use Mollie\Repository\PaymentMethodRepository;
19+
use Mollie\Repository\PaymentMethodRepositoryInterface;
20+
use Mollie\Service\ApiService;
21+
use Mollie\Service\CancelService;
22+
use Mollie\Service\CaptureService;
1923
use Mollie\Service\MolliePaymentMailService;
24+
use Mollie\Service\RefundService;
25+
use Mollie\Service\ShipService;
2026
use Mollie\Utility\NumberUtility;
2127
use Mollie\Utility\TimeUtility;
28+
use Mollie\Utility\TransactionUtility;
2229

2330
if (!defined('_PS_VERSION_')) {
2431
exit;
@@ -31,6 +38,10 @@ class AdminMollieAjaxController extends ModuleAdminController
3138

3239
public function postProcess()
3340
{
41+
if (!Tools::isSubmit('ajax')) {
42+
return;
43+
}
44+
3445
$action = Tools::getValue('action');
3546

3647
$this->context->smarty->assign('bootstrap', true);
@@ -54,6 +65,25 @@ public function postProcess()
5465
case 'updateFixedPaymentFeePrice':
5566
$this->updateFixedPaymentFeePrice();
5667
break;
68+
case 'refundAll':
69+
case 'refund':
70+
$this->processRefund();
71+
break;
72+
case 'shipAll':
73+
case 'ship':
74+
$this->processShip();
75+
break;
76+
case 'captureAll':
77+
case 'capture':
78+
$this->processCapture();
79+
break;
80+
case 'cancelAll':
81+
case 'cancel':
82+
$this->processCancel();
83+
break;
84+
case 'retrieve':
85+
$this->retrieveOrderInfo();
86+
break;
5787
default:
5888
break;
5989
}
@@ -213,4 +243,202 @@ private function updateFixedPaymentFeePrice(): void
213243
])
214244
);
215245
}
246+
247+
private function processRefund(): void
248+
{
249+
try {
250+
$transactionId = Tools::getValue('transactionId');
251+
$refundAmount = (float) Tools::getValue('refundAmount') ?: null;
252+
$orderLineId = Tools::getValue('orderline') ?: null;
253+
254+
/** @var RefundService $refundService */
255+
$refundService = $this->module->getService(RefundService::class);
256+
257+
$status = $refundService->handleRefund($transactionId, $refundAmount, $orderLineId);
258+
259+
$this->ajaxRender(json_encode($status));
260+
} catch (\Throwable $e) {
261+
$this->ajaxRender(
262+
json_encode([
263+
'success' => false,
264+
'message' => $this->module->l('An error occurred while processing the request.'),
265+
'error' => $e->getMessage(),
266+
])
267+
);
268+
}
269+
}
270+
271+
private function processShip(): void
272+
{
273+
$orderId = (int) Tools::getValue('orderId');
274+
$orderLines = Tools::getValue('orderLines') ?: [];
275+
$tracking = Tools::getValue('tracking');
276+
$orderlineId = Tools::getValue('orderline');
277+
278+
try {
279+
$order = new Order($orderId);
280+
281+
/** @var PaymentMethodRepositoryInterface $paymentMethodRepo */
282+
$paymentMethodRepo = $this->module->getService(PaymentMethodRepositoryInterface::class);
283+
$transactionId = $paymentMethodRepo->getPaymentBy('order_id', (string) $orderId)['transaction_id'];
284+
285+
/** @var ShipService $shipService */
286+
$shipService = $this->module->getService(ShipService::class);
287+
$status = $shipService->handleShip($transactionId, $orderlineId, $tracking);
288+
289+
$this->ajaxRender(json_encode($status));
290+
} catch (\Throwable $e) {
291+
$this->ajaxRender(
292+
json_encode([
293+
'success' => false,
294+
'message' => $this->module->l('An error occurred while processing the request.'),
295+
'error' => $e->getMessage(),
296+
])
297+
);
298+
}
299+
}
300+
301+
private function processCapture(): void
302+
{
303+
$orderId = (int) Tools::getValue('orderId');
304+
$captureAmount = Tools::getValue('captureAmount') ?: null;
305+
306+
try {
307+
$order = new Order($orderId);
308+
309+
/** @var PaymentMethodRepositoryInterface $paymentMethodRepo */
310+
$paymentMethodRepo = $this->module->getService(PaymentMethodRepositoryInterface::class);
311+
$transactionId = $paymentMethodRepo->getPaymentBy('order_id', (string) $orderId)['transaction_id'];
312+
313+
/** @var CaptureService $captureService */
314+
$captureService = $this->module->getService(CaptureService::class);
315+
316+
// Use provided amount for individual product capture, or full order amount for capture all
317+
$amount = $captureAmount ? (float) $captureAmount : $order->total_paid;
318+
$status = $captureService->handleCapture($transactionId, $amount);
319+
320+
$this->ajaxRender(json_encode($status));
321+
} catch (\Throwable $e) {
322+
$this->ajaxRender(
323+
json_encode([
324+
'success' => false,
325+
'message' => $this->module->l('An error occurred while processing the request.'),
326+
'error' => $e->getMessage(),
327+
])
328+
);
329+
}
330+
}
331+
332+
private function processCancel(): void
333+
{
334+
$orderId = (int) Tools::getValue('orderId');
335+
$orderlineId = Tools::getValue('orderline') ?: null;
336+
337+
try {
338+
$order = new Order($orderId);
339+
340+
/** @var PaymentMethodRepositoryInterface $paymentMethodRepo */
341+
$paymentMethodRepo = $this->module->getService(PaymentMethodRepositoryInterface::class);
342+
$transactionId = $paymentMethodRepo->getPaymentBy('order_id', (string) $orderId)['transaction_id'];
343+
344+
/** @var CancelService $cancelService */
345+
$cancelService = $this->module->getService(CancelService::class);
346+
$status = $cancelService->handleCancel($transactionId, $orderlineId);
347+
348+
$this->ajaxRender(json_encode($status));
349+
} catch (\Throwable $e) {
350+
$this->ajaxRender(
351+
json_encode([
352+
'success' => false,
353+
'message' => $this->module->l('An error occurred while processing the request.'),
354+
'error' => $e->getMessage(),
355+
])
356+
);
357+
}
358+
}
359+
360+
private function retrieveOrderInfo(): void
361+
{
362+
$orderId = (int) Tools::getValue('orderId');
363+
364+
try {
365+
$order = new Order($orderId);
366+
367+
/** @var PaymentMethodRepositoryInterface $paymentMethodRepo */
368+
$paymentMethodRepo = $this->module->getService(PaymentMethodRepositoryInterface::class);
369+
$transaction = $paymentMethodRepo->getPaymentBy('order_id', (string) $orderId);
370+
371+
if (!$transaction) {
372+
$this->ajaxRender(
373+
json_encode([
374+
'success' => false,
375+
'message' => $this->module->l('No Mollie transaction found for this order.'),
376+
])
377+
);
378+
379+
return;
380+
}
381+
382+
$transactionId = $transaction['transaction_id'];
383+
$this->module->updateApiKey((int) $order->id_shop);
384+
385+
if (!$this->module->getApiClient()) {
386+
$this->ajaxRender(
387+
json_encode([
388+
'success' => false,
389+
'message' => $this->module->l('Unable to connect to Mollie API.'),
390+
])
391+
);
392+
393+
return;
394+
}
395+
396+
/** @var ApiService $apiService */
397+
$apiService = $this->module->getService(ApiService::class);
398+
399+
$mollieApiType = TransactionUtility::isOrderTransaction($transactionId) ? 'orders' : 'payments';
400+
401+
if ($mollieApiType === 'orders') {
402+
$orderInfo = $this->module->getApiClient()->orders->get($transactionId);
403+
$isShipping = $orderInfo->status === 'completed';
404+
$isCaptured = $orderInfo->isPaid();
405+
$isRefunded = $orderInfo->amountRefunded->value > 0;
406+
$isCanceled = $orderInfo->status === 'canceled';
407+
408+
$response = [
409+
'success' => true,
410+
'isShipping' => $isShipping,
411+
'isCaptured' => $isCaptured,
412+
'isRefunded' => $isRefunded,
413+
'isCanceled' => $isCanceled,
414+
'orderStatus' => $orderInfo->status ?? null,
415+
];
416+
} else {
417+
$paymentInfo = $this->module->getApiClient()->payments->get($transactionId);
418+
$isShipping = false;
419+
$isCaptured = false;
420+
421+
$isCaptured = $paymentInfo->isPaid();
422+
$isRefunded = $paymentInfo->amountRefunded->value > 0;
423+
424+
$response = [
425+
'success' => true,
426+
'isShipping' => $isShipping,
427+
'isCaptured' => $isCaptured,
428+
'isRefunded' => $isRefunded,
429+
'orderStatus' => $paymentInfo->status ?? null,
430+
];
431+
}
432+
433+
$this->ajaxRender(json_encode($response));
434+
} catch (Exception $e) {
435+
$this->ajaxRender(
436+
json_encode([
437+
'success' => false,
438+
'message' => $this->module->l('An error occurred while retrieving order information.'),
439+
'error' => $e->getMessage(),
440+
])
441+
);
442+
}
443+
}
216444
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
/**
3+
* Admin controller for handling Mollie order actions: refund, capture, ship.
4+
*
5+
* Follows PrestaShop module conventions and delegates business logic to services.
6+
*/
7+
8+
use Mollie\Adapter\ToolsAdapter;
9+
use Mollie\Logger\LoggerInterface;
10+
use Mollie\Service\CaptureService;
11+
use Mollie\Service\RefundService;
12+
use Mollie\Service\ShipService;
13+
14+
if (!defined('_PS_VERSION_')) {
15+
exit;
16+
}
17+
18+
class AdminMollieOrderController extends ModuleAdminController
19+
{
20+
const FILE_NAME = 'AdminMollieOrderController';
21+
22+
/** @var Mollie */
23+
public $module;
24+
25+
public function __construct()
26+
{
27+
$this->bootstrap = true;
28+
parent::__construct();
29+
}
30+
31+
public function postProcess(): bool
32+
{
33+
if (!$this->context->employee->can('edit', 'AdminOrders')) {
34+
return false;
35+
}
36+
37+
/** @var ToolsAdapter $tools */
38+
$tools = $this->module->getService(ToolsAdapter::class);
39+
/** @var LoggerInterface $logger */
40+
$logger = $this->module->getService(LoggerInterface::class);
41+
$cookie = \Context::getContext()->cookie;
42+
43+
$orderId = $tools->getValueAsInt('orderId');
44+
$errors = json_decode($cookie->__get('mollie_order_management_errors'), false) ?: [];
45+
46+
if ($tools->isSubmit('capture-order')) {
47+
try {
48+
$amount = (float) $tools->getValue('capture_amount');
49+
/** @var CaptureService $captureService */
50+
$captureService = $this->module->getService(CaptureService::class);
51+
$captureService->handleCapture($orderId, $amount);
52+
} catch (\Throwable $exception) {
53+
$errors[$orderId] = 'Capture failed. See logs.';
54+
$cookie->__set('mollie_order_management_errors', json_encode($errors));
55+
$logger->error('Failed to capture order.', [
56+
'order_id' => $orderId,
57+
'amount' => $amount ?? null,
58+
'exception' => $exception->getMessage(),
59+
]);
60+
}
61+
}
62+
63+
if ($tools->isSubmit('refund-order')) {
64+
try {
65+
$amount = (float) $tools->getValue('refund_amount');
66+
/** @var RefundService $refundService */
67+
$refundService = $this->module->getService(RefundService::class);
68+
$refundService->handleRefund($orderId, $amount);
69+
} catch (\Throwable $exception) {
70+
$errors[$orderId] = 'Refund failed. See logs.';
71+
$cookie->__set('mollie_order_management_errors', json_encode($errors));
72+
$logger->error('Failed to refund order.', [
73+
'order_id' => $orderId,
74+
'amount' => $amount ?? null,
75+
'exception' => $exception->getMessage(),
76+
]);
77+
}
78+
}
79+
80+
if ($tools->isSubmit('ship-order')) {
81+
try {
82+
/** @var ShipService $shipService */
83+
$shipService = $this->module->getService(ShipService::class);
84+
$shipService->handleShip($orderId);
85+
} catch (\Throwable $exception) {
86+
$errors[$orderId] = 'Shipping failed. See logs.';
87+
$cookie->__set('mollie_order_management_errors', json_encode($errors));
88+
$logger->error('Failed to ship order.', [
89+
'order_id' => $orderId,
90+
'exception' => $exception->getMessage(),
91+
]);
92+
}
93+
}
94+
95+
$this->redirectToOrderController('AdminOrders', $orderId);
96+
97+
return true;
98+
}
99+
100+
private function redirectToOrderController(string $controller, int $orderId): void
101+
{
102+
$url = \Context::getContext()->link->getAdminLink($controller, true, [], ['id_order' => $orderId, 'vieworder' => 1]);
103+
\Tools::redirectAdmin($url);
104+
}
105+
}

0 commit comments

Comments
 (0)