Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cancel Payment command (again and again) #34719

Merged
merged 48 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
49504fd
Redo cancel payment after revert
Gonals Jan 18, 2024
89fad7e
Spanish
Gonals Jan 18, 2024
3fd0df3
remove extra import
Gonals Jan 18, 2024
7ed1450
prettier
Gonals Jan 18, 2024
f925a4e
Merge branch 'main' into alberto-unpay3
Gonals Jan 19, 2024
e774d17
Display better cancelled message
Gonals Jan 19, 2024
c2fd7fc
display paid message correctly
Gonals Jan 19, 2024
60f8f21
display cancelled message correctly
Gonals Jan 19, 2024
91e0f37
pass and use amount in message
Gonals Jan 22, 2024
4187474
display amount correctly
Gonals Jan 22, 2024
57da597
Merge branch 'main' into alberto-unpay3
Gonals Jan 23, 2024
db4e048
conflicts
Gonals Jan 24, 2024
cf82a5e
Return to correct state/status
Gonals Jan 24, 2024
43519be
Do not set iouReport on cancelPayment
Gonals Jan 25, 2024
ae02760
pass reportID
Gonals Jan 29, 2024
a218cc7
conflicts
Gonals Jan 29, 2024
d712da8
lint
Gonals Jan 29, 2024
4735091
prettier
Gonals Jan 29, 2024
eef5a23
typescript
Gonals Jan 29, 2024
8ce76da
more typescript
Gonals Jan 29, 2024
ba07688
conflicts
Gonals Jan 31, 2024
886cbb4
prettier
Gonals Jan 31, 2024
4c63cfb
conflicts
Gonals Feb 1, 2024
ce897db
remove extra param
Gonals Feb 1, 2024
ecc81ff
remove param from doc
Gonals Feb 1, 2024
aa9f717
Merge branch 'main' into alberto-unpay3
Gonals Feb 5, 2024
a5182bf
conflicts
Gonals Feb 6, 2024
ec7a962
typescript
Gonals Feb 6, 2024
ccdbee2
more typescript
Gonals Feb 6, 2024
50803d4
Add file for params
Gonals Feb 6, 2024
880f7bd
prettier
Gonals Feb 6, 2024
592ed61
more fights agains typescript
Gonals Feb 6, 2024
320dc12
more fight. I'm losing
Gonals Feb 6, 2024
1661ec1
and one more
Gonals Feb 6, 2024
46c9b6c
style now
Gonals Feb 6, 2024
49842e5
ignore markedReimbursed action
Gonals Feb 7, 2024
89a1f48
always display positive amount in message
Gonals Feb 7, 2024
140ed0e
Merge branch 'main' into alberto-unpay3
Gonals Feb 7, 2024
928ccd0
more typescript
Gonals Feb 7, 2024
69f3e7c
still typescript kicking my butt
Gonals Feb 7, 2024
48aee18
Merge branch 'main' into alberto-unpay3
Gonals Feb 8, 2024
6a86314
show consistent message after cancel payment
Gonals Feb 8, 2024
866aeb9
merge main
Gonals Feb 9, 2024
5b4e973
conflicts
Gonals Feb 9, 2024
ede6e3b
conflicts
Gonals Feb 14, 2024
c44ef01
remove todo
Gonals Feb 14, 2024
4df6a65
typescript
Gonals Feb 14, 2024
1380e65
style
Gonals Feb 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,9 @@ const CONST = {
},
THREAD_DISABLED: ['CREATED'],
},
CANCEL_PAYMENT_REASONS: {
ADMIN: 'CANCEL_REASON_ADMIN',
},
ACTIONABLE_MENTION_WHISPER_RESOLUTION: {
INVITE: 'invited',
NOTHING: 'nothing',
Expand Down
31 changes: 30 additions & 1 deletion src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useMemo} from 'react';
import React, {useCallback, useMemo, useState} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
Expand All @@ -17,7 +17,9 @@ import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
import Button from './Button';
import ConfirmModal from './ConfirmModal';
import HeaderWithBackButton from './HeaderWithBackButton';
import * as Expensicons from './Icon/Expensicons';
import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar';
import {usePersonalDetails} from './OnyxProvider';
import SettlementButton from './SettlementButton';
Expand Down Expand Up @@ -62,6 +64,16 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
isPolicyAdmin && (isApproved || isManager)
: isPolicyAdmin || (ReportUtils.isMoneyRequestReport(moneyRequestReport) && isManager);
const isDraft = ReportUtils.isDraftExpenseReport(moneyRequestReport);
const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false);

const cancelPayment = useCallback(() => {
if (!chatReport) {
return;
}
IOU.cancelPayment(moneyRequestReport, chatReport);
setIsConfirmModalVisible(false);
}, [moneyRequestReport, chatReport]);

const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy);
const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy);
const shouldShowPayButton = useMemo(
Expand Down Expand Up @@ -93,6 +105,13 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
);

const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(moneyRequestReport)];
if (isPayer && isSettled && ReportUtils.isExpenseReport(moneyRequestReport)) {
threeDotsMenuItems.push({
icon: Expensicons.Trashcan,
text: translate('iou.cancelPayment'),
onSelected: () => setIsConfirmModalVisible(true),
});
}

return (
<View style={[styles.pt0]}>
Expand Down Expand Up @@ -178,6 +197,16 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
</View>
)}
</View>
<ConfirmModal
title={translate('iou.cancelPayment')}
isVisible={isConfirmModalVisible}
onConfirm={cancelPayment}
onCancel={() => setIsConfirmModalVisible(false)}
prompt={translate('iou.cancelPaymentConfirmation')}
confirmText={translate('iou.cancelPayment')}
cancelText={translate('common.dismiss')}
danger
/>
</View>
);
}
Expand Down
5 changes: 5 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import CONST from '@src/CONST';
import type {Country} from '@src/CONST';
import type {
AddressLineParams,
AdminCanceledRequestParams,
AlreadySignedInParams,
AmountEachParams,
ApprovedAmountParams,
Expand Down Expand Up @@ -113,6 +114,7 @@ type AllCountries = Record<Country, string>;
export default {
common: {
cancel: 'Cancel',
dismiss: 'Dismiss',
yes: 'Yes',
no: 'No',
ok: 'OK',
Expand Down Expand Up @@ -582,6 +584,8 @@ export default {
requestMoney: 'Request money',
sendMoney: 'Send money',
pay: 'Pay',
cancelPayment: 'Cancel payment',
cancelPaymentConfirmation: 'Are you sure that you want to cancel this payment?',
viewDetails: 'View details',
pending: 'Pending',
canceled: 'Canceled',
Expand Down Expand Up @@ -620,6 +624,7 @@ export default {
payerSettled: ({amount}: PayerSettledParams) => `paid ${amount}`,
approvedAmount: ({amount}: ApprovedAmountParams) => `approved ${amount}`,
waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`,
adminCanceledRequest: ({manager, amount}: AdminCanceledRequestParams) => `${manager} cancelled the ${amount} payment.`,
canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) =>
`Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
Expand Down
5 changes: 5 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Str from 'expensify-common/lib/str';
import CONST from '@src/CONST';
import type {
AddressLineParams,
AdminCanceledRequestParams,
AlreadySignedInParams,
AmountEachParams,
ApprovedAmountParams,
Expand Down Expand Up @@ -103,6 +104,7 @@ import type {
export default {
common: {
cancel: 'Cancelar',
dismiss: 'Descartar',
yes: 'Sí',
no: 'No',
ok: 'OK',
Expand Down Expand Up @@ -575,6 +577,8 @@ export default {
requestMoney: 'Pedir dinero',
sendMoney: 'Enviar dinero',
pay: 'Pagar',
cancelPayment: 'Cancelar el pago',
cancelPaymentConfirmation: '¿Estás seguro de que quieres cancelar este pago?',
viewDetails: 'Ver detalles',
pending: 'Pendiente',
canceled: 'Canceló',
Expand Down Expand Up @@ -613,6 +617,7 @@ export default {
payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`,
approvedAmount: ({amount}: ApprovedAmountParams) => `aprobó ${amount}`,
waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`,
adminCanceledRequest: ({manager, amount}: AdminCanceledRequestParams) => `${manager} canceló el pago de ${amount}.`,
canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) =>
`Canceló el pago ${amount}, porque ${submitterDisplayName} no habilitó su billetera Expensify en un plazo de 30 días.`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
Expand Down
3 changes: 3 additions & 0 deletions src/languages/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ type WaitingOnBankAccountParams = {submitterDisplayName: string};

type CanceledRequestParams = {amount: string; submitterDisplayName: string};

type AdminCanceledRequestParams = {manager: string; amount: string};

type SettledAfterAddedBankAccountParams = {submitterDisplayName: string; amount: string};

type PaidElsewhereWithAmountParams = {payer?: string; amount: string};
Expand Down Expand Up @@ -290,6 +292,7 @@ type TermsParams = {amount: string};
type ElectronicFundsParams = {percentage: string; amount: string};

export type {
AdminCanceledRequestParams,
ApprovedAmountParams,
AddressLineParams,
AlreadySignedInParams,
Expand Down
8 changes: 8 additions & 0 deletions src/libs/API/parameters/CancelPaymentParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type CancelPaymentParams = {
iouReportID: string;
chatReportID: string;
managerAccountID: number;
reportActionID: string;
};

export default CancelPaymentParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,5 @@ export type {default as ReplaceReceiptParams} from './ReplaceReceiptParams';
export type {default as SubmitReportParams} from './SubmitReportParams';
export type {default as DetachReceiptParams} from './DetachReceiptParams';
export type {default as PayMoneyRequestParams} from './PayMoneyRequestParams';
export type {default as CancelPaymentParams} from './CancelPaymentParams';
export type {default as AcceptACHContractForBankAccount} from './AcceptACHContractForBankAccount';
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ const WRITE_COMMANDS = {
DETACH_RECEIPT: 'DetachReceipt',
PAY_MONEY_REQUEST_WITH_WALLET: 'PayMoneyRequestWithWallet',
PAY_MONEY_REQUEST: 'PayMoneyRequest',
CANCEL_PAYMENT: 'CancelPayment',
ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT: 'AcceptACHContractForBankAccount',
} as const;

Expand Down Expand Up @@ -280,6 +281,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.DETACH_RECEIPT]: Parameters.DetachReceiptParams;
[WRITE_COMMANDS.PAY_MONEY_REQUEST_WITH_WALLET]: Parameters.PayMoneyRequestParams;
[WRITE_COMMANDS.PAY_MONEY_REQUEST]: Parameters.PayMoneyRequestParams;
[WRITE_COMMANDS.CANCEL_PAYMENT]: Parameters.CancelPaymentParams;
[WRITE_COMMANDS.ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT]: Parameters.AcceptACHContractForBankAccount;
};

Expand Down
2 changes: 1 addition & 1 deletion src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ function getLastMessageTextForReport(report: OnyxEntry<Report>, lastActorDetails
} else if (ReportActionUtils.isReimbursementQueuedAction(lastReportAction)) {
lastMessageTextFromReport = ReportUtils.getReimbursementQueuedActionMessage(lastReportAction, report);
} else if (ReportActionUtils.isReimbursementDeQueuedAction(lastReportAction)) {
lastMessageTextFromReport = ReportUtils.getReimbursementDeQueuedActionMessage(report);
lastMessageTextFromReport = ReportUtils.getReimbursementDeQueuedActionMessage(lastReportAction, report);
} else if (ReportActionUtils.isDeletedParentAction(lastReportAction) && ReportUtils.isChatReport(report)) {
lastMessageTextFromReport = ReportUtils.getDeletedParentActionMessageForChatReport(lastReportAction);
} else if (ReportActionUtils.isPendingRemove(lastReportAction) && ReportActionUtils.isThreadParentMessage(lastReportAction, report?.reportID ?? '')) {
Expand Down
6 changes: 6 additions & 0 deletions src/libs/ReportActionsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,12 @@ function shouldReportActionBeVisible(reportAction: OnyxEntry<ReportAction>, key:
return false;
}

// Ignore markedAsReimbursed action here since we're already display message that explains the request was paid
// elsewhere in the IOU reportAction
if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.MARKEDREIMBURSED) {
return false;
}

if (isWhisperActionTargetedToOthers(reportAction)) {
return false;
}
Expand Down
73 changes: 64 additions & 9 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,16 @@ import type {
} from '@src/types/onyx';
import type {Participant} from '@src/types/onyx/IOU';
import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon';
import type {ChangeLog, IOUMessage, OriginalMessageActionName, OriginalMessageCreated, OriginalMessageRenamed, PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import type {
ChangeLog,
IOUMessage,
OriginalMessageActionName,
OriginalMessageCreated,
OriginalMessageReimbursementDequeued,
OriginalMessageRenamed,
PaymentMethodType,
ReimbursementDeQueuedMessage,
} from '@src/types/onyx/OriginalMessage';
import type {Status} from '@src/types/onyx/PersonalDetails';
import type {NotificationPreference} from '@src/types/onyx/Report';
import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction';
Expand Down Expand Up @@ -186,6 +195,11 @@ type OptimisticSubmittedReportAction = Pick<
'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'isAttachment' | 'originalMessage' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction'
>;

type OptimisticCancelPaymentReportAction = Pick<
ReportAction,
'actionName' | 'actorAccountID' | 'message' | 'originalMessage' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction'
>;

type OptimisticEditedTaskReportAction = Pick<
ReportAction,
'reportActionID' | 'actionName' | 'pendingAction' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'shouldShow' | 'message' | 'person'
Expand Down Expand Up @@ -1714,11 +1728,55 @@ function getReimbursementQueuedActionMessage(reportAction: OnyxEntry<ReportActio
/**
* Returns the preview message for `REIMBURSEMENTDEQUEUED` action
*/
function getReimbursementDeQueuedActionMessage(report: OnyxEntry<Report>): string {
function getReimbursementDeQueuedActionMessage(reportAction: OnyxEntry<ReportActionBase & OriginalMessageReimbursementDequeued>, report: OnyxEntry<Report> | EmptyObject): string {
const originalMessage = reportAction?.originalMessage as ReimbursementDeQueuedMessage | undefined;
const amount = originalMessage?.amount;
const currency = originalMessage?.currency;
const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency);
if (originalMessage?.cancellationReason === CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN) {
const payerOrApproverName = isExpenseReport(report) ? getPolicyName(report, false) : getDisplayNameForParticipant(report?.managerID) ?? '';
return Localize.translateLocal('iou.adminCanceledRequest', {manager: payerOrApproverName, amount: formattedAmount});
}
const submitterDisplayName = getDisplayNameForParticipant(report?.ownerAccountID, true) ?? '';
const amount = CurrencyUtils.convertToDisplayString(report?.total ?? 0, report?.currency);
return Localize.translateLocal('iou.canceledRequest', {submitterDisplayName, amount: formattedAmount});
}

return Localize.translateLocal('iou.canceledRequest', {submitterDisplayName, amount});
/**
* Builds an optimistic REIMBURSEMENTDEQUEUED report action with a randomly generated reportActionID.
*
*/
function buildOptimisticCancelPaymentReportAction(expenseReportID: string, amount: number, currency: string): OptimisticCancelPaymentReportAction {
return {
actionName: CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED,
actorAccountID: currentUserAccountID,
message: [
{
cancellationReason: CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN,
expenseReportID,
type: CONST.REPORT.MESSAGE.TYPE.COMMENT,
text: '',
amount,
currency,
},
],
originalMessage: {
cancellationReason: CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN,
expenseReportID,
amount,
currency,
},
person: [
{
style: 'strong',
text: currentUserPersonalDetails?.displayName ?? currentUserEmail,
type: 'TEXT',
},
],
reportActionID: NumberUtils.rand64(),
shouldShow: true,
created: DateUtils.getDBTime(),
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
};
}

/**
Expand Down Expand Up @@ -1977,10 +2035,6 @@ function getMoneyRequestReportName(report: OnyxEntry<Report>, policy: OnyxEntry<
return `${payerPaidAmountMessage} • ${Localize.translateLocal('iou.pending')}`;
}

if (report?.isCancelledIOU) {
return `${payerPaidAmountMessage} • ${Localize.translateLocal('iou.canceled')}`;
}

if (hasNonReimbursableTransactions(report?.reportID)) {
return Localize.translateLocal('iou.payerSpentAmount', {payer: payerOrApproverName, amount: formattedAmount});
}
Expand Down Expand Up @@ -4550,7 +4604,7 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry<ReportAction>)
// differentiate between these two scenarios, we check if the `originalMessage` contains the `IOUDetails`
// property. If it does, it indicates that this is a 'Send money' action.
const {amount, currency} = originalMessage.IOUDetails ?? originalMessage;
const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency) ?? '';
const formattedAmount = CurrencyUtils.convertToDisplayString(Math.abs(amount), currency) ?? '';
const payerName = isExpenseReport(iouReport) ? getPolicyName(iouReport) : getDisplayNameForParticipant(iouReport?.managerID, true);

switch (originalMessage.paymentType) {
Expand Down Expand Up @@ -4932,6 +4986,7 @@ export {
buildOptimisticIOUReportAction,
buildOptimisticReportPreview,
buildOptimisticModifiedExpenseReportAction,
buildOptimisticCancelPaymentReportAction,
updateReportPreview,
buildOptimisticTaskReportAction,
buildOptimisticAddCommentReportAction,
Expand Down
Loading
Loading