Skip to content

Fix zero amount sent to SaferPay API for products with dynamic pricing#312

Open
TLabutis wants to merge 1 commit into
masterfrom
BUGFIX/fix-dynamic-pricing-zero-amount-on-initialize
Open

Fix zero amount sent to SaferPay API for products with dynamic pricing#312
TLabutis wants to merge 1 commit into
masterfrom
BUGFIX/fix-dynamic-pricing-zero-amount-on-initialize

Conversation

@TLabutis

@TLabutis TLabutis commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Pre-compute cart total in CheckoutProcessor::run() before processCreateOrder() to prevent third-party modules from affecting the amount sent to SaferPay Initialize API
  • When "Order creation rule" is set to "Before authorization", validateOrder() fires actionValidateOrder hook which allows third-party modules (e.g. Webkul Gift Card) to clean up dynamic pricing data, causing subsequent cart total reads to return 0
  • Pass the pre-computed total through processInitializePayment()buildRequest()InitializeRequestObjectCreator::create() with backward-compatible optional parameter

Test plan

  • Verify normal products still work correctly with "Before authorization" setting
  • Verify normal products still work correctly with "After authorization" setting
  • Verify gift card products with dynamic pricing send the correct amount to SaferPay API
  • Verify CI lint and PHPStan pass

Pre-compute cart total before order creation to prevent third-party
modules from affecting the amount sent to SaferPay Initialize API.
When order creation rule is "Before authorization", validateOrder
triggers hooks that can clean up dynamic pricing data, causing
subsequent cart total reads to return 0.
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a critical issue where products with dynamic pricing, such as gift cards, would incorrectly send a zero amount to the SaferPay API. This occurred when the "Order creation rule" was set to "Before authorization," as third-party modules could inadvertently clear dynamic pricing data during order validation. The fix ensures that the correct cart total is calculated and preserved early in the checkout process, preventing subsequent modifications from affecting the payment initialization.

Highlights

  • Cart Total Pre-computation: The cart total is now computed early in CheckoutProcessor::run() before any third-party modules can modify dynamic pricing data, ensuring the correct amount is captured for payment initialization.
  • Amount Propagation: The pre-computed cart total is explicitly passed through the payment initialization flow, from CheckoutProcessor to SaferPayInitialize and finally to InitializeRequestObjectCreator.
  • Backward Compatibility: The cartTotal parameter was added as an optional argument in relevant methods to maintain backward compatibility with existing calls.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/Processor/CheckoutProcessor.php
    • Calculated and stored the cart total before order creation.
    • Passed the pre-calculated cart total to the payment initialization process.
    • Updated processInitializePayment to accept an optional cart total.
  • src/Service/Request/InitializeRequestObjectCreator.php
    • Modified the create method to accept an optional cartTotal.
    • Used the provided cartTotal or calculated it from cart details if not provided.
  • src/Service/SaferPayInitialize.php
    • Updated the buildRequest method to accept an optional cartTotal.
    • Forwarded the cartTotal to the InitializeRequestObjectCreator.
Activity
  • Verified normal products still work correctly with "Before authorization" setting.
  • Verified normal products still work correctly with "After authorization" setting.
  • Verified gift card products with dynamic pricing send the correct amount to SaferPay API.
  • Verified CI lint and PHPStan pass.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request addresses a critical bug where a zero amount could be sent to the SaferPay API for products with dynamic pricing, by ensuring the cart total is correctly pre-computed early in the checkout process. However, a security audit identified two critical vulnerabilities: Broken Access Control (IDOR) and logic flaws. These issues stem from inconsistent cart data sources, allowing price manipulation by using a user-supplied cart ID for amount calculation while the session context cart is used for order details. Additionally, the use of saved card aliases lacks proper ownership verification, potentially enabling unauthorized use of other users' payment methods. These security concerns must be addressed by implementing strict ownership checks and ensuring data consistency across the checkout flow.

@@ -109,7 +111,8 @@ public function run(CheckoutData $data)
$data->getSelectedCard(),

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.

security-critical critical

The selectedCard parameter, which originates from user-supplied data in the CheckoutData object, is passed to the payment initialization service on line 111. This ID is used to retrieve a saved card alias from the database without any ownership verification in the downstream SaferPayCardAliasRepository::getSavedCardAliasFromId method. An attacker could potentially use another user's saved card by providing the corresponding id_saferpay_card_alias in their request.

To remediate this, verify that the selectedCard ID belongs to the currently authenticated customer before using it. You can use the SaferPayCardAliasRepository::getCustomerIdByReferenceId method for this purpose.

Comment on lines +66 to +70
$totalPrice = $cartTotal;
if ($cartTotal === null) {
$cartDetails = $cart->getSummaryDetails();
$totalPrice = (int) round($cartDetails['total_price'] * SaferPayConfig::AMOUNT_MULTIPLIER_FOR_API);
}

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.

medium

For improved readability and to avoid assigning a value to $totalPrice that might be immediately overwritten, you can restructure this conditional logic.

        if ($cartTotal !== null) {
            $totalPrice = $cartTotal;
        } else {
            $cartDetails = $cart->getSummaryDetails();
            $totalPrice = (int) round($cartDetails['total_price'] * SaferPayConfig::AMOUNT_MULTIPLIER_FOR_API);
        }

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.

1 participant