Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions controllers/admin/AdminSaferPayOfficialSettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -693,17 +693,36 @@ private function getCurrencies()
*/
private function getPaymentMethodsData()
{
/** @var SaferPayPaymentRepository $paymentRepository */
$paymentRepository = $this->module->getService(SaferPayPaymentRepository::class);

try {
/** @var SaferPayObtainPaymentMethods $obtainMethods */
$obtainMethods = $this->module->getService(SaferPayObtainPaymentMethods::class);
$paymentMethods = $obtainMethods->obtainPaymentMethodsNamesAsArray();
// Re-read the account and reconcile the stored list when the Payment Methods
// settings open, so methods added/removed on the Saferpay account are reflected
// (and persisted for the front office) without requiring a Save click. Enabled
// flags are preserved by the refresh; newly added methods default to disabled.
/** @var SaferPayRefreshPaymentsService $refreshPaymentsService */
$refreshPaymentsService = $this->module->getService(SaferPayRefreshPaymentsService::class);
$refreshPaymentsService->refreshPayments();

// The refresh persists the account's methods, so read them back from storage
// instead of calling the API a second time.
$paymentMethods = array_column($paymentRepository->getAllPaymentMethodsNames(), 'name');

// refreshPayments() is a no-op when nothing is active yet (e.g. a fresh setup),
// so fall back to the live account list to still surface newly available methods.
if (empty($paymentMethods)) {
/** @var SaferPayObtainPaymentMethods $obtainMethods */
$obtainMethods = $this->module->getService(SaferPayObtainPaymentMethods::class);
$paymentMethods = $obtainMethods->obtainPaymentMethodsNamesAsArray();
}
} catch (SaferPayApiException $exception) {
return ['error' => $this->module->l('Failed to load payment methods. Please verify your API credentials.', self::FILE_NAME)];
// Account unreachable (bad credentials / offline): keep the last-known stored
// list rather than wiping the page. Credential validity is surfaced separately
// on the Credentials tab. Never clears stored configuration.
$paymentMethods = array_column($paymentRepository->getAllPaymentMethodsNames(), 'name');
}

/** @var SaferPayPaymentRepository $paymentRepository */
$paymentRepository = $this->module->getService(SaferPayPaymentRepository::class);

/** @var SaferPayLogoRepository $logoRepository */
$logoRepository = $this->module->getService(SaferPayLogoRepository::class);

Expand Down
15 changes: 15 additions & 0 deletions src/Repository/SaferPayPaymentRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@ public function getActivePaymentMethodsNames()
return $result;
}

public function getAllPaymentMethodsNames()
{
$query = new DbQuery();
$query->select('name');
$query->from('saferpay_payment');

$result = Db::getInstance()->executeS($query);

if (!$result) {
return [];
}

return $result;
}

public function truncateTable()
{
$query = 'TRUNCATE TABLE ' . _DB_PREFIX_ . 'saferpay_payment;';
Expand Down
137 changes: 137 additions & 0 deletions tests/Unit/Service/SaferPayRefreshPaymentsServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php
/**
*NOTICE OF LICENSE
*
*This source file is subject to the Open Software License (OSL 3.0)
*that is bundled with this package in the file LICENSE.txt.
*It is also available through the world-wide-web at this URL:
*http://opensource.org/licenses/osl-3.0.php
*If you did not receive a copy of the license and are unable to
*obtain it through the world-wide-web, please send an email
*to license@prestashop.com so we can send you a copy immediately.
*
*DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
*versions in the future. If you wish to customize PrestaShop for your
*needs please refer to http://www.prestashop.com for more information.
*
*@author INVERTUS UAB www.invertus.eu <support@invertus.eu>
*@copyright SIX Payment Services
*@license SIX Payment Services
*/

namespace Invertus\SaferPay\Tests\Unit\Service;

use Invertus\SaferPay\Logger\LoggerInterface;
use Invertus\SaferPay\Repository\SaferPayFieldRepository;
use Invertus\SaferPay\Repository\SaferPayPaymentRepository;
use Invertus\SaferPay\Repository\SaferPayRestrictionRepository;
use Invertus\SaferPay\Service\SaferPayObtainPaymentMethods;
use Invertus\SaferPay\Service\SaferPayRefreshPaymentsService;
use PHPUnit\Framework\TestCase;

class SaferPayRefreshPaymentsServiceTest extends TestCase
{
public function testReconcilePreservesEnabledAddsNewDisabledDropsRemoved()
{
// Stored: VISA (enabled), AMEX (enabled). Account: VISA (kept), TWINT (added), AMEX removed.
$paymentRepository = $this->mockPaymentRepository([
['name' => 'VISA', 'active' => '1'],
['name' => 'AMEX', 'active' => '1'],
]);
$fieldRepository = $this->mockFieldRepository(['VISA' => true, 'AMEX' => false]);
$obtainPaymentMethods = $this->mockObtainPaymentMethods(['VISA', 'TWINT']);

// Both tables are rebuilt.
$paymentRepository->expects($this->once())->method('truncateTable');
$fieldRepository->expects($this->once())->method('truncateTable');

// VISA keeps active=1; TWINT added as active=0; AMEX (removed) is never re-inserted.
$paymentRepository->expects($this->exactly(2))
->method('insertPayment')
->withConsecutive(
[['name' => 'VISA', 'active' => 1]],
[['name' => 'TWINT', 'active' => 0]]
);
// Custom-form flag preserved for VISA (true -> 1), default 0 for the new TWINT.
$fieldRepository->expects($this->exactly(2))
->method('insertField')
->withConsecutive(
[['name' => 'VISA', 'active' => 1]],
[['name' => 'TWINT', 'active' => 0]]
);

$this->makeService($paymentRepository, $obtainPaymentMethods, $fieldRepository)->refreshPayments();
Comment on lines +51 to +65

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The withConsecutive() method is deprecated in PHPUnit 9.6 and completely removed in PHPUnit 10. Using it will trigger deprecation warnings and block future PHPUnit upgrades.

Recommendation

Instead of withConsecutive(), you can collect the arguments using willReturnCallback() and assert them afterward. This is fully compatible with PHPUnit 10+ and avoids deprecation warnings.

        $insertedPayments = [];
        $paymentRepository->expects($this->exactly(2))
            ->method('insertPayment')
            ->willReturnCallback(function ($data) use (&$insertedPayments) {
                $insertedPayments[] = $data;
            });

        // Custom-form flag preserved for VISA (true -> 1), default 0 for the new TWINT.
        $insertedFields = [];
        $fieldRepository->expects($this->exactly(2))
            ->method('insertField')
            ->willReturnCallback(function ($data) use (&$insertedFields) {
                $insertedFields[] = $data;
            });

        $this->makeService($paymentRepository, $obtainPaymentMethods, $fieldRepository)->refreshPayments();

        $this->assertEquals([
            ['name' => 'VISA', 'active' => 1],
            ['name' => 'TWINT', 'active' => 0],
        ], $insertedPayments);

        $this->assertEquals([
            ['name' => 'VISA', 'active' => 1],
            ['name' => 'TWINT', 'active' => 0],
        ], $insertedFields);

}

public function testDoesNothingWhenNoActivePaymentMethodsStored()
{
$paymentRepository = $this->mockPaymentRepository([]);
$fieldRepository = $this->mockFieldRepository([]);
$obtainPaymentMethods = $this->mockObtainPaymentMethods(['VISA']);

// Early return: no API reconciliation and no destructive rebuild.
$obtainPaymentMethods->expects($this->never())->method('obtainPaymentMethodsNamesAsArray');
$paymentRepository->expects($this->never())->method('truncateTable');
$paymentRepository->expects($this->never())->method('insertPayment');

$this->makeService($paymentRepository, $obtainPaymentMethods, $fieldRepository)->refreshPayments();
}

private function makeService($paymentRepository, $obtainPaymentMethods, $fieldRepository)
{
return new SaferPayRefreshPaymentsService(
$paymentRepository,
$obtainPaymentMethods,
$this->createMockWithMethods(SaferPayRestrictionRepository::class, []),
$fieldRepository,
$this->createMockWithMethods(LoggerInterface::class, [])
);
}

private function mockPaymentRepository(array $activePayments)
{
$mock = $this->createMockWithMethods(
SaferPayPaymentRepository::class,
['getActivePaymentMethods', 'truncateTable', 'insertPayment']
);
$mock->method('getActivePaymentMethods')->willReturn($activePayments);

return $mock;
}

private function mockFieldRepository(array $activeByName)
{
$mock = $this->createMockWithMethods(
SaferPayFieldRepository::class,
['isActiveByName', 'truncateTable', 'insertField']
);
$mock->method('isActiveByName')->willReturnCallback(function ($name) use ($activeByName) {
return isset($activeByName[$name]) ? $activeByName[$name] : false;
});

return $mock;
}

private function mockObtainPaymentMethods(array $names)
{
$mock = $this->createMockWithMethods(
SaferPayObtainPaymentMethods::class,
['obtainPaymentMethodsNamesAsArray']
);
$mock->method('obtainPaymentMethodsNamesAsArray')->willReturn($names);

return $mock;
}

private function createMockWithMethods($class, array $methods)
{
$builder = $this->getMockBuilder($class)->disableOriginalConstructor();
if (!empty($methods)) {
$builder->setMethods($methods);
}

return $builder->getMock();
}
Comment on lines +128 to +136

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The setMethods() method is deprecated in PHPUnit 9 and removed in PHPUnit 10. It should be replaced with onlyMethods() for existing methods on the class.

    private function createMockWithMethods($class, array $methods)
    {
        $builder = $this->getMockBuilder($class)->disableOriginalConstructor();
        if (!empty($methods)) {
            $builder->onlyMethods($methods);
        }

        return $builder->getMock();
    }

}
Loading