v0.6.50#251
Merged
Merged
Conversation
Introduces a B2C customer surface directly in FleetOps so portals can
authenticate end-customers with the existing flb_live_… API credential —
no Storefront publishable-key + Store/Network coupling required.
Endpoints (all under /v1/customers/...):
Public (API credential only):
POST request-creation-code send email/SMS verification code
POST / create Contact+User after verifying code
POST login email/phone + password → Sanctum token
POST login-with-sms send login code (SMS, falls back to email)
POST verify-code verify code → Sanctum token
POST forgot-password send reset code
POST reset-password verify reset code + set new password
Authenticated (require Customer-Token):
GET me / PUT me profile read/update (mirrors to linked User)
POST logout / logout-all revoke current / all tokens for this user
GET orders scoped Order::where('customer_uuid', …)
POST orders create freight order with customer_uuid set
GET orders/{id} owner-checked order detail
GET places customer's saved Places
POST register-device push-token registration for linked User
Implementation:
- Tokens are Sanctum PersonalAccessToken with `name` = Contact UUID
(matches Storefront convention so SDKs and headers are interchangeable).
- AuthenticateCustomerToken middleware verifies the Customer-Token header
and cross-checks the resolved Contact's company_uuid against the API
credential's session('company'); 401/403 on mismatch.
- CustomerAuth helper resolves the token with a company-preferred
fallback for the multi-company edge case.
- Customer model is a thin Contact specialization with type=customer.
- Verification slugs are fleetops_* (create_customer, customer_login,
customer_password_reset) — no Storefront slugs reused.
- No new tables, no migrations: contacts.email/phone/user_uuid,
orders.customer_uuid, and personal_access_tokens already cover it.
- OAuth providers (Apple/Google/Facebook) deferred to a follow-up.
Static-shape tests live in server/tests/CustomerEndpointTest.php and
follow the package's existing pest convention. End-to-end HTTP tests
belong in the parent api/ harness.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related bugs in the freshly-added v1/customers endpoints surfaced on
first live test (HAR shows: 400 from /v1/customers/request-creation-code
with "Attempt to read property 'name' on null (View:
.../mail/verification.blade.php)").
Bug 1: requestCreationCode passed an unsaved Contact as the verification
subject, so the morphTo subject_uuid was null. The verification mail
template references {{ $user->name }} via the morphTo relation, which
resolves null → fatal in blade.
Fix: look up an existing User by identity, or stub-create one with
`name = "Pending Customer"`. The stub gives the mail renderer a real
record to greet, and create() backfills name + password on verification.
Bug 2: `password` and `type` are guarded on the User model, so
`User::create([... 'password' => ..., 'type' => 'customer'])` silently
dropped both fields. Customers created via signup would therefore have
no password and no type — login would fail.
Fix: assign password via `$user->password = $plaintext` (the model's
setPasswordAttribute mutator hashes) and set type via `setUserType()`.
Also stop double-hashing in resetPassword (was Hash::make then mutator).
Also makes create() idempotent on the Contact: reuse the existing
customer-Contact for (user, company) instead of crashing on the second
signup attempt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The signup form already collected the customer's name (and phone) by the
time it asked for a verification code. Plumb those through so:
- the verification email greets the customer by their real name
- the pre-created User row holds real values instead of the
"Pending Customer" placeholder
- create() doesn't need to overwrite stub values on confirmation
VerifyCreateCustomerRequest now accepts optional `name` and `phone`;
requestCreationCode uses them when stub-creating the User, and refreshes
the row's name when an existing pending stub is re-prompted.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When `POST /v1/customers` receives a home-address payload (either as a
top-level `address` object or nested under `meta.address`), create a
Place record:
- company_uuid = the API credential's company
- owner_uuid / owner_type = the new customer Contact (polymorphic)
- type = "residential"
- Both Storefront-style (street1/province/postal_code) and
portal-form-style (line1/state/zip) keys are accepted.
Then link the Place to the customer via Contact.place_uuid so it appears
as the default address and `GET /v1/customers/places` returns it.
Idempotent: only creates a Place when the Contact has no place_uuid set
and the payload has at least one usable address field.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This is a public Fleetbase API surface — it must follow established model field shapes, not invent client-specific aliases. Audited and removed every divergence I'd introduced: - `create()` and the `place` field — drop the line1/line2/state/zip aliases. Accept only the canonical Place fillable shape (street1, street2, city, province, postal_code, neighborhood, district, building, country, phone, meta) or an existing Place public_id. Field whitelist via `array_intersect_key` so unknown keys are silently ignored. - Drop `meta.origin = 'fleetops_customer_portal'` invention from create(), login(), and verifyCode(). Storefront uses meta.storefront_id legitimately; FleetOps customers don't need a portal-specific tag. - `createOrder()` — full rewrite to mirror `OrderController::create`'s canonical Order shape. Accepts the same fields the operator API does: type / order_config / scheduled_at / notes / meta / internal_id, and either a `payload` (object or public_id) or top-level pickup / dropoff / return / waypoints / entities. No top-level `item`, `weight`, `value`, `mode`, `delivery`, or `category` aliases — clients translate their form shapes into entities + meta before calling. The customer endpoint *forces* customer_uuid from the Customer-Token, status='created', and ignores any client-supplied customer / driver / vehicle / facilitator / dispatch fields. Payload-building delegates to `Payload::setPickup` / `setDropoff` / `setEntities` so customer-created orders are indistinguishable from operator-created ones at the data layer. - CreateCustomerOrderRequest validates only the canonical fields. - CustomerEndpointTest gains a "no client-portal field aliases" assertion that fails the build if any of those forbidden patterns reappear in the controller source, plus a "createOrder mirrors the canonical Fleet-Ops order shape" assertion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Make the principle explicit at the only meta write that future contributors might be tempted to touch: customer creation. `meta` is client-owned; controller-side stamps are only justified when the backend itself reads that data back (e.g. Storefront's `meta.storefront_id` for query scoping). The customer surface has no such backend need. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two additive, canonical extensions to the public Fleet-Ops API surface,
both convention-aligned with existing resources:
1. OrderConfigs as a first-class read-only public resource (mirrors how
Places, Vendors, Contacts, Orders are already exposed):
GET /v1/order-configs — list configs for the company
GET /v1/order-configs/{id} — find by uuid|public_id|namespace|key
`find()` defers to `OrderConfig::resolveFromIdentifier()` so callers
can use `/transport`, the namespace, the public_id, or the uuid.
A new `v1/OrderConfig` resource projects only the public-safe shape:
id, key, name, namespace, description, tags, status, version, and the
activity `flow[]` carrying `{code, status, details, color, complete,
pod_method, require_pod}`. Internal-only fields (raw entities JSON,
flow logic blocks) are filtered out so the public response is small,
safe, and useful for drivers/portals/integrations that need to render
status chips and activity labels from the canonical config.
2. `company` sub-object on the `Customer` resource (returned by
/v1/customers/me, /login, /signup, /verify-code, etc.). Resolves
currency through the existing canonical helper
`Utils::getCompanyTransactionCurrency()` which already does the
`companies.currency` → ledger `base_currency` → "USD" chain. The
sub-object exposes id, name, currency, country, phone — same fields
any caller could already discover via other channels, just bundled
conveniently so authenticated customer apps don't need a separate
request to render currency labels or contact info.
No new write surface, no new auth requirements, no client-portal
aliases. Both endpoints require only the public Fleet-Ops API key, like
the existing /v1/tracking-numbers/{n} public read endpoint.
Static-shape tests assert:
- order-configs routes register both methods
- OrderConfigController exposes only read-only methods (no create/
update/delete on the public surface)
- OrderConfig resource emits `flow[]` with the canonical keys and
never exposes the raw entities JSON
- Customer resource exposes `company` and uses
`Utils::getCompanyTransactionCurrency`
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an optional `service_quote` field to the customer order create
endpoint, matching how `OrderController::create` resolves quotes:
- CreateCustomerOrderRequest accepts a `service_quote` string (uuid or
`sqte_…` public_id).
- `createOrder()` resolves it via `ServiceQuote::resolveFromRequest`
and calls `$order->purchaseServiceQuote()` after creation so the
PurchaseRate is locked onto the order with the quoted pricing.
This lets customer portals pull live quotes from
`GET /v1/service-quotes`, present them to the customer, and submit the
chosen one when creating the order — same flow operators use.
No write-surface changes beyond accepting one more optional field; all
existing tests + canonical-shape assertions still hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add public customer API + OrderConfig resource + Customer.company sub-object
…rvice-rates Add multi-zone distance service rates
…dicators Add required indicators to order form
…ening-v0.6.50 Harden live map viewport loading
…ehicle-status Use available driver and vehicle status defaults
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.