Merge pull request #24680 from overleaf/kh-rename-recurly-namespace

[web] rename recurly namespace

GitOrigin-RevId: b7cfd26923d47bd7f3de4140be24d2d1ef20f6c8
This commit is contained in:
Kristina
2025-04-10 11:27:09 +02:00
committed by Copybot
parent 84dd590650
commit 36fcc401cc
46 changed files with 514 additions and 496 deletions

View File

@@ -49,7 +49,7 @@ const Modules = require('../../infrastructure/Modules')
const UserGetter = require('../User/UserGetter')
const {
isStandaloneAiAddOnPlanCode,
} = require('../Subscription/RecurlyEntities')
} = require('../Subscription/PaymentProviderEntities')
const SubscriptionController = require('../Subscription/SubscriptionController.js')
const { formatCurrency } = require('../../util/currency')

View File

@@ -14,7 +14,7 @@ const UserGetter = require('../User/UserGetter')
const AnalyticsManager = require('../Analytics/AnalyticsManager')
const Queues = require('../../infrastructure/Queues')
const Modules = require('../../infrastructure/Modules')
const { AI_ADD_ON_CODE } = require('./RecurlyEntities')
const { AI_ADD_ON_CODE } = require('./PaymentProviderEntities')
/**
* Enqueue a job for refreshing features for the given user

View File

@@ -9,7 +9,7 @@ const AI_ADD_ON_CODE = 'assistant'
const MEMBERS_LIMIT_ADD_ON_CODE = 'additional-license'
const STANDALONE_AI_ADD_ON_CODES = ['assistant', 'assistant-annual']
class RecurlySubscription {
class PaymentProviderSubscription {
/**
* @param {object} props
* @param {string} props.id
@@ -17,7 +17,7 @@ class RecurlySubscription {
* @param {string} props.planCode
* @param {string} props.planName
* @param {number} props.planPrice
* @param {RecurlySubscriptionAddOn[]} [props.addOns]
* @param {PaymentProviderSubscriptionAddOn[]} [props.addOns]
* @param {number} props.subtotal
* @param {number} [props.taxRate]
* @param {number} [props.taxAmount]
@@ -26,7 +26,7 @@ class RecurlySubscription {
* @param {Date} props.periodStart
* @param {Date} props.periodEnd
* @param {string} props.collectionMethod
* @param {RecurlySubscriptionChange} [props.pendingChange]
* @param {PaymentProviderSubscriptionChange} [props.pendingChange]
* @param {string} [props.service]
* @param {string} [props.state]
* @param {Date|null} [props.trialPeriodEnd]
@@ -97,7 +97,7 @@ class RecurlySubscription {
/**
* Change this subscription's plan
*
* @return {RecurlySubscriptionChangeRequest}
* @return {PaymentProviderSubscriptionChangeRequest}
*/
getRequestForPlanChange(planCode) {
const currentPlan = PlansLocator.findLocalPlanInSettings(this.planCode)
@@ -115,7 +115,7 @@ class RecurlySubscription {
newPlan
)
const changeRequest = new RecurlySubscriptionChangeRequest({
const changeRequest = new PaymentProviderSubscriptionChangeRequest({
subscription: this,
timeframe: shouldChangeAtTermEnd ? 'term_end' : 'now',
planCode,
@@ -127,7 +127,7 @@ class RecurlySubscription {
(!shouldChangeAtTermEnd && this.hasAddOn(AI_ADD_ON_CODE)) ||
(shouldChangeAtTermEnd && this.hasAddOnNextPeriod(AI_ADD_ON_CODE))
) {
const addOnUpdate = new RecurlySubscriptionAddOnUpdate({
const addOnUpdate = new PaymentProviderSubscriptionAddOnUpdate({
code: AI_ADD_ON_CODE,
quantity: 1,
})
@@ -143,7 +143,7 @@ class RecurlySubscription {
* @param {string} code
* @param {number} [quantity]
* @param {number} [unitPrice]
* @return {RecurlySubscriptionChangeRequest} - the change request to send to
* @return {PaymentProviderSubscriptionChangeRequest} - the change request to send to
* Recurly
*
* @throws {DuplicateAddOnError} if the add-on is already present on the subscription
@@ -158,9 +158,9 @@ class RecurlySubscription {
const addOnUpdates = this.addOns.map(addOn => addOn.toAddOnUpdate())
addOnUpdates.push(
new RecurlySubscriptionAddOnUpdate({ code, quantity, unitPrice })
new PaymentProviderSubscriptionAddOnUpdate({ code, quantity, unitPrice })
)
return new RecurlySubscriptionChangeRequest({
return new PaymentProviderSubscriptionChangeRequest({
subscription: this,
timeframe: 'now',
addOnUpdates,
@@ -172,7 +172,7 @@ class RecurlySubscription {
*
* @param {string} code
* @param {number} quantity
* @return {RecurlySubscriptionChangeRequest} - the change request to send to
* @return {PaymentProviderSubscriptionChangeRequest} - the change request to send to
* Recurly
*
* @throws {AddOnNotPresentError} if the subscription doesn't have the add-on
@@ -198,7 +198,7 @@ class RecurlySubscription {
return update
})
return new RecurlySubscriptionChangeRequest({
return new PaymentProviderSubscriptionChangeRequest({
subscription: this,
timeframe: 'now',
addOnUpdates,
@@ -209,7 +209,7 @@ class RecurlySubscription {
* Remove an add-on from this subscription
*
* @param {string} code
* @return {RecurlySubscriptionChangeRequest}
* @return {PaymentProviderSubscriptionChangeRequest}
*
* @throws {AddOnNotPresentError} if the subscription doesn't have the add-on
*/
@@ -226,7 +226,7 @@ class RecurlySubscription {
const addOnUpdates = this.addOns
.filter(addOn => addOn.code !== code)
.map(addOn => addOn.toAddOnUpdate())
return new RecurlySubscriptionChangeRequest({
return new PaymentProviderSubscriptionChangeRequest({
subscription: this,
timeframe: 'term_end',
addOnUpdates,
@@ -237,19 +237,19 @@ class RecurlySubscription {
* Upgrade group plan with the plan code provided
*
* @param {string} newPlanCode
* @return {RecurlySubscriptionChangeRequest}
* @return {PaymentProviderSubscriptionChangeRequest}
*/
getRequestForGroupPlanUpgrade(newPlanCode) {
// Ensure all the existing add-ons are added to the new plan
const addOns = this.addOns.map(
addOn =>
new RecurlySubscriptionAddOnUpdate({
new PaymentProviderSubscriptionAddOnUpdate({
code: addOn.code,
quantity: addOn.quantity,
})
)
return new RecurlySubscriptionChangeRequest({
return new PaymentProviderSubscriptionChangeRequest({
subscription: this,
timeframe: 'now',
addOnUpdates: addOns,
@@ -270,7 +270,7 @@ class RecurlySubscription {
/**
* An add-on attached to a subscription
*/
class RecurlySubscriptionAddOn {
class PaymentProviderSubscriptionAddOn {
/**
* @param {object} props
* @param {string} props.code
@@ -290,7 +290,7 @@ class RecurlySubscriptionAddOn {
* Return an add-on update that doesn't modify the add-on
*/
toAddOnUpdate() {
return new RecurlySubscriptionAddOnUpdate({
return new PaymentProviderSubscriptionAddOnUpdate({
code: this.code,
quantity: this.quantity,
unitPrice: this.unitPrice,
@@ -298,17 +298,19 @@ class RecurlySubscriptionAddOn {
}
}
class RecurlySubscriptionChangeRequest {
class PaymentProviderSubscriptionChangeRequest {
/**
* @param {object} props
* @param {RecurlySubscription} props.subscription
* @param {PaymentProviderSubscription} props.subscription
* @param {"now" | "term_end"} props.timeframe
* @param {string} [props.planCode]
* @param {RecurlySubscriptionAddOnUpdate[]} [props.addOnUpdates]
* @param {PaymentProviderSubscriptionAddOnUpdate[]} [props.addOnUpdates]
*/
constructor(props) {
if (props.planCode == null && props.addOnUpdates == null) {
throw new OError('Invalid RecurlySubscriptionChangeRequest', { props })
throw new OError('Invalid PaymentProviderSubscriptionChangeRequest', {
props,
})
}
this.subscription = props.subscription
this.timeframe = props.timeframe
@@ -317,7 +319,7 @@ class RecurlySubscriptionChangeRequest {
}
}
class RecurlySubscriptionAddOnUpdate {
class PaymentProviderSubscriptionAddOnUpdate {
/**
* @param {object} props
* @param {string} props.code
@@ -331,15 +333,15 @@ class RecurlySubscriptionAddOnUpdate {
}
}
class RecurlySubscriptionChange {
class PaymentProviderSubscriptionChange {
/**
* @param {object} props
* @param {RecurlySubscription} props.subscription
* @param {PaymentProviderSubscription} props.subscription
* @param {string} props.nextPlanCode
* @param {string} props.nextPlanName
* @param {number} props.nextPlanPrice
* @param {RecurlySubscriptionAddOn[]} props.nextAddOns
* @param {RecurlyImmediateCharge} [props.immediateCharge]
* @param {PaymentProviderSubscriptionAddOn[]} props.nextAddOns
* @param {PaymentProviderImmediateCharge} [props.immediateCharge]
*/
constructor(props) {
this.subscription = props.subscription
@@ -349,7 +351,12 @@ class RecurlySubscriptionChange {
this.nextAddOns = props.nextAddOns
this.immediateCharge =
props.immediateCharge ??
new RecurlyImmediateCharge({ subtotal: 0, tax: 0, total: 0, discount: 0 })
new PaymentProviderImmediateCharge({
subtotal: 0,
tax: 0,
total: 0,
discount: 0,
})
this.subtotal = this.nextPlanPrice
for (const addOn of this.nextAddOns) {
@@ -388,7 +395,7 @@ class CreditCardPaymentMethod {
}
}
class RecurlyImmediateCharge {
class PaymentProviderImmediateCharge {
/**
* @param {object} props
* @param {number} props.subtotal
@@ -407,7 +414,7 @@ class RecurlyImmediateCharge {
/**
* An add-on configuration, independent of any subscription
*/
class RecurlyAddOn {
class PaymentProviderAddOn {
/**
* @param {object} props
* @param {string} props.code
@@ -422,7 +429,7 @@ class RecurlyAddOn {
/**
* A plan configuration
*/
class RecurlyPlan {
class PaymentProviderPlan {
/**
* @param {object} props
* @param {string} props.code
@@ -437,7 +444,7 @@ class RecurlyPlan {
/**
* A coupon in the payment provider
*/
class RecurlyCoupon {
class PaymentProviderCoupon {
/**
* @param {object} props
* @param {string} props.code
@@ -454,7 +461,7 @@ class RecurlyCoupon {
/**
* An account in the payment provider
*/
class RecurlyAccount {
class PaymentProviderAccount {
/**
* @param {object} props
* @param {string} props.code
@@ -480,7 +487,7 @@ function isStandaloneAiAddOnPlanCode(planCode) {
/**
* Returns whether subscription change will have have the ai bundle once the change is processed
*
* @param {RecurlySubscriptionChange} subscriptionChange The subscription change object coming from Recurly
* @param {PaymentProviderSubscriptionChange} subscriptionChange The subscription change object coming from payment provider
*
* @return {boolean}
*/
@@ -497,18 +504,18 @@ module.exports = {
AI_ADD_ON_CODE,
MEMBERS_LIMIT_ADD_ON_CODE,
STANDALONE_AI_ADD_ON_CODES,
RecurlySubscription,
RecurlySubscriptionAddOn,
RecurlySubscriptionChange,
RecurlySubscriptionChangeRequest,
RecurlySubscriptionAddOnUpdate,
PaymentProviderSubscription,
PaymentProviderSubscriptionAddOn,
PaymentProviderSubscriptionChange,
PaymentProviderSubscriptionChangeRequest,
PaymentProviderSubscriptionAddOnUpdate,
PaypalPaymentMethod,
CreditCardPaymentMethod,
RecurlyAddOn,
RecurlyPlan,
RecurlyCoupon,
RecurlyAccount,
PaymentProviderAddOn,
PaymentProviderPlan,
PaymentProviderCoupon,
PaymentProviderAccount,
isStandaloneAiAddOnPlanCode,
subscriptionChangeIsAiAssistUpgrade,
RecurlyImmediateCharge,
PaymentProviderImmediateCharge,
}

View File

@@ -5,7 +5,7 @@ const logger = require('@overleaf/logger')
const { callbackify } = require('util')
/**
* @import { RecurlySubscription, RecurlyAccount, RecurlyCoupon } from "./RecurlyEntities"
* @import { PaymentProviderSubscription, PaymentProviderAccount, PaymentProviderCoupon } from "./PaymentProviderEntities.js"
* @import { ObjectId } from 'mongodb'
*/
@@ -19,9 +19,9 @@ const { callbackify } = require('util')
/**
* @typedef {object} PaymentRecord
* @property {RecurlySubscription} subscription
* @property {RecurlyAccount | null} account
* @property {RecurlyCoupon[]} coupons
* @property {PaymentProviderSubscription} subscription
* @property {PaymentProviderAccount | null} account
* @property {PaymentProviderCoupon[]} coupons
*/
/**

View File

@@ -7,24 +7,24 @@ const OError = require('@overleaf/o-error')
const { callbackify } = require('util')
const UserGetter = require('../User/UserGetter')
const {
RecurlySubscription,
RecurlySubscriptionAddOn,
RecurlySubscriptionChange,
PaymentProviderSubscription,
PaymentProviderSubscriptionAddOn,
PaymentProviderSubscriptionChange,
PaypalPaymentMethod,
CreditCardPaymentMethod,
RecurlyAddOn,
RecurlyPlan,
RecurlyCoupon,
RecurlyAccount,
RecurlyImmediateCharge,
} = require('./RecurlyEntities')
PaymentProviderAddOn,
PaymentProviderPlan,
PaymentProviderCoupon,
PaymentProviderAccount,
PaymentProviderImmediateCharge,
} = require('./PaymentProviderEntities')
const {
MissingBillingInfoError,
SubtotalLimitExceededError,
} = require('./Errors')
/**
* @import { RecurlySubscriptionChangeRequest } from './RecurlyEntities'
* @import { PaymentProviderSubscriptionChangeRequest } from './PaymentProviderEntities'
* @import { PaymentMethod } from './types'
*/
@@ -37,7 +37,7 @@ const client = new recurly.Client(recurlyApiKey)
* Get account for a given user
*
* @param {string} userId
* @return {Promise<RecurlyAccount | null>}
* @return {Promise<PaymentProviderAccount | null>}
*/
async function getAccountForUserId(userId) {
try {
@@ -76,7 +76,7 @@ async function createAccountForUserId(userId) {
* Get active coupons for a given user
*
* @param {string} userId
* @return {Promise<RecurlyCoupon[]>}
* @return {Promise<PaymentProviderCoupon[]>}
*/
async function getActiveCouponsForUserId(userId) {
try {
@@ -104,7 +104,7 @@ async function getActiveCouponsForUserId(userId) {
* Get a subscription from Recurly
*
* @param {string} subscriptionId
* @return {Promise<RecurlySubscription>}
* @return {Promise<PaymentProviderSubscription>}
*/
async function getSubscription(subscriptionId) {
const subscription = await client.getSubscription(`uuid-${subscriptionId}`)
@@ -118,7 +118,7 @@ async function getSubscription(subscriptionId) {
* error if the user has more than one subscription.
*
* @param {string} userId
* @return {Promise<RecurlySubscription | null>}
* @return {Promise<PaymentProviderSubscription | null>}
*/
async function getSubscriptionForUser(userId) {
try {
@@ -153,7 +153,7 @@ async function getSubscriptionForUser(userId) {
/**
* Request a susbcription change from Recurly
*
* @param {RecurlySubscriptionChangeRequest} changeRequest
* @param {PaymentProviderSubscriptionChangeRequest} changeRequest
*/
async function applySubscriptionChangeRequest(changeRequest) {
const body = subscriptionChangeRequestToApi(changeRequest)
@@ -193,8 +193,8 @@ async function applySubscriptionChangeRequest(changeRequest) {
/**
* Preview a subscription change
*
* @param {RecurlySubscriptionChangeRequest} changeRequest
* @return {Promise<RecurlySubscriptionChange>}
* @param {PaymentProviderSubscriptionChangeRequest} changeRequest
* @return {Promise<PaymentProviderSubscriptionChange>}
*/
async function previewSubscriptionChange(changeRequest) {
const body = subscriptionChangeRequestToApi(changeRequest)
@@ -303,7 +303,7 @@ async function getPaymentMethod(userId) {
*
* @param {string} planCode
* @param {string} addOnCode
* @return {Promise<RecurlyAddOn>}
* @return {Promise<PaymentProviderAddOn>}
*/
async function getAddOn(planCode, addOnCode) {
const addOn = await client.getPlanAddOn(
@@ -317,7 +317,7 @@ async function getAddOn(planCode, addOnCode) {
* Get the configuration for a given plan
*
* @param {string} planCode
* @return {Promise<RecurlyPlan>}
* @return {Promise<PaymentProviderPlan>}
*/
async function getPlan(planCode) {
const plan = await client.getPlan(`code-${planCode}`)
@@ -330,10 +330,10 @@ function subscriptionIsCanceledOrExpired(subscription) {
}
/**
* Build a RecurlyAccount from Recurly API data
* Build a PaymentProviderAccount from Recurly API data
*
* @param {recurly.Account} apiAccount
* @return {RecurlyAccount}
* @return {PaymentProviderAccount}
*/
function accountFromApi(apiAccount) {
if (apiAccount.code == null || apiAccount.email == null) {
@@ -341,7 +341,7 @@ function accountFromApi(apiAccount) {
account: apiAccount,
})
}
return new RecurlyAccount({
return new PaymentProviderAccount({
code: apiAccount.code,
email: apiAccount.email,
hasPastDueInvoice: apiAccount.hasPastDueInvoice ?? false,
@@ -349,10 +349,10 @@ function accountFromApi(apiAccount) {
}
/**
* Build a RecurlyCoupon from Recurly API data
* Build a PaymentProviderCoupon from Recurly API data
*
* @param {recurly.CouponRedemption} apiRedemption
* @return {RecurlyCoupon}
* @return {PaymentProviderCoupon}
*/
function couponFromApi(apiRedemption) {
if (apiRedemption.coupon == null || apiRedemption.coupon.code == null) {
@@ -360,7 +360,7 @@ function couponFromApi(apiRedemption) {
coupon: apiRedemption,
})
}
return new RecurlyCoupon({
return new PaymentProviderCoupon({
code: apiRedemption.coupon.code,
name: apiRedemption.coupon.name ?? '',
description: apiRedemption.coupon.hostedPageDescription ?? '',
@@ -368,10 +368,10 @@ function couponFromApi(apiRedemption) {
}
/**
* Build a RecurlySubscription from Recurly API data
* Build a PaymentProviderSubscription from Recurly API data
*
* @param {recurly.Subscription} apiSubscription
* @return {RecurlySubscription}
* @return {PaymentProviderSubscription}
*/
function subscriptionFromApi(apiSubscription) {
if (
@@ -394,7 +394,7 @@ function subscriptionFromApi(apiSubscription) {
})
}
const subscription = new RecurlySubscription({
const subscription = new PaymentProviderSubscription({
id: apiSubscription.uuid,
userId: apiSubscription.account.code,
planCode: apiSubscription.plan.code,
@@ -427,10 +427,10 @@ function subscriptionFromApi(apiSubscription) {
}
/**
* Build a RecurlySubscriptionAddOn from Recurly API data
* Build a PaymentProviderSubscriptionAddOn from Recurly API data
*
* @param {recurly.SubscriptionAddOn} addOn
* @return {RecurlySubscriptionAddOn}
* @return {PaymentProviderSubscriptionAddOn}
*/
function subscriptionAddOnFromApi(addOn) {
if (
@@ -442,7 +442,7 @@ function subscriptionAddOnFromApi(addOn) {
throw new OError('Invalid Recurly add-on', { addOn })
}
return new RecurlySubscriptionAddOn({
return new PaymentProviderSubscriptionAddOn({
code: addOn.addOn.code,
name: addOn.addOn.name,
quantity: addOn.quantity ?? 1,
@@ -451,11 +451,11 @@ function subscriptionAddOnFromApi(addOn) {
}
/**
* Build a RecurlySubscriptionChange from Recurly API data
* Build a PaymentProviderSubscriptionChange from Recurly API data
*
* @param {RecurlySubscription} subscription - the current subscription
* @param {PaymentProviderSubscription} subscription - the current subscription
* @param {recurly.SubscriptionChange} subscriptionChange - the subscription change returned from the API
* @return {RecurlySubscriptionChange}
* @return {PaymentProviderSubscriptionChange}
*/
function subscriptionChangeFromApi(subscription, subscriptionChange) {
if (
@@ -472,7 +472,7 @@ function subscriptionChangeFromApi(subscription, subscriptionChange) {
subscriptionAddOnFromApi
)
return new RecurlySubscriptionChange({
return new PaymentProviderSubscriptionChange({
subscription,
nextPlanCode: subscriptionChange.plan.code,
nextPlanName: subscriptionChange.plan.name,
@@ -486,7 +486,7 @@ function subscriptionChangeFromApi(subscription, subscriptionChange) {
* Compute immediate charge based on invoice collection
*
* @param {recurly.SubscriptionChange} subscriptionChange - the subscription change returned from the API
* @return {RecurlyImmediateCharge}
* @return {PaymentProviderImmediateCharge}
*/
function computeImmediateCharge(subscriptionChange) {
const roundToTwoDecimal = (/** @type {number} */ num) =>
@@ -506,7 +506,7 @@ function computeImmediateCharge(subscriptionChange) {
tax = roundToTwoDecimal(tax + (creditInvoice.tax ?? 0))
discount = roundToTwoDecimal(discount + (creditInvoice.discount ?? 0))
}
return new RecurlyImmediateCharge({
return new PaymentProviderImmediateCharge({
subtotal,
total,
tax,
@@ -540,41 +540,41 @@ function paymentMethodFromApi(billingInfo) {
}
/**
* Build a RecurlyAddOn from Recurly API data
* Build a PaymentProviderAddOn from Recurly API data
*
* @param {recurly.AddOn} addOn
* @return {RecurlyAddOn}
* @return {PaymentProviderAddOn}
*/
function addOnFromApi(addOn) {
if (addOn.code == null || addOn.name == null) {
throw new OError('Invalid Recurly add-on', { addOn })
}
return new RecurlyAddOn({
return new PaymentProviderAddOn({
code: addOn.code,
name: addOn.name,
})
}
/**
* Build a RecurlyPlan from Recurly API data
* Build a PaymentProviderPlan from Recurly API data
*
* @param {recurly.Plan} plan
* @return {RecurlyPlan}
* @return {PaymentProviderPlan}
*/
function planFromApi(plan) {
if (plan.code == null || plan.name == null) {
throw new OError('Invalid Recurly add-on', { plan })
}
return new RecurlyPlan({
return new PaymentProviderPlan({
code: plan.code,
name: plan.name,
})
}
/**
* Build an API request from a RecurlySubscriptionChangeRequest
* Build an API request from a PaymentProviderSubscriptionChangeRequest
*
* @param {RecurlySubscriptionChangeRequest} changeRequest
* @param {PaymentProviderSubscriptionChangeRequest} changeRequest
* @return {recurly.SubscriptionChangeCreate}
*/
function subscriptionChangeRequestToApi(changeRequest) {

View File

@@ -1,7 +1,7 @@
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
const AnalyticsManager = require('../Analytics/AnalyticsManager')
const SubscriptionEmailHandler = require('./SubscriptionEmailHandler')
const { AI_ADD_ON_CODE } = require('./RecurlyEntities')
const { AI_ADD_ON_CODE } = require('./PaymentProviderEntities')
const { ObjectId } = require('mongodb-legacy')
const INVOICE_SUBSCRIPTION_LIMIT = 10

View File

@@ -22,14 +22,14 @@ const Modules = require('../../infrastructure/Modules')
const async = require('async')
const HttpErrorHandler = require('../Errors/HttpErrorHandler')
const RecurlyClient = require('./RecurlyClient')
const { AI_ADD_ON_CODE } = require('./RecurlyEntities')
const { AI_ADD_ON_CODE } = require('./PaymentProviderEntities')
const PlansLocator = require('./PlansLocator')
const RecurlyEntities = require('./RecurlyEntities')
const PaymentProviderEntities = require('./PaymentProviderEntities')
/**
* @import { SubscriptionChangeDescription } from '../../../../types/subscription/subscription-change-preview'
* @import { SubscriptionChangePreview } from '../../../../types/subscription/subscription-change-preview'
* @import { RecurlySubscriptionChange } from './RecurlyEntities'
* @import { PaymentProviderSubscriptionChange } from './PaymentProviderEntities'
* @import { PaymentMethod } from './types'
*/
@@ -325,7 +325,9 @@ async function previewAddonPurchase(req, res) {
const hasBundleViaWritefull =
await FeaturesUpdater.promises.hasFeaturesViaWritefull(userId)
const isAiUpgrade =
RecurlyEntities.subscriptionChangeIsAiAssistUpgrade(subscriptionChange)
PaymentProviderEntities.subscriptionChangeIsAiAssistUpgrade(
subscriptionChange
)
if (hasBundleViaWritefull && isAiUpgrade) {
return res.redirect(
'/user/subscription?redirect-reason=writefull-entitled'
@@ -724,7 +726,7 @@ function getPlanNameForDisplay(planName, planCode) {
* Build a subscription change preview for display purposes
*
* @param {SubscriptionChangeDescription} subscriptionChangeDescription A description of the change for the frontend
* @param {RecurlySubscriptionChange} subscriptionChange The subscription change object coming from Recurly
* @param {PaymentProviderSubscriptionChange} subscriptionChange The subscription change object coming from Recurly
* @param {PaymentMethod} paymentMethod The payment method associated to the user
* @return {SubscriptionChangePreview}
*/

View File

@@ -7,7 +7,7 @@ const RecurlyClient = require('./RecurlyClient')
const PlansLocator = require('./PlansLocator')
const SubscriptionHandler = require('./SubscriptionHandler')
const GroupPlansData = require('./GroupPlansData')
const { MEMBERS_LIMIT_ADD_ON_CODE } = require('./RecurlyEntities')
const { MEMBERS_LIMIT_ADD_ON_CODE } = require('./PaymentProviderEntities')
const {
ManuallyCollectedError,
PendingChangeError,

View File

@@ -14,7 +14,7 @@ const UserUpdater = require('../User/UserUpdater')
const { NotFoundError } = require('../Errors/Errors')
/**
* @import { RecurlySubscription, RecurlySubscriptionChange } from './RecurlyEntities'
* @import { PaymentProviderSubscription, PaymentProviderSubscriptionChange } from './PaymentProviderEntities'
*/
async function validateNoSubscriptionInRecurly(userId) {
@@ -69,7 +69,7 @@ async function createSubscription(user, subscriptionDetails, recurlyTokenIds) {
*
* @param {string} userId
* @param {string} planCode
* @return {Promise<RecurlySubscriptionChange>}
* @return {Promise<PaymentProviderSubscriptionChange>}
*/
async function previewSubscriptionChange(userId, planCode) {
const subscription = await getSubscriptionForUser(userId)
@@ -283,7 +283,7 @@ async function _updateSubscriptionFromRecurly(subscription) {
*
* @param {string} userId
* @param {string} addOnCode
* @return {Promise<RecurlySubscriptionChange>}
* @return {Promise<PaymentProviderSubscriptionChange>}
*/
async function previewAddonPurchase(userId, addOnCode) {
const subscription = await getSubscriptionForUser(userId)
@@ -353,7 +353,7 @@ async function removeAddon(userId, addOnCode) {
* Throws a NotFoundError if the subscription can't be found
*
* @param {string} userId
* @return {Promise<RecurlySubscription>}
* @return {Promise<PaymentProviderSubscription>}
*/
async function getSubscriptionForUser(userId) {
const subscription =

View File

@@ -5,7 +5,7 @@ const logger = require('@overleaf/logger')
const {
AI_ADD_ON_CODE,
isStandaloneAiAddOnPlanCode,
} = require('./RecurlyEntities')
} = require('./PaymentProviderEntities')
require('./GroupPlansData') // make sure dynamic group plans are loaded
const SubscriptionLocator = {

View File

@@ -5,7 +5,7 @@ const PlansLocator = require('./PlansLocator')
const {
isStandaloneAiAddOnPlanCode,
MEMBERS_LIMIT_ADD_ON_CODE,
} = require('./RecurlyEntities')
} = require('./PaymentProviderEntities')
const SubscriptionFormatters = require('./SubscriptionFormatters')
const SubscriptionLocator = require('./SubscriptionLocator')
const SubscriptionUpdater = require('./SubscriptionUpdater')
@@ -138,8 +138,6 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
},
})
const recurlySubscription = paymentRecord && paymentRecord.subscription
if (memberGroupSubscriptions == null) {
memberGroupSubscriptions = []
} else {
@@ -214,14 +212,6 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
personalSubscription.plan = plan
}
// Subscription DB object contains a recurly property, used to cache trial info
// on the project-list. However, this can cause the wrong template to render,
// if we do not have any subscription data from Recurly (recurlySubscription)
// TODO: Delete this workaround once recurly cache property name migration rolled out.
if (personalSubscription) {
delete personalSubscription.recurly
}
function getPlanOnlyDisplayPrice(
totalPlanPriceInCents,
taxRate,
@@ -243,7 +233,7 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
return formatCurrency(
totalPlanPriceInCents -
allAddOnsTotalPriceInCentsExceptAdditionalLicensePrice,
recurlySubscription.currency,
paymentRecord.subscription.currency,
locale
)
}
@@ -257,7 +247,7 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
if (totalPriceInCents > 0) {
prev[curr.code] = formatCurrency(
totalPriceInCents,
recurlySubscription.currency,
paymentRecord.subscription.currency,
locale
)
}
@@ -267,15 +257,15 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
}, {})
}
if (personalSubscription && recurlySubscription) {
const tax = recurlySubscription.taxAmount || 0
if (personalSubscription && paymentRecord && paymentRecord.subscription) {
const tax = paymentRecord.subscription.taxAmount || 0
// Some plans allow adding more seats than the base plan provides.
// This is recorded as a subscription add on.
// Note: taxAmount already includes the tax for any addon.
let addOnPrice = 0
let additionalLicenses = 0
const addOns = recurlySubscription.addOns || []
const taxRate = recurlySubscription.taxRate
const addOns = paymentRecord.subscription.addOns || []
const taxRate = paymentRecord.subscription.taxRate
addOns.forEach(addOn => {
addOnPrice += addOn.quantity * addOn.unitPrice
if (addOn.code === plan.membersLimitAddOn) {
@@ -283,7 +273,7 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
}
})
const totalLicenses = (plan.membersLimit || 0) + additionalLicenses
personalSubscription.recurly = {
personalSubscription.payment = {
taxRate,
billingDetailsLink: buildHostedLink('billing-details'),
accountManagementLink: buildHostedLink('account-management'),
@@ -291,25 +281,26 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
addOns,
totalLicenses,
nextPaymentDueAt: SubscriptionFormatters.formatDateTime(
recurlySubscription.periodEnd
paymentRecord.subscription.periodEnd
),
nextPaymentDueDate: SubscriptionFormatters.formatDate(
recurlySubscription.periodEnd
paymentRecord.subscription.periodEnd
),
currency: recurlySubscription.currency,
state: recurlySubscription.state,
currency: paymentRecord.subscription.currency,
state: paymentRecord.subscription.state,
trialEndsAtFormatted: SubscriptionFormatters.formatDateTime(
recurlySubscription.trialPeriodEnd
paymentRecord.subscription.trialPeriodEnd
),
trialEndsAt: recurlySubscription.trialPeriodEnd,
trialEndsAt: paymentRecord.subscription.trialPeriodEnd,
activeCoupons: paymentRecord.coupons,
accountEmail: paymentRecord.account.email,
hasPastDueInvoice: paymentRecord.account.hasPastDueInvoice,
pausedAt: recurlySubscription.pausePeriodStart,
remainingPauseCycles: recurlySubscription.remainingPauseCycles,
pausedAt: paymentRecord.subscription.pausePeriodStart,
remainingPauseCycles: paymentRecord.subscription.remainingPauseCycles,
}
if (recurlySubscription.pendingChange) {
const pendingPlanCode = recurlySubscription.pendingChange.nextPlanCode
if (paymentRecord.subscription.pendingChange) {
const pendingPlanCode =
paymentRecord.subscription.pendingChange.nextPlanCode
const pendingPlan = PlansLocator.findLocalPlanInSettings(pendingPlanCode)
if (pendingPlan == null) {
throw new Error(`No plan found for planCode '${pendingPlanCode}'`)
@@ -317,10 +308,10 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
let pendingAdditionalLicenses = 0
let pendingAddOnTax = 0
let pendingAddOnPrice = 0
if (recurlySubscription.pendingChange.nextAddOns) {
const pendingRecurlyAddons =
recurlySubscription.pendingChange.nextAddOns
pendingRecurlyAddons.forEach(addOn => {
if (paymentRecord.subscription.pendingChange.nextAddOns) {
const pendingAddOns =
paymentRecord.subscription.pendingChange.nextAddOns
pendingAddOns.forEach(addOn => {
pendingAddOnPrice += addOn.quantity * addOn.unitPrice
if (addOn.code === pendingPlan.membersLimitAddOn) {
pendingAdditionalLicenses += addOn.quantity
@@ -328,50 +319,50 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
})
// Need to calculate tax ourselves as we don't get tax amounts for pending subs
pendingAddOnTax =
personalSubscription.recurly.taxRate * pendingAddOnPrice
pendingPlan.addOns = pendingRecurlyAddons
personalSubscription.payment.taxRate * pendingAddOnPrice
pendingPlan.addOns = pendingAddOns
}
const pendingSubscriptionTax =
personalSubscription.recurly.taxRate *
recurlySubscription.pendingChange.nextPlanPrice
personalSubscription.payment.taxRate *
paymentRecord.subscription.pendingChange.nextPlanPrice
const totalPrice =
recurlySubscription.pendingChange.nextPlanPrice +
paymentRecord.subscription.pendingChange.nextPlanPrice +
pendingAddOnPrice +
pendingAddOnTax +
pendingSubscriptionTax
personalSubscription.recurly.displayPrice = formatCurrency(
personalSubscription.payment.displayPrice = formatCurrency(
totalPrice,
recurlySubscription.currency,
paymentRecord.subscription.currency,
locale
)
personalSubscription.recurly.planOnlyDisplayPrice =
personalSubscription.payment.planOnlyDisplayPrice =
getPlanOnlyDisplayPrice(
totalPrice,
taxRate,
recurlySubscription.pendingChange.nextAddOns
paymentRecord.subscription.pendingChange.nextAddOns
)
personalSubscription.recurly.addOnDisplayPricesWithoutAdditionalLicense =
personalSubscription.payment.addOnDisplayPricesWithoutAdditionalLicense =
getAddOnDisplayPricesWithoutAdditionalLicense(
taxRate,
recurlySubscription.pendingChange.nextAddOns
paymentRecord.subscription.pendingChange.nextAddOns
)
const pendingTotalLicenses =
(pendingPlan.membersLimit || 0) + pendingAdditionalLicenses
personalSubscription.recurly.pendingAdditionalLicenses =
personalSubscription.payment.pendingAdditionalLicenses =
pendingAdditionalLicenses
personalSubscription.recurly.pendingTotalLicenses = pendingTotalLicenses
personalSubscription.payment.pendingTotalLicenses = pendingTotalLicenses
personalSubscription.pendingPlan = pendingPlan
} else {
const totalPrice = recurlySubscription.planPrice + addOnPrice + tax
personalSubscription.recurly.displayPrice = formatCurrency(
const totalPrice = paymentRecord.subscription.planPrice + addOnPrice + tax
personalSubscription.payment.displayPrice = formatCurrency(
totalPrice,
recurlySubscription.currency,
paymentRecord.subscription.currency,
locale
)
personalSubscription.recurly.planOnlyDisplayPrice =
personalSubscription.payment.planOnlyDisplayPrice =
getPlanOnlyDisplayPrice(totalPrice, taxRate, addOns)
personalSubscription.recurly.addOnDisplayPricesWithoutAdditionalLicense =
personalSubscription.payment.addOnDisplayPricesWithoutAdditionalLicense =
getAddOnDisplayPricesWithoutAdditionalLicense(taxRate, addOns)
}
}

View File

@@ -1,3 +1,6 @@
import { PaypalPaymentMethod, CreditCardPaymentMethod } from './RecurlyEntities'
import {
PaypalPaymentMethod,
CreditCardPaymentMethod,
} from './PaymentProviderEntities'
export type PaymentMethod = PaypalPaymentMethod | CreditCardPaymentMethod

View File

@@ -23,9 +23,9 @@ block append meta
meta(name="ol-showGroupDiscount", data-type="boolean", content=showGroupDiscount)
meta(name="ol-groupSettingsEnabledFor", data-type="json" content=groupSettingsEnabledFor)
meta(name="ol-user" data-type="json" content=user)
if (personalSubscription && personalSubscription.recurly)
if (personalSubscription && personalSubscription.payment)
meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey)
meta(name="ol-recommendedCurrency" content=personalSubscription.recurly.currency)
meta(name="ol-recommendedCurrency" content=personalSubscription.payment.currency)
meta(name="ol-groupPlans" data-type="json" content=groupPlans)
block content

View File

@@ -15,7 +15,7 @@ import { debugConsole } from '@/utils/debugging'
import * as eventTracking from '../../../../infrastructure/event-tracking'
import PauseDuck from '../../images/pause-duck.svg'
import GenericErrorAlert from './generic-error-alert'
import { RecurlySubscription } from '../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../types/subscription/dashboard/subscription'
const pauseMonthDurationOptions = [1, 2, 3]
@@ -35,13 +35,13 @@ export default function PauseSubscriptionModal() {
const location = useLocation()
function handleCancelSubscriptionClick() {
const subscription = personalSubscription as RecurlySubscription
const subscription = personalSubscription as PaidSubscription
eventTracking.sendMB('subscription-page-cancel-button-click', {
plan_code: subscription?.planCode,
is_trial:
subscription?.recurly.trialEndsAtFormatted &&
subscription?.recurly.trialEndsAt &&
new Date(subscription.recurly.trialEndsAt).getTime() > Date.now(),
subscription?.payment.trialEndsAtFormatted &&
subscription?.payment.trialEndsAt &&
new Date(subscription.payment.trialEndsAt).getTime() > Date.now(),
})
setShowCancellation(true)
}

View File

@@ -18,9 +18,9 @@ function PersonalSubscriptionRecurlySyncEmail() {
runAsync(postJSON('/user/subscription/account/email'))
}
if (!personalSubscription || !('recurly' in personalSubscription)) return null
if (!personalSubscription || !('payment' in personalSubscription)) return null
const recurlyEmail = personalSubscription.recurly.accountEmail
const recurlyEmail = personalSubscription.payment.accountEmail
if (!userEmail || recurlyEmail === userEmail) return null

View File

@@ -1,5 +1,5 @@
import { Trans, useTranslation } from 'react-i18next'
import { RecurlySubscription } from '../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../types/subscription/dashboard/subscription'
import { PausedSubscription } from './states/active/paused'
import { ActiveSubscriptionNew } from '@/features/subscription/components/dashboard/states/active/active-new'
import { CanceledSubscription } from './states/canceled'
@@ -11,7 +11,7 @@ import OLNotification from '@/features/ui/components/ol/ol-notification'
function PastDueSubscriptionAlert({
subscription,
}: {
subscription: RecurlySubscription
subscription: PaidSubscription
}) {
const { t } = useTranslation()
return (
@@ -21,7 +21,7 @@ function PastDueSubscriptionAlert({
<>
{t('account_has_past_due_invoice_change_plan_warning')}{' '}
<a
href={subscription.recurly.accountManagementLink}
href={subscription.payment.accountManagementLink}
target="_blank"
rel="noreferrer noopener"
>
@@ -57,10 +57,10 @@ function RedirectAlerts() {
function PersonalSubscriptionStates({
subscription,
}: {
subscription: RecurlySubscription
subscription: PaidSubscription
}) {
const { t } = useTranslation()
const state = subscription?.recurly.state
const state = subscription?.payment.state
if (state === 'active') {
// This version handles subscriptions with and without addons
@@ -83,7 +83,7 @@ function PersonalSubscription() {
if (!personalSubscription) return null
if (!('recurly' in personalSubscription)) {
if (!('payment' in personalSubscription)) {
return (
<p>
<Trans
@@ -97,11 +97,11 @@ function PersonalSubscription() {
return (
<>
<RedirectAlerts />
{personalSubscription.recurly.hasPastDueInvoice && (
{personalSubscription.payment.hasPastDueInvoice && (
<PastDueSubscriptionAlert subscription={personalSubscription} />
)}
<PersonalSubscriptionStates
subscription={personalSubscription as RecurlySubscription}
subscription={personalSubscription as PaidSubscription}
/>
{recurlyLoadError && (
<OLNotification

View File

@@ -1,7 +1,7 @@
import { useTranslation, Trans } from 'react-i18next'
import { PriceExceptions } from '../../../shared/price-exceptions'
import { useSubscriptionDashboardContext } from '../../../../context/subscription-dashboard-context'
import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { CancelSubscriptionButton } from './cancel-subscription-button'
import { CancelSubscription } from './cancel-plan/cancel-subscription'
import { TrialEnding } from './trial-ending'
@@ -33,7 +33,7 @@ import Notification from '@/shared/components/notification'
export function ActiveSubscriptionNew({
subscription,
}: {
subscription: RecurlySubscription
subscription: PaidSubscription
}) {
const { t } = useTranslation()
const {
@@ -77,14 +77,14 @@ export function ActiveSubscriptionNew({
}
}
const hasPendingPause = Boolean(
subscription.recurly.state === 'active' &&
subscription.recurly.remainingPauseCycles &&
subscription.recurly.remainingPauseCycles > 0
subscription.payment.state === 'active' &&
subscription.payment.remainingPauseCycles &&
subscription.payment.remainingPauseCycles > 0
)
const isLegacyPlan =
subscription.recurly.totalLicenses !==
subscription.recurly.additionalLicenses
subscription.payment.totalLicenses !==
subscription.payment.additionalLicenses
return (
<>
@@ -103,7 +103,7 @@ export function ActiveSubscriptionNew({
{subscription.plan.annual ? (
<Trans
i18nKey="billed_annually_at"
values={{ price: subscription.recurly.displayPrice }}
values={{ price: subscription.payment.displayPrice }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
components={[
@@ -116,7 +116,7 @@ export function ActiveSubscriptionNew({
) : (
<Trans
i18nKey="billed_monthly_at"
values={{ price: subscription.recurly.displayPrice }}
values={{ price: subscription.payment.displayPrice }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
components={[
@@ -131,7 +131,7 @@ export function ActiveSubscriptionNew({
<p className="mb-1">
<Trans
i18nKey="renews_on"
values={{ date: subscription.recurly.nextPaymentDueDate }}
values={{ date: subscription.payment.nextPaymentDueDate }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
components={[<strong />]} // eslint-disable-line react/jsx-key
@@ -139,7 +139,7 @@ export function ActiveSubscriptionNew({
</p>
<div>
<a
href={subscription.recurly.accountManagementLink}
href={subscription.payment.accountManagementLink}
target="_blank"
rel="noreferrer noopener"
className="me-2"
@@ -147,7 +147,7 @@ export function ActiveSubscriptionNew({
{t('view_invoices')}
</a>
<a
href={subscription.recurly.billingDetailsLink}
href={subscription.payment.billingDetailsLink}
target="_blank"
rel="noreferrer noopener"
>
@@ -171,21 +171,21 @@ export function ActiveSubscriptionNew({
subscription.pendingPlan.name !== subscription.plan.name && (
<p className="mb-1">{t('want_change_to_apply_before_plan_end')}</p>
)}
{isInFreeTrial(subscription.recurly.trialEndsAt) &&
subscription.recurly.trialEndsAtFormatted && (
{isInFreeTrial(subscription.payment.trialEndsAt) &&
subscription.payment.trialEndsAtFormatted && (
<TrialEnding
trialEndsAtFormatted={subscription.recurly.trialEndsAtFormatted}
trialEndsAtFormatted={subscription.payment.trialEndsAtFormatted}
className="mb-1"
/>
)}
{subscription.recurly.totalLicenses > 0 && (
{subscription.payment.totalLicenses > 0 && (
<p className="mb-1">
{isLegacyPlan && subscription.recurly.additionalLicenses > 0 ? (
{isLegacyPlan && subscription.payment.additionalLicenses > 0 ? (
<Trans
i18nKey="plus_x_additional_licenses_for_a_total_of_y_licenses"
values={{
count: subscription.recurly.totalLicenses,
additionalLicenses: subscription.recurly.additionalLicenses,
count: subscription.payment.totalLicenses,
additionalLicenses: subscription.payment.additionalLicenses,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
@@ -194,7 +194,7 @@ export function ActiveSubscriptionNew({
) : (
<Trans
i18nKey="supports_up_to_x_licenses"
values={{ count: subscription.recurly.totalLicenses }}
values={{ count: subscription.payment.totalLicenses }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
components={[<strong />]} // eslint-disable-line react/jsx-key
@@ -209,7 +209,7 @@ export function ActiveSubscriptionNew({
i18nKey="your_subscription_will_pause_on"
values={{
planName: subscription.plan.name,
pauseDate: subscription.recurly.nextPaymentDueAt,
pauseDate: subscription.payment.nextPaymentDueAt,
reactivationDate: getFormattedRenewalDate(),
}}
shouldUnescape
@@ -227,10 +227,10 @@ export function ActiveSubscriptionNew({
<p className="mb-1">
{subscription.plan.annual
? t('x_price_per_year', {
price: subscription.recurly.planOnlyDisplayPrice,
price: subscription.payment.planOnlyDisplayPrice,
})
: t('x_price_per_month', {
price: subscription.recurly.planOnlyDisplayPrice,
price: subscription.payment.planOnlyDisplayPrice,
})}
</p>
)}
@@ -261,7 +261,7 @@ export function ActiveSubscriptionNew({
}
type PlanActionsProps = {
subscription: RecurlySubscription
subscription: PaidSubscription
onStandalonePlan: boolean
handlePlanChange: () => void
hasPendingPause: boolean
@@ -301,7 +301,7 @@ function PlanActions({
<FlexibleGroupLicensingActions subscription={subscription} />
) : (
<>
{!hasPendingPause && !subscription.recurly.hasPastDueInvoice && (
{!hasPendingPause && !subscription.payment.hasPastDueInvoice && (
<OLButton variant="secondary" onClick={handlePlanChange}>
{t('change_plan')}
</OLButton>
@@ -334,7 +334,7 @@ function PlanActions({
function FlexibleGroupLicensingActions({
subscription,
}: {
subscription: RecurlySubscription
subscription: PaidSubscription
}) {
const { t } = useTranslation()

View File

@@ -1,7 +1,7 @@
import { useTranslation, Trans } from 'react-i18next'
import { PriceExceptions } from '../../../shared/price-exceptions'
import { useSubscriptionDashboardContext } from '../../../../context/subscription-dashboard-context'
import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { CancelSubscriptionButton } from './cancel-subscription-button'
import { CancelSubscription } from './cancel-plan/cancel-subscription'
import { PendingPlanChange } from './pending-plan-change'
@@ -27,7 +27,7 @@ import LoadingSpinner from '@/shared/components/loading-spinner'
export function ActiveSubscription({
subscription,
}: {
subscription: RecurlySubscription
subscription: PaidSubscription
}) {
const { t } = useTranslation()
const {
@@ -46,9 +46,9 @@ export function ActiveSubscription({
if (showCancellation) return <CancelSubscription />
const hasPendingPause =
subscription.recurly.state === 'active' &&
subscription.recurly.remainingPauseCycles &&
subscription.recurly.remainingPauseCycles > 0
subscription.payment.state === 'active' &&
subscription.payment.remainingPauseCycles &&
subscription.payment.remainingPauseCycles > 0
const handleCancelPendingPauseClick = async () => {
try {
@@ -96,19 +96,19 @@ export function ActiveSubscription({
</>
)}
{!subscription.pendingPlan &&
subscription.recurly.additionalLicenses > 0 && (
subscription.payment.additionalLicenses > 0 && (
<>
{' '}
<PendingAdditionalLicenses
additionalLicenses={subscription.recurly.additionalLicenses}
totalLicenses={subscription.recurly.totalLicenses}
additionalLicenses={subscription.payment.additionalLicenses}
totalLicenses={subscription.payment.totalLicenses}
/>
</>
)}
{!recurlyLoadError &&
!subscription.groupPlan &&
!hasPendingPause &&
!subscription.recurly.hasPastDueInvoice && (
!subscription.payment.hasPastDueInvoice && (
<>
{' '}
<OLButton
@@ -128,10 +128,10 @@ export function ActiveSubscription({
{(!subscription.pendingPlan ||
subscription.pendingPlan.name === subscription.plan.name) &&
subscription.plan.groupPlan && <ContactSupportToChangeGroupPlan />}
{isInFreeTrial(subscription.recurly.trialEndsAt) &&
subscription.recurly.trialEndsAtFormatted && (
{isInFreeTrial(subscription.payment.trialEndsAt) &&
subscription.payment.trialEndsAtFormatted && (
<TrialEnding
trialEndsAtFormatted={subscription.recurly.trialEndsAtFormatted}
trialEndsAtFormatted={subscription.payment.trialEndsAtFormatted}
/>
)}
@@ -142,7 +142,7 @@ export function ActiveSubscription({
i18nKey="your_subscription_will_pause_on"
values={{
planName: subscription.plan.name,
pauseDate: subscription.recurly.nextPaymentDueAt,
pauseDate: subscription.payment.nextPaymentDueAt,
reactivationDate: getFormattedRenewalDate(),
}}
shouldUnescape
@@ -174,7 +174,7 @@ export function ActiveSubscription({
<Trans
i18nKey="next_payment_of_x_collectected_on_y"
values={{
paymentAmmount: subscription.recurly.displayPrice,
paymentAmmount: subscription.payment.displayPrice,
collectionDate: getFormattedRenewalDate(),
}}
shouldUnescape
@@ -192,7 +192,7 @@ export function ActiveSubscription({
<PriceExceptions subscription={subscription} />
<p className="d-inline-flex flex-wrap gap-1">
<a
href={subscription.recurly.billingDetailsLink}
href={subscription.payment.billingDetailsLink}
target="_blank"
rel="noreferrer noopener"
className="btn btn-secondary-info btn-secondary"
@@ -200,7 +200,7 @@ export function ActiveSubscription({
{t('update_your_billing_details')}
</a>{' '}
<a
href={subscription.recurly.accountManagementLink}
href={subscription.payment.accountManagementLink}
target="_blank"
rel="noreferrer noopener"
className="btn btn-secondary-info btn-secondary"

View File

@@ -9,11 +9,11 @@ import {
AI_STANDALONE_PLAN_CODE,
} from '@/features/subscription/data/add-on-codes'
import sparkle from '@/shared/svgs/sparkle.svg'
import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { LICENSE_ADD_ON } from '@/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details'
type AddOnsProps = {
subscription: RecurlySubscription
subscription: PaidSubscription
onStandalonePlan: boolean
handleCancelClick: (code: string) => void
}
@@ -106,9 +106,9 @@ function AddOns({
const { t } = useTranslation()
const addOnsDisplayPrices = onStandalonePlan
? {
[AI_STANDALONE_PLAN_CODE]: subscription.recurly.displayPrice,
[AI_STANDALONE_PLAN_CODE]: subscription.payment.displayPrice,
}
: subscription.recurly.addOnDisplayPricesWithoutAdditionalLicense
: subscription.payment.addOnDisplayPricesWithoutAdditionalLicense
const addOnsToDisplay = onStandalonePlan
? [{ addOnCode: AI_STANDALONE_PLAN_CODE }]
: subscription.addOns?.filter(addOn => addOn.addOnCode !== LICENSE_ADD_ON)
@@ -130,7 +130,7 @@ function AddOns({
)
}
displayPrice={addOnsDisplayPrices[addOn.addOnCode]}
nextBillingDate={subscription.recurly.nextPaymentDueDate}
nextBillingDate={subscription.payment.nextPaymentDueDate}
/>
))
) : (

View File

@@ -155,14 +155,14 @@ export function CancelSubscription() {
isSuccessSecondaryAction ||
isSuccessCancel
if (!personalSubscription || !('recurly' in personalSubscription)) return null
if (!personalSubscription || !('payment' in personalSubscription)) return null
const showDowngrade = showDowngradeOption(
personalSubscription.plan.planCode,
personalSubscription.plan.groupPlan,
personalSubscription.recurly.trialEndsAt,
personalSubscription.recurly.pausedAt,
personalSubscription.recurly.remainingPauseCycles
personalSubscription.payment.trialEndsAt,
personalSubscription.payment.pausedAt,
personalSubscription.payment.remainingPauseCycles
)
const planToDowngradeTo = plans.find(
plan => plan.planCode === planCodeToDowngradeTo

View File

@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import * as eventTracking from '../../../../../../infrastructure/event-tracking'
import { useSubscriptionDashboardContext } from '../../../../context/subscription-dashboard-context'
import OLButton from '@/features/ui/components/ol/ol-button'
import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { useFeatureFlag } from '@/shared/context/split-test-context'
export function CancelSubscriptionButton() {
@@ -14,16 +14,16 @@ export function CancelSubscriptionButton() {
setShowCancellation,
} = useSubscriptionDashboardContext()
const subscription = personalSubscription as RecurlySubscription
const subscription = personalSubscription as PaidSubscription
const isInTrial =
subscription?.recurly.trialEndsAtFormatted &&
subscription?.recurly.trialEndsAt &&
new Date(subscription.recurly.trialEndsAt).getTime() > Date.now()
subscription?.payment.trialEndsAtFormatted &&
subscription?.payment.trialEndsAt &&
new Date(subscription.payment.trialEndsAt).getTime() > Date.now()
const hasPendingOrActivePause =
subscription.recurly.state === 'paused' ||
(subscription.recurly.state === 'active' &&
subscription.recurly.remainingPauseCycles &&
subscription.recurly.remainingPauseCycles > 0)
subscription.payment.state === 'paused' ||
(subscription.payment.state === 'active' &&
subscription.payment.remainingPauseCycles &&
subscription.payment.remainingPauseCycles > 0)
const planIsEligibleForPause =
!subscription.pendingPlan &&
!subscription.groupPlan &&

View File

@@ -3,17 +3,17 @@ import { useSubscriptionDashboardContext } from '../../../../../context/subscrip
import OLButton from '@/features/ui/components/ol/ol-button'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import isInFreeTrial from '../../../../../util/is-in-free-trial'
import { RecurlySubscription } from '../../../../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../../../../types/subscription/dashboard/subscription'
export function ChangeToGroupPlan() {
const { t } = useTranslation()
const { handleOpenModal, personalSubscription } =
useSubscriptionDashboardContext()
// TODO: Better way to get RecurlySubscription/trialEndsAt
// TODO: Better way to get PaidSubscription/trialEndsAt
const subscription =
personalSubscription && 'recurly' in personalSubscription
? (personalSubscription as RecurlySubscription)
personalSubscription && 'payment' in personalSubscription
? (personalSubscription as PaidSubscription)
: null
const handleClick = () => {
@@ -25,7 +25,7 @@ export function ChangeToGroupPlan() {
<h2 style={{ marginTop: 0 }}>{t('looking_multiple_licenses')}</h2>
<p style={{ margin: 0 }}>{t('reduce_costs_group_licenses')}</p>
<br />
{isInFreeTrial(subscription?.recurly?.trialEndsAt) ? (
{isInFreeTrial(subscription?.payment?.trialEndsAt) ? (
<>
<OLTooltip
id="disabled-change-to-group-plan"

View File

@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'
import { useTranslation, Trans } from 'react-i18next'
import { RecurlySubscription } from '../../../../../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../../../../../types/subscription/dashboard/subscription'
import { PriceForDisplayData } from '../../../../../../../../../../types/subscription/plan'
import { postJSON } from '../../../../../../../../infrastructure/fetch-json'
import getMeta from '../../../../../../../../utils/meta'
@@ -115,7 +115,7 @@ export function ChangeToGroupModal() {
useContactUsModal({ autofillProjectUrl: false })
const groupPlans = getMeta('ol-groupPlans')
const showGroupDiscount = getMeta('ol-showGroupDiscount')
const personalSubscription = getMeta('ol-subscription') as RecurlySubscription
const personalSubscription = getMeta('ol-subscription') as PaidSubscription
const [error, setError] = useState(false)
const [inflight, setInflight] = useState(false)
const location = useLocation()

View File

@@ -12,7 +12,7 @@ import OLButton from '@/features/ui/components/ol/ol-button'
import { postJSON } from '@/infrastructure/fetch-json'
import { useLocation } from '@/shared/hooks/use-location'
import OLNotification from '@/features/ui/components/ol/ol-notification'
import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
export function ConfirmUnpauseSubscriptionModal() {
const modalId: SubscriptionDashModalIds = 'unpause-subscription'
@@ -22,7 +22,7 @@ export function ConfirmUnpauseSubscriptionModal() {
const { handleCloseModal, modalIdShown, personalSubscription } =
useSubscriptionDashboardContext()
const location = useLocation()
const subscription = personalSubscription as RecurlySubscription
const subscription = personalSubscription as PaidSubscription
async function handleConfirmUnpause() {
setError(false)
@@ -70,7 +70,7 @@ export function ConfirmUnpauseSubscriptionModal() {
<Trans
i18nKey="lets_get_those_premium_features"
values={{
paymentAmount: subscription.recurly.displayPrice,
paymentAmount: subscription.payment.displayPrice,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}

View File

@@ -1,7 +1,7 @@
import { useSubscriptionDashboardContext } from '@/features/subscription/context/subscription-dashboard-context'
import Notification from '@/shared/components/notification'
import { Trans, useTranslation } from 'react-i18next'
import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { useEffect, useState } from 'react'
import { useLocation } from '@/shared/hooks/use-location'
@@ -17,7 +17,7 @@ export function FlashMessage() {
'flash'
) as FlashMessageName
)
const subscription = personalSubscription as RecurlySubscription
const subscription = personalSubscription as PaidSubscription
useEffect(() => {
// clear any flash message IDs so they only show once
if (location.toString()) {
@@ -36,7 +36,7 @@ export function FlashMessage() {
<Trans
i18nKey="your_subscription_will_pause_on_short"
values={{
pauseDate: subscription.recurly.nextPaymentDueAt,
pauseDate: subscription.payment.nextPaymentDueAt,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}

View File

@@ -1,6 +1,6 @@
import { useTranslation, Trans } from 'react-i18next'
import { useSubscriptionDashboardContext } from '../../../../context/subscription-dashboard-context'
import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { CancelSubscriptionButton } from './cancel-subscription-button'
import { CancelSubscription } from './cancel-plan/cancel-subscription'
import { ChangePlanModal } from './change-plan/modals/change-plan-modal'
@@ -14,7 +14,7 @@ import { ConfirmUnpauseSubscriptionModal } from './confirm-unpause-modal'
export function PausedSubscription({
subscription,
}: {
subscription: RecurlySubscription
subscription: PaidSubscription
}) {
const { t } = useTranslation()
const {
@@ -67,7 +67,7 @@ export function PausedSubscription({
<p className="d-inline-flex flex-wrap gap-1">
<a
href={subscription.recurly.billingDetailsLink}
href={subscription.payment.billingDetailsLink}
target="_blank"
rel="noreferrer noopener"
className="btn btn-secondary-info btn-secondary"
@@ -75,7 +75,7 @@ export function PausedSubscription({
{t('update_your_billing_details')}
</a>{' '}
<a
href={subscription.recurly.accountManagementLink}
href={subscription.payment.accountManagementLink}
target="_blank"
rel="noreferrer noopener"
className="btn btn-secondary-info btn-secondary"

View File

@@ -1,16 +1,16 @@
import { Trans } from 'react-i18next'
import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { PendingRecurlyPlan } from '../../../../../../../../types/subscription/plan'
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { PendingPaymentProviderPlan } from '../../../../../../../../types/subscription/plan'
import { AI_ADD_ON_CODE, ADD_ON_NAME } from '../../../../data/add-on-codes'
export function PendingPlanChange({
subscription,
}: {
subscription: RecurlySubscription
subscription: PaidSubscription
}) {
if (!subscription.pendingPlan) return null
const pendingPlan = subscription.pendingPlan as PendingRecurlyPlan
const pendingPlan = subscription.pendingPlan as PendingPaymentProviderPlan
const hasAiAddon = subscription.addOns?.some(
addOn => addOn.addOnCode === AI_ADD_ON_CODE
@@ -21,9 +21,9 @@ export function PendingPlanChange({
!pendingPlan.addOns?.some(addOn => addOn.code === AI_ADD_ON_CODE)
const pendingAdditionalLicenses =
(subscription.recurly.pendingAdditionalLicenses &&
subscription.recurly.pendingAdditionalLicenses > 0) ||
subscription.recurly.additionalLicenses > 0
(subscription.payment.pendingAdditionalLicenses &&
subscription.payment.pendingAdditionalLicenses > 0) ||
subscription.payment.additionalLicenses > 0
return (
<>
@@ -49,8 +49,8 @@ export function PendingPlanChange({
i18nKey="pending_additional_licenses"
values={{
pendingAdditionalLicenses:
subscription.recurly.pendingAdditionalLicenses,
pendingTotalLicenses: subscription.recurly.pendingTotalLicenses,
subscription.payment.pendingAdditionalLicenses,
pendingTotalLicenses: subscription.payment.pendingTotalLicenses,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}

View File

@@ -1,8 +1,8 @@
import { Trans } from 'react-i18next'
import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
type SubscriptionRemainderProps = {
subscription: RecurlySubscription
subscription: PaidSubscription
hideTime?: boolean
}
@@ -11,13 +11,13 @@ function SubscriptionRemainder({
hideTime,
}: SubscriptionRemainderProps) {
const stillInATrial =
subscription.recurly.trialEndsAtFormatted &&
subscription.recurly.trialEndsAt &&
new Date(subscription.recurly.trialEndsAt).getTime() > Date.now()
subscription.payment.trialEndsAtFormatted &&
subscription.payment.trialEndsAt &&
new Date(subscription.payment.trialEndsAt).getTime() > Date.now()
const terminationDate = hideTime
? subscription.recurly.nextPaymentDueDate
: subscription.recurly.nextPaymentDueAt
? subscription.payment.nextPaymentDueDate
: subscription.payment.nextPaymentDueAt
return stillInATrial ? (
<Trans
i18nKey="subscription_will_remain_active_until_end_of_trial_period_x"

View File

@@ -1,12 +1,12 @@
import { useTranslation, Trans } from 'react-i18next'
import { RecurlySubscription } from '../../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../../types/subscription/dashboard/subscription'
import ReactivateSubscription from '../reactivate-subscription'
import OLButton from '@/features/ui/components/ol/ol-button'
export function CanceledSubscription({
subscription,
}: {
subscription: RecurlySubscription
subscription: PaidSubscription
}) {
const { t } = useTranslation()
@@ -30,7 +30,7 @@ export function CanceledSubscription({
<Trans
i18nKey="subscription_canceled_and_terminate_on_x"
values={{
terminateDate: subscription.recurly.nextPaymentDueAt,
terminateDate: subscription.payment.nextPaymentDueAt,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
@@ -42,7 +42,7 @@ export function CanceledSubscription({
</p>
<p>
<OLButton
href={subscription.recurly.accountManagementLink}
href={subscription.payment.accountManagementLink}
target="_blank"
variant="secondary"
rel="noopener noreferrer"

View File

@@ -1,11 +1,11 @@
import { useTranslation } from 'react-i18next'
import { RecurlySubscription } from '../../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../../types/subscription/dashboard/subscription'
import OLButton from '@/features/ui/components/ol/ol-button'
export function ExpiredSubscription({
subscription,
}: {
subscription: RecurlySubscription
subscription: PaidSubscription
}) {
const { t } = useTranslation()
@@ -14,7 +14,7 @@ export function ExpiredSubscription({
<p>{t('your_subscription_has_expired')}</p>
<p>
<OLButton
href={subscription.recurly.accountManagementLink}
href={subscription.payment.accountManagementLink}
target="_blank"
rel="noreferrer noopener"
variant="secondary"

View File

@@ -1,13 +1,13 @@
import { useTranslation } from 'react-i18next'
import { RecurlySubscription } from '../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../types/subscription/dashboard/subscription'
type PriceExceptionsProps = {
subscription: RecurlySubscription
subscription: PaidSubscription
}
export function PriceExceptions({ subscription }: PriceExceptionsProps) {
const { t } = useTranslation()
const { activeCoupons } = subscription.recurly
const { activeCoupons } = subscription.payment
return (
<>

View File

@@ -12,7 +12,7 @@ import {
ADD_ON_NAME,
isStandaloneAiPlanCode,
} from '../../data/add-on-codes'
import { RecurlySubscription } from '../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../types/subscription/dashboard/subscription'
function SuccessfulSubscription() {
const { t } = useTranslation()
@@ -21,7 +21,7 @@ function SuccessfulSubscription() {
const postCheckoutRedirect = getMeta('ol-postCheckoutRedirect')
const { appName, adminEmail } = getMeta('ol-ExposedSettings')
if (!subscription || !('recurly' in subscription)) return null
if (!subscription || !('payment' in subscription)) return null
const onAiStandalonePlan = isStandaloneAiPlanCode(subscription.planCode)
@@ -37,15 +37,15 @@ function SuccessfulSubscription() {
type="success"
content={
<>
{subscription.recurly.trialEndsAt && (
{subscription.payment.trialEndsAt && (
<>
<p>
<Trans
i18nKey="next_payment_of_x_collectected_on_y"
values={{
paymentAmmount: subscription.recurly.displayPrice,
paymentAmmount: subscription.payment.displayPrice,
collectionDate:
subscription.recurly.nextPaymentDueAt,
subscription.payment.nextPaymentDueAt,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
@@ -126,7 +126,7 @@ function ThankYouSection({
subscription,
onAiStandalonePlan,
}: {
subscription: RecurlySubscription
subscription: PaidSubscription
onAiStandalonePlan: boolean
}) {
const { t } = useTranslation()

View File

@@ -12,7 +12,7 @@ import {
CustomSubscription,
ManagedGroupSubscription,
MemberGroupSubscription,
RecurlySubscription,
PaidSubscription,
} from '../../../../../types/subscription/dashboard/subscription'
import {
Plan,
@@ -52,7 +52,7 @@ type SubscriptionDashboardContextValue = {
managedPublishers: Publisher[]
updateManagedInstitution: (institution: ManagedInstitution) => void
modalIdShown?: SubscriptionDashModalIds
personalSubscription?: RecurlySubscription | CustomSubscription
personalSubscription?: PaidSubscription | CustomSubscription
hasSubscription: boolean
plans: Plan[]
planCodeToChangeTo?: string
@@ -136,21 +136,21 @@ export function SubscriptionDashboardProvider({
)
const hasValidActiveSubscription = Boolean(
['active', 'canceled'].includes(personalSubscription?.recurly?.state) ||
['active', 'canceled'].includes(personalSubscription?.payment?.state) ||
institutionMemberships?.length > 0 ||
memberGroupSubscriptions?.length > 0
)
const getFormattedRenewalDate = useCallback(() => {
if (
!personalSubscription.recurly.pausedAt ||
!personalSubscription.recurly.remainingPauseCycles
!personalSubscription.payment.pausedAt ||
!personalSubscription.payment.remainingPauseCycles
) {
return personalSubscription.recurly.nextPaymentDueAt
return personalSubscription.payment.nextPaymentDueAt
}
const pausedDate = new Date(personalSubscription.recurly.pausedAt)
const pausedDate = new Date(personalSubscription.payment.pausedAt)
pausedDate.setMonth(
pausedDate.getMonth() + personalSubscription.recurly.remainingPauseCycles
pausedDate.getMonth() + personalSubscription.payment.remainingPauseCycles
)
return formatTime(pausedDate, 'MMMM Do, YYYY')
}, [personalSubscription])
@@ -167,9 +167,9 @@ export function SubscriptionDashboardProvider({
if (
isRecurlyLoaded() &&
plansWithoutDisplayPrice &&
personalSubscription?.recurly
personalSubscription?.payment
) {
const { currency, taxRate } = personalSubscription.recurly
const { currency, taxRate } = personalSubscription.payment
const fetchPlansDisplayPrices = async () => {
for (const plan of plansWithoutDisplayPrice) {
try {
@@ -203,11 +203,11 @@ export function SubscriptionDashboardProvider({
groupPlanToChangeToCode &&
groupPlanToChangeToSize &&
groupPlanToChangeToUsage &&
personalSubscription?.recurly
personalSubscription?.payment
) {
setQueryingGroupPlanToChangeToPrice(true)
const { currency, taxRate } = personalSubscription.recurly
const { currency, taxRate } = personalSubscription.payment
const fetchGroupDisplayPrice = async () => {
setGroupPlanToChangeToPriceError(false)
let priceData

View File

@@ -83,7 +83,7 @@ describe('<PersonalSubscription />', function () {
'Your subscription has been canceled and will terminate on',
{ exact: false }
)
screen.getByText(canceledSubscription.recurly!.nextPaymentDueAt, {
screen.getByText(canceledSubscription.payment!.nextPaymentDueAt, {
exact: false,
})
@@ -134,7 +134,7 @@ describe('<PersonalSubscription />', function () {
{},
JSON.parse(JSON.stringify(annualActiveSubscription))
)
withStateDeleted.recurly.state = undefined
withStateDeleted.payment.state = undefined
renderWithSubscriptionDashContext(<PersonalSubscription />, {
metaTags: [{ name: 'ol-subscription', value: withStateDeleted }],
})
@@ -194,7 +194,7 @@ describe('<PersonalSubscription />', function () {
})
})
it('shows different recurly email address section', async function () {
it('shows different payment email address section', async function () {
fetchMock.post('/user/subscription/account/email', 200)
const usersEmail = 'foo@example.com'
renderWithSubscriptionDashContext(<PersonalSubscription />, {
@@ -208,7 +208,7 @@ describe('<PersonalSubscription />', function () {
/your billing email address is currently/i
).textContent
expect(billingText).to.contain(
`Your billing email address is currently ${annualActiveSubscription.recurly.accountEmail}.` +
`Your billing email address is currently ${annualActiveSubscription.payment.accountEmail}.` +
` If needed you can update your billing address to ${usersEmail}`
)

View File

@@ -1,7 +1,7 @@
import { expect } from 'chai'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import * as eventTracking from '@/infrastructure/event-tracking'
import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import {
annualActiveSubscription,
groupActiveSubscription,
@@ -36,7 +36,7 @@ describe('<ActiveSubscription />', function () {
sendMBSpy.restore()
})
function expectedInActiveSubscription(subscription: RecurlySubscription) {
function expectedInActiveSubscription(subscription: PaidSubscription) {
// sentence broken up by bolding
screen.getByText('You are currently subscribed to the', { exact: false })
screen.getByText(subscription.plan.name, { exact: false })
@@ -45,11 +45,11 @@ describe('<ActiveSubscription />', function () {
// sentence broken up by bolding
screen.getByText('The next payment of', { exact: false })
screen.getByText(subscription.recurly.displayPrice, {
screen.getByText(subscription.payment.displayPrice, {
exact: false,
})
screen.getByText('will be collected on', { exact: false })
const dates = screen.getAllByText(subscription.recurly.nextPaymentDueAt, {
const dates = screen.getAllByText(subscription.payment.nextPaymentDueAt, {
exact: false,
})
expect(dates.length).to.equal(2)
@@ -110,7 +110,7 @@ describe('<ActiveSubscription />', function () {
JSON.parse(JSON.stringify(annualActiveSubscription))
)
activePastDueSubscription.recurly.hasPastDueInvoice = true
activePastDueSubscription.payment.hasPastDueInvoice = true
renderActiveSubscription(activePastDueSubscription)
@@ -126,14 +126,14 @@ describe('<ActiveSubscription />', function () {
})
screen.getByText(
groupActiveSubscriptionWithPendingLicenseChange.recurly
groupActiveSubscriptionWithPendingLicenseChange.payment
.pendingAdditionalLicenses!
)
screen.getByText('additional license(s) for a total of', { exact: false })
screen.getByText(
groupActiveSubscriptionWithPendingLicenseChange.recurly
groupActiveSubscriptionWithPendingLicenseChange.payment
.pendingTotalLicenses!
)
@@ -146,10 +146,10 @@ describe('<ActiveSubscription />', function () {
it('shows the pending license change message when plan change is not pending', function () {
const subscription = Object.assign({}, groupActiveSubscription)
subscription.recurly.additionalLicenses = 4
subscription.recurly.totalLicenses =
subscription.recurly.totalLicenses +
subscription.recurly.additionalLicenses
subscription.payment.additionalLicenses = 4
subscription.payment.totalLicenses =
subscription.payment.totalLicenses +
subscription.payment.additionalLicenses
renderActiveSubscription(subscription)
@@ -157,11 +157,11 @@ describe('<ActiveSubscription />', function () {
exact: false,
})
screen.getByText(subscription.recurly.additionalLicenses)
screen.getByText(subscription.payment.additionalLicenses)
screen.getByText('additional license(s) for a total of', { exact: false })
screen.getByText(subscription.recurly.totalLicenses)
screen.getByText(subscription.payment.totalLicenses)
})
it('shows when trial ends and first payment collected and when subscription would become inactive if cancelled', function () {
@@ -169,14 +169,14 @@ describe('<ActiveSubscription />', function () {
screen.getByText('Youre on a free trial which ends on', { exact: false })
const endDate = screen.getAllByText(
trialSubscription.recurly.trialEndsAtFormatted!
trialSubscription.payment.trialEndsAtFormatted!
)
expect(endDate.length).to.equal(3)
})
it('shows current discounts', function () {
const subscriptionWithActiveCoupons = cloneDeep(annualActiveSubscription)
subscriptionWithActiveCoupons.recurly.activeCoupons = [
subscriptionWithActiveCoupons.payment.activeCoupons = [
{
name: 'fake coupon name',
code: 'fake-coupon',
@@ -188,7 +188,7 @@ describe('<ActiveSubscription />', function () {
/this does not include your current discounts, which will be applied automatically before your next payment/i
)
screen.getByText(
subscriptionWithActiveCoupons.recurly.activeCoupons[0].name
subscriptionWithActiveCoupons.payment.activeCoupons[0].name
)
})
@@ -225,7 +225,7 @@ describe('<ActiveSubscription />', function () {
{ exact: false }
)
const dates = screen.getAllByText(
annualActiveSubscription.recurly.nextPaymentDueAt,
annualActiveSubscription.payment.nextPaymentDueAt,
{
exact: false,
}
@@ -244,7 +244,7 @@ describe('<ActiveSubscription />', function () {
{ exact: false }
)
const dates = screen.getAllByText(
trialSubscription.recurly.trialEndsAtFormatted!
trialSubscription.payment.trialEndsAtFormatted!
)
expect(dates.length).to.equal(3)
const button = screen.getByRole('button', {

View File

@@ -1,7 +1,7 @@
import {
CustomSubscription,
GroupSubscription,
RecurlySubscription,
PaidSubscription,
} from '../../../../../types/subscription/dashboard/subscription'
import dateformat from 'dateformat'
@@ -15,7 +15,7 @@ const sevenDaysFromTodayFormatted = dateformat(
'dS mmmm yyyy'
)
export const annualActiveSubscription: RecurlySubscription = {
export const annualActiveSubscription: PaidSubscription = {
manager_ids: ['abc123'],
member_ids: [],
invited_emails: [],
@@ -33,7 +33,7 @@ export const annualActiveSubscription: RecurlySubscription = {
annual: true,
featureDescription: [],
},
recurly: {
payment: {
taxRate: 0,
billingDetailsLink: '/user/subscription/recurly/billing-details',
accountManagementLink: '/user/subscription/recurly/account-management',
@@ -55,7 +55,7 @@ export const annualActiveSubscription: RecurlySubscription = {
},
}
export const annualActiveSubscriptionEuro: RecurlySubscription = {
export const annualActiveSubscriptionEuro: PaidSubscription = {
manager_ids: ['abc123'],
member_ids: [],
invited_emails: [],
@@ -73,7 +73,7 @@ export const annualActiveSubscriptionEuro: RecurlySubscription = {
annual: true,
featureDescription: [],
},
recurly: {
payment: {
taxRate: 0.24,
billingDetailsLink: '/user/subscription/recurly/billing-details',
accountManagementLink: '/user/subscription/recurly/account-management',
@@ -95,7 +95,7 @@ export const annualActiveSubscriptionEuro: RecurlySubscription = {
},
}
export const annualActiveSubscriptionPro: RecurlySubscription = {
export const annualActiveSubscriptionPro: PaidSubscription = {
manager_ids: ['abc123'],
member_ids: [],
invited_emails: [],
@@ -112,7 +112,7 @@ export const annualActiveSubscriptionPro: RecurlySubscription = {
price_in_cents: 4500,
featureDescription: [],
},
recurly: {
payment: {
taxRate: 0,
billingDetailsLink: '/user/subscription/recurly/billing-details',
accountManagementLink: '/user/subscription/recurly/account-management',
@@ -134,7 +134,7 @@ export const annualActiveSubscriptionPro: RecurlySubscription = {
},
}
export const pastDueExpiredSubscription: RecurlySubscription = {
export const pastDueExpiredSubscription: PaidSubscription = {
manager_ids: ['abc123'],
member_ids: [],
invited_emails: [],
@@ -152,7 +152,7 @@ export const pastDueExpiredSubscription: RecurlySubscription = {
annual: true,
featureDescription: [],
},
recurly: {
payment: {
taxRate: 0,
billingDetailsLink: '/user/subscription/recurly/billing-details',
accountManagementLink: '/user/subscription/recurly/account-management',
@@ -174,7 +174,7 @@ export const pastDueExpiredSubscription: RecurlySubscription = {
},
}
export const canceledSubscription: RecurlySubscription = {
export const canceledSubscription: PaidSubscription = {
manager_ids: ['abc123'],
member_ids: [],
invited_emails: [],
@@ -192,7 +192,7 @@ export const canceledSubscription: RecurlySubscription = {
annual: true,
featureDescription: [],
},
recurly: {
payment: {
taxRate: 0,
billingDetailsLink: '/user/subscription/recurly/billing-details',
accountManagementLink: '/user/subscription/recurly/account-management',
@@ -214,7 +214,7 @@ export const canceledSubscription: RecurlySubscription = {
},
}
export const pendingSubscriptionChange: RecurlySubscription = {
export const pendingSubscriptionChange: PaidSubscription = {
manager_ids: ['abc123'],
member_ids: [],
invited_emails: [],
@@ -232,7 +232,7 @@ export const pendingSubscriptionChange: RecurlySubscription = {
annual: true,
featureDescription: [],
},
recurly: {
payment: {
taxRate: 0,
billingDetailsLink: '/user/subscription/recurly/billing-details',
accountManagementLink: '/user/subscription/recurly/account-management',
@@ -283,7 +283,7 @@ export const groupActiveSubscription: GroupSubscription = {
membersLimit: 10,
membersLimitAddOn: 'additional-license',
},
recurly: {
payment: {
taxRate: 0,
billingDetailsLink: '/user/subscription/recurly/billing-details',
accountManagementLink: '/user/subscription/recurly/account-management',
@@ -328,7 +328,7 @@ export const groupActiveSubscriptionWithPendingLicenseChange: GroupSubscription
membersLimit: 10,
membersLimitAddOn: 'additional-license',
},
recurly: {
payment: {
taxRate: 0,
billingDetailsLink: '/user/subscription/recurly/billing-details',
accountManagementLink: '/user/subscription/recurly/account-management',
@@ -362,7 +362,7 @@ export const groupActiveSubscriptionWithPendingLicenseChange: GroupSubscription
},
}
export const trialSubscription: RecurlySubscription = {
export const trialSubscription: PaidSubscription = {
manager_ids: ['abc123'],
member_ids: [],
invited_emails: [],
@@ -380,7 +380,7 @@ export const trialSubscription: RecurlySubscription = {
featureDescription: [],
hideFromUsers: true,
},
recurly: {
payment: {
taxRate: 0,
billingDetailsLink: '/user/subscription/recurly/billing-details',
accountManagementLink: '/user/subscription/recurly/account-management',
@@ -423,7 +423,7 @@ export const customSubscription: CustomSubscription = {
customAccount: true,
}
export const trialCollaboratorSubscription: RecurlySubscription = {
export const trialCollaboratorSubscription: PaidSubscription = {
manager_ids: ['abc123'],
member_ids: [],
invited_emails: [],
@@ -441,7 +441,7 @@ export const trialCollaboratorSubscription: RecurlySubscription = {
featureDescription: [],
hideFromUsers: true,
},
recurly: {
payment: {
taxRate: 0,
billingDetailsLink: '/user/subscription/recurly/billing-details',
accountManagementLink: '/user/subscription/recurly/account-management',
@@ -463,7 +463,7 @@ export const trialCollaboratorSubscription: RecurlySubscription = {
},
}
export const monthlyActiveCollaborator: RecurlySubscription = {
export const monthlyActiveCollaborator: PaidSubscription = {
manager_ids: ['abc123'],
member_ids: [],
invited_emails: [],
@@ -480,7 +480,7 @@ export const monthlyActiveCollaborator: RecurlySubscription = {
price_in_cents: 212300900,
featureDescription: [],
},
recurly: {
payment: {
taxRate: 0,
billingDetailsLink: '/user/subscription/recurly/billing-details',
accountManagementLink: '/user/subscription/recurly/account-management',

View File

@@ -1,12 +1,12 @@
import { ActiveSubscription } from '../../../../../frontend/js/features/subscription/components/dashboard/states/active/active'
import { RecurlySubscription } from '../../../../../types/subscription/dashboard/subscription'
import { PaidSubscription } from '../../../../../types/subscription/dashboard/subscription'
import { groupPlans, plans } from '../fixtures/plans'
import { renderWithSubscriptionDashContext } from './render-with-subscription-dash-context'
import { MetaTag } from '@/utils/meta'
import { CurrencyCode } from '../../../../../types/subscription/currency'
export function renderActiveSubscription(
subscription: RecurlySubscription,
subscription: PaidSubscription,
tags: MetaTag[] = [],
currencyCode?: CurrencyCode
) {

View File

@@ -4,7 +4,7 @@ const sinon = require('sinon')
const { ObjectId } = require('mongodb-legacy')
const {
AI_ADD_ON_CODE,
} = require('../../../../app/src/Features/Subscription/RecurlyEntities')
} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
const MODULE_PATH = '../../../../app/src/Features/Subscription/FeaturesUpdater'

View File

@@ -5,16 +5,17 @@ const { expect } = require('chai')
const Errors = require('../../../../app/src/Features/Subscription/Errors')
const {
AI_ADD_ON_CODE,
RecurlySubscriptionChangeRequest,
RecurlySubscriptionChange,
RecurlySubscription,
RecurlySubscriptionAddOnUpdate,
} = require('../../../../app/src/Features/Subscription/RecurlyEntities')
PaymentProviderSubscriptionChangeRequest,
PaymentProviderSubscriptionChange,
PaymentProviderSubscription,
PaymentProviderSubscriptionAddOnUpdate,
} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
const MODULE_PATH = '../../../../app/src/Features/Subscription/RecurlyEntities'
const MODULE_PATH =
'../../../../app/src/Features/Subscription/PaymentProviderEntities'
describe('RecurlyEntities', function () {
describe('RecurlySubscription', function () {
describe('PaymentProviderEntities', function () {
describe('PaymentProviderSubscription', function () {
beforeEach(function () {
this.Settings = {
plans: [
@@ -26,7 +27,7 @@ describe('RecurlyEntities', function () {
features: [],
}
this.RecurlyEntities = SandboxedModule.require(MODULE_PATH, {
this.PaymentProviderEntities = SandboxedModule.require(MODULE_PATH, {
requires: {
'@overleaf/settings': this.Settings,
'./Errors': Errors,
@@ -36,15 +37,17 @@ describe('RecurlyEntities', function () {
describe('with add-ons', function () {
beforeEach(function () {
const { RecurlySubscription, RecurlySubscriptionAddOn } =
this.RecurlyEntities
this.addOn = new RecurlySubscriptionAddOn({
const {
PaymentProviderSubscription,
PaymentProviderSubscriptionAddOn,
} = this.PaymentProviderEntities
this.addOn = new PaymentProviderSubscriptionAddOn({
code: 'add-on-code',
name: 'My Add-On',
quantity: 1,
unitPrice: 2,
})
this.subscription = new RecurlySubscription({
this.subscription = new PaymentProviderSubscription({
id: 'subscription-id',
userId: 'user-id',
planCode: 'regular-plan',
@@ -71,11 +74,12 @@ describe('RecurlyEntities', function () {
describe('getRequestForPlanChange()', function () {
it('returns a change request for upgrades', function () {
const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities
const { PaymentProviderSubscriptionChangeRequest } =
this.PaymentProviderEntities
const changeRequest =
this.subscription.getRequestForPlanChange('premium-plan')
expect(changeRequest).to.deep.equal(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
planCode: 'premium-plan',
@@ -84,11 +88,12 @@ describe('RecurlyEntities', function () {
})
it('returns a change request for downgrades', function () {
const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities
const { PaymentProviderSubscriptionChangeRequest } =
this.PaymentProviderEntities
const changeRequest =
this.subscription.getRequestForPlanChange('cheap-plan')
expect(changeRequest).to.deep.equal(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'term_end',
planCode: 'cheap-plan',
@@ -97,17 +102,18 @@ describe('RecurlyEntities', function () {
})
it('preserves the AI add-on on upgrades', function () {
const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities
const { PaymentProviderSubscriptionChangeRequest } =
this.PaymentProviderEntities
this.addOn.code = AI_ADD_ON_CODE
const changeRequest =
this.subscription.getRequestForPlanChange('premium-plan')
expect(changeRequest).to.deep.equal(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
planCode: 'premium-plan',
addOnUpdates: [
new RecurlySubscriptionAddOnUpdate({
new PaymentProviderSubscriptionAddOnUpdate({
code: AI_ADD_ON_CODE,
quantity: 1,
}),
@@ -117,17 +123,18 @@ describe('RecurlyEntities', function () {
})
it('preserves the AI add-on on downgrades', function () {
const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities
const { PaymentProviderSubscriptionChangeRequest } =
this.PaymentProviderEntities
this.addOn.code = AI_ADD_ON_CODE
const changeRequest =
this.subscription.getRequestForPlanChange('cheap-plan')
expect(changeRequest).to.deep.equal(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'term_end',
planCode: 'cheap-plan',
addOnUpdates: [
new RecurlySubscriptionAddOnUpdate({
new PaymentProviderSubscriptionAddOnUpdate({
code: AI_ADD_ON_CODE,
quantity: 1,
}),
@@ -137,18 +144,19 @@ describe('RecurlyEntities', function () {
})
it('preserves the AI add-on on upgrades from the standalone AI plan', function () {
const { RecurlySubscriptionChangeRequest } = this.RecurlyEntities
const { PaymentProviderSubscriptionChangeRequest } =
this.PaymentProviderEntities
this.subscription.planCode = 'assistant-annual'
this.subscription.addOns = []
const changeRequest =
this.subscription.getRequestForPlanChange('cheap-plan')
expect(changeRequest).to.deep.equal(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'term_end',
planCode: 'cheap-plan',
addOnUpdates: [
new RecurlySubscriptionAddOnUpdate({
new PaymentProviderSubscriptionAddOnUpdate({
code: AI_ADD_ON_CODE,
quantity: 1,
}),
@@ -161,22 +169,22 @@ describe('RecurlyEntities', function () {
describe('getRequestForAddOnPurchase()', function () {
it('returns a change request', function () {
const {
RecurlySubscriptionChangeRequest,
RecurlySubscriptionAddOnUpdate,
} = this.RecurlyEntities
PaymentProviderSubscriptionChangeRequest,
PaymentProviderSubscriptionAddOnUpdate,
} = this.PaymentProviderEntities
const changeRequest =
this.subscription.getRequestForAddOnPurchase('another-add-on')
expect(changeRequest).to.deep.equal(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
addOnUpdates: [
new RecurlySubscriptionAddOnUpdate({
new PaymentProviderSubscriptionAddOnUpdate({
code: this.addOn.code,
quantity: this.addOn.quantity,
unitPrice: this.addOn.unitPrice,
}),
new RecurlySubscriptionAddOnUpdate({
new PaymentProviderSubscriptionAddOnUpdate({
code: 'another-add-on',
quantity: 1,
}),
@@ -187,9 +195,9 @@ describe('RecurlyEntities', function () {
it('returns a change request with quantity and unit price specified', function () {
const {
RecurlySubscriptionChangeRequest,
RecurlySubscriptionAddOnUpdate,
} = this.RecurlyEntities
PaymentProviderSubscriptionChangeRequest,
PaymentProviderSubscriptionAddOnUpdate,
} = this.PaymentProviderEntities
const quantity = 5
const unitPrice = 10
const changeRequest = this.subscription.getRequestForAddOnPurchase(
@@ -198,16 +206,16 @@ describe('RecurlyEntities', function () {
unitPrice
)
expect(changeRequest).to.deep.equal(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
addOnUpdates: [
new RecurlySubscriptionAddOnUpdate({
new PaymentProviderSubscriptionAddOnUpdate({
code: this.addOn.code,
quantity: this.addOn.quantity,
unitPrice: this.addOn.unitPrice,
}),
new RecurlySubscriptionAddOnUpdate({
new PaymentProviderSubscriptionAddOnUpdate({
code: 'another-add-on',
quantity,
unitPrice,
@@ -227,20 +235,20 @@ describe('RecurlyEntities', function () {
describe('getRequestForAddOnUpdate()', function () {
it('returns a change request', function () {
const {
RecurlySubscriptionChangeRequest,
RecurlySubscriptionAddOnUpdate,
} = this.RecurlyEntities
PaymentProviderSubscriptionChangeRequest,
PaymentProviderSubscriptionAddOnUpdate,
} = this.PaymentProviderEntities
const newQuantity = 2
const changeRequest = this.subscription.getRequestForAddOnUpdate(
'add-on-code',
newQuantity
)
expect(changeRequest).to.deep.equal(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
addOnUpdates: [
new RecurlySubscriptionAddOnUpdate({
new PaymentProviderSubscriptionAddOnUpdate({
code: this.addOn.code,
quantity: newQuantity,
unitPrice: this.addOn.unitPrice,
@@ -263,7 +271,7 @@ describe('RecurlyEntities', function () {
this.addOn.code
)
expect(changeRequest).to.deep.equal(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'term_end',
addOnUpdates: [],
@@ -283,13 +291,13 @@ describe('RecurlyEntities', function () {
const changeRequest =
this.subscription.getRequestForGroupPlanUpgrade('test_plan_code')
const addOns = [
new RecurlySubscriptionAddOnUpdate({
new PaymentProviderSubscriptionAddOnUpdate({
code: 'add-on-code',
quantity: 1,
}),
]
expect(changeRequest).to.deep.equal(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
addOnUpdates: addOns,
@@ -301,8 +309,8 @@ describe('RecurlyEntities', function () {
describe('without add-ons', function () {
beforeEach(function () {
const { RecurlySubscription } = this.RecurlyEntities
this.subscription = new RecurlySubscription({
const { PaymentProviderSubscription } = this.PaymentProviderEntities
this.subscription = new PaymentProviderSubscription({
id: 'subscription-id',
userId: 'user-id',
planCode: 'regular-plan',
@@ -325,17 +333,17 @@ describe('RecurlyEntities', function () {
describe('getRequestForAddOnPurchase()', function () {
it('returns a change request', function () {
const {
RecurlySubscriptionChangeRequest,
RecurlySubscriptionAddOnUpdate,
} = this.RecurlyEntities
PaymentProviderSubscriptionChangeRequest,
PaymentProviderSubscriptionAddOnUpdate,
} = this.PaymentProviderEntities
const changeRequest =
this.subscription.getRequestForAddOnPurchase('some-add-on')
expect(changeRequest).to.deep.equal(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
addOnUpdates: [
new RecurlySubscriptionAddOnUpdate({
new PaymentProviderSubscriptionAddOnUpdate({
code: 'some-add-on',
quantity: 1,
}),
@@ -356,10 +364,10 @@ describe('RecurlyEntities', function () {
})
})
describe('RecurlySubscriptionChange', function () {
describe('PaymentProviderSubscriptionChange', function () {
describe('constructor', function () {
it('rounds the amounts when calculating the taxes', function () {
const subscription = new RecurlySubscription({
const subscription = new PaymentProviderSubscription({
id: 'subscription-id',
userId: 'user-id',
planCode: 'premium-plan',
@@ -374,7 +382,7 @@ describe('RecurlyEntities', function () {
periodEnd: new Date(),
collectionMethod: 'automatic',
})
const change = new RecurlySubscriptionChange({
const change = new PaymentProviderSubscriptionChange({
subscription,
nextPlanCode: 'promotional-plan',
nextPlanName: 'Promotial plan',

View File

@@ -2,10 +2,10 @@ const sinon = require('sinon')
const { expect } = require('chai')
const SandboxedModule = require('sandboxed-module')
const {
RecurlySubscription,
RecurlyAccount,
RecurlyCoupon,
} = require('../../../../app/src/Features/Subscription/RecurlyEntities')
PaymentProviderSubscription,
PaymentProviderAccount,
PaymentProviderCoupon,
} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
const MODULE_PATH = '../../../../app/src/Features/Subscription/PaymentService'
@@ -14,7 +14,7 @@ describe('PaymentService', function () {
this.user = {
_id: '123456',
}
this.recurlySubscription = new RecurlySubscription({
this.recurlySubscription = new PaymentProviderSubscription({
id: 'subscription-id',
userId: this.user._id,
currency: 'EUR',
@@ -30,13 +30,13 @@ describe('PaymentService', function () {
periodEnd: new Date(),
collectionMethod: 'automatic',
})
this.recurlyAccount = new RecurlyAccount({
this.recurlyAccount = new PaymentProviderAccount({
code: this.user._id,
email: 'example@example.com',
hasPastDueInvoice: true,
})
this.recurlyCoupons = [
new RecurlyCoupon({
new PaymentProviderCoupon({
code: 'coupon-code',
name: 'coupon name',
description: 'coupon description',

View File

@@ -3,12 +3,12 @@ const { expect } = require('chai')
const recurly = require('recurly')
const SandboxedModule = require('sandboxed-module')
const {
RecurlySubscription,
RecurlySubscriptionChangeRequest,
RecurlySubscriptionAddOnUpdate,
RecurlyAccount,
RecurlyCoupon,
} = require('../../../../app/src/Features/Subscription/RecurlyEntities')
PaymentProviderSubscription,
PaymentProviderSubscriptionChangeRequest,
PaymentProviderSubscriptionAddOnUpdate,
PaymentProviderAccount,
PaymentProviderCoupon,
} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
const MODULE_PATH = '../../../../app/src/Features/Subscription/RecurlyClient'
@@ -41,7 +41,7 @@ describe('RecurlyClient', function () {
preTaxTotal: 2,
}
this.subscription = new RecurlySubscription({
this.subscription = new PaymentProviderSubscription({
id: 'subscription-id',
userId: 'user-id',
currency: 'EUR',
@@ -156,7 +156,7 @@ describe('RecurlyClient', function () {
const account = await this.RecurlyClient.promises.getAccountForUserId(
this.user._id
)
const expectedAccount = new RecurlyAccount({
const expectedAccount = new PaymentProviderAccount({
code: this.user._id,
email: this.user.email,
hasPastDueInvoice: false,
@@ -226,7 +226,7 @@ describe('RecurlyClient', function () {
const coupons =
await this.RecurlyClient.promises.getActiveCouponsForUserId('some-user')
const expectedCoupons = [
new RecurlyCoupon({
new PaymentProviderCoupon({
code: 'coupon-code',
name: 'Coupon Name',
description: 'hosted page description',
@@ -329,7 +329,7 @@ describe('RecurlyClient', function () {
it('handles plan changes', async function () {
await this.RecurlyClient.promises.applySubscriptionChangeRequest(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
planCode: 'new-plan',
@@ -343,11 +343,11 @@ describe('RecurlyClient', function () {
it('handles add-on changes', async function () {
await this.RecurlyClient.promises.applySubscriptionChangeRequest(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
addOnUpdates: [
new RecurlySubscriptionAddOnUpdate({
new PaymentProviderSubscriptionAddOnUpdate({
code: 'new-add-on',
quantity: 2,
unitPrice: 8.99,
@@ -514,7 +514,7 @@ describe('RecurlyClient', function () {
})
const { immediateCharge } =
await this.RecurlyClient.promises.previewSubscriptionChange(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
planCode: 'new-plan',
@@ -546,7 +546,7 @@ describe('RecurlyClient', function () {
})
const { immediateCharge } =
await this.RecurlyClient.promises.previewSubscriptionChange(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
planCode: 'new-plan',
@@ -570,7 +570,7 @@ describe('RecurlyClient', function () {
.throws(new ValidationError())
await expect(
this.RecurlyClient.promises.previewSubscriptionChange(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
planCode: 'new-plan',
@@ -583,7 +583,7 @@ describe('RecurlyClient', function () {
this.client.previewSubscriptionChange = sinon.stub().throws(new Error())
await expect(
this.RecurlyClient.promises.previewSubscriptionChange(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.subscription,
timeframe: 'now',
planCode: 'new-plan',

View File

@@ -15,12 +15,12 @@ describe('SubscriptionGroupHandler', function () {
this.subscription_id = '31DSd1123D'
this.adding = 1
this.paymentMethod = { cardType: 'Visa', lastFour: '1111' }
this.RecurlyEntities = {
this.PaymentProviderEntities = {
MEMBERS_LIMIT_ADD_ON_CODE: 'additional-license',
}
this.localPlanInSettings = {
membersLimit: 5,
membersLimitAddOn: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
membersLimitAddOn: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
}
this.subscription = {
@@ -40,7 +40,7 @@ describe('SubscriptionGroupHandler', function () {
id: 123,
addOns: [
{
code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
quantity: 1,
},
],
@@ -98,7 +98,7 @@ describe('SubscriptionGroupHandler', function () {
this.previewSubscriptionChange = {
nextAddOns: [
{
code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
quantity: this.recurlySubscription.addOns[0].quantity + this.adding,
},
],
@@ -166,7 +166,7 @@ describe('SubscriptionGroupHandler', function () {
},
'./RecurlyClient': this.RecurlyClient,
'./PlansLocator': this.PlansLocator,
'./RecurlyEntities': this.RecurlyEntities,
'./PaymentProviderEntities': this.PaymentProviderEntities,
'../Authentication/SessionManager': this.SessionManager,
'./GroupPlansData': this.GroupPlansData,
},
@@ -325,7 +325,8 @@ describe('SubscriptionGroupHandler', function () {
},
plan: {
membersLimit: 5,
membersLimitAddOn: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
membersLimitAddOn:
this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
canUseFlexibleLicensing: true,
},
recurlySubscription: this.recurlySubscription,
@@ -347,14 +348,14 @@ describe('SubscriptionGroupHandler', function () {
beforeEach(function () {
this.recurlySubscription.addOns = [
{
code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
quantity: 6,
},
]
this.prevQuantity = this.recurlySubscription.addOns[0].quantity
this.previewSubscriptionChange.nextAddOns = [
{
code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
quantity: this.prevQuantity + this.adding,
},
]
@@ -367,7 +368,7 @@ describe('SubscriptionGroupHandler', function () {
this.recurlySubscription.getRequestForAddOnUpdate
.calledWith(
this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
this.recurlySubscription.addOns[0].quantity + this.adding
)
.should.equal(true)
@@ -391,7 +392,7 @@ describe('SubscriptionGroupHandler', function () {
{
type: 'add-on-update',
addOn: {
code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
quantity:
this.previewSubscriptionChange.nextAddOns[0].quantity,
prevQuantity: this.prevQuantity,
@@ -435,7 +436,7 @@ describe('SubscriptionGroupHandler', function () {
this.prevQuantity = this.recurlySubscription.addOns[0]?.quantity ?? 0
this.previewSubscriptionChange.nextAddOns = [
{
code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
quantity: this.prevQuantity + this.adding,
},
]
@@ -467,7 +468,7 @@ describe('SubscriptionGroupHandler', function () {
{
type: 'add-on-update',
addOn: {
code: this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
code: this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
quantity:
this.previewSubscriptionChange.nextAddOns[0].quantity,
prevQuantity: this.prevQuantity,
@@ -493,7 +494,7 @@ describe('SubscriptionGroupHandler', function () {
)
this.recurlySubscription.getRequestForAddOnPurchase
.calledWithExactly(
this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
this.adding,
this.GroupPlansData.enterprise.collaborator.USD[5]
.additional_license_legacy_price_in_cents / 100
@@ -513,7 +514,7 @@ describe('SubscriptionGroupHandler', function () {
)
this.recurlySubscription.getRequestForAddOnPurchase
.calledWithExactly(
this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
this.adding,
undefined
)
@@ -538,7 +539,7 @@ describe('SubscriptionGroupHandler', function () {
)
this.recurlySubscription.getRequestForAddOnPurchase
.calledWithExactly(
this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
this.adding,
this.GroupPlansData.enterprise.collaborator.USD[5]
.additional_license_legacy_price_in_cents / 100
@@ -563,7 +564,7 @@ describe('SubscriptionGroupHandler', function () {
)
this.recurlySubscription.getRequestForAddOnPurchase
.calledWithExactly(
this.RecurlyEntities.MEMBERS_LIMIT_ADD_ON_CODE,
this.PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE,
this.adding,
undefined
)

View File

@@ -3,9 +3,9 @@ const sinon = require('sinon')
const chai = require('chai')
const { expect } = chai
const {
RecurlySubscription,
RecurlySubscriptionChangeRequest,
} = require('../../../../app/src/Features/Subscription/RecurlyEntities')
PaymentProviderSubscription,
PaymentProviderSubscriptionChangeRequest,
} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
const MODULE_PATH =
'../../../../app/src/Features/Subscription/SubscriptionHandler'
@@ -27,7 +27,7 @@ const mockRecurlySubscriptions = {
}
const mockRecurlyClientSubscriptions = {
'subscription-123-active': new RecurlySubscription({
'subscription-123-active': new PaymentProviderSubscription({
id: 'subscription-123-active',
userId: 'user-id',
planCode: 'collaborator',
@@ -275,7 +275,7 @@ describe('SubscriptionHandler', function () {
expect(
this.RecurlyClient.promises.applySubscriptionChangeRequest
).to.have.been.calledWith(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.activeRecurlyClientSubscription,
timeframe: 'now',
planCode: this.plan_code,
@@ -388,7 +388,7 @@ describe('SubscriptionHandler', function () {
expect(
this.RecurlyClient.promises.applySubscriptionChangeRequest
).to.be.calledWith(
new RecurlySubscriptionChangeRequest({
new PaymentProviderSubscriptionChangeRequest({
subscription: this.activeRecurlyClientSubscription,
timeframe: 'now',
planCode: this.plan_code,

View File

@@ -2,11 +2,11 @@ const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const { assert } = require('chai')
const {
RecurlyAccount,
RecurlySubscription,
RecurlySubscriptionAddOn,
RecurlySubscriptionChange,
} = require('../../../../app/src/Features/Subscription/RecurlyEntities')
PaymentProviderAccount,
PaymentProviderSubscription,
PaymentProviderSubscriptionAddOn,
PaymentProviderSubscriptionChange,
} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
const modulePath =
'../../../../app/src/Features/Subscription/SubscriptionViewModelBuilder'
@@ -33,7 +33,7 @@ describe('SubscriptionViewModelBuilder', function () {
state: 'active',
},
}
this.recurlySubscription = new RecurlySubscription({
this.paymentRecord = new PaymentProviderSubscription({
id: this.recurlySubscription_id,
userId: this.user._id,
currency: 'EUR',
@@ -41,7 +41,7 @@ describe('SubscriptionViewModelBuilder', function () {
planName: 'plan-name',
planPrice: 13,
addOns: [
new RecurlySubscriptionAddOn({
new PaymentProviderSubscriptionAddOn({
code: 'addon-code',
name: 'addon name',
quantity: 1,
@@ -265,7 +265,7 @@ describe('SubscriptionViewModelBuilder', function () {
plan: this.plan,
recurlySubscription_id: this.recurlySubscription_id,
}
this.recurlySubscription = {
this.paymentRecord = {
state: 'active',
}
this.SubscriptionLocator.promises.getUsersSubscription
@@ -279,7 +279,7 @@ describe('SubscriptionViewModelBuilder', function () {
.withArgs(this.individualSubscription.recurlySubscription_id, {
includeAccount: true,
})
.resolves(this.recurlySubscription)
.resolves(this.paymentRecord)
const usersBestSubscription =
await this.SubscriptionViewModelBuilder.promises.getBestSubscription(
@@ -293,7 +293,7 @@ describe('SubscriptionViewModelBuilder', function () {
)
sinon.assert.calledWith(
this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly,
this.recurlySubscription,
this.paymentRecord,
this.individualSubscriptionWithoutRecurly
)
assert.deepEqual(usersBestSubscription, {
@@ -507,14 +507,14 @@ describe('SubscriptionViewModelBuilder', function () {
describe('buildUsersSubscriptionViewModel', function () {
describe('with a recurly subscription', function () {
it('adds recurly data to the personal subscription', async function () {
it('adds payment data to the personal subscription', async function () {
this.SubscriptionLocator.getUsersSubscription.yields(
null,
this.individualSubscription
)
this.PaymentService.getPaymentFromRecord.yields(null, {
subscription: this.recurlySubscription,
account: new RecurlyAccount({
subscription: this.paymentRecord,
account: new PaymentProviderAccount({
email: 'example@example.com',
hasPastDueInvoice: false,
}),
@@ -524,7 +524,7 @@ describe('SubscriptionViewModelBuilder', function () {
await this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel(
this.user
)
assert.deepEqual(result.personalSubscription.recurly, {
assert.deepEqual(result.personalSubscription.payment, {
taxRate: 0.1,
billingDetailsLink: '/user/subscription/recurly/billing-details',
accountManagementLink:
@@ -564,28 +564,29 @@ describe('SubscriptionViewModelBuilder', function () {
null,
this.individualSubscription
)
this.recurlySubscription.pendingChange = new RecurlySubscriptionChange({
subscription: this.recurlySubscription,
nextPlanCode: this.groupPlanCode,
nextPlanName: 'Group Collaborator (Annual) 4 licenses',
nextPlanPrice: 1400,
nextAddOns: [
new RecurlySubscriptionAddOn({
code: 'additional-license',
name: 'additional license',
quantity: 8,
unitPrice: 24.4,
}),
new RecurlySubscriptionAddOn({
code: 'addon-code',
name: 'addon name',
quantity: 1,
unitPrice: 2,
}),
],
})
this.paymentRecord.pendingChange =
new PaymentProviderSubscriptionChange({
subscription: this.paymentRecord,
nextPlanCode: this.groupPlanCode,
nextPlanName: 'Group Collaborator (Annual) 4 licenses',
nextPlanPrice: 1400,
nextAddOns: [
new PaymentProviderSubscriptionAddOn({
code: 'additional-license',
name: 'additional license',
quantity: 8,
unitPrice: 24.4,
}),
new PaymentProviderSubscriptionAddOn({
code: 'addon-code',
name: 'addon name',
quantity: 1,
unitPrice: 2,
}),
],
})
this.PaymentService.getPaymentFromRecord.yields(null, {
subscription: this.recurlySubscription,
subscription: this.paymentRecord,
account: {},
coupons: [],
})
@@ -594,24 +595,24 @@ describe('SubscriptionViewModelBuilder', function () {
this.user
)
assert.equal(
result.personalSubscription.recurly.displayPrice,
result.personalSubscription.payment.displayPrice,
'€1,756.92'
)
assert.equal(
result.personalSubscription.recurly.planOnlyDisplayPrice,
result.personalSubscription.payment.planOnlyDisplayPrice,
'€1,754.72'
)
assert.deepEqual(
result.personalSubscription.recurly
result.personalSubscription.payment
.addOnDisplayPricesWithoutAdditionalLicense,
{ 'addon-code': '€2.20' }
)
assert.equal(
result.personalSubscription.recurly.pendingAdditionalLicenses,
result.personalSubscription.payment.pendingAdditionalLicenses,
8
)
assert.equal(
result.personalSubscription.recurly.pendingTotalLicenses,
result.personalSubscription.payment.pendingTotalLicenses,
12
)
})

View File

@@ -1,6 +1,11 @@
import { CurrencyCode } from '../currency'
import { Nullable } from '../../utils'
import { Plan, AddOn, RecurlyAddOn, PendingRecurlyPlan } from '../plan'
import {
Plan,
AddOn,
PaymentProviderAddOn,
PendingPaymentProviderPlan,
} from '../plan'
import { User } from '../../user'
type SubscriptionState = 'active' | 'canceled' | 'expired' | 'paused'
@@ -10,18 +15,18 @@ export type PurchasingAddOnCode = {
code: string
}
type RecurlyCoupon = {
type PaymentProviderCoupon = {
code: string
name: string
description: string
}
type Recurly = {
type PaymentProviderRecord = {
taxRate: number
billingDetailsLink: string
accountManagementLink: string
additionalLicenses: number
addOns: RecurlyAddOn[]
addOns: PaymentProviderAddOn[]
totalLicenses: number
nextPaymentDueAt: string
nextPaymentDueDate: string
@@ -29,7 +34,7 @@ type Recurly = {
state?: SubscriptionState
trialEndsAtFormatted: Nullable<string>
trialEndsAt: Nullable<string>
activeCoupons: RecurlyCoupon[]
activeCoupons: PaymentProviderCoupon[]
accountEmail: string
hasPastDueInvoice: boolean
displayPrice: string
@@ -58,19 +63,19 @@ export type Subscription = {
planCode: string
recurlySubscription_id: string
plan: Plan
pendingPlan?: PendingRecurlyPlan
pendingPlan?: PendingPaymentProviderPlan
addOns?: AddOn[]
}
export type RecurlySubscription = Subscription & {
recurly: Recurly
export type PaidSubscription = Subscription & {
payment: PaymentProviderRecord
}
export type CustomSubscription = Subscription & {
customAccount: boolean
}
export type GroupSubscription = RecurlySubscription & {
export type GroupSubscription = PaidSubscription & {
teamName: string
teamNotice?: string
}

View File

@@ -22,8 +22,8 @@ export type AddOn = {
unitAmountInCents: number
}
// add-ons directly accessed through recurly
export type RecurlyAddOn = {
// add-ons directly accessed through payment
export type PaymentProviderAddOn = {
code: string
name: string
quantity: number
@@ -32,11 +32,11 @@ export type RecurlyAddOn = {
displayPrice?: string
}
export type PendingRecurlyPlan = {
export type PendingPaymentProviderPlan = {
annual?: boolean
displayPrice?: string
featureDescription?: Record<string, unknown>[]
addOns?: RecurlyAddOn[]
addOns?: PaymentProviderAddOn[]
features?: Features
groupPlan?: boolean
hideFromUsers?: boolean