Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
2 changes: 2 additions & 0 deletions .bc-exclude.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@
\preg_quote('An enum expression Monolog\Level::Debug is not supported in class Monolog\Handler\AbstractHandler'),
// Storefront package is not installed
\preg_quote('"Shopware\Storefront\Framework\Cookie\CookieProviderInterface" could not be found in the located source'),
// internal const
\preg_quote('Value of constant Swag\PayPal\Setting\Settings::DEFAULT_VALUES changed from array') . '.*',
],
];
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# REPLACE_GLOBALLY_WITH_NEXT_VERSION
- Fixes an issue, where cookies are added even though associated payment methods are not active (shopware/SwagPayPal#457)
- Fixes an issue, where the state of a country was not correctly transmitted to PayPal (shopware/SwagPayPal#469)
- Added option to disable PayPal callbacks (shopware/SwagPayPal#463)

# 10.3.0
- Added compatibility with subscription mixed carts (shopware/shopware#10486)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG_de-DE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# REPLACE_GLOBALLY_WITH_NEXT_VERSION
- Behebt ein Problem, bei dem Cookies geladen wurden, obwohl die zugehörigen Zahlungsarten nicht aktiv sind (shopware/SwagPayPal#457)
- Behebt ein Problem, bei dem der Bundesstaat nicht korrekt an PayPal übermittelt wurde (shopware/SwagPayPal#469)
- Fügt eine Option zum deaktiveren von PayPal Callbacks hinzu (shopware/SwagPayPal#463)

# 10.3.0
- Fügt Kompatibilität mit Abos in gemischten Warenkörben hinzu (shopware/shopware#10486)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Shopware\Core\Framework\Plugin\Exception\DecorationPatternException;
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\PayPalSDK\Struct\V2\Order\ApplicationContext;
use Shopware\PayPalSDK\Struct\V2\Order\PaymentSource\Common\Attributes\OrderUpdateCallbackConfig;
use Swag\PayPal\Checkout\Cart\Service\CartPriceService;
Expand All @@ -22,6 +23,7 @@
use Swag\PayPal\OrdersApi\Builder\PayPalOrderBuilder;
use Swag\PayPal\RestApi\PartnerAttributionId;
use Swag\PayPal\RestApi\V2\Resource\OrderResource;
use Swag\PayPal\Setting\Settings;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
Expand All @@ -40,6 +42,7 @@ public function __construct(
private readonly PayPalOrderBuilder $paypalOrderBuilder,
private readonly OrderResource $orderResource,
private readonly CartPriceService $cartPriceService,
private readonly SystemConfigService $systemConfigService,
private readonly RouterInterface $router,
private readonly LoggerInterface $logger,
) {
Expand Down Expand Up @@ -80,17 +83,21 @@ public function createPayPalOrder(Request $request, SalesChannelContext $salesCh
$experienceContext->setUserAction(ApplicationContext::USER_ACTION_CONTINUE);

// Configure shipping callback for dynamic price recalculation
$callbackConfig = new OrderUpdateCallbackConfig();
$callbackUrl = $this->router->generate(
'store-api.paypal.express.shipping_callback',
['salesChannelId' => $salesChannelContext->getSalesChannelId(), 'token' => $salesChannelContext->getToken()],
UrlGeneratorInterface::ABSOLUTE_URL,
);
$callbackConfig->setCallbackUrl($callbackUrl);
$callbackConfig->setCallbackEvents([OrderUpdateCallbackConfig::CALLBACK_EVENT_SHIPPING_OPTIONS]);
$experienceContext->setOrderUpdateCallbackConfig($callbackConfig);
if (!$this->systemConfigService->getBool(Settings::PAYPAL_CALLBACKS_DISABLED)) {
$callbackConfig = new OrderUpdateCallbackConfig();
$callbackUrl = $this->router->generate(
'store-api.paypal.express.shipping_callback',
['salesChannelId' => $salesChannelContext->getSalesChannelId(), 'token' => $salesChannelContext->getToken()],
UrlGeneratorInterface::ABSOLUTE_URL,
);
$callbackConfig->setCallbackUrl($callbackUrl);
$callbackConfig->setCallbackEvents([OrderUpdateCallbackConfig::CALLBACK_EVENT_SHIPPING_OPTIONS]);
$experienceContext->setOrderUpdateCallbackConfig($callbackConfig);

$this->logger->debug('Configured shipping callback', ['callbackUrl' => $callbackUrl]);
$this->logger->debug('Configured shipping callback', ['callbackUrl' => $callbackUrl]);
} else {
$this->logger->debug('Skipped shipping callback due to being disabled in system config');
}
}

$orderResponse = $this->orderResource->create(
Expand Down
3 changes: 2 additions & 1 deletion src/Resources/app/administration/src/app/snippet/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@
"vaultingEnabledACDC": "Vaulting für Kredit- und Debitkarten-Zahlungen verwenden",
"vaultingEnabledVenmo": "Vaulting für Venmo-Zahlungen verwenden",
"crossBorderMessagingEnabled" : "Länderübergreifende Lokalisierung der \"Später bezahlen\"-Nachricht aktivieren",
"crossBorderBuyerCountry" : "Lokalisierung"
"crossBorderBuyerCountry" : "Lokalisierung",
"paypalCallbacksDisabled": "PayPal Callbacks deaktivieren"
},
"helpText": {
"clientId": "Die Client-ID der REST-API, die das Plugin dazu verwendet, sich mit der PayPal-API zu authentifizieren.",
Expand Down
3 changes: 2 additions & 1 deletion src/Resources/app/administration/src/app/snippet/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@
"vaultingEnabledACDC": "Enable Vaulting for credit and debit cards",
"vaultingEnabledVenmo": "Enable Vaulting for Venmo payments",
"crossBorderMessagingEnabled" : "Enable cross-border localization of Pay Later message",
"crossBorderBuyerCountry" : "Localization"
"crossBorderBuyerCountry" : "Localization",
"paypalCallbacksDisabled": "Disable PayPal Callbacks"
},
"helpText": {
"clientId": "The REST API client ID is used to authenticate this plugin with the PayPal API.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ export const SYSTEM_CONFIGS = [

'SwagPayPal.settings.webhookId',
'SwagPayPal.settings.webhookExecuteToken',

'SwagPayPal.settings.paypalCallbacksDisabled',
] as const;

export type SYSTEM_CONFIG = typeof SYSTEM_CONFIGS[number];
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@
"info": "Mit länderübergreifende Nachrichten für \"Später bezahlen\" wird die \"Später bezahlen\"-Nachricht in der Sprache des Kunden anzeigt. Diese Funktion ist nur für bestimmte Länder verfügbar. {0}",
"buyerCountryAuto": "Automatische Bestimmung",
"buyerCountryOverride": "Lokalisierung"
},
"paypalCallbacks": {
"title": "PayPal Callbacks",
"warning": "Wir empfehlen, die PayPal Callbacks nicht zu deaktivieren, da dies die Funktionalität der Erweiterung beeinträchtigen würde. Deaktiviere sie bitte nur, wenn dein Shop nicht öffentlich erreichbar ist.",
"info": "Callbacks ermöglichen es PayPal, den Shop für Bestellstatus-Aktualisierungen und Warenkorbkalkulationen im Express-Checkout-UI von PayPal zu kontaktieren."
}
},
"swag-paypal-settings-sales-channel-switch": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@
"info": "Pay Later messaging is a feature that allows you to display a Pay Later message in the language of the customer. This feature is only available for certain countries. {0}",
"buyerCountryAuto": "Automatic determination",
"buyerCountryOverride": "Localization"
},
"paypalCallbacks": {
"title": "PayPal Callbacks",
"warning": "We recommend not disabling PayPal Callbacks, as this would impair the functionality of the extension. Please only disable them if your shop is not publicly accessible.",
"info": "Callbacks enable PayPal to contact the shop for order status updates and cart calculations in PayPal's Express Checkout UI."
}
},
"swag-paypal-settings-sales-channel-switch": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,22 @@
/>
</div>
</mt-card>

<mt-card
class="swag-paypal-settings-paypal-callbacks"
position-identifier="swag-paypal-settings-paypal-callbacks"
:title="$t('swag-paypal-settings.paypalCallbacks.title')"
:is-loading="settingsStore.isLoading"
>
<div class="swag-paypal-settings-paypal-callbacks__content">
<mt-banner class="swag-paypal-settings-paypal-callbacks__warning-text" variant="attention">
{{ $t('swag-paypal-settings.paypalCallbacks.warning') }}
</mt-banner>

<span class="swag-paypal-settings-paypal-callbacks__info-text">
{{ $t('swag-paypal-settings.paypalCallbacks.info') }}
</span>

<swag-paypal-setting path="SwagPayPal.settings.paypalCallbacksDisabled" />
</div>
</mt-card>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@import "~scss/variables";

.swag-paypal-settings-cross-border {
.swag-paypal-settings-cross-border, .swag-paypal-settings-paypal-callbacks {
&__warning-text {
width: 100%;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('swag-paypal-settings-advanced', () => {

expect(cardClasses).toEqual([
'swag-paypal-settings-cross-border',
'swag-paypal-settings-paypal-callbacks',
]);
});

Expand All @@ -54,6 +55,7 @@ describe('swag-paypal-settings-advanced', () => {
expect(Object.keys(settings)).toEqual([
'SwagPayPal.settings.crossBorderMessagingEnabled',
'SwagPayPal.settings.crossBorderBuyerCountry',
'SwagPayPal.settings.paypalCallbacksDisabled',
]);

expect(settings['SwagPayPal.settings.crossBorderBuyerCountry'].vm.$attrs.options)
Expand All @@ -63,7 +65,7 @@ describe('swag-paypal-settings-advanced', () => {
it('should have cross-border information', async () => {
const wrapper = await createWrapper();

const alert = wrapper.find('.mt-banner');
const alert = wrapper.find('.swag-paypal-settings-cross-border .mt-banner');

expect(alert.exists()).toBe(true);
expect(alert.classes()).toContain('swag-paypal-settings-cross-border__warning-text');
Expand All @@ -73,4 +75,18 @@ describe('swag-paypal-settings-advanced', () => {
expect(info.exists()).toBe(true);
expect(info.text()).toBe('swag-paypal-settings.crossBorder.info');
});

it('should have paypal-callback information', async () => {
const wrapper = await createWrapper();

const alert = wrapper.find('.swag-paypal-settings-paypal-callbacks .mt-banner');

expect(alert.exists()).toBe(true);
expect(alert.classes()).toContain('swag-paypal-settings-paypal-callbacks__warning-text');

const info = wrapper.find('.swag-paypal-settings-paypal-callbacks__info-text');

expect(info.exists()).toBe(true);
expect(info.text()).toBe('swag-paypal-settings.paypalCallbacks.info');
});
});
4 changes: 4 additions & 0 deletions src/Resources/app/administration/src/types/system-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export declare type SystemConfig = {

'SwagPayPal.settings.crossBorderMessagingEnabled'?: boolean;
'SwagPayPal.settings.crossBorderBuyerCountry'?: typeof COUNTRY_OVERRIDES[number] | null;

'SwagPayPal.settings.paypalCallbacksDisabled'?: boolean;
};

/**
Expand Down Expand Up @@ -106,4 +108,6 @@ export const SystemConfigDefinition: Record<SYSTEM_CONFIG, 'string' | 'password'

'SwagPayPal.settings.crossBorderMessagingEnabled': 'boolean',
'SwagPayPal.settings.crossBorderBuyerCountry': 'string',

'SwagPayPal.settings.paypalCallbacksDisabled': 'boolean',
};
1 change: 1 addition & 0 deletions src/Resources/config/services/express_checkout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<argument type="service" id="Swag\PayPal\OrdersApi\Builder\PayPalOrderBuilder"/>
<argument type="service" id="Swag\PayPal\RestApi\V2\Resource\OrderResource"/>
<argument type="service" id="Swag\PayPal\Checkout\Cart\Service\CartPriceService"/>
<argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService"/>
<argument type="service" id="router"/>
<argument type="service" id="monolog.logger.paypal"/>
</service>
Expand Down
2 changes: 2 additions & 0 deletions src/Setting/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ final class Settings
public const VAULTING_ENABLED_WALLET = self::SYSTEM_CONFIG_DOMAIN . 'vaultingEnabledWallet';
public const VAULTING_ENABLED_ACDC = self::SYSTEM_CONFIG_DOMAIN . 'vaultingEnabledACDC';
public const VAULTING_ENABLED_VENMO = self::SYSTEM_CONFIG_DOMAIN . 'vaultingEnabledVenmo';
public const PAYPAL_CALLBACKS_DISABLED = self::SYSTEM_CONFIG_DOMAIN . 'paypalCallbacksDisabled';

/**
* @internal these may change at any time
Expand Down Expand Up @@ -98,6 +99,7 @@ final class Settings
self::VAULTING_ENABLED_VENMO => false,
self::CROSS_BORDER_MESSAGING_ENABLED => false,
self::CROSS_BORDER_BUYER_COUNTRY => null,
self::PAYPAL_CALLBACKS_DISABLED => true,
];

public const LIVE_CREDENTIAL_KEYS = [
Expand Down
6 changes: 5 additions & 1 deletion src/Webhook/Registration/WebhookSystemConfigHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public function checkWebhookBefore(array $newData): array
continue;
}

if (!$this->configHasChangedSettings($newSettings, $oldActualSettings)) {
if (!$newData[Settings::PAYPAL_CALLBACKS_DISABLED] && !$this->configHasChangedSettings($newSettings, $oldActualSettings)) {
// No writing of new credentials in this Sales Channel
continue;
}
Expand Down Expand Up @@ -105,6 +105,10 @@ public function checkWebhookAfter(array $salesChannelIds): array
$salesChannelId = null;
}

if ($this->systemConfigService->get(Settings::PAYPAL_CALLBACKS_DISABLED, $salesChannelId)) {
continue;
}

$newSettings = $this->fetchSettings($salesChannelId);
if (empty(\array_filter($newSettings))) {
// has no own valid configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Framework\Test\TestCaseBase\IntegrationTestBehaviour;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\PayPalSDK\Struct\V2\Order;
use Swag\PayPal\Checkout\Cart\Service\CartPriceService;
use Swag\PayPal\Checkout\Exception\OrderZeroValueException;
use Swag\PayPal\Checkout\ExpressCheckout\SalesChannel\ExpressCreateOrderRoute;
Expand Down Expand Up @@ -47,7 +49,7 @@ class ExpressCreateOrderRouteTest extends TestCase
use GatewayTestBehaviour;
use IntegrationTestBehaviour;

public function testCreatePayment(): void
public function testCreatePaymentWithZeroValueCart(): void
{
$salesChannelContext = $this->getSalesChannelContext();

Expand All @@ -62,6 +64,7 @@ public function testCreatePayment(): void
$this->getContainer()->get(PayPalOrderBuilder::class),
new OrderResource(self::orderGateway(), new ApiContextFactoryMock()),
$this->getContainer()->get(CartPriceService::class),
$this->getContainer()->get(SystemConfigService::class),
$this->createMock(RouterInterface::class),
new NullLogger(),
);
Expand All @@ -71,21 +74,50 @@ public function testCreatePayment(): void
$route->createPayPalOrder(new Request(), $salesChannelContext);
}

public function testCreatePaymentWithZeroValueCart(): void
public function testCreatePayment(): void
{
$salesChannelContext = $this->getSalesChannelContext();

$response = $this->createRoute()->createPayPalOrder(new Request(), $salesChannelContext);

static::assertSame(Response::HTTP_OK, $response->getStatusCode());
static::assertSame(CreateOrderCapture::ID, $response->getToken());

$request = $this->getClient()->getLast();
static::assertNotNull($request);

$order = (new Order())->assign($request->getRequestBody() ?? []);

$experienceContext = $order->getPaymentSource()?->getPaypal()?->getExperienceContext();
static::assertNotNull($experienceContext);
static::assertNotNull($experienceContext->getOrderUpdateCallbackConfig());
}

private function createRoute(): ExpressCreateOrderRoute
public function testCreateWithoutShippingCallback(): void
{
$salesChannelContext = $this->getSalesChannelContext();

$response = $this->createRoute(true)->createPayPalOrder(new Request(), $salesChannelContext);

static::assertSame(Response::HTTP_OK, $response->getStatusCode());
static::assertSame(CreateOrderCapture::ID, $response->getToken());

$request = $this->getClient()->getLast();
static::assertNotNull($request);

$order = (new Order())->assign($request->getRequestBody() ?? []);

$experienceContext = $order->getPaymentSource()?->getPaypal()?->getExperienceContext();
static::assertNotNull($experienceContext);
static::assertNull($experienceContext->getOrderUpdateCallbackConfig());
}

private function createRoute(bool $callbacksDisabled = false): ExpressCreateOrderRoute
{
$systemConfig = $this->createSystemConfigServiceMock([
Settings::CLIENT_ID => 'testClientId',
Settings::CLIENT_SECRET => 'testClientSecret',
Settings::PAYPAL_CALLBACKS_DISABLED => $callbacksDisabled,
]);

$priceFormatter = new PriceFormatter();
Expand All @@ -108,6 +140,7 @@ private function createRoute(): ExpressCreateOrderRoute
$paypalOrderBuilder,
new OrderResource(self::orderGateway(), new ApiContextFactoryMock()),
$this->getContainer()->get(CartPriceService::class),
$systemConfig,
$this->createMock(RouterInterface::class),
new NullLogger(),
);
Expand Down
Loading