Commit Graph

31 Commits

Author SHA1 Message Date
Fabian @ Blax Software 53832f49ce fix(stripe): cents everywhere, idempotent refunds, admin refunds, webhook ledger sync
- Webhook handlers stored euros in cent columns (charge.succeeded,
  charge.refunded, refund.created, payment_intent.succeeded, the success
  redirect, dispute/invoice notes). All amounts are cents now; purchases get
  their own line amount as amount_paid.
- One Stripe refund arrives as refund.created AND charge.refunded, in either
  order. New Order::applyStripeRefund() / applyStripeRefundedTotal() raise the
  order's Stripe refund total (meta.stripe_refunded, meta.stripe_refunds) to
  what Stripe confirmed, so nothing is counted twice. refund.created now finds
  Checkout orders by payment_intent.
- New ShopService::refundOrder($order, $cents, $reason) refunds through the
  Stripe API (idempotency key per order/amount/state) and records it, plus
  refundableAmount(). Bind \Stripe\StripeClient to swap the client.
- charge.succeeded remembers meta.stripe_charge_id; with
  shop.ledger.sync_on_webhook (default off) charge/refund/dispute webhooks
  upsert the balance transactions into stripe_transactions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 10:50:25 +02:00
Fabian @ Blax Software 0d9823df04 fix(orders): dispatch OrderPaid once; honour shop.models.order on webhook + service paths
OrderPaid was defined but never dispatched. Order::booted() now fires it from
the `updated` hook exactly when paid_at goes null -> value (recordPayment()
reaching amount_total, the Stripe checkout webhook, or a direct update). Later
saves of a paid order, partial payments and overpayments do not re-fire it, and
a host subclass configured via shop.models.order gets it with its own instance.

StripeWebhookController, ShopService and Cart::checkout used the base
Blax\Shop\Models\Order statically (createFromCart, find, where, scopes,
aggregates), so a host app's configured Order subclass — with its own boot
hooks — was bypassed on the real webhook path. They now resolve
config('shop.models.order') through a small orderModel() helper. Return types
are unchanged (subclass instances satisfy Order); Order::formatMoney stays a
pure static.

Tests: OrderPaidEventTest (once-only on recordPayment, partial payments,
direct paid_at update, host subclass) and StripeWebhookOrderModelTest
(handleCheckoutSessionCompleted builds the configured subclass and its boot
hook runs; webhook/ShopService/Cart finders return it). Suite: 1485 tests green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-24 08:22:55 +02:00
Fabian @ Blax Software b4125c1ee1 feat(i18n,stripe): de/pl translations + order address snapshots from Checkout Session
- lang/de and lang/pl for cart.php and auth.php (informal register), every
  key of lang/en translated; README documents the `shop::` namespace and
  the lang/vendor/shop/<locale>/ override path.
- StripeWebhookController::handleCheckoutSessionCompleted now persists the
  session's customer_details as Order.billing_address and shipping_details
  as Order.shipping_address (falls back to billing when Stripe sent no
  shipping block). Snapshot shape: name, email, phone, line1, line2,
  postal_code, city, state, country, uid (first tax id). Only empty columns
  are filled; a session without either block leaves the order untouched.
  Optional host-added customer_email/customer_name columns are filled when
  present and empty. Accepts Stripe objects, stdClass and arrays.
- Controller formatted with pint (composer lint).

Tests: tests/Unit/Stripe/StripeWebhookAddressTest.php (5),
tests/Feature/ShopServiceProvider/TranslationsTest.php (4). Full suite
green (1477, 7 skipped).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 13:39:40 +02:00
Fabian @ Blax Software 68d02b01fc feat(cart): WS-first storefront primitives + shared cart/auth controllers
Adds the reusable pieces a headless WS-first shop needs, so every consumer
(immohost, nirioci, future shops) shares one implementation instead of
re-inventing it in app code:

- CartService::adopt() — resolve + re-bind a guest cart to a stable connection
  id (socket/session), so the cart follows a stateless client across reconnects.
- CartService::mergeGuestIntoUser() — actually honour shop.cart.merge_on_login,
  which was dead config: re-parent guest items onto the user's cart at login
  (preserves claimed stock and resolved prices; no re-add side effects).
- Cart facade passthrough checkoutSessionLink() for the {redirect} checkout shape.
- CartResource / CartItemResource: a stable, presentation-agnostic wire shape
  (money in integer minor units + explicit currency; best-effort image).
- Blax\Shop\Websocket\Controllers\{Cart,Auth}Controller: thin, config-resolved,
  translatable base WS controllers a host exposes via one-line subclass stubs.
  laravel-websockets stays a soft (suggest) dependency; the Websocket dir is
  excluded from coverage since its logic lives in the covered CartService.
- shop:: translation namespace (en) for the controller messages.

Tests: tests/Unit/Cart/CartAdoptMergeTest.php (7). Full suite green (1468).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-17 10:53:28 +02:00
Fabian @ Blax Software 2c31a23255 feat(pricing): percentage conditional prices + cheapest-effective resolver + authoring helper
ProductPrice gains meta.percent_off support: percentOff()/isPercentage() and
effectiveAmount($base) — a percentage price resolves against a base (the
product's cheapest standalone price), so it can express e.g. '90% off with
Full Seat' rather than a fixed amount. Product::resolvePriceFor now ranks the
eligible prices by EFFECTIVE amount (percentage-aware) and returns the cheapest,
so a product linked to multiple prerequisites with different fixed/percentage
discounts always resolves to the cheapest the buyer qualifies for. Adds
ProductPrice::setConditionalPricing($requires, $percentOff, $label) — a
merge-safe authoring seam host admin UIs can call instead of hand-poking meta.
2026-08-26 15:52:24 +02:00
Fabian @ Blax Software 07a794c028 feat(pricing): conditional prices via meta.requires + entitlement-checker resolver
Add a generic conditional-pricing mechanic: a ProductPrice may carry
meta.requires (e.g. {"role":"seat"}) so it only applies to buyers who
already hold a prerequisite (loyalty / bundle pricing). Product::resolvePriceFor
returns the cheapest price the buyer is eligible for, delegating the
'does the buyer satisfy this requirement' test to a host-bound
EntitlementChecker contract. Defaults to DenyAllEntitlementChecker so an
unconfigured host never leaks a discount. Adds requires()/isConditional()/
requiresLabel()/requiresSatisfiedBy() and conditional()/standalone() scopes.
2026-08-26 12:35:14 +02:00
Fabian @ Blax Software f10d5c2f04 feat(tax): configurable TaxService applied across every checkout path
Blax\Shop\Services\TaxService::rates($exempt, $rates=null) is one source of
truth that turns "which rate(s)" (config shop.tax.rates or an explicit list)
and "is this customer exempt" into the array Stripe expects. It fails loud
(TaxRateNotConfiguredException) when shop.tax.require is set and no rate is
configured for a non-exempt charge, instead of silently billing 0% VAT.

New config('shop.tax') block (SHOP_TAX_RATES / SHOP_TAX_REQUIRE). Unit tested.

Consumers (learn-atc #1509) delegate getApplicableTaxRates() here so no path
can apply the rate twice (Stripe rejects duplicate tax rates) or drop VAT to 0%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-21 08:23:56 +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 0cfbdf221d BF edgecases, R structure, A tests 2026-05-18 13:05:38 +02:00
Fabian @ Blax Software afdcd8bc75 feat: Enhance traits with strict types and improve method signatures
- Added strict types declaration to multiple traits for better type safety.
- Updated method signatures in traits to use nullable types where applicable.
- Improved documentation for traits, including host-model contracts and method descriptions.
- Added new tests to ensure correct behavior of loan checkout and stock management.
- Fixed regression in order number generation to ensure proper string formatting.
- Ensured that currency codes sent to Stripe are consistently lowercased.
2026-05-15 20:26:24 +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 440be3a36f I order notes, BF checkout currency 2026-01-25 10:40:17 +01:00
Fabian @ Blax Software 1a8f111110 I order 2026-01-05 12:29:55 +01:00
Fabian @ Blax Software a66fd7ccb8 BFI cart / orders 2026-01-05 10:30:21 +01: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 6e9c9043ae I order, A handy methods 2025-12-29 10:26:51 +01:00
Fabian @ Blax Software 9c1fcd6cfd AM orders 2025-12-29 09:59:02 +01:00
a6a2f5842 9e7812c70e BFI cart single item 2025-12-24 19:40:10 +01:00
Fabian @ Blax Software 0e6b420297 BFI pool cart 2025-12-20 12:19:34 +01:00
Fabian @ Blax Software f6c60f3d79 BFI cart conversion product purchase, R cart dates 2025-12-19 09:53:44 +01:00
Fabian @ Blax Software 7a26c9a965 I cart methods 2025-12-18 15:33:47 +01:00
Fabian @ Blax Software d13945a731 IMU cart + test 2025-12-18 12:21:29 +01:00
Fabian @ Blax Software 7360391581 BF pool priority claiming, IA date cast 2025-12-17 12:47:18 +01:00
Fabian @ Blax Software d2cf70ce44 ABFI cart, cartitems, pool/bookings, tests 2025-12-15 12:28:15 +01:00
Fabian @ Blax Software 4a303c0f3f I category methods, IRA product relations & tests 2025-12-05 09:21:07 +01:00
a6a2f5842 c5004158eb A types, enums & statuses, U product models, I shopping trait, A booking support, R tests 2025-12-03 13:59:01 +01:00
a6a2f5842 bc8158ba7b BF test 2025-11-29 08:32:58 +01:00
a6a2f5842 3593a462a1 I basic tests 2025-11-25 17:14:00 +01:00
a6a2f5842 5fb31d0c4f D basic tests 2025-11-25 17:02:39 +01:00
a6a2f5842 7c6b61da45 BFI tests 2025-11-24 14:32:11 +01:00
a6a2f5842 d610cc5717 init 2025-11-21 11:49:41 +01:00