- {paymentInstrument.success ? (
+ {issuerType != null ? (
{paymentMethodName}
{" · "}
- {paymentInstrument.data.card_type != null ? (
+ {cardType != null ? (
- {paymentInstrument.data.card_type}{" "}
- {paymentInstrument.data.issuer_type}
- {paymentInstrument.data.card_last_digits != null && (
+ {cardType} {issuerType}
+ {cardLastDigits != null && (
- ··{paymentInstrument.data.card_last_digits}
+ ··{cardLastDigits}
+
+ )}
+ {cardExpiry != null && (
+
+ {`${t("common.card_expires")} `}
+ {cardExpiry}
)}
- {paymentInstrument.data.card_expiry_month != null &&
- paymentInstrument.data.card_expiry_year != null && (
-
- {`${t("common.card_expires")} `}
- {paymentInstrument.data.card_expiry_month}/
- {paymentInstrument.data.card_expiry_year.slice(2)}
-
- )}
) : (
- paymentInstrument.data.issuer_type
+ issuerType
)}
@@ -258,3 +254,56 @@ function getPaymentMethodNameFromCustomerPaymentSource(
return paymentMethodName
}
+
+/**
+ * Extracts payment instrument details from an Order or CustomerPaymentSource resource.
+ * Returns individual parts, all of which may be `undefined` if not available.
+ */
+export function getPaymentInstrumentDetails(resource: PaymentMethodResource): {
+ paymentMethodName: string | undefined
+ cardType: string | undefined
+ issuerType: string | undefined
+ cardLastDigits: string | undefined
+ cardExpiry: string | undefined
+} {
+ const rawName =
+ "payment_method" in resource
+ ? resource.payment_method?.name
+ : resource.type === "customer_payment_sources"
+ ? getPaymentMethodNameFromCustomerPaymentSource(resource)
+ : undefined
+ const paymentMethodName = rawName ?? undefined
+
+ const paymentInstrument = paymentInstrumentType.safeParse(
+ resource.payment_source?.payment_instrument,
+ )
+
+ if (!paymentInstrument.success) {
+ return {
+ paymentMethodName,
+ cardType: undefined,
+ issuerType: undefined,
+ cardLastDigits: undefined,
+ cardExpiry: undefined,
+ }
+ }
+
+ const {
+ card_type,
+ issuer_type,
+ card_last_digits,
+ card_expiry_month,
+ card_expiry_year,
+ } = paymentInstrument.data
+
+ return {
+ paymentMethodName,
+ cardType: card_type,
+ issuerType: issuer_type,
+ cardLastDigits: card_last_digits,
+ cardExpiry:
+ card_expiry_month != null && card_expiry_year != null
+ ? `${card_expiry_month}/${card_expiry_year.slice(2)}`
+ : undefined,
+ }
+}
diff --git a/packages/docs/src/stories/resources/ResourcePaymentMethod.stories.tsx b/packages/docs/src/stories/resources/ResourcePaymentMethod.stories.tsx
index bed87adc2..93aabdb45 100644
--- a/packages/docs/src/stories/resources/ResourcePaymentMethod.stories.tsx
+++ b/packages/docs/src/stories/resources/ResourcePaymentMethod.stories.tsx
@@ -1,6 +1,9 @@
import type { Meta, StoryFn } from "@storybook/react-vite"
import { Icon } from "#ui/atoms/Icon"
-import { ResourcePaymentMethod } from "#ui/resources/ResourcePaymentMethod"
+import {
+ getPaymentInstrumentDetails,
+ ResourcePaymentMethod,
+} from "#ui/resources/ResourcePaymentMethod"
import {
customerPaymentSource,
orderWithoutPaymentSourceResponse,
@@ -77,3 +80,33 @@ WithActionButton.args = {
),
}
+
+/**
+ * `getPaymentInstrumentDetails` is a standalone helper that extracts payment data
+ * from an `Order` or `CustomerPaymentSource` resource as plain strings, without
+ * rendering any UI. Useful when you need those values independently.
+ */
+export const HelperGetPaymentInstrumentDetails: StoryFn = () => {
+ const examples = [
+ { label: "Order", resource: orderWithPaymentSourceResponse },
+ { label: "CustomerPaymentSource", resource: customerPaymentSource },
+ ]
+
+ return (
+
+ {examples.map(({ label, resource }) => {
+ const details = getPaymentInstrumentDetails(resource)
+ return (
+
+
+ {label}
+
+
{JSON.stringify(details, null, 2)}
+
+ )
+ })}
+
+ )
+}
+HelperGetPaymentInstrumentDetails.storyName =
+ "Helper: getPaymentInstrumentDetails"
From 074151b580ecf11f12ec51cbded70d310b0f1c0d Mon Sep 17 00:00:00 2001
From: gciotola <30926550+gciotola@users.noreply.github.com>
Date: Fri, 15 May 2026 10:53:21 +0200
Subject: [PATCH 2/3] fix: always format card last digits
---
.../src/ui/resources/ResourcePaymentMethod.test.tsx | 4 ++--
.../app-elements/src/ui/resources/ResourcePaymentMethod.tsx | 5 +++--
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/packages/app-elements/src/ui/resources/ResourcePaymentMethod.test.tsx b/packages/app-elements/src/ui/resources/ResourcePaymentMethod.test.tsx
index 371fb379b..e7d5aa066 100644
--- a/packages/app-elements/src/ui/resources/ResourcePaymentMethod.test.tsx
+++ b/packages/app-elements/src/ui/resources/ResourcePaymentMethod.test.tsx
@@ -85,7 +85,7 @@ describe("getPaymentInstrumentDetails", () => {
expect(result.paymentMethodName).toBe("Adyen Payment")
expect(result.cardType).toBe("Amex")
expect(result.issuerType).toBe("credit card")
- expect(result.cardLastDigits).toBe("4242")
+ expect(result.cardLastDigits).toBe("··4242")
expect(result.cardExpiry).toBeUndefined()
})
@@ -94,7 +94,7 @@ describe("getPaymentInstrumentDetails", () => {
expect(result.paymentMethodName).toBe("Braintree")
expect(result.cardType).toBe("Visa")
expect(result.issuerType).toBe("braintree")
- expect(result.cardLastDigits).toBe("0004")
+ expect(result.cardLastDigits).toBe("··0004")
expect(result.cardExpiry).toBe("10/30")
})
})
diff --git a/packages/app-elements/src/ui/resources/ResourcePaymentMethod.tsx b/packages/app-elements/src/ui/resources/ResourcePaymentMethod.tsx
index f06e6f2f3..a800992c3 100644
--- a/packages/app-elements/src/ui/resources/ResourcePaymentMethod.tsx
+++ b/packages/app-elements/src/ui/resources/ResourcePaymentMethod.tsx
@@ -98,7 +98,7 @@ export const ResourcePaymentMethod: FC
= ({
{cardType} {issuerType}
{cardLastDigits != null && (
- ··{cardLastDigits}
+ {cardLastDigits}
)}
{cardExpiry != null && (
@@ -300,7 +300,8 @@ export function getPaymentInstrumentDetails(resource: PaymentMethodResource): {
paymentMethodName,
cardType: card_type,
issuerType: issuer_type,
- cardLastDigits: card_last_digits,
+ cardLastDigits:
+ card_last_digits != null ? `··${card_last_digits}` : undefined,
cardExpiry:
card_expiry_month != null && card_expiry_year != null
? `${card_expiry_month}/${card_expiry_year.slice(2)}`
From c947323eafdcd69a4fb34351fddefa480909b7f4 Mon Sep 17 00:00:00 2001
From: gciotola <30926550+gciotola@users.noreply.github.com>
Date: Fri, 15 May 2026 11:38:49 +0200
Subject: [PATCH 3/3] fix: add optional cancel button label to ConfirmDialog
---
packages/app-elements/src/hooks/useConfirmDialog.tsx | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/app-elements/src/hooks/useConfirmDialog.tsx b/packages/app-elements/src/hooks/useConfirmDialog.tsx
index b00c78ce5..ed02c2d68 100644
--- a/packages/app-elements/src/hooks/useConfirmDialog.tsx
+++ b/packages/app-elements/src/hooks/useConfirmDialog.tsx
@@ -28,6 +28,8 @@ interface ConfirmDialogProps {
description?: React.ReactNode
/** Configuration for the confirm (primary action) button */
confirm: ConfirmDialogConfirmProps
+ /** Optional label for the cancel button - Default "Cancel" */
+ cancelLabel?: string
/**
* Message shown in the error toast when `confirm.onClick` rejects and overrides the API errors.
*/
@@ -53,6 +55,7 @@ const ConfirmDialog: FC = ({
title,
description,
confirm,
+ cancelLabel = "Cancel",
errorMessage,
successMessage,
successVariant = "default",
@@ -113,7 +116,7 @@ const ConfirmDialog: FC = ({
disabled={isPending}
fullWidth
>
- Cancel
+ {cancelLabel}