Sync fluent SDK to OpenAPI spec: catalogue lifecycle, checkout locale, customer recovery, test helpers - #96
Conversation
…locale, customer recovery, test helpers) Expose the new api-php surface (PR #56, released as alpha.25) through the fluent SDK's own idioms: - Catalogue management: OneOffProductService + SubscriptionPlanService via Vatly::oneOffProducts() / ::subscriptionPlans() (create/find/update/archive/ unarchive/list). Returns api-php resources carrying the new fields taxBehavior, productType, archivedAt, pendingUpdates, updateStatus. - Checkout locale: withLocale() on CheckoutBuilder and SubscriptionBuilder. - Subscription scheduledUpdate: scheduledUpdate() / hasScheduledUpdate() on SubscriptionHandle (live GET; untyped stdClass|null, mirroring api-php). - Customer recovery: CustomerService::findByEmail() / findOneByEmail() backed by a new ListCustomersByEmail action, plus Vatly::listCustomersByEmail(). - Test helper: Vatly::testHelpers() -> TestHelpers with advanceRenewal / forceRenewalPaid / forceRenewalFailed(reason) over fast-forward-renewal. - Webhook: the 10 catalogue events flow through the WebhookProcessor and are recorded + dispatched as UnsupportedWebhookReceived (typed DTOs deferred, blocked on the vatlify spec gap - same as api-php). Bumps vatly/vatly-api-php constraint to ^0.1.0-alpha.25.
CI failed at composer install: the required ^0.1.0-alpha.25 base client is not published yet (max is alpha.24). Point the dependency at api-php PR #56's branch (sync/openapi-2026-08) via a VCS repository + inline alias so CI can install and validate against the real new surface across all PHP versions. TEMPORARY: revert to the plain "^0.1.0-alpha.25" constraint (and drop the repositories entry) once vatly-api-php #56 is merged and released as alpha.25.
|
Before this leaves draft / merges: revert |
…eleased) vatly/vatly-api-php v0.1.0-alpha.25 is released and ships typed webhook DTOs for the 10 catalogue events, so the fluent WebhookProcessor (which delegates to api-php's WebhookEventFactory) now surfaces them typed automatically: - OneOffProductUpdateSubmitted / UpdateApproved / UpdateRejected / Archived / Unarchived (carry oneOffProductId, testmode, oneOffProduct resource) - SubscriptionPlanUpdateSubmitted / UpdateApproved / UpdateRejected / Archived / Unarchived (carry subscriptionPlanId, testmode, subscriptionPlan resource) Changes: - Revert the CI scaffold in composer.json: drop the vcs `repositories` entry and restore the plain Packagist constraint `^0.1.0-alpha.25`. - Rewrite the WebhookProcessor data-provider test: the 10 catalogue events now assert the typed DTOs instead of UnsupportedWebhookReceived. - README: document the typed catalogue events; drop the deferral note. composer test: 270 passing. composer analyse (phpstan L6): clean.
|
Un-drafted: the release gate is cleared. alpha.25 also ships typed webhook DTOs for the 10 catalogue events (
|
…elpers (#68), doc effectiveAt Doc fix — scheduledUpdate field list: - Add `effectiveAt` (the next-renewal date the change applies; nullable) to the ScheduledSubscriptionUpdate field enumerations in README.md and docs/webhook-flow.md, matching the OpenAPI spec's required set. #95 — catch (VatlyException) now covers API-layer errors: - Add Vatly\Fluent\Exceptions\ApiCallFailedException (extends RuntimeException, implements VatlyException); wraps api-php's ApiException as $previous, preserving code + message, with apiException() to reach the original. - Add Concerns\GuardsApiCalls trait; route every fluent->api-php boundary through it: all BaseAction subclasses (incl. CreateCustomer's non-duplicate path), the catalogue services (create/find/update/archive/unarchive/list), and testHelpers(). SubscriptionHandle is covered transitively via its actions. - Vatly::customerIdFromCheckout() now catches ApiCallFailedException|ApiException (code preserved), so its 404->null behaviour holds for both wrapped (prod) and bare (mocked) exceptions. - Document on the VatlyException interface that API transport/HTTP errors are included; add an "Error handling" section to the README. #68 — customer name/email read + update helpers (api-php prerequisite shipped): - Add Vatly\Fluent\Data\UpdateCustomerData DTO (name?, email?; toPayload strips nulls, per the repo's DTO convention). - CustomerService::update() now accepts UpdateCustomerData|array; add CustomerService::identity() read-back (name + email) over the Customer resource. - README documents both. composer stays on released ^0.1.0-alpha.25 (no scaffold). composer test: 286 passing (was 270). composer analyse (phpstan L6): clean.
|
Follow-up pushed ( Doc — #95 — #68 — customer identity read + update (api-php prerequisite shipped in alpha.25): new composer stays on released Note on #40 (CustomerHandle): left open. This work adds identity ops on |
…tionUpdate (alpha.26) vatly/vatly-api-php v0.1.0-alpha.26 makes Subscription::$scheduledUpdate a typed ScheduledSubscriptionUpdate (all 8 fields, incl. effectiveAt), replacing the prior stdClass passthrough. - Bump the constraint to ^0.1.0-alpha.26. - SubscriptionHandle::scheduledUpdate() now returns ?ScheduledSubscriptionUpdate instead of ?object; hasScheduledUpdate() unchanged (null check). - Update the tests to build the typed DTO and assert the class + effectiveAt. - README: scheduledUpdate() described as typed (was "untyped stdClass|null"). composer test: 286 passing. composer analyse (phpstan L6): clean.
Merchants say "Products" / "Manage Products", not the internal "catalogue" jargon. Since #96 is pre-release (nothing shipped), rename the developer-facing namespace too for consistency; the public accessors ($vatly->oneOffProducts() / ->subscriptionPlans()) already used product names and are unchanged. - Namespace Vatly\Fluent\Catalogue -> Vatly\Fluent\Products (git mv of src/Catalogue -> src/Products and tests/Catalogue -> tests/Products; updated namespaces + `use` statements + test namespaces). - README + docs: "Catalogue management" -> "Managing products", "catalogue events" -> "product events", and all other catalogue/catalog wording. - Reword catalogue mentions in docblocks (Vatly, the two services, GuardsApiCalls, VatlyException). - Rename the webhook test's catalogueEventProvider -> productEventProvider and test_it_dispatches_typed_catalogue_events -> ..._typed_product_events. Webhook event name strings (one_off_product.*, subscription_plan.*) are the API contract and are untouched. composer test: 286 passing. composer analyse (phpstan L6): clean.
Wraps the new api-php surface in the fluent SDK's own idioms, delegating to the
updated base client and reconciled against the authoritative
openapi.bundled.yaml. Follows api-php's lead on what is typed vs. untyped.What's implemented, by feature
Catalogue management
Vatly\Fluent\Catalogue\OneOffProductServiceandSubscriptionPlanService,reachable via
$vatly->oneOffProducts()/$vatly->subscriptionPlans()(api-only, no
Wiringrequired). Each exposescreate,find,update,archive,unarchive,list, delegating to api-php's endpoints and returningthe hydrated
OneOffProduct/SubscriptionPlanresources.taxBehavior,productType,archivedAt,pendingUpdates,updateStatus(plusisArchived()), all fromapi-php - the fluent services surface them by returning the real resource.
thin composed service (like
CustomerService) delegating directly to theclient endpoint keeps the surface discoverable without a per-op
Actionexplosion. Tested by mocking the endpoint on the client (the repo's existing
pattern, cf.
UpdateCustomerTest).Checkout locale
withLocale(?string)onCheckoutBuilderandSubscriptionBuilder(threaded into the checkout payload). Accepts a bare code / BCP 47 tag / POSIX
locale; omitted by default so the checkout detects from the browser.
Subscription
scheduledUpdatescheduledUpdate(): ?objectandhasScheduledUpdate(): boolonSubscriptionHandle(liveGET). Untyped (stdClass|null), mirroringapi-php's
Subscription::$scheduledUpdate- see deferral note.Customer recovery
CustomerService::findByEmail()(returns theCustomerCollection) andfindOneByEmail()(first match ornull), backed by a newListCustomersByEmailaction andVatly::listCustomersByEmail().Test helper: fast-forward-renewal forced outcome
$vatly->testHelpers()->Vatly\Fluent\TestHelperswithadvanceRenewal(),forceRenewalPaid(),forceRenewalFailed(?string $failureReason), and ageneral
fastForwardRenewal($id, $body)passthrough - over api-php'stestHelpers->fastForwardSubscriptionRenewal($id, [...]).Webhooks: 10 new catalogue events
one_off_product.*/subscription_plan.*events (update_submitted,update_approved,update_rejected,archived,unarchivedx2) flow throughthe
WebhookProcessorunchanged: they are recorded and dispatched asUnsupportedWebhookReceived(the api-php factory's fallback). No productioncode change was needed; a data-provider test locks the behaviour in.
Deferred / checklist
WebhookEvent.objectoneOf/discriminator omitsOneOffProduct/SubscriptionPlaneven though theeventNameenum includes the 10 events, sothere is no spec-defined payload to type against. Deliberately left untyped in
all three layers (api-php, here, laravel) so typed handling can light up later
without breaking consumers who match on
$event->eventNamenow.scheduledUpdate/pendingUpdates- same spec gap; surfaced asfluent-accessible but untyped
stdClass|null, matching api-php.Dependency wiring
Bumps
vatly/vatly-api-phpfrom^0.1.0-alpha.24to^0.1.0-alpha.25(therelease that will carry api-php #56). Locally this was built against a
pathrepository symlinking the api-php
sync/openapi-2026-08branch; that localrepo/
*@devconstraint was reverted before committing (only the genuine versionbump is committed).
composer.lockis gitignored, so nothing pins the symlink.Tests & quality
composer test- 270 passing (up from 228), 623 assertions.composer analyse(phpstan level 6) - no errors.