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