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
9 changes: 0 additions & 9 deletions config/services/client.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,5 @@
method="create"/>
<argument type="string" key="$factoryName">payplug_wero</argument><!-- Gateway factory name -->
</service>

<service id="payplug_sylius_payplug_plugin.api_client.uhf"
class="PayPlug\SyliusPayPlugPlugin\ApiClient\PayPlugApiClient"
public="true"
lazy="true">
<factory service="PayPlug\SyliusPayPlugPlugin\ApiClient\PayPlugApiClientFactory"
method="create"/>
<argument type="string" key="$factoryName">payplug_uhf</argument><!-- Gateway factory name -->
</service>
</services>
</container>
24 changes: 9 additions & 15 deletions config/twig_hooks/admin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,18 @@ sylius_twig_hooks:
one_click:
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/one_click.html.twig'
priority: 0
integrated_payment:
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/integrated_payment.html.twig'
hosted_fields_mode:
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/hosted_fields_mode.html.twig'
priority: 0
hf_identifier:
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/hf_identifier.html.twig'
priority: -1
hf_sub_merchant_id:
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/hf_sub_merchant_id.html.twig'
priority: -2
deferred_capture:
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/deferred_capture.html.twig'
priority: 0
priority: -3
'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_oney': &oneyGateway
live_checkbox: *liveCheckbox
fees_for:
Expand All @@ -44,15 +50,6 @@ sylius_twig_hooks:
'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_wero': &weroGateway
live_checkbox: *liveCheckbox

'sylius_admin.payment_method.create.content.form.sections.gateway_configuration.payplug_uhf': &uhfGateway
live_checkbox: *liveCheckbox
one_click:
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/one_click.html.twig'
priority: 0
hf_identifier_default: &hfIdentifierDefault
template: '@PayPlugSyliusPayPlugPlugin/admin/payment_method/form/hf_identifier_default.html.twig'
priority: -1

'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug':
<<: *payplugGateway
renew_oauth: &renewOAuth
Expand All @@ -76,6 +73,3 @@ sylius_twig_hooks:
'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_wero':
<<: *weroGateway
renew_oauth: *renewOAuth
'sylius_admin.payment_method.update.content.form.sections.gateway_configuration.payplug_uhf':
<<: *uhfGateway
renew_oauth: *renewOAuth
3 changes: 0 additions & 3 deletions config/twig_hooks/shop.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,3 @@ sylius_twig_hooks:
'sylius_shop.shared.form.select_payment.payment.choice.details#payplug_wero':
wero:
template: '@PayPlugSyliusPayPlugPlugin/shop/select_payment/_wero.html.twig'
'sylius_shop.shared.form.select_payment.payment.choice.details#payplug_uhf':
uhf:
template: '@PayPlugSyliusPayPlugPlugin/shop/select_payment/_payplug_uhf.html.twig'
10 changes: 5 additions & 5 deletions features/shop/hosted_fields_payment_method.feature
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@
Feature: Paying with Hosted Fields during checkout
In order to buy products
As a Customer
I want to see Hosted Fields as a distinct payment method at checkout
I want to see Hosted Fields when the merchant enabled it on the PayPlug payment method

Background:
Given the store operates on a single channel in "United States"
And that channel also allows to shop using the "EUR" currency
And there is a user "john@bitbag.pl" identified by "password123"
And I changed my currency to "EUR"
And the store has a payment method "PayPlug Hosted Fields" with a code "payplug_hosted_fields" and PayPlug Hosted Fields payment gateway
And the store has a payment method "PayPlug" with a code "payplug" and PayPlug Hosted Fields payment gateway
And This secret Key is valid
And the store ships everywhere for free
And the store has "DHL" shipping method with "$0.00" fee
And I am logged in as "john@bitbag.pl"

@ui
Scenario: I can see and select the Hosted Fields payment method
Scenario: I can see and select the PayPlug payment method with Hosted Fields enabled
Given the store has a product "PHP T-Shirt" priced at "€50.00"
And I added product "PHP T-Shirt" to the cart
And I chose "DHL" shipping method
Then I should be on the checkout payment step
And I should be able to select "PayPlug Hosted Fields" payment method
And I select "PayPlug Hosted Fields" payment method
And I should be able to select "PayPlug" payment method
And I select "PayPlug" payment method
And I should see the "#card-container" element on the page
7 changes: 5 additions & 2 deletions src/EventSubscriber/PostPaymentSelectEventSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace PayPlug\SyliusPayPlugPlugin\EventSubscriber;

use Doctrine\ORM\EntityManagerInterface;
use PayPlug\SyliusPayPlugPlugin\Gateway\UhfGatewayFactory;
use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory;
use PayPlug\SyliusPayPlugPlugin\PaymentProcessing\HostedFieldsPaymentProcessorInterface;
use Sylius\Abstraction\StateMachine\StateMachineInterface;
use Sylius\Bundle\ResourceBundle\Event\ResourceControllerEvent;
Expand Down Expand Up @@ -194,7 +194,10 @@ private function handleHostedFieldsToken(Request $request, PaymentInterface $las

private function isHostedFieldsEnabled(PaymentInterface $payment): bool
{
return UhfGatewayFactory::FACTORY_NAME === $payment->getMethod()?->getGatewayConfig()?->getFactoryName();
$gatewayConfig = $payment->getMethod()?->getGatewayConfig();

return PayPlugGatewayFactory::FACTORY_NAME === $gatewayConfig?->getFactoryName() &&
true === ($gatewayConfig->getConfig()[PayPlugGatewayFactory::HOSTED_FIELDS] ?? false);
}

private function getRequestField(Request $request, string $field): string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,21 @@
use PayPlug\SyliusPayPlugPlugin\Gateway\PayPlugGatewayFactory;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Contracts\Translation\TranslatorInterface;

final class PayPlugGatewayConfigurationTypeExtension extends AbstractTypeExtension
{
public function __construct(private TranslatorInterface $translator)
{
}

/**
* @inheritdoc
*/
Expand All @@ -29,12 +38,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
'help_html' => true,
'required' => false,
])
->add(PayPlugGatewayFactory::INTEGRATED_PAYMENT, CheckboxType::class, [
'block_name' => 'payplug_checkbox',
'label' => 'payplug_sylius_payplug_plugin.form.integrated_payment_enable',
'validation_groups' => AbstractGatewayConfigurationType::VALIDATION_GROUPS,
'required' => false,
])
->add(PayPlugGatewayFactory::DEFERRED_CAPTURE, CheckboxType::class, [
'block_name' => 'payplug_checkbox',
'label' => 'payplug_sylius_payplug_plugin.form.deferred_capture_enable',
Expand All @@ -43,6 +46,49 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
'help_html' => true,
'required' => false,
])
->add(PayPlugGatewayFactory::DISPLAY_MODE_FIELD, ChoiceType::class, [
'mapped' => false,
'required' => false,
'expanded' => true,
'placeholder' => 'payplug_sylius_payplug_plugin.form.redirected_payment_enable',
'validation_groups' => AbstractGatewayConfigurationType::VALIDATION_GROUPS,
'choices' => [
'payplug_sylius_payplug_plugin.form.integrated_payment_enable' => PayPlugGatewayFactory::DISPLAY_MODE_INTEGRATED_PAYMENT,
'payplug_sylius_payplug_plugin.ui.hosted_fields_option' => PayPlugGatewayFactory::DISPLAY_MODE_HOSTED_FIELDS,
],
])
->add(PayPlugGatewayFactory::HF_IDENTIFIER, TextType::class, [
'label' => 'payplug_sylius_payplug_plugin.ui.hf_identifier_label',
'required' => false,
'validation_groups' => AbstractGatewayConfigurationType::VALIDATION_GROUPS,
])
->add(PayPlugGatewayFactory::HF_SUB_MERCHANT_ID, PasswordType::class, [
'label' => 'payplug_sylius_payplug_plugin.ui.hf_sub_merchant_id_label',
'required' => false,
'validation_groups' => AbstractGatewayConfigurationType::VALIDATION_GROUPS,
])
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event): void {
$rawData = $event->getData();
if (!is_array($rawData)) {
return;
}

$submitted = $rawData[PayPlugGatewayFactory::HF_SUB_MERCHANT_ID] ?? '';
if (!is_scalar($submitted) || '' !== trim((string) $submitted)) {
return;
}

// PasswordType keeps its default `always_empty` (never echoes the stored secret
// back into the rendered `value` attribute), so a blank submission means "left
// untouched", not "clear it" - same convention as a change-password form. Restore
// the previously persisted value instead of letting a blank field wipe it out.
$previousData = $event->getForm()->getData();
$previousValue = is_array($previousData) ? ($previousData[PayPlugGatewayFactory::HF_SUB_MERCHANT_ID] ?? null) : null;
if (is_string($previousValue) && '' !== $previousValue) {
$rawData[PayPlugGatewayFactory::HF_SUB_MERCHANT_ID] = $previousValue;
$event->setData($rawData);
}
})
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event): void {
$data = $event->getData();
// phpstan check
Expand All @@ -52,6 +98,48 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
$data['payum.http_client'] = '@payplug_sylius_payplug_plugin.api_client.payplug';
$event->setData($data);
})
// DISPLAY_MODE_FIELD's pre-selection must happen on POST_SET_DATA, not PRE_SET_DATA:
// it's `mapped => false`, and Symfony's DataMapper::mapDataToForms() runs right after
// PRE_SET_DATA dispatches (as part of the same parent setData() call), resetting every
// unmapped child back to its configured (null) default — silently wiping out a
// setData() call made from PRE_SET_DATA. POST_SET_DATA fires after that reset, so
// nothing overwrites it afterward.
->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event): void {
$data = $event->getData();
if (!is_array($data)) {
return;
}

$event->getForm()->get(PayPlugGatewayFactory::DISPLAY_MODE_FIELD)->setData(
PayPlugGatewayFactory::resolveDisplayMode($data),
);
})
->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
$form = $event->getForm();
$submittedData = [
PayPlugGatewayFactory::DISPLAY_MODE_FIELD => $form->get(PayPlugGatewayFactory::DISPLAY_MODE_FIELD)->getData(),
PayPlugGatewayFactory::HF_IDENTIFIER => $form->get(PayPlugGatewayFactory::HF_IDENTIFIER)->getData(),
PayPlugGatewayFactory::HF_SUB_MERCHANT_ID => $form->get(PayPlugGatewayFactory::HF_SUB_MERCHANT_ID)->getData(),
];

foreach (PayPlugGatewayFactory::missingHostedFieldsRequirements($submittedData) as $field) {
$messageKey = PayPlugGatewayFactory::HF_IDENTIFIER === $field
? 'payplug_sylius_payplug_plugin.form.account_id_required'
: 'payplug_sylius_payplug_plugin.form.submerchant_id_required';

$form->get($field)->addError(new FormError($this->translator->trans($messageKey)));
}
})
->addEventListener(FormEvents::SUBMIT, function (FormEvent $event): void {
$data = $event->getData();
if (!is_array($data)) {
return;
}

$displayMode = $event->getForm()->get(PayPlugGatewayFactory::DISPLAY_MODE_FIELD)->getData();
$displayMode = is_string($displayMode) ? $displayMode : null;
$event->setData(array_merge($data, PayPlugGatewayFactory::resolveDisplayModeFlags($displayMode)));
})
;
}

Expand Down

This file was deleted.

52 changes: 44 additions & 8 deletions src/Gateway/Form/Type/AbstractGatewayConfigurationType.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
if (!$dataFormChannels instanceof Collection) {
return;
}

$rawData = $event->getData();
if (!\is_array($rawData) || !$this->shouldValidateBaseCurrency($rawData)) {
return;
}

$flashedMessages = [];
/** @var ChannelInterface $dataFormChannel */
foreach ($dataFormChannels as $key => $dataFormChannel) {
$baseCurrency = $dataFormChannel->getBaseCurrency();
Expand All @@ -77,15 +84,12 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
}
$baseCurrencyCode = $baseCurrency->getCode();
if ($this->gatewayBaseCurrencyCode !== $baseCurrencyCode) {
$message = $this->translator->trans(
'payplug_sylius_payplug_plugin.form.base_currency_not_euro',
[
'#channel_code#' => $dataFormChannel->getCode(),
'#payment_method#' => $this->gatewayFactoryTitle,
],
);
$message = $this->baseCurrencyViolationMessage($dataFormChannel);
$formChannels->get((string) $key)->addError(new FormError($message));
$this->requestStack->getSession()->getFlashBag()->add('error', $message);
if (!\in_array($message, $flashedMessages, true)) {
$flashedMessages[] = $message;
$this->requestStack->getSession()->getFlashBag()->add('error', $message);
}
}
}
})
Expand Down Expand Up @@ -119,4 +123,36 @@ private function checkCreationRequirements(
/* @phpstan-ignore-next-line */
$form->getParent()->getParent()->get('enabled')->addError(new FormError($message));
}

/**
* Hook for subtypes to scope the base-currency-per-channel restriction below.
* Default: always enforced, preserving today's behavior for every gateway that doesn't
* override this (Bancontact, American Express, Scalapay, Wero, Oney...).
*
* @see baseCurrencyViolationMessage() Companion hook customizing the message this guards.
*
* @param array<int|string, mixed> $rawFormData Raw PRE_SUBMIT data of the gateway config form.
*/
protected function shouldValidateBaseCurrency(array $rawFormData): bool
{
return true;
}

/**
* Hook for subtypes to customize the currency-violation message. Default matches today's
* generic wording, used by every gateway subtype that doesn't override it (Bancontact,
* American Express, Scalapay, Wero, Oney...).
*
* @see shouldValidateBaseCurrency() Companion hook scoping when this message is used.
*/
protected function baseCurrencyViolationMessage(ChannelInterface $channel): string
{
return $this->translator->trans(
'payplug_sylius_payplug_plugin.form.base_currency_not_euro',
[
'#channel_code#' => $channel->getCode(),
'#payment_method#' => $this->gatewayFactoryTitle,
],
);
}
}
Loading
Loading