Skip to content

Comments

Add new unified OrderCaptureDialog with amount options#6200

Merged
witoszekdev merged 102 commits intomainfrom
enable-granular-capture
Jan 14, 2026
Merged

Add new unified OrderCaptureDialog with amount options#6200
witoszekdev merged 102 commits intomainfrom
enable-granular-capture

Conversation

@mirekm
Copy link
Member

@mirekm mirekm commented Dec 12, 2025

Summary

  • New unified OrderCaptureDialog for both Legacy Payments and Transactions APIs
  • Shows order context (total, captured so far, balance due) and transaction context (available to capture, already captured)
  • Handles authorization states: full, partial, none, and fully captured
  • Outcome prediction shows resulting order status before confirming (e.g., "This will result in a Fully captured order")
  • Supports currency-aware decimal input with comma/dot normalization
  • Displays server errors from both API types
  • New shared utilities in PriceField/utils.ts: normalizeDecimalSeparator, parseDecimalValue, limitDecimalPlaces
  • Fixed Callout component dark mode colors using theme-aware getStatusColor

Test plan

  • Legacy API: Capture works on orders with totalAuthorized > 0
  • Transactions API: Charge action works on transactions with authorizedAmount > 0
  • Dialog correctly shows partial authorization warning when auth < balance
  • Outcome prediction updates based on selected amount
  • Decimal input respects currency precision (e.g., JPY = 0 decimals)
  • Error messages display correctly on failed capture

mirekm added 29 commits December 5, 2025 18:38
When no products are available in the channel or channel is inactive.
- Create new OrderCaptureDialog component using macaw-ui-next
- Supports three capture options: order total, authorized amount, custom
- Shows contextual warnings when authorized differs from total
- Integrate with both Legacy Payments API and Transactions API flows
- Include development overrides for testing (to be removed)
@mirekm mirekm requested a review from a team as a code owner December 12, 2025 12:58
Copilot AI review requested due to automatic review settings January 13, 2026 07:51
@mirekm mirekm removed the request for review from witoszekdev January 13, 2026 07:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Member

@witoszekdev witoszekdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are my comments from other PR that had changes from this one by accident, these are still valid since the diff is the same:

Copilot AI review requested due to automatic review settings January 14, 2026 14:27
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +420 to +437
<Input
size="small"
type="text"
inputMode="decimal"
value={customAmountInput}
onChange={handleCustomAmountChange}
error={showCustomAmountError}
disabled={
authStatus === "none" ||
authStatus === "charged" ||
selectedOption !== "custom"
}
endAdornment={
<Text size={2} color="default2" marginRight={2}>
{currency}
</Text>
}
/>
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The custom amount input field lacks an associated label or aria-label attribute, which can impact accessibility for screen reader users. Consider adding an aria-label or associating it with a visible label element to describe its purpose.

Copilot uses AI. Check for mistakes.
Comment on lines 14 to 17
statusPartial: {
id: "ZUYQ+C",
defaultMessage: "Partial Authorisation",
description: "status pill for partial authorization",
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent spelling: The default message uses British English "authorisation" while the description uses American English "authorization". Consider using consistent spelling throughout (either "authorization" or "authorisation" for both).

Copilot uses AI. Check for mistakes.
Comment on lines +296 to +305
// Also check for Apollo errors (network errors, GraphQL execution errors)
// These are stored in opts.error, not in the mutation response data
const hasApolloError = !!opts.error;

return getMutationState(
opts.called,
opts.loading,
errors,
hasApolloError ? [{ error: opts.error }] : [],
);
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error array passed to getMutationState should contain actual error objects that match the expected structure. The current implementation passes [{ error: opts.error }] which will always evaluate as truthy (non-empty array) even though it doesn't contain proper error objects. This may cause the button state to show "error" even when opts.error is undefined or null.

Consider checking if opts.error actually exists before creating the array, or pass an empty array when there's no error.

Copilot uses AI. Check for mistakes.
Comment on lines +397 to +405
onSubmit={amount =>
orderTransactionAction
.mutate({
action: params.type,
transactionId: params.id,
amount,
})
.finally(() => closeModal())
}
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transaction capture dialog closes immediately after submission (via .finally(() => closeModal())), which means errors won't be visible to the user. The legacy payment capture implementation (lines 448-453) doesn't have this .finally() call, allowing errors to be displayed in the dialog before it's closed. Consider removing the .finally(() => closeModal()) call to match the legacy behavior and allow users to see error messages.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings January 14, 2026 14:47
@witoszekdev witoszekdev added test deployment Deploy Pull Request to *.saleor.rocks environment and removed test deployment Deploy Pull Request to *.saleor.rocks environment labels Jan 14, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +442 to +454
<OrderCaptureDialog
confirmButtonState={orderPaymentCapture.opts.status}
errors={orderPaymentCapture.opts.data?.orderCapture?.errors ?? []}
orderTotal={order?.total.gross ?? defaultZeroMoney}
authorizedAmount={order?.totalAuthorized ?? defaultZeroMoney}
onClose={closeModal}
onSubmit={amount =>
orderPaymentCapture.mutate({
amount,
id,
})
}
/>
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OrderCaptureDialog for legacy payment capture (action === "capture") is missing a key prop. The transaction-charge-action dialog has key={params.id} at line 389, but this one doesn't. Without a key prop, React won't remount the component when switching between different orders, causing stale state in the dialog. Add a unique key prop to force remounting when the order changes.

Copilot uses AI. Check for mistakes.
Comment on lines +395 to +407
<OrderCaptureDialog
confirmButtonState={orderPaymentCapture.opts.status}
errors={orderPaymentCapture.opts.data?.orderCapture?.errors ?? []}
orderTotal={order.total.gross}
authorizedAmount={order.totalAuthorized}
onClose={closeModal}
onSubmit={amount =>
orderPaymentCapture.mutate({
amount,
id,
})
}
/>
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OrderCaptureDialog for legacy payment capture (action === "capture") is missing a key prop. The transaction-charge-action dialog has key={params.id} at line 323, but this one doesn't. Without a key prop, React won't remount the component when switching between different orders, causing stale state in the dialog. Add a unique key prop to force remounting when the order changes.

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +102
export const limitDecimalPlaces = (value: string, maxDecimalPlaces: number): string => {
const normalized = normalizeDecimalSeparator(value);
const separator = value.includes(",") ? "," : ".";
const [integerPart, decimalPart] = normalized.split(".");

if (!decimalPart) {
return value;
}

if (maxDecimalPlaces === 0) {
return integerPart;
}

if (decimalPart.length > maxDecimalPlaces) {
return `${integerPart}${separator}${decimalPart.slice(0, maxDecimalPlaces)}`;
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the user types a value with thousand separators (e.g., "1,234.56"), the function will detect the separator as "," at line 90 because the value contains a comma, even though the decimal separator is actually a dot. This will cause the limited value to be reconstructed with the wrong separator (e.g., if limiting to 1 decimal, it might become "1234,5" instead of "1234.5"). Consider detecting the decimal separator by checking which separator appears last in the original value, or by checking the normalized value's decimal position.

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +31
jest.mock("@saleor/macaw-ui-next", () => ({
...(jest.requireActual("@saleor/macaw-ui-next") as object),
useTheme: () => ({ theme: "default" }),
}));
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mock for useTheme returns { theme: "default" }, but the actual useTheme hook from macaw-ui-next likely returns a theme value of type DefaultTheme which should be either "defaultLight" or "defaultDark", not "default". This could cause tests to fail or produce incorrect results when testing theme-dependent functionality. Verify the correct theme value by checking the DefaultTheme type definition or update to use "defaultLight" which is the typical default.

Copilot uses AI. Check for mistakes.
Copy link
Member

@witoszekdev witoszekdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feature wise it works fine, but I think we should rethink logic for displaying button to open this modal.

For example we can request capture via API when it's in TransactionItem.actions, but after this change we hide capture button for Transaction:

Image Image Image

@witoszekdev witoszekdev merged commit f824411 into main Jan 14, 2026
13 of 14 checks passed
@witoszekdev witoszekdev deleted the enable-granular-capture branch January 14, 2026 16:44
This was referenced Jan 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test deployment Deploy Pull Request to *.saleor.rocks environment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants