Skip to content

Referrals & resell assistance

Once a shopper has bought a unit they enter the Pre-Delivery owner area — a post-purchase suite that also lets them do two outbound things: refer a buyer to ODH, and request help reselling a unit they own. Both are handled by the shopper app, both go out through crms-middleware to the CRM, and both leave the same kind of local footprint: a row in user_requests (App\Models\UserRequest, app/Models/UserRequest.php:11) tagged with a request_type from UserRequestsEnums. The CRM record is the authoritative one — the portal keeps only a thin analytics mirror.

sequenceDiagram
actor OW as Owner (Shopper)
participant SPA as Pre-Delivery SPA
participant API as Backend (Laravel)
participant MW as crms-middleware
participant CRM as CRM
participant DB as Portal DB
Note over OW,DB: Flow A — Refer a buyer
OW->>SPA: Fill referral form (buyer name, phone, unit, way to share)
SPA->>API: POST leads with portal_page = REFERRAL_PRE_DELIVERY
API->>MW: POST /shopper/leads (is_referral computed)
MW->>CRM: Create lead, flagged is_referral
CRM-->>API: 200
API->>DB: SaveUserRequest → UserRequest request_type = referral
API-->>SPA: 200 mapped response
Note over OW,DB: Flow B — Request resale assistance
OW->>SPA: Fill resell form (asking price, currency)
SPA->>API: POST units resell-assistance (authed)
API->>MW: POST /resell-assistance
MW->>CRM: Create resale request
CRM-->>API: 200
API->>DB: SaveUserRequest → UserRequest request_type = unit_resale
API-->>SPA: 200 mapped response
Referrals and resell — owner submits, CRM records, portal mirrors as UserRequest

The shape is the same for both: submit to the CRM first, then — only on an HTTP 200 — write the local UserRequest mirror. If the CRM call is non-200, no local row is written and the request never shows up in the portal’s analytics or owner views (LeadController.php:42, CustomerRequestController.php:48, :81).

  1. Route. POST leadsLeadController@submitLead (routes/api/shopper.php:86). This route sits outside the auth:sanctum group that starts at routes/api/shopper.php:93 — it is scoped only by user-provider:shoppers (the whole file, :9). So it is token-optional: the controller reads $shopper = Auth::user() and stores user_id => $shopper?->id, null-safe (LeadController.php:29, :46).
  2. Validation. SubmitLeadRequest carries the referral fields: referral_unit_name, referral_customer, referral_customer_name, referral_unit_destination, and referral_way_to_share_unit (constrained to ReferralWayToShareUnitEnums), plus a required is_predelivery boolean and an optional portal_page (SubmitLeadRequest.php:37-42).
  3. CRM submit. ShopperSubmitNewLead::handle POSTs the prepared payload to {crms-middleware}/shopper/leads with the country header resolved from the destination (ShopperSubmitNewLead.php:16-31). It computes is_referral = referral_customer && referral_unit_name (:41) — this is what marks the lead as a referral CRM-side.
  4. Local mirror (on 200 only). request_type starts as LEAD, then is promoted to REFERRAL when portal_page == PortalPages::REFERRAL_PRE_DELIVERY (value 5, app/Enums/PortalPages.php:15; promotion at LeadController.php:48-51). country_code is derived from the phone number (:43-44), UTM fields are stripped (:52-54), and the row is persisted by SaveUserRequest (app/Actions/UserRequest/SaveUserRequest.php:12-21), which also syncs the destination pivot.
  1. Route. POST units/{publishedUnitShopper}/resell-assistanceCustomerRequestController@submitResellAssistance (routes/api/shopper.php:140-143). Unlike the referral path, this route is inside the auth:sanctum + ability:ACCESS_SHOPPER_API group (:93-96), so the owner must be logged in.
  2. Validation. SubmitResellAssistanceRequest accepts only customer_source_id, asking_price, and currency_source_id (SubmitResellAssistanceRequest.php:11-15).
  3. CRM submit. SubmitResellAssistance::handle resolves the currency_code from currency_source_id + the unit’s country, then POSTs to {crms-middleware}/resell-assistance with the country header (app/Actions/CustomerRequests/SubmitResellAssistance.php:18-45).
  4. Local mirror (on 200 only). formatUserRequestData builds the row with request_type = unit_resale, unit_type_id = unit->unit_type_id, and the unit’s destination_slug for the pivot; SaveUserRequest persists it (CustomerRequestController.php:64-107).

Every one of these lands as a user_requests row (table renamed from lead_requests in database/migrations/2024_09_18_125025_rename_requests_tables.php:19,26-29). The request_type column defaults to lead; service_type is nullable. Only the four values below exist (app/Enums/UserRequestsEnums.php:10-13):

Value Enum case How it is created CRM endpoint
lead LEAD POST leads (default request_type) /shopper/leads
referral REFERRAL POST leads with portal_page = REFERRAL_PRE_DELIVERY /shopper/leads (lead flagged is_referral)
unit_service UNIT_SERVICE POST units/{publishedUnitShopper}/customer-requests /customer-requests
unit_resale UNIT_RESALE POST units/{publishedUnitShopper}/resell-assistance /resell-assistance