From ca9e4f40b66d109b9f32124b5418c84a3297a7d9 Mon Sep 17 00:00:00 2001 From: Lee Messenger Date: Fri, 24 Apr 2026 08:30:18 +0100 Subject: [PATCH 1/2] Fix CI version detection to ignore legacy v0.1.* tags The publish workflow picked up old v0.1.* tags when bumping patch, causing stable releases to appear as 0.1.60, 0.1.61 instead of 1.0.0. Tighten both the HEAD-tag reuse check and the latest-stable lookup to require major >= 1, so the fallback correctly starts the new line at 1.0.0 and subsequent pushes increment as 1.0.1, 1.0.2, etc. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5ac5656..f287b0f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,7 +89,7 @@ jobs: git fetch --tags --force # If HEAD already has a stable vX.Y.Z tag, reuse it (idempotent re-runs). - existing_tag="$(git tag --points-at HEAD | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1 || true)" + existing_tag="$(git tag --points-at HEAD | grep -E '^v[1-9][0-9]*\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1 || true)" if [[ -n "${existing_tag}" ]]; then echo "tag_already_exists=true" >> "$GITHUB_OUTPUT" echo "tag=${existing_tag}" >> "$GITHUB_OUTPUT" @@ -98,7 +98,7 @@ jobs: fi # Find the latest stable (non-prerelease) tag anywhere in history. - latest="$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1 || true)" + latest="$(git tag -l 'v[1-9]*' | grep -E '^v[1-9][0-9]*\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1 || true)" if [[ -z "${latest}" ]]; then # No stable tag yet — this push becomes v1.0.0. version="1.0.0" From f853c57746a36a741896a619fe1b6796ec3160c3 Mon Sep 17 00:00:00 2001 From: Lee Messenger Date: Fri, 24 Apr 2026 08:30:23 +0100 Subject: [PATCH 2/2] Rewrite docs source links to absolute GitHub URLs Replace relative ../../src/... links with https://github.com/YodasMyDad/Merchello/blob/main/src/... across all documentation pages so source-code references resolve correctly when the docs are rendered on GitHub Pages (relative paths break out of the mkdocs site root). Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/documentation/caching/caching.md | 2 +- docs/documentation/checkout/abandoned-cart.md | 4 +-- .../checkout/checkout-addresses.md | 2 +- docs/documentation/checkout/checkout-api.md | 6 ++-- .../checkout/checkout-authentication.md | 2 +- .../checkout/checkout-discounts.md | 2 +- docs/documentation/checkout/checkout-flow.md | 12 +++---- docs/documentation/checkout/checkout-razor.md | 6 ++-- .../checkout/checkout-session.md | 2 +- .../checkout/checkout-shipping.md | 2 +- .../customers/customer-management.md | 4 +-- .../customers/customer-segments.md | 6 ++-- .../discounts/discounts-overview.md | 18 +++++----- docs/documentation/email/email-overview.md | 18 +++++----- .../extending/commerce-protocol-adapters.md | 4 +-- .../creating-address-lookup-providers.md | 4 +-- .../creating-exchange-rate-providers.md | 4 +-- .../creating-fulfilment-providers.md | 8 ++--- .../extending/creating-payment-providers.md | 12 +++---- .../creating-product-feed-resolvers.md | 14 ++++---- .../extending/creating-shipping-providers.md | 12 +++---- .../extending/creating-tax-providers.md | 6 ++-- .../extending/custom-order-grouping.md | 2 +- .../extending/extension-manager.md | 24 ++++++------- .../extending/notification-handlers.md | 8 ++--- .../getting-started/installation.md | 12 +++---- .../getting-started/project-structure.md | 12 +++---- .../getting-started/seed-data.md | 2 +- .../starter-site-walkthrough.md | 32 ++++++++--------- docs/documentation/index.md | 4 +-- .../multi-currency/exchange-rate-providers.md | 18 +++++----- .../multi-currency/multi-currency-overview.md | 6 ++-- .../notifications/notification-system.md | 36 +++++++++---------- docs/documentation/orders/invoice-editing.md | 2 +- docs/documentation/orders/manual-orders.md | 6 ++-- docs/documentation/orders/orders-overview.md | 8 ++--- docs/documentation/orders/statements.md | 2 +- docs/documentation/payments/payment-links.md | 4 +-- .../payments/payment-providers.md | 4 +-- .../payments/payment-system-overview.md | 24 ++++++------- docs/documentation/payments/refunds.md | 10 +++--- .../payments/saved-payment-methods.md | 14 ++++---- .../product-feeds/product-feeds-overview.md | 16 ++++----- .../products/collections-and-categories.md | 2 +- .../products/digital-products.md | 2 +- .../products/element-type-properties.md | 2 +- .../products/inventory-and-stock.md | 4 +-- .../documentation/products/product-filters.md | 4 +-- .../documentation/products/product-routing.md | 6 ++-- docs/documentation/products/product-views.md | 6 ++-- .../products/products-overview.md | 6 ++-- .../products/variants-and-options.md | 6 ++-- .../reporting/reporting-overview.md | 10 +++--- .../shipping/dynamic-shipping-providers.md | 6 ++-- .../shipping/flat-rate-shipping.md | 6 ++-- docs/documentation/shipping/order-grouping.md | 4 +-- .../shipping/shipping-overview.md | 14 ++++---- docs/documentation/shopping/basket-api.md | 2 +- docs/documentation/shopping/basket-service.md | 4 +-- .../countries-and-regions.md | 10 +++--- .../store-configuration/store-settings.md | 2 +- .../store-configuration/suppliers.md | 2 +- .../store-configuration/warehouses.md | 6 ++-- .../documentation/storefront/price-display.md | 2 +- .../storefront/product-availability.md | 4 +-- .../storefront/storefront-context.md | 4 +-- docs/documentation/tax/tax-overview.md | 6 ++-- docs/documentation/ucp/ucp-overview.md | 24 ++++++------- .../documentation/upsells/upsells-overview.md | 14 ++++---- .../webhooks/webhooks-overview.md | 16 ++++----- 70 files changed, 285 insertions(+), 285 deletions(-) diff --git a/docs/documentation/caching/caching.md b/docs/documentation/caching/caching.md index 4bfb276f..b8cac6c0 100644 --- a/docs/documentation/caching/caching.md +++ b/docs/documentation/caching/caching.md @@ -114,7 +114,7 @@ This means you do not need to manually handle distributed invalidation -- just u ## Common Cache Key Prefixes -Merchello's canonical cache key/tag constants live in `Merchello.Core/Constants.cs` under `CacheKeys` and `CacheTags` (see [Constants.cs](../../../src/Merchello.Core/Constants.cs)). Services that own their own cache also define local constants. Current prefixes in use: +Merchello's canonical cache key/tag constants live in `Merchello.Core/Constants.cs` under `CacheKeys` and `CacheTags` (see [Constants.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Constants.cs)). Services that own their own cache also define local constants. Current prefixes in use: | Prefix / Key | Used For | Source | |---|---|---| diff --git a/docs/documentation/checkout/abandoned-cart.md b/docs/documentation/checkout/abandoned-cart.md index 4f4fcd3d..92bcfff0 100644 --- a/docs/documentation/checkout/abandoned-cart.md +++ b/docs/documentation/checkout/abandoned-cart.md @@ -6,7 +6,7 @@ Industry data shows 60-80% of shopping carts are abandoned. Merchello's abandone **Why you use it:** You do not have to write any tracking code — the existing checkout services update abandoned-checkout records automatically. You enable it by registering `IAbandonedCheckoutService` (configured in `Startup.cs`) and configuring the recovery email topics. -Source: [AbandonedCheckoutDetectionJob.cs](../../../src/Merchello.Core/Checkout/Services/AbandonedCheckoutDetectionJob.cs), [AbandonedCheckoutSettings.cs](../../../src/Merchello.Core/Checkout/AbandonedCheckoutSettings.cs), [CheckoutAbandonedNotificationBase.cs](../../../src/Merchello.Core/Notifications/CheckoutNotifications/CheckoutAbandonedNotificationBase.cs). +Source: [AbandonedCheckoutDetectionJob.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Services/AbandonedCheckoutDetectionJob.cs), [AbandonedCheckoutSettings.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/AbandonedCheckoutSettings.cs), [CheckoutAbandonedNotificationBase.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Notifications/CheckoutNotifications/CheckoutAbandonedNotificationBase.cs). ## How It Works @@ -162,7 +162,7 @@ Subscribe to webhook topics to trigger external platforms (Klaviyo, Mailchimp, e ## Notification Properties -All recovery email notifications inherit from [`CheckoutAbandonedNotificationBase`](../../../src/Merchello.Core/Notifications/CheckoutNotifications/CheckoutAbandonedNotificationBase.cs): +All recovery email notifications inherit from [`CheckoutAbandonedNotificationBase`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Notifications/CheckoutNotifications/CheckoutAbandonedNotificationBase.cs): | Property | Type | Description | |----------|------|-------------| diff --git a/docs/documentation/checkout/checkout-addresses.md b/docs/documentation/checkout/checkout-addresses.md index 9658e3d9..740b1f2b 100644 --- a/docs/documentation/checkout/checkout-addresses.md +++ b/docs/documentation/checkout/checkout-addresses.md @@ -6,7 +6,7 @@ The checkout address step captures the customer's billing and shipping addresses **Why it matters:** Saving the address also triggers a basket recalculation because tax rates, shipping options, and some automatic discounts depend on the shipping destination. This is not just a data save — it is the entry point for location-dependent checkout state. -Source: [CheckoutApiController.SaveAddresses](../../../src/Merchello/Controllers/CheckoutApiController.cs), [ICheckoutService.SaveAddressesAsync](../../../src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutService.cs), [ICheckoutValidator.cs](../../../src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutValidator.cs). +Source: [CheckoutApiController.SaveAddresses](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/CheckoutApiController.cs), [ICheckoutService.SaveAddressesAsync](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutService.cs), [ICheckoutValidator.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutValidator.cs). ## Saving Addresses diff --git a/docs/documentation/checkout/checkout-api.md b/docs/documentation/checkout/checkout-api.md index 0650d417..29a802cd 100644 --- a/docs/documentation/checkout/checkout-api.md +++ b/docs/documentation/checkout/checkout-api.md @@ -4,8 +4,8 @@ Complete REST API reference for all checkout endpoints. These are public-facing All checkout endpoints are prefixed with `/api/merchello/checkout` and accept JSON request bodies. The surface is split across two controllers that share the route prefix: -- [`CheckoutApiController`](../../../src/Merchello/Controllers/CheckoutApiController.cs) — basket, countries, addresses, shipping, discounts, auth, address lookup, recovery. -- [`CheckoutPaymentsApiController`](../../../src/Merchello/Controllers/CheckoutPaymentsApiController.cs) — payment methods, payment session creation, express checkout, widget orders, provider returns. +- [`CheckoutApiController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/CheckoutApiController.cs) — basket, countries, addresses, shipping, discounts, auth, address lookup, recovery. +- [`CheckoutPaymentsApiController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/CheckoutPaymentsApiController.cs) — payment methods, payment session creation, express checkout, widget orders, provider returns. > **Invariant:** Controllers do no business logic. They validate input, delegate to `ICheckoutService` / `ICheckoutPaymentsOrchestrationService`, and map the result. Basket totals are always produced by `CheckoutService.CalculateBasketAsync()`. @@ -21,7 +21,7 @@ Retrieves the current basket with formatted totals in the customer's display cur GET /api/merchello/checkout/basket ``` -**Response** `200 OK` -- [`CheckoutBasketDto`](../../../src/Merchello.Core/Checkout/Dtos/CheckoutBasketDto.cs) +**Response** `200 OK` -- [`CheckoutBasketDto`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Dtos/CheckoutBasketDto.cs) The DTO carries **both** store-currency amounts (for calculation reconciliation) and display-currency amounts (for rendering). Pre-formatted strings are included so views never need to format money themselves. diff --git a/docs/documentation/checkout/checkout-authentication.md b/docs/documentation/checkout/checkout-authentication.md index 9e6505b9..227444f1 100644 --- a/docs/documentation/checkout/checkout-authentication.md +++ b/docs/documentation/checkout/checkout-authentication.md @@ -6,7 +6,7 @@ Customer authentication during checkout -- how Merchello handles email checks, s **Why it exists:** Standard ecommerce flows need all three of guest / sign-in / sign-up from one screen, plus a password-reset path that doesn't dump users back to a generic Umbraco login page. Merchello keeps these inside the checkout UI so conversion isn't broken by authentication detours. -Source: [ICheckoutMemberService.cs](../../../src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutMemberService.cs), [CheckoutApiController.cs](../../../src/Merchello/Controllers/CheckoutApiController.cs), [CheckoutPasswordResetController.cs](../../../src/Merchello/Controllers/CheckoutPasswordResetController.cs). +Source: [ICheckoutMemberService.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutMemberService.cs), [CheckoutApiController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/CheckoutApiController.cs), [CheckoutPasswordResetController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/CheckoutPasswordResetController.cs). ## Overview diff --git a/docs/documentation/checkout/checkout-discounts.md b/docs/documentation/checkout/checkout-discounts.md index bf89f41f..3ffb4fff 100644 --- a/docs/documentation/checkout/checkout-discounts.md +++ b/docs/documentation/checkout/checkout-discounts.md @@ -1,6 +1,6 @@ # Checkout Discounts -Merchello supports three types of discounts during checkout: manual discount codes, automatic discounts, and Google auto-discounts. All discount logic is handled by [`ICheckoutDiscountService`](../../../src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutDiscountService.cs). +Merchello supports three types of discounts during checkout: manual discount codes, automatic discounts, and Google auto-discounts. All discount logic is handled by [`ICheckoutDiscountService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutDiscountService.cs). **What it is:** The checkout-side façade over the discount engine. It knows how to apply, remove, and refresh discounts against a basket while keeping totals in sync. diff --git a/docs/documentation/checkout/checkout-flow.md b/docs/documentation/checkout/checkout-flow.md index 798450b7..6fc5d71d 100644 --- a/docs/documentation/checkout/checkout-flow.md +++ b/docs/documentation/checkout/checkout-flow.md @@ -10,12 +10,12 @@ Merchello provides a built-in, Shopify-style checkout experience. It is a consis | Layer | Responsibility | Source | |-------|----------------|--------| -| `CheckoutContentFinder` | Resolves `/checkout/*` URLs into a virtual page | [CheckoutContentFinder.cs](../../../src/Merchello/Routing/CheckoutContentFinder.cs) | -| `MerchelloCheckoutController` | Razor-hijacked controller that renders each step | [MerchelloCheckoutController.cs](../../../src/Merchello/Controllers/MerchelloCheckoutController.cs) | -| `CheckoutApiController` | Public AJAX surface at `/api/merchello/checkout` | [CheckoutApiController.cs](../../../src/Merchello/Controllers/CheckoutApiController.cs) | -| `CheckoutPaymentsApiController` | Payment/express-checkout endpoints on the same base path | [CheckoutPaymentsApiController.cs](../../../src/Merchello/Controllers/CheckoutPaymentsApiController.cs) | -| `ICheckoutService` | Business logic: basket math, order grouping, address/shipping saves | [ICheckoutService.cs](../../../src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutService.cs) | -| `ICheckoutSessionService` | Per-basket session state (step, addresses, shipping selections, invoice id) | [ICheckoutSessionService.cs](../../../src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutSessionService.cs) | +| `CheckoutContentFinder` | Resolves `/checkout/*` URLs into a virtual page | [CheckoutContentFinder.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Routing/CheckoutContentFinder.cs) | +| `MerchelloCheckoutController` | Razor-hijacked controller that renders each step | [MerchelloCheckoutController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/MerchelloCheckoutController.cs) | +| `CheckoutApiController` | Public AJAX surface at `/api/merchello/checkout` | [CheckoutApiController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/CheckoutApiController.cs) | +| `CheckoutPaymentsApiController` | Payment/express-checkout endpoints on the same base path | [CheckoutPaymentsApiController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/CheckoutPaymentsApiController.cs) | +| `ICheckoutService` | Business logic: basket math, order grouping, address/shipping saves | [ICheckoutService.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutService.cs) | +| `ICheckoutSessionService` | Per-basket session state (step, addresses, shipping selections, invoice id) | [ICheckoutSessionService.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutSessionService.cs) | > **Invariant:** Controllers never touch `DbContext`. All persistence and calculation flows through services. Basket totals are always computed by `CheckoutService.CalculateBasketAsync()` — never in views, controllers, or JS. diff --git a/docs/documentation/checkout/checkout-razor.md b/docs/documentation/checkout/checkout-razor.md index 637ed388..f2a54387 100644 --- a/docs/documentation/checkout/checkout-razor.md +++ b/docs/documentation/checkout/checkout-razor.md @@ -8,7 +8,7 @@ Merchello ships with a Shopify-style checkout rendered via Razor views. This gui ## How Checkout Rendering Works -[`CheckoutContentFinder`](../../../src/Merchello/Routing/CheckoutContentFinder.cs) intercepts any `/checkout/*` URL, parses the step and optional invoice ID from the path, and sets a virtual `IPublishedContent` (`MerchelloCheckoutPage`) whose content type alias is `"MerchelloCheckout"`. Umbraco's route hijacking then resolves [`MerchelloCheckoutController`](../../../src/Merchello/Controllers/MerchelloCheckoutController.cs) by controller-name convention and its `Index(CancellationToken)` action renders the Razor view for the current step. +[`CheckoutContentFinder`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Routing/CheckoutContentFinder.cs) intercepts any `/checkout/*` URL, parses the step and optional invoice ID from the path, and sets a virtual `IPublishedContent` (`MerchelloCheckoutPage`) whose content type alias is `"MerchelloCheckout"`. Umbraco's route hijacking then resolves [`MerchelloCheckoutController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/MerchelloCheckoutController.cs) by controller-name convention and its `Index(CancellationToken)` action renders the Razor view for the current step. You do not create Umbraco content nodes for checkout pages — the virtual content is generated from the URL. @@ -157,8 +157,8 @@ This lets you build the confirmation page however you want while keeping the res Branding is split across two configuration sections: -- `Merchello:Store` binds [`StoreSettings`](../../../src/Merchello.Core/Shared/Models/StoreSettings.cs) — things that are the store's identity (name, **logo URL**, contact details). -- `Merchello:Checkout` binds [`CheckoutSettings`](../../../src/Merchello.Core/Checkout/Models/CheckoutSettings.cs) — checkout-specific colours, typography, and behaviour. +- `Merchello:Store` binds [`StoreSettings`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shared/Models/StoreSettings.cs) — things that are the store's identity (name, **logo URL**, contact details). +- `Merchello:Checkout` binds [`CheckoutSettings`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Models/CheckoutSettings.cs) — checkout-specific colours, typography, and behaviour. ```json { diff --git a/docs/documentation/checkout/checkout-session.md b/docs/documentation/checkout/checkout-session.md index a9d9d42a..d566e5d0 100644 --- a/docs/documentation/checkout/checkout-session.md +++ b/docs/documentation/checkout/checkout-session.md @@ -6,7 +6,7 @@ The checkout session is per-basket state that tracks where the customer is in th **Why it exists:** The basket stores the latest-saved addresses and totals, but the session tracks checkout-specific signals the basket doesn't know about — like which shipping options the customer picked, what rate they were quoted, and which invoice they're paying for. It also enforces security by binding the active invoice to the session cookie so someone with a stolen URL can't pay for someone else's order. -Source: [CheckoutSession.cs](../../../src/Merchello.Core/Checkout/Models/CheckoutSession.cs), [CheckoutSessionService.cs](../../../src/Merchello.Core/Checkout/Services/CheckoutSessionService.cs) +Source: [CheckoutSession.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Models/CheckoutSession.cs), [CheckoutSessionService.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Services/CheckoutSessionService.cs) ## What the Session Stores diff --git a/docs/documentation/checkout/checkout-shipping.md b/docs/documentation/checkout/checkout-shipping.md index 505bc047..a0892122 100644 --- a/docs/documentation/checkout/checkout-shipping.md +++ b/docs/documentation/checkout/checkout-shipping.md @@ -6,7 +6,7 @@ During checkout the customer selects a shipping method for each order group. Thi **Why you need to understand it:** Every basket is at least one order group (one warehouse). Multi-warehouse or vendor baskets produce multiple groups and the customer must pick shipping for each one. Getting the order-grouping, selection-key, and quoted-cost contracts right is what makes multi-warehouse and live-rate carrier checkouts work. -Source: [ICheckoutService.cs](../../../src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutService.cs), [DefaultOrderGroupingStrategy.cs](../../../src/Merchello.Core/Checkout/Strategies/DefaultOrderGroupingStrategy.cs), [OrderGroup.cs](../../../src/Merchello.Core/Checkout/Strategies/Models/OrderGroup.cs). +Source: [ICheckoutService.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutService.cs), [DefaultOrderGroupingStrategy.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Strategies/DefaultOrderGroupingStrategy.cs), [OrderGroup.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Strategies/Models/OrderGroup.cs). ## Order Grouping diff --git a/docs/documentation/customers/customer-management.md b/docs/documentation/customers/customer-management.md index 2974ed4c..da43b08f 100644 --- a/docs/documentation/customers/customer-management.md +++ b/docs/documentation/customers/customer-management.md @@ -4,7 +4,7 @@ Merchello customers are created automatically during checkout -- you do not need ## How It Works -At checkout, `ICustomerService.GetOrCreateByEmailAsync(...)` ([ICustomerService.cs:61](../../../src/Merchello.Core/Customers/Services/Interfaces/ICustomerService.cs#L61)) checks for an existing customer record matching the billing email address. If one exists, the invoice is linked to it. If not, a new customer record is created from the billing address details. +At checkout, `ICustomerService.GetOrCreateByEmailAsync(...)` ([ICustomerService.cs:61](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Customers/Services/Interfaces/ICustomerService.cs#L61)) checks for an existing customer record matching the billing email address. If one exists, the invoice is linked to it. If not, a new customer record is created from the billing address details. This means guest checkout works out of the box. Returning guests are matched by email to their previous orders automatically without needing to sign in. @@ -19,7 +19,7 @@ Merchello `Customer` and Umbraco `Member` are separate concepts: ## Accessing Customer Data in Storefront Code -Inject [`ICustomerService`](../../../src/Merchello.Core/Customers/Services/Interfaces/ICustomerService.cs) to look up customers from Razor controllers or view components: +Inject [`ICustomerService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Customers/Services/Interfaces/ICustomerService.cs) to look up customers from Razor controllers or view components: ```csharp public class AccountController(ICustomerService customerService) diff --git a/docs/documentation/customers/customer-segments.md b/docs/documentation/customers/customer-segments.md index 7d42fffc..c5c96be9 100644 --- a/docs/documentation/customers/customer-segments.md +++ b/docs/documentation/customers/customer-segments.md @@ -1,13 +1,13 @@ # Customer Segments -Customer Segments are an admin tool for grouping customers together to target discounts and promotions. Segments come in two flavors ([`CustomerSegmentType.cs`](../../../src/Merchello.Core/Customers/Models/CustomerSegmentType.cs)): +Customer Segments are an admin tool for grouping customers together to target discounts and promotions. Segments come in two flavors ([`CustomerSegmentType.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Customers/Models/CustomerSegmentType.cs)): - **Manual** -- membership is explicit. Customers are added and removed by a backoffice user (stored in `CustomerSegmentMember`). - **Automated** -- membership is calculated on the fly from criteria rules (e.g. "total spend >= 500" or "first order in the last 30 days"). No table of members exists; evaluation happens per request. ## Criteria Fields (Automated Segments) -Automated segments evaluate against a fixed catalogue of fields ([`SegmentCriteriaField.cs`](../../../src/Merchello.Core/Customers/Models/SegmentCriteriaField.cs)): +Automated segments evaluate against a fixed catalogue of fields ([`SegmentCriteriaField.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Customers/Models/SegmentCriteriaField.cs)): | Category | Fields | | -------- | ------ | @@ -31,7 +31,7 @@ Rules are combined using a `SegmentMatchMode` (`All` / `Any`) and an operator pe Segments are configured entirely in the Merchello backoffice. There are **no segment APIs used on the storefront** -- segments work behind the scenes to control which customers are eligible for specific discounts. -When a segment-restricted discount is active (`DiscountEligibilityType.CustomerSegments` -- see [Discounts Overview](../discounts/discounts-overview.md#segment-targeting)), the discount engine calls [`ICustomerSegmentService.IsCustomerInSegmentAsync(segmentId, customerId, ct)`](../../../src/Merchello.Core/Customers/Services/Interfaces/ICustomerSegmentService.cs#L76) -- the single centralized method for membership checks. Manual segments hit the DB, automated segments evaluate criteria; callers do not need to care which. +When a segment-restricted discount is active (`DiscountEligibilityType.CustomerSegments` -- see [Discounts Overview](../discounts/discounts-overview.md#segment-targeting)), the discount engine calls [`ICustomerSegmentService.IsCustomerInSegmentAsync(segmentId, customerId, ct)`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Customers/Services/Interfaces/ICustomerSegmentService.cs#L76) -- the single centralized method for membership checks. Manual segments hit the DB, automated segments evaluate criteria; callers do not need to care which. No storefront code is needed to make segment-restricted discounts work: the customer is identified by email at checkout, segment membership is resolved at discount-evaluation time, and the applicable discounts flow through the usual basket totals. diff --git a/docs/documentation/discounts/discounts-overview.md b/docs/documentation/discounts/discounts-overview.md index 0b548c28..b790f120 100644 --- a/docs/documentation/discounts/discounts-overview.md +++ b/docs/documentation/discounts/discounts-overview.md @@ -6,21 +6,21 @@ This page covers the storefront surface: the API, how discounts appear in the ba ## Discount Categories -Four `DiscountCategory` values ([`DiscountCategory.cs`](../../../src/Merchello.Core/Discounts/Models/DiscountCategory.cs)): +Four `DiscountCategory` values ([`DiscountCategory.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Discounts/Models/DiscountCategory.cs)): | Category | Description | Notes | | -------- | ----------- | ----- | | `AmountOffProducts` | Discount on specific products, collections, or product types | Targeted via `DiscountTargetRule` (SKUs, collections, product types, tags) | | `AmountOffOrder` | Discount on the entire order subtotal | Applied at order level; honors `RequirementType` / `RequirementValue` | -| `BuyXGetY` | Buy qualifying items, get other items free or discounted | Uses `DiscountBuyXGetYConfig` and [`IBuyXGetYCalculator`](../../../src/Merchello.Core/Discounts/Services/Interfaces/IBuyXGetYCalculator.cs); respects `PerOrderUsageLimit` | +| `BuyXGetY` | Buy qualifying items, get other items free or discounted | Uses `DiscountBuyXGetYConfig` and [`IBuyXGetYCalculator`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Discounts/Services/Interfaces/IBuyXGetYCalculator.cs); respects `PerOrderUsageLimit` | | `FreeShipping` | Free or discounted shipping | Uses `DiscountFreeShippingConfig`; free-shipping allow-lists validate against all selected shipping groups (`DiscountContext.SelectedShippingOptionIds`) | -Each discount has a **method** that controls how it activates ([`DiscountMethod.cs`](../../../src/Merchello.Core/Discounts/Models/DiscountMethod.cs)): +Each discount has a **method** that controls how it activates ([`DiscountMethod.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Discounts/Models/DiscountMethod.cs)): - `Code` -- customer enters a code at checkout - `Automatic` -- applied whenever conditions are met, no code needed -And a **value type** ([`DiscountValueType.cs`](../../../src/Merchello.Core/Accounting/Models/DiscountValueType.cs)): +And a **value type** ([`DiscountValueType.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Accounting/Models/DiscountValueType.cs)): - `FixedAmount` -- e.g. £5 off - `Percentage` -- e.g. 10% off @@ -30,7 +30,7 @@ And a **value type** ([`DiscountValueType.cs`](../../../src/Merchello.Core/Accou ## Applying a Discount Code -Use the checkout API to apply a discount code to the current basket ([`CheckoutApiController.cs:383`](../../../src/Merchello/Controllers/CheckoutApiController.cs#L383)): +Use the checkout API to apply a discount code to the current basket ([`CheckoutApiController.cs:383`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/CheckoutApiController.cs#L383)): ```http POST /api/merchello/checkout/discount/apply @@ -71,11 +71,11 @@ Common failure reasons include expired codes, minimum order value not met, and p DELETE /api/merchello/checkout/discount/{discountId} ``` -Returns the same response shape with the updated basket and `discountDelta`. See [`CheckoutApiController.cs:719`](../../../src/Merchello/Controllers/CheckoutApiController.cs#L719). +Returns the same response shape with the updated basket and `discountDelta`. See [`CheckoutApiController.cs:719`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/CheckoutApiController.cs#L719). ### JavaScript Example (Checkout Runtime) -The checkout runtime JS at `/App_Plugins/Merchello/js/checkout/services/api.js` exposes these methods (source of truth: [`Client/public/js/checkout/services/api.js`](../../../src/Merchello/Client/public/js/checkout/services/api.js)): +The checkout runtime JS at `/App_Plugins/Merchello/js/checkout/services/api.js` exposes these methods (source of truth: [`Client/public/js/checkout/services/api.js`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Client/public/js/checkout/services/api.js)): ```js // Apply a code @@ -109,7 +109,7 @@ Automatic discounts require no customer action. The checkout service evaluates a If a customer adds a fourth item and triggers a "Buy 3 Get 1 Free" promotion, the discount appears automatically. If they remove an item and no longer qualify, the discount is removed and a warning is included in the response. -You do not need to call any API to trigger automatic discount evaluation -- it happens internally whenever basket state changes. See [`ICheckoutDiscountService`](../../../src/Merchello.Core/Discounts/Services/Interfaces/IDiscountEngine.cs) and [Checkout Discounts](../checkout/checkout-discounts.md) for the service-level contract. +You do not need to call any API to trigger automatic discount evaluation -- it happens internally whenever basket state changes. See [`ICheckoutDiscountService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Discounts/Services/Interfaces/IDiscountEngine.cs) and [Checkout Discounts](../checkout/checkout-discounts.md) for the service-level contract. ## How Discounts Appear in the Basket @@ -181,7 +181,7 @@ At each stage, if a previously valid discount becomes invalid (e.g. the customer ## Segment Targeting -A discount's `EligibilityType` ([`DiscountEligibilityType.cs`](../../../src/Merchello.Core/Discounts/Models/DiscountEligibilityType.cs)) decides who qualifies: +A discount's `EligibilityType` ([`DiscountEligibilityType.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Discounts/Models/DiscountEligibilityType.cs)) decides who qualifies: - `AllCustomers` -- no restriction - `CustomerSegments` -- only members of one or more [customer segments](../customers/customer-segments.md) (manual or automated) diff --git a/docs/documentation/email/email-overview.md b/docs/documentation/email/email-overview.md index fb77eacc..1f3ca63c 100644 --- a/docs/documentation/email/email-overview.md +++ b/docs/documentation/email/email-overview.md @@ -4,7 +4,7 @@ Merchello's email system handles all transactional emails -- order confirmations ## How It Works -The email system connects internal Merchello [notifications](../notifications/notification-system.md) to email templates through a topic registry. When something happens in the system (an order is created, a shipment ships, a cart is abandoned), a notification fires. The [`EmailNotificationHandler`](../../../src/Merchello.Core/Email/Handlers/EmailNotificationHandler.cs) picks it up, finds all email configurations for that topic, and queues a delivery for each one. +The email system connects internal Merchello [notifications](../notifications/notification-system.md) to email templates through a topic registry. When something happens in the system (an order is created, a shipment ships, a cart is abandoned), a notification fires. The [`EmailNotificationHandler`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Email/Handlers/EmailNotificationHandler.cs) picks it up, finds all email configurations for that topic, and queues a delivery for each one. ``` Notification fires -> EmailNotificationHandler (priority 2100) -> Find configs for topic -> Queue deliveries @@ -12,7 +12,7 @@ Notification fires -> EmailNotificationHandler (priority 2100) -> Find configs f This means you can have multiple email configurations for the same topic. For example, you might send an order confirmation to the customer and a different notification to your warehouse team -- both triggered by the same `order.created` topic. -> **CLAUDE.md invariant:** The handler swallows (catch/log) all dispatch errors and never rethrows. An email delivery failure must never break the business operation that triggered it. See [EmailNotificationHandler.cs:ProcessEmailsAsync](../../../src/Merchello.Core/Email/Handlers/EmailNotificationHandler.cs#L194). +> **CLAUDE.md invariant:** The handler swallows (catch/log) all dispatch errors and never rethrows. An email delivery failure must never break the business operation that triggered it. See [EmailNotificationHandler.cs:ProcessEmailsAsync](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Email/Handlers/EmailNotificationHandler.cs#L194). ## Email Configurations @@ -46,7 +46,7 @@ Available tokens vary by topic and are listed in the backoffice when you edit an ## Email Topics -Topics are registered in `IEmailTopicRegistry` and defined as constants in [Constants.cs:EmailTopics](../../../src/Merchello.Core/Constants.cs#L303). Each topic maps to a notification type and lists its available tokens. Here are the built-in topics by category: +Topics are registered in `IEmailTopicRegistry` and defined as constants in [Constants.cs:EmailTopics](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Constants.cs#L303). Each topic maps to a notification type and lists its available tokens. Here are the built-in topics by category: ### Orders - `order.created` -- New order placed @@ -101,13 +101,13 @@ Topics are registered in `IEmailTopicRegistry` and defined as constants in [Cons Merchello uses [MJML](https://mjml.io) for email templates. MJML is a markup language that compiles to responsive HTML email. You write simple markup and it generates the complex table-based HTML that email clients need. -Templates are Razor (`.cshtml`) files whose rendered output is compiled to HTML by the `IMjmlCompiler` ([MjmlCompiler.cs](../../../src/Merchello.Core/Email/Services/MjmlCompiler.cs)) and support theme settings (colors, fonts, logo) from `EmailThemeSettings` ([EmailThemeSettings.cs](../../../src/Merchello.Core/Email/EmailThemeSettings.cs)). The provided HTML helpers live in `Merchello.Email.Extensions` so you can mix helpers (`@Html.Mjml().EmailStart(...)`) with raw MJML elements. +Templates are Razor (`.cshtml`) files whose rendered output is compiled to HTML by the `IMjmlCompiler` ([MjmlCompiler.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Email/Services/MjmlCompiler.cs)) and support theme settings (colors, fonts, logo) from `EmailThemeSettings` ([EmailThemeSettings.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Email/EmailThemeSettings.cs)). The provided HTML helpers live in `Merchello.Email.Extensions` so you can mix helpers (`@Html.Mjml().EmailStart(...)`) with raw MJML elements. -Sample templates ship in the example site at [src/Merchello.Site/Views/Emails/](../../../src/Merchello.Site/Views/Emails/) (`OrderConfirmation.cshtml`, `AbandonedCartFirst.cshtml`, `AbandonedCartReminder.cshtml`, `AbandonedCartFinal.cshtml`, `DigitalProductDelivered.cshtml`, `PasswordReset.cshtml`, `SupplierOrder.cshtml`). Copy one as a starting point — they already demonstrate the strongly-typed `EmailModel` pattern. +Sample templates ship in the example site at [src/Merchello.Site/Views/Emails/](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.Site/Views/Emails) (`OrderConfirmation.cshtml`, `AbandonedCartFirst.cshtml`, `AbandonedCartReminder.cshtml`, `AbandonedCartFinal.cshtml`, `DigitalProductDelivered.cshtml`, `PasswordReset.cshtml`, `SupplierOrder.cshtml`). Copy one as a starting point — they already demonstrate the strongly-typed `EmailModel` pattern. ### Template Locations -Templates are resolved from the view locations configured in [EmailSettings.TemplateViewLocations](../../../src/Merchello.Core/Email/EmailSettings.cs#L12) (in order): +Templates are resolved from the view locations configured in [EmailSettings.TemplateViewLocations](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Email/EmailSettings.cs#L12) (in order): 1. `/App_Plugins/Merchello/Views/Emails/{template}.cshtml` 2. `/Views/Emails/{template}.cshtml` @@ -143,7 +143,7 @@ The `EmailAttachmentCleanupJob` removes orphaned attachment files after 72 hours ## Delivery Queue and Retries -Emails are not sent immediately. They are queued as `OutboundDelivery` records ([OutboundDelivery.cs](../../../src/Merchello.Core/Webhooks/Models/OutboundDelivery.cs)) and processed by the `OutboundDeliveryJob` background service ([OutboundDeliveryJob.cs](../../../src/Merchello.Core/Webhooks/Services/OutboundDeliveryJob.cs)). Email and [webhook](../webhooks/webhooks-overview.md) deliveries share the same queue and job; the `DeliveryType` column distinguishes them (`Webhook = 0`, `Email = 1`). +Emails are not sent immediately. They are queued as `OutboundDelivery` records ([OutboundDelivery.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Webhooks/Models/OutboundDelivery.cs)) and processed by the `OutboundDeliveryJob` background service ([OutboundDeliveryJob.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Webhooks/Services/OutboundDeliveryJob.cs)). Email and [webhook](../webhooks/webhooks-overview.md) deliveries share the same queue and job; the `DeliveryType` column distinguishes them (`Webhook = 0`, `Email = 1`). This means: @@ -209,7 +209,7 @@ Full email settings in `appsettings.json`: ## Backoffice API -Routes are relative to the Umbraco management API prefix. Source: [EmailConfigurationApiController.cs](../../../src/Merchello/Controllers/EmailConfigurationApiController.cs), [EmailMetadataApiController.cs](../../../src/Merchello/Controllers/EmailMetadataApiController.cs). +Routes are relative to the Umbraco management API prefix. Source: [EmailConfigurationApiController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/EmailConfigurationApiController.cs), [EmailMetadataApiController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/EmailMetadataApiController.cs). | Endpoint | Method | Description | |---|---|---| @@ -239,4 +239,4 @@ The `EmailNotificationHandler` runs at priority **2100**, which means it execute - [Outbound Webhooks](../webhooks/webhooks-overview.md) - [Background Jobs](../background-jobs/background-jobs.md) - [Abandoned Cart Recovery](../checkout/abandoned-cart.md) -- [Developer reference - docs/EmailSystem.md](../../EmailSystem.md) (internal guide, not shipped as documentation) +- [Developer reference - docs/EmailSystem.md](https://github.com/YodasMyDad/Merchello/blob/main/docs/EmailSystem.md) (internal guide, not shipped as documentation) diff --git a/docs/documentation/extending/commerce-protocol-adapters.md b/docs/documentation/extending/commerce-protocol-adapters.md index dca28938..1c41b56b 100644 --- a/docs/documentation/extending/commerce-protocol-adapters.md +++ b/docs/documentation/extending/commerce-protocol-adapters.md @@ -254,6 +254,6 @@ Protocol adapters have their own notification events: | Adapter | Location | Notes | |---|---|---| -| UCP | [UCPProtocolAdapter.cs](../../../src/Merchello.Core/Protocols/UCP/UCPProtocolAdapter.cs) | Full UCP implementation with checkout, catalog, payments, capability negotiation | +| UCP | [UCPProtocolAdapter.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Protocols/UCP/UCPProtocolAdapter.cs) | Full UCP implementation with checkout, catalog, payments, capability negotiation | -Interface: [ICommerceProtocolAdapter.cs](../../../src/Merchello.Core/Protocols/Interfaces/ICommerceProtocolAdapter.cs). For deeper UCP-specific protocol internals see [`docs/UCP.md`](../../UCP.md). +Interface: [ICommerceProtocolAdapter.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Protocols/Interfaces/ICommerceProtocolAdapter.cs). For deeper UCP-specific protocol internals see [`docs/UCP.md`](https://github.com/YodasMyDad/Merchello/blob/main/docs/UCP.md). diff --git a/docs/documentation/extending/creating-address-lookup-providers.md b/docs/documentation/extending/creating-address-lookup-providers.md index 95c0d245..3d919c08 100644 --- a/docs/documentation/extending/creating-address-lookup-providers.md +++ b/docs/documentation/extending/creating-address-lookup-providers.md @@ -223,6 +223,6 @@ You don't need to create any controllers -- your provider is called automaticall | Provider | Location | Notes | |---|---|---| -| getAddress | [GetAddressLookupProvider.cs](../../../src/Merchello.Core/AddressLookup/Providers/BuiltIn/GetAddressLookupProvider.cs) | UK address lookup, uses getAddress.io API | +| getAddress | [GetAddressLookupProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/AddressLookup/Providers/BuiltIn/GetAddressLookupProvider.cs) | UK address lookup, uses getAddress.io API | -Base class: [AddressLookupProviderBase.cs](../../../src/Merchello.Core/AddressLookup/Providers/AddressLookupProviderBase.cs). Interface: [IAddressLookupProvider.cs](../../../src/Merchello.Core/AddressLookup/Providers/Interfaces/IAddressLookupProvider.cs). +Base class: [AddressLookupProviderBase.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/AddressLookup/Providers/AddressLookupProviderBase.cs). Interface: [IAddressLookupProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/AddressLookup/Providers/Interfaces/IAddressLookupProvider.cs). diff --git a/docs/documentation/extending/creating-exchange-rate-providers.md b/docs/documentation/extending/creating-exchange-rate-providers.md index 1b20c04e..29c8708e 100644 --- a/docs/documentation/extending/creating-exchange-rate-providers.md +++ b/docs/documentation/extending/creating-exchange-rate-providers.md @@ -214,6 +214,6 @@ The exchange rate service decides when to refresh rates from your provider (it c | Provider | Location | Notes | |---|---|---| -| Frankfurter | [FrankfurterExchangeRateProvider.cs](../../../src/Merchello.Core/ExchangeRates/Providers/FrankfurterExchangeRateProvider.cs) | Free ECB rates, no API key needed | +| Frankfurter | [FrankfurterExchangeRateProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Providers/FrankfurterExchangeRateProvider.cs) | Free ECB rates, no API key needed | -Metadata: [ExchangeRateProviderMetadata.cs](../../../src/Merchello.Core/ExchangeRates/Models/ExchangeRateProviderMetadata.cs). Interface: [IExchangeRateProvider.cs](../../../src/Merchello.Core/ExchangeRates/Providers/Interfaces/IExchangeRateProvider.cs). +Metadata: [ExchangeRateProviderMetadata.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Models/ExchangeRateProviderMetadata.cs). Interface: [IExchangeRateProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Providers/Interfaces/IExchangeRateProvider.cs). diff --git a/docs/documentation/extending/creating-fulfilment-providers.md b/docs/documentation/extending/creating-fulfilment-providers.md index eda9bfaa..ce7b0641 100644 --- a/docs/documentation/extending/creating-fulfilment-providers.md +++ b/docs/documentation/extending/creating-fulfilment-providers.md @@ -346,7 +346,7 @@ public override async Task> GetInventory ## Submission Trigger Policies -How orders get submitted to fulfilment providers is controlled by trigger policies. The values are defined on the Supplier Direct-style enum ([SupplierDirectSubmissionTrigger.cs](../../../src/Merchello.Core/Fulfilment/Providers/SupplierDirect/SupplierDirectSubmissionTrigger.cs)): +How orders get submitted to fulfilment providers is controlled by trigger policies. The values are defined on the Supplier Direct-style enum ([SupplierDirectSubmissionTrigger.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Fulfilment/Providers/SupplierDirect/SupplierDirectSubmissionTrigger.cs)): | Policy | Behavior | |---|---| @@ -363,7 +363,7 @@ How orders get submitted to fulfilment providers is controlled by trigger polici | Provider | Location | Notes | |---|---|---| -| ShipBob | [ShipBobFulfilmentProvider.cs](../../../src/Merchello.Core/Fulfilment/Providers/ShipBob/ShipBobFulfilmentProvider.cs) | Full REST API integration with webhooks | -| Supplier Direct | [SupplierDirectFulfilmentProvider.cs](../../../src/Merchello.Core/Fulfilment/Providers/SupplierDirect/SupplierDirectFulfilmentProvider.cs) | CSV/FTP-based submission; supports `OnPaid` and `ExplicitRelease` triggers | +| ShipBob | [ShipBobFulfilmentProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Fulfilment/Providers/ShipBob/ShipBobFulfilmentProvider.cs) | Full REST API integration with webhooks | +| Supplier Direct | [SupplierDirectFulfilmentProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Fulfilment/Providers/SupplierDirect/SupplierDirectFulfilmentProvider.cs) | CSV/FTP-based submission; supports `OnPaid` and `ExplicitRelease` triggers | -Base class: [FulfilmentProviderBase.cs](../../../src/Merchello.Core/Fulfilment/Providers/FulfilmentProviderBase.cs). Metadata: [FulfilmentProviderMetadata.cs](../../../src/Merchello.Core/Fulfilment/Providers/FulfilmentProviderMetadata.cs). Interface: [IFulfilmentProvider.cs](../../../src/Merchello.Core/Fulfilment/Providers/Interfaces/IFulfilmentProvider.cs). +Base class: [FulfilmentProviderBase.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Fulfilment/Providers/FulfilmentProviderBase.cs). Metadata: [FulfilmentProviderMetadata.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Fulfilment/Providers/FulfilmentProviderMetadata.cs). Interface: [IFulfilmentProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Fulfilment/Providers/Interfaces/IFulfilmentProvider.cs). diff --git a/docs/documentation/extending/creating-payment-providers.md b/docs/documentation/extending/creating-payment-providers.md index 0af543de..d7323b86 100644 --- a/docs/documentation/extending/creating-payment-providers.md +++ b/docs/documentation/extending/creating-payment-providers.md @@ -283,7 +283,7 @@ Merchello enforces idempotency at two layers -- preserve both: - `ProcessPaymentRequest.IdempotencyKey` -- callers (checkout, backoffice test panel, API) pass a client-generated key. `PaymentIdempotencyService` caches the result for 24 hours so a retried request returns the cached outcome instead of re-charging. Do not strip or regenerate this key in your provider. - `Payment.WebhookEventId` -- when you record a payment from a webhook, the gateway's event ID flows through `RecordPaymentParameters.WebhookEventId` into `Payment.WebhookEventId`. `PaymentService` rejects a second record with the same `(InvoiceId, WebhookEventId)`, so always surface the gateway event ID from `ProcessWebhookAsync()`. -See [PaymentIdempotencyService.cs](../../../src/Merchello.Core/Payments/Services/PaymentIdempotencyService.cs) and [PaymentService.cs](../../../src/Merchello.Core/Payments/Services/PaymentService.cs) for the authoritative implementations. +See [PaymentIdempotencyService.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Services/PaymentIdempotencyService.cs) and [PaymentService.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Services/PaymentService.cs) for the authoritative implementations. ### Multi-Currency and Rate Locking (CRITICAL) @@ -425,9 +425,9 @@ Study these built-in providers for real-world patterns: | Provider | Location | Notes | |---|---|---| -| Manual Payment | [ManualPaymentProvider.cs](../../../src/Merchello.Core/Payments/Providers/BuiltIn/ManualPaymentProvider.cs) | Simplest example, `DirectForm` integration | -| Stripe | [StripePaymentProvider.cs](../../../src/Merchello.Core/Payments/Providers/Stripe/StripePaymentProvider.cs) | Full-featured: webhooks, refunds, auth/capture, vaulting, payment links | -| PayPal | [PayPalPaymentProvider.cs](../../../src/Merchello.Core/Payments/Providers/PayPal/PayPalPaymentProvider.cs) | Redirect flow | -| Braintree | [BraintreePaymentProvider.cs](../../../src/Merchello.Core/Payments/Providers/Braintree/BraintreePaymentProvider.cs) | SDK embed flow | +| Manual Payment | [ManualPaymentProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Providers/BuiltIn/ManualPaymentProvider.cs) | Simplest example, `DirectForm` integration | +| Stripe | [StripePaymentProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Providers/Stripe/StripePaymentProvider.cs) | Full-featured: webhooks, refunds, auth/capture, vaulting, payment links | +| PayPal | [PayPalPaymentProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Providers/PayPal/PayPalPaymentProvider.cs) | Redirect flow | +| Braintree | [BraintreePaymentProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Providers/Braintree/BraintreePaymentProvider.cs) | SDK embed flow | -Base class: [PaymentProviderBase.cs](../../../src/Merchello.Core/Payments/Providers/PaymentProviderBase.cs). Metadata: [PaymentProviderMetadata.cs](../../../src/Merchello.Core/Payments/Providers/PaymentProviderMetadata.cs). Interface: [IPaymentProvider.cs](../../../src/Merchello.Core/Payments/Providers/Interfaces/IPaymentProvider.cs). +Base class: [PaymentProviderBase.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Providers/PaymentProviderBase.cs). Metadata: [PaymentProviderMetadata.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Providers/PaymentProviderMetadata.cs). Interface: [IPaymentProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Providers/Interfaces/IPaymentProvider.cs). diff --git a/docs/documentation/extending/creating-product-feed-resolvers.md b/docs/documentation/extending/creating-product-feed-resolvers.md index a7939113..dbb2558c 100644 --- a/docs/documentation/extending/creating-product-feed-resolvers.md +++ b/docs/documentation/extending/creating-product-feed-resolvers.md @@ -159,12 +159,12 @@ Merchello includes several built-in resolvers you can use as reference: | Resolver | Alias | Description | Location | |---|---|---|---| -| On Sale | `on-sale` | Returns "true" when sale pricing is active | [ProductFeedOnSaleResolver.cs](../../../src/Merchello.Core/ProductFeeds/Services/ProductFeedOnSaleResolver.cs) | -| Stock Status | `stock-status` | Returns availability based on stock levels | [ProductFeedStockStatusResolver.cs](../../../src/Merchello.Core/ProductFeeds/Services/ProductFeedStockStatusResolver.cs) | -| Product Type | `product-type` | Returns the Google product type | [ProductFeedProductTypeResolver.cs](../../../src/Merchello.Core/ProductFeeds/Services/ProductFeedProductTypeResolver.cs) | -| Collections | `collections` | Returns collection membership | [ProductFeedCollectionsResolver.cs](../../../src/Merchello.Core/ProductFeeds/Services/ProductFeedCollectionsResolver.cs) | -| Supplier | `supplier` | Returns supplier information | [ProductFeedSupplierResolver.cs](../../../src/Merchello.Core/ProductFeeds/Services/ProductFeedSupplierResolver.cs) | -| Native Commerce | `native-commerce` | Composite resolver for OpenAI / native-commerce feed extras | [ProductFeedNativeCommerceResolver.cs](../../../src/Merchello.Core/ProductFeeds/Services/ProductFeedNativeCommerceResolver.cs) | +| On Sale | `on-sale` | Returns "true" when sale pricing is active | [ProductFeedOnSaleResolver.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/Services/ProductFeedOnSaleResolver.cs) | +| Stock Status | `stock-status` | Returns availability based on stock levels | [ProductFeedStockStatusResolver.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/Services/ProductFeedStockStatusResolver.cs) | +| Product Type | `product-type` | Returns the Google product type | [ProductFeedProductTypeResolver.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/Services/ProductFeedProductTypeResolver.cs) | +| Collections | `collections` | Returns collection membership | [ProductFeedCollectionsResolver.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/Services/ProductFeedCollectionsResolver.cs) | +| Supplier | `supplier` | Returns supplier information | [ProductFeedSupplierResolver.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/Services/ProductFeedSupplierResolver.cs) | +| Native Commerce | `native-commerce` | Composite resolver for OpenAI / native-commerce feed extras | [ProductFeedNativeCommerceResolver.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/Services/ProductFeedNativeCommerceResolver.cs) | ## Example: On Sale Resolver (Built-in) @@ -217,4 +217,4 @@ The backoffice uses this registry to show available resolvers in the feed config - **Resolver aliases must be unique** across all discovered assemblies. - **Don't use `Task.WhenAll`** around database-touching work inside a resolver. Umbraco's `EFCoreScope` uses `AsyncLocal` ambient state and concurrent DB calls corrupt it. -Interface: [IProductFeedValueResolver.cs](../../../src/Merchello.Core/ProductFeeds/Services/Interfaces/IProductFeedValueResolver.cs). Context: [ProductFeedResolverContext.cs](../../../src/Merchello.Core/ProductFeeds/Models/ProductFeedResolverContext.cs). +Interface: [IProductFeedValueResolver.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/Services/Interfaces/IProductFeedValueResolver.cs). Context: [ProductFeedResolverContext.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/Models/ProductFeedResolverContext.cs). diff --git a/docs/documentation/extending/creating-shipping-providers.md b/docs/documentation/extending/creating-shipping-providers.md index c09b79ae..7021d86c 100644 --- a/docs/documentation/extending/creating-shipping-providers.md +++ b/docs/documentation/extending/creating-shipping-providers.md @@ -30,8 +30,8 @@ Customer enters address Merchello distinguishes two provider flavors by the `ConfigCapabilities.UsesLiveRates` flag on your `ShippingProviderMetadata`: -- **Flat-rate** (`UsesLiveRates = false`): rates come from the configured `ShippingOption` / `ShippingCost` tables. Selection keys use the `so:{guid}` format. Built-in example: [FlatRateShippingProvider.cs](../../../src/Merchello.Core/Shipping/Providers/BuiltIn/FlatRateShippingProvider.cs). -- **Dynamic / live-rate** (`UsesLiveRates = true`): rates are fetched from a carrier API at checkout time. Selection keys use the `dyn:{providerKey}:{serviceCode}` format. The provider **must not** rely on fixed-cost entries. Visibility is gated by provider enablement *and* the owning warehouse's provider config. `ProductRoot.AllowExternalCarrierShipping = false` blocks dynamic options for that product. Built-in examples: [FedExShippingProvider.cs](../../../src/Merchello.Core/Shipping/Providers/FedEx/FedExShippingProvider.cs), [UpsShippingProvider.cs](../../../src/Merchello.Core/Shipping/Providers/UPS/UpsShippingProvider.cs). +- **Flat-rate** (`UsesLiveRates = false`): rates come from the configured `ShippingOption` / `ShippingCost` tables. Selection keys use the `so:{guid}` format. Built-in example: [FlatRateShippingProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/BuiltIn/FlatRateShippingProvider.cs). +- **Dynamic / live-rate** (`UsesLiveRates = true`): rates are fetched from a carrier API at checkout time. Selection keys use the `dyn:{providerKey}:{serviceCode}` format. The provider **must not** rely on fixed-cost entries. Visibility is gated by provider enablement *and* the owning warehouse's provider config. `ProductRoot.AllowExternalCarrierShipping = false` blocks dynamic options for that product. Built-in examples: [FedExShippingProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/FedEx/FedExShippingProvider.cs), [UpsShippingProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/UPS/UpsShippingProvider.cs). Both live on the same `ShippingProviderBase`; it's the metadata flag and the source of cost data that differ. @@ -339,8 +339,8 @@ The base class includes `ApplyMarkup()` which applies percentage markups from wa | Provider | Location | Notes | |---|---|---| -| Flat Rate | [FlatRateShippingProvider.cs](../../../src/Merchello.Core/Shipping/Providers/BuiltIn/FlatRateShippingProvider.cs) | Configured rates, no API calls. `UsesLiveRates = false`. | -| FedEx | [FedExShippingProvider.cs](../../../src/Merchello.Core/Shipping/Providers/FedEx/FedExShippingProvider.cs) | Live rates from FedEx API. `UsesLiveRates = true`. | -| UPS | [UpsShippingProvider.cs](../../../src/Merchello.Core/Shipping/Providers/UPS/UpsShippingProvider.cs) | Live rates from UPS API. `UsesLiveRates = true`. | +| Flat Rate | [FlatRateShippingProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/BuiltIn/FlatRateShippingProvider.cs) | Configured rates, no API calls. `UsesLiveRates = false`. | +| FedEx | [FedExShippingProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/FedEx/FedExShippingProvider.cs) | Live rates from FedEx API. `UsesLiveRates = true`. | +| UPS | [UpsShippingProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/UPS/UpsShippingProvider.cs) | Live rates from UPS API. `UsesLiveRates = true`. | -Base class: [ShippingProviderBase.cs](../../../src/Merchello.Core/Shipping/Providers/ShippingProviderBase.cs). Metadata: [ShippingProviderMetadata.cs](../../../src/Merchello.Core/Shipping/Providers/ShippingProviderMetadata.cs). Capability flags: [ProviderConfigCapabilities.cs](../../../src/Merchello.Core/Shipping/Providers/ProviderConfigCapabilities.cs). +Base class: [ShippingProviderBase.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/ShippingProviderBase.cs). Metadata: [ShippingProviderMetadata.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/ShippingProviderMetadata.cs). Capability flags: [ProviderConfigCapabilities.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/ProviderConfigCapabilities.cs). diff --git a/docs/documentation/extending/creating-tax-providers.md b/docs/documentation/extending/creating-tax-providers.md index 15aa54c9..7cb8d6dc 100644 --- a/docs/documentation/extending/creating-tax-providers.md +++ b/docs/documentation/extending/creating-tax-providers.md @@ -310,7 +310,7 @@ Tax calculation is coordinated by `ITaxOrchestrationService` (which is itself in | Provider | Location | Notes | |---|---|---| -| Manual Tax | [ManualTaxProvider.cs](../../../src/Merchello.Core/Tax/Providers/BuiltIn/ManualTaxProvider.cs) | Uses manually configured rates per country/region | -| Avalara | [AvalaraTaxProvider.cs](../../../src/Merchello.Core/Tax/Providers/BuiltIn/AvalaraTaxProvider.cs) | Real-time tax calculation via AvaTax API | +| Manual Tax | [ManualTaxProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Tax/Providers/BuiltIn/ManualTaxProvider.cs) | Uses manually configured rates per country/region | +| Avalara | [AvalaraTaxProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Tax/Providers/BuiltIn/AvalaraTaxProvider.cs) | Real-time tax calculation via AvaTax API | -Base class: [TaxProviderBase.cs](../../../src/Merchello.Core/Tax/Providers/TaxProviderBase.cs). Interface: [ITaxProvider.cs](../../../src/Merchello.Core/Tax/Providers/Interfaces/ITaxProvider.cs). +Base class: [TaxProviderBase.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Tax/Providers/TaxProviderBase.cs). Interface: [ITaxProvider.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Tax/Providers/Interfaces/ITaxProvider.cs). diff --git a/docs/documentation/extending/custom-order-grouping.md b/docs/documentation/extending/custom-order-grouping.md index 37401818..eb7effa2 100644 --- a/docs/documentation/extending/custom-order-grouping.md +++ b/docs/documentation/extending/custom-order-grouping.md @@ -24,7 +24,7 @@ Customer submits shipping address ## The Default Strategy -The built-in [`DefaultOrderGroupingStrategy`](../../../src/Merchello.Core/Checkout/Strategies/DefaultOrderGroupingStrategy.cs) (key `default-warehouse`) groups items by warehouse. It: +The built-in [`DefaultOrderGroupingStrategy`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Strategies/DefaultOrderGroupingStrategy.cs) (key `default-warehouse`) groups items by warehouse. It: 1. Selects the best warehouse for each product using the standard warehouse selection order: `ProductRootWarehouse` priority → service region eligibility → stock availability (`Stock - Reserved >= qty`). 2. Groups items shipping from the same warehouse together. diff --git a/docs/documentation/extending/extension-manager.md b/docs/documentation/extending/extension-manager.md index d87250a0..4223c11a 100644 --- a/docs/documentation/extending/extension-manager.md +++ b/docs/documentation/extending/extension-manager.md @@ -18,20 +18,20 @@ App Starts ## What Gets Discovered -Merchello scans for implementations of these interfaces (see [`DiscoverProviderAssemblies()` in `Startup.cs`](../../../src/Merchello/Startup.cs) for the authoritative list): +Merchello scans for implementations of these interfaces (see [`DiscoverProviderAssemblies()` in `Startup.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Startup.cs) for the authoritative list): | Interface | Purpose | |---|---| -| [`IPaymentProvider`](../../../src/Merchello.Core/Payments/Providers/Interfaces/IPaymentProvider.cs) | Payment gateways (Stripe, PayPal, etc.) | -| [`IShippingProvider`](../../../src/Merchello.Core/Shipping/Providers/Interfaces/IShippingProvider.cs) | Shipping rate providers (FedEx, UPS, flat-rate) | -| [`ITaxProvider`](../../../src/Merchello.Core/Tax/Providers/Interfaces/ITaxProvider.cs) | Tax calculation providers (Avalara, manual rates) | -| [`IFulfilmentProvider`](../../../src/Merchello.Core/Fulfilment/Providers/Interfaces/IFulfilmentProvider.cs) | 3PL fulfilment providers (ShipBob, Supplier Direct) | -| [`IExchangeRateProvider`](../../../src/Merchello.Core/ExchangeRates/Providers/Interfaces/IExchangeRateProvider.cs) | Currency exchange rate sources | -| [`IAddressLookupProvider`](../../../src/Merchello.Core/AddressLookup/Providers/Interfaces/IAddressLookupProvider.cs) | Address autocomplete/validation providers | -| [`IOrderGroupingStrategy`](../../../src/Merchello.Core/Checkout/Strategies/Interfaces/IOrderGroupingStrategy.cs) | Custom order grouping strategies | -| [`ICommerceProtocolAdapter`](../../../src/Merchello.Core/Protocols/Interfaces/ICommerceProtocolAdapter.cs) | Commerce protocol adapters (UCP) | -| [`IProductFeedValueResolver`](../../../src/Merchello.Core/ProductFeeds/Services/Interfaces/IProductFeedValueResolver.cs) | Custom product feed field resolvers | -| `IMerchelloAction` | Custom checkout/invoice actions (see [MerchelloActions.md](../../MerchelloActions.md)) | +| [`IPaymentProvider`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Providers/Interfaces/IPaymentProvider.cs) | Payment gateways (Stripe, PayPal, etc.) | +| [`IShippingProvider`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/Interfaces/IShippingProvider.cs) | Shipping rate providers (FedEx, UPS, flat-rate) | +| [`ITaxProvider`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Tax/Providers/Interfaces/ITaxProvider.cs) | Tax calculation providers (Avalara, manual rates) | +| [`IFulfilmentProvider`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Fulfilment/Providers/Interfaces/IFulfilmentProvider.cs) | 3PL fulfilment providers (ShipBob, Supplier Direct) | +| [`IExchangeRateProvider`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Providers/Interfaces/IExchangeRateProvider.cs) | Currency exchange rate sources | +| [`IAddressLookupProvider`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/AddressLookup/Providers/Interfaces/IAddressLookupProvider.cs) | Address autocomplete/validation providers | +| [`IOrderGroupingStrategy`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Strategies/Interfaces/IOrderGroupingStrategy.cs) | Custom order grouping strategies | +| [`ICommerceProtocolAdapter`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Protocols/Interfaces/ICommerceProtocolAdapter.cs) | Commerce protocol adapters (UCP) | +| [`IProductFeedValueResolver`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/Services/Interfaces/IProductFeedValueResolver.cs) | Custom product feed field resolvers | +| `IMerchelloAction` | Custom checkout/invoice actions (see [MerchelloActions.md](https://github.com/YodasMyDad/Merchello/blob/main/docs/MerchelloActions.md)) | | `IHealthCheck` | Custom health checks surfaced in the backoffice | | `IEmailAttachment` | Custom email attachment providers | @@ -65,7 +65,7 @@ builder.CreateUmbracoBuilder() ## How ExtensionManager Instantiates Providers -The [`ExtensionManager`](../../../src/Merchello.Core/Shared/Reflection/ExtensionManager.cs) uses `ActivatorUtilities.CreateInstance()` from the DI container. This means your providers can use **constructor injection** for any registered service: +The [`ExtensionManager`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shared/Reflection/ExtensionManager.cs) uses `ActivatorUtilities.CreateInstance()` from the DI container. This means your providers can use **constructor injection** for any registered service: ```csharp public class AcmeShippingProvider( diff --git a/docs/documentation/extending/notification-handlers.md b/docs/documentation/extending/notification-handlers.md index 62ea458a..bb94f697 100644 --- a/docs/documentation/extending/notification-handlers.md +++ b/docs/documentation/extending/notification-handlers.md @@ -69,7 +69,7 @@ public class OrderCreatedSyncHandler( ## Registering Your Handler -Unlike providers (which are auto-discovered), notification handlers must be **explicitly registered** against the Umbraco notification pipeline. This is standard Umbraco v17 behavior. See [`src/Merchello/Startup.cs`](../../../src/Merchello/Startup.cs) for how Merchello's own handlers are wired. +Unlike providers (which are auto-discovered), notification handlers must be **explicitly registered** against the Umbraco notification pipeline. This is standard Umbraco v17 behavior. See [`src/Merchello/Startup.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Startup.cs) for how Merchello's own handlers are wired. Register handlers in your `Startup.cs` or a composer: @@ -291,7 +291,7 @@ public async Task HandleAsync(OrderCreatedNotification notification, Cancellatio ## Reference -- Priority attribute: [`NotificationHandlerPriorityAttribute.cs`](../../../src/Merchello.Core/Notifications/NotificationHandlerPriorityAttribute.cs) (default `1000`, lower runs first). -- Cancelable base class: [`MerchelloCancelableNotification.cs`](../../../src/Merchello.Core/Notifications/Base/MerchelloCancelableNotification.cs) (exposes `Entity`, `CancelOperation(reason)`, `CancelReason`). -- Built-in registrations: [`src/Merchello/Startup.cs`](../../../src/Merchello/Startup.cs) (search for `AddNotificationAsyncHandler`). +- Priority attribute: [`NotificationHandlerPriorityAttribute.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Notifications/NotificationHandlerPriorityAttribute.cs) (default `1000`, lower runs first). +- Cancelable base class: [`MerchelloCancelableNotification.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Notifications/Base/MerchelloCancelableNotification.cs) (exposes `Entity`, `CancelOperation(reason)`, `CancelReason`). +- Built-in registrations: [`src/Merchello/Startup.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Startup.cs) (search for `AddNotificationAsyncHandler`). - Real-world handler examples: built-in handlers such as `InvoiceTimelineHandler` and `WebhookNotificationHandler` wired from `Startup.cs`, plus the `Merchello.ActionExamples` project for action-driven handlers. diff --git a/docs/documentation/getting-started/installation.md b/docs/documentation/getting-started/installation.md index 7203fe92..c760ce72 100644 --- a/docs/documentation/getting-started/installation.md +++ b/docs/documentation/getting-started/installation.md @@ -6,7 +6,7 @@ Merchello is an enterprise ecommerce package for Umbraco v17+. This guide walks Before you begin, make sure you have: -- **.NET 10 SDK** installed (Merchello targets `net10.0`; see [Merchello.csproj](../../../src/Merchello/Merchello.csproj)) +- **.NET 10 SDK** installed (Merchello targets `net10.0`; see [Merchello.csproj](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Merchello.csproj)) - **Umbraco v17+** (Merchello is built specifically for this major version) - A code editor (Visual Studio, VS Code, Rider, etc.) @@ -16,7 +16,7 @@ Merchello is in public beta. The current package versions are visible on [nuget. ## Option 1: .NET Template (Recommended for New Projects) -The fastest way to get started is with the Merchello starter site template. This gives you a working example store with products, categories, basket, and checkout already wired up. The template mirrors the in-repo example at [src/Merchello.Site](../../../src/Merchello.Site) (see [the prepare-starter-template script](../../../scripts/prepare-starter-template.ps1) for how it is produced) with Merchello referenced as a NuGet package instead of a project reference. +The fastest way to get started is with the Merchello starter site template. This gives you a working example store with products, categories, basket, and checkout already wired up. The template mirrors the in-repo example at [src/Merchello.Site](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.Site) (see [the prepare-starter-template script](https://github.com/YodasMyDad/Merchello/blob/main/scripts/prepare-starter-template.ps1) for how it is produced) with Merchello referenced as a NuGet package instead of a project reference. ```bash # Install the template (once per machine) @@ -26,7 +26,7 @@ dotnet new install Umbraco.Community.Merchello.StarterSite:: dotnet new merchello-starter -n MyStore ``` -This creates a project named `MyStore.Web` (the `.Web` suffix is added automatically by the template, defined in [.template.config/template.json](../../../src/Merchello.Site/.template.config/template.json)). If you want a different project name, use the `--projectName` flag: +This creates a project named `MyStore.Web` (the `.Web` suffix is added automatically by the template, defined in [.template.config/template.json](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/.template.config/template.json)). If you want a different project name, use the `--projectName` flag: ```bash dotnet new merchello-starter -n MyStore --projectName MyCustomProjectName @@ -46,7 +46,7 @@ dotnet add package Umbraco.Community.Merchello --version ### Register Merchello in Program.cs -After installing the package, you need to add Merchello to the Umbraco builder pipeline. Open your `Program.cs` and add `.AddMerchello()`. This mirrors exactly what the starter site does in [Program.cs](../../../src/Merchello.Site/Program.cs): +After installing the package, you need to add Merchello to the Umbraco builder pipeline. Open your `Program.cs` and add `.AddMerchello()`. This mirrors exactly what the starter site does in [Program.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Program.cs): ```csharp var builder = WebApplication.CreateBuilder(args); @@ -77,7 +77,7 @@ app.UseUmbraco() await app.RunAsync(); ``` -The `AddMerchello()` extension registers every Merchello service, factory, background job, notification handler, email/webhook handler, content finder, and SignalR hub in one call. See [Startup.cs](../../../src/Merchello/Startup.cs) for the full registration list. You do not need to wire up middleware manually -- Merchello installs itself via an `IStartupFilter` and an `UmbracoPipelineFilter` internally. +The `AddMerchello()` extension registers every Merchello service, factory, background job, notification handler, email/webhook handler, content finder, and SignalR hub in one call. See [Startup.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Startup.cs) for the full registration list. You do not need to wire up middleware manually -- Merchello installs itself via an `IStartupFilter` and an `UmbracoPipelineFilter` internally. > **Note:** Merchello shares Umbraco's database. It registers its own `MerchelloDbContext` against Umbraco's configured database provider (SQLite or SQL Server) -- you do not need a separate connection string. @@ -131,7 +131,7 @@ See the [Seed Data](./seed-data.md) guide for details on exactly what gets creat When Merchello starts for the first time, it automatically: -1. **Runs database migrations** -- `RunMerchMigration` (wired in [Startup.cs](../../../src/Merchello/Startup.cs)) applies EF Core migrations and creates every Merchello table alongside your Umbraco tables. +1. **Runs database migrations** -- `RunMerchMigration` (wired in [Startup.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Startup.cs)) applies EF Core migrations and creates every Merchello table alongside your Umbraco tables. 2. **Ensures built-in payment providers** -- the Manual Payment provider is created and enabled so you can process test orders without configuring a gateway. 3. **Installs essential data types** -- the product description TipTap rich text editor is registered. This runs regardless of `InstallSeedData`. 4. **Registers product routing** -- if `EnableProductRendering` is `true` (the default), `ProductContentFinder` resolves product root URLs directly without an Umbraco content node. diff --git a/docs/documentation/getting-started/project-structure.md b/docs/documentation/getting-started/project-structure.md index 7eab9cab..a9650fd8 100644 --- a/docs/documentation/getting-started/project-structure.md +++ b/docs/documentation/getting-started/project-structure.md @@ -6,7 +6,7 @@ Merchello is split into clearly separated projects, each with a specific respons ### Merchello.Core -This is the heart of the system. It contains all business logic, domain models, services, data access, and provider architecture. It has **no dependency on Umbraco's web layer** -- it only depends on Umbraco's core EF libraries. Browse at [src/Merchello.Core](../../../src/Merchello.Core). +This is the heart of the system. It contains all business logic, domain models, services, data access, and provider architecture. It has **no dependency on Umbraco's web layer** -- it only depends on Umbraco's core EF libraries. Browse at [src/Merchello.Core](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.Core). ``` Merchello.Core/ @@ -43,7 +43,7 @@ Merchello.Core/ Webhooks/ # Outbound webhook delivery, topic registry ``` -Two sibling projects, [Merchello.Core.SqlServer](../../../src/Merchello.Core.SqlServer) and [Merchello.Core.Sqlite](../../../src/Merchello.Core.Sqlite), provide provider-specific EF Core migrations. Both ship as transitive dependencies of the `Umbraco.Community.Merchello` NuGet package. +Two sibling projects, [Merchello.Core.SqlServer](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.Core.SqlServer) and [Merchello.Core.Sqlite](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.Core.Sqlite), provide provider-specific EF Core migrations. Both ship as transitive dependencies of the `Umbraco.Community.Merchello` NuGet package. Each feature area follows a consistent internal structure: @@ -63,7 +63,7 @@ Feature/ ### Merchello (Web Project) -This is the Umbraco integration layer. It handles HTTP concerns, routing, backoffice UI, and view rendering. It depends on `Merchello.Core` but never the other way around. Browse at [src/Merchello](../../../src/Merchello). +This is the Umbraco integration layer. It handles HTTP concerns, routing, backoffice UI, and view rendering. It depends on `Merchello.Core` but never the other way around. Browse at [src/Merchello](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello). ``` Merchello/ @@ -84,11 +84,11 @@ Merchello/ wwwroot/ # Static assets (built into App_Plugins/Merchello) ``` -The single `AddMerchello()` extension in [Startup.cs](../../../src/Merchello/Startup.cs) registers every service, factory, background job, notification handler, and content finder. You rarely need additional wiring in your own `Program.cs`. +The single `AddMerchello()` extension in [Startup.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Startup.cs) registers every service, factory, background job, notification handler, and content finder. You rarely need additional wiring in your own `Program.cs`. ### Merchello.Site (Example Store) -This is the starter site -- a working example that shows how to build a storefront using Merchello. When you use the .NET template (via [Merchello.StarterSite.Template](../../../src/Merchello.StarterSite.Template)) you get a copy of this project with `Merchello` referenced as a NuGet package instead of a project reference. Browse at [src/Merchello.Site](../../../src/Merchello.Site). +This is the starter site -- a working example that shows how to build a storefront using Merchello. When you use the .NET template (via [Merchello.StarterSite.Template](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.StarterSite.Template)) you get a copy of this project with `Merchello` referenced as a NuGet package instead of a project reference. Browse at [src/Merchello.Site](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.Site). ``` Merchello.Site/ @@ -116,7 +116,7 @@ Merchello.Site/ appsettings.json # Full configuration example ``` -The starter template lives at [src/Merchello.StarterSite.Template](../../../src/Merchello.StarterSite.Template) and is regenerated from `Merchello.Site` by [scripts/prepare-starter-template.ps1](../../../scripts/prepare-starter-template.ps1) for each release. +The starter template lives at [src/Merchello.StarterSite.Template](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.StarterSite.Template) and is regenerated from `Merchello.Site` by [scripts/prepare-starter-template.ps1](https://github.com/YodasMyDad/Merchello/blob/main/scripts/prepare-starter-template.ps1) for each release. ## Architecture Principles diff --git a/docs/documentation/getting-started/seed-data.md b/docs/documentation/getting-started/seed-data.md index cfcf7961..05820327 100644 --- a/docs/documentation/getting-started/seed-data.md +++ b/docs/documentation/getting-started/seed-data.md @@ -6,7 +6,7 @@ Merchello includes a comprehensive seed data system that populates your store wi Seed data is a set of sample products, warehouses, suppliers, customers, invoices, discounts, and more that gets installed into your Merchello store. It uses the same services your application code uses (no direct `DbContext` access), so it also serves as a battle test of the entire order flow. -Source: [src/Merchello.Core/Data/Seeding/DbSeeder.cs](../../../src/Merchello.Core/Data/Seeding/DbSeeder.cs). The seeder is registered as a scoped service in [Startup.cs](../../../src/Merchello/Startup.cs) and is invoked from the backoffice install action -- not from `AddMerchello()` or any startup handler. +Source: [src/Merchello.Core/Data/Seeding/DbSeeder.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Data/Seeding/DbSeeder.cs). The seeder is registered as a scoped service in [Startup.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Startup.cs) and is invoked from the backoffice install action -- not from `AddMerchello()` or any startup handler. ## How to Install diff --git a/docs/documentation/getting-started/starter-site-walkthrough.md b/docs/documentation/getting-started/starter-site-walkthrough.md index 473427a3..cc431c25 100644 --- a/docs/documentation/getting-started/starter-site-walkthrough.md +++ b/docs/documentation/getting-started/starter-site-walkthrough.md @@ -17,13 +17,13 @@ The starter site is deliberately simple -- it is a bare-bones example showing th - A basket/cart page - Product detail pages (handled by Merchello's built-in routing via `ProductContentFinder`) -Source: [src/Merchello.Site](../../../src/Merchello.Site). The entry point is [Program.cs](../../../src/Merchello.Site/Program.cs) (a vanilla Umbraco host with `.AddMerchello()` added to the builder pipeline). +Source: [src/Merchello.Site](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.Site). The entry point is [Program.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Program.cs) (a vanilla Umbraco host with `.AddMerchello()` added to the builder pipeline). ## First Run: 15-Minute Flow 1. `dotnet run` and complete the Umbraco install wizard. 2. In the backoffice, enable the **Merchello** section on your user group (Settings > User Groups > Allowed Sections). -3. Run the uSync dashboard import to load the sample document types and content tree from [uSync/v17](../../../src/Merchello.Site/uSync/v17). +3. Run the uSync dashboard import to load the sample document types and content tree from [uSync/v17](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.Site/uSync/v17). 4. Open the **Merchello** section, click the root node, and click **Install Seed Data**. This populates products, warehouses, suppliers, customers, and sample invoices (see [Seed Data](./seed-data.md)). 5. Browse the homepage -- `HomeController` renders best sellers pulled from `IReportingService`. 6. Click a category (e.g. "Clothing") -- `CategoryController` renders a filtered, paged product grid. @@ -61,7 +61,7 @@ Merchello.Site/ ## The Base Controller -All site controllers inherit from `BaseController` ([Shared/Controllers/BaseController.cs](../../../src/Merchello.Site/Shared/Controllers/BaseController.cs)), which extends Umbraco's `SurfaceController` and implements `IRenderController`: +All site controllers inherit from `BaseController` ([Shared/Controllers/BaseController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Shared/Controllers/BaseController.cs)), which extends Umbraco's `SurfaceController` and implements `IRenderController`: ```csharp public class BaseController( @@ -88,7 +88,7 @@ This gives you Umbraco's route hijacking -- when a content node uses a document ## HomeController -The homepage shows best-selling products using Merchello's reporting service. Source: [Home/Controllers/HomeController.cs](../../../src/Merchello.Site/Home/Controllers/HomeController.cs). +The homepage shows best-selling products using Merchello's reporting service. Source: [Home/Controllers/HomeController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Home/Controllers/HomeController.cs). ```csharp public class HomeController( @@ -108,11 +108,11 @@ Key points: - The controller injects `IReportingService` from Merchello - `GetBestSellersAsync(take: 8)` queries the top 8 best-selling products by order volume (ranked by paid-order line quantity) -- The model is the Umbraco published content model `Home`. The `BestSellers` property is defined on the partial class in [Home/Models/Home.cs](../../../src/Merchello.Site/Home/Models/Home.cs) as `List`. +- The model is the Umbraco published content model `Home`. The `BestSellers` property is defined on the partial class in [Home/Models/Home.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Home/Models/Home.cs) as `List`. ## BasketController -The basket page loads the customer's current basket and maps it for display. Source: [Basket/Controllers/BasketController.cs](../../../src/Merchello.Site/Basket/Controllers/BasketController.cs). +The basket page loads the customer's current basket and maps it for display. Source: [Basket/Controllers/BasketController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Basket/Controllers/BasketController.cs). ```csharp public class BasketController( @@ -158,7 +158,7 @@ Key concepts: ## CategoryController -The category page demonstrates querying products with filtering, sorting, and pagination. Source: [Category/Controllers/CategoryController.cs](../../../src/Merchello.Site/Category/Controllers/CategoryController.cs) and [Category/Models/CategoryPageViewModel.cs](../../../src/Merchello.Site/Category/Models/CategoryPageViewModel.cs). +The category page demonstrates querying products with filtering, sorting, and pagination. Source: [Category/Controllers/CategoryController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Category/Controllers/CategoryController.cs) and [Category/Models/CategoryPageViewModel.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Category/Models/CategoryPageViewModel.cs). ```csharp public class CategoryController( @@ -238,14 +238,14 @@ When a request comes in for a URL like `/mesh-office-chair`, Merchello's `Produc You do not write a controller for product pages. Instead, you create Razor views in `~/Views/Products/` -- the location is controlled by the `ProductViewLocations` config key ([Configuration Reference](./configuration-reference.md#product-view-locations)). See the [Product Routing](../products/product-routing.md) and [Building Product Views](../products/product-views.md) guides for full details. -The starter site includes [Views/Products/Default.cshtml](../../../src/Merchello.Site/Views/Products/Default.cshtml) which demonstrates: +The starter site includes [Views/Products/Default.cshtml](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Products/Default.cshtml) which demonstrates: -- Image gallery with variant-specific images ([_ProductGallery.cshtml](../../../src/Merchello.Site/Views/Products/Partials/_ProductGallery.cshtml)) +- Image gallery with variant-specific images ([_ProductGallery.cshtml](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Products/Partials/_ProductGallery.cshtml)) - Price display with tax and multi-currency support via `IStorefrontContextService.GetDisplayContextAsync()` -- Variant option selectors (color swatches, size dropdowns) in [_ProductPurchasePanel.cshtml](../../../src/Merchello.Site/Views/Products/Partials/_ProductPurchasePanel.cshtml) +- Variant option selectors (color swatches, size dropdowns) in [_ProductPurchasePanel.cshtml](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Products/Partials/_ProductPurchasePanel.cshtml) - Add-on options (`IsVariant = false`) with `PriceAdjustment` and `SkuSuffix` - Stock availability display via `GetProductAvailabilityAsync` -- Post-purchase upsells via [_ProductUpsells.cshtml](../../../src/Merchello.Site/Views/Products/Partials/_ProductUpsells.cshtml) +- Post-purchase upsells via [_ProductUpsells.cshtml](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Products/Partials/_ProductUpsells.cshtml) - Schema.org structured data and SEO meta tags - Add-to-cart wired to the storefront basket API @@ -253,19 +253,19 @@ The starter site includes [Views/Products/Default.cshtml](../../../src/Merchello ### Website.cshtml (Layout) -[Views/Website.cshtml](../../../src/Merchello.Site/Views/Website.cshtml) wraps all pages with a consistent header, navigation, and footer. All page views reference it via `Layout = "Website.cshtml"`. +[Views/Website.cshtml](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Website.cshtml) wraps all pages with a consistent header, navigation, and footer. All page views reference it via `Layout = "Website.cshtml"`. ### Home.cshtml -[Views/Home.cshtml](../../../src/Merchello.Site/Views/Home.cshtml) renders the homepage content and loops through `Model.BestSellers` to show product cards (via the `ProductBox` view component). +[Views/Home.cshtml](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Home.cshtml) renders the homepage content and loops through `Model.BestSellers` to show product cards (via the `ProductBox` view component). ### Basket.cshtml -[Views/Basket.cshtml](../../../src/Merchello.Site/Views/Basket.cshtml) reads the basket data from `ViewBag.BasketData` (set by `BasketController`) and renders line items with quantities, prices, and a checkout link. +[Views/Basket.cshtml](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Basket.cshtml) reads the basket data from `ViewBag.BasketData` (set by `BasketController`) and renders line items with quantities, prices, and a checkout link. ### Category.cshtml -[Views/Category.cshtml](../../../src/Merchello.Site/Views/Category.cshtml) uses the `CategoryPageViewModel` to render a product grid with sidebar filters (color, size, etc.), price range slider, sort dropdown, and pagination. +[Views/Category.cshtml](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Category.cshtml) uses the `CategoryPageViewModel` to render a product grid with sidebar filters (color, size, etc.), price range slider, sort dropdown, and pagination. ## How to Extend @@ -277,7 +277,7 @@ The starter site is a starting point, not a finished store. Common ways to exten 4. **Style the checkout** -- the integrated checkout reads theme tokens from the `Merchello:Checkout` configuration section (see [Configuration Reference](./configuration-reference.md#checkout-settings)). 5. **Override order grouping** -- set `OrderGroupingStrategy` in config or implement `IOrderGroupingStrategy` and register it. See [Custom Order Grouping](../extending/custom-order-grouping.md). -> **Controller rule:** Site controllers orchestrate HTTP and delegate to Merchello services. Never access `DbContext` directly, and never duplicate business math (basket totals, tax, shipping cost, payment status) -- always call the designated service. See the invariants in the [project rules](../../../.claude/CLAUDE.md). +> **Controller rule:** Site controllers orchestrate HTTP and delegate to Merchello services. Never access `DbContext` directly, and never duplicate business math (basket totals, tax, shipping cost, payment status) -- always call the designated service. See the invariants in the [project rules](https://github.com/YodasMyDad/Merchello/blob/main/.claude/claude.md). ## Next Steps diff --git a/docs/documentation/index.md b/docs/documentation/index.md index bea175d6..f8c53709 100644 --- a/docs/documentation/index.md +++ b/docs/documentation/index.md @@ -17,7 +17,7 @@ dotnet new merchello-starter -n MyStore dotnet add package Umbraco.Community.Merchello --version ``` -Then add Merchello to your Umbraco builder (see [Program.cs](../../src/Merchello.Site/Program.cs)): +Then add Merchello to your Umbraco builder (see [Program.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Program.cs)): ```csharp builder.CreateUmbracoBuilder() @@ -28,7 +28,7 @@ builder.CreateUmbracoBuilder() .Build(); ``` -Configure your store in `appsettings.json` (see the [starter site appsettings.json](../../src/Merchello.Site/appsettings.json) for a full example): +Configure your store in `appsettings.json` (see the [starter site appsettings.json](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/appsettings.json) for a full example): ```json { diff --git a/docs/documentation/multi-currency/exchange-rate-providers.md b/docs/documentation/multi-currency/exchange-rate-providers.md index c137078d..7fe58123 100644 --- a/docs/documentation/multi-currency/exchange-rate-providers.md +++ b/docs/documentation/multi-currency/exchange-rate-providers.md @@ -4,18 +4,18 @@ Merchello uses a pluggable provider system for fetching exchange rates. Out of t ## Provider Architecture -Exchange rate providers follow the same pattern as other Merchello providers (see [`IExchangeRateProvider.cs`](../../../src/Merchello.Core/ExchangeRates/Providers/Interfaces/IExchangeRateProvider.cs)): +Exchange rate providers follow the same pattern as other Merchello providers (see [`IExchangeRateProvider.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Providers/Interfaces/IExchangeRateProvider.cs)): 1. Discovered automatically by `ExtensionManager` during startup. 2. Configured through the backoffice with provider-specific settings (`GetConfigurationFieldsAsync` + `ConfigureAsync`). -3. **Only one provider can be active at a time** -- radio selection, enforced by [`ExchangeRateProviderManager.SetActiveProviderAsync(...)`](../../../src/Merchello.Core/ExchangeRates/Providers/ExchangeRateProviderManager.cs#L149). +3. **Only one provider can be active at a time** -- radio selection, enforced by [`ExchangeRateProviderManager.SetActiveProviderAsync(...)`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Providers/ExchangeRateProviderManager.cs#L149). -When you activate a provider, any previously active provider is automatically deactivated. If no provider is explicitly activated, the manager falls back to Frankfurter when it is present ([ExchangeRateProviderManager.cs:117](../../../src/Merchello.Core/ExchangeRates/Providers/ExchangeRateProviderManager.cs#L117)). +When you activate a provider, any previously active provider is automatically deactivated. If no provider is explicitly activated, the manager falls back to Frankfurter when it is present ([ExchangeRateProviderManager.cs:117](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Providers/ExchangeRateProviderManager.cs#L117)). ## Built-in: Frankfurter (ECB Rates) **Alias:** `frankfurter` -**Source:** [`FrankfurterExchangeRateProvider.cs`](../../../src/Merchello.Core/ExchangeRates/Providers/FrankfurterExchangeRateProvider.cs) +**Source:** [`FrankfurterExchangeRateProvider.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Providers/FrankfurterExchangeRateProvider.cs) The Frankfurter provider fetches rates from [frankfurter.dev](https://frankfurter.dev), which sources data from the European Central Bank. It is free, requires no API key and supports all major currencies. @@ -34,7 +34,7 @@ The Frankfurter provider fetches rates from [frankfurter.dev](https://frankfurte GET https://api.frankfurter.dev/v1/latest?base=GBP ``` -The provider normalizes the response and returns an [`ExchangeRateResult`](../../../src/Merchello.Core/ExchangeRates/Models/ExchangeRateResult.cs) containing the base currency, a `Dictionary` of rates and a UTC timestamp. +The provider normalizes the response and returns an [`ExchangeRateResult`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Models/ExchangeRateResult.cs) containing the base currency, a `Dictionary` of rates and a UTC timestamp. ### Limitations @@ -53,7 +53,7 @@ Consider building a custom provider ([guide](../extending/creating-exchange-rate ## Exchange Rate Cache -Rates are not fetched on every request. Merchello uses a multi-layer cache via [`IExchangeRateCache`](../../../src/Merchello.Core/ExchangeRates/Services/Interfaces/IExchangeRateCache.cs): +Rates are not fetched on every request. Merchello uses a multi-layer cache via [`IExchangeRateCache`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Services/Interfaces/IExchangeRateCache.cs): ### Cache flow @@ -72,7 +72,7 @@ Request for rate ### Cache configuration -Configured via [`ExchangeRateOptions`](../../../src/Merchello.Core/ExchangeRates/Models/ExchangeRateOptions.cs) bound from `appsettings.json`: +Configured via [`ExchangeRateOptions`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Models/ExchangeRateOptions.cs) bound from `appsettings.json`: ```json { @@ -133,7 +133,7 @@ Snapshots serve as the fallback when: ## Rate Quotes -When you request a rate via `IExchangeRateCache.GetRateQuoteAsync(...)`, you get an [`ExchangeRateQuote`](../../../src/Merchello.Core/ExchangeRates/Models/ExchangeRateQuote.cs) containing: +When you request a rate via `IExchangeRateCache.GetRateQuoteAsync(...)`, you get an [`ExchangeRateQuote`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Models/ExchangeRateQuote.cs) containing: | Property | Description | |----------|-------------| @@ -231,7 +231,7 @@ Package your provider as a NuGet package referencing `Merchello.Core`. When the ### Key points for custom providers - **Identity rate short-circuit**: if `fromCurrency == toCurrency`, return `1m` before doing anything else. -- **Error handling**: return `ExchangeRateResult(Success: false, ..., ErrorMessage: "...")` rather than throwing. The cache and [refresh job](../../../src/Merchello.Core/ExchangeRates/Services/ExchangeRateRefreshJob.cs) handle failures gracefully and fall back to the last stored snapshot. +- **Error handling**: return `ExchangeRateResult(Success: false, ..., ErrorMessage: "...")` rather than throwing. The cache and [refresh job](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Services/ExchangeRateRefreshJob.cs) handle failures gracefully and fall back to the last stored snapshot. - **Rate direction**: return rates as "1 baseCurrency = X targetCurrency". If base is USD and target is EUR, return `0.92` (1 USD = 0.92 EUR). The cache inverts / cross-computes as needed. - **Sensitive fields**: mark API keys with `IsSensitive = true` -- Merchello encrypts them at rest. - **Metadata**: set `SupportsHistoricalRates` and `SupportedCurrencies` accurately. An empty `SupportedCurrencies` array means all currencies are supported. diff --git a/docs/documentation/multi-currency/multi-currency-overview.md b/docs/documentation/multi-currency/multi-currency-overview.md index 36fcae64..5f5c4d62 100644 --- a/docs/documentation/multi-currency/multi-currency-overview.md +++ b/docs/documentation/multi-currency/multi-currency-overview.md @@ -46,7 +46,7 @@ The two directions are intentional and non-negotiable. Display and checkout use ## Rate Locking at Invoice Creation -When an invoice is created at checkout, the exchange rate is **locked** onto the invoice with three audit fields on [`Invoice.cs`](../../../src/Merchello.Core/Accounting/Models/Invoice.cs#L90): +When an invoice is created at checkout, the exchange rate is **locked** onto the invoice with three audit fields on [`Invoice.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Accounting/Models/Invoice.cs#L90): | Field | Description | Example | |-------|-------------|---------| @@ -59,7 +59,7 @@ These fields serve two purposes: 1. **Financial accuracy** -- the customer pays exactly what they saw at checkout, regardless of rate fluctuations after the order is placed. 2. **Audit trail** -- you can always trace back to the exact rate used and where it came from (useful for reconciling refunds and disputes). -The rate, timestamp and source are populated from an [`ExchangeRateQuote`](../../../src/Merchello.Core/ExchangeRates/Models/ExchangeRateQuote.cs) returned by `IExchangeRateCache.GetRateQuoteAsync()` at invoice creation time. +The rate, timestamp and source are populated from an [`ExchangeRateQuote`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ExchangeRates/Models/ExchangeRateQuote.cs) returned by `IExchangeRateCache.GetRateQuoteAsync()` at invoice creation time. ## Store Currency Equivalents @@ -146,7 +146,7 @@ The GET counterpart returns the current display currency: GET /api/merchello/storefront/currency ``` -Endpoints live on [`StorefrontApiController.cs:273-310`](../../../src/Merchello/Controllers/StorefrontApiController.cs#L273). +Endpoints live on [`StorefrontApiController.cs:273-310`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/StorefrontApiController.cs#L273). ## Best Practices diff --git a/docs/documentation/notifications/notification-system.md b/docs/documentation/notifications/notification-system.md index 09e104c0..eddec78a 100644 --- a/docs/documentation/notifications/notification-system.md +++ b/docs/documentation/notifications/notification-system.md @@ -6,7 +6,7 @@ Merchello's two integration bridges ([Email](../email/email-overview.md) and [We ## Base Classes -All Merchello notifications extend one of three base classes. The attribute is declared in [NotificationHandlerPriorityAttribute.cs](../../../src/Merchello.Core/Notifications/NotificationHandlerPriorityAttribute.cs) and the base classes live in [Merchello.Core/Notifications/Base](../../../src/Merchello.Core/Notifications/Base). +All Merchello notifications extend one of three base classes. The attribute is declared in [NotificationHandlerPriorityAttribute.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Notifications/NotificationHandlerPriorityAttribute.cs) and the base classes live in [Merchello.Core/Notifications/Base](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.Core/Notifications/Base). ### MerchelloNotification @@ -102,7 +102,7 @@ public class SyncToErpHandler : INotificationAsyncHandler **Tip:** Use the priority ranges as a guide. The key principle is: validation first, business logic in the middle, external communication last. > -> **Enforced:** The `NotificationHandlerPriorityRangeAnalyzer` Roslyn analyzer (`MERCH020`) flags handlers outside these documented ranges at build time. See [NotificationHandlerPriorityRangeAnalyzer.cs](../../../src/Merchello.ArchitectureAnalyzers/Analyzers/NotificationHandlerPriorityRangeAnalyzer.cs). +> **Enforced:** The `NotificationHandlerPriorityRangeAnalyzer` Roslyn analyzer (`MERCH020`) flags handlers outside these documented ranges at build time. See [NotificationHandlerPriorityRangeAnalyzer.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.ArchitectureAnalyzers/Analyzers/NotificationHandlerPriorityRangeAnalyzer.cs). ## The State Dictionary @@ -138,7 +138,7 @@ public class LogPriceChangeHandler : INotificationAsyncHandler **Scope note:** Invoice editing recalculates totals and updates line items. It does **not** refund payments. If an edit lowers the total below what's been paid, reconcile via a [refund](../payments/refunds.md) — payment status is still calculated by `IPaymentService.CalculatePaymentStatus`. diff --git a/docs/documentation/orders/manual-orders.md b/docs/documentation/orders/manual-orders.md index f6732c9e..474782ab 100644 --- a/docs/documentation/orders/manual-orders.md +++ b/docs/documentation/orders/manual-orders.md @@ -14,7 +14,7 @@ Not every order comes through the website checkout. Sometimes you need to create ## Creating a Manual Order -The backoffice "New order" flow posts [`CreateManualOrderDto`](../../../src/Merchello.Core/Accounting/Dtos/CreateManualOrderDto.cs) to `POST /umbraco/api/v1/orders/manual` — handled by [`OrdersApiController`](../../../src/Merchello/Controllers/OrdersApiController.cs) which calls `invoiceService.CreateManualOrderAsync`: +The backoffice "New order" flow posts [`CreateManualOrderDto`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Accounting/Dtos/CreateManualOrderDto.cs) to `POST /umbraco/api/v1/orders/manual` — handled by [`OrdersApiController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/OrdersApiController.cs) which calls `invoiceService.CreateManualOrderAsync`: ```json POST /umbraco/api/v1/orders/manual @@ -82,7 +82,7 @@ Manual orders are created as drafts (unpaid). They progress through the same ord ## Batch Mark-as-Paid -For B2B scenarios where customers pay on account, you can mark multiple invoices as paid in one call. Handled by [`OrdersApiController.BatchMarkAsPaid`](../../../src/Merchello/Controllers/OrdersApiController.cs) → `IPaymentService.BatchMarkAsPaidAsync`: +For B2B scenarios where customers pay on account, you can mark multiple invoices as paid in one call. Handled by [`OrdersApiController.BatchMarkAsPaid`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/OrdersApiController.cs) → `IPaymentService.BatchMarkAsPaidAsync`: ```http POST /umbraco/api/v1/orders/batch-mark-paid @@ -102,7 +102,7 @@ The service creates one payment per invoice for that invoice's outstanding balan ## Line Items for Manual Orders -Once a draft is created, add products and custom items through the [invoice edit API](invoice-editing.md). Under the hood line items are created via [`InvoiceFactory`](../../../src/Merchello.Core/Accounting/Factories/) / [`LineItemFactory`](../../../src/Merchello.Core/Accounting/Factories/) — **never `new LineItem {}` directly in controllers** (see [CLAUDE.md Layering Rules](../../../.claude/CLAUDE.md)). +Once a draft is created, add products and custom items through the [invoice edit API](invoice-editing.md). Under the hood line items are created via [`InvoiceFactory`](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.Core/Accounting/Factories) / [`LineItemFactory`](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.Core/Accounting/Factories) — **never `new LineItem {}` directly in controllers** (see [CLAUDE.md Layering Rules](https://github.com/YodasMyDad/Merchello/blob/main/.claude/claude.md)). > **Warning:** Custom line items must set `TaxGroupId` to be taxed correctly. Tax providers select rates by `TaxGroupId`; without it the item is treated as non-taxable. diff --git a/docs/documentation/orders/orders-overview.md b/docs/documentation/orders/orders-overview.md index e710c743..f83f1e94 100644 --- a/docs/documentation/orders/orders-overview.md +++ b/docs/documentation/orders/orders-overview.md @@ -1,6 +1,6 @@ # Orders and Invoices Overview -Merchello uses a three-level hierarchy for commerce transactions: **Invoices** contain **Orders**, and Orders contain **Shipments**. The invoice is the financial contract; orders are fulfilment units; shipments are packages. Full domain model: [`Invoice.cs`](../../../src/Merchello.Core/Accounting/Models/Invoice.cs), [`OrderStatus.cs`](../../../src/Merchello.Core/Accounting/Models/OrderStatus.cs). See [Architecture Diagrams §2.4](https://github.com/YodasMyDad/Merchello/blob/main/docs/Architecture-Diagrams.md) for the full service catalog. +Merchello uses a three-level hierarchy for commerce transactions: **Invoices** contain **Orders**, and Orders contain **Shipments**. The invoice is the financial contract; orders are fulfilment units; shipments are packages. Full domain model: [`Invoice.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Accounting/Models/Invoice.cs), [`OrderStatus.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Accounting/Models/OrderStatus.cs). See [Architecture Diagrams §2.4](https://github.com/YodasMyDad/Merchello/blob/main/docs/Architecture-Diagrams.md) for the full service catalog. ## The Hierarchy @@ -115,11 +115,11 @@ Line items represent individual products or charges on an order: ## Invoice Source Tracking (Invariant) -The [`Invoice.Source`](../../../src/Merchello.Core/Accounting/Models/InvoiceSource.cs) property tracks where and how an invoice was created — essential for analytics and auditing. **Preserve these semantics in every invoice-creating flow.** See [Architecture Diagrams §2.4](https://github.com/YodasMyDad/Merchello/blob/main/docs/Architecture-Diagrams.md) for the full source-type catalog. +The [`Invoice.Source`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Accounting/Models/InvoiceSource.cs) property tracks where and how an invoice was created — essential for analytics and auditing. **Preserve these semantics in every invoice-creating flow.** See [Architecture Diagrams §2.4](https://github.com/YodasMyDad/Merchello/blob/main/docs/Architecture-Diagrams.md) for the full source-type catalog. | Property | Description | |----------|-------------| -| `Type` | Well-known values from [`Constants.InvoiceSources`](../../../src/Merchello.Core/Constants.cs): `"web"`, `"ucp"`, `"api"`, `"pos"`, `"draft"` (alias `"manual"`), `"mobile"`, `"import"`, `"other"` | +| `Type` | Well-known values from [`Constants.InvoiceSources`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Constants.cs): `"web"`, `"ucp"`, `"api"`, `"pos"`, `"draft"` (alias `"manual"`), `"mobile"`, `"import"`, `"other"` | | `DisplayName` | Human-readable name (e.g., "Online Store", "Point of Sale") | | `SourceId` | Unique identifier for the source instance (agent ID, API key ID, terminal ID) | | `SourceName` | Label for the source instance | @@ -139,7 +139,7 @@ await invoiceService.QueryInvoices(new InvoiceQueryParameters { SourceType = Con ## Querying Orders -Use [`IInvoiceService.QueryInvoices`](../../../src/Merchello.Core/Accounting/Services/Interfaces/IInvoiceService.cs) for paged, filterable queries: +Use [`IInvoiceService.QueryInvoices`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Accounting/Services/Interfaces/IInvoiceService.cs) for paged, filterable queries: ```csharp var result = await invoiceService.QueryInvoices(new InvoiceQueryParameters diff --git a/docs/documentation/orders/statements.md b/docs/documentation/orders/statements.md index f2d04e10..1773b03b 100644 --- a/docs/documentation/orders/statements.md +++ b/docs/documentation/orders/statements.md @@ -25,7 +25,7 @@ Closing Balance: 525.00 ## Generating Statement Data -Use [`IStatementService.GetStatementDataAsync`](../../../src/Merchello.Core/Accounting/Services/Interfaces/IStatementService.cs) to load a statement. Parameters live on [`GenerateStatementParameters`](../../../src/Merchello.Core/Accounting/Services/Parameters/GenerateStatementParameters.cs): +Use [`IStatementService.GetStatementDataAsync`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Accounting/Services/Interfaces/IStatementService.cs) to load a statement. Parameters live on [`GenerateStatementParameters`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Accounting/Services/Parameters/GenerateStatementParameters.cs): ```csharp var statement = await statementService.GetStatementDataAsync( diff --git a/docs/documentation/payments/payment-links.md b/docs/documentation/payments/payment-links.md index cd46a889..c01811db 100644 --- a/docs/documentation/payments/payment-links.md +++ b/docs/documentation/payments/payment-links.md @@ -29,7 +29,7 @@ Not all providers support payment links. The provider must declare `SupportsPaym | WorldPay | No | | Manual | No | -Payment link creation, retrieval, and deactivation are all managed through the backoffice UI — backed by [`PaymentLinksApiController`](../../../src/Merchello/Controllers/PaymentLinksApiController.cs) under `/umbraco/api/v1/` and orchestrated by [`IPaymentLinkService`](../../../src/Merchello.Core/Payments/Services/Interfaces/IPaymentLinkService.cs). The link URL and provider info are persisted on `Invoice.ExtendedData`. There is no storefront-facing API for payment links — they are an admin-only feature. +Payment link creation, retrieval, and deactivation are all managed through the backoffice UI — backed by [`PaymentLinksApiController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/PaymentLinksApiController.cs) under `/umbraco/api/v1/` and orchestrated by [`IPaymentLinkService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Services/Interfaces/IPaymentLinkService.cs). The link URL and provider info are persisted on `Invoice.ExtendedData`. There is no storefront-facing API for payment links — they are an admin-only feature. --- @@ -48,7 +48,7 @@ The system sends two types of reminders: ### Configuration -Bound from `Merchello:Invoices:Reminders` in `appsettings.json` (see [`InvoiceReminderSettings.cs`](../../../src/Merchello.Core/Accounting/InvoiceReminderSettings.cs) and the `Startup.cs` binding): +Bound from `Merchello:Invoices:Reminders` in `appsettings.json` (see [`InvoiceReminderSettings.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Accounting/InvoiceReminderSettings.cs) and the `Startup.cs` binding): ```json { diff --git a/docs/documentation/payments/payment-providers.md b/docs/documentation/payments/payment-providers.md index 294eb3af..209146aa 100644 --- a/docs/documentation/payments/payment-providers.md +++ b/docs/documentation/payments/payment-providers.md @@ -1,6 +1,6 @@ # Built-in Payment Providers -Merchello ships with six payment providers out of the box (including the built-in `Manual` provider). Each provider is a plugin discovered by the `ExtensionManager` — you configure them in the backoffice under **Settings > Payment Providers**. For an in-depth developer walkthrough including hosted fields, webhooks, vaulting, express checkout, and payment links, see the repo-root [PaymentProviders-DevGuide](../../PaymentProviders-DevGuide.md). To build your own, see [Creating Payment Providers](../extending/creating-payment-providers.md). +Merchello ships with six payment providers out of the box (including the built-in `Manual` provider). Each provider is a plugin discovered by the `ExtensionManager` — you configure them in the backoffice under **Settings > Payment Providers**. For an in-depth developer walkthrough including hosted fields, webhooks, vaulting, express checkout, and payment links, see the repo-root [PaymentProviders-DevGuide](https://github.com/YodasMyDad/Merchello/blob/main/docs/PaymentProviders-DevGuide.md). To build your own, see [Creating Payment Providers](../extending/creating-payment-providers.md). > **Alias stability:** The `Alias` on a provider is permanent — it's used for webhook URLs, stored settings, and `Payment.PaymentProviderAlias`. Never change it after deployment. @@ -254,7 +254,7 @@ WorldPay uses `HostedFields` integration -- the Access Checkout SDK renders card ## Manual Payment -**Provider alias:** `manual` (see [`ManualPaymentProvider.cs`](../../../src/Merchello.Core/Payments/Providers/BuiltIn/ManualPaymentProvider.cs)) +**Provider alias:** `manual` (see [`ManualPaymentProvider.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Providers/BuiltIn/ManualPaymentProvider.cs)) The Manual Payment provider handles offline payments and purchase orders. It's automatically enabled on install — no configuration needed. diff --git a/docs/documentation/payments/payment-system-overview.md b/docs/documentation/payments/payment-system-overview.md index 4ef345db..8239396c 100644 --- a/docs/documentation/payments/payment-system-overview.md +++ b/docs/documentation/payments/payment-system-overview.md @@ -1,8 +1,8 @@ # Payment System Overview -Merchello's payment system is built around a provider-based architecture. Payment providers (Stripe, PayPal, etc.) are plugins that handle the specifics of each payment gateway, while [`IPaymentService`](../../../src/Merchello.Core/Payments/Services/Interfaces/IPaymentService.cs) owns the payment lifecycle — recording payments, dedupe, refunds, and status. +Merchello's payment system is built around a provider-based architecture. Payment providers (Stripe, PayPal, etc.) are plugins that handle the specifics of each payment gateway, while [`IPaymentService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Services/Interfaces/IPaymentService.cs) owns the payment lifecycle — recording payments, dedupe, refunds, and status. -> **Single source of truth:** Never recompute payment status, refund totals, or balance-due in controllers, views, or JS. Call [`IPaymentService.CalculatePaymentStatus`](../../../src/Merchello.Core/Payments/Services/Interfaces/IPaymentService.cs#L114) (sync, on already-loaded payments) or `GetInvoicePaymentStatusAsync` (async, fetches payments for you). See [Architecture Diagrams §2.6 Payments](https://github.com/YodasMyDad/Merchello/blob/main/docs/Architecture-Diagrams.md). +> **Single source of truth:** Never recompute payment status, refund totals, or balance-due in controllers, views, or JS. Call [`IPaymentService.CalculatePaymentStatus`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Services/Interfaces/IPaymentService.cs#L114) (sync, on already-loaded payments) or `GetInvoicePaymentStatusAsync` (async, fetches payments for you). See [Architecture Diagrams §2.6 Payments](https://github.com/YodasMyDad/Merchello/blob/main/docs/Architecture-Diagrams.md). ## Key Concepts @@ -14,7 +14,7 @@ A payment provider represents a payment gateway (Stripe, PayPal, Braintree, etc. - Offers one or more **payment methods** (card, PayPal, Apple Pay, Google Pay, etc.) - Handles **payment sessions**, **processing**, **refunds**, and **webhooks** -Providers implement [`IPaymentProvider`](../../../src/Merchello.Core/Payments/Providers/Interfaces/IPaymentProvider.cs) (or extend `PaymentProviderBase` for sensible defaults) and are discovered automatically by the `ExtensionManager`. See [Creating Payment Providers](../extending/creating-payment-providers.md) for a full walkthrough. +Providers implement [`IPaymentProvider`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Providers/Interfaces/IPaymentProvider.cs) (or extend `PaymentProviderBase` for sensible defaults) and are discovered automatically by the `ExtensionManager`. See [Creating Payment Providers](../extending/creating-payment-providers.md) for a full walkthrough. ### Payment Methods @@ -47,7 +47,7 @@ A payment session represents a single payment attempt. When a customer clicks "P ## Payment Flow -Here is the standard payment flow from checkout to completed order. Storefront endpoints live under `/api/merchello/checkout/*` (see [Checkout API](../checkout/checkout-api.md)), implemented in [`CheckoutPaymentsApiController.cs`](../../../src/Merchello/Controllers/CheckoutPaymentsApiController.cs): +Here is the standard payment flow from checkout to completed order. Storefront endpoints live under `/api/merchello/checkout/*` (see [Checkout API](../checkout/checkout-api.md)), implemented in [`CheckoutPaymentsApiController.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/CheckoutPaymentsApiController.cs): ```text Customer clicks "Pay" @@ -85,13 +85,13 @@ Redirect to /checkout/confirmation/{invoiceId} ## Payment Status -Payment status is calculated centrally by [`IPaymentService.CalculatePaymentStatus`](../../../src/Merchello.Core/Payments/Services/Interfaces/IPaymentService.cs#L114). This is the single source of truth — never recompute it in controllers, views, or JS. +Payment status is calculated centrally by [`IPaymentService.CalculatePaymentStatus`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Services/Interfaces/IPaymentService.cs#L114). This is the single source of truth — never recompute it in controllers, views, or JS. -The method returns [`PaymentStatusDetails`](../../../src/Merchello.Core/Payments/Models/PaymentStatusDetails.cs): +The method returns [`PaymentStatusDetails`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Models/PaymentStatusDetails.cs): | Property | Description | |----------|-------------| -| `Status` | `Unpaid`, `AwaitingPayment`, `PartiallyPaid`, `Paid`, `PartiallyRefunded`, `Refunded` (see [`InvoicePaymentStatus`](../../../src/Merchello.Core/Payments/Models/InvoicePaymentStatus.cs)) | +| `Status` | `Unpaid`, `AwaitingPayment`, `PartiallyPaid`, `Paid`, `PartiallyRefunded`, `Refunded` (see [`InvoicePaymentStatus`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Models/InvoicePaymentStatus.cs)) | | `StatusDisplay` | Human-readable label (`"Partially Refunded"`, etc.) | | `TotalPaid` / `TotalPaidInStoreCurrency` | Sum of successful `PaymentType.Payment` rows | | `TotalRefunded` / `TotalRefundedInStoreCurrency` | Sum of refund rows (positive numbers) | @@ -122,7 +122,7 @@ var status = await paymentService.GetInvoicePaymentStatusAsync(invoiceId, ct); ## Idempotency & Dedupe (Invariant) -`Payment.IdempotencyKey` and `Payment.WebhookEventId` are how Merchello prevents double-charges. Both fields live on the [`Payment`](../../../src/Merchello.Core/Accounting/Models/Payment.cs) record and **must be preserved** by every flow that records or updates a payment. +`Payment.IdempotencyKey` and `Payment.WebhookEventId` are how Merchello prevents double-charges. Both fields live on the [`Payment`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Accounting/Models/Payment.cs) record and **must be preserved** by every flow that records or updates a payment. ### Idempotency keys @@ -140,7 +140,7 @@ Provider webhooks store the event ID on `Payment.WebhookEventId`. Before process ## IPaymentService Reference -Full interface: [`IPaymentService.cs`](../../../src/Merchello.Core/Payments/Services/Interfaces/IPaymentService.cs). +Full interface: [`IPaymentService.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Services/Interfaces/IPaymentService.cs). ### Payment processing @@ -190,7 +190,7 @@ Handlers with lower `[NotificationHandlerPriority(N)]` run first. Custom handler ## Provider Interface -Payment providers implement [`IPaymentProvider`](../../../src/Merchello.Core/Payments/Providers/Interfaces/IPaymentProvider.cs). Here are the required and optional methods: +Payment providers implement [`IPaymentProvider`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Providers/Interfaces/IPaymentProvider.cs). Here are the required and optional methods: ### Required (must implement) @@ -218,7 +218,7 @@ Payment providers implement [`IPaymentProvider`](../../../src/Merchello.Core/Pay ## Webhooks -Payment providers that use webhooks have a dedicated endpoint handled by [`PaymentWebhookController.cs`](../../../src/Merchello/Controllers/PaymentWebhookController.cs): +Payment providers that use webhooks have a dedicated endpoint handled by [`PaymentWebhookController.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/PaymentWebhookController.cs): ```text POST /umbraco/merchello/webhooks/payments/{providerAlias} @@ -259,4 +259,4 @@ See [Multi-Currency Overview](../multi-currency/multi-currency-overview.md) for - [Orders Overview](../orders/orders-overview.md) — how payments attach to invoices and orders - [Checkout API](../checkout/checkout-api.md) — the storefront endpoints that drive payment sessions - [Webhook API](../api/webhook-api.md) — incoming provider webhooks -- [Creating Payment Providers](../extending/creating-payment-providers.md) and the [PaymentProviders DevGuide](../../PaymentProviders-DevGuide.md) +- [Creating Payment Providers](../extending/creating-payment-providers.md) and the [PaymentProviders DevGuide](https://github.com/YodasMyDad/Merchello/blob/main/docs/PaymentProviders-DevGuide.md) diff --git a/docs/documentation/payments/refunds.md b/docs/documentation/payments/refunds.md index 45db13ca..9b420bcd 100644 --- a/docs/documentation/payments/refunds.md +++ b/docs/documentation/payments/refunds.md @@ -1,6 +1,6 @@ # Refunds -Merchello supports processing refunds through payment providers, recording manual (externally-processed) refunds, and previewing refund calculations before committing. All refund operations live on [`IPaymentService`](../../../src/Merchello.Core/Payments/Services/Interfaces/IPaymentService.cs). +Merchello supports processing refunds through payment providers, recording manual (externally-processed) refunds, and previewing refund calculations before committing. All refund operations live on [`IPaymentService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Services/Interfaces/IPaymentService.cs). ## Refund Types @@ -16,7 +16,7 @@ A refund recorded in Merchello when the money was returned outside the system (r ## Processing a Provider Refund -All refund parameters live on [`ProcessRefundParameters`](../../../src/Merchello.Core/Payments/Services/Parameters/ProcessRefundParameters.cs). `Reason` is **required**. +All refund parameters live on [`ProcessRefundParameters`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Services/Parameters/ProcessRefundParameters.cs). `Reason` is **required**. ```csharp var result = await paymentService.ProcessRefundAsync( @@ -89,7 +89,7 @@ var preview = await paymentService.PreviewRefundAsync( cancellationToken); ``` -The preview returns a [`RefundPreviewDto`](../../../src/Merchello.Core/Payments/Dtos/RefundPreviewDto.cs): +The preview returns a [`RefundPreviewDto`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Dtos/RefundPreviewDto.cs): | Property | Description | |----------|-------------| @@ -108,7 +108,7 @@ The preview returns a [`RefundPreviewDto`](../../../src/Merchello.Core/Payments/ ## Recording a Manual Refund -When a refund was processed outside Merchello (directly in the provider dashboard, cash in-store, etc.), record it for accounting via [`RecordManualRefundParameters`](../../../src/Merchello.Core/Payments/Services/Parameters/RecordManualRefundParameters.cs). Note that this is keyed off the **original payment** — not the invoice: +When a refund was processed outside Merchello (directly in the provider dashboard, cash in-store, etc.), record it for accounting via [`RecordManualRefundParameters`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Services/Parameters/RecordManualRefundParameters.cs). Note that this is keyed off the **original payment** — not the invoice: ```csharp var result = await paymentService.RecordManualRefundAsync( @@ -127,7 +127,7 @@ No gateway call is made — the refund row is created and invoice status recalcu ## Backoffice API -All backoffice refund operations go through the single [`PaymentsApiController`](../../../src/Merchello/Controllers/PaymentsApiController.cs) endpoint. Manual refunds share the same URL and are selected via `isManualRefund: true` in the body. +All backoffice refund operations go through the single [`PaymentsApiController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/PaymentsApiController.cs) endpoint. Manual refunds share the same URL and are selected via `isManualRefund: true` in the body. ### Process Refund (provider or manual) diff --git a/docs/documentation/payments/saved-payment-methods.md b/docs/documentation/payments/saved-payment-methods.md index dc8875ca..eb55ce7c 100644 --- a/docs/documentation/payments/saved-payment-methods.md +++ b/docs/documentation/payments/saved-payment-methods.md @@ -1,6 +1,6 @@ # Saved Payment Methods (Vaulting) -Saved payment methods let customers store their card details for faster checkout on future orders. Merchello handles vaulting through the payment provider — card details are never stored in your database. Full service surface: [`ISavedPaymentMethodService.cs`](../../../src/Merchello.Core/Payments/Services/Interfaces/ISavedPaymentMethodService.cs). For a deeper dive into the design and payment-side wiring, see [VaultedPayments.md](../../VaultedPayments.md) in the repo root. +Saved payment methods let customers store their card details for faster checkout on future orders. Merchello handles vaulting through the payment provider — card details are never stored in your database. Full service surface: [`ISavedPaymentMethodService.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Services/Interfaces/ISavedPaymentMethodService.cs). For a deeper dive into the design and payment-side wiring, see [VaultedPayments.md](https://github.com/YodasMyDad/Merchello/blob/main/docs/VaultedPayments.md) in the repo root. ## How Vaulting Works @@ -35,7 +35,7 @@ To enable vaulting for a provider, toggle the **Enable Card Vaulting** option in The simplest way for customers to save a payment method is during checkout. When the customer opts in to "Save this card for future purchases", the method is saved after successful payment. -The checkout API handles this via `savePaymentMethod: true` in [`InitiatePaymentDto`](../../../src/Merchello.Core/Payments/Dtos/InitiatePaymentDto.cs) posted to `POST /api/merchello/checkout/pay`: +The checkout API handles this via `savePaymentMethod: true` in [`InitiatePaymentDto`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Dtos/InitiatePaymentDto.cs) posted to `POST /api/merchello/checkout/pay`: ```json { @@ -53,7 +53,7 @@ After the payment succeeds, `ISavedPaymentMethodService.SaveFromCheckoutAsync()` ## Standalone Vault Setup -Customers can also save payment methods outside of checkout (e.g. from an account settings page). This uses a two-step setup flow exposed by [`StorefrontSavedPaymentMethodsController`](../../../src/Merchello/Controllers/StorefrontSavedPaymentMethodsController.cs). +Customers can also save payment methods outside of checkout (e.g. from an account settings page). This uses a two-step setup flow exposed by [`StorefrontSavedPaymentMethodsController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/StorefrontSavedPaymentMethodsController.cs). ### Step 1: Create Setup Session @@ -71,7 +71,7 @@ Content-Type: application/json } ``` -**Response ([`VaultSetupResponseDto`](../../../src/Merchello.Core/Payments/Dtos/VaultSetupResponseDto.cs)):** +**Response ([`VaultSetupResponseDto`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Dtos/VaultSetupResponseDto.cs)):** ```json { @@ -129,9 +129,9 @@ When a logged-in customer reaches the payment step, the checkout API returns the GET /api/merchello/checkout/payment-options ``` -Returns [`CheckoutPaymentOptionsDto`](../../../src/Merchello.Core/Checkout/Dtos/CheckoutPaymentOptionsDto.cs) — provider methods + saved methods + `CanSavePaymentMethods` flag. +Returns [`CheckoutPaymentOptionsDto`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Dtos/CheckoutPaymentOptionsDto.cs) — provider methods + saved methods + `CanSavePaymentMethods` flag. -To pay with a saved method, POST [`ProcessSavedPaymentMethodDto`](../../../src/Merchello.Core/Payments/Dtos/ProcessSavedPaymentMethodDto.cs): +To pay with a saved method, POST [`ProcessSavedPaymentMethodDto`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Dtos/ProcessSavedPaymentMethodDto.cs): ```http POST /api/merchello/checkout/process-saved-payment @@ -260,7 +260,7 @@ Saved methods can be charged off-session for scenarios like: - Repeat purchases - Subscription renewals -`ChargeAsync` always attaches the charge to an **invoice** — create one first if the charge doesn't originate from an existing order. See [`ChargeSavedMethodParameters`](../../../src/Merchello.Core/Payments/Services/Parameters/ChargeSavedMethodParameters.cs): +`ChargeAsync` always attaches the charge to an **invoice** — create one first if the charge doesn't originate from an existing order. See [`ChargeSavedMethodParameters`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Payments/Services/Parameters/ChargeSavedMethodParameters.cs): ```csharp var result = await savedPaymentMethodService.ChargeAsync( diff --git a/docs/documentation/product-feeds/product-feeds-overview.md b/docs/documentation/product-feeds/product-feeds-overview.md index 49d1f7a1..622c607a 100644 --- a/docs/documentation/product-feeds/product-feeds-overview.md +++ b/docs/documentation/product-feeds/product-feeds-overview.md @@ -13,7 +13,7 @@ A **product feed** is a configuration that defines: Each feed has a **slug** that forms part of its public URL. You create and manage feeds in the Umbraco backoffice under **Settings > Product Feeds**. -The feed generators live in [Merchello.Core/ProductFeeds/Services](../../../src/Merchello.Core/ProductFeeds/Services) ([`GoogleProductFeedGenerator`](../../../src/Merchello.Core/ProductFeeds/Services/GoogleProductFeedGenerator.cs), [`GooglePromotionFeedGenerator`](../../../src/Merchello.Core/ProductFeeds/Services/GooglePromotionFeedGenerator.cs)) and the public controller is [`ProductFeedsPublicController`](../../../src/Merchello/Controllers/ProductFeedsPublicController.cs). +The feed generators live in [Merchello.Core/ProductFeeds/Services](https://github.com/YodasMyDad/Merchello/tree/main/src/Merchello.Core/ProductFeeds/Services) ([`GoogleProductFeedGenerator`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/Services/GoogleProductFeedGenerator.cs), [`GooglePromotionFeedGenerator`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/Services/GooglePromotionFeedGenerator.cs)) and the public controller is [`ProductFeedsPublicController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/ProductFeedsPublicController.cs). ## Public Feed URLs @@ -68,9 +68,9 @@ Merchello can validate and surface Google auto-discount tokens. Configure the pu ## Automatic Refresh -Feeds are cached in the database so serving them is a fast read, not a full product query. A background job ([`ProductFeedRefreshJob`](../../../src/Merchello.Core/ProductFeeds/Services/ProductFeedRefreshJob.cs)) periodically rebuilds all enabled feeds. +Feeds are cached in the database so serving them is a fast read, not a full product query. A background job ([`ProductFeedRefreshJob`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/Services/ProductFeedRefreshJob.cs)) periodically rebuilds all enabled feeds. -Configure the refresh schedule in `appsettings.json` (binds to [`ProductFeedSettings`](../../../src/Merchello.Core/ProductFeeds/ProductFeedSettings.cs)): +Configure the refresh schedule in `appsettings.json` (binds to [`ProductFeedSettings`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/ProductFeedSettings.cs)): ```json { @@ -83,15 +83,15 @@ Configure the refresh schedule in `appsettings.json` (binds to [`ProductFeedSett } ``` -You can also trigger a manual rebuild from the backoffice or via `POST /api/v1/product-feeds/{id}/rebuild` (see [ProductFeedsApiController](../../../src/Merchello/Controllers/ProductFeedsApiController.cs)). +You can also trigger a manual rebuild from the backoffice or via `POST /api/v1/product-feeds/{id}/rebuild` (see [ProductFeedsApiController](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/ProductFeedsApiController.cs)). ## Custom Field Resolvers Product feed field values are resolved through a pluggable resolver system. Built-in resolvers handle standard Google Shopping fields (title, description, price, availability, etc.), but you can register custom resolvers for specialized needs. -Each resolver receives a [`ProductFeedResolverContext`](../../../src/Merchello.Core/ProductFeeds/Models/ProductFeedResolverContext.cs) with the product data, feed configuration, and store settings, and returns the resolved field value. Resolvers are discovered via `ExtensionManager`. +Each resolver receives a [`ProductFeedResolverContext`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/ProductFeeds/Models/ProductFeedResolverContext.cs) with the product data, feed configuration, and store settings, and returns the resolved field value. Resolvers are discovered via `ExtensionManager`. -For the full developer guide (registering resolvers, field aliases, testing) see [docs/Product-Feed-Resolvers.md](../../Product-Feed-Resolvers.md). +For the full developer guide (registering resolvers, field aliases, testing) see [docs/Product-Feed-Resolvers.md](https://github.com/YodasMyDad/Merchello/blob/main/docs/Product-Feed-Resolvers.md). ## Multiple Feeds @@ -105,7 +105,7 @@ Each feed has its own public URL to submit to Google Merchant Center. ## Backoffice API -Source: [ProductFeedsApiController.cs](../../../src/Merchello/Controllers/ProductFeedsApiController.cs). +Source: [ProductFeedsApiController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/ProductFeedsApiController.cs). | Endpoint | Method | Description | |---|---|---| @@ -123,5 +123,5 @@ Source: [ProductFeedsApiController.cs](../../../src/Merchello/Controllers/Produc - [Products](../products/) - [Google Auto Discount](../advanced/google-auto-discount.md) -- [Product Feed Resolvers (dev guide)](../../Product-Feed-Resolvers.md) +- [Product Feed Resolvers (dev guide)](https://github.com/YodasMyDad/Merchello/blob/main/docs/Product-Feed-Resolvers.md) - [Background Jobs](../background-jobs/background-jobs.md) diff --git a/docs/documentation/products/collections-and-categories.md b/docs/documentation/products/collections-and-categories.md index b64442be..bb51732a 100644 --- a/docs/documentation/products/collections-and-categories.md +++ b/docs/documentation/products/collections-and-categories.md @@ -97,7 +97,7 @@ Use `ProductAvailabilityFilter` to control which products appear: ## Building Category Pages -The starter site demonstrates the recommended pattern for category pages in [CategoryController.cs](../../../src/Merchello.Site/Category/Controllers/CategoryController.cs) and the [Category.cshtml view](../../../src/Merchello.Site/Views/Category.cshtml). Here is how it works: +The starter site demonstrates the recommended pattern for category pages in [CategoryController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Category/Controllers/CategoryController.cs) and the [Category.cshtml view](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Category.cshtml). Here is how it works: ### Step 1: Create an Umbraco Document Type diff --git a/docs/documentation/products/digital-products.md b/docs/documentation/products/digital-products.md index 6d9b31fc..114feebe 100644 --- a/docs/documentation/products/digital-products.md +++ b/docs/documentation/products/digital-products.md @@ -6,7 +6,7 @@ Digital products let you sell downloadable items like software, ebooks, music, o ## How Digital Products Work -A digital product is a regular product root with `IsDigitalProduct = true`. All digital-specific settings are stored in `ProductRoot.ExtendedData` using constant keys accessed via [ProductRootDigitalExtensions.cs](../../../src/Merchello.Core/DigitalProducts/Extensions/ProductRootDigitalExtensions.cs) -- there are no extra database columns. Service contract: [IDigitalProductService.cs](../../../src/Merchello.Core/DigitalProducts/Services/Interfaces/IDigitalProductService.cs). +A digital product is a regular product root with `IsDigitalProduct = true`. All digital-specific settings are stored in `ProductRoot.ExtendedData` using constant keys accessed via [ProductRootDigitalExtensions.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/DigitalProducts/Extensions/ProductRootDigitalExtensions.cs) -- there are no extra database columns. Service contract: [IDigitalProductService.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/DigitalProducts/Services/Interfaces/IDigitalProductService.cs). ### Key Constraints diff --git a/docs/documentation/products/element-type-properties.md b/docs/documentation/products/element-type-properties.md index ec76054d..b16a9d24 100644 --- a/docs/documentation/products/element-type-properties.md +++ b/docs/documentation/products/element-type-properties.md @@ -71,7 +71,7 @@ You can also iterate over all properties: ### MerchelloPublishedElementFactory -When [`ProductContentFinder`](../../../src/Merchello/Routing/ProductContentFinder.cs) resolves a product, it checks if the `ProductRoot` has an `ElementTypeAlias`. If so, it uses [`MerchelloPublishedElementFactory`](../../../src/Merchello/Factories/MerchelloPublishedElementFactory.cs) to create an `IPublishedElement` from the stored JSON: +When [`ProductContentFinder`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Routing/ProductContentFinder.cs) resolves a product, it checks if the `ProductRoot` has an `ElementTypeAlias`. If so, it uses [`MerchelloPublishedElementFactory`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Factories/MerchelloPublishedElementFactory.cs) to create an `IPublishedElement` from the stored JSON: ```csharp // In ProductContentFinder: diff --git a/docs/documentation/products/inventory-and-stock.md b/docs/documentation/products/inventory-and-stock.md index ecc240d8..b3bf175f 100644 --- a/docs/documentation/products/inventory-and-stock.md +++ b/docs/documentation/products/inventory-and-stock.md @@ -4,7 +4,7 @@ Merchello tracks stock levels per product variant per warehouse. This guide cove > **Invariant:** All stock mutations must go through `IInventoryService`. Never adjust `Stock`, `ReservedStock`, or `TrackStock` directly on a `ProductWarehouse` -- the service enforces the lifecycle rules below, handles optimistic concurrency, and publishes the notifications that other subsystems listen on. -Source: [IInventoryService.cs](../../../src/Merchello.Core/Products/Services/Interfaces/IInventoryService.cs), [ProductWarehouse.cs](../../../src/Merchello.Core/Products/Models/ProductWarehouse.cs). +Source: [IInventoryService.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Products/Services/Interfaces/IInventoryService.cs), [ProductWarehouse.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Products/Models/ProductWarehouse.cs). ## Core Concepts @@ -195,7 +195,7 @@ When a product exists in multiple warehouses, Merchello selects the best warehou 2. **Service region eligibility** -- the warehouse must be able to ship to the customer's country/region. 3. **Stock availability** -- the warehouse must have `Stock - ReservedStock >= requested quantity`. -> **Tip:** Use [`IWarehouseService.SelectWarehouseForProduct()`](../../../src/Merchello.Core/Warehouses/Services/WarehouseService.cs#L35) to get the best warehouse for a product and shipping destination. This is what the checkout order grouping strategy uses internally ([DefaultOrderGroupingStrategy.cs:84](../../../src/Merchello.Core/Checkout/Strategies/DefaultOrderGroupingStrategy.cs#L84)). +> **Tip:** Use [`IWarehouseService.SelectWarehouseForProduct()`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Warehouses/Services/WarehouseService.cs#L35) to get the best warehouse for a product and shipping destination. This is what the checkout order grouping strategy uses internally ([DefaultOrderGroupingStrategy.cs:84](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Strategies/DefaultOrderGroupingStrategy.cs#L84)). ## Key Points diff --git a/docs/documentation/products/product-filters.md b/docs/documentation/products/product-filters.md index d22266d3..1c872e5d 100644 --- a/docs/documentation/products/product-filters.md +++ b/docs/documentation/products/product-filters.md @@ -46,7 +46,7 @@ This is the method used by the `.Site` example project's `CategoryController`. ## Real-World Example: Category Page -The starter site's [CategoryController.cs](../../../src/Merchello.Site/Category/Controllers/CategoryController.cs) accepts filter selections from query parameters, queries products, and loads the relevant filter groups: +The starter site's [CategoryController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Category/Controllers/CategoryController.cs) accepts filter selections from query parameters, queries products, and loads the relevant filter groups: ```csharp public async Task Category( @@ -94,7 +94,7 @@ public async Task Category( ## Rendering Filters in a View -The starter site uses a [ProductFiltersViewComponent](../../../src/Merchello.Site/Shared/Components/ProductFilters/ProductFiltersViewComponent.cs) with the view at [Views/Shared/Components/ProductFilters/Default.cshtml](../../../src/Merchello.Site/Views/Shared/Components/ProductFilters/Default.cshtml). The component receives the filter groups and selected keys, then renders checkboxes (or colour swatches for colour groups): +The starter site uses a [ProductFiltersViewComponent](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Shared/Components/ProductFilters/ProductFiltersViewComponent.cs) with the view at [Views/Shared/Components/ProductFilters/Default.cshtml](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Shared/Components/ProductFilters/Default.cshtml). The component receives the filter groups and selected keys, then renders checkboxes (or colour swatches for colour groups): ```html @model ProductFiltersViewModel diff --git a/docs/documentation/products/product-routing.md b/docs/documentation/products/product-routing.md index d6d4fc00..847ddbdd 100644 --- a/docs/documentation/products/product-routing.md +++ b/docs/documentation/products/product-routing.md @@ -6,9 +6,9 @@ One of Merchello's most distinctive features is that products render at root-lev The routing pipeline has three components: -1. **ProductContentFinder** -- intercepts requests and looks up products. Source: [ProductContentFinder.cs](../../../src/Merchello/Routing/ProductContentFinder.cs). -2. **MerchelloPublishedProduct** -- a virtual `IPublishedContent` that Umbraco's rendering pipeline can work with. Source: [MerchelloPublishedProduct.cs](../../../src/Merchello/Models/MerchelloPublishedProduct.cs). -3. **MerchelloProductController** -- renders the product view. Source: [MerchelloProductController.cs](../../../src/Merchello/Controllers/MerchelloProductController.cs). +1. **ProductContentFinder** -- intercepts requests and looks up products. Source: [ProductContentFinder.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Routing/ProductContentFinder.cs). +2. **MerchelloPublishedProduct** -- a virtual `IPublishedContent` that Umbraco's rendering pipeline can work with. Source: [MerchelloPublishedProduct.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Models/MerchelloPublishedProduct.cs). +3. **MerchelloProductController** -- renders the product view. Source: [MerchelloProductController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/MerchelloProductController.cs). ### Step 1: ProductContentFinder diff --git a/docs/documentation/products/product-views.md b/docs/documentation/products/product-views.md index 37354510..44237fe6 100644 --- a/docs/documentation/products/product-views.md +++ b/docs/documentation/products/product-views.md @@ -2,7 +2,7 @@ Product views are Razor `.cshtml` files that render your product pages. They receive a `MerchelloProductViewModel` with all the data you need -- product details, pricing, variants, stock, images, and SEO metadata. -The reference implementation is the starter site's [Default.cshtml](../../../src/Merchello.Site/Views/Products/Default.cshtml) -- it demonstrates every pattern shown in this guide. +The reference implementation is the starter site's [Default.cshtml](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Products/Default.cshtml) -- it demonstrates every pattern shown in this guide. ## The Basics @@ -94,7 +94,7 @@ The raw `Model.Price` is a net price in your store currency. To display prices c > **Invariant:** Stored prices never change when the customer's display currency changes. Display uses multiply (`amount * ExchangeRate`); checkout and payment use divide via the invoice conversion path. Never charge a customer using display amounts. See [Storefront Context](../storefront/storefront-context.md) and [Price Display](../storefront/price-display.md) for the rules. -This pattern is lifted from [Default.cshtml:23-79](../../../src/Merchello.Site/Views/Products/Default.cshtml#L23): +This pattern is lifted from [Default.cshtml:23-79](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Products/Default.cshtml#L23): ```html @inject Merchello.Core.Storefront.Services.Interfaces.IStorefrontContextService StorefrontContext @@ -309,7 +309,7 @@ The view model provides SEO properties. The starter site renders them in a `@sec ## Using Partials -The starter site splits the product page into partials for maintainability ([Default.cshtml:261-282](../../../src/Merchello.Site/Views/Products/Default.cshtml#L261)): +The starter site splits the product page into partials for maintainability ([Default.cshtml:261-282](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Products/Default.cshtml#L261)): ```html
diff --git a/docs/documentation/products/products-overview.md b/docs/documentation/products/products-overview.md index 4f7a3c72..0398e23c 100644 --- a/docs/documentation/products/products-overview.md +++ b/docs/documentation/products/products-overview.md @@ -8,7 +8,7 @@ Products in Merchello use a two-level hierarchy: **ProductRoot** and **Product** ### ProductRoot (The Parent) -A `ProductRoot` represents the conceptual product -- "Mesh Office Chair" or "Classic T-Shirt". It holds everything that is shared across all variants. Source: [ProductRoot.cs](../../../src/Merchello.Core/Products/Models/ProductRoot.cs). +A `ProductRoot` represents the conceptual product -- "Mesh Office Chair" or "Classic T-Shirt". It holds everything that is shared across all variants. Source: [ProductRoot.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Products/Models/ProductRoot.cs). ```csharp public class ProductRoot @@ -50,7 +50,7 @@ public class ProductRoot ### Product (The Variant) -A `Product` represents a specific purchasable SKU -- "Mesh Office Chair - Blue / Large". Each product root has at least one product (the default variant). Source: [Product.cs](../../../src/Merchello.Core/Products/Models/Product.cs). +A `Product` represents a specific purchasable SKU -- "Mesh Office Chair - Blue / Large". Each product root has at least one product (the default variant). Source: [Product.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Products/Models/Product.cs). ```csharp public class Product @@ -121,7 +121,7 @@ The `RootUrl` is auto-generated from the product name when creating a product ro ## Creating Products -Products are created through [`IProductService`](../../../src/Merchello.Core/Products/Services/Interfaces/IProductService.cs). You create the root first, then variants are generated from options: +Products are created through [`IProductService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Products/Services/Interfaces/IProductService.cs). You create the root first, then variants are generated from options: ```csharp // Step 1: Create the product root with a default variant diff --git a/docs/documentation/products/variants-and-options.md b/docs/documentation/products/variants-and-options.md index 6e755893..c470039b 100644 --- a/docs/documentation/products/variants-and-options.md +++ b/docs/documentation/products/variants-and-options.md @@ -32,7 +32,7 @@ Add-on values can adjust price, cost, SKU, and weight. ## The ProductOption Model -Source: [ProductOption.cs](../../../src/Merchello.Core/Products/Models/ProductOption.cs), values in [ProductOptionValue.cs](../../../src/Merchello.Core/Products/Models/ProductOptionValue.cs). +Source: [ProductOption.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Products/Models/ProductOption.cs), values in [ProductOptionValue.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Products/Models/ProductOptionValue.cs). ```csharp public class ProductOption @@ -140,7 +140,7 @@ public IReadOnlyList AddOnOptions { get; } // IsVariant == fal The starter site renders variant options as interactive selectors. When a customer selects different option values, the page updates to show the matching variant's price, stock, and images. -The [Default.cshtml product view](../../../src/Merchello.Site/Views/Products/Default.cshtml) passes a JSON configuration object to the frontend JavaScript (see the `variantOptions` block, [Default.cshtml:127](../../../src/Merchello.Site/Views/Products/Default.cshtml#L127)): +The [Default.cshtml product view](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Products/Default.cshtml) passes a JSON configuration object to the frontend JavaScript (see the `variantOptions` block, [Default.cshtml:127](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Products/Default.cshtml#L127)): ```csharp variantOptions = viewModel.VariantOptions @@ -199,7 +199,7 @@ When a product page loads, the selected variant is determined from the URL: - `/mesh-office-chair` -- selects the default variant - `/mesh-office-chair/blue-large` -- selects the "blue-large" variant by matching `Product.Url` -The `VariantOptionsKey` on each variant identifies which option values created it. The starter site uses this to pre-select the correct options in the UI ([Default.cshtml:51-67](../../../src/Merchello.Site/Views/Products/Default.cshtml#L51)): +The `VariantOptionsKey` on each variant identifies which option values created it. The starter site uses this to pre-select the correct options in the UI ([Default.cshtml:51-67](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Products/Default.cshtml#L51)): ```csharp var selectedValueIds = viewModel.SelectedVariant.VariantOptionsKey diff --git a/docs/documentation/reporting/reporting-overview.md b/docs/documentation/reporting/reporting-overview.md index 7c0ee538..c33c7353 100644 --- a/docs/documentation/reporting/reporting-overview.md +++ b/docs/documentation/reporting/reporting-overview.md @@ -1,6 +1,6 @@ # Reporting and Analytics -Merchello provides a reporting system for tracking sales performance, understanding trends, and exporting data. All reporting data is served through [`IReportingService`](../../../src/Merchello.Core/Reporting/Services/Interfaces/IReportingService.cs) ([implementation](../../../src/Merchello.Core/Reporting/Services/ReportingService.cs)) and exposed via the backoffice API in [`ReportingApiController`](../../../src/Merchello/Controllers/ReportingApiController.cs). +Merchello provides a reporting system for tracking sales performance, understanding trends, and exporting data. All reporting data is served through [`IReportingService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Reporting/Services/Interfaces/IReportingService.cs) ([implementation](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Reporting/Services/ReportingService.cs)) and exposed via the backoffice API in [`ReportingApiController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/ReportingApiController.cs). ## KPI Summary @@ -111,7 +111,7 @@ Quick stats for today's operations — exposed via the Orders API: GET /api/v1/orders/stats ``` -Returns `OrderStatsDto` with today's counts (`Orders`, `Items`, `Fulfilled`, `Outstanding`). Implementation: [OrdersApiController.GetOrderStats](../../../src/Merchello/Controllers/OrdersApiController.cs#L120). +Returns `OrderStatsDto` with today's counts (`Orders`, `Items`, `Fulfilled`, `Outstanding`). Implementation: [OrdersApiController.GetOrderStats](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/OrdersApiController.cs#L120). ## Dashboard Statistics @@ -121,7 +121,7 @@ Monthly metrics with percentage changes for the main dashboard: GET /api/v1/orders/dashboard-stats ``` -Returns `DashboardStatsDto`. Implementation: [OrdersApiController.GetDashboardStats](../../../src/Merchello/Controllers/OrdersApiController.cs#L130). +Returns `DashboardStatsDto`. Implementation: [OrdersApiController.GetDashboardStats](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/OrdersApiController.cs#L130). ## CSV Export @@ -137,11 +137,11 @@ Content-Type: application/json } ``` -Returns a list of `OrderExportItemDto` records with all fields needed for a comprehensive CSV export (order number, customer, line items, totals). Implementation: [OrdersApiController.ExportOrders](../../../src/Merchello/Controllers/OrdersApiController.cs#L140). +Returns a list of `OrderExportItemDto` records with all fields needed for a comprehensive CSV export (order number, customer, line items, totals). Implementation: [OrdersApiController.ExportOrders](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/OrdersApiController.cs#L140). ## API Summary -Reporting endpoints live under `/api/v1/reporting` (see [ReportingApiController.cs](../../../src/Merchello/Controllers/ReportingApiController.cs)). Order stats / dashboard / export live under `/api/v1/orders`. +Reporting endpoints live under `/api/v1/reporting` (see [ReportingApiController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/ReportingApiController.cs)). Order stats / dashboard / export live under `/api/v1/orders`. | Endpoint | Method | Description | |---|---|---| diff --git a/docs/documentation/shipping/dynamic-shipping-providers.md b/docs/documentation/shipping/dynamic-shipping-providers.md index 7986fb73..c2a9b124 100644 --- a/docs/documentation/shipping/dynamic-shipping-providers.md +++ b/docs/documentation/shipping/dynamic-shipping-providers.md @@ -18,8 +18,8 @@ Dynamic providers set `UsesLiveRates = true` in their metadata and must **not** Built-in dynamic providers: -- [`FedExShippingProvider`](../../../src/Merchello.Core/Shipping/Providers/FedEx/FedExShippingProvider.cs) (`fedex`) -- [`UpsShippingProvider`](../../../src/Merchello.Core/Shipping/Providers/UPS/UpsShippingProvider.cs) (`ups`) +- [`FedExShippingProvider`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/FedEx/FedExShippingProvider.cs) (`fedex`) +- [`UpsShippingProvider`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/UPS/UpsShippingProvider.cs) (`ups`) --- @@ -239,7 +239,7 @@ Set `ProductRoot.AllowExternalCarrierShipping = false` to prevent dynamic carrie | `CalculateDeliveryDateSurchargeAsync(request, service, date)` | Calculate surcharge for a specific date | | `ValidateDeliveryDateAsync(request, service, date)` | Validate a delivery date is still available | -See the full interface at [`IShippingProvider.cs`](../../../src/Merchello.Core/Shipping/Providers/Interfaces/IShippingProvider.cs). +See the full interface at [`IShippingProvider.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/Interfaces/IShippingProvider.cs). ## Related Topics diff --git a/docs/documentation/shipping/flat-rate-shipping.md b/docs/documentation/shipping/flat-rate-shipping.md index 2200ad36..d96b6707 100644 --- a/docs/documentation/shipping/flat-rate-shipping.md +++ b/docs/documentation/shipping/flat-rate-shipping.md @@ -4,9 +4,9 @@ Flat rate shipping lets you configure fixed shipping costs based on destination ## How It Works -The flat-rate provider ([`FlatRateShippingProvider`](../../../src/Merchello.Core/Shipping/Providers/BuiltIn/FlatRateShippingProvider.cs), key `flat-rate`) uses `ShippingOption` records with associated `ShippingCost` entries to determine the shipping price. You create shipping options (like "Standard Shipping" or "Express Delivery") and then add cost entries for each destination you ship to. +The flat-rate provider ([`FlatRateShippingProvider`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Providers/BuiltIn/FlatRateShippingProvider.cs), key `flat-rate`) uses `ShippingOption` records with associated `ShippingCost` entries to determine the shipping price. You create shipping options (like "Standard Shipping" or "Express Delivery") and then add cost entries for each destination you ship to. -Cost lookup is delegated to [`ShippingCostResolver`](../../../src/Merchello.Core/Shipping/Services/ShippingCostResolver.cs) -- the single source of truth for flat-rate matching. `FlatRateShippingProvider` never duplicates the match logic; it consumes a pre-resolved `DestinationCost`. +Cost lookup is delegated to [`ShippingCostResolver`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Services/ShippingCostResolver.cs) -- the single source of truth for flat-rate matching. `FlatRateShippingProvider` never duplicates the match logic; it consumes a pre-resolved `DestinationCost`. > **Invariant (CLAUDE.md):** Only flat-rate options carry fixed costs in the DB. Dynamic providers (`UsesLiveRates = true`) must NOT use fixed-cost entries. If you want a carrier-named option at a fixed price, create it as `ProviderKey = "flat-rate"`. @@ -197,7 +197,7 @@ This happens automatically -- you don't need to store costs in multiple currenci > **Tip:** Use the universal (`*`) destination as a catch-all. Without it, customers in countries without a specific cost entry will only see the `fixedCost` fallback. -> **Tip:** Set `fixedCost` to `0` for free shipping as a fallback. For flat-rate options, a null `FixedCost` is normalized to `0` in [`ShippingCostResolver.GetTotalShippingCost()`](../../../src/Merchello.Core/Shipping/Services/ShippingCostResolver.cs) so the option always resolves. +> **Tip:** Set `fixedCost` to `0` for free shipping as a fallback. For flat-rate options, a null `FixedCost` is normalized to `0` in [`ShippingCostResolver.GetTotalShippingCost()`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Services/ShippingCostResolver.cs) so the option always resolves. > **Tip:** The delivery day range (`daysFrom`/`daysTo`) is displayed to customers at checkout. Keep these realistic -- they set customer expectations. diff --git a/docs/documentation/shipping/order-grouping.md b/docs/documentation/shipping/order-grouping.md index eb095f1f..0ea52937 100644 --- a/docs/documentation/shipping/order-grouping.md +++ b/docs/documentation/shipping/order-grouping.md @@ -4,7 +4,7 @@ When a customer checks out, Merchello needs to figure out *how* to split their b ## How It Works -During checkout, [`IShippingService.GetShippingOptionsForBasket()`](../../../src/Merchello.Core/Shipping/Services/Interfaces/IShippingService.cs) is called. Internally this builds an `OrderGroupingContext` and passes it to the active [`IOrderGroupingStrategy`](../../../src/Merchello.Core/Checkout/Strategies/Interfaces/IOrderGroupingStrategy.cs). The strategy returns an `OrderGroupingResult` containing the groups, available shipping options per group, and any errors. +During checkout, [`IShippingService.GetShippingOptionsForBasket()`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Services/Interfaces/IShippingService.cs) is called. Internally this builds an `OrderGroupingContext` and passes it to the active [`IOrderGroupingStrategy`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Strategies/Interfaces/IOrderGroupingStrategy.cs). The strategy returns an `OrderGroupingResult` containing the groups, available shipping options per group, and any errors. > **Invariant (CLAUDE.md):** `GetShippingOptionsForBasket()` is the basket-level entry point -- it uses the active grouping strategy internally. Do not call grouping strategies from controllers. @@ -26,7 +26,7 @@ Each `OrderGroup` contains: ## The Default Strategy: Warehouse Grouping -Out of the box, Merchello uses [`DefaultOrderGroupingStrategy`](../../../src/Merchello.Core/Checkout/Strategies/DefaultOrderGroupingStrategy.cs) (key: `"default-warehouse"`). It groups items by warehouse based on stock availability and shipping region. +Out of the box, Merchello uses [`DefaultOrderGroupingStrategy`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Strategies/DefaultOrderGroupingStrategy.cs) (key: `"default-warehouse"`). It groups items by warehouse based on stock availability and shipping region. ### What it does step by step diff --git a/docs/documentation/shipping/shipping-overview.md b/docs/documentation/shipping/shipping-overview.md index 4059bb00..db03ecd7 100644 --- a/docs/documentation/shipping/shipping-overview.md +++ b/docs/documentation/shipping/shipping-overview.md @@ -36,15 +36,15 @@ Shipping functionality is split across three services: | Service | Purpose | Source | |---------|---------|--------| -| `IShippingService` | Business logic and orchestration for basket/product shipping | [IShippingService.cs](../../../src/Merchello.Core/Shipping/Services/Interfaces/IShippingService.cs) | -| `IShippingQuoteService` | Fetches quotes from shipping providers (FedEx, UPS, flat-rate) | [IShippingQuoteService.cs](../../../src/Merchello.Core/Shipping/Services/Interfaces/IShippingQuoteService.cs) | -| `IShippingCostResolver` | Resolves costs from flat-rate shipping option configurations | [ShippingCostResolver.cs](../../../src/Merchello.Core/Shipping/Services/ShippingCostResolver.cs) | +| `IShippingService` | Business logic and orchestration for basket/product shipping | [IShippingService.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Services/Interfaces/IShippingService.cs) | +| `IShippingQuoteService` | Fetches quotes from shipping providers (FedEx, UPS, flat-rate) | [IShippingQuoteService.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Services/Interfaces/IShippingQuoteService.cs) | +| `IShippingCostResolver` | Resolves costs from flat-rate shipping option configurations | [ShippingCostResolver.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Services/ShippingCostResolver.cs) | > **Invariant (CLAUDE.md):** `IShippingService.GetShippingOptionsForBasket()` is the basket-level entry point; it uses the active `IOrderGroupingStrategy` internally. `IShippingQuoteService.GetQuotes*()` is the source of truth for quote retrieval. `ShippingCostResolver.ResolveBaseCost()` is the single source of truth for the flat-rate fallback chain -- do not reimplement the match logic anywhere else. ### Cost Resolution Priority -For flat-rate shipping, costs are resolved in this strict priority order by [`ShippingCostResolver.ResolveBaseCost()`](../../../src/Merchello.Core/Shipping/Services/ShippingCostResolver.cs): +For flat-rate shipping, costs are resolved in this strict priority order by [`ShippingCostResolver.ResolveBaseCost()`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Services/ShippingCostResolver.cs): 1. **State/Region** -- A cost entry matching the exact country + state/province code 2. **Country** -- A cost entry matching the country code with no region @@ -116,7 +116,7 @@ Shipping selections use a stable key format that must remain unchanged: | Flat-rate | `so:{guid}` | `so:abc12345-...` | | Dynamic | `dyn:{provider}:{serviceCode}` | `dyn:fedex:FEDEX_GROUND` | -These keys are parsed by [`SelectionKeyExtensions.TryParse()`](../../../src/Merchello.Core/Shipping/Extensions/SelectionKeyExtensions.cs) into order fields (`ShippingProviderKey`, `ShippingServiceCode`, `ShippingServiceName`) and must remain stable across the checkout flow and any custom grouping strategies. +These keys are parsed by [`SelectionKeyExtensions.TryParse()`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Extensions/SelectionKeyExtensions.cs) into order fields (`ShippingProviderKey`, `ShippingServiceCode`, `ShippingServiceName`) and must remain stable across the checkout flow and any custom grouping strategies. > **Invariant (CLAUDE.md):** Treat this contract as wire-protocol. Checkout, order edit, notifications, and fulfilment all rely on it. Keep the parsed rate in `QuotedCosts` so dynamic rates seen at selection time are what the customer pays. @@ -146,7 +146,7 @@ See [Flat Rate Shipping](flat-rate-shipping.md) and [Dynamic Shipping Providers] ## IShippingService Reference -Defined in [`IShippingService.cs`](../../../src/Merchello.Core/Shipping/Services/Interfaces/IShippingService.cs): +Defined in [`IShippingService.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shipping/Services/Interfaces/IShippingService.cs): | Method | Purpose | |--------|---------| @@ -173,7 +173,7 @@ Shipping can be taxable depending on jurisdiction. Merchello supports four shipp | `Proportional` | Tax rate is a weighted average of basket item tax rates (EU/UK VAT) | | `ProviderCalculated` | Tax provider determines from full order context (e.g., Avalara) | -The shipping tax configuration is queried via [`ITaxProviderManager.GetShippingTaxConfigurationAsync(countryCode, stateCode)`](../../../src/Merchello.Core/Tax/Providers/Interfaces/ITaxProviderManager.cs) and returned as a [`ShippingTaxConfigurationResult`](../../../src/Merchello.Core/Tax/Providers/Models/ShippingTaxConfigurationResult.cs). Proportional math is centralized in [`ITaxCalculationService.CalculateProportionalShippingTax()`](../../../src/Merchello.Core/Tax/Services/Interfaces/ITaxCalculationService.cs). See [Shipping Tax](../tax/shipping-tax.md) for the full model. +The shipping tax configuration is queried via [`ITaxProviderManager.GetShippingTaxConfigurationAsync(countryCode, stateCode)`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Tax/Providers/Interfaces/ITaxProviderManager.cs) and returned as a [`ShippingTaxConfigurationResult`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Tax/Providers/Models/ShippingTaxConfigurationResult.cs). Proportional math is centralized in [`ITaxCalculationService.CalculateProportionalShippingTax()`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Tax/Services/Interfaces/ITaxCalculationService.cs). See [Shipping Tax](../tax/shipping-tax.md) for the full model. > **Invariant (CLAUDE.md):** Never hardcode shipping tax rates. Always query the tax provider. Never reimplement the proportional formula outside `ITaxCalculationService`. diff --git a/docs/documentation/shopping/basket-api.md b/docs/documentation/shopping/basket-api.md index 729ab89c..e2ef8d5b 100644 --- a/docs/documentation/shopping/basket-api.md +++ b/docs/documentation/shopping/basket-api.md @@ -2,7 +2,7 @@ The storefront API provides REST endpoints for basket operations at `/api/merchello/storefront`. These endpoints are designed for JavaScript-driven storefronts -- you can build your entire shopping cart experience with these APIs. -Source: [StorefrontApiController.cs](../../../src/Merchello/Controllers/StorefrontApiController.cs). For the service-level basket contract behind these endpoints, see [Basket Service](./basket-service.md). +Source: [StorefrontApiController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/StorefrontApiController.cs). For the service-level basket contract behind these endpoints, see [Basket Service](./basket-service.md). ## Base URL diff --git a/docs/documentation/shopping/basket-service.md b/docs/documentation/shopping/basket-service.md index 16de6e16..69dcfd31 100644 --- a/docs/documentation/shopping/basket-service.md +++ b/docs/documentation/shopping/basket-service.md @@ -1,6 +1,6 @@ # Basket / Shopping Cart -The basket (shopping cart) is at the heart of the shopping experience. Merchello exposes basket operations on [`ICheckoutService`](../../../src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutService.cs) -- adding items, updating quantities, removing items, and calculating totals. +The basket (shopping cart) is at the heart of the shopping experience. Merchello exposes basket operations on [`ICheckoutService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Services/Interfaces/ICheckoutService.cs) -- adding items, updating quantities, removing items, and calculating totals. > **Invariant:** Basket totals are calculated by `CheckoutService.CalculateBasketAsync()` -- never duplicate that math in controllers, views, or JS. Basket amounts are stored in store currency; display currency is applied on-the-fly. See [Storefront Context](../storefront/storefront-context.md) for the currency rules. @@ -137,7 +137,7 @@ if (hasDigital) ## MVC Example: The .Site Basket Page -The starter site's [BasketController.cs](../../../src/Merchello.Site/Basket/Controllers/BasketController.cs) (paired with [Basket.cshtml](../../../src/Merchello.Site/Views/Basket.cshtml)) shows how to render a basket page with availability data: +The starter site's [BasketController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Basket/Controllers/BasketController.cs) (paired with [Basket.cshtml](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Basket.cshtml)) shows how to render a basket page with availability data: ```csharp public async Task Basket( diff --git a/docs/documentation/store-configuration/countries-and-regions.md b/docs/documentation/store-configuration/countries-and-regions.md index d3c1ca67..2c428107 100644 --- a/docs/documentation/store-configuration/countries-and-regions.md +++ b/docs/documentation/store-configuration/countries-and-regions.md @@ -4,7 +4,7 @@ Merchello includes a built-in locality data system that provides country and reg ## How Locality Data Works -Locality data in Merchello comes from a static, auto-generated dataset based on [country-region-data](https://github.com/country-regions/country-region-data). The data lives in [LocalityData.cs](../../../src/Merchello.Core/Locality/Data/LocalityData.cs) and includes: +Locality data in Merchello comes from a static, auto-generated dataset based on [country-region-data](https://github.com/country-regions/country-region-data). The data lives in [LocalityData.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Locality/Data/LocalityData.cs) and includes: - **All countries** from .NET's `CultureInfo` plus additional territories (Guernsey, Isle of Man, Jersey) - **Subdivisions** (states, provinces, regions) for each country using ISO 3166-2 suffix codes @@ -42,12 +42,12 @@ public class CountyState These are lightweight read-only records returned by the locality catalog: -- `CountryInfo(string Code, string Name)` -- e.g., `("US", "United States")`. See [CountryInfo.cs](../../../src/Merchello.Core/Locality/Models/CountryInfo.cs). -- `SubdivisionInfo(string CountryCode, string RegionCode, string Name)` -- e.g., `("US", "CA", "California")`. See [SubdivisionInfo.cs](../../../src/Merchello.Core/Locality/Models/SubdivisionInfo.cs). +- `CountryInfo(string Code, string Name)` -- e.g., `("US", "United States")`. See [CountryInfo.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Locality/Models/CountryInfo.cs). +- `SubdivisionInfo(string CountryCode, string RegionCode, string Name)` -- e.g., `("US", "CA", "California")`. See [SubdivisionInfo.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Locality/Models/SubdivisionInfo.cs). ## The Locality Catalog -The `ILocalityCatalog` service provides access to the locality data. It is the main entry point for querying countries and regions. See [ILocalityCatalog.cs](../../../src/Merchello.Core/Locality/Services/Interfaces/ILocalityCatalog.cs). +The `ILocalityCatalog` service provides access to the locality data. It is the main entry point for querying countries and regions. See [ILocalityCatalog.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Locality/Services/Interfaces/ILocalityCatalog.cs). ### Getting All Countries @@ -123,7 +123,7 @@ Shipping costs can be configured at different geographic levels with a priority Merchello uses a consistent `Address` model across all address contexts (warehouse origin, supplier contact, customer billing/shipping): -The model is an EF Core owned type, meaning it is stored as columns on the parent entity's table rather than in its own table. See [Address.cs](../../../src/Merchello.Core/Locality/Models/Address.cs) and the API contract in [AddressDto.cs](../../../src/Merchello.Core/Locality/Dtos/AddressDto.cs). +The model is an EF Core owned type, meaning it is stored as columns on the parent entity's table rather than in its own table. See [Address.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Locality/Models/Address.cs) and the API contract in [AddressDto.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Locality/Dtos/AddressDto.cs). > **Warning:** When working with addresses across the C#/TypeScript boundary, use the canonical field names defined in the project conventions: `AddressOne` (not `address1`), `TownCity` (not `city`), `CountyState` (not `state`), `RegionCode` (not `stateCode`). This is a cross-boundary invariant -- see the CLAUDE.md naming conventions for the full list. diff --git a/docs/documentation/store-configuration/store-settings.md b/docs/documentation/store-configuration/store-settings.md index db7f648d..dc5d81c7 100644 --- a/docs/documentation/store-configuration/store-settings.md +++ b/docs/documentation/store-configuration/store-settings.md @@ -4,7 +4,7 @@ Store settings control how your storefront displays prices, stock levels, and cu ## Settings That Affect Your Storefront -These settings are configured in `appsettings.json` under the `Merchello` key and bind to [MerchelloSettings.cs](../../../src/Merchello.Core/Shared/Models/MerchelloSettings.cs): +These settings are configured in `appsettings.json` under the `Merchello` key and bind to [MerchelloSettings.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shared/Models/MerchelloSettings.cs): ```json { diff --git a/docs/documentation/store-configuration/suppliers.md b/docs/documentation/store-configuration/suppliers.md index 03f16822..0513b963 100644 --- a/docs/documentation/store-configuration/suppliers.md +++ b/docs/documentation/store-configuration/suppliers.md @@ -18,7 +18,7 @@ Suppliers are managed in the Merchello backoffice. Each supplier has: - A collection of warehouses they own. - Optional Supplier Direct fulfilment settings (CSV/FTP/email delivery for purchase orders). -See the DTO contracts in [SupplierDetailDto.cs](../../../src/Merchello.Core/Warehouses/Dtos/SupplierDetailDto.cs) and related `CreateSupplierDto` / `UpdateSupplierDto` for the full set of fields. +See the DTO contracts in [SupplierDetailDto.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Warehouses/Dtos/SupplierDetailDto.cs) and related `CreateSupplierDto` / `UpdateSupplierDto` for the full set of fields. ## Vendor Order Grouping diff --git a/docs/documentation/store-configuration/warehouses.md b/docs/documentation/store-configuration/warehouses.md index e88058a7..edb4ad75 100644 --- a/docs/documentation/store-configuration/warehouses.md +++ b/docs/documentation/store-configuration/warehouses.md @@ -6,7 +6,7 @@ Warehouses drive two storefront behaviours: they determine which shipping option ## The Warehouse Model -Source: [Warehouse.cs](../../../src/Merchello.Core/Warehouses/Models/Warehouse.cs) +Source: [Warehouse.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Warehouses/Models/Warehouse.cs) A warehouse has an origin address (used as the shipping "ship-from"), a collection of service regions, and a `SupplierId` in multi-vendor setups. @@ -30,7 +30,7 @@ public class WarehouseServiceRegion } ``` -Source: [WarehouseServiceRegion.cs](../../../src/Merchello.Core/Warehouses/Models/WarehouseServiceRegion.cs) +Source: [WarehouseServiceRegion.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Warehouses/Models/WarehouseServiceRegion.cs) Country codes use ISO 3166-1 alpha-2 (e.g. `"GB"`) and region codes use the ISO 3166-2 suffix (e.g. `"CA"`, not `"US-CA"`). Use `"*"` as a wildcard country to service everywhere with optional exclusions. @@ -44,7 +44,7 @@ When multiple warehouses can fulfil an order, Merchello picks one using a strict 2. **Service region eligibility** -- the warehouse must be able to ship to the customer's country/region. 3. **Stock availability** -- `Stock - ReservedStock >= requested quantity` at the selected warehouse. -Source: [WarehouseService.cs:35](../../../src/Merchello.Core/Warehouses/Services/WarehouseService.cs#L35), selection parameters in [SelectWarehouseForProductParameters.cs](../../../src/Merchello.Core/Warehouses/Services/Parameters/SelectWarehouseForProductParameters.cs). +Source: [WarehouseService.cs:35](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Warehouses/Services/WarehouseService.cs#L35), selection parameters in [SelectWarehouseForProductParameters.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Warehouses/Services/Parameters/SelectWarehouseForProductParameters.cs). See [Inventory and Stock](../products/inventory-and-stock.md) for the full stock lifecycle and [Products Overview](../products/products-overview.md) for how `ProductRootWarehouse` priority is set. diff --git a/docs/documentation/storefront/price-display.md b/docs/documentation/storefront/price-display.md index 0ebe9860..1c5a5f28 100644 --- a/docs/documentation/storefront/price-display.md +++ b/docs/documentation/storefront/price-display.md @@ -21,7 +21,7 @@ This is configured in your store settings and is automatically included in the ` ## How the .Site Project Does It -Here is the pattern from the example product page ([Views/Products/Default.cshtml:23-79](../../../src/Merchello.Site/Views/Products/Default.cshtml#L23)): +Here is the pattern from the example product page ([Views/Products/Default.cshtml:23-79](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Products/Default.cshtml#L23)): ```csharp // 1. Get the display context (currency, tax settings, exchange rate) diff --git a/docs/documentation/storefront/product-availability.md b/docs/documentation/storefront/product-availability.md index 9b3bc025..7caa6879 100644 --- a/docs/documentation/storefront/product-availability.md +++ b/docs/documentation/storefront/product-availability.md @@ -15,7 +15,7 @@ A product might be "in stock" at your New York warehouse but "unavailable" for a ## The ProductLocationAvailability Record -Every availability check returns a [`ProductLocationAvailability`](../../../src/Merchello.Core/Storefront/Models/ProductLocationAvailability.cs) record: +Every availability check returns a [`ProductLocationAvailability`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Storefront/Models/ProductLocationAvailability.cs) record: ```csharp public record ProductLocationAvailability( @@ -114,7 +114,7 @@ const availability = await response.json(); ## Displaying Stock on Product Pages -The starter site demonstrates the pattern for showing stock status on a product page ([Default.cshtml:37-49](../../../src/Merchello.Site/Views/Products/Default.cshtml#L37)): +The starter site demonstrates the pattern for showing stock status on a product page ([Default.cshtml:37-49](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/Views/Products/Default.cshtml#L37)): ```csharp // Get availability for each variant diff --git a/docs/documentation/storefront/storefront-context.md b/docs/documentation/storefront/storefront-context.md index c6fe422c..c0a58dd5 100644 --- a/docs/documentation/storefront/storefront-context.md +++ b/docs/documentation/storefront/storefront-context.md @@ -1,6 +1,6 @@ # Storefront Context and Display -The [`IStorefrontContextService`](../../../src/Merchello.Core/Storefront/Services/Interfaces/IStorefrontContextService.cs) is the central service for everything your storefront needs to know about the current customer's context -- their shipping location, preferred currency, exchange rates, tax settings, and product availability. It reads from cookies, store settings, and fallback defaults to build a consistent context for every page render. +The [`IStorefrontContextService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Storefront/Services/Interfaces/IStorefrontContextService.cs) is the central service for everything your storefront needs to know about the current customer's context -- their shipping location, preferred currency, exchange rates, tax settings, and product availability. It reads from cookies, store settings, and fallback defaults to build a consistent context for every page render. > **Invariant:** `GetDisplayContextAsync()` is the single source of truth for storefront display. Basket amounts are always stored in store currency; display currency is applied on-the-fly using multiply (`amount * ExchangeRate`). Never charge a customer from display amounts -- checkout and payment use the invoice conversion path (divide). See CLAUDE.md for the full multi-currency rule. @@ -194,7 +194,7 @@ var availability = await storefrontContext.GetBasketAvailabilityAsync( ## Storefront REST API -The [`StorefrontApiController`](../../../src/Merchello/Controllers/StorefrontApiController.cs) exposes these operations as REST endpoints at `/api/merchello/storefront`: +The [`StorefrontApiController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/StorefrontApiController.cs) exposes these operations as REST endpoints at `/api/merchello/storefront`: | Method | Endpoint | Description | |--------|----------|-------------| diff --git a/docs/documentation/tax/tax-overview.md b/docs/documentation/tax/tax-overview.md index a27d4bec..501402ca 100644 --- a/docs/documentation/tax/tax-overview.md +++ b/docs/documentation/tax/tax-overview.md @@ -47,7 +47,7 @@ Each rate has: ## Rate Lookup Chain -When Merchello needs to calculate tax for a product, it uses [`ITaxService.GetApplicableRateAsync()`](../../../src/Merchello.Core/Accounting/Services/Interfaces/ITaxService.cs) which follows a strict priority: +When Merchello needs to calculate tax for a product, it uses [`ITaxService.GetApplicableRateAsync()`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Accounting/Services/Interfaces/ITaxService.cs) which follows a strict priority: ``` 1. State-specific rate --> Found? Use it. @@ -89,8 +89,8 @@ The checkout basket DTO includes reactive fields for tax-inclusive display: 1. **Product setup**: You assign a `TaxGroupId` to each `ProductRoot`. 2. **Basket creation**: When a product is added to the basket, the `TaxGroupId` is captured on the line item by the line-item factory. -3. **Checkout calculation**: [`CheckoutService.CalculateBasketAsync()`](../../../src/Merchello.Core/Checkout/Services/CheckoutService.cs) triggers tax calculation. -4. **Tax orchestration**: [`ITaxOrchestrationService`](../../../src/Merchello.Core/Tax/Services/Interfaces/ITaxOrchestrationService.cs) coordinates with the active tax provider ([`TaxOrchestrationService`](../../../src/Merchello.Core/Tax/Services/TaxOrchestrationService.cs)). +3. **Checkout calculation**: [`CheckoutService.CalculateBasketAsync()`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Checkout/Services/CheckoutService.cs) triggers tax calculation. +4. **Tax orchestration**: [`ITaxOrchestrationService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Tax/Services/Interfaces/ITaxOrchestrationService.cs) coordinates with the active tax provider ([`TaxOrchestrationService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Tax/Services/TaxOrchestrationService.cs)). 5. **Rate application**: The provider (manual or external) returns rates per line item. 6. **Invoice creation**: Tax amounts are locked into the invoice. diff --git a/docs/documentation/ucp/ucp-overview.md b/docs/documentation/ucp/ucp-overview.md index 2c7848ac..3bd120ba 100644 --- a/docs/documentation/ucp/ucp-overview.md +++ b/docs/documentation/ucp/ucp-overview.md @@ -61,7 +61,7 @@ Extensions add optional features to the checkout flow: ## Checkout Sessions -The primary UCP flow is the checkout session. An agent creates a session, adds items, sets addresses, and completes payment. All transactional endpoints are implemented in [`UcpCheckoutSessionsController`](../../../src/Merchello/Controllers/UcpCheckoutSessionsController.cs) and delegate to [`UCPProtocolAdapter`](../../../src/Merchello.Core/Protocols/UCP/UCPProtocolAdapter.cs). +The primary UCP flow is the checkout session. An agent creates a session, adds items, sets addresses, and completes payment. All transactional endpoints are implemented in [`UcpCheckoutSessionsController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/UcpCheckoutSessionsController.cs) and delegate to [`UCPProtocolAdapter`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Protocols/UCP/UCPProtocolAdapter.cs). ### Session Lifecycle @@ -84,7 +84,7 @@ Each session tracks: ## Cart API (Draft) -The cart API provides pre-checkout exploration. Agents can create carts, add/remove items, and see pricing before committing to a checkout session. Source: [`UcpCartController`](../../../src/Merchello/Controllers/UcpCartController.cs). +The cart API provides pre-checkout exploration. Agents can create carts, add/remove items, and see pricing before committing to a checkout session. Source: [`UcpCartController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/UcpCartController.cs). | Endpoint | Method | Description | |---|---|---| @@ -95,7 +95,7 @@ The cart API provides pre-checkout exploration. Agents can create carts, add/rem ## Orders API -Agents can query order status after checkout. Source: [`UcpOrdersController`](../../../src/Merchello/Controllers/UcpOrdersController.cs). +Agents can query order status after checkout. Source: [`UcpOrdersController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/UcpOrdersController.cs). | Endpoint | Method | Description | |---|---|---| @@ -112,7 +112,7 @@ Agents can browse and search the product catalog: UCP uses the `UCP-Agent` header for agent identification. The header contains a profile URI pointing to the agent's own `/.well-known/ucp` manifest. -Merchello's [`AgentAuthenticationMiddleware`](../../../src/Merchello/Middleware/AgentAuthenticationMiddleware.cs) processes this header to: +Merchello's [`AgentAuthenticationMiddleware`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Middleware/AgentAuthenticationMiddleware.cs) processes this header to: 1. Parse the agent identity 2. Fetch the agent's profile (if needed) @@ -123,7 +123,7 @@ The middleware only activates on UCP paths: `/.well-known/ucp`, `/api/v1/checkou ### Signing Keys -UCP uses ES256 (ECDSA with P-256) for request signing. The [`UcpSigningKeyRotationJob`](../../../src/Merchello.Core/Protocols/Webhooks/UcpSigningKeyRotationJob.cs) background service enforces the configured rotation policy (daily check, rotation after `Ucp:SigningKeyRotationDays`, default 90). Setting `SigningKeyRotationDays` to 0 disables automatic rotation. +UCP uses ES256 (ECDSA with P-256) for request signing. The [`UcpSigningKeyRotationJob`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Protocols/Webhooks/UcpSigningKeyRotationJob.cs) background service enforces the configured rotation policy (daily check, rotation after `Ucp:SigningKeyRotationDays`, default 90). Setting `SigningKeyRotationDays` to 0 disables automatic rotation. ### OAuth 2.0 Integration @@ -142,7 +142,7 @@ This returns OAuth 2.0 Authorization Server Metadata (RFC 8414) with: ## Configuration -Configure UCP in `appsettings.json` (binds to [`ProtocolSettings`](../../../src/Merchello.Core/Protocols/Models/ProtocolSettings.cs) / [`UcpSettings`](../../../src/Merchello.Core/Protocols/Models/UcpSettings.cs)): +Configure UCP in `appsettings.json` (binds to [`ProtocolSettings`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Protocols/Models/ProtocolSettings.cs) / [`UcpSettings`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Protocols/Models/UcpSettings.cs)): ```json { @@ -177,9 +177,9 @@ Configure UCP in `appsettings.json` (binds to [`ProtocolSettings`](../../../src/ ## Protocol Adapter Architecture -UCP is implemented through the [`ICommerceProtocolAdapter`](../../../src/Merchello.Core/Protocols/Interfaces/ICommerceProtocolAdapter.cs) interface, which is discovered by Merchello's `ExtensionManager`. The [`UCPProtocolAdapter`](../../../src/Merchello.Core/Protocols/UCP/UCPProtocolAdapter.cs) translates between UCP's protocol format and Merchello's internal checkout/payment models. +UCP is implemented through the [`ICommerceProtocolAdapter`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Protocols/Interfaces/ICommerceProtocolAdapter.cs) interface, which is discovered by Merchello's `ExtensionManager`. The [`UCPProtocolAdapter`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Protocols/UCP/UCPProtocolAdapter.cs) translates between UCP's protocol format and Merchello's internal checkout/payment models. -The [`ICommerceProtocolManager`](../../../src/Merchello.Core/Protocols/Interfaces/ICommerceProtocolManager.cs) coordinates adapters and handles: +The [`ICommerceProtocolManager`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Protocols/Interfaces/ICommerceProtocolManager.cs) coordinates adapters and handles: - Protocol support detection - Manifest generation and caching @@ -190,7 +190,7 @@ This architecture means you could implement adapters for other commerce protocol ## Testing -Merchello includes a UCP test agent profile endpoint for development (implemented in [`WellKnownController`](../../../src/Merchello/Controllers/WellKnownController.cs)): +Merchello includes a UCP test agent profile endpoint for development (implemented in [`WellKnownController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/WellKnownController.cs)): ``` GET /.well-known/ucp-test-agent/{agentId} @@ -198,11 +198,11 @@ GET /.well-known/ucp-test-agent/{agentId} This returns a simulated agent profile with the store's published signing keys and negotiated capabilities, making it easier to test UCP flows without a real agent. -The [`UcpTestApiController`](../../../src/Merchello/Controllers/UcpTestApiController.cs) provides additional testing endpoints backed by [`IUcpFlowTestService`](../../../src/Merchello.Core/Protocols/UCP/Services/Interfaces/IUcpFlowTestService.cs) for validating create/update/complete session flows end-to-end. +The [`UcpTestApiController`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/UcpTestApiController.cs) provides additional testing endpoints backed by [`IUcpFlowTestService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Protocols/UCP/Services/Interfaces/IUcpFlowTestService.cs) for validating create/update/complete session flows end-to-end. ## UCP Webhooks -UCP supports webhook notifications for session state changes. These are separate from Merchello's standard [outbound webhooks](../webhooks/webhooks-overview.md) and follow UCP's webhook specification with ES256-signed payloads, dispatched by [`UcpOrderWebhookHandler`](../../../src/Merchello.Core/Protocols/UCP/Handlers/UcpOrderWebhookHandler.cs) (priority 3000). +UCP supports webhook notifications for session state changes. These are separate from Merchello's standard [outbound webhooks](../webhooks/webhooks-overview.md) and follow UCP's webhook specification with ES256-signed payloads, dispatched by [`UcpOrderWebhookHandler`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Protocols/UCP/Handlers/UcpOrderWebhookHandler.cs) (priority 3000). ## Related Topics @@ -210,4 +210,4 @@ UCP supports webhook notifications for session state changes. These are separate - [Notification System](../notifications/notification-system.md) (UCP handler priority 3000) - [Outbound Webhooks](../webhooks/webhooks-overview.md) - [Background Jobs](../background-jobs/background-jobs.md) -- [Developer reference - docs/UCP.md](../../UCP.md) (internal integration guide) +- [Developer reference - docs/UCP.md](https://github.com/YodasMyDad/Merchello/blob/main/docs/UCP.md) (internal integration guide) diff --git a/docs/documentation/upsells/upsells-overview.md b/docs/documentation/upsells/upsells-overview.md index 6d50b28a..eea52998 100644 --- a/docs/documentation/upsells/upsells-overview.md +++ b/docs/documentation/upsells/upsells-overview.md @@ -2,7 +2,7 @@ Merchello's upsell system recommends additional products to customers based on what is in their basket, the products they are viewing, and where they are in the purchase journey. Upsell rules are configured in the Merchello backoffice; this page covers the storefront API and display integration. -> **Upsells are separate from the cart/checkout flow.** The upsell engine ([`IUpsellEngine`](../../../src/Merchello.Core/Upsells/Services/Interfaces/IUpsellEngine.cs)) only produces suggestions and records analytics events. It does not add anything to the basket, modify pricing, or change the checkout state. The one exception is **post-purchase** upsells, which run **after** successful payment against a saved payment method and apply as an **invoice edit** on the already-paid invoice via [`IPostPurchaseUpsellService`](../../../src/Merchello.Core/Upsells/Services/Interfaces/IPostPurchaseUpsellService.cs) -- they still do not touch the original cart/checkout flow. +> **Upsells are separate from the cart/checkout flow.** The upsell engine ([`IUpsellEngine`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Upsells/Services/Interfaces/IUpsellEngine.cs)) only produces suggestions and records analytics events. It does not add anything to the basket, modify pricing, or change the checkout state. The one exception is **post-purchase** upsells, which run **after** successful payment against a saved payment method and apply as an **invoice edit** on the already-paid invoice via [`IPostPurchaseUpsellService`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Upsells/Services/Interfaces/IPostPurchaseUpsellService.cs) -- they still do not touch the original cart/checkout flow. ## Display Locations @@ -18,7 +18,7 @@ Upsells can appear in multiple places during the shopping experience: ## Fetching Upsell Suggestions -Endpoints live on [`StorefrontUpsellController.cs`](../../../src/Merchello/Controllers/StorefrontUpsellController.cs). +Endpoints live on [`StorefrontUpsellController.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/StorefrontUpsellController.cs). ### Product Page @@ -38,7 +38,7 @@ Fetch upsell suggestions based on the current basket contents: GET /api/merchello/storefront/upsells?location=Basket&countryCode=GB®ionCode=ENG ``` -The `location` parameter is a [`UpsellDisplayLocation`](../../../src/Merchello.Core/Upsells/Models/UpsellDisplayLocation.cs) flags enum: `Checkout` (1), `Basket` (2), `ProductPage` (4), `Email` (8), `Confirmation` (16). Because it is `[Flags]`, a single rule can target multiple locations. +The `location` parameter is a [`UpsellDisplayLocation`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Upsells/Models/UpsellDisplayLocation.cs) flags enum: `Checkout` (1), `Basket` (2), `ProductPage` (4), `Email` (8), `Confirmation` (16). Because it is `[Flags]`, a single rule can target multiple locations. Country and region codes are optional. If omitted, the API falls back to the basket shipping address, then to the storefront display context (`IStorefrontContextService.GetDisplayContextAsync()`). @@ -154,7 +154,7 @@ Upsells are re-fetched on the basket page when the country or region changes, si ## Checkout Modes -When upsells target the checkout, the `checkoutMode` field ([`CheckoutUpsellMode.cs`](../../../src/Merchello.Core/Upsells/Models/CheckoutUpsellMode.cs)) indicates how to render them: +When upsells target the checkout, the `checkoutMode` field ([`CheckoutUpsellMode.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Upsells/Models/CheckoutUpsellMode.cs)) indicates how to render them: | Mode | Behavior | | ---- | -------- | @@ -176,7 +176,7 @@ Key constraints: ### API Endpoints -All four endpoints are routed under `/api/merchello/checkout/post-purchase` ([`PostPurchaseUpsellController.cs`](../../../src/Merchello/Controllers/PostPurchaseUpsellController.cs)): +All four endpoints are routed under `/api/merchello/checkout/post-purchase` ([`PostPurchaseUpsellController.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/PostPurchaseUpsellController.cs)): | Method | Endpoint | Description | | ------ | -------- | ----------- | @@ -244,11 +244,11 @@ Content-Type: application/json } ``` -Event types are `Impression`, `Click`, and `Conversion` ([`UpsellEventType.cs`](../../../src/Merchello.Core/Upsells/Models/UpsellEventType.cs)). The display location numeric values match the `UpsellDisplayLocation` flags enum (`Checkout` = 1, `Basket` = 2, `ProductPage` = 4, `Email` = 8, `Confirmation` = 16). +Event types are `Impression`, `Click`, and `Conversion` ([`UpsellEventType.cs`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Upsells/Models/UpsellEventType.cs)). The display location numeric values match the `UpsellDisplayLocation` flags enum (`Checkout` = 1, `Basket` = 2, `ProductPage` = 4, `Email` = 8, `Confirmation` = 16). > **Impressions are recorded for you.** `GET /api/merchello/storefront/upsells` automatically captures an impression record for every suggestion it returns with products. You typically only need to `POST` click events manually. -The starter site tracks events like this (source of truth: [`merchello-api.js`](../../../src/Merchello.Site/wwwroot/scripts/merchello-api.js)): +The starter site tracks events like this (source of truth: [`merchello-api.js`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Site/wwwroot/scripts/merchello-api.js)): ```js // Record a click when a customer interacts with an upsell product diff --git a/docs/documentation/webhooks/webhooks-overview.md b/docs/documentation/webhooks/webhooks-overview.md index c15b1321..bd9c6d93 100644 --- a/docs/documentation/webhooks/webhooks-overview.md +++ b/docs/documentation/webhooks/webhooks-overview.md @@ -2,7 +2,7 @@ Merchello can send HTTP webhooks to external systems when events happen in your store. When an order is placed, a product is updated, or a shipment ships, Merchello posts a JSON payload to your configured URL. This lets you integrate with ERPs, marketing tools, analytics platforms, or any system that can receive HTTP requests. -Webhooks are driven by the same [notification pipeline](../notifications/notification-system.md) as the [email system](../email/email-overview.md). The [`WebhookNotificationHandler`](../../../src/Merchello.Core/Webhooks/Handlers/WebhookNotificationHandler.cs) runs at priority **2200** and queues an `OutboundDelivery` row for each active subscription whose topic and filter match. +Webhooks are driven by the same [notification pipeline](../notifications/notification-system.md) as the [email system](../email/email-overview.md). The [`WebhookNotificationHandler`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Webhooks/Handlers/WebhookNotificationHandler.cs) runs at priority **2200** and queues an `OutboundDelivery` row for each active subscription whose topic and filter match. ``` Notification -> WebhookNotificationHandler (2200) -> IWebhookService.QueueDeliveryAsync @@ -14,7 +14,7 @@ Notification -> WebhookNotificationHandler (2200) -> IWebhookService.QueueDelive ## Key Features -- **36 event topics** across 11 categories (see [WebhookTopicRegistry.cs](../../../src/Merchello.Core/Webhooks/Services/WebhookTopicRegistry.cs)) +- **36 event topics** across 11 categories (see [WebhookTopicRegistry.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Webhooks/Services/WebhookTopicRegistry.cs)) - **HMAC-SHA256 / SHA512 signing** for payload verification - **Multiple auth types** (HMAC, Bearer Token, API Key, Basic Auth, None) - **Automatic retry** with configurable backoff and stale-send recovery @@ -107,7 +107,7 @@ Webhooks are organized by category. Here are all the available topics: | `fulfilment.inventory_updated` | Inventory synced from 3PL | | `fulfilment.product_synced` | Products synced to 3PL | -> **Topic naming:** Keys use dots as category separators and underscores within a word. Always reference the constants in [Constants.cs:WebhookTopics](../../../src/Merchello.Core/Constants.cs#L239) rather than copy/pasting strings. +> **Topic naming:** Keys use dots as category separators and underscores within a word. Always reference the constants in [Constants.cs:WebhookTopics](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Constants.cs#L239) rather than copy/pasting strings. ## Creating a Webhook Subscription @@ -152,7 +152,7 @@ POST /api/v1/webhooks ### HMAC Signing -When you create a subscription with HMAC auth, Merchello generates a secret key. Each webhook delivery includes a signature header computed from the UTF-8 bytes of the raw request body, Base64-encoded (see [WebhookDispatcher.AddSignature](../../../src/Merchello.Core/Webhooks/Services/WebhookDispatcher.cs#L236)): +When you create a subscription with HMAC auth, Merchello generates a secret key. Each webhook delivery includes a signature header computed from the UTF-8 bytes of the raw request body, Base64-encoded (see [WebhookDispatcher.AddSignature](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Webhooks/Services/WebhookDispatcher.cs#L236)): ``` X-Merchello-Hmac-SHA256: {base64-encoded-signature} @@ -187,7 +187,7 @@ POST /api/v1/webhooks/{id}/regenerate-secret ## Delivery and Retries -Webhooks are delivered asynchronously through the [`OutboundDeliveryJob`](../../../src/Merchello.Core/Webhooks/Services/OutboundDeliveryJob.cs) background service, which also processes [email](../email/email-overview.md) deliveries. This means: +Webhooks are delivered asynchronously through the [`OutboundDeliveryJob`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Webhooks/Services/OutboundDeliveryJob.cs) background service, which also processes [email](../email/email-overview.md) deliveries. This means: - Webhook dispatch never blocks the main operation - Failed deliveries are automatically retried @@ -207,7 +207,7 @@ Webhooks are delivered asynchronously through the [`OutboundDeliveryJob`](../../ ### Retry Policy -Configurable via [`WebhookSettings`](../../../src/Merchello.Core/Webhooks/Models/WebhookSettings.cs) (binds from `Merchello:Webhooks`): +Configurable via [`WebhookSettings`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Webhooks/Models/WebhookSettings.cs) (binds from `Merchello:Webhooks`): ```json { @@ -276,7 +276,7 @@ Returns aggregate statistics for webhook deliveries: success count, failure coun ## URL Security -Webhook target URLs are validated before delivery by [`UrlSecurityValidator.TryValidatePublicHttpUrl`](../../../src/Merchello.Core/Shared/Security/UrlSecurityValidator.cs). Enforced rules: +Webhook target URLs are validated before delivery by [`UrlSecurityValidator.TryValidatePublicHttpUrl`](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello.Core/Shared/Security/UrlSecurityValidator.cs). Enforced rules: - Must be a valid HTTP/HTTPS URL - Private/internal IP addresses are blocked (SSRF protection) - The URL validation runs on every delivery attempt, not just at subscription creation @@ -284,7 +284,7 @@ Webhook target URLs are validated before delivery by [`UrlSecurityValidator.TryV ## Backoffice API Summary -Source: [WebhooksApiController.cs](../../../src/Merchello/Controllers/WebhooksApiController.cs). +Source: [WebhooksApiController.cs](https://github.com/YodasMyDad/Merchello/blob/main/src/Merchello/Controllers/WebhooksApiController.cs). | Endpoint | Method | Description | |---|---|---|