Skip to content

Store automatically added BuyXGetY reward lines using the morph map - #2632

Merged
glennjacobs merged 2 commits into
lunarphp:1.xfrom
kha333n:fix/buyxgety-reward-morph-class
Aug 25, 2026
Merged

Store automatically added BuyXGetY reward lines using the morph map#2632
glennjacobs merged 2 commits into
lunarphp:1.xfrom
kha333n:fix/buyxgety-reward-morph-class

Conversation

@kha333n

@kha333n kha333n commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

A reward line added automatically by a Buy X Get Y discount is written to
lunar_cart_lines with a different purchasable_type than every other line
holding the same product.

What happens now

  • The reward line stores Lunar\Models\ProductVariant, while a line the
    shopper added stores product_variant.
  • The same purchasable therefore appears under two different types in one
    cart.
  • Any query that filters cart lines by morph type — reporting, an
    whereHasMorph, a custom pipeline stage — silently skips these lines.
  • It survives into lunar_order_lines, so the inconsistency is persisted
    against the placed order too.

What should happen

  • A reward line stores the same purchasable_type as any other line for that
    purchasable.

Why

Lunar registers a morph map at boot (ModelManifest::morphMap(), keyed by the
snake-cased class basename), so ProductVariant::getMorphClass() returns
product_variant. Every cart line in the codebase is created with
getMorphClass(), except this one:

$rewardLine = $cart->lines()->make([
    'purchasable_type' => get_class($purchasable),

get_class() bypasses the map and writes the concrete class name.

The fix

Use getMorphClass(), matching how cart lines are created everywhere else. It
also means a store that has extended ProductVariant gets the mapped alias
rather than its own class name written into the table.

Deliberately out of scope: with reward_qty greater than one this method also
creates one cart line per reward unit rather than a single line, because its
"is it already in cart?" check reads an in-memory $cart->lines the loop never
appends to. That is a real but separate defect, and fixing it here would make
this diff much harder to review. Also note #2509 is currently rewriting this
method for stock availability — no overlap in intent, but a rebase for whichever
lands second.

Tests

tests/core/Unit/DiscountTypes/BuyXGetYTest.php:

  • can store an automatically added reward line using the morph map — an
    automatic reward on a qualifying cart, asserting the persisted
    purchasable_type. Fails on 1.x with:

    -'product_variant'
    +'Lunar\Models\ProductVariant'
    

Resolves the test-file conflict with lunarphp#2631 by keeping both new tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@glennjacobs
glennjacobs merged commit d73dd36 into lunarphp:1.x Aug 25, 2026
14 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Roadmap Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants