diff --git a/src/CONST.js b/src/CONST.js
index cbf24bae5610..b1edc54f3372 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -661,6 +661,8 @@ const CONST = {
PAY: 'pay',
CREATE: 'create',
SPLIT: 'split',
+ DECLINE: 'decline',
+ CANCEL: 'cancel',
},
AMOUNT_MAX_LENGTH: 10,
},
diff --git a/src/components/KYCWall/kycWallPropTypes.js b/src/components/KYCWall/kycWallPropTypes.js
index 5886001c8c92..73e2b550b69f 100644
--- a/src/components/KYCWall/kycWallPropTypes.js
+++ b/src/components/KYCWall/kycWallPropTypes.js
@@ -24,7 +24,7 @@ const propTypes = {
userWallet: userWalletPropTypes,
/** When the button is opened via an IOU, ID for the chatReport that the IOU is linked to */
- chatReportID: PropTypes.number,
+ chatReportID: PropTypes.string,
};
const defaultProps = {
@@ -32,7 +32,7 @@ const defaultProps = {
popoverPlacement: 'top',
shouldListenForResize: false,
isDisabled: false,
- chatReportID: 0,
+ chatReportID: '',
};
export {propTypes, defaultProps};
diff --git a/src/components/ReportTransaction.js b/src/components/ReportTransaction.js
index 8e2aaeb165ab..21158e9a94b1 100644
--- a/src/components/ReportTransaction.js
+++ b/src/components/ReportTransaction.js
@@ -1,14 +1,14 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
-import lodashGet from 'lodash/get';
import {View} from 'react-native';
-import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
-import ONYXKEYS from '../ONYXKEYS';
import styles from '../styles/styles';
+import CONST from '../CONST';
import * as IOU from '../libs/actions/IOU';
+import * as ReportActions from '../libs/actions/ReportActions';
import reportActionPropTypes from '../pages/home/report/reportActionPropTypes';
import ReportActionItemSingle from '../pages/home/report/ReportActionItemSingle';
+import withLocalize, {withLocalizePropTypes} from './withLocalize';
+import OfflineWithFeedback from './OfflineWithFeedback';
import Text from './Text';
import Button from './Button';
@@ -27,17 +27,10 @@ const propTypes = {
/** Can this transaction be rejected? */
canBeRejected: PropTypes.bool,
- /** Text label for the reject transaction button */
- rejectButtonLabelText: PropTypes.string.isRequired,
+ /** Type of the reject transaction button */
+ rejectButtonType: PropTypes.oneOf([CONST.IOU.REPORT_ACTION_TYPE.DECLINE, CONST.IOU.REPORT_ACTION_TYPE.CANCEL]).isRequired,
- /* Onyx Props */
-
- /** List of transactionIDs in process of rejection */
- /* eslint-disable-next-line react/no-unused-prop-types, react/require-default-props */
- transactionsBeingRejected: PropTypes.shape({
- /** IOUTransactionID that's being rejected */
- transactionID: PropTypes.bool,
- }),
+ ...withLocalizePropTypes,
};
const defaultProps = {
@@ -48,62 +41,57 @@ class ReportTransaction extends Component {
constructor(props) {
super(props);
- this.rejectTransaction = this.rejectTransaction.bind(this);
- }
-
- rejectTransaction() {
- IOU.rejectTransaction({
- reportID: this.props.iouReportID,
- chatReportID: this.props.chatReportID,
- transactionID: this.props.action.originalMessage.IOUTransactionID,
- comment: '',
- });
+ this.cancelMoneyRequest = this.cancelMoneyRequest.bind(this);
}
- /**
- * Checks if current IOUTransactionID is being rejected.
- * @returns {boolean} Returns `true` if current IOUtransactionID is being rejected, else `false`.
- */
- isBeingRejected() {
- const IOUTransactionID = lodashGet(this.props.action, 'originalMessage.IOUTransactionID', '');
- const transactionsBeingRejected = lodashGet(this.props, 'transactionsBeingRejected', {});
- if (_.isEmpty(transactionsBeingRejected)) {
- return false;
- }
- return _.has(transactionsBeingRejected, IOUTransactionID);
+ cancelMoneyRequest() {
+ IOU.cancelMoneyRequest(
+ this.props.chatReportID,
+ this.props.iouReportID,
+ this.props.rejectButtonType,
+ this.props.action,
+ );
}
render() {
return (
-
-
-
- {this.props.action.message[0].text}
-
-
- {this.props.canBeRejected && (
-
-
-
- )}
-
+ {
+ if (this.props.action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) {
+ ReportActions.deleteOptimisticReportAction(this.props.chatReportID, this.props.action.sequenceNumber);
+ } else {
+ ReportActions.clearReportActionErrors(this.props.chatReportID, this.props.action.sequenceNumber);
+ }
+ }}
+ pendingAction={this.props.action.pendingAction}
+ errors={this.props.action.errors}
+ errorRowStyles={[styles.ml10, styles.mr2]}
+ >
+
+
+
+ {this.props.action.message[0].text}
+
+
+ {this.props.canBeRejected && (
+
+
+
+ )}
+
+
);
}
}
ReportTransaction.defaultProps = defaultProps;
ReportTransaction.propTypes = propTypes;
-export default withOnyx({
- transactionsBeingRejected: {
- key: ONYXKEYS.TRANSACTIONS_BEING_REJECTED,
- },
-})(ReportTransaction);
+export default withLocalize(ReportTransaction);
diff --git a/src/components/SettlementButton.js b/src/components/SettlementButton.js
index 316e356b51e3..8b819ba3c8d4 100644
--- a/src/components/SettlementButton.js
+++ b/src/components/SettlementButton.js
@@ -28,7 +28,7 @@ const propTypes = {
network: networkPropTypes.isRequired,
/** When the button is opened via an IOU, ID for the chatReport that the IOU is linked to */
- chatReportID: PropTypes.number,
+ chatReportID: PropTypes.string,
...withLocalizePropTypes,
};
@@ -36,7 +36,7 @@ const propTypes = {
const defaultProps = {
currency: CONST.CURRENCY.USD,
shouldShowPaypal: false,
- chatReportID: 0,
+ chatReportID: '',
};
class SettlementButton extends React.Component {
diff --git a/src/languages/en.js b/src/languages/en.js
index 86fb9adf4a3f..b67f87172a68 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -262,6 +262,7 @@ export default {
invalidSplit: 'Split amounts do not equal total amount',
other: 'Unexpected error, please try again later',
genericCreateFailureMessage: 'Unexpected error requesting money, please try again later',
+ genericCancelFailureMessage: ({type}) => `Unexpected error ${type === 'decline' ? 'declining' : 'cancelling'} the money request, please try again later`,
},
},
notificationPreferences: {
diff --git a/src/languages/es.js b/src/languages/es.js
index 12a5c48029df..30a3eb3cc02d 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -262,6 +262,8 @@ export default {
invalidSplit: 'La suma de las partes no equivale al monto total',
other: 'Error inesperado, por favor inténtalo más tarde',
genericCreateFailureMessage: 'Error inesperado solicitando dinero, por favor inténtalo más tarde',
+ genericCancelFailureMessage: ({type}) => `Error inesperado al ${type === 'decline' ? 'rechazar' : 'cancelar'} la solicitud de dinero, por favor inténtalo más tarde`,
+
},
},
notificationPreferences: {
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index 530f735aed34..4f2128063d76 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -702,18 +702,27 @@ function buildOptimisticIOUReport(ownerEmail, userEmail, total, chatReportID, cu
*/
function getIOUReportActionMessage(type, total, participants, comment, currency) {
const amount = NumberFormatUtils.format(preferredLocale, total / 100, {style: 'currency', currency});
- const displayNames = _.map(participants, participant => getDisplayNameForParticipant(participant.login, true) || participant.login);
- const from = displayNames.length < 3
+ const displayNames = _.map(participants, participant => getDisplayNameForParticipant(participant.login, true));
+ const who = displayNames.length < 3
? displayNames.join(' and ')
: `${displayNames.slice(0, -1).join(', ')}, and ${_.last(displayNames)}`;
let iouMessage;
switch (type) {
case CONST.IOU.REPORT_ACTION_TYPE.CREATE:
- iouMessage = `Requested ${amount} from ${from}${comment && ` for ${comment}`}`;
+ iouMessage = `Requested ${amount} from ${who}${comment && ` for ${comment}`}`;
break;
case CONST.IOU.REPORT_ACTION_TYPE.SPLIT:
- iouMessage = `Split ${amount} with ${from}${comment && ` for ${comment}`}`;
+ iouMessage = `Split ${amount} with ${who}${comment && ` for ${comment}`}`;
+ break;
+ case CONST.IOU.REPORT_ACTION_TYPE.CANCEL:
+ iouMessage = `Cancelled the ${amount} request${comment && ` for ${comment}`}`;
+ break;
+ case CONST.IOU.REPORT_ACTION_TYPE.DECLINE:
+ iouMessage = `Declined the ${amount} request${comment && ` for ${comment}`}`;
+ break;
+ case CONST.IOU.REPORT_ACTION_TYPE.PAY:
+ iouMessage = `Paid ${amount} to ${who}${comment && ` for ${comment}`}`;
break;
default:
break;
@@ -733,7 +742,7 @@ function getIOUReportActionMessage(type, total, participants, comment, currency)
* @param {Number} sequenceNumber - Caller is responsible for providing a best guess at what the next sequenceNumber will be.
* @param {String} type - IOUReportAction type. Can be oneOf(create, decline, cancel, pay, split).
* @param {Number} amount - IOU amount in cents.
- * @param {String} currency - IOU currency.
+ * @param {String} currency
* @param {String} comment - User comment for the IOU.
* @param {Array} participants - An array with participants details.
* @param {String} paymentType - Only required if the IOUReportAction type is 'pay'. Can be oneOf(elsewhere, payPal, Expensify).
diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js
index 25de771e0fe6..d9ac2d1519a1 100644
--- a/src/libs/actions/IOU.js
+++ b/src/libs/actions/IOU.js
@@ -10,14 +10,19 @@ import Navigation from '../Navigation/Navigation';
import Growl from '../Growl';
import * as Localize from '../Localize';
import asyncOpenURL from '../asyncOpenURL';
-import Log from '../Log';
import * as API from '../API';
import * as ReportUtils from '../ReportUtils';
import * as IOUUtils from '../IOUUtils';
import * as OptionsListUtils from '../OptionsListUtils';
-import * as NumberUtils from '../NumberUtils';
import DateUtils from '../DateUtils';
+let chatReports;
+Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: val => chatReports = val,
+});
+
let iouReports;
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT_IOUS,
@@ -210,7 +215,7 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com
comment,
iouReportID: iouReport.reportID,
chatReportID: chatReport.reportID,
- transactionID: NumberUtils.rand64(),
+ transactionID: optimisticReportAction.originalMessage.IOUTransactionID,
reportActionID: optimisticReportAction.reportActionID,
clientID: optimisticReportAction.sequenceNumber,
}, {optimisticData, successData, failureData});
@@ -535,41 +540,102 @@ function splitBillAndOpenReport(participants, currentUserLogin, amount, comment,
}
/**
- * Reject an iouReport transaction. Declining and cancelling transactions are done via the same Auth command.
+ * Cancels or declines a transaction in iouReport.
+ * Declining and cancelling transactions are done via the same Auth command.
*
- * @param {Object} params
- * @param {Number} params.reportID
- * @param {Number} params.chatReportID
- * @param {String} params.transactionID
- * @param {String} params.comment
+ * @param {String} chatReportID
+ * @param {String} iouReportID
+ * @param {String} type - cancel|decline
+ * @param {Object} moneyRequestAction - the create IOU reportAction we are cancelling
*/
-function rejectTransaction({
- reportID, chatReportID, transactionID, comment,
-}) {
- Onyx.merge(ONYXKEYS.TRANSACTIONS_BEING_REJECTED, {
- [transactionID]: true,
- });
- DeprecatedAPI.RejectTransaction({
- reportID,
+function cancelMoneyRequest(chatReportID, iouReportID, type, moneyRequestAction) {
+ const chatReport = chatReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`];
+ const iouReport = iouReports[`${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportID}`];
+ const transactionID = moneyRequestAction.originalMessage.IOUTransactionID;
+
+ // Get the amount we are cancelling
+ const amount = moneyRequestAction.originalMessage.amount;
+ const newSequenceNumber = Report.getMaxSequenceNumber(chatReport.reportID) + 1;
+ const optimisticReportAction = ReportUtils.buildOptimisticIOUReportAction(
+ newSequenceNumber,
+ type,
+ amount,
+ moneyRequestAction.originalMessage.currency,
+ moneyRequestAction.originalMessage.comment,
+ [],
+ '',
transactionID,
- comment,
- })
- .then((response) => {
- if (response.jsonCode !== 200) {
- Log.hmmm('Error rejecting transaction', {error: response.error});
- return;
- }
+ iouReportID,
+ );
- const chatReport = response.reports[chatReportID];
- const iouReport = response.reports[reportID];
- Report.syncChatAndIOUReports(chatReport, iouReport);
- })
- .finally(() => {
- // Setting as null deletes the transactionID
- Onyx.merge(ONYXKEYS.TRANSACTIONS_BEING_REJECTED, {
- [transactionID]: null,
- });
- });
+ const currentUserEmail = optimisticReportAction.actorEmail;
+ const updatedIOUReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, currentUserEmail, amount, type);
+
+ chatReport.maxSequenceNumber = newSequenceNumber;
+ chatReport.lastReadSequenceNumber = newSequenceNumber;
+ chatReport.lastMessageText = optimisticReportAction.message[0].text;
+ chatReport.lastMessageHtml = optimisticReportAction.message[0].html;
+ chatReport.hasOutstandingIOU = updatedIOUReport.total !== 0;
+
+ const optimisticData = [
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`,
+ value: {
+ [optimisticReportAction.sequenceNumber]: {
+ ...optimisticReportAction,
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ },
+ },
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`,
+ value: chatReport,
+ },
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportID}`,
+ value: updatedIOUReport,
+ },
+ ];
+ const successData = [
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`,
+ value: {
+ [optimisticReportAction.sequenceNumber]: {
+ pendingAction: null,
+ },
+ },
+ },
+ ];
+ const failureData = [
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`,
+ value: {
+ [optimisticReportAction.sequenceNumber]: {
+ pendingAction: null,
+ errors: {
+ [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericCancelFailureMessage', {type}),
+ },
+ },
+ },
+ },
+ ];
+
+ API.write('CancelMoneyRequest', {
+ transactionID,
+ iouReportID: updatedIOUReport.reportID,
+ comment: '',
+ clientID: optimisticReportAction.sequenceNumber,
+ cancelMoneyRequestReportActionID: optimisticReportAction.reportActionID,
+ chatReportID,
+ debtorEmail: chatReport.participants[0],
+ }, {optimisticData, successData, failureData});
+
+ Navigation.navigate(ROUTES.getReportRoute(chatReportID));
}
/**
@@ -655,9 +721,9 @@ function payIOUReport({
}
export {
+ cancelMoneyRequest,
splitBill,
splitBillAndOpenReport,
- rejectTransaction,
requestMoney,
payIOUReport,
setIOUSelectedCurrency,
diff --git a/src/libs/deprecatedAPI.js b/src/libs/deprecatedAPI.js
index d17d1945b99a..b5dd9d2b4cd0 100644
--- a/src/libs/deprecatedAPI.js
+++ b/src/libs/deprecatedAPI.js
@@ -186,18 +186,6 @@ function PersonalDetails_Update(parameters) {
return Network.post(commandName, parameters);
}
-/**
- * @param {Object} parameters
- * @param {Number} parameters.reportID
- * @param {String} parameters.transactionID
- * @returns {Promise}
- */
-function RejectTransaction(parameters) {
- const commandName = 'RejectTransaction';
- requireParameters(['reportID', 'transactionID'], parameters, commandName);
- return Network.post(commandName, parameters);
-}
-
/**
* @param {Object} parameters
* @param {String} parameters.email
@@ -367,7 +355,6 @@ export {
PayWithWallet,
PersonalDetails_GetForEmails,
PersonalDetails_Update,
- RejectTransaction,
ResendValidateCode,
SetNameValuePair,
SetPassword,
diff --git a/src/pages/EnablePayments/walletTermsPropTypes.js b/src/pages/EnablePayments/walletTermsPropTypes.js
index db77ecb36883..c5f19cd3a9f2 100644
--- a/src/pages/EnablePayments/walletTermsPropTypes.js
+++ b/src/pages/EnablePayments/walletTermsPropTypes.js
@@ -6,5 +6,5 @@ export default PropTypes.shape({
errors: PropTypes.objectOf(PropTypes.string),
/** When the user accepts the Wallet's terms in order to pay an IOU, this is the ID of the chatReport the IOU is linked to */
- chatReportID: PropTypes.number,
+ chatReportID: PropTypes.string,
});
diff --git a/src/pages/iou/IOUTransactions.js b/src/pages/iou/IOUTransactions.js
index f57499ffb6ea..af5718303dd8 100644
--- a/src/pages/iou/IOUTransactions.js
+++ b/src/pages/iou/IOUTransactions.js
@@ -4,12 +4,11 @@ import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
-import compose from '../../libs/compose';
-import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
import styles from '../../styles/styles';
import ONYXKEYS from '../../ONYXKEYS';
import reportActionPropTypes from '../home/report/reportActionPropTypes';
import ReportTransaction from '../../components/ReportTransaction';
+import CONST from '../../CONST';
const propTypes = {
/** Actions from the ChatReport */
@@ -26,8 +25,6 @@ const propTypes = {
/** Is the associated IOU settled? */
isIOUSettled: PropTypes.bool,
-
- ...withLocalizePropTypes,
};
const defaultProps = {
@@ -92,9 +89,7 @@ class IOUTransactions extends Component {
action={reportAction}
key={reportAction.sequenceNumber}
canBeRejected={canBeRejected}
- rejectButtonLabelText={isCurrentUserTransactionCreator
- ? this.props.translate('common.cancel')
- : this.props.translate('common.decline')}
+ rejectButtonType={isCurrentUserTransactionCreator ? CONST.IOU.REPORT_ACTION_TYPE.CANCEL : CONST.IOU.REPORT_ACTION_TYPE.DECLINE}
/>
);
})}
@@ -105,12 +100,9 @@ class IOUTransactions extends Component {
IOUTransactions.defaultProps = defaultProps;
IOUTransactions.propTypes = propTypes;
-export default compose(
- withLocalize,
- withOnyx({
- reportActions: {
- key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`,
- canEvict: false,
- },
- }),
-)(IOUTransactions);
+export default withOnyx({
+ reportActions: {
+ key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`,
+ canEvict: false,
+ },
+})(IOUTransactions);