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

Wise: payBatch and error logging tweaks #9885

Merged
merged 2 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
51 changes: 38 additions & 13 deletions server/controllers/transferwise.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import config from 'config';
import { Request, Response } from 'express';

import expenseStatus from '../constants/expense-status';
Expand All @@ -6,22 +7,28 @@ import { idDecode, IDENTIFIER_TYPES } from '../graphql/v2/identifiers';
import errors from '../lib/errors';
import logger from '../lib/logger';
import { reportErrorToSentry, reportMessageToSentry } from '../lib/sentry';
import { simulateTransferSuccess } from '../lib/transferwise';
import models, { Op } from '../models';
import transferwise from '../paymentProviders/transferwise';
import { handleTransferStateChange } from '../paymentProviders/transferwise/webhook';
import { BatchGroup } from '../types/transferwise';

const processPaidExpense = (host, remoteUser, batchGroup: BatchGroup) => async expense => {
await expense.reload();
if (expense.data?.transfer) {
const payoutMethod = await expense.getPayoutMethod();
const { feesInHostCurrency } = await getExpenseFees(expense, host, { payoutMethod });
return setTransferWiseExpenseAsProcessing({
expense,
host,
data: { batchGroup },
feesInHostCurrency,
remoteUser,
});
try {
await expense.reload();
if (expense.data?.transfer) {
const payoutMethod = await expense.getPayoutMethod();
const { feesInHostCurrency } = await getExpenseFees(expense, host, { payoutMethod });
return setTransferWiseExpenseAsProcessing({
expense,
host,
data: { batchGroup },
feesInHostCurrency,
remoteUser,
});
}
} catch (e) {
logger.error(`Wise Batch Payment: Error processing paid expense ${expense.id}`, e);
}
};

Expand Down Expand Up @@ -83,10 +90,28 @@ export async function payBatch(
res.setHeader('x-2fa-approval', fundResponse.headers['x-2fa-approval']);
res.sendStatus(fundResponse.status);
} else if (fundResponse.status === 'COMPLETED') {
// Send 200 to the frontend
res.sendStatus(200);
// Mark expenses as paid and create transactions
await Promise.all(expenses.map(processPaidExpense(host, remoteUser, fundResponse)));
// Send 200 to the frontend
res.sendStatus(200);
// Simulate transfer success in other environments so transactions don't get stuck.
if (['development', 'staging'].includes(config.env)) {
const connectedAccount = await host.getAccountForPaymentProvider('transferwise');
logger.debug(`Wise: Simulating transfer success for batch group ${fundResponse.id}`);
for (const transferId of fundResponse.transferIds) {
const response = await simulateTransferSuccess(connectedAccount, transferId);
logger.debug(`Wise: Simulated transfer success for transfer ${transferId}`);
const expense = expenses.find(e => e.data.transfer.id === transferId);
await expense.update({ data: { ...expense.data, transfer: response } });
// In development mode we don't have webhooks set up, so we need to manually trigger the event handler.
if (config.env === 'development') {
await handleTransferStateChange({
// eslint-disable-next-line camelcase
data: { resource: response, current_state: 'outgoing_payment_sent' },
} as any);
}
}
}
} else {
throw new Error(`Could not pay for batch group #${fundResponse.id}, please contact support.`);
}
Expand Down
36 changes: 20 additions & 16 deletions server/lib/transferwise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,21 +97,26 @@ export async function getToken(connectedAccount: ConnectedAccount, refresh = fal
};

const refreshAndUpdateToken = async connectedAccount => {
const newToken = await getOrRefreshToken({ refreshToken: connectedAccount.refreshToken });
if (!newToken) {
Activity.create({
type: ActivityTypes.CONNECTED_ACCOUNT_ERROR,
data: {
connectedAccount: connectedAccount.activity,
error: 'There was an error refreshing the Wise token',
},
CollectiveId: connectedAccount.CollectiveId,
});
throw new Error('There was an error refreshing the Wise token');
try {
const newToken = await getOrRefreshToken({ refreshToken: connectedAccount.refreshToken });
if (!newToken) {
Activity.create({
type: ActivityTypes.CONNECTED_ACCOUNT_ERROR,
data: {
connectedAccount: connectedAccount.activity,
error: 'There was an error refreshing the Wise token',
},
CollectiveId: connectedAccount.CollectiveId,
});
throw new Error('There was an error refreshing the Wise token');
}
const { access_token: token, refresh_token: refreshToken, ...data } = newToken;
await connectedAccount.update({ token, refreshToken, data: { ...connectedAccount.data, ...data } });
return token;
} catch (e) {
logger.error(`Error refreshing Wise token for connected account #${connectedAccount.id}`, e);
throw e;
}
const { access_token: token, refresh_token: refreshToken, ...data } = newToken;
await connectedAccount.update({ token, refreshToken, data: { ...connectedAccount.data, ...data } });
return token;
};

if (!checkTokenIsExpired(connectedAccount)) {
Expand Down Expand Up @@ -773,9 +778,8 @@ export const getOrRefreshToken = async ({
return token;
} catch (e) {
debug(JSON.stringify(e));
const error = parseError(e, "There was an error while refreshing host's Wise token");
logger.error(error.toString());
reportErrorToSentry(e, { feature: FEATURE.TRANSFERWISE });
const error = parseError(e, "There was an error while refreshing host's Wise token");
throw error;
}
};
Expand Down
20 changes: 0 additions & 20 deletions server/paymentProviders/transferwise/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,26 +482,6 @@ async function payExpensesBatchGroup(host, expenses, x2faApproval?: string, remo
batchGroupId,
x2faApproval,
)) as BatchGroup;
// Simulate transfer success in other environments so transactions don't get stuck.
if (['development', 'staging'].includes(config.env)) {
logger.debug(`Wise: Simulating transfer success for batch group ${batchGroup.id}`);
const expenses = await Expense.findAll({
where: { data: { batchGroup: { id: batchGroup.id } } },
});
for (const transferId of batchGroup.transferIds) {
const response = await transferwise.simulateTransferSuccess(connectedAccount, transferId);
logger.debug(`Wise: Simulated transfer success for transfer ${transferId}`);
const expense = expenses.find(e => e.data.transfer.id === transferId);
await expense.update({ data: { ...expense.data, transfer: response } });
// In development mode we don't have webhooks set up, so we need to manually trigger the event handler.
if (config.env === 'development') {
await handleTransferStateChange({
// eslint-disable-next-line camelcase
data: { resource: response, current_state: 'outgoing_payment_sent' },
} as any);
}
}
}
return batchGroup;
} else {
throw new Error('payExpensesBatchGroup: you need to pass either expenses or x2faApproval');
Expand Down
Loading