@@ -46,12 +46,42 @@ export async function banUser(email: string) {
4646 await updateUserMetadata ( user . user_id ! , { banned : true } ) ;
4747}
4848
49- export async function updateProUserData ( email : string , subscription : Partial < PayingUserMetadata > ) {
50- dropUndefinedValues ( subscription ) ;
49+ export async function updateProUserData ( email : string , subscriptionUpdate : Partial < PayingUserMetadata > ) {
50+ dropUndefinedValues ( subscriptionUpdate ) ;
5151
5252 const user = await getOrCreateUserData ( email ) ;
5353 const appData = user . app_metadata as AppMetadata ;
5454
55+ // Does the user already have unrelated subscription data?
56+ if (
57+ appData &&
58+ 'subscription_id' in appData &&
59+ subscriptionUpdate . subscription_id &&
60+ appData . subscription_id !== subscriptionUpdate . subscription_id
61+ ) {
62+ // If the user has an existing subscription and we get an event for a new one, there's a few
63+ // possibilities. One (especially with PayPro) is that they're manually renewing an expiring
64+ // one, or they have briefly overlapping subscriptions and the old one has now lapsed.
65+
66+ // The possibilities here are quite complicated (e.g. new subs can start as 'cancelled' due to
67+ // manual renewal configuration in PayPro) but "latest expiry" tends to be the right answer.
68+
69+ if ( subscriptionUpdate . subscription_expiry ! < appData . subscription_expiry ) {
70+ log . warn ( `User ${ email } received a outdated subscription event for an inactive subscription - ignoring` ) ;
71+ return ; // Ignore the update entirely in this case
72+ }
73+
74+ // If there's an update for a different sub, and the user's existing subscription is active and has
75+ // plenty of time left on it, this is probably a mistake (some users do accidentally complete the
76+ // checkout twice) which needs manual intervention.
77+ if (
78+ appData . subscription_status !== 'past_due' &&
79+ moment ( appData . subscription_expiry ) . subtract ( 5 , 'days' ) . valueOf ( ) > Date . now ( )
80+ ) {
81+ reportError ( `Mismatched subscription event for Pro user ${ email } with existing subscription` ) ;
82+ }
83+ }
84+
5585 // Is the user already a member of a team?
5686 if ( appData && 'subscription_owner_id' in appData ) {
5787 const owner = await getUserById ( appData . subscription_owner_id ! ) ;
@@ -66,25 +96,11 @@ export async function updateProUserData(email: string, subscription: Partial<Pay
6696 // update the membership state on both sides:
6797 const updatedTeamMembers = ownerData . team_member_ids . filter ( id => id !== user . user_id ) ;
6898 await updateUserMetadata ( appData . subscription_owner_id ! , { team_member_ids : updatedTeamMembers } ) ;
69- ( subscription as Partial < TeamMemberMetadata > ) . subscription_owner_id = null as any ; // Setting to null deletes the property
70- }
71-
72- // If the user has another subscription with time left on it, this is probably a mistake
73- // (some users do accidentally complete the checkout twice) which needs manual intervention.
74- if (
75- // Existing subscription that hasn't expired yet (48+ hours left)
76- appData &&
77- 'subscription_expiry' in appData &&
78- moment ( appData . subscription_expiry ) . subtract ( 48 , 'hour' ) . valueOf ( ) > Date . now ( ) &&
79- // Not just another event for the same sub (payment vs creation etc)
80- ( appData as PayingUserMetadata ) . subscription_id !== subscription . subscription_id
81-
82- ) {
83- reportError ( `Signup for existing Pro user ${ email } with active subscription` ) ;
99+ ( subscriptionUpdate as Partial < TeamMemberMetadata > ) . subscription_owner_id = null as any ; // Setting to null deletes the property
84100 }
85101
86- if ( ! _ . isEmpty ( subscription ) ) {
87- await updateUserMetadata ( user . user_id ! , subscription ) ;
102+ if ( ! _ . isEmpty ( subscriptionUpdate ) ) {
103+ await updateUserMetadata ( user . user_id ! , subscriptionUpdate ) ;
88104 }
89105}
90106
0 commit comments