Skip to content

PRE-3742: harden Apple Pay AJAX endpoints (rate limit, contact sanitising, error handling) - #1240

Closed
hdelaforce-payplug wants to merge 2 commits into
developfrom
feature/PRE-3742_harden-apple-pay-ajax-endpoints
Closed

hdelaforce-payplug wants to merge 2 commits into
developfrom
feature/PRE-3742_harden-apple-pay-ajax-endpoints

Conversation

@hdelaforce-payplug

@hdelaforce-payplug hdelaforce-payplug commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Follow-up hardening of the Apple Pay wc-ajax endpoints after PRE-3734:

  • Anonymous visitors can no longer flood the shop with pending orders from the Apple Pay cart and product buttons.
  • When WooCommerce cannot create the Apple Pay cart order, the button shows an error instead of leaving the page blocked.
  • The product page add-to-cart request rejects invalid product ids and quantities.
  • Contact data sent by Apple Pay is cleaned before it reaches the order.
  • update_applepay_order no longer returns the whole order to the browser.
  • Two unused Apple Pay methods are removed.

Rate limit on place_order_with_dummy_data (Apple Pay cart and product page buttons)

  • New Front\ApplePayRateLimiter, checked only when the cart is not empty, before the order is created.
  • Two fixed-window counters stored in transients:
    • 10 attempts per 600 s per subject (logged-in user id, otherwise the WooCommerce session customer id);
    • 100 attempts per 600 s per REMOTE_ADDR.
  • X-Forwarded-For and other client-supplied headers are never read.
  • A refused attempt is not counted. An attempt whose order creation then fails is counted.
  • Refusal answers HTTP 200 with wp_send_json_error carrying both msg (classic cart/product JS) and message (Blocks). It creates no order and keeps the cart. A 429 would break the classic JS, which has no fail branch.
  • The new filter payplug_applepay_dummy_order_rate_limit receives ['subject' => ['limit' => 10, 'window' => 600], 'ip' => ['limit' => 100, 'window' => 600]]:
    • a limit or window of 0 or false disables that scope;
    • a non-array result disables both scopes;
    • malformed values fall back to the defaults.
  • Refusals are logged at warning level with the scope and a truncated wp_hash of the identity only. No IP or session id appears in clear.
  • New translatable string "Too many Apple Pay attempts. Please wait a few minutes and try again." (FR added to .po / .mo, .pot updated).

Order creation failures in place_order_with_dummy_data

  • Order creation moves to a private helper, create_cart_order().
  • Three failure cases are handled:
    • WC_Checkout::create_order() returns a WP_Error;
    • the created order cannot be loaded;
    • any \Throwable is thrown while the order is created and prepared, from create_order() to save() (previously only \Exception was caught).
  • In each case the endpoint answers HTTP 200 with wp_send_json_error carrying both msg and message ("Your order was cancelled."). It does not empty the cart or start a payment, and creates no order in the first two cases. The attempt still counts toward the rate limit.
  • Before this change, a WP_Error from create_order() caused a fatal error on $order->set_address(). The classic JS then never got a response it could read, and the page stayed blocked under its loading layer.
  • Logs are at warning level. They carry only the WP_Error code, order not found or the exception class, and never the exception message or customer data.

Minimal hardening of applepay_add_to_cart (product page button)

  • product_id / product_variation_id go through wp_unslash and must be numeric and greater than 0 before absint. Negative, non-numeric and non-scalar (array) values are refused.

  • An invalid product_id with a valid product_variation_id falls back to the variation. A valid product_id is still preferred over the variation id.

  • product_quantity:

    • absent or '' → 1;
    • otherwise it goes through wc_stock_amount(wp_unslash());
    • below 1, or non-scalar → refused.
  • With no valid id or no valid quantity, the endpoint answers the same msg + message error and adds nothing to the cart.

  • A \Throwable thrown while adding to the cart answers the same error.

  • Otherwise the WC()->cart->add_to_cart($product_id, $quantity) call and the { total } success response are unchanged.

  • Not part of this PR:

    • passing variation attributes to add_to_cart();
    • running woocommerce_add_to_cart_validation;
    • using the return value of add_to_cart().

    See the follow-ups below.

Sanitised contacts in update_applepay_order

  • New PayplugWoocommerceHelper::sanitize_apple_pay_contact():
    • keeps only the known Apple Pay keys;
    • applies wc_clean(wp_unslash()) and length limits (names, city, country 100; postcode 20; phone 30; address line and email 255);
    • validates the email with sanitize_email + is_email;
    • drops empty address lines and keeps at most two;
    • ignores addressLines sent as a string.
  • A missing, mistyped or invalid field is ignored. The request is not rejected.
  • The two switch blocks are replaced by setter maps (SHIPPING_CONTACT_SETTERS / BILLING_CONTACT_SETTERS) applied by a private apply_contact(). The mapping is unchanged: the shipping contact still provides the billing email, and the billing contact provides no email or phone.
  • Apostrophes are now stored without backslashes (O'Brien, not O\'Brien).

Response of update_applepay_order

  • Answers {"success":true} (wp_send_json_success()) instead of serialising the whole WC_Order (personal data, order key). No first-party caller reads the response: classic cart and product JS and the Blocks source were checked.

Dead code

  • Removed PayplugWoocommerceRequest::applepay_empty_cart() and ::applepay_add_to_cart(). Nothing hooks or calls them. The live handlers of Front\ApplePay and the ajax_url_applepay_* JS parameters stay.

Payment methods and flows touched: Apple Pay only (cart button, product page button, Blocks express via the same endpoints). No JS or Blocks bundle change, no UPC change, no dependency change.

Related Issue

Ticket: PRE-3742

Type of Change

[x] 🐛 Bug fix
[ ] ✨ New feature
[x] 💥 Breaking change
[x] ♻️ Refactor
[ ] 🔧 Configuration / CI
[ ] 🚀 Release (release/* branch targeting master)
[ ] 📦 Dependency update
[x] 🔒 Security fix
[ ] 📝 Documentation update


✅ Quality Checklist

Local Environment & Hooks

  • Local Git hooks (CaptainHook) are installed and executed cleanly.
  • Commit messages strictly follow the (PRE|SMP)-XXXX: description pattern.
  • Core configuration files (phpstan.neon / .php-cs-fixer.php) were generated successfully from .dist templates.

Testing & Code Quality

  • Coding style rules have been applied locally (composer cs:fix).
  • Static analysis checks pass with no new regressions (vendor/bin/phpstan).
  • I have added/updated unit or integration tests if applicable.
  • I have verified these changes locally on a native WooCommerce environment.

CI/CD Deployment Context

  • The CI pipeline passes fully on GitHub.
  • For Release Branches: If this is a release/* branch, I am targeting the correct base branch to allow the automated apply-release version bumping job to run.

Screenshots (if applicable)

N/A: no UI change. Two visible differences:

  • the Apple Pay button shows the new refusal message after more than 10 attempts in 10 minutes;
  • it shows "Your order was cancelled." instead of staying blocked when the cart order cannot be created.

Notes for Reviewer

Impact / BC

  • Breaking change (narrow):
    • two public methods are removed from PayplugWoocommerceRequest (applepay_empty_cart(), applepay_add_to_cart()). They are unhooked and have no callers in the plugin;
    • the response of wc_ajax_update_applepay_order changes shape (no first-party consumer). A third-party script that read the order from it would break.
  • The error responses of place_order_with_dummy_data (order not created) and applepay_add_to_cart now carry msg in addition to message. This is additive, and message is unchanged.
  • Shops behind a reverse proxy or CDN that hides the client IP in REMOTE_ADDR put all visitors in the same 100 / 600 s IP bucket. They can raise or disable the IP scope with payplug_applepay_dummy_order_rate_limit. This is stated in the changelog line.
  • PHP 7.4 syntax only. No UPC change or bump needed.
  • Changelog: three Fix: and two Change: lines for PRE-3742 in release.json (3.1.1):
    • Fix: rate limit;
    • Fix: contact sanitising;
    • Fix: order creation error and add-to-cart validation;
    • Change: update_applepay_order response;
    • Change: removed methods.
  • Translations: payplug.pot, payplug-fr_FR.po and payplug-fr_FR.mo updated for the new rate-limit string. Besides the new entry, the .mo diff only drops the POT-Creation-Date header. The order-creation error reuses WooCommerce's own "Your order was cancelled." string, so no new string is added for it.

Testing

  • PHPUnit was not run locally. The WordPress test library is missing on the host, and make test (Docker) fails on the composer platform check. The GitHub CI of this repo does not run PHPUnit either (it runs composer cs-lint, PHPCompatibility, the QA zip build and SonarCloud), so none of the new tests has run yet.
  • New tests in tests/phpunit/src: 48 test methods, 119 cases with data providers (counted in the files):
    • Front/ApplePayRateLimiter_test.php (23 methods, 41 cases): defaults, fixed window and restart, refused attempts not counted, user vs session vs IP counters, forwarded headers never read, unusable REMOTE_ADDR, no WC session, filter disable/lower/malformed values, hashed-only warning logs, transient key length;
    • Front/ApplePay_test.php (22 methods, 44 cases). Rate limit and contacts (13 methods): refusal in HTTP 200 without creating an order, the IP cap across sessions, an empty cart not using the quota, a failed order creation counted, a normal buyer retrying up to the limit, the filter disabling the limit, update_applepay_order answering success only, apostrophes, garbage contacts, partial contacts, invalid email, billing contact email/phone ignored, a full valid payload stored as before;
    • Front/ApplePay_test.php, order creation errors and add-to-cart (9 methods, 27 cases):
      • create_order() returning a WP_Error (forced through an exception on woocommerce_checkout_create_order, the real WooCommerce path);
      • an order id that cannot be loaded;
      • Error / TypeError / Exception thrown after creation;
      • each of these answering msg + message, keeping the cart and logging no customer data;
      • 14 invalid add-to-cart inputs (absent / empty / zero / non-numeric / trailing-text / negative ids, slashed quoted id, ids and quantity in arrays, quantity 0 / negative / non-numeric);
      • quantity absent or empty → 1;
      • slashed POST with a posted quantity;
      • variation paths (empty, absent or invalid product id);
      • product id preferred over the variation id;
      • a throw inside add_to_cart().
    • The test "a failed order creation is counted" was rewritten to fail on the real WP_Error path instead of an exception thrown after creation;
    • PayplugWoocommerceHelper_test.php (1 method, 32 cases) for sanitize_apple_pay_contact();
    • PayplugWoocommerceRequest_test.php (2 methods): the removed methods are gone and the Front\ApplePay handlers stay hooked;
    • shared fixtures extended in Support/WooCommerceAjaxFixtures.php.
  • Checks that were run locally:
    • php -l under PHP 7.4.33: exit 0 on the 9 PHP files of the first commit (also under 8.3) and on the 2 PHP files of the second commit (src/Front/ApplePay.php, tests/phpunit/src/Front/ApplePay_test.php);
    • php-cs-fixer dry-run on the touched PHP files: exit 0, 0 files to fix;
    • PHPStan: 138 errors, identical to the develop baseline (0 new, none in src/Front/ApplePay.php);
    • msgfmt --check on the FR catalogue: exit 0;
    • release.json parses as valid JSON.
  • CI on 6e409b9f:
    • Payplug Woocommerce CI passed: quality (composer cs-lint, PHP 7.4), PHP Compatibility 7.4-8.1, QA zip build, SonarCloud;
    • the Wiz scanners passed;
    • task-list-completed passed.
  • Not run: PHPUnit (see above), and manual verification on a WooCommerce shop (no sandbox Apple Pay run yet).

Checklist items left unticked

  • CaptainHook: the hooks are not installed in this clone (captainhook.json is present, .git/hooks is not). The commit messages follow the pattern anyway.
  • Native WooCommerce verification: not done yet, see the QA notes below.
  • Release branch item: not applicable, this is a feature branch targeting develop.
  • composer cs:fix is ticked on the basis of a php-cs-fixer dry-run on the touched files, which reported 0 files to fix.
  • The CI box is ticked because the CI workflow passed on the head commit. It does not cover PHPUnit, which this repo's CI does not run.

Suggested QA (sandbox)

  • Apple Pay on the classic cart, on the product page and in the Blocks checkout: a normal buyer is never blocked, and the order gets the shipping and billing contact.
  • More than 10 attempts within 10 minutes from the same session (open and cancel the sheet): the button shows the refusal message, no new order is created and the cart is kept.
  • add_filter('payplug_applepay_dummy_order_rate_limit', '__return_false'); disables both limits.
  • A name or address with an apostrophe is stored without a backslash.
  • Product page:
    • quantity field emptied or set to an invalid value (via dev tools): the add-to-cart call is refused, nothing is added;
    • a variable product bought through Apple Pay: the selected variation is added with the chosen quantity.
  • Forced order creation failure, e.g. a snippet add_action('woocommerce_checkout_create_order', function () { throw new Exception('test'); });: the button shows "Your order was cancelled.", the page is not left blocked, no order is created and the cart is kept.
  • Check the stored country value: Apple Pay sends a country name, not an ISO code. This PR passes it through unchanged, as before.
  • Known cosmetic issue: after a refusal, the classic JS still sends a cancel call without an order id. It gets a harmless 403, plus a warning log when debug is on.

Risk / focus

  • ApplePayRateLimiter::hit(): window anchoring, the transient expiry kept on each write, and no write on refusal.
  • ApplePayRateLimiter::limits(): normalisation of filter values (0 / false / non-array / malformed).
  • The IP check runs before the subject check, so a blocked IP creates no subject transient.
  • place_order_with_dummy_data / create_cart_order():
    • the cart is emptied only after an order was created, loaded and saved;
    • the \Throwable catch also wraps empty_cart() and process_cart_payment(). A throw there answers the same error, but the order already exists and the cart is already emptied. This matches the previous behaviour for \Exception.
  • get_posted_id() / get_posted_quantity(): the variation fallback and the quantity default of 1.
  • sanitize_apple_pay_contact() + apply_contact(): valid Apple Pay payloads must store the same values as before, apart from the removed backslashes.
  • Intentional: the class docblock of ApplePayRateLimiter states that X-Forwarded-For is never read. It was kept as security documentation (Low review note, left as is).

Open points

  • The length limits (100 / 20 / 255 / 30) are still to be confirmed with the PayPlug API owner (an expected result of the ticket).

Out of scope, to ticket separately

  • needs_payment() check on ajax_create_payment (update_applepay_order already has it since PRE-3734).
  • Spike PRE-3755: create the Apple Pay order at payment authorisation instead of on button click.
  • applepay_add_to_cart ignores the return value of WC()->cart->add_to_cart(). When WooCommerce refuses the product (out of stock, not purchasable), the endpoint still answers success.
  • The empty-cart branch of place_order_with_dummy_data answers a bare wp_send_json_error(). After an add-to-cart refusal, the product page JS then throws on results.data.msg (pre-existing, one-line fix).
  • If something throws after create_order() has saved the order, that order stays pending. If the throw comes from process_cart_payment(), the cart has also already been emptied (pre-existing).

🤖 Generated with Claude Code

…order contacts and remove unused Apple Pay methods
…ated and validate the Apple Pay add-to-cart product id and quantity
@hdelaforce-payplug hdelaforce-payplug changed the title PRE-3742: rate-limit Apple Pay cart orders and sanitise contact updates PRE-3742: harden Apple Pay AJAX endpoints (rate limit, contact sanitising, error handling) Oct 19, 2026

@jhoaraupp jhoaraupp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review de PRE-3742, à approuver après les deux points Moyen :

  • IPv6 compté par /64 (ou risque accepté explicitement avec le PO) ;
  • PHPUnit exécuté au moins une fois, plus le QA sandbox.

Vérifié de mon côté sur 6e409b9 :

  • php -l (PHP 7.4) : OK ;
  • php-cs-fixer --dry-run : 0 fichier ;
  • PHPStan : 138 erreurs, identique à la baseline annoncée, aucune dans le code ajouté ;
  • consommateurs JS (cart, product, Blocks) relus : compatibles avec les nouvelles réponses (msg / message, réponse de update_applepay_order jamais lue).

Bon travail sur les logs sans donnée client et la validation stricte des entrées.

return '' !== $customer_id ? 's:' . $customer_id : '';
}

private static function remote_addr(): string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Moyen] La limite par IP se contourne en IPv6, et la limite par session ne protège pas d'un attaquant

remote_addr() compte par adresse exacte. En IPv6, un client dispose en général d'au moins un /64, soit 2⁶⁴ adresses.

La limite par sujet ne compense pas : sans cookie, un attaquant obtient une nouvelle session WC à chaque appel applepay_add_to_cart, puis enchaîne place_order_with_dummy_data avec ce cookie (2 requêtes par commande).

Scénario : un script qui change d'adresse IPv6 dans son /64 à chaque paire de requêtes crée des commandes pending sans limite. C'est précisément le flood que PRE-3742 veut bloquer.

Proposition : pour l'IPv6, compter par préfixe /64 (inet_pton, mettre à zéro les 8 derniers octets, inet_ntop comme identité), et ajouter un test « deux adresses du même /64 partagent le compteur ».


$key = self::transient_key($scope, $identity);
$now = time();
$bucket = get_transient($key);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Faible] Une salve parallèle peut dépasser la limite

get_transient() puis set_transient() ne sont pas atomiques : N requêtes simultanées lisent toutes count < limit et passent toutes.

C'est acceptable pour une limite « best effort », mais il vaut mieux l'écrire dans le docblock de hit(). Avec un cache objet persistant, wp_cache_add + wp_cache_incr donneraient un compteur atomique.

}

if ((int) $bucket['count'] >= $limit['limit']) {
PayplugGateway::log(sprintf(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Faible] Un log warning est écrit à chaque requête refusée

Sous flood, avec les logs activés, on obtient une ligne par requête refusée dans payplug_gateway. Logguer seulement le premier refus de chaque fenêtre (par exemple avec un drapeau dans le bucket) suffirait au diagnostic.

@@ -0,0 +1,148 @@
<?php

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nit] Pas de garde defined('ABSPATH') || exit;

C'est cohérent avec Front/ApplePay.php, et le dépôt ne l'a que dans environ un tiers des fichiers de src/. À ajouter seulement si on veut converger vers la règle.

Comment thread src/Front/ApplePay.php
return;
}

try {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Faible] Le try / catch (\Throwable) englobe aussi empty_cart() et process_cart_payment()

  • En production, wp_send_json() termine par un die, donc pas d'effet. Mais l'AjaxDieException du harnais de test étend \Error. Tout futur test qui ne surcharge pas process_cart_payment() verra ce « die » avalé par ce catch, puis un second JSON concaténé.
  • Une exception levée dans process_cart_payment() (paiement PayPlug éventuellement créé, panier déjà vidé) répond « Your order was cancelled. », ce qui est faux pour l'acheteur.

Proposition : limiter le try à $this->create_cart_order($apple_pay) et laisser la suite hors du catch.

Comment thread src/Front/ApplePay.php
*/
private function create_cart_order(ApplePayGateway $apple_pay): ?\WC_Order
{
$order_id = WC()->checkout()->create_order(['payment_method' => $apple_pay->id]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Faible] Une exception après create_order() laisse une commande pending orpheline

create_order() a déjà sauvegardé la commande. Si une étape suivante lève une exception (calculate_taxes, calculate_totals, save…), le meta CART_ORDER_META n'a été posé qu'en mémoire et n'est jamais persisté. applepay_cancel_order refusera donc de supprimer cette commande.

Comme le panier est conservé, chaque nouvel essai crée un orphelin de plus (au plus 10 par fenêtre grâce à la limite). Le test ..._when_a_step_after_create_order_throws fige ce comportement : il se contente de faire le ménage via track_order.

C'est préexistant et listé hors scope, mais la correction est simple : dans create_cart_order(), entourer l'après-création d'un try/catch qui fait $order->delete(true) (ou passe la commande en failed), puis relance l'exception.

Comment thread src/Front/ApplePay.php
@@ -197,66 +242,8 @@ public function update_applepay_order(): void
}

$selected_shipping_method = !empty($_POST['shipping_method']) ? $_POST['shipping_method'] : null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nit, hors scope] $_POST['shipping_method'] reste brut

Il ne sert qu'à une comparaison stricte avec $id_shipping_method, donc c'est inoffensif. Pour être homogène avec le reste de la PR : wc_clean(wp_unslash(...)) avec un contrôle is_string.

Comment thread languages/payplug.pot
msgid "The total amount of your order should be between %s€ and %s€ to pay with Scalapay."
msgstr ""

#: src/Front/ApplePay.php:593

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nit] Référence de ligne périmée

La chaîne est à src/Front/ApplePay.php:612 sur le head (le second commit l'a décalée). C'est aussi le cas dans payplug-fr_FR.po. C'est cosmétique ; un wp i18n make-pot la corrigerait.

use Payplug\PayplugWoocommerce\Tests\phpunit\src\Support\WooCommerceAjaxFixtures;
use PHPUnit\Framework\TestCase;

class ApplePayRateLimiter_test extends TestCase

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Moyen] Aucun des 48 nouveaux tests n'a encore tourné

La CI de ce dépôt ne lance pas PHPUnit, et l'exécution locale n'a pas été faite. Les tests du rate limiter, du chemin WP_Error et de la validation add-to-cart ne garantissent donc rien pour l'instant.

À faire avant le merge :

  • lancer make test (avec PHP_VERSION=7.4, en contournant le platform check) ou une suite WP-tests au moins une fois ;
  • dans la checklist de la PR, décocher « verified these changes locally on a native WooCommerce environment » tant que le QA sandbox Apple Pay (panier, produit, Blocks) n'est pas fait, puisque les notes indiquent qu'il ne l'est pas encore.

@ilajili ilajili left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compléments de review, à lire avec la review existante. Les points déjà relevés (atomicité du compteur, commande orpheline, catch \Throwable, IPv6) ne sont pas répétés.


private const DEFAULT_LIMITS = [
'subject' => ['limit' => 10, 'window' => 600],
'ip' => ['limit' => 100, 'window' => 600],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Moyen] La limite par IP peut bloquer Apple Pay pour toute une boutique derrière un proxy

Elle est active par défaut (100 / 600 s sur REMOTE_ADDR). Derrière un reverse proxy ou un CDN qui ne restaure pas l'IP réelle, tous les visiteurs partagent la même REMOTE_ADDR. Au bout de 100 tentatives en 10 min, le bouton est refusé à tout le monde, et seul le changelog le signale.

Un blocage silencieux du paiement coûte plus cher que des commandes pending. Pistes : un log ou une notice admin dédiés quand la limite IP se déclenche, ou une limite IP désactivée par défaut. À trancher avec le PO.

$limits = self::limits();

$ip = self::remote_addr();
if ('' !== $ip && !self::hit('ip', $ip, $limits['ip'])) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Faible] L'IP est comptée même quand la limite par sujet refuse ensuite

allow() incrémente le compteur IP, puis hit('subject', …) peut refuser. La tentative est donc comptée côté IP alors que le docblock dit « a refused attempt is not counted ». Soit on ajuste la doc, soit on vérifie les deux limites avant d'écrire.

Comment thread src/Front/ApplePay.php
$cart = WC()->cart;

if (!$cart->is_empty()) {
if (!ApplePayRateLimiter::allow()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Faible, hors scope] Pas de nonce sur place_order_with_dummy_data

L'endpoint crée une commande sans nonce. Un site tiers peut donc déclencher des créations depuis le navigateur d'un visiteur. À mettre en suivi (nonce par session, aussi côté JS cart / product / Blocks).

Comment thread src/Front/ApplePay.php
} else {
$product_id = $_POST['product_variation_id'];
}
WC()->cart->add_to_cart($product_id, $quantity);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Moyen] applepay_add_to_cart répond « succès » quand l'ajout échoue

WC()->cart->add_to_cart() renvoie false pour un produit inexistant, en rupture ou non achetable, mais la valeur de retour est ignorée : la réponse est success avec le total du panier. Le titre du changelog (« rejects invalid product ids ») ne couvre que le format de l'id.

Le point est listé hors périmètre dans la description. Comme ce bloc est déjà modifié ici, le fix tient en quelques lignes : if (false === WC()->cart->add_to_cart(...)) { $this->send_order_not_created(); return; }.

Comment thread src/Front/ApplePay.php
private function get_posted_quantity()
{
if (!isset($_POST['product_quantity']) || '' === $_POST['product_quantity']) {
return 1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Faible] Changement de comportement sur la quantité 0, et pas de borne haute

Avant, !empty('0') valait faux et la quantité retombait à 1. Maintenant 0 est refusé. C'est cohérent avec la description, mais absent du changelog. Il n'y a pas non plus de borne maximale : un entier énorme passe wc_stock_amount.

}
}

if (isset($contact['emailAddress']) && is_string($contact['emailAddress'])) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Faible] Un champ invalide est écarté sans trace et les placeholders restent

Un e-mail invalide (ici) ou des addressLines toutes vides sont ignorés en silence. La commande garde alors payplug_applepay_email@payplug.com ou payplug_applepay_address, qui partent à PayPlug, et le client ne reçoit pas son e-mail de confirmation. Un log warning quand un champ est écarté (sans donnée client) suffirait au diagnostic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants