Gifting Runtime and API

The Gifting module is a source-neutral runtime for native Payments gifts and post-purchase membership transfers. It intentionally keeps one canonical write owner and lets Payments, Bricks forms, and supported commerce integrations delegate into the same mutation contract.

Module Boundary

  • Owned by Gifting: gift mode resolution, gift writes, gift history reads, shared gift schema, claim flow, source-transfer markers, and recipient account provisioning handoff
  • Owned by Payments: native checkout UI, offer and provider configuration, normalized billing ingest, and provider webhook handling
  • Owned by source integrations: WooCommerce, SureCart, and FluentCart lifecycle hooks plus source-object metadata persistence
  • Not supported: generic webhook gifting

Canonical Owners

  • GiftWriteService — canonical write owner for native gift rows, gift claims, and non-native source transfers
  • GiftHistoryReadService — shared-table read owner for admin history and native lookup reads
  • GiftModeResolver — resolves lightweight transfer mode vs subscription and history mode and normalizes shared gifting settings
  • GiftSchemaService — creates and verifies the shared brm_gifts table when table mode is required
  • GiftClaimController — public adapter for the native claim route and login handoff only; it is not a second write owner
  • UserProvisioningService — duplicate-safe recipient account creation by email

Modes

The gifting runtime has two effective operating modes:

  • Lightweight transfer mode — Gifting active, Payments inactive, and brm_gifting_settings['subscription_history_mode'] disabled. One-time source transfers work without the shared gift table.
  • Subscription and history mode — Payments active or subscription_history_mode enabled. Native gifts, non-native subscription transfers, and stored transfer history all use wp_brm_gifts.

Do not add a third write path around these modes. Mode switching is a read of shared settings, not a different business contract.

Entry Points

  • BootstrapCoordinator to GiftingFeatureRegistrar::boot()
  • Settings save path to SettingsFormHandler to AdminSettingsService
  • Payments admin gifting settings to PaymentsAdminPage to SettingsFormHandler::handle_payments_admin_post() to AdminSettingsService::handle_payments_admin_action()
  • Native checkout request through PaymentsRestRegistrar to PaymentsCheckoutController, which validates the gift payload and later hands off to GiftWriteService after billing persistence
  • Bricks transfer action to GiftTransferFormIntegration to GiftWriteService::transfer_source_entitlement()
  • Public claim route to ?brm_action=claim_gift&gift_token=... to GiftClaimController to GiftWriteService::claim_native_gift()

Storage Contract

Shared settings and module flags:

  • brm_enable_gifting
  • brm_gifting_settings['subscription_history_mode']
  • brm_gifting_settings['create_recipient_users']
  • brm_gifting_settings['recipient_default_role']

Native Payments gifting also reads:

  • brm_payments_settings['gifting']
  • brm_payments_offers[$offer_key]['gifting']

Shared table: {$wpdb->prefix}brm_gifts

  • one current-state row per native gift or source transfer in table mode
  • gift_mode discriminates native_checkout vs source_transfer
  • query columns include source identity, payment or subscription refs, offer and level, purchaser, recipient, claim timestamps, transfer timestamps, and context_json
  • context_json is for non-query extras only and must not become canonical lookup truth

Source-object compatibility markers for non-native transfers:

  • _brm_gift_level_id
  • _brm_gift_recipient_user_id
  • _brm_gift_recipient_email
  • _brm_gift_transferred_at

Native Payments Handoff

Native checkout gifting is not a second storage system inside Payments. The runtime path is:

  1. PaymentsRestRegistrar registers the hosted checkout route (POST /bricksmembers/v1/billing/checkout) and the embedded checkout route (POST /bricksmembers/v1/billing/create-embedded-checkout), then PaymentsCheckoutController validates the incoming gift payload.
  2. The provider completes checkout and the billing adapters normalize the payment or subscription mutation.
  3. BillingOrchestratorService persists normalized billing data first.
  4. After that persistence succeeds, the gifting handoff calls GiftWriteService to create or update the native gift row.
  5. BillingAccessSyncService suppresses purchaser access for unclaimed native gifts until claim succeeds.

Do not move native gift schema or write ownership into BillingSchemaService. Payments is an adapter here, not the gift owner.

Non-Native Source Transfers

GiftWriteService::transfer_source_entitlement() is the canonical non-native transfer path.

Since 0.9.94, GiftTransferContextReadService provides a read-only Bricks context for transfer templates. It resolves the selected source object from brm_gift_source, brm_gift_object_type, and brm_gift_object_id, validates it belongs to the current user, and feeds {brm_gift_transfer:*} dynamic tags. It does not mutate transfer state.

  • It validates the current purchaser user.
  • It resolves the source object and requires exactly one mapped BRM level.
  • It rejects same-email transfers and duplicate transfers.
  • It finds or creates the recipient user through UserProvisioningService.
  • It adds the level to the recipient, removes the same level from the current holder, writes source markers, and optionally writes a shared history row in table mode.

Subscription-based source transfers are rejected when subscription and history mode is disabled.

Claim Flow

Native gift claims use one public route:

?brm_action=claim_gift&gift_token={token}

GiftClaimController owns route handling, login handoff, queued post-login redirects, and success or error redirect parameters. It does not own the claim mutation. The actual mutation is GiftWriteService::claim_native_gift().

Claim validation includes payment state, expiry, recipient-email matching, and gifted level resolution before the recipient level is granted.

Since 0.9.94, Bricks claim templates can read native gift claim context through {brm_gift:*} dynamic tags. The tag renderer is read-only: it resolves gift rows through GiftHistoryReadService, derives offer labels through BillingSourceConfigService, and reads claim result messages from brm_gift_claim/brm_gift_claim_code query arguments. Since 0.9.95, {brm_gift:claim_handoff_pending} returns 1 only while the current logged-out request has a matching server-side claim handoff, so login/register templates can conditionally show preserved-claim messaging without exposing the claim token.

Email and User Provisioning Rules

  • Native claim emails are sent from GiftWriteService through EmailTransportService.
  • When a recipient account is created automatically, wp_send_new_user_notifications() sends the standard WordPress user email.
  • UserProvisioningService is duplicate-safe and does not overwrite existing-user role or profile data unless explicitly asked.

Hooks and Side Effects

  • brm_gift_created
  • brm_gift_paid
  • brm_gift_claimed
  • brm_gift_transferred
  • brm_gift_claim_email_sent

Bricks Integration

The gifting-specific Bricks surface is intentionally small:

  • GiftTransferFormIntegration registers the brm_transfer_gift_level form action.
  • BRM Checkout Context exposes native gift fields through gift rows inside the canonical BRM Checkout Fields child element when gifting is available for the resolved offer and provider combination.
  • GiftingBricksIntegration registers brmUserGiftsQuery for sent/received gift history and brmTransferableAccessQuery for transferable source contexts. Both queries are current-user scoped and expose status/scope/limit controls as applicable.

Neither surface owns business writes. They validate or collect input and then delegate into GiftWriteService.

Get BricksMembers

Start Building Your Membership Site Today

Create, sell, and manage your content without limits. BricksMembers gives you everything you need to build membership and LMS sites with Bricks Builder.

Lifetime updates & bug fixes • Premium support • 0% transaction fees • 60-day money-back guarantee