Commit Graph

17 Commits

Author SHA1 Message Date
Fabian @ Blax Software db827bbb8c feat(enums): add SERVICE product type; fix flaky multi-product checkout test
- ProductType::SERVICE for intangible/served products (subscriptions, licences,
  consulting) — no stock, behaves like SIMPLE for cart purposes. Lets hosts
  whose catalogue includes services adopt the package without a value clash.
- it_creates_separate_line_items_for_multiple_products pinned its two prices to
  one_time; without a type the factory randomised it and could mix recurring +
  one-time, tripping MixedCheckoutModeException intermittently.
2026-06-03 09:49:12 +02:00
Fabian @ Blax Software d4ae9339ac feat(subscriptions): Cashier-backed subscription lifecycle with product link + events
Adds the package's missing subscription lifecycle so any host app gets
duration-aware, product-linked subscriptions without re-implementing billing:

- Models: Subscription (extends Laravel\Cashier\Subscription) + SubscriptionItem,
  in the package's UUID convention, resolved through shop.models/shop.tables.
  Subscription gains product()/resolveProduct(), callProductActions() (runs the
  product's ProductActions with the subscription + an access-expiry override),
  and recordStarted()/recordRenewed()/recordCanceled() lifecycle hooks.
- Events: SubscriptionStarted / SubscriptionRenewed / SubscriptionCanceled,
  carrying the Cashier subscription so host subclasses work too.
- Migration: UUID subscriptions / subscription_items tables (hasTable-guarded,
  config table names, nullable product_id + current_period_* columns).
- ShopServiceProvider points Cashier at these models by default; opt out via
  shop.subscriptions.register_cashier_models for apps that subclass Cashier.

Additive and backward-compatible (registration is config-gated, tables are
guarded). Adds SubscriptionLifecycleTest; full suite 1409 green. Docs + README.
2026-06-02 11:40:15 +02:00
Fabian @ Blax Software 96b9a19287 feat(fulfillment): add model-agnostic PurchaseCompleted event
Introduce a first-class PurchaseCompleted lifecycle event so host apps can run
fulfillment (grant access, send receipts, provision licences) without coupling
to the ProductAction table or to a concrete purchasable model. It fires when a
purchase is created already-COMPLETED and when one transitions into COMPLETED,
and is transition-guarded so it does not re-fire on unrelated saves of an
already-completed purchase.

Also generalise the built-in ProductAction fulfillment in ProductPurchase:
the actionable product is now resolved via config('shop.models.product') /
'...product_price' (instead of a hard instanceof the bundled Product), and
callActions() is only invoked when the resolved product exposes it — so apps
overriding the models, or using IsSimplePurchasable host models, complete
cleanly. Existing behaviour for the bundled Product is unchanged.

Adds 4 EventsWiredUpTest cases; full suite 1404 green. Docs + README updated.
2026-06-02 11:19:21 +02:00
Fabian @ Blax Software 30dc3755d2 test: pin clock in loan-vocabulary test; docs: bump suite counts to 1400/3755
PurchaseResourceTest::it_translates_e_commerce_columns_into_loan_vocabulary
relied on the real wall clock falling inside the fixture's loan window
(2026-05-14..2026-05-28); once that window passed, the loan resolved to
`overdue` and the test failed. Freeze the clock inside the window like its
sibling tests already do.

Update the README test/assertion badges and the Testing section to the current
suite size (1400 tests, 3755 assertions) after the subscription-checkout tests.
2026-06-02 09:48:26 +02:00
Fabian @ Blax Software 6d22e130ed A cart availability + test 2026-05-19 11:17:35 +02:00
Fabian @ Blax Software ab8ea6afec BF stock calculation 2026-05-18 11:54:11 +02:00
Fabian @ Blax Software 8eb1802ef8 feat: promote IsLoanableProduct to MayBeLoanableProduct on Product
Renames the host-attached IsLoanableProduct trait to MayBeLoanableProduct
and mixes it directly into Product, matching the existing MayBePoolProduct
shape. Hosts opt in with a single DEFAULT_TYPE constant — no more manual
`use ...Trait` ceremony to forget:

    class Book extends Product
    {
        public const DEFAULT_TYPE = ProductType::LOANABLE;
    }

The boot hook reads DEFAULT_TYPE on the concrete class and only applies
the LOANABLE creating-defaults (type, status, is_visible, manage_stock)
when it matches; type-specific helpers (checkOutTo, total_quantity,
available_quantity) early-out via isLoanable() so they're harmless on
non-loanable products. checkOutTo now throws NotLoanableProductException
when called on the wrong type, mirroring NotPoolProductException.

Also fixes total_quantity for the loan lifecycle: previously summed every
INCREASE entry in product_stocks, which inflated the displayed total
after each loan cycle because returns fire increaseStock(). Now reports
physical inventory as availableStock + activeLoans.

README gains a Testing section that surfaces the current phpunit summary
line, plus passing and assertion-count badges linking to it.
2026-05-16 12:17:38 +02:00
Fabian @ Blax Software fe41475c84 I loanable product type, tiered pricing, lifecycle events, host helpers 2026-05-15 10:27:59 +02:00
Fabian @ Blax Software 80bc7293b1 I readme 2026-05-14 10:44:31 +02:00
Fabian @ Blax Software 136b7ade63 A prompts, I docs/readme, BF orders, R tests locations 2025-12-30 09:29:43 +01:00
Fabian @ Blax Software cabae43950 A facades, U readme 2025-12-09 10:59:46 +01:00
a6a2f5842 676951cda0 AI readme, tests 2025-12-03 14:12:22 +01:00
a6a2f5842 a4fedcdb58 A tests, I product action 2025-11-29 20:09:19 +01:00
a6a2f5842 ffc8716c22 A stripe & BFI cart 2025-11-28 10:24:07 +01:00
a6a2f5842 ce41dea486 A add example products command, I cleanup 2025-11-21 15:52:06 +01:00
a6a2f5842 756801f649 I minor 2025-11-21 11:59:25 +01:00
a6a2f5842 d610cc5717 init 2025-11-21 11:49:41 +01:00