Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5961189
PPSYL-163 - Replace secretKey by live checkbox
Jibbarth Aug 29, 2025
70839bb
PPSYL-163 - Remove secretKey from form
Jibbarth Aug 29, 2025
5222bda
PPSYL-163 - Handle Oauth redirection and creation of clients
Jibbarth Aug 29, 2025
147b3e2
PPSYL-163 - Retrieve access_token and use it in PayplugApiClient
Jibbarth Aug 29, 2025
1b347d2
PPSYL-163 - Remove usage of initialize method from PayplugApiClient
Jibbarth Aug 29, 2025
91ba46c
PPSYL-163 - Allow renew oauth when updating payment method
Jibbarth Aug 29, 2025
eee9f67
PPSYL-163 - Delegate constraint check after submit
Jibbarth Aug 29, 2025
40837b0
PPSYL-163 - Add custom flashes error message to display html
Jibbarth Sep 5, 2025
6d19c1b
PPSYL-163 - Add translations
Jibbarth Sep 5, 2025
7c7020f
PPSYL-163 - Bump payplug sdk
Jibbarth Sep 5, 2025
bb74f87
PPSYL-163 - Improve error catching and log on dedicated channel
Jibbarth Sep 5, 2025
45279d0
PPSYL-163 - Fix Oney validation
Jibbarth Sep 5, 2025
4d940b5
PPSYL-163 - Fix prevent start oauth on non payplug payment
Jibbarth Sep 5, 2025
376d9f3
Merge branch 'feature/upgrade-v2' into feature/PPSYL-163-unified-auth
Jibbarth Sep 8, 2025
de63653
PPSYL-163 - Do not validate when paymentmethod is not payplug on update
Jibbarth Sep 8, 2025
ee8e9d3
PPSYL-163 - Fix retrieving cards
Jibbarth Sep 9, 2025
541463f
PPSYL-163 - PHPStan getSession flashbag - Ignore error
Jibbarth Sep 9, 2025
58a1013
PPSYL-163 - PHPStan Clean issues
Jibbarth Sep 9, 2025
99cc145
PPSYL-163 - Add Changelog
Jibbarth Sep 9, 2025
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: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.0.0] - Unreleased

### Added
- Support for Sylius v2.0+
- PHP 8.2+ compatibility
- Use Payment Request API from Sylius
- New Unified Authentication System (OAuth2)

> [!IMPORTANT]
> Merchants will need to contact support to switch to the new authentication method.

### Changed
- Plugin structure has been changed to follow the new Symfony bundle structure
- Front assets have been migrated to use Stimulus

### Removed
- Drop Payum support
- Drop Sylius 1.x support
- Drop usage of Secret key - Use OAuth2 instead

Please refer to [github releases](https://github.com/payplug/SyliusPayPlugPlugin/releases) for historical release information.

---

For migration guides and upgrade instructions, see [UPGRADE.md](UPGRADE.md).
For contributing guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md).
4 changes: 3 additions & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Release Process

Upon releasing a new version there are checks and updates to be made:
* Update plugin's version inside `src/PayPlugSyliusPayPlugPlugin.php` and `src/Resources/dev/package.json`
* Update plugin's version inside `src/PayPlugSyliusPayPlugPlugin.php`
* Ensure that the `CHANGELOG.md` is up to date with the changes made
* Ensure that the date of the release version is updated in `CHANGELOG.md`
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"php": "^8.2",
"ext-json": "*",
"giggsey/libphonenumber-for-php": "^8.12",
"payplug/payplug-php": "^3.1",
"payplug/payplug-php": "^4.0",
"php-http/message-factory": "^1.1",
"sylius/refund-plugin": "^2.0",
"sylius/sylius": "^2.0",
Expand Down
4 changes: 3 additions & 1 deletion config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ services:
PayPlug\SyliusPayPlugPlugin\:
resource: '../src/*'
exclude: '../src/{ApiClient,DependencyInjection,Entity,Exception,Model,Repository,PayPlugSyliusPayPlugPlugin.php}'
bind:
Psr\Log\LoggerInterface: '@monolog.logger.payplug'

PayPlug\SyliusPayPlugPlugin\Repository\PaymentRepositoryInterface:
class: PayPlug\SyliusPayPlugPlugin\Repository\PaymentRepository
Expand Down Expand Up @@ -73,7 +75,7 @@ services:
tags:
- name: sylius.payment_request.provider.http_response
gateway_factory: !php/const PayPlug\SyliusPayPlugPlugin\Gateway\OneyGatewayFactory::FACTORY_NAME


## Bancontact Payplug Gateway ##
payplug_sylius_payplug_plugin.command_provider.payplug_bancontact:
Expand Down
39 changes: 28 additions & 11 deletions config/twig_hooks/admin.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
sylius_twig_hooks:
hooks:
sylius_admin.payment_method.update.content:
payplug_flashes:
template: '@PayPlugSyliusPayPlugPlugin/admin/shared/flashes.html.twig'
priority: 350
'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug': &payplugGateway
secret_key: &secretKey
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/secret_key.html.twig'
live_checkbox: &liveCheckbox
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/live_checkbox.html.twig'
priority: 0
one_click:
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/one_click.html.twig'
Expand All @@ -14,19 +18,32 @@ sylius_twig_hooks:
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/deferred_capture.html.twig'
priority: 0
'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_oney': &oneyGateway
secret_key: *secretKey
live_checkbox: *liveCheckbox
fees_for:
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/fees_for.html.twig'
priority: 0
'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_bancontact': &bancontactGateway
secret_key: *secretKey
live_checkbox: *liveCheckbox
'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_apple_pay': &applePayGateway
secret_key: *secretKey
live_checkbox: *liveCheckbox

'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_american_express': &amexGateway
secret_key: *secretKey
live_checkbox: *liveCheckbox

'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug': *payplugGateway
'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_oney': *oneyGateway
'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_bancontact': *bancontactGateway
'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_apple_pay': *applePayGateway
'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_american_express': *amexGateway
'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug':
<<: *payplugGateway
renew_oauth: &renewOAuth
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/renew_oauth.html.twig'
priority: -5
'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_oney':
<<: *oneyGateway
renew_oauth: *renewOAuth
'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_bancontact':
<<: *bancontactGateway
renew_oauth: *renewOAuth
'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_apple_pay':
<<: *applePayGateway
renew_oauth: *renewOAuth
'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_american_express':
<<: *amexGateway
renew_oauth: *renewOAuth
16 changes: 16 additions & 0 deletions ruleset/phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -462,11 +462,27 @@ parameters:
count: 1
path: ../src/Controller/OrderController.php

# Session Specific
-
message: '#^Cannot call method add\(\) on mixed\.$#'
identifier: method.nonObject
count: 4
path: ../src/Controller/OrderController.php
-
message: '#^Cannot call method add\(\) on mixed\.$#'
identifier: method.nonObject
count: 2
path: ../src/Action/Admin/Auth/UnifiedAuthenticationController.php
-
message: '#^Cannot call method add\(\) on mixed\.$#'
identifier: method.nonObject
count: 2
path: ../src/Validator/PaymentMethodValidator.php
-
message: '#^Cannot call method add\(\) on mixed\.$#'
identifier: method.nonObject
count: 1
path: ../src/EventListener/PostSavePaymentMethodEventListener.php

-
message: '#^Cannot call method apply\(\) on mixed\.$#'
Expand Down
147 changes: 147 additions & 0 deletions src/Action/Admin/Auth/UnifiedAuthenticationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php

declare(strict_types=1);

namespace PayPlug\SyliusPayPlugPlugin\Action\Admin\Auth;

use Doctrine\ORM\EntityManagerInterface;
use Payplug\Authentication;
use Payplug\Payplug;
use PayPlug\SyliusPayPlugPlugin\Validator\PaymentMethodValidator;
use Psr\Log\LoggerInterface;
use Sylius\Resource\Doctrine\Persistence\RepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;

/**
* This controller is used to authenticate the user with PayPlug
*
* The OAuth process start when creating a new payment method or updated it.
* @see PayPlug\SyliusPayPlugPlugin\EventListener\PostSavePaymentMethodEventListener
*/
#[Route('/payplug/auth')]
final class UnifiedAuthenticationController extends AbstractController
{
/**
* @param RepositoryInterface<\Sylius\Component\Core\Model\PaymentMethod> $paymentMethodRepository
*/
public function __construct(
private RouterInterface $router,
private RepositoryInterface $paymentMethodRepository,
private EntityManagerInterface $entityManager,
private PaymentMethodValidator $paymentMethodValidator,
private LoggerInterface $logger,
) {
}

#[Route('/setup-redirection', name: 'payplug_sylius_admin_auth_setup_redirection')]
public function setupRedirection(Request $request): Response
{
try {
$clientId = $request->query->get('client_id');
$companyId = $request->query->get('company_id');

$request->getSession()->set('payplug_client_id', $clientId);
$request->getSession()->set('payplug_company_id', $companyId);

$challenge = bin2hex(openssl_random_pseudo_bytes(50));
$request->getSession()->set('payplug_oauth_challenge', $challenge);

$callBackUrl = $this->router->generate('payplug_sylius_admin_auth_oauth_callback', [], RouterInterface::ABSOLUTE_URL);

// This method will redirect the user to PayPlug's oauth page via header('Location')'
Authentication::initiateOAuth($clientId, $callBackUrl, $challenge);
// Fetch the header Location the Sdk put and redirect the user to it
$headers = \headers_list();
foreach ($headers as $header) {
if (str_starts_with($header, 'Location:')) {
return new RedirectResponse(substr($header, 9));
}
}
throw new \LogicException('No location header found');
} catch (\Throwable $e) {
$this->logger->critical('Error while perform Payplug OAuth Setup redirection', ['message' => $e->getMessage(), 'exception' => $e]);
return $this->handleOAuthError($request);
}
}

#[Route('/oauth-callback', name: 'payplug_sylius_admin_auth_oauth_callback')]
public function oauthCallback(Request $request): Response
{
try {
$code = $request->query->getString('code');
/** @var string $clientId */
$clientId = $request->getSession()->get('payplug_client_id');
/** @var string $challenge */
$challenge = $request->getSession()->get('payplug_oauth_challenge');
$callback = $this->generateUrl('payplug_sylius_admin_auth_oauth_callback', [], UrlGeneratorInterface::ABSOLUTE_URL);

$jwt = Authentication::generateJWTOneShot($code, $callback, $clientId, $challenge);
if ([] === $jwt || $jwt['httpStatus'] !== 200 || !\is_array($jwt['httpResponse'])) {
throw new BadRequestHttpException('Error while generating JWT');
}
$paymentMethodId = $request->getSession()->get('payplug_sylius_oauth_payment_method_id');
if (null === $paymentMethodId) {
throw new BadRequestHttpException('No payment method id found in session');
}
$paymentMethod = $this->paymentMethodRepository->find($paymentMethodId);
if (null === $paymentMethod) {
throw new \LogicException('No payment method found');
}
$gatewayConfig = $paymentMethod->getGatewayConfig();
if (null === $gatewayConfig) {
throw new \LogicException('No gateway config found');
}

$companyId = $request->getSession()->get('payplug_company_id');
Payplug::init(['secretKey' => $jwt['httpResponse']['access_token']]);
$clientName = 'Sylius - ' . $paymentMethod->getName();
$testClientDataResult = Authentication::createClientIdAndSecret($companyId, $clientName, 'test');
$liveClientDataResult = Authentication::createClientIdAndSecret($companyId, $clientName, 'live');

$config = $gatewayConfig->getConfig();
$config['live_client'] = $liveClientDataResult['httpResponse'];
$config['test_client'] = $testClientDataResult['httpResponse'];
$gatewayConfig->setConfig($config);

$this->entityManager->flush();
$this->cleanSession($request);

$request->getSession()->getFlashBag()->add('success', 'payplug_sylius_payplug_plugin.admin.oauth_callback_success');

// Ensure that the payment method is well configured
$this->paymentMethodValidator->process($paymentMethod);

return new RedirectResponse($this->router->generate('sylius_admin_payment_method_update', ['id' => $paymentMethod->getId()]));
} catch (\Throwable $e) {
$this->logger->critical('Error while perform Payplug OAuth callback', ['message' => $e->getMessage(), 'exception' => $e]);
return $this->handleOAuthError($request);
}
}

private function handleOAuthError(Request $request): RedirectResponse
{
$request->getSession()->getFlashBag()->add('error', 'payplug_sylius_payplug_plugin.admin.oauth_setup_error');
$paymentMethodId = $request->getSession()->get('payplug_sylius_oauth_payment_method_id');
if (null === $paymentMethodId) {
return new RedirectResponse($this->router->generate('sylius_admin_payment_method_index'));
}

return new RedirectResponse($this->router->generate('sylius_admin_payment_method_update', ['id' => $paymentMethodId]));
}

private function cleanSession(Request $request): void
{
$session = $request->getSession();
$session->remove('payplug_client_id');
$session->remove('payplug_company_id');
$session->remove('payplug_oauth_challenge');
$session->remove('payplug_sylius_oauth_payment_method_id');
}
}
13 changes: 0 additions & 13 deletions src/ApiClient/PayPlugApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,6 @@ public function __construct(string $secretKey, ?string $factoryName = null, ?Cac
);
}

/**
* @deprecated use DI instead to get a pre-configured client
*/
public function initialise(string $secretKey): void
{
Payplug::setSecretKey($secretKey);
HttpClient::addDefaultUserAgentProduct(
'PayPlug-Sylius',
PayPlugSyliusPayPlugPlugin::VERSION,
'Sylius/' . SyliusCoreBundle::VERSION,
);
}

public function getAccount(bool $refresh = false): array
{
$cacheKey = 'payplug_account_' . substr($this->configuration->getToken(), 8);
Expand Down
Loading
Loading