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_giftstable 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_modeenabled. Native gifts, non-native subscription transfers, and stored transfer history all usewp_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
SettingsFormHandlertoAdminSettingsService - Payments admin gifting settings to
PaymentsAdminPagetoSettingsFormHandler::handle_payments_admin_post()toAdminSettingsService::handle_payments_admin_action() - Native checkout request to
PaymentsRestController, which validates thegiftpayload and later hands off toGiftWriteServiceafter billing persistence - Bricks transfer action to
GiftTransferFormIntegrationtoGiftWriteService::transfer_source_entitlement() - Public claim route to
?brm_action=claim_gift&gift_token=...toGiftClaimControllertoGiftWriteService::claim_native_gift()
Storage Contract
Shared settings and module flags:
brm_enable_giftingbrm_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_modediscriminatesnative_checkoutvssource_transfer- query columns include source identity, payment or subscription refs, offer and level, purchaser, recipient, claim timestamps, transfer timestamps, and
context_json context_jsonis 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:
PaymentsRestControllervalidates the incominggiftpayload on the hosted checkout route (POST /bricksmembers/v1/billing/checkout) and the embedded checkout route (POST /bricksmembers/v1/billing/create-embedded-checkout).- The provider completes checkout and the billing adapters normalize the payment or subscription mutation.
BillingOrchestratorServicepersists normalized billing data first.- After that persistence succeeds, the gifting handoff calls
GiftWriteServiceto create or update the native gift row. BillingAccessSyncServicesuppresses 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.
- 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.
Email and User Provisioning Rules
- Native claim emails are sent from
GiftWriteServicethroughEmailTransportService. - When a recipient account is created automatically,
wp_send_new_user_notifications()sends the standard WordPress user email. UserProvisioningServiceis duplicate-safe and does not overwrite existing-user role or profile data unless explicitly asked.
Hooks and Side Effects
brm_gift_createdbrm_gift_paidbrm_gift_claimedbrm_gift_transferredbrm_gift_claim_email_sent
Bricks Integration
The gifting-specific Bricks surface is intentionally small:
GiftTransferFormIntegrationregisters thebrm_transfer_gift_levelform action.BillingCheckoutElementexposes native gift fields when gifting is available for the resolved offer and provider combination.
Neither surface owns business writes. They validate or collect input and then delegate into GiftWriteService.
Related
- User post: Gift Memberships and Transfers
- Feature map:
docs/feature-maps/gifting.md - Payments developer post:
docs/dev-posts/23-payments-api.html