For the complete documentation index, see llms.txt. This page is also available as Markdown.

Cart recalculation extension points

Available from B2Bsellers Suite 4.1.5.

Several B2Bsellers features price a cart outside the customer's own session: order approvals, offers, and sales representative fast orders all build a virtual cart and recalculate it. Order approvals go one step further and store a snapshot of the cart, then rebuild it from that snapshot whenever the approval is refreshed.

If your plugin keeps its own data on cart line items, or relies on entity extensions on the customer address, that data does not automatically survive the round trip. The snapshot stores a fixed set of fields, and a rebuilt address is a plain entity without your extensions. Anything your cart collectors or processors depend on has to be put back before recalculation runs.

These events are where you put it back.

Quick overview

Event
Dispatched
Use it to

OrderApprovalLineItemStructBuildEvent

Once per line item while the snapshot is created

Copy your payload data into the stored snapshot

OrderApprovalCartLineItemBuildEvent

Once per line item while the cart is rebuilt

Put your payload data back on the line item

OrderApprovalCartRebuiltEvent

Once after the cart has been rebuilt

Do one lookup for the whole cart instead of one per item

OrderApprovalAddressMappedEvent

When a customer address is stored in the approval snapshot

Carry address data that is not in the columns into the snapshot

VirtualCartContextBuiltEvent

After the virtual context is built, before recalculation

Re-attach entity extensions the recalculation needs

The first four live in B2bOrderApproval\Components\OrderApproval\Event. VirtualCartContextBuiltEvent lives in B2bSellersCore\Components\Checkout\Cart\Event and applies to every virtual cart, not only order approvals.

All five extend Symfony\Contracts\EventDispatcher\Event and expose their data through getters.

Preserving a value on the line item payload

The common case: your plugin writes a key to the line item payload, and your cart processor needs it to calculate a price. Use the two per-item events as a pair — one copies the value out, the other puts it back.

public function onStructBuild(OrderApprovalLineItemStructBuildEvent $event): void
{
    $customFields = $event->getStructItem()->customFields ?? [];

    $customFields[self::PAYLOAD_KEY] = $event->getCartLineItem()->getPayloadValue(self::PAYLOAD_KEY);

    $event->getStructItem()->customFields = $customFields;
}

public function onCartLineItemBuild(OrderApprovalCartLineItemBuildEvent $event): void
{
    $customFields = $event->getStructItem()->customFields ?? [];

    if (isset($customFields[self::PAYLOAD_KEY])) {
        $event->getLineItem()->setPayloadValue(self::PAYLOAD_KEY, $customFields[self::PAYLOAD_KEY]);
    }
}

The struct item is handed to you as a live object, so writing to its properties changes what is persisted into the order_approval.line_items column. The same is true of the line item: it is the exact instance that is added to the cart.

Write to the top level payload, not to payload['customFields'].

Shopware's CartSerializationCleaner filters payload['customFields'] against the custom_field.allow_cart_expose allow list before a cart is stored, and the order is later persisted from that stored payload. A top level key such as unitType passes through untouched; a value nested inside customFields may not.

Note the asymmetry in the example above: customFields on the struct is fine, because the snapshot is our own JSON column and is never passed through the cleaner. It is the cart payload that has to be top level.

OrderApprovalCartLineItemBuildEvent is dispatched after every payload value the suite writes itself — position, originalUnitPrice, originalQuantity, customFields, comment, options — and after the B2bLineItemInformation and LineItemBudget extensions have been attached. Your subscriber always writes last and can override any of them.

Doing one lookup for the whole cart

OrderApprovalCartRebuiltEvent is dispatched once, after the cart has been rebuilt and every per-item event has run. It fires even when the snapshot contained nothing to rebuild.

Preserving data on the address

An order approval stores its own copy of the billing and shipping address, with a newly generated id. Entity extensions on the customer address are not part of that copy, and the originating customer_address.id cannot be recovered afterwards — so anything you need later has to be stored at the moment the snapshot is written.

OrderApprovalAddressMappedEvent gives you the source address and the payload that is about to be written. Both the billing and the shipping path go through it, so one subscriber covers every way an approval is created, including creation by a sales representative and conversion from an offer.

The payload is a DAL write payload for order_approval_address. Only keys the entity definition knows may be added, so put your own data under customFields.

Custom fields and extensions stored on the snapshot are carried back onto the CustomerAddressEntity when the cart is rebuilt, so you can also register an entity extension on order_approval_address and read it directly instead of going through custom fields.

Adjusting the context before recalculation

VirtualCartContextBuiltEvent is the last point at which the sales channel context can be changed so that cart collectors and processors see it. It applies to every virtual cart — order approvals, offers, and sales representative fast orders.

getSalesChannelContext() returns the virtual context built for this calculation, not the caller's context. Changes affect only this calculation, which is what you want. The options are informational — the context has already been built by the time the event fires, so changing them has no effect.

Last updated

Was this helpful?