@@ -46,12 +46,42 @@ export async function banUser(email: string) {
46
46
await updateUserMetadata ( user . user_id ! , { banned : true } ) ;
47
47
}
48
48
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 ) ;
51
51
52
52
const user = await getOrCreateUserData ( email ) ;
53
53
const appData = user . app_metadata as AppMetadata ;
54
54
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
+
55
85
// Is the user already a member of a team?
56
86
if ( appData && 'subscription_owner_id' in appData ) {
57
87
const owner = await getUserById ( appData . subscription_owner_id ! ) ;
@@ -66,25 +96,11 @@ export async function updateProUserData(email: string, subscription: Partial<Pay
66
96
// update the membership state on both sides:
67
97
const updatedTeamMembers = ownerData . team_member_ids . filter ( id => id !== user . user_id ) ;
68
98
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
84
100
}
85
101
86
- if ( ! _ . isEmpty ( subscription ) ) {
87
- await updateUserMetadata ( user . user_id ! , subscription ) ;
102
+ if ( ! _ . isEmpty ( subscriptionUpdate ) ) {
103
+ await updateUserMetadata ( user . user_id ! , subscriptionUpdate ) ;
88
104
}
89
105
}
90
106
0 commit comments