[web] mv PaymentService to modules

GitOrigin-RevId: 73d739f53d96ff9e9d51a535907dbdc878aa6624
This commit is contained in:
Kristina Hjertberg
2025-04-09 16:45:46 +02:00
committed by Copybot
parent 04d36122bd
commit f8f2585164
4 changed files with 32 additions and 219 deletions

View File

@@ -1,81 +0,0 @@
// @ts-check
const RecurlyClient = require('./RecurlyClient.js')
const logger = require('@overleaf/logger')
const { callbackify } = require('util')
/**
* @import { PaymentProviderSubscription, PaymentProviderAccount, PaymentProviderCoupon } from "./PaymentProviderEntities.js"
* @import { ObjectId } from 'mongodb'
*/
/**
* this represents a subset of the Mongo Subscription record
*
* @typedef {object} MongoSubscription
* @property {ObjectId} admin_id
* @property {string} [recurlySubscription_id]
*/
/**
* @typedef {object} PaymentRecord
* @property {PaymentProviderSubscription} subscription
* @property {PaymentProviderAccount | null} account
* @property {PaymentProviderCoupon[]} coupons
*/
/**
* Get payment information from our Mongo record
*
* @param {MongoSubscription} subscription
* @return {Promise<PaymentRecord | null>}
*/
async function getPaymentFromRecord(subscription) {
if (subscription == null) {
logger.debug('no subscription provided')
return null
}
const userId = (subscription.admin_id._id || subscription.admin_id).toString()
const recurlySubscriptionId = subscription.recurlySubscription_id
// TODO: handle non-recurly payment records
if (recurlySubscriptionId == null || recurlySubscriptionId === '') {
logger.debug(
{ userId },
"no recurly subscription id found for user's subscription"
)
return null
}
const subscriptionResponse = await RecurlyClient.promises.getSubscription(
recurlySubscriptionId
)
if (!subscriptionResponse) {
logger.debug(
{ recurlySubscriptionId },
'no recurly subscription found for subscription id'
)
return null
}
const accountResponse =
await RecurlyClient.promises.getAccountForUserId(userId)
const accountCoupons =
await RecurlyClient.promises.getActiveCouponsForUserId(userId)
// TODO: include account and coupons in subscription class instead of separately here
// if Recurly is removed (Recurly needs 2 extra requests to get account & coupon data)
return {
subscription: subscriptionResponse,
account: accountResponse,
coupons: accountCoupons,
}
}
module.exports = {
getPaymentFromRecord: callbackify(getPaymentFromRecord),
promises: {
getPaymentFromRecord,
},
}

View File

@@ -23,8 +23,8 @@ const {
V1ConnectionError,
} = require('../Errors/Errors')
const FeaturesHelper = require('./FeaturesHelper')
const PaymentService = require('./PaymentService')
const { formatCurrency } = require('../../util/currency')
const Modules = require('../../infrastructure/Modules')
/**
* @import { Subscription } from "../../../../types/project/dashboard/subscription"
@@ -82,16 +82,16 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
currentInstitutionsWithLicence,
managedInstitutions,
managedPublishers,
paymentRecord,
fetchedPaymentRecord,
plan,
} = await async.auto({
personalSubscription(cb) {
SubscriptionLocator.getUsersSubscription(user, cb)
},
paymentRecord: [
fetchedPaymentRecord: [
'personalSubscription',
({ personalSubscription }, cb) => {
PaymentService.getPaymentFromRecord(personalSubscription, cb)
Modules.hooks.fire('getPaymentFromRecord', personalSubscription, cb)
},
],
plan: [
@@ -138,6 +138,8 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
},
})
const paymentRecord = fetchedPaymentRecord && fetchedPaymentRecord[0]
if (memberGroupSubscriptions == null) {
memberGroupSubscriptions = []
} else {

View File

@@ -1,117 +0,0 @@
const sinon = require('sinon')
const { expect } = require('chai')
const SandboxedModule = require('sandboxed-module')
const {
PaymentProviderSubscription,
PaymentProviderAccount,
PaymentProviderCoupon,
} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
const MODULE_PATH = '../../../../app/src/Features/Subscription/PaymentService'
describe('PaymentService', function () {
beforeEach(function () {
this.user = {
_id: '123456',
}
this.recurlySubscription = new PaymentProviderSubscription({
id: 'subscription-id',
userId: this.user._id,
currency: 'EUR',
planCode: 'plan-code',
planName: 'plan-name',
planPrice: 13,
addOns: [],
subtotal: 15,
taxRate: 0.1,
taxAmount: 1.5,
total: 14.5,
periodStart: new Date(),
periodEnd: new Date(),
collectionMethod: 'automatic',
})
this.recurlyAccount = new PaymentProviderAccount({
code: this.user._id,
email: 'example@example.com',
hasPastDueInvoice: true,
})
this.recurlyCoupons = [
new PaymentProviderCoupon({
code: 'coupon-code',
name: 'coupon name',
description: 'coupon description',
}),
]
this.mongoSubscription = {
admin_id: this.user,
recurlySubscription_id: this.recurlySubscription.id,
}
this.RecurlyClient = {
promises: {
getSubscription: sinon.stub(),
getAccountForUserId: sinon.stub(),
getActiveCouponsForUserId: sinon.stub(),
},
}
return (this.PaymentService = SandboxedModule.require(MODULE_PATH, {
requires: {
'./RecurlyClient': this.RecurlyClient,
},
}))
})
describe('getPaymentFromRecord', function () {
it('should return null for a missing subscription', async function () {
const response =
await this.PaymentService.promises.getPaymentFromRecord(null)
expect(response).to.equal(null)
})
it('should return null if payment service subscription id is missing', async function () {
this.mongoSubscription.recurlySubscription_id = null
const response = await this.PaymentService.promises.getPaymentFromRecord(
this.mongoSubscription
)
expect(response).to.equal(null)
})
it('should return the subscription', async function () {
this.RecurlyClient.promises.getSubscription.returns(
this.recurlySubscription
)
const response = await this.PaymentService.promises.getPaymentFromRecord(
this.mongoSubscription
)
expect(response.subscription).to.deep.equal(this.recurlySubscription)
})
it('should return account information if found', async function () {
this.RecurlyClient.promises.getSubscription.returns(
this.recurlySubscription
)
this.RecurlyClient.promises.getAccountForUserId.returns(
this.recurlyAccount
)
const response = await this.PaymentService.promises.getPaymentFromRecord(
this.mongoSubscription
)
expect(response.account.email).to.equal(this.recurlyAccount.email)
expect(response.account.hasPastDueInvoice).to.equal(
this.recurlyAccount.hasPastDueInvoice
)
})
it('should include coupons if found', async function () {
this.RecurlyClient.promises.getSubscription.returns(
this.recurlySubscription
)
this.RecurlyClient.promises.getActiveCouponsForUserId.returns(
this.recurlyCoupons
)
const response = await this.PaymentService.promises.getPaymentFromRecord(
this.mongoSubscription
)
expect(response.coupons).to.deep.equal(this.recurlyCoupons)
})
})
})

View File

@@ -143,9 +143,6 @@ describe('SubscriptionViewModelBuilder', function () {
this.PlansLocator = {
findLocalPlanInSettings: sinon.stub(),
}
this.PaymentService = {
getPaymentFromRecord: sinon.stub().yields(),
}
this.SubscriptionViewModelBuilder = SandboxedModule.require(modulePath, {
requires: {
'@overleaf/settings': this.Settings,
@@ -155,7 +152,11 @@ describe('SubscriptionViewModelBuilder', function () {
'./RecurlyWrapper': this.RecurlyWrapper,
'./SubscriptionUpdater': this.SubscriptionUpdater,
'./PlansLocator': this.PlansLocator,
'./PaymentService': this.PaymentService,
'../../infrastructure/Modules': (this.Modules = {
hooks: {
fire: sinon.stub().yields(null, []),
},
}),
'./V1SubscriptionManager': {},
'../Publishers/PublishersGetter': this.PublishersGetter,
'./SubscriptionHelper': {},
@@ -512,14 +513,18 @@ describe('SubscriptionViewModelBuilder', function () {
null,
this.individualSubscription
)
this.PaymentService.getPaymentFromRecord.yields(null, {
subscription: this.paymentRecord,
account: new PaymentProviderAccount({
email: 'example@example.com',
hasPastDueInvoice: false,
}),
coupons: [],
})
this.Modules.hooks.fire
.withArgs('getPaymentFromRecord', this.individualSubscription)
.yields(null, [
{
subscription: this.paymentRecord,
account: new PaymentProviderAccount({
email: 'example@example.com',
hasPastDueInvoice: false,
}),
coupons: [],
},
])
const result =
await this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel(
this.user
@@ -585,11 +590,15 @@ describe('SubscriptionViewModelBuilder', function () {
}),
],
})
this.PaymentService.getPaymentFromRecord.yields(null, {
subscription: this.paymentRecord,
account: {},
coupons: [],
})
this.Modules.hooks.fire
.withArgs('getPaymentFromRecord', this.individualSubscription)
.yields(null, [
{
subscription: this.paymentRecord,
account: {},
coupons: [],
},
])
const result =
await this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel(
this.user