Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: paylater button #117

Merged
merged 15 commits into from
Feb 12, 2025
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# 9.6.6
# 9.7.0
- PPI-1044 - Improved compatibility of Vaulting with Store API usage and Headless setups
- PPI-1000 - Fixes an issue, where PayLater is shown in cases where it should not be available

# 9.6.5
- PPI-1025 - Improves the performance of the installment banner in the Storefront
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG_de-DE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# 9.6.6
# 9.7.0
- PPI-1044 - Verbesserte Kompatibilität von Vaulting mit der Store-API und Headless
- PPI-1000 - Behebt ein Problem, bei dem 'Später Bezahlen' in Fällen angezeigt wird, in denen es nicht verfügbar sein sollte

# 9.6.5
- PPI-1025 - Verbessert die Performance des Ratenzahlungsbanners in der Storefront
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
use Swag\PayPal\Setting\Service\CredentialsUtilInterface;
use Swag\PayPal\Setting\Settings;
use Swag\PayPal\Storefront\Data\Service\AbstractScriptDataService;
use Swag\PayPal\Util\Availability\AvailabilityContext;
use Swag\PayPal\Util\Availability\AvailabilityContextBuilder;
use Swag\PayPal\Util\Lifecycle\Method\PayLaterMethodData;
use Swag\PayPal\Util\LocaleCodeProvider;
use Swag\PayPal\Util\PaymentMethodUtil;
use Symfony\Component\Routing\RouterInterface;
Expand All @@ -36,6 +39,7 @@ public function __construct(
SystemConfigService $systemConfigService,
CredentialsUtilInterface $credentialsUtil,
private readonly CartPriceService $cartPriceService,
private readonly PayLaterMethodData $payLaterMethodData,
) {
parent::__construct($localeCodeProvider, $systemConfigService, $credentialsUtil);
}
Expand Down Expand Up @@ -63,7 +67,9 @@ public function buildExpressCheckoutButtonData(
$salesChannelId = $salesChannelContext->getSalesChannelId();

$fundingSources = ['paypal', 'venmo'];
if ($this->systemConfigService->getBool(Settings::ECS_SHOW_PAY_LATER, $salesChannelId)) {

$availabilityContext = AvailabilityContextBuilder::buildFromCart($cart, $salesChannelContext);
if ($this->showPayLater($salesChannelId, $availabilityContext)) {
array_splice($fundingSources, 1, 0, ['paylater']);
}

Expand All @@ -90,7 +96,7 @@ public function buildExpressCheckoutButtonData(
'addErrorUrl' => $this->router->generate('frontend.paypal.error'),
'handleErrorUrl' => $this->router->generate('frontend.paypal.handle-error'),
'cancelRedirectUrl' => $this->router->generate($addProductToCart ? 'frontend.checkout.cart.page' : 'frontend.checkout.register.page'),
'showPayLater' => $this->systemConfigService->getBool(Settings::ECS_SHOW_PAY_LATER, $salesChannelId),
'showPayLater' => $this->showPayLater($salesChannelId, $availabilityContext),
'fundingSources' => $fundingSources,
]);
}
Expand All @@ -105,4 +111,10 @@ protected function getButtonLanguage(SalesChannelContext $context): string
$this->localeCodeProvider->getLocaleCodeFromContext($context->getContext())
);
}

private function showPayLater(string $salesChannelId, AvailabilityContext $availabilityContext): bool
{
return $this->systemConfigService->getBool(Settings::ECS_SHOW_PAY_LATER, $salesChannelId)
&& $this->payLaterMethodData->isAvailable($availabilityContext);
}
}
64 changes: 39 additions & 25 deletions src/Installment/Banner/InstallmentBannerSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Shopware\Storefront\Page\Checkout\Offcanvas\OffcanvasCartPageLoadedEvent;
use Shopware\Storefront\Page\Checkout\Register\CheckoutRegisterPage;
use Shopware\Storefront\Page\Checkout\Register\CheckoutRegisterPageLoadedEvent;
use Shopware\Storefront\Page\Page;
use Shopware\Storefront\Page\PageLoadedEvent;
use Shopware\Storefront\Page\Product\ProductPage;
use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
Expand All @@ -29,6 +30,9 @@
use Swag\PayPal\Installment\Banner\Service\BannerDataServiceInterface;
use Swag\PayPal\Setting\Exception\PayPalSettingsInvalidException;
use Swag\PayPal\Setting\Service\SettingsValidationServiceInterface;
use Swag\PayPal\Util\Availability\AvailabilityContext;
use Swag\PayPal\Util\Availability\AvailabilityContextBuilder;
use Swag\PayPal\Util\Lifecycle\Method\PayLaterMethodData;
use Swag\PayPal\Util\PaymentMethodUtil;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

Expand All @@ -41,28 +45,14 @@ class InstallmentBannerSubscriber implements EventSubscriberInterface
public const PAYPAL_INSTALLMENT_BANNER_DATA_EXTENSION_ID = 'payPalInstallmentBannerData';
public const PAYPAL_INSTALLMENT_BANNER_DATA_CART_PAGE_EXTENSION_ID = 'payPalInstallmentBannerDataCheckoutCart';

private SettingsValidationServiceInterface $settingsValidationService;

private PaymentMethodUtil $paymentMethodUtil;

private BannerDataServiceInterface $bannerDataService;

private LoggerInterface $logger;

private ExcludedProductValidator $excludedProductValidator;

public function __construct(
SettingsValidationServiceInterface $settingsValidationService,
PaymentMethodUtil $paymentMethodUtil,
BannerDataServiceInterface $bannerDataService,
ExcludedProductValidator $excludedProductValidator,
LoggerInterface $logger,
private readonly SettingsValidationServiceInterface $settingsValidationService,
private readonly PaymentMethodUtil $paymentMethodUtil,
private readonly BannerDataServiceInterface $bannerDataService,
private readonly ExcludedProductValidator $excludedProductValidator,
private readonly LoggerInterface $logger,
private readonly PayLaterMethodData $payLaterMethodData,
) {
$this->settingsValidationService = $settingsValidationService;
$this->paymentMethodUtil = $paymentMethodUtil;
$this->bannerDataService = $bannerDataService;
$this->excludedProductValidator = $excludedProductValidator;
$this->logger = $logger;
}

public static function getSubscribedEvents(): array
Expand Down Expand Up @@ -95,13 +85,23 @@ public function addInstallmentBanner(PageLoadedEvent $pageLoadedEvent): void
/** @var CheckoutCartPage|CheckoutConfirmPage|CheckoutRegisterPage|OffcanvasCartPage|ProductPage $page */
$page = $pageLoadedEvent->getPage();

if ($page instanceof ProductPage
&& $this->excludedProductValidator->isProductExcluded($page->getProduct(), $pageLoadedEvent->getSalesChannelContext())) {
return;
if ($page instanceof ProductPage) {
if ($this->excludedProductValidator->isProductExcluded($page->getProduct(), $pageLoadedEvent->getSalesChannelContext())) {
return;
}

$availabilityContext = AvailabilityContextBuilder::buildFromProduct($page->getProduct(), $salesChannelContext);
}

if (!$page instanceof ProductPage) {
if ($this->excludedProductValidator->cartContainsExcludedProduct($page->getCart(), $pageLoadedEvent->getSalesChannelContext())) {
return;
}

$availabilityContext = AvailabilityContextBuilder::buildFromCart($page->getCart(), $salesChannelContext);
}

if (!$page instanceof ProductPage
&& $this->excludedProductValidator->cartContainsExcludedProduct($page->getCart(), $pageLoadedEvent->getSalesChannelContext())) {
if (!$this->shouldDisplayPayLaterBanner($page, $availabilityContext)) {
return;
}

Expand Down Expand Up @@ -152,4 +152,18 @@ public function addInstallmentBannerPagelet(PageletLoadedEvent $pageletLoadedEve
$bannerData
);
}

private function shouldDisplayPayLaterBanner(Page $page, AvailabilityContext $availabilityContext): bool
{
return $this->pageOfCorrectType($page)
? $this->payLaterMethodData->isAvailable($availabilityContext)
: true;
}

private function pageOfCorrectType(Page $page): bool
{
return $page instanceof CheckoutRegisterPage
|| $page instanceof OffcanvasCartPage
|| $page instanceof CheckoutConfirmPage;
}
}
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 @@ -13,6 +13,7 @@
<argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService"/>
<argument type="service" id="Swag\PayPal\Setting\Service\CredentialsUtil"/>
<argument type="service" id="Swag\PayPal\Checkout\Cart\Service\CartPriceService"/>
<argument type="service" id="Swag\PayPal\Util\Lifecycle\Method\PayLaterMethodData"/>
</service>

<service id="Swag\PayPal\Checkout\ExpressCheckout\SalesChannel\ExpressPrepareCheckoutRoute" public="true">
Expand Down
1 change: 1 addition & 0 deletions src/Resources/config/services/installment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<argument type="service" id="Swag\PayPal\Installment\Banner\Service\BannerDataService"/>
<argument type="service" id="Swag\PayPal\Checkout\Cart\Service\ExcludedProductValidator"/>
<argument type="service" id="monolog.logger.paypal"/>
<argument type="service" id="Swag\PayPal\Util\Lifecycle\Method\PayLaterMethodData"/>
<tag name="kernel.event_subscriber"/>
</service>

Expand Down
1 change: 0 additions & 1 deletion src/Storefront/Data/Service/VaultDataService.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Swag\PayPal\DataAbstractionLayer\VaultToken\VaultTokenEntity;
use Swag\PayPal\RestApi\V1\Resource\TokenResourceInterface;
use Swag\PayPal\Storefront\Data\Struct\VaultData;
use Swag\PayPal\Util\Lifecycle\Method\ACDCMethodData;
use Swag\PayPal\Util\Lifecycle\Method\PaymentMethodDataRegistry;
Expand Down
83 changes: 83 additions & 0 deletions src/Util/Availability/AvailabilityContextBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php declare(strict_types=1);
/*
* (c) shopware AG <info@shopware.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Swag\PayPal\Util\Availability;

use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
use Shopware\Core\Content\Product\State;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Framework\Struct\ArrayStruct;
use Shopware\Core\System\SalesChannel\SalesChannelContext;

/**
* @internal
*/
#[Package('checkout')]
final class AvailabilityContextBuilder
{
private function __construct()
{
}

public static function buildFromCart(Cart $cart, SalesChannelContext $salesChannelContext): AvailabilityContext
{
return self::buildContext(
$salesChannelContext,
$cart->getPrice()->getTotalPrice(),
$salesChannelContext->hasExtension('subscription'),
$cart->getLineItems()->hasLineItemWithState(State::IS_DOWNLOAD)
);
}

public static function buildFromProduct(SalesChannelProductEntity $product, SalesChannelContext $salesChannelContext): AvailabilityContext
{
return self::buildContext(
$salesChannelContext,
$product->getCalculatedPrice()->getTotalPrice(),
$salesChannelContext->hasExtension('subscription'),
\in_array(State::IS_DOWNLOAD, $product->getStates(), true)
);
}

public static function buildFromOrder(OrderEntity $order, SalesChannelContext $salesChannelContext): AvailabilityContext
{
return self::buildContext(
$salesChannelContext,
$order->getPrice()->getTotalPrice(),
$order->getExtensionOfType('foreignKeys', ArrayStruct::class)?->get('subscriptionId') !== null,
(bool) $order->getLineItems()?->hasLineItemWithState(State::IS_DOWNLOAD)
);
}

private static function buildContext(
SalesChannelContext $salesChannelContext,
float $price,
bool $subscription,
bool $downloadable,
): AvailabilityContext {
$context = new AvailabilityContext();

$context->assign([
'billingCountryCode' => self::getBillingCountryCode($salesChannelContext),
'currencyCode' => $salesChannelContext->getCurrency()->getIsoCode(),
'totalAmount' => $price,
'subscription' => $subscription,
'salesChannelId' => $salesChannelContext->getSalesChannelId(),
'hasDigitalProducts' => $downloadable,
]);

return $context;
}

private static function getBillingCountryCode(SalesChannelContext $salesChannelContext): string
{
return $salesChannelContext->getCustomer()?->getActiveBillingAddress()?->getCountry()?->getIso()
?? $salesChannelContext->getShippingLocation()->getCountry()->getIso() ?? '';
}
}
43 changes: 6 additions & 37 deletions src/Util/Availability/AvailabilityService.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Checkout\Payment\PaymentMethodCollection;
use Shopware\Core\Checkout\Payment\PaymentMethodEntity;
use Shopware\Core\Content\Product\State;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Framework\Struct\ArrayStruct;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Swag\PayPal\Util\Lifecycle\Method\PaymentMethodDataRegistry;

Expand All @@ -37,7 +35,7 @@ public function filterPaymentMethods(PaymentMethodCollection $paymentMethods, Ca
{
$handlers = [];

$context = $this->buildAvailabilityContext($cart, $salesChannelContext);
$context = AvailabilityContextBuilder::buildFromCart($cart, $salesChannelContext);

foreach ($paymentMethods as $paymentMethod) {
if (!$this->isAvailable($paymentMethod, $context)) {
Expand All @@ -55,12 +53,7 @@ public function filterPaymentMethodsByOrder(PaymentMethodCollection $paymentMeth
{
$handlers = [];

$context = $this->buildAvailabilityContext($cart, $salesChannelContext);
$context->assign([
'totalAmount' => $order->getPrice()->getTotalPrice(),
'subscription' => $order->getExtensionOfType('foreignKeys', ArrayStruct::class)?->get('subscriptionId') !== null,
'hasDigitalProducts' => (bool) $order->getLineItems()?->hasLineItemWithState(State::IS_DOWNLOAD),
]);
$context = AvailabilityContextBuilder::buildFromOrder($order, $salesChannelContext);

foreach ($paymentMethods as $paymentMethod) {
if (!$this->isAvailable($paymentMethod, $context)) {
Expand All @@ -73,9 +66,10 @@ public function filterPaymentMethodsByOrder(PaymentMethodCollection $paymentMeth

public function isPaymentMethodAvailable(PaymentMethodEntity $paymentMethod, Cart $cart, SalesChannelContext $salesChannelContext): bool
{
$context = $this->buildAvailabilityContext($cart, $salesChannelContext);

return $this->isAvailable($paymentMethod, $context);
return $this->isAvailable(
$paymentMethod,
AvailabilityContextBuilder::buildFromCart($cart, $salesChannelContext)
);
}

private function isAvailable(PaymentMethodEntity $paymentMethod, AvailabilityContext $context): bool
Expand All @@ -87,29 +81,4 @@ private function isAvailable(PaymentMethodEntity $paymentMethod, AvailabilityCon

return $methodData->isAvailable($context);
}

private function buildAvailabilityContext(Cart $cart, SalesChannelContext $salesChannelContext): AvailabilityContext
{
$context = new AvailabilityContext();

if (($customer = $salesChannelContext->getCustomer())
&& ($address = $customer->getActiveBillingAddress())
&& ($country = $address->getCountry())
&& ($isoCode = $country->getIso())) {
$billingCountryCode = $isoCode;
} else {
$billingCountryCode = $salesChannelContext->getShippingLocation()->getCountry()->getIso();
}

$context->assign([
'billingCountryCode' => $billingCountryCode,
'currencyCode' => $salesChannelContext->getCurrency()->getIsoCode(),
'totalAmount' => $cart->getPrice()->getTotalPrice(),
'subscription' => $salesChannelContext->hasExtension('subscription'),
'salesChannelId' => $salesChannelContext->getSalesChannelId(),
'hasDigitalProducts' => $cart->getLineItems()->hasLineItemWithState(State::IS_DOWNLOAD),
]);

return $context;
}
}
Loading
Loading