@wepublish/api
Version:
API core for we.publish.
375 lines • 15.8 kB
JavaScript
"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