From 4fbc3907aa9a367cf2e36ef6b8d422233ade8e4f Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Tue, 21 Jul 2026 17:59:25 -0700 Subject: [PATCH 1/8] refactor(PaymentFlow): extract CreatePaymentFlow and ViewHistoryFlow spokes (SDK-1127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Carves the create-payment and view-history steps out of PaymentFlow into their own hub+spoke flows, mirroring the PayrollFlow/PayrollExecutionFlow pattern: each spoke owns its own robot3 machine and breadcrumb trail, prefixed with the hub's landing breadcrumb, and bubbles terminal events (exit, cancel, landing navigation) back up to the hub. Prepares the fourth spoke slot for the upcoming HistoricalPaymentFlow. Pure refactor โ€” no behavior change, all six original events unchanged. Adds paymentStateMachine.test.ts as a characterization test for the prior flat machine, then splits machine-level unit test coverage across the hub and each new spoke (createPaymentMachine.test.ts, viewHistoryMachine.test.ts). Co-Authored-By: Claude Sonnet 5 --- .reports/embedded-react-sdk.api.md | 20 ++ docs/guides/endpoint-inventory.json | 19 +- docs/guides/endpoint-reference.md | 4 +- docs/reference/blocks.md | 2 + docs/reference/contractor/index.mdx | 2 +- .../management/create-payment-flow.md | 92 ++++++++++ docs/reference/contractor/management/index.md | 2 + .../contractor/management/payment-flow.md | 31 +--- .../management/view-history-flow.md | 94 ++++++++++ docs/reference/index.mdx | 2 +- docs/reference/workflows.md | 2 + .../CreatePaymentFlow/CreatePaymentFlow.tsx | 114 ++++++++++++ .../CreatePaymentFlowComponents.tsx | 44 +++++ .../Payments/CreatePaymentFlow/GUIDE.md | 16 ++ .../createPaymentMachine.test.ts | 78 ++++++++ .../CreatePaymentFlow/createPaymentMachine.ts | 91 +++++++++ .../Payments/CreatePaymentFlow/index.ts | 2 + .../Contractor/Payments/PaymentFlow/GUIDE.md | 17 +- .../Payments/PaymentFlow/PaymentFlow.tsx | 6 +- .../PaymentFlow/PaymentFlowComponents.tsx | 49 ++--- .../PaymentFlow/paymentStateMachine.test.ts | 173 ++++++++++++++++++ .../PaymentFlow/paymentStateMachine.ts | 165 ++++------------- .../Payments/ViewHistoryFlow/GUIDE.md | 16 ++ .../ViewHistoryFlow/ViewHistoryFlow.tsx | 114 ++++++++++++ .../ViewHistoryFlowComponents.tsx | 40 ++++ .../Payments/ViewHistoryFlow/index.ts | 2 + .../viewHistoryMachine.test.ts | 91 +++++++++ .../ViewHistoryFlow/viewHistoryMachine.ts | 76 ++++++++ .../exports/contractorManagement.ts | 2 + 29 files changed, 1162 insertions(+), 204 deletions(-) create mode 100644 docs/reference/contractor/management/create-payment-flow.md create mode 100644 docs/reference/contractor/management/view-history-flow.md create mode 100644 src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlow.tsx create mode 100644 src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlowComponents.tsx create mode 100644 src/components/Contractor/Payments/CreatePaymentFlow/GUIDE.md create mode 100644 src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.test.ts create mode 100644 src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.ts create mode 100644 src/components/Contractor/Payments/CreatePaymentFlow/index.ts create mode 100644 src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.test.ts create mode 100644 src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md create mode 100644 src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx create mode 100644 src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx create mode 100644 src/components/Contractor/Payments/ViewHistoryFlow/index.ts create mode 100644 src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.test.ts create mode 100644 src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.ts diff --git a/.reports/embedded-react-sdk.api.md b/.reports/embedded-react-sdk.api.md index 66bc38594..d4c74983b 100644 --- a/.reports/embedded-react-sdk.api.md +++ b/.reports/embedded-react-sdk.api.md @@ -1773,6 +1773,10 @@ declare namespace ContractorManagement { ContractorTab, PaymentFlow, PaymentFlowProps, + CreatePaymentFlow, + CreatePaymentFlowProps, + ViewHistoryFlow, + ViewHistoryFlowProps, PaymentsList, PaymentsListProps, CreatePayment, @@ -2072,6 +2076,14 @@ type CreatableTimeOffPolicyType = Extract; // @public function CreatePayment(props: CreatePaymentProps): JSX; +// @public +function CreatePaymentFlow(props: CreatePaymentFlowProps): JSX; + +// @public +interface CreatePaymentFlowProps extends BaseComponentInterface { + companyId: string; +} + // @public interface CreatePaymentProps extends BaseComponentInterface<'Contractor.Payments.CreatePayment'> { companyId: string; @@ -6204,6 +6216,14 @@ export type UseWorkAddressFormResult = HookLoadingResult | UseWorkAddressFormRea // @public export type ValidationMessages = Record & Partial>; +// @public +function ViewHistoryFlow(props: ViewHistoryFlowProps): JSX; + +// @public +interface ViewHistoryFlowProps extends BaseComponentInterface { + paymentId: string; +} + // @public function ViewHolidayEmployees(props: ViewHolidayEmployeesProps): JSX; diff --git a/docs/guides/endpoint-inventory.json b/docs/guides/endpoint-inventory.json index 5fb23dd18..702d638e4 100644 --- a/docs/guides/endpoint-inventory.json +++ b/docs/guides/endpoint-inventory.json @@ -2300,16 +2300,27 @@ "EmployeeOnboarding.OnboardingFlow" ] }, - "ContractorManagement.PaymentFlow": { + "ContractorManagement.CreatePaymentFlow": { "blocks": [ "ContractorManagement.CreatePayment", - "ContractorManagement.PaymentHistory", - "ContractorManagement.PaymentStatement", - "ContractorManagement.PaymentSummary", + "ContractorManagement.PaymentSummary" + ] + }, + "ContractorManagement.PaymentFlow": { + "blocks": [ + "ContractorManagement.CreatePaymentFlow", "ContractorManagement.PaymentsList", + "ContractorManagement.ViewHistoryFlow", "InformationRequests.InformationRequestsFlow" ] }, + "ContractorManagement.ViewHistoryFlow": { + "blocks": [ + "ContractorManagement.CreatePayment", + "ContractorManagement.PaymentHistory", + "ContractorManagement.PaymentStatement" + ] + }, "ContractorOnboarding.OnboardingFlow": { "blocks": [ "ContractorOnboarding.Address", diff --git a/docs/guides/endpoint-reference.md b/docs/guides/endpoint-reference.md index 6e2571cb5..c313d9394 100644 --- a/docs/guides/endpoint-reference.md +++ b/docs/guides/endpoint-reference.md @@ -341,7 +341,9 @@ import inventory from '@gusto/embedded-react-sdk/endpoint-inventory.json' | Flow | Blocks included | | --- | --- | -| **ContractorManagement.PaymentFlow** | ContractorManagement.CreatePayment, ContractorManagement.PaymentHistory, ContractorManagement.PaymentStatement, ContractorManagement.PaymentSummary, ContractorManagement.PaymentsList, InformationRequests.InformationRequestsFlow | +| **ContractorManagement.CreatePaymentFlow** | ContractorManagement.CreatePayment, ContractorManagement.PaymentSummary | +| **ContractorManagement.PaymentFlow** | ContractorManagement.CreatePaymentFlow, ContractorManagement.PaymentsList, ContractorManagement.ViewHistoryFlow, InformationRequests.InformationRequestsFlow | +| **ContractorManagement.ViewHistoryFlow** | ContractorManagement.CreatePayment, ContractorManagement.PaymentHistory, ContractorManagement.PaymentStatement | | **ContractorOnboarding.OnboardingFlow** | ContractorOnboarding.Address, ContractorOnboarding.ContractorList, ContractorOnboarding.ContractorProfile, ContractorOnboarding.ContractorSubmit, ContractorOnboarding.NewHireReport, ContractorOnboarding.PaymentMethod | | **ContractorOnboarding.SelfOnboardingFlow** | ContractorOnboarding.Address, ContractorOnboarding.ContractorProfile, ContractorOnboarding.DocumentSigner, ContractorOnboarding.Landing, ContractorOnboarding.OnboardingSummary, ContractorOnboarding.PaymentMethod | diff --git a/docs/reference/blocks.md b/docs/reference/blocks.md index f7fbc30c6..0a052c09c 100644 --- a/docs/reference/blocks.md +++ b/docs/reference/blocks.md @@ -252,6 +252,8 @@ shape mixed into every public SDK feature component. - [`SignatureFormProps`](contractor/onboarding/blocks.md#signatureformprops) - [`ManagementContractorListProps`](contractor/management/blocks.md#managementcontractorlistprops) - [`PaymentFlowProps`](contractor/management/payment-flow.md#paymentflowprops) +- [`CreatePaymentFlowProps`](contractor/management/create-payment-flow.md#createpaymentflowprops) +- [`ViewHistoryFlowProps`](contractor/management/view-history-flow.md#viewhistoryflowprops) - [`PaymentsListProps`](contractor/management/blocks.md#paymentslistprops) - [`CreatePaymentProps`](contractor/management/blocks.md#createpaymentprops) - [`PaymentHistoryProps`](contractor/management/blocks.md#paymenthistoryprops) diff --git a/docs/reference/contractor/index.mdx b/docs/reference/contractor/index.mdx index a6ed387ae..d2dabbb3d 100644 --- a/docs/reference/contractor/index.mdx +++ b/docs/reference/contractor/index.mdx @@ -34,7 +34,7 @@ Flows and blocks for managing contractors after onboarding โ€” payments, payment import { ContractorManagement } from '@gusto/embedded-react-sdk' ``` - + --- diff --git a/docs/reference/contractor/management/create-payment-flow.md b/docs/reference/contractor/management/create-payment-flow.md new file mode 100644 index 000000000..7b6334747 --- /dev/null +++ b/docs/reference/contractor/management/create-payment-flow.md @@ -0,0 +1,92 @@ +--- +# Autogenerated by TypeDoc from TSDoc comments in the source code. +# To update content: edit TSDoc comments in src/. +# To update structure: edit docs-site/typedoc.config.ts or docs-site/plugins/typedoc-custom/. +# Then run `npm run docs:api:generate` to regenerate. +title: CreatePaymentFlow +description: CreatePaymentFlow reference. +sidebar_position: 2 +generated_by: typedoc +custom_edit_url: null +--- + +# CreatePaymentFlow + +Guided flow to create a contractor payment and review the resulting summary. + +## Remarks + +This is the inner flow that powers the create-payment spoke of `ContractorManagement.PaymentFlow`. +Render it directly when you have built your own payments landing page and want to hand the user +off to the standard create-payment experience without re-implementing it. The flow ships with +breadcrumb navigation and handles Fast ACH blockers and wire transfer requirements inline. + +## Example + +```tsx title="App.tsx" +import { ContractorManagement } from '@gusto/embedded-react-sdk' + +function MyApp() { + return ( + {}} + /> + ) +} +``` + +## CreatePaymentFlowProps + + + +Props for CreatePaymentFlow. + +| Property | Type | Description | +| ------ | ------ | ------ | +| `companyId` | `string` | The associated company identifier. | +| `onEvent` | [`OnEventType`](../../events.md#oneventtype)\<[`EventType`](../../events.md#eventtype), `unknown`\> | Callback invoked each time the component emits an event โ€” user interactions, successful API responses, step transitions, or errors. Receives the event type constant and an optional payload whose shape varies by event. See the [Event Handling guide](https://docs.gusto.com/embedded-payroll/docs/event-handling) and each component's event table for the full list of emitted events. | + +_Inherits `children`, `className`, `defaultValues`, `dictionary`, `FallbackComponent`, `LoaderComponent` from [BaseComponentInterface](../../blocks.md#basecomponentinterface)._ + +## Events + +| Event | Description | Data | +| ----- | ----------- | ---- | +| `contractor/payments/created` | Fired when a payment group is successfully created | The created `ContractorPaymentGroup` | +| `contractor/payments/exit` | Fired when the user completes the payment flow | `{ uuid?: string \| null }` | +| `payroll/wire/form/done` | Fired when wire transfer details are submitted | `{ wireInRequest: WireInRequest, confirmationAlert: { title: string, content?: string } }` | +| `breadcrumb/navigate` | Fired when the user clicks a breadcrumb to navigate back | `{ key: string, onNavigate: (ctx) => ctx }` | + +## Sub-components + +| Component | Description | +| ------ | ------ | +| [CreatePayment](blocks.md#createpayment) | Form for creating a contractor payment group, including date selection, per-contractor edits, preview, and submission blockers. | +| [PaymentSummary](blocks.md#paymentsummary) | Displays a summary of a created contractor payment group, including payment totals, debit information, contractor details, and wire transfer instructions when required. | + + +## Step flow + +`CreatePaymentFlow` has no hub of its own โ€” it's a straight line from creating a payment to reviewing the result. `CreatePayment` handles selecting a date, editing per-contractor amounts, and submitting; Fast ACH blockers and wire transfer requirements are handled inline. On success (`contractor/payments/created`) the flow hands off to `PaymentSummary`, which shows the created group, debit details, and wire instructions when required. + +```mermaid +flowchart LR + start@{ shape: sm-circ } --> CreatePayment + CreatePayment -->|"contractor/payments/created"| PaymentSummary + PaymentSummary -->|"contractor/payments/exit"| done@{ shape: fr-circ, label: " " } +``` + +The breadcrumb header (`breadcrumb/navigate`) returns to the payments list; submitting wire-transfer details (`payroll/wire/form/done`) surfaces a success alert on `PaymentSummary` without leaving the step. + + +## Endpoints + +| Method | Path | +| --- | --- | +| GET | [`/v1/companies/:companyId/bank_accounts`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_id-bank-accounts) | +| POST | [`/v1/companies/:companyId/contractor_payment_groups`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/post-v1-companies-company_id-contractor_payment_groups) | +| POST | [`/v1/companies/:companyId/contractor_payment_groups/preview`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/post-v1-companies-company_id-contractor_payment_groups-preview) | +| GET | [`/v1/companies/:companyUuid/contractors`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_uuid-contractors) | +| GET | [`/v1/companies/:companyUuid/payment_configs`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-company-payment-configs) | +| GET | [`/v1/contractor_payment_groups/:contractorPaymentGroupUuid`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-contractor_payment_groups-contractor_payment_group_id) | diff --git a/docs/reference/contractor/management/index.md b/docs/reference/contractor/management/index.md index 3edad495a..c28a74718 100644 --- a/docs/reference/contractor/management/index.md +++ b/docs/reference/contractor/management/index.md @@ -22,7 +22,9 @@ import { ContractorManagement } from '@gusto/embedded-react-sdk' | Component | Description | | --------- | ----------- | +| [CreatePaymentFlow](create-payment-flow.md) | Guided flow to create a contractor payment and review the resulting summary. | | [PaymentFlow](payment-flow.md) | Hub for creating and managing contractor payments for a company. | +| [ViewHistoryFlow](view-history-flow.md) | Guided flow to inspect a contractor payment group's history and drill into an individual contractor's payment statement. | ## ๐Ÿงฉ Blocks diff --git a/docs/reference/contractor/management/payment-flow.md b/docs/reference/contractor/management/payment-flow.md index 2862fe1b3..21f65ab2c 100644 --- a/docs/reference/contractor/management/payment-flow.md +++ b/docs/reference/contractor/management/payment-flow.md @@ -81,10 +81,8 @@ _Inherits `children`, `className`, `defaultValues`, `dictionary`, `FallbackCompo | Component | Description | | ------ | ------ | | [PaymentsList](blocks.md#paymentslist) | Displays a list of contractor payment groups for a company. | -| [CreatePayment](blocks.md#createpayment) | Form for creating a contractor payment group, including date selection, per-contractor edits, preview, and submission blockers. | -| [PaymentSummary](blocks.md#paymentsummary) | Displays a summary of a created contractor payment group, including payment totals, debit information, contractor details, and wire transfer instructions when required. | -| [PaymentHistory](blocks.md#paymenthistory) | Displays a contractor payment group, including each individual contractor payment, with actions to view details or cancel. | -| [PaymentStatement](blocks.md#paymentstatement) | Displays a single contractor's payment statement within a payment group, including wage breakdown, bonuses, reimbursements, and a receipt card for funded direct-deposit payments. | +| [CreatePaymentFlow](create-payment-flow.md) | Guided flow to create a contractor payment and review the resulting summary. | +| [ViewHistoryFlow](view-history-flow.md) | Guided flow to inspect a contractor payment group's history and drill into an individual contractor's payment statement. | | [InformationRequests.InformationRequestsFlow](../../company/information-requests/information-requests-flow.md) | Hub for viewing and responding to outstanding information requests from Gusto. | @@ -92,23 +90,22 @@ _Inherits `children`, `className`, `defaultValues`, `dictionary`, `FallbackCompo The flow is a hub-and-spoke loop with no terminal state โ€” the payments list is the landing screen, and every path returns to it: -- **Create a payment** โ€” `PaymentsList` โ†’ `CreatePayment` โ†’ `PaymentSummary`, then back to the list. -- **View history** โ€” `PaymentsList` โ†’ `PaymentHistory` โ†’ `PaymentStatement`; the history view can also cancel a payment and return to the list. +- **Create a payment** โ€” `PaymentsList` hands off to `CreatePaymentFlow`, which returns to the list once the flow completes. +- **View history** โ€” `PaymentsList` hands off to `ViewHistoryFlow`, which returns to the list once the flow completes. - **Respond to a request** โ€” `PaymentsList` opens the embedded `InformationRequestsFlow`, returning to the list once the request is submitted or cancelled. -Breadcrumbs navigate back to any prior step, and submitting wire-transfer details surfaces a success alert on the list and summary screens. The diagram below shows the topology; the event behind each transition is listed in the events table above. +Breadcrumbs navigate back to the list from anywhere inside `CreatePaymentFlow` or `ViewHistoryFlow`, and submitting wire-transfer details surfaces a success alert on the list screen. The diagram below shows the topology; the event behind each transition is listed in the events table above. ```mermaid flowchart LR start@{ shape: sm-circ } --> PaymentsList - PaymentsList --> CreatePayment --> PaymentSummary --> PaymentsList - - PaymentsList --> PaymentHistory --> PaymentStatement - PaymentHistory --> PaymentsList - - PaymentsList --> InformationRequests["InformationRequests.
InformationRequestsFlow"] --> PaymentsList + PaymentsList <--> CreatePaymentFlow + PaymentsList <--> ViewHistoryFlow + PaymentsList <--> InformationRequests["InformationRequests.
InformationRequestsFlow"] + class CreatePaymentFlow flow + class ViewHistoryFlow flow class InformationRequests flow ``` @@ -147,14 +144,6 @@ When wire transfer is required: | Method | Path | | --- | --- | -| GET | [`/v1/companies/:companyId/bank_accounts`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_id-bank-accounts) | | GET | [`/v1/companies/:companyId/contractor_payment_groups`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_id-contractor_payment_groups) | -| POST | [`/v1/companies/:companyId/contractor_payment_groups`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/post-v1-companies-company_id-contractor_payment_groups) | -| POST | [`/v1/companies/:companyId/contractor_payment_groups/preview`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/post-v1-companies-company_id-contractor_payment_groups-preview) | -| DELETE | [`/v1/companies/:companyId/contractor_payments/:contractorPaymentId`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/delete-v1-companies-company_id-contractor_payment-contractor-payment) | -| GET | [`/v1/companies/:companyUuid/contractors`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_uuid-contractors) | | GET | [`/v1/companies/:companyUuid/information_requests`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-information-requests) | -| GET | [`/v1/companies/:companyUuid/payment_configs`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-company-payment-configs) | -| GET | [`/v1/contractor_payment_groups/:contractorPaymentGroupUuid`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-contractor_payment_groups-contractor_payment_group_id) | -| GET | [`/v1/contractor_payments/:contractorPaymentUuid/receipt`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-contractor_payments-contractor_payment_uuid-receipt) | | PUT | `/v1/information_requests/:informationRequestUuid/submit` | diff --git a/docs/reference/contractor/management/view-history-flow.md b/docs/reference/contractor/management/view-history-flow.md new file mode 100644 index 000000000..7a9e7adf0 --- /dev/null +++ b/docs/reference/contractor/management/view-history-flow.md @@ -0,0 +1,94 @@ +--- +# Autogenerated by TypeDoc from TSDoc comments in the source code. +# To update content: edit TSDoc comments in src/. +# To update structure: edit docs-site/typedoc.config.ts or docs-site/plugins/typedoc-custom/. +# Then run `npm run docs:api:generate` to regenerate. +title: ViewHistoryFlow +description: ViewHistoryFlow reference. +sidebar_position: 2 +generated_by: typedoc +custom_edit_url: null +--- + +# ViewHistoryFlow + +Guided flow to inspect a contractor payment group's history and drill into an individual +contractor's payment statement. + +## Remarks + +This is the inner flow that powers the view-history spoke of `ContractorManagement.PaymentFlow`. +Render it directly when you have built your own payments landing page and want to hand the user +off to the standard history-viewing experience without re-implementing it. The flow ships with +breadcrumb navigation and lets the user cancel an individual payment from the history screen. + +## Example + +```tsx title="App.tsx" +import { ContractorManagement } from '@gusto/embedded-react-sdk' + +function MyApp() { + return ( + {}} + /> + ) +} +``` + +## ViewHistoryFlowProps + + + +Props for ViewHistoryFlow. + +| Property | Type | Description | +| ------ | ------ | ------ | +| `onEvent` | [`OnEventType`](../../events.md#oneventtype)\<[`EventType`](../../events.md#eventtype), `unknown`\> | Callback invoked each time the component emits an event โ€” user interactions, successful API responses, step transitions, or errors. Receives the event type constant and an optional payload whose shape varies by event. See the [Event Handling guide](https://docs.gusto.com/embedded-payroll/docs/event-handling) and each component's event table for the full list of emitted events. | +| `paymentId` | `string` | Identifier of the payment group to inspect. | + +_Inherits `children`, `className`, `defaultValues`, `dictionary`, `FallbackComponent`, `LoaderComponent` from [BaseComponentInterface](../../blocks.md#basecomponentinterface)._ + +## Events + +| Event | Description | Data | +| ----- | ----------- | ---- | +| `contractor/payments/view/details` | Fired when the user views a specific contractor payment | `{ contractor: Contractor, paymentGroupId: string }` | +| `contractor/payments/cancel` | Fired when a payment is cancelled | `{ paymentId: string }` | +| `breadcrumb/navigate` | Fired when the user clicks a breadcrumb to navigate back | `{ key: string, onNavigate: (ctx) => ctx }` | + +## Sub-components + +| Component | Description | +| ------ | ------ | +| [PaymentHistory](blocks.md#paymenthistory) | Displays a contractor payment group, including each individual contractor payment, with actions to view details or cancel. | +| [PaymentStatement](blocks.md#paymentstatement) | Displays a single contractor's payment statement within a payment group, including wage breakdown, bonuses, reimbursements, and a receipt card for funded direct-deposit payments. | + + +## Step flow + +`ViewHistoryFlow` centers on `PaymentHistory` as its hub: it shows a payment group's details and can either drill into an individual contractor's statement (`contractor/payments/view/details` โ†’ `PaymentStatement`) or cancel the group outright (`contractor/payments/cancel`), which exits the flow. + +```mermaid +flowchart LR + start@{ shape: sm-circ } --> PaymentHistory + PaymentHistory <-->|"contractor/payments/view/details"| PaymentStatement + PaymentHistory -->|"contractor/payments/cancel"| done@{ shape: fr-circ, label: " " } +``` + +The breadcrumb header (`breadcrumb/navigate`) returns from `PaymentStatement` to `PaymentHistory`, or exits the flow entirely from either step. + + +## Endpoints + +| Method | Path | +| --- | --- | +| GET | [`/v1/companies/:companyId/bank_accounts`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_id-bank-accounts) | +| POST | [`/v1/companies/:companyId/contractor_payment_groups`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/post-v1-companies-company_id-contractor_payment_groups) | +| POST | [`/v1/companies/:companyId/contractor_payment_groups/preview`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/post-v1-companies-company_id-contractor_payment_groups-preview) | +| DELETE | [`/v1/companies/:companyId/contractor_payments/:contractorPaymentId`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/delete-v1-companies-company_id-contractor_payment-contractor-payment) | +| GET | [`/v1/companies/:companyUuid/contractors`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_uuid-contractors) | +| GET | [`/v1/companies/:companyUuid/payment_configs`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-company-payment-configs) | +| GET | [`/v1/contractor_payment_groups/:contractorPaymentGroupUuid`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-contractor_payment_groups-contractor_payment_group_id) | +| GET | [`/v1/contractor_payments/:contractorPaymentUuid/receipt`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-contractor_payments-contractor_payment_uuid-receipt) | diff --git a/docs/reference/index.mdx b/docs/reference/index.mdx index 6a8b03a7e..d417dae2b 100644 --- a/docs/reference/index.mdx +++ b/docs/reference/index.mdx @@ -12,7 +12,7 @@ custom_edit_url: null ## Browse by domain - + --- diff --git a/docs/reference/workflows.md b/docs/reference/workflows.md index 14bb22aff..62871a517 100644 --- a/docs/reference/workflows.md +++ b/docs/reference/workflows.md @@ -17,7 +17,9 @@ Full multi-step user experiences as a single component โ€” the fastest path to a | Component | Description | | --- | --- | | [CompanyOnboarding.OnboardingFlow](company/onboarding/onboarding-flow) | Guided flow to onboard a company to Gusto. | +| [ContractorManagement.CreatePaymentFlow](contractor/management/create-payment-flow) | Guided flow to create a contractor payment and review the resulting summary. | | [ContractorManagement.PaymentFlow](contractor/management/payment-flow) | Hub for creating and managing contractor payments for a company. | +| [ContractorManagement.ViewHistoryFlow](contractor/management/view-history-flow) | Guided flow to inspect a contractor payment group's history and drill into an individual contractor's payment statement. | | [ContractorOnboarding.OnboardingFlow](contractor/onboarding/onboarding-flow) | Guided flow for admins to onboard a contractor to the company. | | [ContractorOnboarding.SelfOnboardingFlow](contractor/onboarding/self-onboarding-flow) | Guided flow for contractors to complete their own onboarding. | | [EmployeeManagement.DashboardFlow](employee/management/dashboard-flow) | Hub for viewing and managing a single employee's profile, pay, and documents. | diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlow.tsx b/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlow.tsx new file mode 100644 index 000000000..13e789e83 --- /dev/null +++ b/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlow.tsx @@ -0,0 +1,114 @@ +import { createMachine } from 'robot3' +import { useMemo } from 'react' +import { createPaymentBreadcrumbsNodes, createPaymentMachine } from './createPaymentMachine' +import { + CreatePaymentContextual, + type CreatePaymentFlowContextInterface, + type CreatePaymentFlowProps, +} from './CreatePaymentFlowComponents' +import { Flow } from '@/components/Flow/Flow' +import type { FlowBreadcrumb } from '@/components/Common/FlowBreadcrumbs/FlowBreadcrumbsTypes' +import { buildBreadcrumbs, updateBreadcrumbs } from '@/helpers/breadcrumbHelpers' + +const EMPTY_BREADCRUMBS: FlowBreadcrumb[] = [] + +/** + * Props for the flow-internal {@link CreatePaymentInternalFlow}, which layers a parent flow's + * prefix breadcrumbs on top of the public {@link CreatePaymentFlowProps}. + * + * @internal + */ +export interface CreatePaymentInternalFlowProps extends CreatePaymentFlowProps { + /** + * Breadcrumbs prepended to the flow's own breadcrumb trail. Set by a parent flow (e.g. + * `PaymentFlow`) so the breadcrumb history remains coherent across the handoff. + */ + prefixBreadcrumbs?: FlowBreadcrumb[] +} + +/** + * Guided flow to create a contractor payment and review the resulting summary. + * + * @remarks + * This is the inner flow that powers the create-payment spoke of `ContractorManagement.PaymentFlow`. + * Render it directly when you have built your own payments landing page and want to hand the user + * off to the standard create-payment experience without re-implementing it. The flow ships with + * breadcrumb navigation and handles Fast ACH blockers and wire transfer requirements inline. + * + * @events + * | Event | Description | Data | + * | ----- | ----------- | ---- | + * | `contractor/payments/created` | Fired when a payment group is successfully created | The created `ContractorPaymentGroup` | + * | `contractor/payments/exit` | Fired when the user completes the payment flow | `{ uuid?: string \| null }` | + * | `payroll/wire/form/done` | Fired when wire transfer details are submitted | `{ wireInRequest: WireInRequest, confirmationAlert: { title: string, content?: string } }` | + * | `breadcrumb/navigate` | Fired when the user clicks a breadcrumb to navigate back | `{ key: string, onNavigate: (ctx) => ctx }` | + * + * @components + * - {@link CreatePayment} + * - {@link PaymentSummary} + * + * @param props - See {@link CreatePaymentFlowProps}. + * @returns The composed create-payment flow. + * @public + * + * @example + * ```tsx title="App.tsx" + * import { ContractorManagement } from '@gusto/embedded-react-sdk' + * + * function MyApp() { + * return ( + * {}} + * /> + * ) + * } + * ``` + */ +export function CreatePaymentFlow(props: CreatePaymentFlowProps) { + return +} + +/** + * Flow-internal entry point for {@link CreatePaymentFlow} that additionally accepts + * flow-injected `prefixBreadcrumbs`. Partners use {@link CreatePaymentFlow}; `PaymentFlow` renders + * this directly to prepend its own breadcrumb trail. + * + * @internal + */ +export function CreatePaymentInternalFlow({ + companyId, + onEvent, + prefixBreadcrumbs = EMPTY_BREADCRUMBS, +}: CreatePaymentInternalFlowProps) { + const createPaymentFlow = useMemo(() => { + const baseBreadcrumbs = buildBreadcrumbs(createPaymentBreadcrumbsNodes) + const displayOnlyPrefixes = prefixBreadcrumbs.map(({ onNavigate, ...rest }) => rest) + const breadcrumbs = Object.fromEntries( + Object.entries(baseBreadcrumbs).map(([stateKey, trail]) => [ + stateKey, + [...displayOnlyPrefixes, ...trail], + ]), + ) + + const initialBreadcrumbContext = updateBreadcrumbs('createPayment', { + header: { + type: 'breadcrumbs' as const, + breadcrumbs, + }, + }) + + return createMachine( + 'createPayment', + createPaymentMachine, + (initialContext: CreatePaymentFlowContextInterface) => ({ + ...initialContext, + ...initialBreadcrumbContext, + component: CreatePaymentContextual, + companyId, + }), + ) + }, [companyId, prefixBreadcrumbs]) + + return +} diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlowComponents.tsx b/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlowComponents.tsx new file mode 100644 index 000000000..c3c72c02f --- /dev/null +++ b/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlowComponents.tsx @@ -0,0 +1,44 @@ +import { CreatePayment } from '../CreatePayment/CreatePayment' +import { PaymentSummaryInternal } from '../PaymentSummary/PaymentSummary' +import type { InternalAlert } from '../types' +import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow' +import type { BaseComponentInterface } from '@/components/Base' +import { ensureRequired } from '@/helpers/ensureRequired' + +/** + * Props for {@link CreatePaymentFlow}. + * + * @public + */ +export interface CreatePaymentFlowProps extends BaseComponentInterface { + /** The associated company identifier. */ + companyId: string +} + +/** @internal */ +export interface CreatePaymentFlowContextInterface extends FlowContextInterface { + companyId: string + createdPaymentGroupId?: string + alerts?: InternalAlert[] +} + +/** @internal */ +export function CreatePaymentContextual() { + const { companyId, onEvent } = useFlow() + return +} + +/** @internal */ +export function PaymentSummaryContextual() { + const { createdPaymentGroupId, companyId, onEvent, alerts } = + useFlow() + + return ( + + ) +} diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/GUIDE.md b/src/components/Contractor/Payments/CreatePaymentFlow/GUIDE.md new file mode 100644 index 000000000..602813488 --- /dev/null +++ b/src/components/Contractor/Payments/CreatePaymentFlow/GUIDE.md @@ -0,0 +1,16 @@ + + +# CreatePaymentFlow + +## Step flow + +`CreatePaymentFlow` has no hub of its own โ€” it's a straight line from creating a payment to reviewing the result. `CreatePayment` handles selecting a date, editing per-contractor amounts, and submitting; Fast ACH blockers and wire transfer requirements are handled inline. On success (`contractor/payments/created`) the flow hands off to `PaymentSummary`, which shows the created group, debit details, and wire instructions when required. + +```mermaid +flowchart LR + start@{ shape: sm-circ } --> CreatePayment + CreatePayment -->|"contractor/payments/created"| PaymentSummary + PaymentSummary -->|"contractor/payments/exit"| done@{ shape: fr-circ, label: " " } +``` + +The breadcrumb header (`breadcrumb/navigate`) returns to the payments list; submitting wire-transfer details (`payroll/wire/form/done`) surfaces a success alert on `PaymentSummary` without leaving the step. diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.test.ts b/src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.test.ts new file mode 100644 index 000000000..165a6aef6 --- /dev/null +++ b/src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from 'vitest' +import { createMachine, interpret, type SendFunction } from 'robot3' +import { createPaymentMachine, createPaymentBreadcrumbsNodes } from './createPaymentMachine' +import type { CreatePaymentFlowContextInterface } from './CreatePaymentFlowComponents' +import { componentEvents, payrollWireEvents } from '@/shared/constants' +import { buildBreadcrumbs } from '@/helpers/breadcrumbHelpers' + +function createTestMachine() { + return createMachine( + 'createPayment', + createPaymentMachine, + (initialContext: CreatePaymentFlowContextInterface): CreatePaymentFlowContextInterface => ({ + ...initialContext, + component: () => null, + companyId: 'test-company', + header: { + type: 'breadcrumbs', + breadcrumbs: buildBreadcrumbs(createPaymentBreadcrumbsNodes), + currentBreadcrumbId: 'createPayment', + }, + }), + ) +} + +function createService() { + const machine = createTestMachine() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const service = interpret(machine, () => {}, {} as any) + return service +} + +function send(service: ReturnType, type: string, payload?: unknown) { + ;(service.send as SendFunction)({ type, payload }) +} + +function currentBreadcrumbId(service: ReturnType) { + return service.context.header?.type === 'breadcrumbs' + ? service.context.header.currentBreadcrumbId + : undefined +} + +describe('createPaymentMachine', () => { + describe('createPayment state', () => { + it('transitions to paymentSummary on CONTRACTOR_PAYMENT_CREATED with the created group id', () => { + const service = createService() + expect(service.machine.current).toBe('createPayment') + + send(service, componentEvents.CONTRACTOR_PAYMENT_CREATED, { uuid: 'group-456' }) + + expect(service.machine.current).toBe('paymentSummary') + expect(service.context.createdPaymentGroupId).toBe('group-456') + expect(currentBreadcrumbId(service)).toBe('paymentSummary') + expect(service.context.alerts).toBeUndefined() + }) + }) + + describe('paymentSummary state', () => { + function toPaymentSummary(service: ReturnType) { + send(service, componentEvents.CONTRACTOR_PAYMENT_CREATED, { uuid: 'group-456' }) + expect(service.machine.current).toBe('paymentSummary') + } + + it('sets a success alert on PAYROLL_WIRE_FORM_DONE and stays on paymentSummary', () => { + const service = createService() + toPaymentSummary(service) + + send(service, payrollWireEvents.PAYROLL_WIRE_FORM_DONE, { + wireInRequest: {}, + confirmationAlert: { title: 'wireDetailsSubmitted', content: 'Wire submitted' }, + }) + + expect(service.machine.current).toBe('paymentSummary') + expect(service.context.alerts).toEqual([ + { type: 'success', title: 'wireDetailsSubmitted', content: 'Wire submitted' }, + ]) + }) + }) +}) diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.ts b/src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.ts new file mode 100644 index 000000000..e7c3a6a1c --- /dev/null +++ b/src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.ts @@ -0,0 +1,91 @@ +import { reduce, state, transition } from 'robot3' +import type { ContractorPaymentGroup } from '@gusto/embedded-api/models/components/contractorpaymentgroup' +import type { WireInRequest } from '@gusto/embedded-api/models/components/wireinrequest' +import { + PaymentSummaryContextual, + type CreatePaymentFlowContextInterface, +} from './CreatePaymentFlowComponents' +import { payrollWireEvents, componentEvents } from '@/shared/constants' +import type { MachineEventType, MachineTransition } from '@/types/Helpers' +import { updateBreadcrumbs } from '@/helpers/breadcrumbHelpers' +import type { BreadcrumbNodes } from '@/components/Common/FlowBreadcrumbs/FlowBreadcrumbsTypes' + +type EventPayloads = { + [componentEvents.CONTRACTOR_PAYMENT_CREATED]: ContractorPaymentGroup + [payrollWireEvents.PAYROLL_WIRE_FORM_DONE]: { + wireInRequest: WireInRequest + confirmationAlert: { + title: string + content?: string + } + } +} + +/** @internal */ +export const createPaymentBreadcrumbsNodes: BreadcrumbNodes = { + createPayment: { + parent: null, + item: { + id: 'createPayment', + label: 'breadcrumbLabel', + namespace: 'Contractor.Payments.CreatePayment', + onNavigate: ((ctx: CreatePaymentFlowContextInterface) => ({ + ...updateBreadcrumbs('createPayment', ctx), + })) as (context: unknown) => unknown, + }, + }, + paymentSummary: { + parent: null, + item: { + id: 'paymentSummary', + label: 'breadcrumbLabel', + namespace: 'Contractor.Payments.PaymentSummary', + }, + }, +} + +/** @internal */ +export const createPaymentMachine = { + createPayment: state( + transition( + componentEvents.CONTRACTOR_PAYMENT_CREATED, + 'paymentSummary', + reduce( + ( + ctx: CreatePaymentFlowContextInterface, + ev: MachineEventType, + ): CreatePaymentFlowContextInterface => { + return { + ...updateBreadcrumbs('paymentSummary', ctx), + component: PaymentSummaryContextual, + createdPaymentGroupId: ev.payload.uuid, + alerts: undefined, + } + }, + ), + ), + ), + paymentSummary: state( + transition( + payrollWireEvents.PAYROLL_WIRE_FORM_DONE, + 'paymentSummary', + reduce( + ( + ctx: CreatePaymentFlowContextInterface, + ev: MachineEventType, + ): CreatePaymentFlowContextInterface => { + return { + ...ctx, + alerts: [ + { + type: 'success', + title: 'wireDetailsSubmitted', + content: ev.payload.confirmationAlert.content, + }, + ], + } + }, + ), + ), + ), +} diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/index.ts b/src/components/Contractor/Payments/CreatePaymentFlow/index.ts new file mode 100644 index 000000000..d1853f13d --- /dev/null +++ b/src/components/Contractor/Payments/CreatePaymentFlow/index.ts @@ -0,0 +1,2 @@ +export { CreatePaymentFlow } from './CreatePaymentFlow' +export type { CreatePaymentFlowProps } from './CreatePaymentFlowComponents' diff --git a/src/components/Contractor/Payments/PaymentFlow/GUIDE.md b/src/components/Contractor/Payments/PaymentFlow/GUIDE.md index 427cbecbc..f3f0a6372 100644 --- a/src/components/Contractor/Payments/PaymentFlow/GUIDE.md +++ b/src/components/Contractor/Payments/PaymentFlow/GUIDE.md @@ -16,23 +16,22 @@ The typical step sequence when composing the blocks manually: The flow is a hub-and-spoke loop with no terminal state โ€” the payments list is the landing screen, and every path returns to it: -- **Create a payment** โ€” `PaymentsList` โ†’ `CreatePayment` โ†’ `PaymentSummary`, then back to the list. -- **View history** โ€” `PaymentsList` โ†’ `PaymentHistory` โ†’ `PaymentStatement`; the history view can also cancel a payment and return to the list. +- **Create a payment** โ€” `PaymentsList` hands off to `CreatePaymentFlow`, which returns to the list once the flow completes. +- **View history** โ€” `PaymentsList` hands off to `ViewHistoryFlow`, which returns to the list once the flow completes. - **Respond to a request** โ€” `PaymentsList` opens the embedded `InformationRequestsFlow`, returning to the list once the request is submitted or cancelled. -Breadcrumbs navigate back to any prior step, and submitting wire-transfer details surfaces a success alert on the list and summary screens. The diagram below shows the topology; the event behind each transition is listed in the events table above. +Breadcrumbs navigate back to the list from anywhere inside `CreatePaymentFlow` or `ViewHistoryFlow`, and submitting wire-transfer details surfaces a success alert on the list screen. The diagram below shows the topology; the event behind each transition is listed in the events table above. ```mermaid flowchart LR start@{ shape: sm-circ } --> PaymentsList - PaymentsList --> CreatePayment --> PaymentSummary --> PaymentsList - - PaymentsList --> PaymentHistory --> PaymentStatement - PaymentHistory --> PaymentsList - - PaymentsList --> InformationRequests["InformationRequests.
InformationRequestsFlow"] --> PaymentsList + PaymentsList <--> CreatePaymentFlow + PaymentsList <--> ViewHistoryFlow + PaymentsList <--> InformationRequests["InformationRequests.
InformationRequestsFlow"] + class CreatePaymentFlow flow + class ViewHistoryFlow flow class InformationRequests flow ``` diff --git a/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.tsx b/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.tsx index fca997925..7fc5d550b 100644 --- a/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.tsx +++ b/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.tsx @@ -34,10 +34,8 @@ import { buildBreadcrumbs } from '@/helpers/breadcrumbHelpers' * * @components * - {@link PaymentsList} - * - {@link CreatePayment} - * - {@link PaymentSummary} - * - {@link PaymentHistory} - * - {@link PaymentStatement} + * - {@link CreatePaymentFlow} + * - {@link ViewHistoryFlow} * - {@link InformationRequests.InformationRequestsFlow | InformationRequestsFlow} * * @param props - See {@link PaymentFlowProps}. diff --git a/src/components/Contractor/Payments/PaymentFlow/PaymentFlowComponents.tsx b/src/components/Contractor/Payments/PaymentFlow/PaymentFlowComponents.tsx index b1d192e1f..858be30bc 100644 --- a/src/components/Contractor/Payments/PaymentFlow/PaymentFlowComponents.tsx +++ b/src/components/Contractor/Payments/PaymentFlow/PaymentFlowComponents.tsx @@ -1,13 +1,10 @@ import { PaymentsListInternal } from '../PaymentsList/PaymentsList' -import { CreatePayment } from '../CreatePayment/CreatePayment' -import { PaymentHistory } from '../PaymentHistory/PaymentHistory' -import { PaymentStatement } from '../PaymentStatement/PaymentStatement' -import { PaymentSummaryInternal } from '../PaymentSummary/PaymentSummary' +import { CreatePaymentInternalFlow } from '../CreatePaymentFlow/CreatePaymentFlow' +import { ViewHistoryInternalFlow } from '../ViewHistoryFlow/ViewHistoryFlow' import type { InternalAlert } from '../types' import { InformationRequestsFlow } from '@/components/InformationRequests' import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow' import type { BaseComponentInterface } from '@/components/Base' -import type { BreadcrumbTrail } from '@/components/Common/FlowBreadcrumbs/FlowBreadcrumbsTypes' import { ensureRequired } from '@/helpers/ensureRequired' /** @@ -23,10 +20,7 @@ export interface PaymentFlowProps extends BaseComponentInterface { /** @internal */ export interface PaymentFlowContextInterface extends FlowContextInterface { companyId: string - breadcrumbs?: BreadcrumbTrail currentPaymentId?: string - currentContractorUuid?: string - createdPaymentGroupId?: string alerts?: InternalAlert[] } @@ -38,42 +32,37 @@ export function PaymentListContextual() { ) } -/** @internal */ -export function CreatePaymentContextual() { - const { companyId, onEvent } = useFlow() - return +function useLandingPrefixBreadcrumbs() { + const { header } = useFlow() + const landingBreadcrumb = + header?.type === 'breadcrumbs' ? header.breadcrumbs?.landing?.[0] : undefined + return landingBreadcrumb ? [landingBreadcrumb] : undefined } /** @internal */ -export function PaymentHistoryContextual() { - const { currentPaymentId, onEvent } = useFlow() - return -} +export function CreatePaymentFlowContextual() { + const { companyId, onEvent } = useFlow() + const prefixBreadcrumbs = useLandingPrefixBreadcrumbs() -/** @internal */ -export function PaymentStatementContextual() { - const { currentPaymentId, currentContractorUuid, onEvent } = - useFlow() return ( - ) } /** @internal */ -export function PaymentSummaryContextual() { - const { createdPaymentGroupId, companyId, onEvent, alerts } = - useFlow() +export function ViewHistoryFlowContextual() { + const { currentPaymentId, onEvent } = useFlow() + const prefixBreadcrumbs = useLandingPrefixBreadcrumbs() return ( - ) } diff --git a/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.test.ts b/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.test.ts new file mode 100644 index 000000000..51912663e --- /dev/null +++ b/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.test.ts @@ -0,0 +1,173 @@ +import { describe, expect, it } from 'vitest' +import { createMachine, interpret, type SendFunction } from 'robot3' +import { paymentMachine, paymentFlowBreadcrumbsNodes } from './paymentStateMachine' +import type { PaymentFlowContextInterface } from './PaymentFlowComponents' +import { componentEvents, informationRequestEvents, payrollWireEvents } from '@/shared/constants' +import { buildBreadcrumbs } from '@/helpers/breadcrumbHelpers' +import { ensureRequired } from '@/helpers/ensureRequired' + +function createTestMachine() { + return createMachine( + 'landing', + paymentMachine, + (initialContext: PaymentFlowContextInterface): PaymentFlowContextInterface => ({ + ...initialContext, + component: () => null, + companyId: 'test-company', + header: { + type: 'breadcrumbs', + breadcrumbs: buildBreadcrumbs(paymentFlowBreadcrumbsNodes), + }, + }), + ) +} + +function createService() { + const machine = createTestMachine() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const service = interpret(machine, () => {}, {} as any) + return service +} + +function send(service: ReturnType, type: string, payload?: unknown) { + ;(service.send as SendFunction)({ type, payload }) +} + +function navigateToLanding(service: ReturnType) { + send(service, componentEvents.BREADCRUMB_NAVIGATE, { + key: 'landing', + onNavigate: ensureRequired(paymentFlowBreadcrumbsNodes.landing).item.onNavigate, + }) +} + +describe('paymentMachine', () => { + describe('landing state', () => { + it('transitions to createPayment on CONTRACTOR_PAYMENT_CREATE', () => { + const service = createService() + expect(service.machine.current).toBe('landing') + + send(service, componentEvents.CONTRACTOR_PAYMENT_CREATE) + + expect(service.machine.current).toBe('createPayment') + expect(service.context.alerts).toBeUndefined() + }) + + it('transitions to history on CONTRACTOR_PAYMENT_VIEW with paymentId from event', () => { + const service = createService() + + send(service, componentEvents.CONTRACTOR_PAYMENT_VIEW, { paymentId: 'payment-123' }) + + expect(service.machine.current).toBe('history') + expect(service.context.currentPaymentId).toBe('payment-123') + expect(service.context.alerts).toBeUndefined() + }) + + it('transitions to informationRequests on CONTRACTOR_PAYMENT_RFI_RESPOND', () => { + const service = createService() + + send(service, componentEvents.CONTRACTOR_PAYMENT_RFI_RESPOND) + + expect(service.machine.current).toBe('informationRequests') + }) + + it('sets a success alert on PAYROLL_WIRE_FORM_DONE and stays on landing', () => { + const service = createService() + + send(service, payrollWireEvents.PAYROLL_WIRE_FORM_DONE, { + wireInRequest: {}, + confirmationAlert: { title: 'wireDetailsSubmitted', content: 'Wire submitted' }, + }) + + expect(service.machine.current).toBe('landing') + expect(service.context.alerts).toEqual([ + { type: 'success', title: 'wireDetailsSubmitted', content: 'Wire submitted' }, + ]) + }) + }) + + describe('createPayment state', () => { + function toCreatePayment(service: ReturnType) { + send(service, componentEvents.CONTRACTOR_PAYMENT_CREATE) + expect(service.machine.current).toBe('createPayment') + } + + it('stays active for the full lifetime of the CreatePaymentFlow spoke (no internal transitions)', () => { + const service = createService() + toCreatePayment(service) + + expect(service.context.component).toBeDefined() + }) + + it('transitions to landing on CONTRACTOR_PAYMENT_EXIT bubbled from the spoke', () => { + const service = createService() + toCreatePayment(service) + + send(service, componentEvents.CONTRACTOR_PAYMENT_EXIT, { uuid: 'group-456' }) + + expect(service.machine.current).toBe('landing') + expect(service.context.alerts).toBeUndefined() + }) + + it('transitions to landing on the bubbled landing BREADCRUMB_NAVIGATE', () => { + const service = createService() + toCreatePayment(service) + + navigateToLanding(service) + + expect(service.machine.current).toBe('landing') + }) + }) + + describe('history state', () => { + function toHistory(service: ReturnType) { + send(service, componentEvents.CONTRACTOR_PAYMENT_VIEW, { paymentId: 'payment-123' }) + expect(service.machine.current).toBe('history') + } + + it('transitions to landing with a success alert on CONTRACTOR_PAYMENT_CANCEL bubbled from the spoke', () => { + const service = createService() + toHistory(service) + + send(service, componentEvents.CONTRACTOR_PAYMENT_CANCEL, { paymentId: 'payment-123' }) + + expect(service.machine.current).toBe('landing') + expect(service.context.alerts).toEqual([ + { type: 'success', title: 'paymentCancelledSuccessfully' }, + ]) + }) + + it('transitions to landing on the bubbled landing BREADCRUMB_NAVIGATE', () => { + const service = createService() + toHistory(service) + + navigateToLanding(service) + + expect(service.machine.current).toBe('landing') + }) + }) + + describe('informationRequests state', () => { + function toInformationRequests(service: ReturnType) { + send(service, componentEvents.CONTRACTOR_PAYMENT_RFI_RESPOND) + expect(service.machine.current).toBe('informationRequests') + } + + it('transitions to landing on INFORMATION_REQUEST_FORM_DONE', () => { + const service = createService() + toInformationRequests(service) + + send(service, informationRequestEvents.INFORMATION_REQUEST_FORM_DONE) + + expect(service.machine.current).toBe('landing') + }) + + it('transitions to landing on INFORMATION_REQUEST_FORM_CANCEL', () => { + const service = createService() + toInformationRequests(service) + + send(service, informationRequestEvents.INFORMATION_REQUEST_FORM_CANCEL) + + expect(service.machine.current).toBe('landing') + }) + }) +}) diff --git a/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.ts b/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.ts index 7494cc4c4..7ff55bde4 100644 --- a/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.ts +++ b/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.ts @@ -1,32 +1,22 @@ import { reduce, state, transition } from 'robot3' -import type { ContractorPaymentGroup } from '@gusto/embedded-api/models/components/contractorpaymentgroup' -import type { Contractor } from '@gusto/embedded-api/models/components/contractor' import type { WireInRequest } from '@gusto/embedded-api/models/components/wireinrequest' -import { getContractorDisplayName } from '../CreatePayment/helpers' import { - CreatePaymentContextual, - type PaymentFlowContextInterface, - PaymentHistoryContextual, - PaymentListContextual, - PaymentStatementContextual, - PaymentSummaryContextual, + CreatePaymentFlowContextual, InformationRequestsContextual, + PaymentListContextual, + type PaymentFlowContextInterface, + ViewHistoryFlowContextual, } from './PaymentFlowComponents' import { componentEvents, informationRequestEvents, payrollWireEvents } from '@/shared/constants' import type { MachineEventType, MachineTransition } from '@/types/Helpers' -import { patchBreadcrumbsHeader, updateBreadcrumbs } from '@/helpers/breadcrumbHelpers' +import { patchBreadcrumbsHeader } from '@/helpers/breadcrumbHelpers' import type { BreadcrumbNodes } from '@/components/Common/FlowBreadcrumbs/FlowBreadcrumbsTypes' import { createBreadcrumbNavigateTransition } from '@/components/Common/FlowBreadcrumbs/breadcrumbTransitionHelpers' type EventPayloads = { [componentEvents.CONTRACTOR_PAYMENT_CREATE]: undefined - [componentEvents.CONTRACTOR_PAYMENT_CREATED]: ContractorPaymentGroup [componentEvents.CONTRACTOR_PAYMENT_EXIT]: { uuid?: string | null } [componentEvents.CONTRACTOR_PAYMENT_VIEW]: { paymentId: string } - [componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS]: { - contractor: Contractor - paymentGroupId: string - } [componentEvents.CONTRACTOR_PAYMENT_CANCEL]: { paymentId: string } [componentEvents.CONTRACTOR_PAYMENT_RFI_RESPOND]: undefined [componentEvents.BREADCRUMB_NAVIGATE]: { @@ -44,7 +34,16 @@ type EventPayloads = { } } -/** @internal */ +/** + * Hub-level breadcrumb nodes for {@link PaymentFlow}. + * + * @remarks + * Only `landing` is defined here โ€” the `createPayment` and `history` spokes own and render their + * own breadcrumb trails (via `CreatePaymentFlow`/`ViewHistoryFlow`), prefixed with this `landing` + * item so the trail reads continuously across the hub/spoke boundary. + * + * @internal + */ export const paymentFlowBreadcrumbsNodes: BreadcrumbNodes = { landing: { parent: null, @@ -58,54 +57,23 @@ export const paymentFlowBreadcrumbsNodes: BreadcrumbNodes = { })) as (context: unknown) => unknown, }, }, - createPayment: { - parent: 'landing', - item: { - id: 'createPayment', - label: 'breadcrumbLabel', - namespace: 'Contractor.Payments.CreatePayment', - onNavigate: ((ctx: PaymentFlowContextInterface) => ({ - ...updateBreadcrumbs('createPayment', ctx), - })) as (context: unknown) => unknown, - }, - }, - paymentSummary: { - parent: 'landing', - item: { - id: 'paymentSummary', - label: 'breadcrumbLabel', - namespace: 'Contractor.Payments.PaymentSummary', - onNavigate: ((ctx: PaymentFlowContextInterface) => ({ - ...updateBreadcrumbs('paymentSummary', ctx), - })) as (context: unknown) => unknown, - }, - }, - history: { - parent: 'landing', - item: { - id: 'history', - label: 'breadcrumbLabel', - namespace: 'Contractor.Payments.PaymentHistory', - onNavigate: ((ctx: PaymentFlowContextInterface) => ({ - ...updateBreadcrumbs('history', ctx), - component: PaymentHistoryContextual, - })) as (context: unknown) => unknown, - }, - }, - statement: { - parent: 'history', - item: { - id: 'statement', - label: 'breadcrumbLabel', - namespace: 'Contractor.Payments.PaymentStatement', - }, - }, -} as const +} const breadcrumbNavigateTransition = createBreadcrumbNavigateTransition() -/** @internal */ +/** + * Hub machine for {@link PaymentFlow}. + * + * @remarks + * `createPayment` and `history` each stay active for the full lifetime of their respective spoke + * (`CreatePaymentFlow`, `ViewHistoryFlow`) rather than tracking the spoke's internal screen. The + * spoke's own machine drives its internal steps and breadcrumb trail; events it can't handle + * locally (e.g. the `landing` breadcrumb, or a terminal exit/cancel event) bubble up here to + * transition the hub back to `landing`. + * + * @internal + */ export const paymentMachine = { landing: state( transition( @@ -113,8 +81,8 @@ export const paymentMachine = { 'createPayment', reduce((ctx: PaymentFlowContextInterface): PaymentFlowContextInterface => { return { - ...updateBreadcrumbs('createPayment', ctx), - component: CreatePaymentContextual, + ...ctx, + component: CreatePaymentFlowContextual, alerts: undefined, } }), @@ -128,8 +96,8 @@ export const paymentMachine = { ev: MachineEventType, ): PaymentFlowContextInterface => { return { - ...updateBreadcrumbs('history', ctx), - component: PaymentHistoryContextual, + ...ctx, + component: ViewHistoryFlowContextual, currentPaymentId: ev.payload.paymentId, alerts: undefined, } @@ -169,26 +137,6 @@ export const paymentMachine = { ), ), createPayment: state( - transition( - componentEvents.CONTRACTOR_PAYMENT_CREATED, - 'paymentSummary', - reduce( - ( - ctx: PaymentFlowContextInterface, - ev: MachineEventType, - ): PaymentFlowContextInterface => { - return { - ...updateBreadcrumbs('paymentSummary', ctx), - component: PaymentSummaryContextual, - createdPaymentGroupId: ev.payload.uuid, - alerts: undefined, - } - }, - ), - ), - breadcrumbNavigateTransition('landing'), - ), - paymentSummary: state( transition( componentEvents.CONTRACTOR_PAYMENT_EXIT, 'landing', @@ -196,58 +144,13 @@ export const paymentMachine = { return { ...patchBreadcrumbsHeader(ctx, { currentBreadcrumbId: undefined }), component: PaymentListContextual, - createdPaymentGroupId: undefined, alerts: undefined, } }), ), - transition( - payrollWireEvents.PAYROLL_WIRE_FORM_DONE, - 'paymentSummary', - reduce( - ( - ctx: PaymentFlowContextInterface, - ev: MachineEventType, - ): PaymentFlowContextInterface => { - return { - ...ctx, - alerts: [ - { - type: 'success', - title: 'wireDetailsSubmitted', - content: ev.payload.confirmationAlert.content, - }, - ], - } - }, - ), - ), breadcrumbNavigateTransition('landing'), ), history: state( - transition( - componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS, - 'statement', - reduce( - ( - ctx: PaymentFlowContextInterface, - ev: MachineEventType< - EventPayloads, - typeof componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS - >, - ): PaymentFlowContextInterface => { - return { - ...updateBreadcrumbs('statement', ctx, { - contractorName: getContractorDisplayName(ev.payload.contractor), - }), - component: PaymentStatementContextual, - currentContractorUuid: ev.payload.contractor.uuid, - currentPaymentId: ev.payload.paymentGroupId, - alerts: undefined, - } - }, - ), - ), transition( componentEvents.CONTRACTOR_PAYMENT_CANCEL, 'landing', @@ -266,10 +169,6 @@ export const paymentMachine = { ), breadcrumbNavigateTransition('landing'), ), - statement: state( - breadcrumbNavigateTransition('landing'), - breadcrumbNavigateTransition('history'), - ), informationRequests: state( transition( informationRequestEvents.INFORMATION_REQUEST_FORM_DONE, diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md b/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md new file mode 100644 index 000000000..f68e4fc65 --- /dev/null +++ b/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md @@ -0,0 +1,16 @@ + + +# ViewHistoryFlow + +## Step flow + +`ViewHistoryFlow` centers on `PaymentHistory` as its hub: it shows a payment group's details and can either drill into an individual contractor's statement (`contractor/payments/view/details` โ†’ `PaymentStatement`) or cancel the group outright (`contractor/payments/cancel`), which exits the flow. + +```mermaid +flowchart LR + start@{ shape: sm-circ } --> PaymentHistory + PaymentHistory <-->|"contractor/payments/view/details"| PaymentStatement + PaymentHistory -->|"contractor/payments/cancel"| done@{ shape: fr-circ, label: " " } +``` + +The breadcrumb header (`breadcrumb/navigate`) returns from `PaymentStatement` to `PaymentHistory`, or exits the flow entirely from either step. diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx b/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx new file mode 100644 index 000000000..b950ff0b9 --- /dev/null +++ b/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx @@ -0,0 +1,114 @@ +import { createMachine } from 'robot3' +import { useMemo } from 'react' +import { viewHistoryBreadcrumbsNodes, viewHistoryMachine } from './viewHistoryMachine' +import { + PaymentHistoryContextual, + type ViewHistoryFlowContextInterface, + type ViewHistoryFlowProps, +} from './ViewHistoryFlowComponents' +import { Flow } from '@/components/Flow/Flow' +import type { FlowBreadcrumb } from '@/components/Common/FlowBreadcrumbs/FlowBreadcrumbsTypes' +import { buildBreadcrumbs, updateBreadcrumbs } from '@/helpers/breadcrumbHelpers' + +const EMPTY_BREADCRUMBS: FlowBreadcrumb[] = [] + +/** + * Props for the flow-internal {@link ViewHistoryInternalFlow}, which layers a parent flow's + * prefix breadcrumbs on top of the public {@link ViewHistoryFlowProps}. + * + * @internal + */ +export interface ViewHistoryInternalFlowProps extends ViewHistoryFlowProps { + /** + * Breadcrumbs prepended to the flow's own breadcrumb trail. Set by a parent flow (e.g. + * `PaymentFlow`) so the breadcrumb history remains coherent across the handoff. + */ + prefixBreadcrumbs?: FlowBreadcrumb[] +} + +/** + * Guided flow to inspect a contractor payment group's history and drill into an individual + * contractor's payment statement. + * + * @remarks + * This is the inner flow that powers the view-history spoke of `ContractorManagement.PaymentFlow`. + * Render it directly when you have built your own payments landing page and want to hand the user + * off to the standard history-viewing experience without re-implementing it. The flow ships with + * breadcrumb navigation and lets the user cancel an individual payment from the history screen. + * + * @events + * | Event | Description | Data | + * | ----- | ----------- | ---- | + * | `contractor/payments/view/details` | Fired when the user views a specific contractor payment | `{ contractor: Contractor, paymentGroupId: string }` | + * | `contractor/payments/cancel` | Fired when a payment is cancelled | `{ paymentId: string }` | + * | `breadcrumb/navigate` | Fired when the user clicks a breadcrumb to navigate back | `{ key: string, onNavigate: (ctx) => ctx }` | + * + * @components + * - {@link PaymentHistory} + * - {@link PaymentStatement} + * + * @param props - See {@link ViewHistoryFlowProps}. + * @returns The composed view-history flow. + * @public + * + * @example + * ```tsx title="App.tsx" + * import { ContractorManagement } from '@gusto/embedded-react-sdk' + * + * function MyApp() { + * return ( + * {}} + * /> + * ) + * } + * ``` + */ +export function ViewHistoryFlow(props: ViewHistoryFlowProps) { + return +} + +/** + * Flow-internal entry point for {@link ViewHistoryFlow} that additionally accepts + * flow-injected `prefixBreadcrumbs`. Partners use {@link ViewHistoryFlow}; `PaymentFlow` renders + * this directly to prepend its own breadcrumb trail. + * + * @internal + */ +export function ViewHistoryInternalFlow({ + paymentId, + onEvent, + prefixBreadcrumbs = EMPTY_BREADCRUMBS, +}: ViewHistoryInternalFlowProps) { + const viewHistoryFlow = useMemo(() => { + const baseBreadcrumbs = buildBreadcrumbs(viewHistoryBreadcrumbsNodes) + const displayOnlyPrefixes = prefixBreadcrumbs.map(({ onNavigate, ...rest }) => rest) + const breadcrumbs = Object.fromEntries( + Object.entries(baseBreadcrumbs).map(([stateKey, trail]) => [ + stateKey, + [...displayOnlyPrefixes, ...trail], + ]), + ) + + const initialBreadcrumbContext = updateBreadcrumbs('history', { + header: { + type: 'breadcrumbs' as const, + breadcrumbs, + }, + }) + + return createMachine( + 'history', + viewHistoryMachine, + (initialContext: ViewHistoryFlowContextInterface) => ({ + ...initialContext, + ...initialBreadcrumbContext, + component: PaymentHistoryContextual, + currentPaymentId: paymentId, + }), + ) + }, [paymentId, prefixBreadcrumbs]) + + return +} diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx b/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx new file mode 100644 index 000000000..585fabfd3 --- /dev/null +++ b/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx @@ -0,0 +1,40 @@ +import { PaymentHistory } from '../PaymentHistory/PaymentHistory' +import { PaymentStatement } from '../PaymentStatement/PaymentStatement' +import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow' +import type { BaseComponentInterface } from '@/components/Base' +import { ensureRequired } from '@/helpers/ensureRequired' + +/** + * Props for {@link ViewHistoryFlow}. + * + * @public + */ +export interface ViewHistoryFlowProps extends BaseComponentInterface { + /** Identifier of the payment group to inspect. */ + paymentId: string +} + +/** @internal */ +export interface ViewHistoryFlowContextInterface extends FlowContextInterface { + currentPaymentId?: string + currentContractorUuid?: string +} + +/** @internal */ +export function PaymentHistoryContextual() { + const { currentPaymentId, onEvent } = useFlow() + return +} + +/** @internal */ +export function PaymentStatementContextual() { + const { currentPaymentId, currentContractorUuid, onEvent } = + useFlow() + return ( + + ) +} diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/index.ts b/src/components/Contractor/Payments/ViewHistoryFlow/index.ts new file mode 100644 index 000000000..9701b077d --- /dev/null +++ b/src/components/Contractor/Payments/ViewHistoryFlow/index.ts @@ -0,0 +1,2 @@ +export { ViewHistoryFlow } from './ViewHistoryFlow' +export type { ViewHistoryFlowProps } from './ViewHistoryFlowComponents' diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.test.ts b/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.test.ts new file mode 100644 index 000000000..84ac514c1 --- /dev/null +++ b/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, it } from 'vitest' +import { createMachine, interpret, type SendFunction } from 'robot3' +import { viewHistoryMachine, viewHistoryBreadcrumbsNodes } from './viewHistoryMachine' +import type { ViewHistoryFlowContextInterface } from './ViewHistoryFlowComponents' +import { componentEvents } from '@/shared/constants' +import { buildBreadcrumbs } from '@/helpers/breadcrumbHelpers' +import { ensureRequired } from '@/helpers/ensureRequired' + +function createTestMachine() { + return createMachine( + 'history', + viewHistoryMachine, + (initialContext: ViewHistoryFlowContextInterface): ViewHistoryFlowContextInterface => ({ + ...initialContext, + component: () => null, + currentPaymentId: 'payment-123', + header: { + type: 'breadcrumbs', + breadcrumbs: buildBreadcrumbs(viewHistoryBreadcrumbsNodes), + currentBreadcrumbId: 'history', + }, + }), + ) +} + +function createService() { + const machine = createTestMachine() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const service = interpret(machine, () => {}, {} as any) + return service +} + +function send(service: ReturnType, type: string, payload?: unknown) { + ;(service.send as SendFunction)({ type, payload }) +} + +function currentBreadcrumbId(service: ReturnType) { + return service.context.header?.type === 'breadcrumbs' + ? service.context.header.currentBreadcrumbId + : undefined +} + +describe('viewHistoryMachine', () => { + describe('history state', () => { + it('transitions to statement on CONTRACTOR_PAYMENT_VIEW_DETAILS with contractor and payment ids', () => { + const service = createService() + const contractor = { uuid: 'contractor-789', firstName: 'Ada', lastName: 'Lovelace' } + + send(service, componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS, { + contractor, + paymentGroupId: 'group-456', + }) + + expect(service.machine.current).toBe('statement') + expect(service.context.currentContractorUuid).toBe('contractor-789') + expect(service.context.currentPaymentId).toBe('group-456') + expect(currentBreadcrumbId(service)).toBe('statement') + }) + }) + + describe('statement state', () => { + function toStatement(service: ReturnType) { + send(service, componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS, { + contractor: { uuid: 'contractor-789', firstName: 'Ada', lastName: 'Lovelace' }, + paymentGroupId: 'group-456', + }) + expect(service.machine.current).toBe('statement') + } + + it('transitions to history on BREADCRUMB_NAVIGATE with history key', () => { + const service = createService() + toStatement(service) + + send(service, componentEvents.BREADCRUMB_NAVIGATE, { + key: 'history', + onNavigate: ensureRequired(viewHistoryBreadcrumbsNodes.history).item.onNavigate, + }) + + expect(service.machine.current).toBe('history') + }) + + it('ignores BREADCRUMB_NAVIGATE with an unrelated key', () => { + const service = createService() + toStatement(service) + + send(service, componentEvents.BREADCRUMB_NAVIGATE, { key: 'landing' }) + + expect(service.machine.current).toBe('statement') + }) + }) +}) diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.ts b/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.ts new file mode 100644 index 000000000..45a74bfe9 --- /dev/null +++ b/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.ts @@ -0,0 +1,76 @@ +import { reduce, state, transition } from 'robot3' +import type { Contractor } from '@gusto/embedded-api/models/components/contractor' +import { getContractorDisplayName } from '../CreatePayment/helpers' +import { + PaymentHistoryContextual, + PaymentStatementContextual, + type ViewHistoryFlowContextInterface, +} from './ViewHistoryFlowComponents' +import { componentEvents } from '@/shared/constants' +import type { MachineEventType, MachineTransition } from '@/types/Helpers' +import { updateBreadcrumbs } from '@/helpers/breadcrumbHelpers' +import type { BreadcrumbNodes } from '@/components/Common/FlowBreadcrumbs/FlowBreadcrumbsTypes' +import { createBreadcrumbNavigateTransition } from '@/components/Common/FlowBreadcrumbs/breadcrumbTransitionHelpers' + +type EventPayloads = { + [componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS]: { + contractor: Contractor + paymentGroupId: string + } +} + +const breadcrumbNavigateTransition = + createBreadcrumbNavigateTransition() + +/** @internal */ +export const viewHistoryBreadcrumbsNodes: BreadcrumbNodes = { + history: { + parent: null, + item: { + id: 'history', + label: 'breadcrumbLabel', + namespace: 'Contractor.Payments.PaymentHistory', + onNavigate: ((ctx: ViewHistoryFlowContextInterface) => ({ + ...updateBreadcrumbs('history', ctx), + component: PaymentHistoryContextual, + })) as (context: unknown) => unknown, + }, + }, + statement: { + parent: 'history', + item: { + id: 'statement', + label: 'breadcrumbLabel', + namespace: 'Contractor.Payments.PaymentStatement', + }, + }, +} + +/** @internal */ +export const viewHistoryMachine = { + history: state( + transition( + componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS, + 'statement', + reduce( + ( + ctx: ViewHistoryFlowContextInterface, + ev: MachineEventType< + EventPayloads, + typeof componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS + >, + ): ViewHistoryFlowContextInterface => { + return { + ...updateBreadcrumbs('statement', ctx, { + contractorName: getContractorDisplayName(ev.payload.contractor), + }), + component: PaymentStatementContextual, + currentContractorUuid: ev.payload.contractor.uuid, + currentPaymentId: ev.payload.paymentGroupId, + } + }, + ), + ), + ), + statement: state(breadcrumbNavigateTransition('history')), +} diff --git a/src/components/Contractor/exports/contractorManagement.ts b/src/components/Contractor/exports/contractorManagement.ts index 06611a9ab..bdca363e8 100644 --- a/src/components/Contractor/exports/contractorManagement.ts +++ b/src/components/Contractor/exports/contractorManagement.ts @@ -4,6 +4,8 @@ export { type ContractorTab, } from '../ContractorList/management/ManagementContractorList' export { PaymentFlow, type PaymentFlowProps } from '../Payments/PaymentFlow' +export { CreatePaymentFlow, type CreatePaymentFlowProps } from '../Payments/CreatePaymentFlow' +export { ViewHistoryFlow, type ViewHistoryFlowProps } from '../Payments/ViewHistoryFlow' export { PaymentsList, type PaymentsListProps } from '../Payments/PaymentsList/PaymentsList' export { CreatePayment, type CreatePaymentProps } from '../Payments/CreatePayment/CreatePayment' export { PaymentHistory, type PaymentHistoryProps } from '../Payments/PaymentHistory/PaymentHistory' From 723a67bfaf3dbfec6e7918ff79f3d7b74ebaf8f2 Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Tue, 21 Jul 2026 18:00:18 -0700 Subject: [PATCH 2/8] refactor(PaymentFlow): mark CreatePaymentFlow and ViewHistoryFlow as alpha SDK-1127 Not yet promoted to public: PaymentFlow keeps composing them internally, but its @components list and GUIDE.md step-flow diagram point at the underlying public blocks (CreatePayment, PaymentSummary, PaymentHistory, PaymentStatement) they wrap, matching what a partner using the public surface actually sees. Co-Authored-By: Claude Sonnet 5 --- .../CreatePaymentFlow/CreatePaymentFlow.tsx | 2 +- .../CreatePaymentFlowComponents.tsx | 2 +- .../Contractor/Payments/PaymentFlow/GUIDE.md | 17 +++++++++-------- .../Payments/PaymentFlow/PaymentFlow.tsx | 6 ++++-- .../ViewHistoryFlow/ViewHistoryFlow.tsx | 2 +- .../ViewHistoryFlowComponents.tsx | 2 +- 6 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlow.tsx b/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlow.tsx index 13e789e83..4a98d4a53 100644 --- a/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlow.tsx +++ b/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlow.tsx @@ -49,7 +49,7 @@ export interface CreatePaymentInternalFlowProps extends CreatePaymentFlowProps { * * @param props - See {@link CreatePaymentFlowProps}. * @returns The composed create-payment flow. - * @public + * @alpha * * @example * ```tsx title="App.tsx" diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlowComponents.tsx b/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlowComponents.tsx index c3c72c02f..bc2c1301c 100644 --- a/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlowComponents.tsx +++ b/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlowComponents.tsx @@ -8,7 +8,7 @@ import { ensureRequired } from '@/helpers/ensureRequired' /** * Props for {@link CreatePaymentFlow}. * - * @public + * @alpha */ export interface CreatePaymentFlowProps extends BaseComponentInterface { /** The associated company identifier. */ diff --git a/src/components/Contractor/Payments/PaymentFlow/GUIDE.md b/src/components/Contractor/Payments/PaymentFlow/GUIDE.md index f3f0a6372..427cbecbc 100644 --- a/src/components/Contractor/Payments/PaymentFlow/GUIDE.md +++ b/src/components/Contractor/Payments/PaymentFlow/GUIDE.md @@ -16,22 +16,23 @@ The typical step sequence when composing the blocks manually: The flow is a hub-and-spoke loop with no terminal state โ€” the payments list is the landing screen, and every path returns to it: -- **Create a payment** โ€” `PaymentsList` hands off to `CreatePaymentFlow`, which returns to the list once the flow completes. -- **View history** โ€” `PaymentsList` hands off to `ViewHistoryFlow`, which returns to the list once the flow completes. +- **Create a payment** โ€” `PaymentsList` โ†’ `CreatePayment` โ†’ `PaymentSummary`, then back to the list. +- **View history** โ€” `PaymentsList` โ†’ `PaymentHistory` โ†’ `PaymentStatement`; the history view can also cancel a payment and return to the list. - **Respond to a request** โ€” `PaymentsList` opens the embedded `InformationRequestsFlow`, returning to the list once the request is submitted or cancelled. -Breadcrumbs navigate back to the list from anywhere inside `CreatePaymentFlow` or `ViewHistoryFlow`, and submitting wire-transfer details surfaces a success alert on the list screen. The diagram below shows the topology; the event behind each transition is listed in the events table above. +Breadcrumbs navigate back to any prior step, and submitting wire-transfer details surfaces a success alert on the list and summary screens. The diagram below shows the topology; the event behind each transition is listed in the events table above. ```mermaid flowchart LR start@{ shape: sm-circ } --> PaymentsList - PaymentsList <--> CreatePaymentFlow - PaymentsList <--> ViewHistoryFlow - PaymentsList <--> InformationRequests["InformationRequests.
InformationRequestsFlow"] + PaymentsList --> CreatePayment --> PaymentSummary --> PaymentsList + + PaymentsList --> PaymentHistory --> PaymentStatement + PaymentHistory --> PaymentsList + + PaymentsList --> InformationRequests["InformationRequests.
InformationRequestsFlow"] --> PaymentsList - class CreatePaymentFlow flow - class ViewHistoryFlow flow class InformationRequests flow ``` diff --git a/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.tsx b/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.tsx index 7fc5d550b..fca997925 100644 --- a/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.tsx +++ b/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.tsx @@ -34,8 +34,10 @@ import { buildBreadcrumbs } from '@/helpers/breadcrumbHelpers' * * @components * - {@link PaymentsList} - * - {@link CreatePaymentFlow} - * - {@link ViewHistoryFlow} + * - {@link CreatePayment} + * - {@link PaymentSummary} + * - {@link PaymentHistory} + * - {@link PaymentStatement} * - {@link InformationRequests.InformationRequestsFlow | InformationRequestsFlow} * * @param props - See {@link PaymentFlowProps}. diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx b/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx index b950ff0b9..e0f5f9963 100644 --- a/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx +++ b/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx @@ -49,7 +49,7 @@ export interface ViewHistoryInternalFlowProps extends ViewHistoryFlowProps { * * @param props - See {@link ViewHistoryFlowProps}. * @returns The composed view-history flow. - * @public + * @alpha * * @example * ```tsx title="App.tsx" diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx b/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx index 585fabfd3..fe52cade1 100644 --- a/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx +++ b/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx @@ -7,7 +7,7 @@ import { ensureRequired } from '@/helpers/ensureRequired' /** * Props for {@link ViewHistoryFlow}. * - * @public + * @alpha */ export interface ViewHistoryFlowProps extends BaseComponentInterface { /** Identifier of the payment group to inspect. */ From 30515b2a8dc6f806567f8f8064ab49a259746f59 Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Tue, 21 Jul 2026 18:02:22 -0700 Subject: [PATCH 3/8] chore: update derived files Co-Authored-By: Claude Sonnet 5 --- .reports/embedded-react-sdk.api.md | 8 +- docs/reference/blocks.md | 2 - docs/reference/contractor/index.mdx | 2 +- .../management/create-payment-flow.md | 92 ------------------ docs/reference/contractor/management/index.md | 2 - .../contractor/management/payment-flow.md | 31 ++++-- .../management/view-history-flow.md | 94 ------------------- docs/reference/index.mdx | 2 +- docs/reference/workflows.md | 2 - 9 files changed, 27 insertions(+), 208 deletions(-) delete mode 100644 docs/reference/contractor/management/create-payment-flow.md delete mode 100644 docs/reference/contractor/management/view-history-flow.md diff --git a/.reports/embedded-react-sdk.api.md b/.reports/embedded-react-sdk.api.md index d4c74983b..f8dec361e 100644 --- a/.reports/embedded-react-sdk.api.md +++ b/.reports/embedded-react-sdk.api.md @@ -2076,10 +2076,10 @@ type CreatableTimeOffPolicyType = Extract; // @public function CreatePayment(props: CreatePaymentProps): JSX; -// @public +// @alpha function CreatePaymentFlow(props: CreatePaymentFlowProps): JSX; -// @public +// @alpha interface CreatePaymentFlowProps extends BaseComponentInterface { companyId: string; } @@ -6216,10 +6216,10 @@ export type UseWorkAddressFormResult = HookLoadingResult | UseWorkAddressFormRea // @public export type ValidationMessages = Record & Partial>; -// @public +// @alpha function ViewHistoryFlow(props: ViewHistoryFlowProps): JSX; -// @public +// @alpha interface ViewHistoryFlowProps extends BaseComponentInterface { paymentId: string; } diff --git a/docs/reference/blocks.md b/docs/reference/blocks.md index 0a052c09c..f7fbc30c6 100644 --- a/docs/reference/blocks.md +++ b/docs/reference/blocks.md @@ -252,8 +252,6 @@ shape mixed into every public SDK feature component. - [`SignatureFormProps`](contractor/onboarding/blocks.md#signatureformprops) - [`ManagementContractorListProps`](contractor/management/blocks.md#managementcontractorlistprops) - [`PaymentFlowProps`](contractor/management/payment-flow.md#paymentflowprops) -- [`CreatePaymentFlowProps`](contractor/management/create-payment-flow.md#createpaymentflowprops) -- [`ViewHistoryFlowProps`](contractor/management/view-history-flow.md#viewhistoryflowprops) - [`PaymentsListProps`](contractor/management/blocks.md#paymentslistprops) - [`CreatePaymentProps`](contractor/management/blocks.md#createpaymentprops) - [`PaymentHistoryProps`](contractor/management/blocks.md#paymenthistoryprops) diff --git a/docs/reference/contractor/index.mdx b/docs/reference/contractor/index.mdx index d2dabbb3d..2f488cc62 100644 --- a/docs/reference/contractor/index.mdx +++ b/docs/reference/contractor/index.mdx @@ -34,7 +34,7 @@ Flows and blocks for managing contractors after onboarding โ€” payments, payment import { ContractorManagement } from '@gusto/embedded-react-sdk' ``` - + --- diff --git a/docs/reference/contractor/management/create-payment-flow.md b/docs/reference/contractor/management/create-payment-flow.md deleted file mode 100644 index 7b6334747..000000000 --- a/docs/reference/contractor/management/create-payment-flow.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -# Autogenerated by TypeDoc from TSDoc comments in the source code. -# To update content: edit TSDoc comments in src/. -# To update structure: edit docs-site/typedoc.config.ts or docs-site/plugins/typedoc-custom/. -# Then run `npm run docs:api:generate` to regenerate. -title: CreatePaymentFlow -description: CreatePaymentFlow reference. -sidebar_position: 2 -generated_by: typedoc -custom_edit_url: null ---- - -# CreatePaymentFlow - -Guided flow to create a contractor payment and review the resulting summary. - -## Remarks - -This is the inner flow that powers the create-payment spoke of `ContractorManagement.PaymentFlow`. -Render it directly when you have built your own payments landing page and want to hand the user -off to the standard create-payment experience without re-implementing it. The flow ships with -breadcrumb navigation and handles Fast ACH blockers and wire transfer requirements inline. - -## Example - -```tsx title="App.tsx" -import { ContractorManagement } from '@gusto/embedded-react-sdk' - -function MyApp() { - return ( - {}} - /> - ) -} -``` - -## CreatePaymentFlowProps - - - -Props for CreatePaymentFlow. - -| Property | Type | Description | -| ------ | ------ | ------ | -| `companyId` | `string` | The associated company identifier. | -| `onEvent` | [`OnEventType`](../../events.md#oneventtype)\<[`EventType`](../../events.md#eventtype), `unknown`\> | Callback invoked each time the component emits an event โ€” user interactions, successful API responses, step transitions, or errors. Receives the event type constant and an optional payload whose shape varies by event. See the [Event Handling guide](https://docs.gusto.com/embedded-payroll/docs/event-handling) and each component's event table for the full list of emitted events. | - -_Inherits `children`, `className`, `defaultValues`, `dictionary`, `FallbackComponent`, `LoaderComponent` from [BaseComponentInterface](../../blocks.md#basecomponentinterface)._ - -## Events - -| Event | Description | Data | -| ----- | ----------- | ---- | -| `contractor/payments/created` | Fired when a payment group is successfully created | The created `ContractorPaymentGroup` | -| `contractor/payments/exit` | Fired when the user completes the payment flow | `{ uuid?: string \| null }` | -| `payroll/wire/form/done` | Fired when wire transfer details are submitted | `{ wireInRequest: WireInRequest, confirmationAlert: { title: string, content?: string } }` | -| `breadcrumb/navigate` | Fired when the user clicks a breadcrumb to navigate back | `{ key: string, onNavigate: (ctx) => ctx }` | - -## Sub-components - -| Component | Description | -| ------ | ------ | -| [CreatePayment](blocks.md#createpayment) | Form for creating a contractor payment group, including date selection, per-contractor edits, preview, and submission blockers. | -| [PaymentSummary](blocks.md#paymentsummary) | Displays a summary of a created contractor payment group, including payment totals, debit information, contractor details, and wire transfer instructions when required. | - - -## Step flow - -`CreatePaymentFlow` has no hub of its own โ€” it's a straight line from creating a payment to reviewing the result. `CreatePayment` handles selecting a date, editing per-contractor amounts, and submitting; Fast ACH blockers and wire transfer requirements are handled inline. On success (`contractor/payments/created`) the flow hands off to `PaymentSummary`, which shows the created group, debit details, and wire instructions when required. - -```mermaid -flowchart LR - start@{ shape: sm-circ } --> CreatePayment - CreatePayment -->|"contractor/payments/created"| PaymentSummary - PaymentSummary -->|"contractor/payments/exit"| done@{ shape: fr-circ, label: " " } -``` - -The breadcrumb header (`breadcrumb/navigate`) returns to the payments list; submitting wire-transfer details (`payroll/wire/form/done`) surfaces a success alert on `PaymentSummary` without leaving the step. - - -## Endpoints - -| Method | Path | -| --- | --- | -| GET | [`/v1/companies/:companyId/bank_accounts`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_id-bank-accounts) | -| POST | [`/v1/companies/:companyId/contractor_payment_groups`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/post-v1-companies-company_id-contractor_payment_groups) | -| POST | [`/v1/companies/:companyId/contractor_payment_groups/preview`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/post-v1-companies-company_id-contractor_payment_groups-preview) | -| GET | [`/v1/companies/:companyUuid/contractors`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_uuid-contractors) | -| GET | [`/v1/companies/:companyUuid/payment_configs`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-company-payment-configs) | -| GET | [`/v1/contractor_payment_groups/:contractorPaymentGroupUuid`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-contractor_payment_groups-contractor_payment_group_id) | diff --git a/docs/reference/contractor/management/index.md b/docs/reference/contractor/management/index.md index c28a74718..3edad495a 100644 --- a/docs/reference/contractor/management/index.md +++ b/docs/reference/contractor/management/index.md @@ -22,9 +22,7 @@ import { ContractorManagement } from '@gusto/embedded-react-sdk' | Component | Description | | --------- | ----------- | -| [CreatePaymentFlow](create-payment-flow.md) | Guided flow to create a contractor payment and review the resulting summary. | | [PaymentFlow](payment-flow.md) | Hub for creating and managing contractor payments for a company. | -| [ViewHistoryFlow](view-history-flow.md) | Guided flow to inspect a contractor payment group's history and drill into an individual contractor's payment statement. | ## ๐Ÿงฉ Blocks diff --git a/docs/reference/contractor/management/payment-flow.md b/docs/reference/contractor/management/payment-flow.md index 21f65ab2c..2862fe1b3 100644 --- a/docs/reference/contractor/management/payment-flow.md +++ b/docs/reference/contractor/management/payment-flow.md @@ -81,8 +81,10 @@ _Inherits `children`, `className`, `defaultValues`, `dictionary`, `FallbackCompo | Component | Description | | ------ | ------ | | [PaymentsList](blocks.md#paymentslist) | Displays a list of contractor payment groups for a company. | -| [CreatePaymentFlow](create-payment-flow.md) | Guided flow to create a contractor payment and review the resulting summary. | -| [ViewHistoryFlow](view-history-flow.md) | Guided flow to inspect a contractor payment group's history and drill into an individual contractor's payment statement. | +| [CreatePayment](blocks.md#createpayment) | Form for creating a contractor payment group, including date selection, per-contractor edits, preview, and submission blockers. | +| [PaymentSummary](blocks.md#paymentsummary) | Displays a summary of a created contractor payment group, including payment totals, debit information, contractor details, and wire transfer instructions when required. | +| [PaymentHistory](blocks.md#paymenthistory) | Displays a contractor payment group, including each individual contractor payment, with actions to view details or cancel. | +| [PaymentStatement](blocks.md#paymentstatement) | Displays a single contractor's payment statement within a payment group, including wage breakdown, bonuses, reimbursements, and a receipt card for funded direct-deposit payments. | | [InformationRequests.InformationRequestsFlow](../../company/information-requests/information-requests-flow.md) | Hub for viewing and responding to outstanding information requests from Gusto. | @@ -90,22 +92,23 @@ _Inherits `children`, `className`, `defaultValues`, `dictionary`, `FallbackCompo The flow is a hub-and-spoke loop with no terminal state โ€” the payments list is the landing screen, and every path returns to it: -- **Create a payment** โ€” `PaymentsList` hands off to `CreatePaymentFlow`, which returns to the list once the flow completes. -- **View history** โ€” `PaymentsList` hands off to `ViewHistoryFlow`, which returns to the list once the flow completes. +- **Create a payment** โ€” `PaymentsList` โ†’ `CreatePayment` โ†’ `PaymentSummary`, then back to the list. +- **View history** โ€” `PaymentsList` โ†’ `PaymentHistory` โ†’ `PaymentStatement`; the history view can also cancel a payment and return to the list. - **Respond to a request** โ€” `PaymentsList` opens the embedded `InformationRequestsFlow`, returning to the list once the request is submitted or cancelled. -Breadcrumbs navigate back to the list from anywhere inside `CreatePaymentFlow` or `ViewHistoryFlow`, and submitting wire-transfer details surfaces a success alert on the list screen. The diagram below shows the topology; the event behind each transition is listed in the events table above. +Breadcrumbs navigate back to any prior step, and submitting wire-transfer details surfaces a success alert on the list and summary screens. The diagram below shows the topology; the event behind each transition is listed in the events table above. ```mermaid flowchart LR start@{ shape: sm-circ } --> PaymentsList - PaymentsList <--> CreatePaymentFlow - PaymentsList <--> ViewHistoryFlow - PaymentsList <--> InformationRequests["InformationRequests.
InformationRequestsFlow"] + PaymentsList --> CreatePayment --> PaymentSummary --> PaymentsList + + PaymentsList --> PaymentHistory --> PaymentStatement + PaymentHistory --> PaymentsList + + PaymentsList --> InformationRequests["InformationRequests.
InformationRequestsFlow"] --> PaymentsList - class CreatePaymentFlow flow - class ViewHistoryFlow flow class InformationRequests flow ``` @@ -144,6 +147,14 @@ When wire transfer is required: | Method | Path | | --- | --- | +| GET | [`/v1/companies/:companyId/bank_accounts`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_id-bank-accounts) | | GET | [`/v1/companies/:companyId/contractor_payment_groups`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_id-contractor_payment_groups) | +| POST | [`/v1/companies/:companyId/contractor_payment_groups`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/post-v1-companies-company_id-contractor_payment_groups) | +| POST | [`/v1/companies/:companyId/contractor_payment_groups/preview`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/post-v1-companies-company_id-contractor_payment_groups-preview) | +| DELETE | [`/v1/companies/:companyId/contractor_payments/:contractorPaymentId`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/delete-v1-companies-company_id-contractor_payment-contractor-payment) | +| GET | [`/v1/companies/:companyUuid/contractors`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_uuid-contractors) | | GET | [`/v1/companies/:companyUuid/information_requests`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-information-requests) | +| GET | [`/v1/companies/:companyUuid/payment_configs`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-company-payment-configs) | +| GET | [`/v1/contractor_payment_groups/:contractorPaymentGroupUuid`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-contractor_payment_groups-contractor_payment_group_id) | +| GET | [`/v1/contractor_payments/:contractorPaymentUuid/receipt`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-contractor_payments-contractor_payment_uuid-receipt) | | PUT | `/v1/information_requests/:informationRequestUuid/submit` | diff --git a/docs/reference/contractor/management/view-history-flow.md b/docs/reference/contractor/management/view-history-flow.md deleted file mode 100644 index 7a9e7adf0..000000000 --- a/docs/reference/contractor/management/view-history-flow.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -# Autogenerated by TypeDoc from TSDoc comments in the source code. -# To update content: edit TSDoc comments in src/. -# To update structure: edit docs-site/typedoc.config.ts or docs-site/plugins/typedoc-custom/. -# Then run `npm run docs:api:generate` to regenerate. -title: ViewHistoryFlow -description: ViewHistoryFlow reference. -sidebar_position: 2 -generated_by: typedoc -custom_edit_url: null ---- - -# ViewHistoryFlow - -Guided flow to inspect a contractor payment group's history and drill into an individual -contractor's payment statement. - -## Remarks - -This is the inner flow that powers the view-history spoke of `ContractorManagement.PaymentFlow`. -Render it directly when you have built your own payments landing page and want to hand the user -off to the standard history-viewing experience without re-implementing it. The flow ships with -breadcrumb navigation and lets the user cancel an individual payment from the history screen. - -## Example - -```tsx title="App.tsx" -import { ContractorManagement } from '@gusto/embedded-react-sdk' - -function MyApp() { - return ( - {}} - /> - ) -} -``` - -## ViewHistoryFlowProps - - - -Props for ViewHistoryFlow. - -| Property | Type | Description | -| ------ | ------ | ------ | -| `onEvent` | [`OnEventType`](../../events.md#oneventtype)\<[`EventType`](../../events.md#eventtype), `unknown`\> | Callback invoked each time the component emits an event โ€” user interactions, successful API responses, step transitions, or errors. Receives the event type constant and an optional payload whose shape varies by event. See the [Event Handling guide](https://docs.gusto.com/embedded-payroll/docs/event-handling) and each component's event table for the full list of emitted events. | -| `paymentId` | `string` | Identifier of the payment group to inspect. | - -_Inherits `children`, `className`, `defaultValues`, `dictionary`, `FallbackComponent`, `LoaderComponent` from [BaseComponentInterface](../../blocks.md#basecomponentinterface)._ - -## Events - -| Event | Description | Data | -| ----- | ----------- | ---- | -| `contractor/payments/view/details` | Fired when the user views a specific contractor payment | `{ contractor: Contractor, paymentGroupId: string }` | -| `contractor/payments/cancel` | Fired when a payment is cancelled | `{ paymentId: string }` | -| `breadcrumb/navigate` | Fired when the user clicks a breadcrumb to navigate back | `{ key: string, onNavigate: (ctx) => ctx }` | - -## Sub-components - -| Component | Description | -| ------ | ------ | -| [PaymentHistory](blocks.md#paymenthistory) | Displays a contractor payment group, including each individual contractor payment, with actions to view details or cancel. | -| [PaymentStatement](blocks.md#paymentstatement) | Displays a single contractor's payment statement within a payment group, including wage breakdown, bonuses, reimbursements, and a receipt card for funded direct-deposit payments. | - - -## Step flow - -`ViewHistoryFlow` centers on `PaymentHistory` as its hub: it shows a payment group's details and can either drill into an individual contractor's statement (`contractor/payments/view/details` โ†’ `PaymentStatement`) or cancel the group outright (`contractor/payments/cancel`), which exits the flow. - -```mermaid -flowchart LR - start@{ shape: sm-circ } --> PaymentHistory - PaymentHistory <-->|"contractor/payments/view/details"| PaymentStatement - PaymentHistory -->|"contractor/payments/cancel"| done@{ shape: fr-circ, label: " " } -``` - -The breadcrumb header (`breadcrumb/navigate`) returns from `PaymentStatement` to `PaymentHistory`, or exits the flow entirely from either step. - - -## Endpoints - -| Method | Path | -| --- | --- | -| GET | [`/v1/companies/:companyId/bank_accounts`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_id-bank-accounts) | -| POST | [`/v1/companies/:companyId/contractor_payment_groups`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/post-v1-companies-company_id-contractor_payment_groups) | -| POST | [`/v1/companies/:companyId/contractor_payment_groups/preview`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/post-v1-companies-company_id-contractor_payment_groups-preview) | -| DELETE | [`/v1/companies/:companyId/contractor_payments/:contractorPaymentId`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/delete-v1-companies-company_id-contractor_payment-contractor-payment) | -| GET | [`/v1/companies/:companyUuid/contractors`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-companies-company_uuid-contractors) | -| GET | [`/v1/companies/:companyUuid/payment_configs`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-company-payment-configs) | -| GET | [`/v1/contractor_payment_groups/:contractorPaymentGroupUuid`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-contractor_payment_groups-contractor_payment_group_id) | -| GET | [`/v1/contractor_payments/:contractorPaymentUuid/receipt`](https://docs.gusto.com/embedded-payroll/v2026-06-15/reference/get-v1-contractor_payments-contractor_payment_uuid-receipt) | diff --git a/docs/reference/index.mdx b/docs/reference/index.mdx index d417dae2b..813345c51 100644 --- a/docs/reference/index.mdx +++ b/docs/reference/index.mdx @@ -12,7 +12,7 @@ custom_edit_url: null ## Browse by domain - + --- diff --git a/docs/reference/workflows.md b/docs/reference/workflows.md index 62871a517..14bb22aff 100644 --- a/docs/reference/workflows.md +++ b/docs/reference/workflows.md @@ -17,9 +17,7 @@ Full multi-step user experiences as a single component โ€” the fastest path to a | Component | Description | | --- | --- | | [CompanyOnboarding.OnboardingFlow](company/onboarding/onboarding-flow) | Guided flow to onboard a company to Gusto. | -| [ContractorManagement.CreatePaymentFlow](contractor/management/create-payment-flow) | Guided flow to create a contractor payment and review the resulting summary. | | [ContractorManagement.PaymentFlow](contractor/management/payment-flow) | Hub for creating and managing contractor payments for a company. | -| [ContractorManagement.ViewHistoryFlow](contractor/management/view-history-flow) | Guided flow to inspect a contractor payment group's history and drill into an individual contractor's payment statement. | | [ContractorOnboarding.OnboardingFlow](contractor/onboarding/onboarding-flow) | Guided flow for admins to onboard a contractor to the company. | | [ContractorOnboarding.SelfOnboardingFlow](contractor/onboarding/self-onboarding-flow) | Guided flow for contractors to complete their own onboarding. | | [EmployeeManagement.DashboardFlow](employee/management/dashboard-flow) | Hub for viewing and managing a single employee's profile, pay, and documents. | From b7c2120e6b75a560fa0a8bab0b366763c1b096af Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Tue, 21 Jul 2026 18:25:41 -0700 Subject: [PATCH 4/8] docs(PaymentFlow): fix CreatePaymentFlow/ViewHistoryFlow step-flow diagrams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per .claude/doc-guides/flows.md diagram guidance: - CreatePaymentFlow is a guided flow (linear, real exit) โ€” switch its diagram from LR to TD. - ViewHistoryFlow: contractor/payments/cancel doesn't exit the flow โ€” traced to PaymentHistory.handleCancelPayment, which just refetches and stays on PaymentHistory. Drop the done node/edge; this flow has no exit. Split the PaymentHistory<->PaymentStatement edge into its two real, distinct events (view/details forward, breadcrumb/navigate back) instead of one bidirectional edge showing only the forward label. Corrected prose that mischaracterized cancel as exiting "the group outright." These flows stay @alpha and excluded from the generated reference, so there's no docs/reference diff to regenerate. Co-Authored-By: Claude Sonnet 5 --- .../Contractor/Payments/CreatePaymentFlow/GUIDE.md | 2 +- .../Contractor/Payments/ViewHistoryFlow/GUIDE.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/GUIDE.md b/src/components/Contractor/Payments/CreatePaymentFlow/GUIDE.md index 602813488..0d073ec80 100644 --- a/src/components/Contractor/Payments/CreatePaymentFlow/GUIDE.md +++ b/src/components/Contractor/Payments/CreatePaymentFlow/GUIDE.md @@ -7,7 +7,7 @@ `CreatePaymentFlow` has no hub of its own โ€” it's a straight line from creating a payment to reviewing the result. `CreatePayment` handles selecting a date, editing per-contractor amounts, and submitting; Fast ACH blockers and wire transfer requirements are handled inline. On success (`contractor/payments/created`) the flow hands off to `PaymentSummary`, which shows the created group, debit details, and wire instructions when required. ```mermaid -flowchart LR +flowchart TD start@{ shape: sm-circ } --> CreatePayment CreatePayment -->|"contractor/payments/created"| PaymentSummary PaymentSummary -->|"contractor/payments/exit"| done@{ shape: fr-circ, label: " " } diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md b/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md index f68e4fc65..ef7f1527e 100644 --- a/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md +++ b/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md @@ -4,13 +4,13 @@ ## Step flow -`ViewHistoryFlow` centers on `PaymentHistory` as its hub: it shows a payment group's details and can either drill into an individual contractor's statement (`contractor/payments/view/details` โ†’ `PaymentStatement`) or cancel the group outright (`contractor/payments/cancel`), which exits the flow. +`ViewHistoryFlow` centers on `PaymentHistory` as its hub: it shows a payment group's details, lets you cancel an individual contractor's payment in place (`contractor/payments/cancel`), and can drill into an individual contractor's statement (`contractor/payments/view/details` โ†’ `PaymentStatement`). There's no exit from this flow โ€” cancelling refreshes the group's details without leaving the step. ```mermaid flowchart LR start@{ shape: sm-circ } --> PaymentHistory - PaymentHistory <-->|"contractor/payments/view/details"| PaymentStatement - PaymentHistory -->|"contractor/payments/cancel"| done@{ shape: fr-circ, label: " " } + PaymentHistory -->|"contractor/payments/view/details"| PaymentStatement + PaymentStatement -->|"breadcrumb/navigate"| PaymentHistory ``` -The breadcrumb header (`breadcrumb/navigate`) returns from `PaymentStatement` to `PaymentHistory`, or exits the flow entirely from either step. +Cancelling a contractor's payment (`contractor/payments/cancel`, fired from `PaymentHistory`) removes it from the group and refreshes the view without leaving the step. From f624da9acadecd24a62cf4750b6634e1715943ee Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 22 Jul 2026 18:36:23 +0000 Subject: [PATCH 5/8] chore: update derived files --- docs/reference/contractor/index.mdx | 2 +- docs/reference/index.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/contractor/index.mdx b/docs/reference/contractor/index.mdx index 2f488cc62..a6ed387ae 100644 --- a/docs/reference/contractor/index.mdx +++ b/docs/reference/contractor/index.mdx @@ -34,7 +34,7 @@ Flows and blocks for managing contractors after onboarding โ€” payments, payment import { ContractorManagement } from '@gusto/embedded-react-sdk' ``` - + --- diff --git a/docs/reference/index.mdx b/docs/reference/index.mdx index 813345c51..6a8b03a7e 100644 --- a/docs/reference/index.mdx +++ b/docs/reference/index.mdx @@ -12,7 +12,7 @@ custom_edit_url: null ## Browse by domain - + --- From 234929dcb94376666bc6c22b341d94f52dde61f3 Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Wed, 22 Jul 2026 12:04:17 -0700 Subject: [PATCH 6/8] chore: update generated registry data --- sdk-app/src/generated-registry-data.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sdk-app/src/generated-registry-data.ts b/sdk-app/src/generated-registry-data.ts index 9f66eaf60..b3650c426 100644 --- a/sdk-app/src/generated-registry-data.ts +++ b/sdk-app/src/generated-registry-data.ts @@ -20,12 +20,15 @@ export const ENTITY_REQUIREMENTS: Record = { 'CompanyOnboarding.StateTaxes': ['companyId'], 'CompanyOnboarding.StateTaxesForm': ['companyId'], 'CompanyOnboarding.StateTaxesList': ['companyId'], + 'ContractorManagement.ContractorList': ['companyId'], 'ContractorManagement.CreatePayment': ['companyId'], + 'ContractorManagement.CreatePaymentFlow': ['companyId'], 'ContractorManagement.PaymentFlow': ['companyId'], 'ContractorManagement.PaymentHistory': ['companyId'], 'ContractorManagement.PaymentStatement': ['companyId'], 'ContractorManagement.PaymentSummary': ['companyId'], 'ContractorManagement.PaymentsList': ['companyId'], + 'ContractorManagement.ViewHistoryFlow': ['companyId'], 'ContractorOnboarding.Address': ['contractorId'], 'ContractorOnboarding.ContractorList': ['companyId'], 'ContractorOnboarding.ContractorProfile': ['companyId'], @@ -142,6 +145,7 @@ export const ADDITIONAL_REQUIRED_PROPS: Record = { 'ContractorManagement.PaymentHistory': ['paymentId'], 'ContractorManagement.PaymentStatement': ['paymentGroupId', 'contractorUuid'], 'ContractorManagement.PaymentSummary': ['paymentGroupId'], + 'ContractorManagement.ViewHistoryFlow': ['paymentId'], 'ContractorOnboarding.SignatureForm': ['documentUuid'], 'EmployeeManagement.CompensationEditForm': ['jobId'], 'EmployeeManagement.DocumentManager': ['formId'], From 5b6e3b3b798a69f1dcb7e325777eeca694a0b5ba Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Wed, 22 Jul 2026 12:37:52 -0700 Subject: [PATCH 7/8] feat(sdk-app): add payment as a first-class entity ID ViewHistoryFlow/PaymentHistory require paymentId, but it wasn't recognized as an entity kind, so it had no sidebar combobox, no auto-fetch, and no manual-mode field -- there was no way to supply it short of hand-editing localStorage. Classify paymentId as an entity ID (sdk-app/scripts/analyze-component-props.ts), wire it through the Settings sidebar, auto/manual entity plumbing, and demo provisioning, same as employeeId/contractorId/payrollId. Co-Authored-By: Claude Sonnet 5 --- sdk-app/scripts/analyze-component-props.ts | 2 +- sdk-app/src/App.tsx | 3 ++ sdk-app/src/DemoSettingsPanel.tsx | 29 ++++++++++++++- sdk-app/src/entity-config.ts | 3 ++ sdk-app/src/entityFormatters.ts | 12 +++++++ sdk-app/src/generated-registry-data.ts | 6 ++-- sdk-app/src/useEntities.ts | 7 +++- sdk-app/src/useEntityCatalog.ts | 42 ++++++++++++++++++---- sdk-app/src/useManualConfig.ts | 2 ++ 9 files changed, 92 insertions(+), 14 deletions(-) diff --git a/sdk-app/scripts/analyze-component-props.ts b/sdk-app/scripts/analyze-component-props.ts index 57c93fa9f..5367138ba 100644 --- a/sdk-app/scripts/analyze-component-props.ts +++ b/sdk-app/scripts/analyze-component-props.ts @@ -22,7 +22,7 @@ import { Project, SyntaxKind, type Type } from 'ts-morph' const ROOT = resolve(import.meta.dirname, '../..') const OUTPUT = resolve(import.meta.dirname, '../src/generated-registry-data.ts') -const ENTITY_ID_PATTERN = /^(company|employee|contractor|payroll|request)Id$/ +const ENTITY_ID_PATTERN = /^(company|employee|contractor|payroll|request|payment)Id$/ const NAMESPACES: Record = { CompanyOnboarding: ['src/components/Company/exports/companyOnboarding.ts'], diff --git a/sdk-app/src/App.tsx b/sdk-app/src/App.tsx index 08df2149a..fc4541e4a 100644 --- a/sdk-app/src/App.tsx +++ b/sdk-app/src/App.tsx @@ -65,6 +65,7 @@ function entitiesFromManualConfig(config: ManualConfig): EntityIds { payrollId: config.payrollId, formId: config.formId, requestId: config.requestId, + paymentId: config.paymentId, } } @@ -87,6 +88,7 @@ export function App() { manual.config.payrollId, manual.config.formId, manual.config.requestId, + manual.config.paymentId, ], ) const entityCatalog = useEntityCatalog(isManual ? '' : entities.companyId) @@ -212,6 +214,7 @@ export function App() { payrollId: result.entities.payrollId || '', formId: result.entities.formId || '', requestId: '', + paymentId: result.entities.paymentId || '', }) window.location.reload() } diff --git a/sdk-app/src/DemoSettingsPanel.tsx b/sdk-app/src/DemoSettingsPanel.tsx index 07c4c6cb5..796a704fa 100644 --- a/sdk-app/src/DemoSettingsPanel.tsx +++ b/sdk-app/src/DemoSettingsPanel.tsx @@ -48,6 +48,7 @@ const MANUAL_FIELDS: { key: keyof ManualConfig; label: string; required?: boolea { key: 'payrollId', label: 'Payroll ID' }, { key: 'formId', label: 'Form ID' }, { key: 'requestId', label: 'Request ID' }, + { key: 'paymentId', label: 'Payment ID' }, ] interface EntityComboboxProps { @@ -391,7 +392,8 @@ export function DemoSettingsPanel({ entities.contractorId !== confirmedSnapshot.current.contractorId || entities.payrollId !== confirmedSnapshot.current.payrollId || entities.requestId !== confirmedSnapshot.current.requestId || - entities.formId !== confirmedSnapshot.current.formId + entities.formId !== confirmedSnapshot.current.formId || + entities.paymentId !== confirmedSnapshot.current.paymentId const displayEnv = env === 'localzp' ? 'local' : env @@ -775,6 +777,31 @@ export function DemoSettingsPanel({ } /> + { + onUpdateEntity('paymentId', value) + }} + trailing={ + <> + + + + } + /> + = { 'ContractorManagement.CreatePayment': ['companyId'], 'ContractorManagement.CreatePaymentFlow': ['companyId'], 'ContractorManagement.PaymentFlow': ['companyId'], - 'ContractorManagement.PaymentHistory': ['companyId'], + 'ContractorManagement.PaymentHistory': ['paymentId'], 'ContractorManagement.PaymentStatement': ['companyId'], 'ContractorManagement.PaymentSummary': ['companyId'], 'ContractorManagement.PaymentsList': ['companyId'], - 'ContractorManagement.ViewHistoryFlow': ['companyId'], + 'ContractorManagement.ViewHistoryFlow': ['paymentId'], 'ContractorOnboarding.Address': ['contractorId'], 'ContractorOnboarding.ContractorList': ['companyId'], 'ContractorOnboarding.ContractorProfile': ['companyId'], @@ -142,10 +142,8 @@ export const ENTITY_REQUIREMENTS: Record = { export const ADDITIONAL_REQUIRED_PROPS: Record = { 'CompanyOnboarding.SignatureForm': ['formId'], 'CompanyOnboarding.StateTaxesForm': ['state'], - 'ContractorManagement.PaymentHistory': ['paymentId'], 'ContractorManagement.PaymentStatement': ['paymentGroupId', 'contractorUuid'], 'ContractorManagement.PaymentSummary': ['paymentGroupId'], - 'ContractorManagement.ViewHistoryFlow': ['paymentId'], 'ContractorOnboarding.SignatureForm': ['documentUuid'], 'EmployeeManagement.CompensationEditForm': ['jobId'], 'EmployeeManagement.DocumentManager': ['formId'], diff --git a/sdk-app/src/useEntities.ts b/sdk-app/src/useEntities.ts index 2232839b7..863311795 100644 --- a/sdk-app/src/useEntities.ts +++ b/sdk-app/src/useEntities.ts @@ -14,6 +14,7 @@ function getEnvDefaults(): EntityIds { payrollId: import.meta.env.VITE_PAYROLL_ID || '', requestId: import.meta.env.VITE_REQUEST_ID || '', formId: import.meta.env.VITE_FORM_ID || '', + paymentId: import.meta.env.VITE_PAYMENT_ID || '', } } @@ -46,7 +47,7 @@ function saveToStorage(ids: EntityIds) { } function hasMissingEntities(ids: EntityIds): boolean { - return !ids.employeeId || !ids.contractorId || !ids.payrollId || !ids.formId + return !ids.employeeId || !ids.contractorId || !ids.payrollId || !ids.formId || !ids.paymentId } export function useEntities() { @@ -60,6 +61,7 @@ export function useEntities() { payrollId: stored.payrollId || defaults.payrollId, requestId: stored.requestId || defaults.requestId, formId: stored.formId || defaults.formId, + paymentId: stored.paymentId || defaults.paymentId, } }) @@ -98,6 +100,9 @@ export function useEntities() { ? data.payrollId || prev.payrollId : prev.payrollId || data.payrollId || '', formId: overwrite ? data.formId || prev.formId : prev.formId || data.formId || '', + paymentId: overwrite + ? data.paymentId || prev.paymentId + : prev.paymentId || data.paymentId || '', })) } } catch { diff --git a/sdk-app/src/useEntityCatalog.ts b/sdk-app/src/useEntityCatalog.ts index 11e5271f7..37fc79512 100644 --- a/sdk-app/src/useEntityCatalog.ts +++ b/sdk-app/src/useEntityCatalog.ts @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react' import { type EntityOption, type RawContractor, + type RawContractorPaymentGroup, type RawEmployee, type RawForm, type RawInformationRequest, @@ -10,6 +11,7 @@ import { formatEmployee, formatForm, formatInformationRequestType, + formatPaymentGroup, formatPayPeriod, } from './entityFormatters' @@ -17,6 +19,7 @@ export interface EntityCatalog { employees: EntityOption[] contractors: EntityOption[] payrolls: EntityOption[] + payments: EntityOption[] informationRequests: EntityOption[] forms: EntityOption[] isLoading: boolean @@ -26,6 +29,7 @@ const EMPTY_CATALOG: EntityCatalog = { employees: [], contractors: [], payrolls: [], + payments: [], informationRequests: [], forms: [], isLoading: false, @@ -71,13 +75,23 @@ export function useEntityCatalog(companyId: string): EntityCatalog { end_date: toIso(endDate), per: '100', }) - const [employees, contractors, payrolls, informationRequests, forms] = await Promise.all([ - fetchList(`${base}/employees`, controller.signal), - fetchList(`${base}/contractors`, controller.signal), - fetchList(`${base}/payrolls?${payrollsQuery.toString()}`, controller.signal), - fetchList(`${base}/information_requests`, controller.signal), - fetchList(`${base}/forms`, controller.signal), - ]) + const paymentsQuery = new URLSearchParams({ + start_date: toIso(startDate), + end_date: toIso(endDate), + per: '100', + }) + const [employees, contractors, payrolls, payments, informationRequests, forms] = + await Promise.all([ + fetchList(`${base}/employees`, controller.signal), + fetchList(`${base}/contractors`, controller.signal), + fetchList(`${base}/payrolls?${payrollsQuery.toString()}`, controller.signal), + fetchList( + `${base}/contractor_payment_groups?${paymentsQuery.toString()}`, + controller.signal, + ), + fetchList(`${base}/information_requests`, controller.signal), + fetchList(`${base}/forms`, controller.signal), + ]) if (controller.signal.aborted) return @@ -111,6 +125,20 @@ export function useEntityCatalog(companyId: string): EntityCatalog { }, } }), + payments: payments + .filter(p => !!p.uuid) + .map(p => { + const isFunded = p.status === 'Funded' + return { + value: p.uuid as string, + primary: formatPaymentGroup(p), + secondary: p.uuid as string, + badge: { + label: p.status || 'Unknown', + tone: isFunded ? ('processed' as const) : ('unprocessed' as const), + }, + } + }), informationRequests: informationRequests .filter(r => !!r.uuid) .map(r => ({ diff --git a/sdk-app/src/useManualConfig.ts b/sdk-app/src/useManualConfig.ts index e5746f7f0..83d671062 100644 --- a/sdk-app/src/useManualConfig.ts +++ b/sdk-app/src/useManualConfig.ts @@ -10,6 +10,7 @@ export interface ManualConfig { payrollId: string formId: string requestId: string + paymentId: string } interface PersistedShape { @@ -30,6 +31,7 @@ const EMPTY_CONFIG: ManualConfig = { payrollId: '', formId: '', requestId: '', + paymentId: '', } function readPersisted(): PersistedShape { From 6d63f9c16471c9688134ffd618a1a0cf2da2860c Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Wed, 22 Jul 2026 12:38:42 -0700 Subject: [PATCH 8/8] feat(sdk-app): seed a historical contractor payment on demo creation Demo companies were provisioned with employees/contractors/payrolls but never a contractor payment, so ViewHistoryFlow had nothing to show until you manually created one via PaymentFlow first. When a freshly provisioned demo has a contractor but no paymentId, createDemoAndProvision now records a past-dated "Historical Payment" for it -- record-only, no funding/ACH lead time, so it posts synchronously. Best-effort: a seed failure doesn't fail demo creation. Co-Authored-By: Claude Sonnet 5 --- sdk-app/scripts/demo-provisioner.ts | 59 +++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/sdk-app/scripts/demo-provisioner.ts b/sdk-app/scripts/demo-provisioner.ts index 6c322d5d3..50a334258 100644 --- a/sdk-app/scripts/demo-provisioner.ts +++ b/sdk-app/scripts/demo-provisioner.ts @@ -58,6 +58,51 @@ export interface DemoResult { demoType: string } +/** + * Records a past-dated "Historical Payment" for the given contractor so freshly + * provisioned demos have at least one contractor payment group to inspect + * (e.g. via ContractorManagement.ViewHistoryFlow). Historical payments are + * record-only -- no funding or ACH lead time -- so they post synchronously. + */ +async function seedHistoricalContractorPayment( + proxyBase: string, + companyId: string, + contractorId: string, +): Promise { + const contractorRes = await fetch(`${proxyBase}/v1/contractors/${contractorId}`, { + signal: AbortSignal.timeout(10000), + }) + if (!contractorRes.ok) return '' + const contractor = (await contractorRes.json()) as { wage_type?: string } + + const checkDate = new Date() + checkDate.setDate(checkDate.getDate() - 14) + + const paymentRes = await fetch( + `${proxyBase}/v1/companies/${companyId}/contractor_payment_groups`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + signal: AbortSignal.timeout(10000), + body: JSON.stringify({ + check_date: checkDate.toISOString().slice(0, 10), + creation_token: `sdk-app-seed-${companyId}-${contractorId}`, + contractor_payments: [ + { + contractor_uuid: contractorId, + payment_method: 'Historical Payment', + ...(contractor.wage_type === 'Hourly' ? { hours: '10' } : { wage: '500.00' }), + }, + ], + }), + }, + ) + if (!paymentRes.ok) return '' + + const created = (await paymentRes.json()) as { contractor_payment_group?: { uuid?: string } } + return created.contractor_payment_group?.uuid || '' +} + export async function createDemoAndProvision( gwsFlowsHost: string, demoType: string, @@ -150,6 +195,20 @@ export async function createDemoAndProvision( if (companyId) { onProgress?.('Fetching entity IDs...') entities = await fetchEntityIds(proxyBase, companyId) + + if (entities.contractorId && !entities.paymentId) { + onProgress?.('Seeding a historical contractor payment...') + try { + const paymentId = await seedHistoricalContractorPayment( + proxyBase, + companyId, + entities.contractorId, + ) + if (paymentId) entities.paymentId = paymentId + } catch { + // Best-effort seed; demo creation should not fail if this errors. + } + } } return { flowToken, companyId, entities, demoType }