Merge pull request #24848 from overleaf/mf-save-stripe-checkout-data-to-mongo-db

[web] Save stripe checkout data to mongodb

GitOrigin-RevId: 537778a041f92f43ccf6455c29a56c7a961ce765
This commit is contained in:
Kristina
2025-04-23 12:13:18 +02:00
committed by Copybot
parent a426d08b9c
commit 5cef00a15f
7 changed files with 48 additions and 6 deletions

View File

@@ -1,5 +1,9 @@
// @ts-check
/**
* @import { PaymentProvider } from '../../../../types/subscription/dashboard/subscription'
*/
const OError = require('@overleaf/o-error')
const { DuplicateAddOnError, AddOnNotPresentError } = require('./Errors')
const PlansLocator = require('./PlansLocator')
@@ -27,8 +31,9 @@ class PaymentProviderSubscription {
* @param {Date} props.periodEnd
* @param {string} props.collectionMethod
* @param {PaymentProviderSubscriptionChange} [props.pendingChange]
* @param {string} [props.service]
* @param {PaymentProvider['service']} [props.service]
* @param {string} [props.state]
* @param {Date|null} [props.trialPeriodStart]
* @param {Date|null} [props.trialPeriodEnd]
* @param {Date|null} [props.pausePeriodStart]
* @param {number|null} [props.remainingPauseCycles]
@@ -51,6 +56,7 @@ class PaymentProviderSubscription {
this.pendingChange = props.pendingChange ?? null
this.service = props.service ?? 'recurly'
this.state = props.state ?? 'active'
this.trialPeriodStart = props.trialPeriodStart ?? null
this.trialPeriodEnd = props.trialPeriodEnd ?? null
this.pausePeriodStart = props.pausePeriodStart ?? null
this.remainingPauseCycles = props.remainingPauseCycles ?? null

View File

@@ -411,6 +411,7 @@ function subscriptionFromApi(apiSubscription) {
collectionMethod: apiSubscription.collectionMethod,
service: 'recurly',
state: apiSubscription.state ?? 'active',
trialPeriodStart: apiSubscription.trialStartedAt,
trialPeriodEnd: apiSubscription.trialEndsAt,
pausePeriodStart: apiSubscription.pausedAt,
remainingPauseCycles: apiSubscription.remainingPauseCycles,

View File

@@ -13,6 +13,11 @@ const UserAuditLogHandler = require('../User/UserAuditLogHandler')
const AccountMappingHelper = require('../Analytics/AccountMappingHelper')
const { SSOConfig } = require('../../models/SSOConfig')
/**
* @typedef {import('../../../../types/subscription/dashboard/subscription').Subscription} Subscription
* @typedef {import('../../../../types/subscription/dashboard/subscription').PaymentProvider} PaymentProvider
*/
/**
* Change the admin of the given subscription.
*
@@ -51,7 +56,7 @@ async function syncSubscription(
let subscription =
await SubscriptionLocator.promises.getUsersSubscription(adminUserId)
if (subscription == null) {
subscription = await _createNewSubscription(adminUserId)
subscription = await createNewSubscription(adminUserId)
}
await updateSubscriptionFromRecurly(
recurlySubscription,
@@ -226,7 +231,13 @@ async function createDeletedSubscription(subscription, deleterData) {
await DeletedSubscription.findOneAndUpdate(filter, data, options).exec()
}
async function _createNewSubscription(adminUserId) {
/**
* Creates a new subscription for the given admin user.
*
* @param {string} adminUserId
* @returns {Promise<Subscription>}
*/
async function createNewSubscription(adminUserId) {
const subscription = new Subscription({
admin_id: adminUserId,
manager_ids: [adminUserId],
@@ -242,7 +253,7 @@ async function _deleteAndReplaceSubscriptionFromRecurly(
) {
const adminUserId = subscription.admin_id
await deleteSubscription(subscription, requesterData)
const newSubscription = await _createNewSubscription(adminUserId)
const newSubscription = await createNewSubscription(adminUserId)
await updateSubscriptionFromRecurly(
recurlySubscription,
newSubscription,
@@ -428,6 +439,7 @@ async function _sendSubscriptionEventForAllMembers(subscriptionId, event) {
module.exports = {
updateAdmin: callbackify(updateAdmin),
syncSubscription: callbackify(syncSubscription),
createNewSubscription: callbackify(createNewSubscription),
deleteSubscription: callbackify(deleteSubscription),
createDeletedSubscription: callbackify(createDeletedSubscription),
addUserToGroup: callbackify(addUserToGroup),
@@ -440,6 +452,7 @@ module.exports = {
promises: {
updateAdmin,
syncSubscription,
createNewSubscription,
addUserToGroup,
refreshUsersFeatures,
removeUserFromGroup,

View File

@@ -64,6 +64,15 @@ const SubscriptionSchema = new Schema(
subscriptionId: {
type: String,
},
state: {
type: String,
},
trialStartedAt: {
type: Date,
},
trialEndsAt: {
type: Date,
},
},
collectionMethod: {
type: String,

View File

@@ -114,6 +114,7 @@ describe('SubscriptionHandler', function () {
promises: {
updateSubscriptionFromRecurly: sinon.stub().resolves(),
syncSubscription: sinon.stub().resolves(),
syncStripeSubscription: sinon.stub().resolves(),
startFreeTrial: sinon.stub().resolves(),
},
}

View File

@@ -1,3 +1,5 @@
import { SubscriptionState } from '../../subscription/dashboard/subscription'
type SubscriptionBase = {
featuresPageURL: string
}
@@ -9,7 +11,7 @@ export type FreePlanSubscription = {
type FreeSubscription = FreePlanSubscription
type RecurlyStatus = {
state: 'active' | 'canceled' | 'expired' | 'paused'
state: SubscriptionState
}
type PaidSubscriptionBase = {

View File

@@ -8,7 +8,7 @@ import {
} from '../plan'
import { User } from '../../user'
type SubscriptionState = 'active' | 'canceled' | 'expired' | 'paused'
export type SubscriptionState = 'active' | 'canceled' | 'expired' | 'paused'
// when puchasing a new add-on in recurly, we only need to provide the code
export type PurchasingAddOnCode = {
@@ -99,3 +99,13 @@ export type MemberGroupSubscription = Omit<GroupSubscription, 'admin_id'> & {
planLevelName: string
admin_id: User
}
type PaymentProviderService = 'stripe' | 'recurly'
export type PaymentProvider = {
service: PaymentProviderService
subscriptionId: string
state: SubscriptionState
trialStartedAt?: Nullable<Date>
trialEndsAt?: Nullable<Date>
}