bp - platform fee
MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
@@ -277,7 +277,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
return stripeInvoice;
}
- private async createStripeInvoice(customerID: string, userID: string, idempotencyKey?: string | number): Promise<Stripe.Invoice> {
+ private async createStripeInvoice(customerID: string, userID: string, idempotencyKey: string): Promise<Stripe.Invoice> {
// Let's create the STRIPE invoice
const stripeInvoice: Stripe.Invoice = await this.stripe.invoices.create({
customer: customerID,
@@ -290,7 +290,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
},
}, {
// idempotency_key: idempotencyKey?.toString(),
- idempotencyKey: idempotencyKey?.toString(), // STRIPE version 8.137.0 - property as been renamed!!!
+ idempotencyKey: idempotencyKey?.toString(), // STRIPE version 8.137.0 - property has been renamed!!!
});
return stripeInvoice;
}
@@ -1750,13 +1750,58 @@ 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) {
+ // Synchronize owner if needed
user.billingData = (await this.synchronizeUser(user)).billingData;
- await UserStorage.saveUser(this.tenant, user);
}
+ // Create invoice
+ const invoiceIdempotencyKey = this.buildIdemPotencyKey(billingTransfer.id, 'invoice', 'platformFee');
+ const stripeInvoice = await this.createStripePlatformFeeInvoice(billingTransfer.id, user.billingData.customerID, user.id, invoiceIdempotencyKey);
+ if (!stripeInvoice) {
+ throw new BackendError({
+ message: 'Unexpected situation - platform invoice is not set',
+ module: MODULE_NAME, action: ServerAction.BILLING_TRANSFER_FINALIZE,
+ method: 'markStripeInvoiceAsPaid',
+ });
+ }
+ // Add items to the invoice
+ await this.addItemsToPlatformFeeInvoice(stripeInvoice, billingTransfer, user);
+ // Mark the invoice as paid
+ const stripePaidInvoice = await this.markStripeInvoiceAsPaid(stripeInvoice);
+ // Preserve some information
+ const invoice = this.convertToBillingPlatformInvoice(stripePaidInvoice);
+ return invoice;
+ }
+
+ private async createStripePlatformFeeInvoice(transferID: string, customerID: string, userID: string, idempotencyKey: string): Promise<Stripe.Invoice> {
+ try {
+ // Let's create an empty STRIPE invoice
+ const stripeInvoice: Stripe.Invoice = await this.stripe.invoices.create({
+ customer: customerID,
+ pending_invoice_items_behavior: 'exclude',
+ auto_advance: false, // platform invoices are already paid - c.f.: paid_out_of_band option
+ metadata: {
+ userID,
+ transferID,
+ tenantID: this.tenant.id,
+ },
+ }, {
+ idempotencyKey: idempotencyKey?.toString(), // STRIPE version 8.137.0 - property has been renamed!!!
+ });
+ return stripeInvoice;
+ } catch (e) {
+ throw new BackendError({
+ message: 'Unexpected situation - unable to create the invoice for the platform fee',
+ module: MODULE_NAME, action: ServerAction.BILLING_TRANSFER_FINALIZE,
+ method: 'createStripePlatformFeeInvoice',
+ detailedMessages: { e },
+ });
+ }
+ }
+
+ private async addItemsToPlatformFeeInvoice(stripeInvoice: Stripe.Invoice, billingTransfer: BillingTransfer, user: User): Promise<void> {
// Create invoice items
+ try {
await Promise.all(billingTransfer.sessions.map(async (session) => {
// Build the description with the business owner locale
const description = this.buildTransferLineItemDescription(user, session);
@@ -1764,6 +1809,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
const tax_rates = (session.accountSessionFee.taxExternalID) ? [ session.accountSessionFee.taxExternalID ] : [];
// Prepare item parameters
const parameters: Stripe.InvoiceItemCreateParams = {
+ invoice: stripeInvoice.id,
customer: user.billingData.customerID,
currency: billingTransfer.currency,
tax_rates,
@@ -1776,42 +1822,24 @@ export default class StripeBillingIntegration extends BillingIntegration {
}
};
// Create the invoice item
- return this.createStripeInvoiceItem(parameters, this.buildIdemPotencyKey(session.transactionID, 'invoice', 'platformFee'));
- }));
- } catch (e) {
- throw new BackendError({
- message: 'Unexpected situation - unable to create invoice item',
- module: MODULE_NAME, action: ServerAction.BILLING_TRANSFER_FINALIZE,
- method: 'billPlatformFee',
- detailedMessages: { e },
+ const itemIdempotencyKey = this.buildIdemPotencyKey(session.transactionID, 'invoice', 'platformFee');
+ return await this.stripe.invoiceItems.create(parameters, {
+ idempotencyKey: itemIdempotencyKey // STRIPE version 8.137.0 - property has been renamed!!!
});
- }
- // Create invoice
- let stripeInvoice: Stripe.Invoice;
- try {
- // -------------------------------------------------------------------------------------------
- // TODO - Assuming the business owner is also an EV driver, we have here a potential conflict
- // -------------------------------------------------------------------------------------------
- // > Consider the STRIPE's new options: 'pending_invoice_items_behavior'
- // -------------------------------------------------------------------------------------------
- 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 the invoice for the platform fee',
+ message: 'Unexpected situation - unable to create platform fee invoice item',
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_TRANSFER_FINALIZE,
- method: 'billPlatformFee',
- });
}
+
+ private async markStripeInvoiceAsPaid(stripeInvoice: Stripe.Invoice): Promise<Stripe.Invoice> {
try {
- stripeInvoice = await this.stripe.invoices.pay(stripeInvoice.id, {
+ return await this.stripe.invoices.pay(stripeInvoice.id, {
// Do not charge the customer
paid_out_of_band: true
});
@@ -1819,12 +1847,10 @@ export default class StripeBillingIntegration extends BillingIntegration {
throw new BackendError({
message: 'Unexpected situation - unable to flag the invoice as paid out of band',
module: MODULE_NAME, action: ServerAction.BILLING_TRANSFER_FINALIZE,
- method: 'billPlatformFee',
+ method: 'markStripeInvoiceAsPaid',
detailedMessages: { e },
});
}
- const invoice = this.convertToBillingPlatformInvoice(stripeInvoice);
- return invoice;
}
public convertToBillingPlatformInvoice(stripeInvoice: Stripe.Invoice): BillingPlatformInvoice {
@@ -1860,10 +1886,9 @@ export default class StripeBillingIntegration extends BillingIntegration {
public async sendTransfer(billingTransfer: BillingTransfer, user: User): Promise<string> {
await this.checkConnection();
- // Synchronize owner if needed
if (!user.billingData || !user.billingData.customerID) {
+ // Synchronize owner if needed
user.billingData = (await this.synchronizeUser(user)).billingData;
- await UserStorage.saveUser(this.tenant, user);
}
// Create the actual transfer of funds
let stripeTransfer: Stripe.Transfer;

MODIFY
test/factories/BillingFactory.ts
test/factories/BillingFactory.ts
@@ -4,7 +4,7 @@ import { Factory } from 'rosie';
import faker from 'faker';
export const BillingTransferFactory = Factory.define<BillingTransfer>('billingtransfer')
- .attr('totalAmount', () => faker.datatype.number())
+ .attr('totalAmount', () => faker.datatype.number({ min: 70, max: 79 }))
.attr('accountID', () => faker.datatype.hexaDecimal(24).substring(2).toLowerCase())
.attr('status', () => faker.random.arrayElement([BillingTransferStatus.DRAFT, BillingTransferStatus.PENDING, BillingTransferStatus.FINALIZED, BillingTransferStatus.TRANSFERRED]))
.attr('transferAmount', null)

bp - platform fee