UNPKG

@wepublish/api

Version:
518 lines 25.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MemberContext = exports.calculateAmountForPeriodicity = exports.getNextDateForPeriodicity = void 0; const tslib_1 = require("tslib"); const client_1 = require("@prisma/client"); const api_1 = require("../../mail-api/src"); const api_2 = require("../../user-api/src"); const error_1 = require("./error"); const api_3 = require("../../utils-api/src"); const utility_1 = require("./utility"); const api_4 = require("../../membership-api/src"); const date_fns_1 = require("date-fns"); function getNextDateForPeriodicity(start, periodicity) { start = new Date(start.getTime() - utility_1.ONE_DAY_IN_MILLISECONDS); // create new Date object switch (periodicity) { case client_1.PaymentPeriodicity.monthly: return new Date(start.setMonth(start.getMonth() + 1)); case client_1.PaymentPeriodicity.quarterly: return new Date(start.setMonth(start.getMonth() + 3)); case client_1.PaymentPeriodicity.biannual: return new Date(start.setMonth(start.getMonth() + 6)); case client_1.PaymentPeriodicity.yearly: return new Date(start.setMonth(start.getMonth() + 12)); } } exports.getNextDateForPeriodicity = getNextDateForPeriodicity; function calculateAmountForPeriodicity(monthlyAmount, periodicity) { switch (periodicity) { case client_1.PaymentPeriodicity.monthly: return monthlyAmount; case client_1.PaymentPeriodicity.quarterly: return monthlyAmount * 3; case client_1.PaymentPeriodicity.biannual: return monthlyAmount * 6; case client_1.PaymentPeriodicity.yearly: return monthlyAmount * 12; } } exports.calculateAmountForPeriodicity = calculateAmountForPeriodicity; class MemberContext { constructor(props) { this.loaders = props.loaders; this.paymentProviders = props.paymentProviders; this.prisma = props.prisma; this.mailContext = props.mailContext; this.getLoginUrlForUser = props.getLoginUrlForUser; } handleSubscriptionChange({ subscription }) { return tslib_1.__awaiter(this, void 0, void 0, function* () { // Check if user has any unpaid Periods and delete them and their invoices if so const invoices = yield this.prisma.invoice.findMany({ where: { subscriptionID: subscription.id }, include: { items: true } }); const openInvoice = invoices.find(invoice => (invoice === null || invoice === void 0 ? void 0 : invoice.paidAt) === null && (invoice === null || invoice === void 0 ? void 0 : invoice.canceledAt) === null); if (openInvoice || subscription.paidUntil === null || subscription.paidUntil <= new Date()) { const periodToDelete = subscription.periods.find(period => period.invoiceID === (openInvoice === null || openInvoice === void 0 ? void 0 : openInvoice.id)); if (periodToDelete) { yield this.prisma.subscription.update({ where: { id: subscription.id }, data: { periods: { delete: { id: periodToDelete.id } } } }); } if (openInvoice) { yield this.prisma.invoice.delete({ where: { id: openInvoice.id } }); } const finalUpdatedSubscription = yield this.prisma.subscription.findUnique({ where: { id: subscription.id }, include: { deactivation: true, periods: true, properties: true } }); if (!finalUpdatedSubscription) throw new Error('Error during updateSubscription'); // renew user subscription yield this.renewSubscriptionForUser({ subscription: finalUpdatedSubscription }); return finalUpdatedSubscription; } return subscription; }); } renewSubscriptionForUser({ subscription }) { return tslib_1.__awaiter(this, void 0, void 0, function* () { try { const { periods = [], paidUntil, deactivation } = subscription; if (deactivation) { (0, api_3.logger)('memberContext').info('Subscription with id %s is deactivated and will not be renewed', subscription.id); return null; } periods.sort((periodA, periodB) => { if (periodA.endsAt < periodB.endsAt) return -1; if (periodA.endsAt > periodB.endsAt) return 1; return 0; }); if (periods.length > 0 && (paidUntil === null || (paidUntil !== null && periods[periods.length - 1].endsAt > paidUntil))) { const period = periods[periods.length - 1]; const invoice = yield this.prisma.invoice.findUnique({ where: { id: period.invoiceID }, include: { items: true } }); // only return the invoice if it hasn't been canceled. Otherwise // create a new period and a new invoice if (!(invoice === null || invoice === void 0 ? void 0 : invoice.canceledAt)) { return invoice; } } const startDate = new Date(paidUntil && paidUntil.getTime() > new Date().getTime() - utility_1.ONE_MONTH_IN_MILLISECONDS ? paidUntil.getTime() + utility_1.ONE_DAY_IN_MILLISECONDS : new Date().getTime()); const nextDate = getNextDateForPeriodicity(startDate, subscription.paymentPeriodicity); const amount = calculateAmountForPeriodicity(subscription.monthlyAmount, subscription.paymentPeriodicity); const user = yield this.prisma.user.findUnique({ where: { id: subscription.userID }, select: api_2.unselectPassword }); if (!user) { (0, api_3.logger)('memberContext').info('User with id "%s" not found', subscription.userID); return null; } const subscriptionFlows = yield this.getActionsForSubscriptions({ memberplanId: subscription.memberPlanID, paymentMethodId: subscription.paymentMethodID, periodicity: subscription.paymentPeriodicity, autorenwal: subscription.autoRenew, events: [client_1.SubscriptionEvent.DEACTIVATION_UNPAID] }); const subscriptionFlowActionDeactivationUnpaid = subscriptionFlows.find(a => a.type === client_1.SubscriptionEvent.DEACTIVATION_UNPAID); if (!subscriptionFlowActionDeactivationUnpaid) { (0, api_3.logger)('memberContext').info('Subscription flow for subscription with id "%s" not found', subscription.id); return null; } const deactivationDate = (0, date_fns_1.add)(subscription.paidUntil || new Date(), { days: subscriptionFlowActionDeactivationUnpaid.daysAwayFromEnding }); const newInvoice = yield this.prisma.invoice.create({ data: { subscriptionID: subscription.id, description: `Membership from ${startDate.toISOString()} for ${user.name || user.email}`, mail: user.email, dueAt: startDate, scheduledDeactivationAt: deactivationDate, items: { create: { name: 'Membership', description: `From ${startDate.toISOString()} to ${nextDate.toISOString()}`, amount, quantity: 1 } } }, include: { items: true } }); yield this.prisma.subscriptionPeriod.create({ data: { subscriptionId: subscription.id, startsAt: startDate, endsAt: nextDate, paymentPeriodicity: subscription.paymentPeriodicity, amount, invoiceID: newInvoice.id } }); (0, api_3.logger)('memberContext').info('Renewed or created fresh subscription with id %s', subscription.id); return newInvoice; } catch (error) { (0, api_3.logger)('memberContext').error(error, 'Error while renewing subscription with id %s', subscription.id); } return null; }); } getOffSessionPaymentProviderIDs() { return this.paymentProviders .filter(provider => provider.offSessionPayments) .map(provider => provider.id); } chargeInvoice({ user, invoice, paymentMethodID, customer }) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const offSessionPaymentProvidersID = this.getOffSessionPaymentProviderIDs(); const paymentMethods = yield this.prisma.paymentMethod.findMany(); const paymentMethodIDs = paymentMethods .filter(method => offSessionPaymentProvidersID.includes(method.paymentProviderID)) .map(method => method.id); if (!paymentMethodIDs.includes(paymentMethodID)) { (0, api_3.logger)('memberContext').warn('PaymentMethod %s does not support off session payments', paymentMethodID); return false; } const paymentMethod = paymentMethods.find(method => method.id === paymentMethodID); if (!paymentMethod) { (0, api_3.logger)('memberContext').error('PaymentMethod %s does not exist', paymentMethodID); return false; } const paymentProvider = this.paymentProviders.find(provider => provider.id === paymentMethod.paymentProviderID); if (!paymentProvider) { (0, api_3.logger)('memberContext').error('PaymentProvider %s does not exist', paymentMethod.paymentProviderID); return false; } const payment = yield this.prisma.payment.create({ data: { paymentMethodID, invoiceID: invoice.id, state: client_1.PaymentState.created } }); const intent = yield paymentProvider.createIntent({ paymentID: payment.id, invoice, saveCustomer: false, customerID: customer.customerID }); const updatedPayment = yield this.prisma.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.requiresUserAction) { if (!invoice.subscriptionID) { (0, api_3.logger)('memberContext').error('Invoice %s has no associated subscriptionID', invoice.id); return false; } const subscription = yield this.prisma.subscription.findUnique({ where: { id: invoice.subscriptionID } }); if (!subscription) { (0, api_3.logger)('memberContext').error('No subscription found with ID %s', invoice.subscriptionID); return false; } const remoteTemplate = yield this.getSubscriptionTemplateIdentifier(subscription, client_1.SubscriptionEvent.RENEWAL_FAILED); if (remoteTemplate) { yield this.mailContext.sendMail({ externalMailTemplateId: remoteTemplate, recipient: user, optionalData: { invoice, paymentProviderID: paymentProvider.id, errorCode: intent.errorCode }, mailType: api_1.mailLogType.UserFlow }); } else { (0, api_3.logger)('memberContext').info('No remote template found for subscription %s and event RENEWAL_FAILED', subscription.id); } const { items } = invoice, invoiceData = tslib_1.__rest(invoice, ["items"]); yield this.prisma.invoice.update({ where: { id: invoice.id }, data: Object.assign(Object.assign({}, invoiceData), { items: { deleteMany: { invoiceId: invoiceData.id }, create: items.map((_a) => { var { invoiceId } = _a, item = tslib_1.__rest(_a, ["invoiceId"]); return item; }) } }) }); return updatedPayment; } return updatedPayment; }); } cancelInvoicesForSubscription(subscriptionID) { return tslib_1.__awaiter(this, void 0, void 0, function* () { // Cancel invoices when subscription is canceled const invoices = yield this.prisma.invoice.findMany({ where: { subscriptionID } }); for (const invoice of invoices) { if (!invoice || invoice.paidAt !== null || invoice.canceledAt !== null) continue; yield this.prisma.invoice.update({ where: { id: invoice.id }, data: { canceledAt: new Date() } }); } }); } deactivateSubscription({ subscription, deactivationReason }) { var _a, _b; return tslib_1.__awaiter(this, void 0, void 0, function* () { // deactivate remote subscriptions yield this.cancelRemoteSubscription({ subscriptionId: subscription.id, reason: deactivationReason }); const now = new Date(); const deactivationDate = subscription.paidUntil !== null && subscription.paidUntil > now ? subscription.paidUntil : now; yield this.cancelInvoicesForSubscription(subscription.id); const updatedSubscription = yield this.prisma.subscription.update({ where: { id: subscription.id }, data: { deactivation: { upsert: { create: { date: (_a = deactivationDate !== null && deactivationDate !== void 0 ? deactivationDate : subscription.paidUntil) !== null && _a !== void 0 ? _a : new Date(), reason: deactivationReason !== null && deactivationReason !== void 0 ? deactivationReason : client_1.SubscriptionDeactivationReason.none }, update: { date: (_b = deactivationDate !== null && deactivationDate !== void 0 ? deactivationDate : subscription.paidUntil) !== null && _b !== void 0 ? _b : new Date(), reason: deactivationReason !== null && deactivationReason !== void 0 ? deactivationReason : client_1.SubscriptionDeactivationReason.none } } } }, include: { deactivation: true, properties: true } }); // Send deactivation Mail yield this.sendSubscriptionDeactivationMail(subscription, deactivationReason); return updatedSubscription; }); } cancelRemoteSubscription({ subscriptionId, reason }) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const subscription = yield this.prisma.subscription.findUnique({ where: { id: subscriptionId }, include: { properties: true } }); const { paymentProviderID } = yield this.getPaymentMethodByIDOrSlug(this.loaders, undefined, subscription.paymentMethodID); const paymentProvider = this.paymentProviders.find(paymentProvider => paymentProvider.id === paymentProviderID); yield paymentProvider.cancelRemoteSubscription({ subscription }); }); } /** * Function used to * @param memberPlanID * @param memberPlanSlug * @param paymentMethodID * @param paymentMethodSlug */ validateInputParamsCreateSubscription(memberPlanID, memberPlanSlug, paymentMethodID, paymentMethodSlug) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if ((memberPlanID == null && memberPlanSlug == null) || (memberPlanID != null && memberPlanSlug != null)) { throw new error_1.UserInputError('You must provide either `memberPlanID` or `memberPlanSlug`.'); } if ((paymentMethodID == null && paymentMethodSlug == null) || (paymentMethodID != null && paymentMethodSlug != null)) { throw new error_1.UserInputError('You must provide either `paymentMethodID` or `paymentMethodSlug`.'); } }); } getMemberPlanByIDOrSlug(loaders, memberPlanSlug, memberPlanID) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const memberPlan = memberPlanID ? yield loaders.activeMemberPlansByID.load(memberPlanID) : yield loaders.activeMemberPlansBySlug.load(memberPlanSlug); if (!memberPlan) throw new error_1.NotFound('MemberPlan', memberPlanID || memberPlanSlug); return memberPlan; }); } getPaymentMethodByIDOrSlug(loaders, paymentMethodSlug, paymentMethodID) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const paymentMethod = paymentMethodID ? yield loaders.activePaymentMethodsByID.load(paymentMethodID) : yield loaders.activePaymentMethodsBySlug.load(paymentMethodSlug); if (!paymentMethod) throw new error_1.NotFound('PaymentMethod', paymentMethodID || paymentMethodSlug); return paymentMethod; }); } validateSubscriptionPaymentConfiguration(memberPlan, autoRenew, paymentPeriodicity, paymentMethod) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (!memberPlan.availablePaymentMethods.some(apm => { if (apm.forceAutoRenewal && !autoRenew) return false; return (apm.paymentPeriodicities.includes(paymentPeriodicity) && apm.paymentMethodIDs.includes(paymentMethod.id)); })) throw new error_1.PaymentConfigurationNotAllowed(); }); } processSubscriptionProperties(subscriptionProperties) { return tslib_1.__awaiter(this, void 0, void 0, function* () { return Array.isArray(subscriptionProperties) ? subscriptionProperties.map(property => { return { public: true, key: property.key, value: property.value }; }) : []; }); } createSubscription(subscriptionClient, userID, paymentMethodId, paymentPeriodicity, monthlyAmount, memberPlanId, properties, autoRenew, startsAt) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const subscription = yield subscriptionClient.create({ data: { userID, startsAt: startsAt ? startsAt : new Date(), modifiedAt: new Date(), paymentMethodID: paymentMethodId, paymentPeriodicity, paidUntil: null, monthlyAmount, memberPlanID: memberPlanId, properties: { createMany: { data: properties } }, autoRenew }, include: { deactivation: true, periods: true, properties: true } }); if (!subscription) { (0, api_3.logger)('mutation.public').error('Could not create new subscription for userID "%s"', userID); throw new error_1.InternalError(); } const invoice = yield this.renewSubscriptionForUser({ subscription }); // Send subscribe mail yield this.sendMailForSubscriptionEvent(client_1.SubscriptionEvent.SUBSCRIBE, subscription, {}); return { subscription, invoice }; }); } getSubscriptionTemplateIdentifier(subscription, subscriptionEvent) { return tslib_1.__awaiter(this, void 0, void 0, function* () { return new api_4.SubscriptionEventDictionary(this.prisma).getSubsciptionTemplateIdentifier(subscription, subscriptionEvent); }); } getActionsForSubscriptions(query) { return tslib_1.__awaiter(this, void 0, void 0, function* () { return new api_4.SubscriptionEventDictionary(this.prisma).getActionsForSubscriptions(query); }); } sendSubscriptionDeactivationMail(subscription, deactivation) { return tslib_1.__awaiter(this, void 0, void 0, function* () { let event = client_1.SubscriptionEvent.DEACTIVATION_BY_USER; if (deactivation === client_1.SubscriptionDeactivationReason.invoiceNotPaid) { event = client_1.SubscriptionEvent.DEACTIVATION_UNPAID; } return this.sendMailForSubscriptionEvent(event, subscription, {}); }); } sendMailForSubscriptionEvent(subscriptionEvent, subscription, optionalData) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const user = yield this.prisma.user.findUnique({ where: { id: subscription.userID }, select: api_2.unselectPassword }); if (!user) { (0, api_3.logger)('MemberContext').warn(`User not found %s`, subscription.userID); return; } const remoteTemplate = yield this.getSubscriptionTemplateIdentifier(subscription, subscriptionEvent); if (!remoteTemplate) { (0, api_3.logger)('MemberContext').warn(`RemoteTemplate <%s> for subscription <%s> not found!`, subscriptionEvent, subscription.id); return; } yield this.mailContext.sendMail({ externalMailTemplateId: remoteTemplate, recipient: user, optionalData: Object.assign({ subscription }, optionalData), mailType: api_1.mailLogType.UserFlow }); }); } } exports.MemberContext = MemberContext; //# sourceMappingURL=memberContext.js.map