Skip to content

Store Credit Event Reference

Store Credit is designed to be extended through events rather than through class rewrites. It dispatches five Magento events of its own around reading and writing balances, observes nine Magento events to keep the ledger in step with orders, and emits two Magewire events on the storefront. This page is the reference for all of them.

Store Credit uses its own events internally too: the balance change emails are an observer on hyva_store_credit_balance_changed, not a hardcoded call. Anything the module can do to itself, your module can do the same way.

Events Store Credit Dispatches

These are the extension points for your own code. All five are dispatched by Hyva\StoreCredit\Model\Ledger\LedgerWriter and Hyva\StoreCredit\Model\Ledger\BalanceReader.

hyva_store_credit_append_before

Fired inside LedgerWriter::append() after the entry has been validated and after the idempotency check, but before the database transaction opens. Nothing has been written yet.

Key Type Contents
ledger_entry LedgerEntryInterface The entry about to be written. balance_after is not set yet.

This is the only veto point in the write path. Throwing an exception from an observer here aborts the write before any database work happens, which makes it the right place for a policy check such as refusing credit to a customer group or capping how much credit a single adjustment may add.

hyva_store_credit_append_after

Fired inside LedgerWriter::append() after the transaction has been committed. The entry now has its ID, its balance_after, and its resolved currency.

Key Type Contents
ledger_entry LedgerEntryInterface The entry as written, with entry_id and balance_after populated.
balance_before float The balance before this entry was applied.

Use this when you need the written entry itself, for example to push a row into a reporting table or an external accounting system.

hyva_store_credit_balance_changed

Fired immediately after hyva_store_credit_append_after, with the change flattened into scalars so an observer does not have to unpack the entry. This is the event most integrations want.

Key Type Contents
customer_id int Whose balance changed.
website_id int Which website's balance changed.
event_type int One of the EventType constants.
delta float The signed change, negative for spends and debits.
balance_before float The balance before the change.
balance_after float or null The balance after the change.
base_currency_code string or null The currency the balance is held in.
reason string or null The internal reason. The customer-facing reason is on the entry as reason_external.
source string or null One of the Source constants, such as checkout or creditmemo.
ledger_entry LedgerEntryInterface The full entry, if you need the references or attribution.

This event fires after the balance change has been committed, so it cannot veto anything. That is deliberate: a failing notification must never roll back a customer's credit.

hyva_store_credit_get_balance_before

Fired at the start of BalanceReader::getBalance(), before the projection is read.

Key Type Contents
customer_id int The customer whose balance is being read.
website_id int The website the balance is read for.

hyva_store_credit_get_balance_after

Fired at the end of BalanceReader::getBalance(), with the resolved balance.

Key Type Contents
customer_id int The customer whose balance was read.
website_id int The website the balance was read for.
balance BalanceInterface The balance object, which observers can modify.

The balance object is mutable, so an observer can rewrite the amount before it reaches the caller. That makes it possible to layer promotional credit on top of the stored balance without writing it to the ledger, but it also means an incorrect observer here makes every balance in the system wrong. Read carefully before reaching for it.

Duplicate Writes Fire No Events

When LedgerWriter::append() is called with an idempotency key that already exists, the existing entry is returned and none of append_before, append_after, or balance_changed fires. Anything you hang off these events must therefore be safe to not run on a retry, and must not be the only place a required side effect happens.

Observing a Store Credit Event

Observers are registered the ordinary Magento way. This example logs every balance change to your own audit table.

app/code/Vendor/Module/etc/events.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="hyva_store_credit_balance_changed">
        <observer name="vendor_module_record_credit_change"
                  instance="Vendor\Module\Observer\RecordCreditChange"/>
    </event>
</config>

The observer reads the flattened payload straight off the event:

app/code/Vendor/Module/Observer/RecordCreditChange.php
<?php
declare(strict_types=1);

namespace Vendor\Module\Observer;

use Hyva\StoreCredit\Model\Ledger\EventType;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;

class RecordCreditChange implements ObserverInterface
{
    public function __construct(private readonly \Vendor\Module\Model\AuditWriter $auditWriter)
    {
    }

    public function execute(Observer $observer): void
    {
        $event = $observer->getEvent();
        $eventType = (int) $event->getData('event_type');

        // EventType classifies the change, so no need to test the sign of the delta.
        $this->auditWriter->record(
            (int) $event->getData('customer_id'),
            (int) $event->getData('website_id'),
            (float) $event->getData('delta'),
            EventType::getLabel($eventType),
            EventType::isCredit($eventType)
        );
    }
}

Magento Events Store Credit Observes

Store Credit already hooks these events. Check this table before adding an observer of your own, so you know what has run before yours.

Magento event Observer What it does
sales_model_service_quote_submit_before SpendStoreCredit Copies the applied amount from the quote to the order and writes the SPEND entry, keyed on the order increment ID. Retries with a raised attempt counter when a spend key was already compensated.
sales_model_service_quote_submit_success LinkSpendOrder Back-fills order_id on the spend entry once the order has an ID.
sales_model_service_quote_submit_failure RevertStoreCredit Writes a REVERT entry when submission fails. Skipped when the increment ID is already used, so a late duplicate failure cannot refund twice.
order_cancel_after RevertStoreCredit Writes a REVERT entry for the credit applied minus the credit already refunded.
payment_method_is_active TogglePaymentMethods Disables every payment method except free when store credit brings the base grand total to zero.
sales_order_invoice_save_after IncreaseInvoicedStoreCredit Accumulates base_store_credit_invoiced on the order. Writes no ledger entry.
adminhtml_sales_order_creditmemo_register_before PrepareCreditmemoRefund Reads the requested refund amount, runs the ACL and ceiling checks, and re-collects the credit memo total.
sales_order_creditmemo_save_after RefundStoreCredit Writes the REFUND entry keyed on the credit memo ID, and accumulates base_store_credit_refunded on the order only when a new entry was created.
hyva_store_credit_balance_changed SendBalanceChangeEmail Sends the balance change email when the entry is marked notifiable. Catches and logs any failure.
hyva_config_generate_before RegisterModuleForHyvaConfig Registers the module in hyva-themes.json so its Tailwind config is merged. Adminhtml area only.

Magewire and Browser Events

On the storefront, the checkout component talks to the rest of checkout through Magewire events rather than direct calls.

Hyva\StoreCredit\Magewire\Checkout\StoreCredit emits:

  • store_credit_applied when the customer applies their credit.
  • store_credit_revoked when the customer removes it.

Hyva\StoreCredit\Plugin\Checkout\PriceSummaryListeners adds both of those to Hyvä Checkout's PriceSummary component as refresh listeners, which is how the order summary updates itself when credit goes in or out. If you build a component that needs to react to store credit, listen for the same two events.

The store credit component listens to everything that could change the order total, all mapped to refresh: shipping_method_selected, payment_method_selected, coupon_code_applied, coupon_code_revoked, shipping_address_saved, shipping_address_activated, billing_address_saved, and billing_address_activated.

In the browser, two more events matter:

  • checkout:step:loaded with detail.subsequent re-refreshes the store credit component, which is what keeps the panel alive when the customer navigates forward to payment and back again.
  • private-content-loaded drives the cart and minicart panels, which dispatch reload-customer-section-data after applying or removing credit to refresh the hyva-store-credit section.