Преглед изворни кода

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

[web] Save stripe checkout data to mongodb

GitOrigin-RevId: 537778a041f92f43ccf6455c29a56c7a961ce765
Kristina пре 1 година
родитељ
комит
6f1f1ba744

+ 7 - 1
services/web/app/src/Features/Subscription/PaymentProviderEntities.js

@@ -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

+ 1 - 0
services/web/app/src/Features/Subscription/RecurlyClient.js

@@ -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,

+ 16 - 3
services/web/app/src/Features/Subscription/SubscriptionUpdater.js

@@ -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,

+ 9 - 0
services/web/app/src/models/Subscription.js

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

+ 1 - 0
services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js

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

+ 3 - 1
services/web/types/project/dashboard/subscription.ts

@@ -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 = {

+ 11 - 1
services/web/types/subscription/dashboard/subscription.ts

@@ -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>
+}