bp - sendTransfer
MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
@@ -712,6 +712,8 @@ export default abstract class BillingIntegration {
abstract billPlatformFee(transfer: BillingTransfer, user: User): Promise<BillingPlatformInvoice>;
+ abstract sendTransfer(transfer: BillingTransfer, user: User): Promise<string>;
+
protected async triggerTransferPreparation(billingInvoice: BillingInvoice): Promise<void> {
if (!billingInvoice.sessions) {
// This should not happen!

MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
@@ -1744,6 +1744,11 @@ export default class StripeBillingIntegration extends BillingIntegration {
public async billPlatformFee(billingTransfer: BillingTransfer, user: User): Promise<BillingPlatformInvoice> {
await this.checkConnection();
try {
+ // Synchronize owner if needed
+ if (!user.billingData || !user.billingData.customerID) {
+ user.billingData = (await this.synchronizeUser(user)).billingData;
+ await UserStorage.saveUser(this.tenant, user);
+ }
// Create invoice items
await Promise.all(billingTransfer.sessions.map(async (session) => {
// Build the description with the business owner locale
@@ -1769,9 +1774,9 @@ export default class StripeBillingIntegration extends BillingIntegration {
} catch (e) {
throw new BackendError({
message: 'Unexpected situation - unable to create invoice item',
- detailedMessages: { e },
module: MODULE_NAME, action: ServerAction.BILLING_TRANSFER_FINALIZE,
method: 'billPlatformFee',
+ detailedMessages: { e },
});
}
// Create invoice
@@ -1785,17 +1790,17 @@ export default class StripeBillingIntegration extends BillingIntegration {
stripeInvoice = await this.createStripeInvoice(user.billingData.customerID, user.id, this.buildIdemPotencyKey(billingTransfer.id, 'invoice', 'platformFee'));
} catch (e) {
throw new BackendError({
- message: 'Unexpected situation - unable to create transfer invoice',
- detailedMessages: { e },
+ message: 'Unexpected situation - unable to create the invoice for the platform fee',
module: MODULE_NAME, action: ServerAction.BILLING_TRANSFER_FINALIZE,
method: 'billPlatformFee',
+ detailedMessages: { e },
});
}
if (!stripeInvoice) {
throw new BackendError({
message: 'Unexpected situation - platform invoice is not set',
- module: MODULE_NAME, action: ServerAction.BILLING,
- method: 'convertToBillingPlatformInvoice',
+ module: MODULE_NAME, action: ServerAction.BILLING_TRANSFER_FINALIZE,
+ method: 'billPlatformFee',
});
}
try {
@@ -1806,9 +1811,9 @@ export default class StripeBillingIntegration extends BillingIntegration {
} catch (e) {
throw new BackendError({
message: 'Unexpected situation - unable to flag the invoice as paid out of band',
- detailedMessages: { e },
module: MODULE_NAME, action: ServerAction.BILLING_TRANSFER_FINALIZE,
method: 'billPlatformFee',
+ detailedMessages: { e },
});
}
const invoice = this.convertToBillingPlatformInvoice(stripeInvoice);
@@ -1845,4 +1850,42 @@ export default class StripeBillingIntegration extends BillingIntegration {
return description;
}
+ public async sendTransfer(billingTransfer: BillingTransfer, user: User): Promise<string> {
+ await this.checkConnection();
+ // Synchronize owner if needed
+ if (!user.billingData || !user.billingData.customerID) {
+ user.billingData = (await this.synchronizeUser(user)).billingData;
+ await UserStorage.saveUser(this.tenant, user);
+ }
+ // Create the actual transfer of funds
+ let stripeTransfer: Stripe.Transfer;
+ try {
+ stripeTransfer = await this.stripe.transfers.create({
+ amount: billingTransfer.transferAmount,
+ currency: billingTransfer.currency,
+ destination: billingTransfer.account.accountExternalID,
+ // transfer_group: billingTransfer.id, // TODO - is there any benefit to set a transfer_group?
+ metadata: {
+ userID: user.id,
+ transferID: billingTransfer.id,
+ tenantID: this.tenant.id,
+ }
+ });
+ } catch (e) {
+ throw new BackendError({
+ message: 'Unexpected situation - unable to create transfer invoice',
+ module: MODULE_NAME, action: ServerAction.BILLING_TRANSFER_SEND,
+ method: 'sendTransfer',
+ detailedMessages: { e },
+ });
+ }
+ if (!stripeTransfer) {
+ throw new BackendError({
+ message: 'Unexpected situation - platform transfer is not set',
+ module: MODULE_NAME, action: ServerAction.BILLING_TRANSFER_SEND,
+ method: 'sendTransfer',
+ });
+ }
+ return stripeTransfer.id;
+ }
}

MODIFY
src/server/rest/v1/router/api/BillingRouter.ts
src/server/rest/v1/router/api/BillingRouter.ts
@@ -191,7 +191,7 @@ export default class BillingRouter {
private buildRouteBillingSendTransfer(): void {
this.router.patch(`/${RESTServerRoute.REST_BILLING_TRANSFER_SEND}`, (req: Request, res: Response, next: NextFunction) => {
req.params.ID = req.params.id;
- void RouterUtils.handleRestServerAction(BillingService.handleSendTransferInvoice.bind(this), ServerAction.BILLING_TRANSFER_SEND, req, res, next);
+ void RouterUtils.handleRestServerAction(BillingService.handleSendTransfer.bind(this), ServerAction.BILLING_TRANSFER_SEND, req, res, next);
});
}
}

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
@@ -701,12 +701,7 @@ export default class BillingService {
UtilsService.assertObjectExists(action, billingAccount, `Account ID '${transfer.accountID}' does not exist`, MODULE_NAME, 'handleSendTransferInvoice', req.user);
// Get the account owner
const user = await UserStorage.getUser(req.tenant, billingAccount.businessOwnerID);
- UtilsService.assertObjectExists(action, user, `User ID '${transfer.accountID}' does not exist`, MODULE_NAME, 'handleSendTransferInvoice', req.user);
- // Synchronize owner if needed
- if (!user.billingData || !user.billingData.customerID) {
- user.billingData = (await billingImpl.forceSynchronizeUser(user)).billingData;
- await UserStorage.saveUser(req.tenant, user);
- }
+ UtilsService.assertObjectExists(action, user, `User ID '${billingAccount.businessOwnerID}' does not exist`, MODULE_NAME, 'handleSendTransferInvoice', req.user);
// Generate the invoice with a fee per session
const invoice = await billingImpl.billPlatformFee(transfer, user);
// Let's keep track of the invoice data
@@ -721,7 +716,7 @@ export default class BillingService {
next();
}
- public static async handleSendTransferInvoice(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ public static async handleSendTransfer(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING_PLATFORM,
Action.BILLING_SEND_TRANSFER, Entity.BILLING_TRANSFER, MODULE_NAME, 'handleSendTransferInvoice');
@@ -734,24 +729,32 @@ export default class BillingService {
throw new AppError({
errorCode: HTTPError.GENERAL_ERROR,
message: 'Billing service is not configured',
- module: MODULE_NAME, method: 'handleSendTransferInvoice',
+ module: MODULE_NAME, method: 'handleSendTransfer',
action: action,
user: req.user
});
}
// Get the transfer
const transfer = await BillingStorage.getTransferByID(req.tenant, filteredRequest.ID);
- UtilsService.assertObjectExists(action, transfer, `Transfer ID '${filteredRequest.ID}' does not exist`, MODULE_NAME, 'handleSendTransferInvoice', req.user);
+ UtilsService.assertObjectExists(action, transfer, `Transfer ID '${filteredRequest.ID}' does not exist`, MODULE_NAME, 'handleSendTransfer', req.user);
// Check if the transfer is in draft status
if (transfer.status !== BillingTransferStatus.FINALIZED) {
throw new AppError({
errorCode: HTTPError.GENERAL_ERROR,
- message: 'Transfer finalization aborted - current status should be FINALIZED',
- module: MODULE_NAME, method: 'handleSendTransferInvoice',
+ message: 'Operation aborted - current status should be FINALIZED',
+ module: MODULE_NAME, method: 'handleSendTransfer',
action: action,
user: req.user
});
}
+ // Get the targeted account
+ const billingAccount = await BillingStorage.getAccountByID(req.tenant, transfer.accountID);
+ UtilsService.assertObjectExists(action, billingAccount, `Account ID '${transfer.accountID}' does not exist`, MODULE_NAME, 'handleSendTransfer', req.user);
+ // Get the account owner
+ const user = await UserStorage.getUser(req.tenant, billingAccount.businessOwnerID);
+ UtilsService.assertObjectExists(action, user, `User ID '${billingAccount.businessOwnerID}' does not exist`, MODULE_NAME, 'handleSendTransfer', req.user);
+ // Send the funds
+ transfer.transferExternalID = await billingImpl.sendTransfer(transfer, user);
transfer.status = BillingTransferStatus.TRANSFERRED;
await BillingStorage.saveTransfer(req.tenant, transfer);
// TODO - send an email notification with the invoice

bp - sendTransfer