diff --git a/packages/manager/.changeset/pr-12654-changed-1754641803662.md b/packages/manager/.changeset/pr-12654-changed-1754641803662.md
new file mode 100644
index 00000000000..1980e22c8a9
--- /dev/null
+++ b/packages/manager/.changeset/pr-12654-changed-1754641803662.md
@@ -0,0 +1,5 @@
+---
+"@linode/manager": Changed
+---
+
+IAM permissions for billing payment methods ([#12654](https://github.com/linode/manager/pull/12654))
diff --git a/packages/manager/src/components/PaymentMethodRow/DeletePaymentMethodDialog.test.tsx b/packages/manager/src/components/PaymentMethodRow/DeletePaymentMethodDialog.test.tsx
index 01db716cfdc..758c91e2f5f 100644
--- a/packages/manager/src/components/PaymentMethodRow/DeletePaymentMethodDialog.test.tsx
+++ b/packages/manager/src/components/PaymentMethodRow/DeletePaymentMethodDialog.test.tsx
@@ -14,8 +14,26 @@ const props = {
paymentMethod: undefined,
};
+const queryMocks = vi.hoisted(() => ({
+ userPermissions: vi.fn(() => ({
+ data: {
+ delete_payment_method: false,
+ },
+ })),
+}));
+
+vi.mock('src/features/IAM/hooks/usePermissions', () => ({
+ usePermissions: queryMocks.userPermissions,
+}));
+
describe('Delete Payment Method Dialog', () => {
it('renders the delete payment method dialog', () => {
+ queryMocks.userPermissions.mockReturnValue({
+ data: {
+ delete_payment_method: true,
+ },
+ });
+
const screen = renderWithTheme();
const headerText = screen.getByText('Delete Payment Method');
@@ -31,6 +49,12 @@ describe('Delete Payment Method Dialog', () => {
});
it('calls the corresponding functions when buttons are clicked', () => {
+ queryMocks.userPermissions.mockReturnValue({
+ data: {
+ delete_payment_method: true,
+ },
+ });
+
const screen = renderWithTheme();
const deleteButton = screen.getByText('Delete');
@@ -43,4 +67,17 @@ describe('Delete Payment Method Dialog', () => {
fireEvent.click(cancelButton);
expect(props.onClose).toHaveBeenCalled();
});
+
+ it('disables the delete button if the user does not have the delete_payment_method permission', () => {
+ queryMocks.userPermissions.mockReturnValue({
+ data: {
+ delete_payment_method: false,
+ },
+ });
+
+ const screen = renderWithTheme();
+
+ const deleteButton = screen.getByText('Delete');
+ expect(deleteButton).toBeDisabled();
+ });
});
diff --git a/packages/manager/src/components/PaymentMethodRow/DeletePaymentMethodDialog.tsx b/packages/manager/src/components/PaymentMethodRow/DeletePaymentMethodDialog.tsx
index b513bfc088b..d818eee0f1f 100644
--- a/packages/manager/src/components/PaymentMethodRow/DeletePaymentMethodDialog.tsx
+++ b/packages/manager/src/components/PaymentMethodRow/DeletePaymentMethodDialog.tsx
@@ -5,6 +5,7 @@ import { makeStyles } from 'tss-react/mui';
import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog';
import CreditCard from 'src/features/Billing/BillingPanels/BillingSummary/PaymentDrawer/CreditCard';
+import { usePermissions } from 'src/features/IAM/hooks/usePermissions';
import { ThirdPartyPayment } from './ThirdPartyPayment';
@@ -35,11 +36,16 @@ export const DeletePaymentMethodDialog = React.memo((props: Props) => {
const { error, loading, onClose, onDelete, open, paymentMethod } = props;
const { classes } = useStyles();
+ const { data: permissions } = usePermissions('account', [
+ 'delete_payment_method',
+ ]);
+
const actions = (
({
userPermissions: vi.fn(() => ({
data: {
make_billing_payment: false,
- update_account: false,
+ set_default_payment_method: false,
+ delete_payment_method: false,
},
})),
}));
@@ -148,7 +149,8 @@ describe('Payment Method Row', () => {
queryMocks.userPermissions.mockReturnValue({
data: {
make_billing_payment: false,
- update_account: true,
+ set_default_payment_method: false,
+ delete_payment_method: true,
},
});
const { getByLabelText, getByText } = renderWithTheme(
@@ -174,7 +176,8 @@ describe('Payment Method Row', () => {
queryMocks.userPermissions.mockReturnValue({
data: {
make_billing_payment: true,
- update_account: true,
+ set_default_payment_method: true,
+ delete_payment_method: false,
},
});
const paymentMethod = paymentMethodFactory.build({
@@ -205,7 +208,8 @@ describe('Payment Method Row', () => {
queryMocks.userPermissions.mockReturnValue({
data: {
make_billing_payment: false,
- update_account: false,
+ set_default_payment_method: false,
+ delete_payment_method: false,
},
});
const { getByLabelText, getByText } = renderWithTheme(
@@ -231,7 +235,8 @@ describe('Payment Method Row', () => {
queryMocks.userPermissions.mockReturnValue({
data: {
make_billing_payment: true,
- update_account: false,
+ set_default_payment_method: false,
+ delete_payment_method: false,
},
});
const { getByLabelText, getByText } = renderWithTheme(
diff --git a/packages/manager/src/components/PaymentMethodRow/PaymentMethodRow.tsx b/packages/manager/src/components/PaymentMethodRow/PaymentMethodRow.tsx
index 4a2f6fe98aa..d8c65888d25 100644
--- a/packages/manager/src/components/PaymentMethodRow/PaymentMethodRow.tsx
+++ b/packages/manager/src/components/PaymentMethodRow/PaymentMethodRow.tsx
@@ -45,7 +45,8 @@ export const PaymentMethodRow = (props: Props) => {
const { data: permissions } = usePermissions('account', [
'make_billing_payment',
- 'update_account',
+ 'set_default_payment_method',
+ 'delete_payment_method',
]);
const makeDefault = () => {
@@ -74,7 +75,9 @@ export const PaymentMethodRow = (props: Props) => {
},
{
disabled:
- isChildUser || !permissions.update_account || paymentMethod.is_default,
+ isChildUser ||
+ !permissions.set_default_payment_method ||
+ paymentMethod.is_default,
onClick: makeDefault,
title: 'Make Default',
tooltip: paymentMethod.is_default
@@ -83,7 +86,9 @@ export const PaymentMethodRow = (props: Props) => {
},
{
disabled:
- isChildUser || !permissions.update_account || paymentMethod.is_default,
+ isChildUser ||
+ !permissions.delete_payment_method ||
+ paymentMethod.is_default,
onClick: onDelete,
title: 'Delete',
tooltip: paymentMethod.is_default
diff --git a/packages/manager/src/features/Billing/BillingPanels/PaymentInfoPanel/AddPaymentMethodDrawer/AddCreditCardForm.tsx b/packages/manager/src/features/Billing/BillingPanels/PaymentInfoPanel/AddPaymentMethodDrawer/AddCreditCardForm.tsx
index 06fa6b3d9a4..8ebe2291c8a 100644
--- a/packages/manager/src/features/Billing/BillingPanels/PaymentInfoPanel/AddPaymentMethodDrawer/AddCreditCardForm.tsx
+++ b/packages/manager/src/features/Billing/BillingPanels/PaymentInfoPanel/AddPaymentMethodDrawer/AddCreditCardForm.tsx
@@ -9,6 +9,7 @@ import NumberFormat from 'react-number-format';
import type { NumberFormatProps } from 'react-number-format';
import { makeStyles } from 'tss-react/mui';
+import { usePermissions } from 'src/features/IAM/hooks/usePermissions';
import { parseExpiryYear } from 'src/utilities/creditCard';
import { handleAPIErrors } from 'src/utilities/formikErrorUtils';
@@ -152,9 +153,16 @@ export const AddCreditCardForm = (props: Props) => {
};
const disableInput = isSubmitting || disabled;
+ const { data: permissions } = usePermissions('account', [
+ 'create_payment_method',
+ ]);
const disableAddButton =
- disabled || !values.card_number || !values.cvv || !values.expiry_month;
+ disabled ||
+ !values.card_number ||
+ !values.cvv ||
+ !values.expiry_month ||
+ !permissions?.create_payment_method;
return (