UNPKG

@wepublish/api

Version:
375 lines 15.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SubscriptionService = void 0; const tslib_1 = require("tslib"); const common_1 = require("@nestjs/common"); const client_1 = require("@prisma/client"); const api_1 = require("../../../../payment-api/src"); const date_fns_1 = require("date-fns"); const api_2 = require("../../../../utils-api/src"); let SubscriptionService = exports.SubscriptionService = class SubscriptionService { constructor(prismaService, payments) { this.prismaService = prismaService; this.payments = payments; } getSubscriptionsForInvoiceCreation(runDate, closestRenewalDate) { return tslib_1.__awaiter(this, void 0, void 0, function* () { return yield this.prismaService.subscription.findMany({ where: { paidUntil: { lte: (0, date_fns_1.endOfDay)(closestRenewalDate) }, deactivation: { is: null }, periods: { none: { startsAt: { gt: (0, date_fns_1.startOfDay)(runDate) } } }, autoRenew: true }, include: { periods: true, deactivation: true, user: true, paymentMethod: true, memberPlan: true } }); }); } /** * Get all invoices that are due at the current date or earlier. * @param runDate The current date. * @returns All invoices that are due. */ getInvoicesToCharge(runDate) { return tslib_1.__awaiter(this, void 0, void 0, function* () { return this.prismaService.invoice.findMany({ where: { dueAt: { lte: (0, date_fns_1.endOfDay)(runDate) }, canceledAt: null, paidAt: null, // skip invoices where the subscription has been deleted subscriptionID: { not: null } }, include: { subscription: { include: { paymentMethod: true, memberPlan: true, user: { include: { paymentProviderCustomers: true } } } }, subscriptionPeriods: true, items: true } }); }); } /** * Find all invoices that should be deactivated at the given date and are unpaid. * @param runDate the date to check for. * @returns a list of invoices. */ getSubscriptionsToDeactivate(runDate) { return tslib_1.__awaiter(this, void 0, void 0, function* () { return this.prismaService.invoice.findMany({ where: { scheduledDeactivationAt: { lte: (0, date_fns_1.startOfDay)(runDate) }, canceledAt: null, paidAt: null, // skip invoices where the subscription has been deleted subscriptionID: { not: null } }, include: { subscription: { include: { user: true } } } }); }); } /** * Find all subscriptions that have autorenew false and have a missing deactivation object * @param runDate the date to check for * @returns a list of subscriptions. */ getExpiredNotAutoRenewSubscriptionsToDeactivate(runDate) { return tslib_1.__awaiter(this, void 0, void 0, function* () { return this.prismaService.subscription.findMany({ where: { paidUntil: { lte: (0, date_fns_1.endOfDay)(runDate) }, autoRenew: false, deactivation: { is: null } }, include: { deactivation: true } }); }); } /** * Calculates the start and end of the next subscription period. if no active * periods are passed, the bounds starting from now are returned. * @param periods The currently active periods * @param periodicity The duration of the next period * @returns Start and end date of the next period */ getNextPeriod(periods, periodicity) { if (periods.length === 0) { return { startsAt: (0, date_fns_1.add)(new Date(), { days: 1 }), endsAt: (0, date_fns_1.add)(new Date(), { months: (0, api_2.mapPaymentPeriodToMonths)(periodicity) }) }; } const latestPeriod = periods.reduce(function (prev, current) { return prev.endsAt > current.endsAt ? prev : current; }); return { startsAt: (0, date_fns_1.add)(latestPeriod.endsAt, { days: 1 }), endsAt: (0, date_fns_1.add)(latestPeriod.endsAt, { months: (0, api_2.mapPaymentPeriodToMonths)(periodicity) }) }; } /** * Create an invoice for the new runtime of a subscription. * @param subscription The subscription to create an invoice for. * @param scheduledDeactivation The object containing the deactivation date at the end of the new period. * @returns The invoice. */ createInvoice(subscription, scheduledDeactivation) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (scheduledDeactivation.type !== client_1.SubscriptionEvent.DEACTIVATION_UNPAID) { throw new common_1.BadRequestException(`Given action has not right type! ${scheduledDeactivation.type} should never happen!`); } const amount = subscription.monthlyAmount * (0, api_2.mapPaymentPeriodToMonths)(subscription.paymentPeriodicity); const description = `${subscription.paymentPeriodicity} renewal of subscription ${subscription.memberPlan.name}`; const deactivationDate = (0, date_fns_1.add)(subscription.paidUntil || new Date(), { days: scheduledDeactivation.daysAwayFromEnding || undefined }); return this.prismaService.invoice.create({ data: { mail: subscription.user.email, dueAt: subscription.paidUntil || new Date(), description, items: { create: { name: `${subscription.memberPlan.name}`, description, quantity: 1, amount } }, scheduledDeactivationAt: deactivationDate, subscriptionPeriods: { create: Object.assign({ paymentPeriodicity: subscription.paymentPeriodicity, amount, subscription: { connect: { id: subscription.id } } }, this.getNextPeriod(subscription.periods, subscription.paymentPeriodicity)) }, subscription: { connect: { id: subscription.id } } }, include: { items: true } }); }); } /** * Mark a specific invoice and the corresponding subscription as paid. * @param invoice The invoice to mark. */ markInvoiceAsPaid(invoice) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const newPaidUntil = (0, date_fns_1.add)(invoice.subscription.paidUntil || invoice.subscription.createdAt, { months: (0, api_2.mapPaymentPeriodToMonths)(invoice.subscription.paymentPeriodicity) }); yield this.prismaService.$transaction([ this.prismaService.subscription.update({ where: { id: invoice.subscription.id }, data: { paidUntil: newPaidUntil } }), this.prismaService.invoice.update({ where: { id: invoice.id }, data: { paidAt: new Date() } }) ]); }); } /** * Deactivates the subscription belonging to an invoice. * @param invoice the invoice belonging to subscription. */ deactivateSubscription(invoice) { return tslib_1.__awaiter(this, void 0, void 0, function* () { yield this.prismaService.$transaction([ this.prismaService.subscriptionDeactivation.create({ data: { subscriptionID: invoice.subscriptionID, date: new Date(), reason: client_1.SubscriptionDeactivationReason.invoiceNotPaid } }), this.prismaService.invoice.update({ where: { id: invoice.id }, data: { canceledAt: new Date() } }) ]); }); } /** * Try to charge the payment provider for a specific invoice. If the provider * supports off-session payments, it is charged automatically. If it doesn't * support them, the method returns. * @param invoice The invoice to charge. * @param mailActions The possible mailtemplates to use in case of success/failure. * @returns The transaction status. */ chargeInvoice(invoice, mailActions) { var _a; return tslib_1.__awaiter(this, void 0, void 0, function* () { const paymentProvider = this.payments.findById(invoice.subscription.paymentMethod.paymentProviderID); if (!paymentProvider) { throw new common_1.NotFoundException(`Payment Provider ${(_a = invoice.subscription) === null || _a === void 0 ? void 0 : _a.paymentMethod.paymentProviderID} not found!`); } if (paymentProvider.offSessionPayments) { return yield this.offSessionPayment(invoice, paymentProvider, mailActions); } return { action: undefined, errorCode: '' }; }); } /** * Try to charge an off session payment. This creates a payment record and marks the * invoice as paid if the charge was successful. * @param invoice The invoice to charge. * @param paymentProvider The payment provider. * @param mailActions The possible mails to deliver on successful or failed charge. * @returns The transaction status. */ offSessionPayment(invoice, paymentProvider, mailActions) { var _a, _b; return tslib_1.__awaiter(this, void 0, void 0, function* () { if (invoice.paidAt) { throw new common_1.BadRequestException(`Can not renew paid invoice for subscription ${(_a = invoice.subscription) === null || _a === void 0 ? void 0 : _a.id}`); } if (invoice.canceledAt) { throw new common_1.BadRequestException(`Can not renew canceled invoice for subscription ${(_b = invoice.subscription) === null || _b === void 0 ? void 0 : _b.id}`); } if (!invoice.subscription) { throw new common_1.NotFoundException('Subscription not found!'); } if (!invoice.subscription.user) { throw new common_1.NotFoundException('User not found!'); } const customer = invoice.subscription.user.paymentProviderCustomers.find(ppc => { var _a; return ppc.paymentProviderID === ((_a = invoice.subscription) === null || _a === void 0 ? void 0 : _a.paymentMethod.paymentProviderID); }); const renewalFailedAction = mailActions.find(ma => ma.type === client_1.SubscriptionEvent.RENEWAL_FAILED); if (!customer) { return { action: renewalFailedAction, errorCode: 'customer-not-found' }; } const payment = yield this.prismaService.payment.create({ data: { paymentMethodID: invoice.subscription.paymentMethod.id, invoiceID: invoice.id, state: client_1.PaymentState.created } }); try { const intent = yield paymentProvider.createIntent({ paymentID: payment.id, invoice, saveCustomer: false, customerID: customer.customerID }); yield this.prismaService.payment.update({ where: { id: payment.id }, data: { state: intent.state, intentID: intent.intentID, intentData: intent.intentData, intentSecret: intent.intentSecret, paymentData: intent.paymentData, paymentMethodID: payment.paymentMethodID, invoiceID: payment.invoiceID } }); if (intent.state === client_1.PaymentState.paid) { const renewalSuccessAction = mailActions.find(ma => ma.type === client_1.SubscriptionEvent.RENEWAL_SUCCESS); yield this.markInvoiceAsPaid(invoice); return { action: renewalSuccessAction, errorCode: '' }; } return { action: renewalFailedAction, errorCode: 'user-action-required' }; } catch (e) { yield this.prismaService.payment.update({ where: { id: payment.id }, data: { state: client_1.PaymentState.requiresUserAction, paymentData: JSON.stringify(e), paymentMethodID: payment.paymentMethodID, invoiceID: payment.invoiceID } }); return { action: renewalFailedAction, errorCode: JSON.stringify(e) }; } }); } }; exports.SubscriptionService = SubscriptionService = tslib_1.__decorate([ (0, common_1.Injectable)(), tslib_1.__metadata("design:paramtypes", [client_1.PrismaClient, api_1.PaymentsService]) ], SubscriptionService); //# sourceMappingURL=subscription.service.js.map