QR Coupon Journeys

Status: proposed design, 18 September 2026. Plan label: design:qr-coupon-journeys. Every implementation issue also carries astra. This document specifies future implementation; examples introduce proposed APIs and must not be copied as working code today.

Start here

This page is the entry point for the complete design package. The requested design, mockups and implementation issues are complete and saved in draft PR #2067. The runtime feature and campaign are not implemented or live. There is no production redemption URL yet.

These source links work on GitHub while the proposal awaits merge; the mesh links elsewhere in the documents describe their eventual node locations.

Read or inspect Entry
Overall implementation and fluent actions Continue below on this page
Phone card, offer and installation Phone design
Coupon builder, configurable features and COURSES3M Coupon design
Apple Pay, Google Pay, card setup and renewal Payment design
LinkedIn artwork, post copy and prompts LinkedIn design · PNG artwork
Interactive previews Phone HTML · LinkedIn HTML; GitHub displays source, so download/open locally or use the previews in the design conversation
Delivery sequence, model/effort recommendation and all tasks Implementation backlog · Tracking epic #2066

The idea

An activity can prepare a reusable action, publish it at an opaque GUID URL on a chosen MeshWeaver site, and return an Open QR card action. The sender adds that card to their iPhone or Android home screen. Someone else scans it, signs up or logs in on the destination site, and resumes a prefilled page. Visiting the URL prepares the action; the recipient confirms any mutation.

The first application is COURSES3M: a bounded Personal-plan offer with payment-method setup upfront, no activation charge, and an optional, separately approved renewal feature. There is plenty of free content; non-free items require payment and each separate purchase requires confirmation. The renewal checkbox authorizes the disclosed recurring subscription only. See the coupon specification, payment methods, phone design, and issue backlog.

Product contract

  1. The publisher selects a registered destination site, allowed action, presentation, and permitted prefilled values. A fluent API declares intent; it does not serialize executable C# supplied by a URL visitor.
  2. Publication runs as an Activity, validates publisher rights, and writes a durable action definition. The result contains a stable URL, card URL, and durable activity action descriptor.
  3. The card has a large QR code, verified site hostname, offer title, expiry information, Copy link, Share, and Install on iPhone / Android. Installing creates a home-screen web-app icon. It does not install courses or create a native store app.
  4. Both the card page and the recipient offer are Public but not Anonymous: every signed-in user may open the sanitized presentation without purchasing or enrolling; anonymous visitors see only the sign-in gateway. Management, coupon internals, personal prefills, and claim records remain private.
  5. Scanning opens the destination site's sign-in gateway. Login, registration, verification, and onboarding retain a server-side continuation. The recipient returns to the offer, sees permitted prefilled fields, and explicitly continues.
  6. Payment-method setup saves a method for future customer-confirmed purchases. Without the optional renewal choice, setup must not create a PaymentIntent, recurring subscription, invoice, or charge. When the recipient separately opts into renewal, the provider may create a zero-due trial subscription with the exact disclosed renewal schedule, backed by an explicit recurring mandate. Wallet availability is detected; card entry remains the fallback. No branch may charge for activation. Apple Pay saved-token restrictions require a compatible card fallback for default reusable on-session storage; do not silently select renewal to enable a wallet.
  7. After verified setup and fresh coupon validation, the server activates the bounded plan exactly once, installs the selected eligible course if one was specified, and opens its cover or installed entry point. A plan-only offer may open the course catalog. The destination is checked against current permission and install state.

No outbound email or message is sent merely by creating a link. Copy/Share hands the link to the sender; later messaging integrations require their normal explicit send authorization.

Existing building blocks and gaps

Evidence was read from this repository and the mesh on 18 September 2026. Re-check implementation details when an issue begins.

Existing building block Reuse Missing capability
hub.RunActivity, ActivityContext, owning partition, cancellation and logs Publication, provider setup, fulfillment and retries get ordinary Activities A durable typed navigation action that survives the execution hub unloading
src/MeshWeaver.Graph.Views/ActivityViews.cs Render a current activity's returned controls Current result controls are recovered from a live kernel stream; persisted return-value JSON is not a durable UI contract
Essentials/OperationRequest/Source/OperationDsl.cs Fluent style and reviewable declarations as inspiration OperationRequest runs privileged administrative scripts; it is not the execution engine for shared recipient links
Existing Login, Welcome, Onboarding and Navigation/LocalUrl.cs Safe local return destinations and existing account creation Durable continuation across all auth/onboarding steps and standalone app launches
Framework controls and navigation Bind forms to typed streams; use existing button/navigation primitives QR, PWA install guidance, and reusable Stripe setup controls
Store/Coupon, Licensing, billing, payments and installers Domain validation and fulfillment primitives Payment setup contract, exact-once claim orchestration, and the coupon fluent adapter

The activity skill's general persistence rule still applies: save results on a domain node. The activity action descriptor links to that node; it must never make historical activity rendering depend on a still-live execution hub.

Ownership and dependency direction

Essentials already requires Store. Do not add Store -> Essentials: that would introduce a package cycle.

Composition is in Essentials, which already has Store available. Handler registration is explicit by key and schema version. Publication fails with a useful capability error if the destination lacks the handler or required setup capability. An advertised action cannot rely on a module being accidentally loaded.

Proposed fluent API

All names in this block are proposed, not current MeshWeaver APIs. site and coursePath are resolved registry objects/paths supplied by the publisher; no sample silently chooses a course or site for production.

// Proposed API. Neither constructing the descriptor nor rendering the card executes it.
var couponAction = Coupons.Journey("COURSES3M")
    .OpenCourse(coursePath)
    .Benefit(b => b.UseCouponTierAndExactExpiry())
    .RequirePaymentSetup(s => s
        .ForIndividuallyApprovedPurchases()
        .OfferEligibleWalletsAndCard()
        .ConsentText("Save a payment method for purchases I choose. "
            + "Nothing is charged today."))
    .WithFeatures(f => f
        .OptionalSubscriptionRenewal("continue-personal", r => r
            .ForCouponTier("personal")
            .DefaultSelected(false)
            .StartAtAcceptedBenefitEnd()
            .QuoteCurrentPlan("personal", cadence: "monthly")
            .RequireSeparateRecurringConsent()
            .ShowStartTotalCadenceAndCancellation()))
    .AfterClaim(a => a.OpenSelectedCourse())
    .BuildAction();

// Proposed Essentials composer, outside Store's dependency graph.
return Actions.Link("courses3m")
    .ForSite(site)
    .RequireSignIn()
    .Do(couponAction)
    .Prefill(p => p
        .Language("en")
        .Campaign("courses-three-months")
        .ProfileFromSignedInRecipient())
    .WithCard(c => c.Title("Personal access with COURSES3M"))
    .Sharing(s => s.ReusableInvitation().AllowRecipientCard())
    .Publish(hub, activity);

The result is data: ActionLinkId, RecipientUrl, CardUrl, DefinitionVersion, and a durable ActivityActionDescriptor with caption Open QR card. A generic builder may also support .OpenPage(target).Prefill(...) for navigation-only journeys; side-effecting handlers add a recipient review/confirmation step. Never expose arbitrary scripts, unrestricted redirects, or implicit System execution through .Do(...).

Publish is idempotent for (owner, publisherRequestId). Repeating the same request returns its existing link. A changed definition requires a new revision and a fresh publication request, or an explicit authorized update. Activation binds to a terms snapshot, not whatever a mutable link happens to mean later.

Data and endpoints

Names and paths below are proposed. Use registered content discriminators and owning-hub create/update verbs; these are not hand-authored operational nodes to seed into GitSynced plugin spaces.

Record Contents and access
ActionLinkDefinition GUID v4 from a cryptographic generator, site identity, publisher, handler key/version, validated target, public presentation, allowed shared prefill, valid window, definition revision, revoke state; private management record
ActionLinkPresentation Sanitized projection only, backed by an explicit Public Viewer grant without Anonymous; no raw coupon, billing, user, or action-management data
ActionContinuation Expiring opaque handle bound to site and auth transaction, then recipient identity; carries link id/version and validated local return route server-side
ActionClaim Server-owned, recipient-private domain record, link id, coupon identity, terms snapshot, provider setup reference, idempotency key, domain completion and result path; issuer gets permitted aggregate status only
Activity action descriptor Stable handler-independent Navigate result with label and local target; authorization is rechecked when opened

Only the server writes claim identity, quote, provider references and fulfillment milestones; recipients may submit only validated intent and consent fields. Use a provisioned service-owned operational partition with a documented access policy, not ad hoc nodes inside the GitSynced Essentials or Store partitions. Provision it through supported tenant/service bootstrap. No human Admin grant on a synced space, no global-admin read shortcut. Anonymous middleware can resolve an opaque continuation internally, but cannot read the protected presentation through MCP, search, content, or layout endpoints.

Proposed routes:

The GUID is an identifier, not a credential or entitlement. URLs, QR images, OG previews, browser history and analytics must not contain personal data, payment secrets, coupon internals, session cookies or bearer grants. Apply noindex, restrictive referrer policy and redacted route telemetry. Invalid, expired and revoked links receive a generic non-enumerating response before sign-in. Public access must be an explicit authenticated Public grant: PartitionAccessPolicy.PublicRead would also expose the page anonymously and is unsuitable. Check ancestor grants and all alternative read surfaces.

Authentication, prefills and sites

  1. Resolve the link only against the configured origin/site registration. Never trust Host, forwarded headers, a caller-supplied URL, or a query string as authorization to create redirects.
  2. Store continuation before login and validate it again after callback. Bind it to the auth transaction and destination site; use a short TTL and consume the auth handoff once. Keep the stable link resumable after expiry so the user can restart login safely.
  3. Preserve it through email verification, onboarding, back navigation and refresh. If the account changes, rebind only after a new authenticated flow; do not attach the first user's setup or claim to the second user.
  4. Cross-origin journeys start at the destination site and use its normal identity/SSO process. Never transfer session cookies, viewer names as identity proof, or client secrets between sites.
  5. Shared prefills may contain campaign, locale and a validated public course reference. Name/email come from the current authenticated viewer's own profile and are editable. Never place the sender's personal details in a reusable QR link.
  6. A recipient-specific invitation is a distinct mode: encrypt/store personal values privately, bind access to the intended verified identity, show no private values to other scanners, and never reuse that invitation as a public phone card.
  7. Revalidate on submission. Price, plan tier, duration, scope, claim budget, redirect and entitlement fields are not editable prefills. Normalize and size-limit text; reject unknown keys and encode all rendered values.

Execution and durable recovery

Navigation and redemption are separate. The initial GUID page may build an allowed prefilled form but does not create a charge, save a payment method or grant access. The user explicitly accepts method storage and activates the offer.

The claim has domain milestones such as Draft, AwaitingSetup, SetupVerified, Activating, Active, DeliveryPending, Expired, Failed, and Cancelled. Ordinary Activities hold execution status/logs. Do not recreate an Activity control plane on the claim; retain only durable domain milestones and correlation identifiers needed for safe retries.

Security and review boundaries

This design introduces a public route and payment-method storage. The implementation issues explicitly require maintainer review of the neutral contracts, issuer policy, recipient access policy, and payment-consent behavior before enabling them. The design does not change current security permissions.

Publishing a coupon journey requires domain-specific authority to distribute that coupon, not merely the ability to run an Activity. Public recipients gain only the right to see the sanitized offer; coupon validation authorizes a tightly scoped system write inside the existing domain fulfillment chain. No public handler can select an arbitrary hub, partition, impersonated identity or C# body.

Server-side throttling covers lookup, signup continuation, setup creation and activation; audit mutation attempts without logging payment secrets or personal prefills. Rate limits return actionable retry states. Consent text/version and the terms accepted are stored per claim, including feature selections, method-storage permission and, only if selected, the recurring mandate. Ordinary free-coupon redemption remains governed by the existing coupon policy until a separately approved migration changes it.

Optional features are registered Store-owned capabilities with versioned payload and consent schema, eligibility predicates, explicit defaults and domain handlers. No arbitrary callback or billing option comes from URL prefills. Personal renewal is the first such feature; adding another feature requires a known handler and test coverage. Selecting renewal must never be smuggled into the payment-method consent under the explanation that some catalog items cost money. The feature checkbox starts unchecked; the design may show its checked state after selection. The final CTA repeats the zero-today amount and future price/date. A new price or shortened term invalidates prior acceptance and returns the recipient to review.

Verification and release criteria

Area Required evidence
Access Anonymous cannot read card/offer data by any route; a fresh signed-in, unentitled user can see only the sanitized offer; private claims and publisher controls remain denied
Authorization Unauthorized publisher, altered handler/version/site, private target, tampered prefill and open redirect all fail without side effects
Authentication Existing account, new account, email verification, onboarding, expired continuation, changed account, cross-site callback and standalone PWA all resume the correct link
Activity durability Publish once under concurrent retries; historical Open QR card works after kernel unload, process restart and module refresh
Execution GET, crawler preview and scan do not mutate; duplicate submits/webhooks and crash recovery produce one bounded grant and one install
Phone Actual iPhone Safari/home-screen and Android Chrome/home-screen scans, login, install guidance, offline recovery and camera readability pass
Payments No activation charge in either branch; default setup creates no invoice/subscription; opted-in renewal creates only the explicitly authorized zero-due trial and future schedule; separate purchases need fresh confirmation; all available method paths and card fallback are tested
Boundaries Expiry, cap, existing subscriber, failed setup, revoked link, invalid coupon, failed install and unavailable handler have explicit outcomes

Implement core contracts first through the supported platform CD pipeline, then registry-delivered plugin consumers and clients. Validate node shape, compile all touched types and run their Tests areas on a local mesh; run required PR checks and review. Production verification uses a dedicated authorized test campaign and Stripe test mode before any reviewed live smoke test. Never consume the real COURSES3M offer or charge a real person as an unannounced test.

Decisions still needed before enabling the feature

References

Reconnecting…
The connection to the server was interrupted. Trying to restore it…
Trying again…
The connection could not be restored. Reloading the page…
The server was updated. Reloading the page to pick up the latest version.