@@ -24,6 +24,7 @@ import {
2424 isExpenseReport ,
2525 isIndividualInvoiceRoom ,
2626 isInvoiceReport as isInvoiceReportReportUtils ,
27+ isIOUReport ,
2728 updateReportPreview ,
2829} from '@libs/ReportUtils' ;
2930import playSound , { SOUNDS } from '@libs/Sound' ;
@@ -521,6 +522,87 @@ function getPayMoneyRequestParams({
521522 } ;
522523}
523524
525+ /**
526+ * Cancels a P2P "send money" wallet payment that is waiting for the receiver to set up their wallet. The
527+ * sender (payer) can do this while the payment is held; it returns the held funds. These reports have no
528+ * policy, approval flow, or next step, so we only reverse the optimistic IOU report state.
529+ */
530+ function cancelSendMoneyPayment ( iouReport : OnyxTypes . Report , chatReport : OnyxTypes . Report , currentUserAccountIDParam : number ) {
531+ const optimisticIOUCancelAction = buildOptimisticCancelPaymentReportAction ( iouReport . reportID , - ( iouReport . total ?? 0 ) , iouReport . currency ?? '' , currentUserAccountIDParam ) ;
532+
533+ const optimisticData : Array < OnyxUpdate < typeof ONYXKEYS . COLLECTION . REPORT_ACTIONS | typeof ONYXKEYS . COLLECTION . REPORT > > = [
534+ {
535+ onyxMethod : Onyx . METHOD . MERGE ,
536+ key : `${ ONYXKEYS . COLLECTION . REPORT_ACTIONS } ${ iouReport . reportID } ` ,
537+ value : {
538+ [ optimisticIOUCancelAction . reportActionID ] : optimisticIOUCancelAction as OnyxTypes . ReportAction ,
539+ } ,
540+ } ,
541+ {
542+ onyxMethod : Onyx . METHOD . MERGE ,
543+ key : `${ ONYXKEYS . COLLECTION . REPORT } ${ iouReport . reportID } ` ,
544+ value : {
545+ isWaitingOnBankAccount : false ,
546+ isCancelledIOU : true ,
547+ // These reports belong to the sender's personal policy, which approves optionally, so cancelling
548+ // returns the report to submitted and closed rather than approved.
549+ stateNum : CONST . REPORT . STATE_NUM . SUBMITTED ,
550+ statusNum : CONST . REPORT . STATUS_NUM . CLOSED ,
551+ lastVisibleActionCreated : optimisticIOUCancelAction . created ,
552+ lastMessageText : getReportActionText ( optimisticIOUCancelAction ) ,
553+ lastMessageHtml : getReportActionHtml ( optimisticIOUCancelAction ) ,
554+ } ,
555+ } ,
556+ ] ;
557+
558+ const successData : Array < OnyxUpdate < typeof ONYXKEYS . COLLECTION . REPORT_ACTIONS > > = [
559+ {
560+ onyxMethod : Onyx . METHOD . MERGE ,
561+ key : `${ ONYXKEYS . COLLECTION . REPORT_ACTIONS } ${ iouReport . reportID } ` ,
562+ value : {
563+ [ optimisticIOUCancelAction . reportActionID ] : {
564+ pendingAction : null ,
565+ } ,
566+ } ,
567+ } ,
568+ ] ;
569+
570+ const failureData : Array < OnyxUpdate < typeof ONYXKEYS . COLLECTION . REPORT_ACTIONS | typeof ONYXKEYS . COLLECTION . REPORT > > = [
571+ {
572+ onyxMethod : Onyx . METHOD . MERGE ,
573+ key : `${ ONYXKEYS . COLLECTION . REPORT_ACTIONS } ${ iouReport . reportID } ` ,
574+ value : {
575+ [ optimisticIOUCancelAction . reportActionID ] : {
576+ errors : getMicroSecondOnyxErrorWithTranslationKey ( 'iou.error.other' ) ,
577+ } ,
578+ } ,
579+ } ,
580+ {
581+ onyxMethod : Onyx . METHOD . MERGE ,
582+ key : `${ ONYXKEYS . COLLECTION . REPORT } ${ iouReport . reportID } ` ,
583+ value : {
584+ isWaitingOnBankAccount : iouReport . isWaitingOnBankAccount ,
585+ isCancelledIOU : false ,
586+ stateNum : iouReport . stateNum ,
587+ statusNum : iouReport . statusNum ,
588+ } ,
589+ } ,
590+ ] ;
591+
592+ API . write (
593+ WRITE_COMMANDS . CANCEL_PAYMENT ,
594+ {
595+ iouReportID : iouReport . reportID ,
596+ chatReportID : chatReport . reportID ,
597+ managerAccountID : iouReport . managerID ?? CONST . DEFAULT_NUMBER_ID ,
598+ reportActionID : optimisticIOUCancelAction . reportActionID ,
599+ } ,
600+ { optimisticData, successData, failureData} ,
601+ ) ;
602+
603+ notifyNewAction ( iouReport . reportID , undefined , true ) ;
604+ }
605+
524606function cancelPayment (
525607 expenseReport : OnyxEntry < OnyxTypes . Report > ,
526608 chatReport : OnyxTypes . Report ,
@@ -535,6 +617,15 @@ function cancelPayment(
535617 return ;
536618 }
537619
620+ // A P2P "send money" payment waiting for the receiver to set up their wallet has no policy, approval
621+ // flow, or next step, so it is cancelled through a simplified path that just reverses the optimistic IOU
622+ // report state and lets the backend return the held funds. Other IOU reports (e.g. a money request paid
623+ // elsewhere) keep using the standard path below.
624+ if ( isIOUReport ( expenseReport ) && expenseReport . isWaitingOnBankAccount ) {
625+ cancelSendMoneyPayment ( expenseReport , chatReport , currentUserAccountIDParam ) ;
626+ return ;
627+ }
628+
538629 // Prefer the freshly computed reimbursableTotal over deriving from the (sometimes stale) stored total.
539630 const optimisticReportAction = buildOptimisticCancelPaymentReportAction (
540631 expenseReport . reportID ,
0 commit comments