Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
4 changes: 3 additions & 1 deletion controllers/front/AbstractOpcJsonFrontController.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use PrestaShop\Module\PsOnePageCheckout\Translation\ModuleTranslation;

abstract class Ps_OnepagecheckoutAbstractOpcJsonFrontController extends ModuleFrontController
{
/** @var bool */
Expand Down Expand Up @@ -32,7 +34,7 @@ protected function buildTechnicalErrorResponse(): array
'success' => false,
'errors' => [
'' => [
$this->trans('One-page checkout is currently unavailable.', [], 'Shop.Notifications.Error'),
$this->trans('One-page checkout is currently unavailable.', [], ModuleTranslation::SHOP_DOMAIN),
],
],
];
Expand Down
1 change: 1 addition & 0 deletions controllers/front/addresseslist.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ protected function handleOpcRequest(): array
try {
$handler = new OnePageCheckoutAddressesListHandler(
$this->context,
$this->module->getTranslator(),
new CheckoutCustomerContextResolver($this->context)
);
$response = $handler->handle();
Expand Down
37 changes: 37 additions & 0 deletions controllers/front/giftwrapping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

use PrestaShop\Module\PsOnePageCheckout\Checkout\Ajax\OrderOptions\OnePageCheckoutGiftWrappingHandler;

require_once __DIR__ . '/AbstractOpcJsonFrontController.php';

class Ps_OnepagecheckoutGiftwrappingModuleFrontController extends Ps_OnepagecheckoutAbstractOpcJsonFrontController
{
/**
* @return array<string,mixed>
*/
protected function handleOpcRequest(): array
{
if (!$this->isOpcAvailable()) {
Comment thread
ThbPS marked this conversation as resolved.
Outdated
return $this->buildTechnicalErrorResponse();
}

$handler = new OnePageCheckoutGiftWrappingHandler($this->context, $this->module->getTranslator());
$result = $handler->handle(Tools::getAllValues());

if (empty($result['success']) || !isset($result['cart'])) {
return $result;
}

return [
'success' => true,
'preview' => $this->render(
'checkout/_partials/cart-summary',
[
'cart' => $result['cart'],
'static_token' => Tools::getToken(false),
]
),
'totals' => $result['totals'],
];
}
}
50 changes: 43 additions & 7 deletions controllers/front/opcsubmit.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ class Ps_OnepagecheckoutOpcSubmitModuleFrontController extends Ps_Onepagecheckou
*/
protected function handleOpcRequest(): array
{
$requestParameters = Tools::getAllValues();

if (!$this->isOpcAvailable()) {
return $this->buildTechnicalErrorResponse();
$response = $this->buildTechnicalErrorResponse();
$this->persistReloadErrorStateIfNeeded($response, $requestParameters);

return $response;
}

if (strtoupper($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
Comment thread
ThbPS marked this conversation as resolved.
Expand All @@ -32,16 +37,16 @@ protected function handleOpcRequest(): array
}

try {
$result = $this->createSubmitHandler()->handle(Tools::getAllValues());
if (($result['success'] ?? false) === true && $this->module instanceof Ps_Onepagecheckout) {
$response = $this->createSubmitHandler()->handle($requestParameters);
$this->persistReloadErrorStateIfNeeded($response, $requestParameters);
if (($response['success'] ?? false) === true && $this->module instanceof Ps_Onepagecheckout) {
Comment thread
ThbPS marked this conversation as resolved.
Outdated
Analytics::trackCheckoutCompleted(
(bool) Configuration::get('PS_GUEST_CHECKOUT_ENABLED') ? 'yes' : 'no',
trim((string) (Tools::getValue('paymentMethod') ?? '')),
(string) $this->module->version
);
}

return $result;
return $response;
} catch (Throwable $exception) {
PrestaShopLogger::addLog(
sprintf('ps_onepagecheckout opcSubmit runtime exception: %s', $exception->getMessage()),
Expand All @@ -52,7 +57,10 @@ protected function handleOpcRequest(): array
true
);

return $this->buildTechnicalErrorResponse();
$response = $this->buildTechnicalErrorResponse();
$this->persistReloadErrorStateIfNeeded($response, $requestParameters);

return $response;
}
}

Expand All @@ -77,7 +85,7 @@ protected function createSubmitHandler(): OnePageCheckoutSubmitHandler
new PaymentOptionsFinder(),
new ConditionsToApproveFinder($this->context, $translator)
),
new OnePageCheckoutSubmitValidationStateStorage($this->context)
$this->createSubmitValidationStateStorage()
);
}

Expand All @@ -98,4 +106,32 @@ private function getCheckoutUrl(): string
? (string) $this->context->link->getPageLink('order')
: '';
}

/**
* @param array<string,mixed> $response
* @param array<string,mixed> $requestParameters
*/
private function persistReloadErrorStateIfNeeded(array $response, array $requestParameters): void
{
if (empty($response['reload']) || !isset($response['errors']) || !is_array($response['errors'])) {
return;
}

$this->createSubmitValidationStateStorage()->save([
'cart_id' => $this->getCurrentCartId(),
'validation_errors' => [],
'form_errors' => $response['errors'],
'submitted_values' => $requestParameters,
]);
}

protected function createSubmitValidationStateStorage(): OnePageCheckoutSubmitValidationStateStorage
{
return new OnePageCheckoutSubmitValidationStateStorage($this->context);
}

private function getCurrentCartId(): int
{
return isset($this->context->cart) ? (int) ($this->context->cart->id ?? 0) : 0;
}
}
2 changes: 1 addition & 1 deletion controllers/front/selectpayment.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ protected function handleOpcRequest(): array
return $this->buildTechnicalErrorResponse();
}

$handler = new OnePageCheckoutSelectPaymentHandler($this->context);
$handler = new OnePageCheckoutSelectPaymentHandler($this->context, $this->module->getTranslator());

return $handler->handle(Tools::getAllValues());
}
Expand Down
2 changes: 2 additions & 0 deletions docs/RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Both entry points must render the same module-owned configuration flow (no redir
1. Each migration lot must be implemented with a story/test pair and incremental automated verification.
2. Every lot must ship unit tests for local logic and integration tests for observable behavior.
3. After JS changes, rebuild `views/public/*` and verify the runtime contracts through tests.
4. Automated E2E investigation must start with incremental scope (`test:file`, `test:lot`, or `tests/e2e` `test:all:incremental`) before widening execution.
5. When a human explicitly asks for a full rerun to inventory failures, use exhaustive execution so `serial` suites cannot mask later failures (`tests/e2e` `npm run test:all` or `npm run test:all:exhaustive`).

## Delivery checklist

Expand Down
38 changes: 34 additions & 4 deletions ps_onepagecheckout.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use PrestaShop\Module\PsOnePageCheckout\Checkout\OnePageCheckoutAvailability;
use PrestaShop\Module\PsOnePageCheckout\Checkout\OnePageCheckoutProcessBuilder;
use PrestaShop\Module\PsOnePageCheckout\Form\BackOfficeConfigurationForm;
use PrestaShop\Module\PsOnePageCheckout\Translation\ModuleTranslation;
use Symfony\Contracts\Translation\TranslatorInterface;

class Ps_Onepagecheckout extends Module
Expand All @@ -35,7 +36,7 @@ public function __construct()

$tabNames = [];
foreach (Language::getLanguages(true) as $lang) {
$tabNames[$lang['locale']] = $this->trans('Checkout', [], 'Modules.Psonepagecheckout.Admin', $lang['locale']);
$tabNames[$lang['locale']] = $this->trans('Checkout', [], ModuleTranslation::ADMIN_DOMAIN, $lang['locale']);
}
$this->tabs = [
[
Expand All @@ -44,17 +45,17 @@ public function __construct()
'name' => $tabNames,
'parent_class_name' => 'AdminParentThemes',
'wording' => 'Checkout',
'wording_domain' => 'Modules.Psonepagecheckout.Admin',
'wording_domain' => ModuleTranslation::ADMIN_DOMAIN,
],
];

parent::__construct();

$this->displayName = $this->trans('One-page checkout', [], 'Modules.Psonepagecheckout.Admin');
$this->displayName = $this->trans('One-page checkout', [], ModuleTranslation::ADMIN_DOMAIN);
$this->description = $this->trans(
'Native one-page checkout.',
[],
'Modules.Psonepagecheckout.Admin'
ModuleTranslation::ADMIN_DOMAIN
);
$this->ps_versions_compliancy = ['min' => '9.0.0', 'max' => _PS_VERSION_];
$this->controllers = [
Expand Down Expand Up @@ -276,6 +277,15 @@ public function hookActionFrontControllerSetMedia(): void
null,
true
),
'giftWrapping' => $this->context->link->getModuleLink(
$this->name,
'giftwrapping',
['ajax' => 1, 'action' => 'opcGiftWrapping'],
null,
null,
null,
true
),
'cartTotals' => $this->context->link->getModuleLink(
$this->name,
'carttotals',
Expand All @@ -286,6 +296,25 @@ public function hookActionFrontControllerSetMedia(): void
true
),
],
'messages' => [
'missingGuestInitUrl' => $this->trans('Unable to initialize checkout customer.', [], ModuleTranslation::SHOP_DOMAIN),
'missingAddressFormUrl' => $this->trans('Unable to refresh addresses.', [], ModuleTranslation::SHOP_DOMAIN),
'loadCarriersFailed' => $this->trans('Unable to load delivery methods.', [], ModuleTranslation::SHOP_DOMAIN),
'missingCarrierSelectionPayload' => $this->trans('Missing delivery option.', [], ModuleTranslation::SHOP_DOMAIN),
'selectCarrierFailed' => $this->trans('Unable to select the delivery method.', [], ModuleTranslation::SHOP_DOMAIN),
'loadPaymentMethodsFailed' => $this->trans('Unable to load payment methods.', [], ModuleTranslation::SHOP_DOMAIN),
'missingPaymentSelectionPayload' => $this->trans('Missing payment selection payload.', [], ModuleTranslation::SHOP_DOMAIN),
'selectPaymentFailed' => $this->trans('Unable to select the payment method.', [], ModuleTranslation::SHOP_DOMAIN),
'statesLoadFailed' => $this->trans('Unable to load states.', [], ModuleTranslation::SHOP_DOMAIN),
'missingSaveAddressUrl' => $this->trans('Unable to save address.', [], ModuleTranslation::SHOP_DOMAIN),
'saveAddressFailed' => $this->trans('Unable to save address.', [], ModuleTranslation::SHOP_DOMAIN),
'missingDeleteAddressUrl' => $this->trans('Unable to delete address.', [], ModuleTranslation::SHOP_DOMAIN),
'deleteAddressFailed' => $this->trans('Unable to delete address.', [], ModuleTranslation::SHOP_DOMAIN),
'refreshAddressesFailed' => $this->trans('Unable to refresh addresses.', [], ModuleTranslation::SHOP_DOMAIN),
'missingPaymentForm' => $this->trans('Unable to initialize the selected payment method.', [], ModuleTranslation::SHOP_DOMAIN),
'missingSubmitUrl' => $this->trans('Unable to submit checkout.', [], ModuleTranslation::SHOP_DOMAIN),
'submitFailed' => $this->trans('Unable to submit checkout.', [], ModuleTranslation::SHOP_DOMAIN),
],
];

$this->addOpcJavascriptDefinition([
Expand Down Expand Up @@ -358,6 +387,7 @@ protected function registerOpcJavascriptAssets(): void
['module-ps-onepagecheckout-select-carrier', 'views/public/opc-carrier-select.bundle.js', 154],
['module-ps-onepagecheckout-payment-methods', 'views/public/opc-payment-list.bundle.js', 155],
['module-ps-onepagecheckout-select-payment', 'views/public/opc-payment-select.bundle.js', 156],
['module-ps-onepagecheckout-gift-wrapping', 'views/public/opc-gift-wrapping.bundle.js', 157],
] as [$id, $path, $priority]) {
$this->context->controller->registerJavascript(
$id,
Expand Down
17 changes: 7 additions & 10 deletions src/Analytics/Analytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,21 +82,18 @@ public static function bootstrap(bool $moduleSegmentEnabled): void

public static function trackCheckoutStarted(string $guestCheckoutActive, string $moduleVersion): void
{
self::trackEvent(self::EVENT_OPC_CHECKOUT_STARTED, array_merge(
self::trackEvent(self::EVENT_OPC_CHECKOUT_STARTED,
['guest_checkout_active' => $guestCheckoutActive],
self::buildCommonProps($moduleVersion)
));
$moduleVersion
);
}

public static function trackCheckoutCompleted(string $guestCheckoutActive, string $paymentMethod, string $moduleVersion): void
{
self::trackEvent(self::EVENT_OPC_CHECKOUT_COMPLETED, array_merge(
[
'guest_checkout_active' => $guestCheckoutActive,
'payment_method' => $paymentMethod,
],
self::buildCommonProps($moduleVersion)
));
self::trackEvent(self::EVENT_OPC_CHECKOUT_COMPLETED, [
'guest_checkout_active' => $guestCheckoutActive,
'payment_method' => $paymentMethod,
], $moduleVersion);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,6 @@ public function getTemplateVariables(array $requestParameters): array
continue;
}

if ($name === 'invoice_id_country' && $useSameAddress) {
continue;
}

if (in_array($name, ['id_address_delivery', 'id_address_invoice'], true) && (int) $requestParameters[$name] <= 0) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@

namespace PrestaShop\Module\PsOnePageCheckout\Checkout\Ajax;

use PrestaShop\Module\PsOnePageCheckout\Translation\ModuleTranslation;
use Symfony\Contracts\Translation\TranslatorInterface;

class OnePageCheckoutAddressesListHandler
{
private \Context $context;
private TranslatorInterface $translator;
private CheckoutCustomerContextResolver $customerResolver;
private CheckoutCustomerTemplateBuilder $customerTemplateBuilder;

public function __construct(
\Context $context,
TranslatorInterface $translator,
CheckoutCustomerContextResolver $customerResolver,
?CheckoutCustomerTemplateBuilder $customerTemplateBuilder = null,
) {
$this->context = $context;
$this->translator = $translator;
$this->customerResolver = $customerResolver;
$this->customerTemplateBuilder = $customerTemplateBuilder ?? new CheckoutCustomerTemplateBuilder(
$context,
Expand All @@ -28,7 +34,9 @@ public function handle(): array
{
$customer = $this->customerResolver->resolve();
if (!$customer instanceof \Customer) {
return CheckoutAjaxResponse::error('Unable to resolve checkout customer.');
return CheckoutAjaxResponse::error(
ModuleTranslation::translate($this->translator, 'Unable to resolve checkout customer.')
);
}

$customerTemplate = $this->customerTemplateBuilder->build();
Expand Down
19 changes: 11 additions & 8 deletions src/Checkout/Ajax/Address/OnePageCheckoutDeleteAddressHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PrestaShop\Module\PsOnePageCheckout\Checkout\Ajax;

use PrestaShop\Module\PsOnePageCheckout\Translation\ModuleTranslation;
use Symfony\Contracts\Translation\TranslatorInterface;

class OnePageCheckoutDeleteAddressHandler
Expand Down Expand Up @@ -32,12 +33,16 @@ public function handle(array $requestParameters = []): array
{
$customer = $this->customerResolver->resolve();
if (!$customer instanceof \Customer) {
return CheckoutAjaxResponse::error('Unable to resolve checkout customer.');
return CheckoutAjaxResponse::error(
ModuleTranslation::translate($this->translator, 'Unable to resolve checkout customer.')
);
}

$address = $this->loadOwnedAddress($customer, (int) ($requestParameters['id_address'] ?? 0));
if (!$address instanceof \Address) {
return CheckoutAjaxResponse::error('Unable to load the requested address.');
return CheckoutAjaxResponse::error(
ModuleTranslation::translate($this->translator, 'Unable to load the requested address.')
);
}

$addressId = (int) $address->id;
Expand All @@ -48,7 +53,9 @@ public function handle(array $requestParameters = []): array
&& (int) $this->context->cart->id_address_invoice === $addressId;

if (!$this->buildAddressPersister($customer)->delete($address, \Tools::getToken(true, $this->context))) {
return CheckoutAjaxResponse::error('Unable to delete address.');
return CheckoutAjaxResponse::error(
ModuleTranslation::translate($this->translator, 'Unable to delete address.')
);
}

$remainingAddresses = $customer->getAddresses((int) $this->context->language->id);
Expand All @@ -68,11 +75,7 @@ public function handle(array $requestParameters = []): array
return [
'success' => true,
'id_address' => $addressId,
'message' => $this->translator->trans(
'Address successfully deleted.',
[],
'Shop.Notifications.Success'
),
'message' => ModuleTranslation::translate($this->translator, 'Address successfully deleted.'),
];
}

Expand Down
Loading
Loading