@@ -32,8 +32,10 @@ interface Props {
32
32
hideSubheading ?: boolean ;
33
33
}
34
34
35
+ // Guard against multiple calls to subscripe (per page load)
36
+ let didAlreadyCallSubscribe = false ;
37
+
35
38
export default function UsageBasedBillingConfig ( { attributionId, hideSubheading = false } : Props ) {
36
- const location = useLocation ( ) ;
37
39
const currentOrg = useCurrentOrg ( ) . data ;
38
40
const attrId = attributionId ? AttributionId . parse ( attributionId ) : undefined ;
39
41
const [ showUpdateLimitModal , setShowUpdateLimitModal ] = useState < boolean > ( false ) ;
@@ -49,6 +51,9 @@ export default function UsageBasedBillingConfig({ attributionId, hideSubheading
49
51
undefined ,
50
52
) ;
51
53
54
+ // Stripe-controlled parameters
55
+ const location = useLocation ( ) ;
56
+
52
57
const now = useMemo ( ( ) => dayjs ( ) . utc ( true ) , [ ] ) ;
53
58
const [ billingCycleFrom , setBillingCycleFrom ] = useState < dayjs . Dayjs > ( now . startOf ( "month" ) ) ;
54
59
const [ billingCycleTo , setBillingCycleTo ] = useState < dayjs . Dayjs > ( now . endOf ( "month" ) ) ;
@@ -90,53 +95,82 @@ export default function UsageBasedBillingConfig({ attributionId, hideSubheading
90
95
} , [ attributionId , refreshSubscriptionDetails ] ) ;
91
96
92
97
useEffect ( ( ) => {
93
- if ( ! attributionId ) {
94
- return ;
95
- }
96
98
const params = new URLSearchParams ( location . search ) ;
97
- if ( ! params . get ( "setup_intent" ) || params . get ( "redirect_status" ) !== "succeeded" ) {
98
- return ;
99
+ const setupIntentId = params . get ( "setup_intent" ) ;
100
+ const redirectStatus = params . get ( "redirect_status" ) ;
101
+ if ( setupIntentId && redirectStatus ) {
102
+ subscribeToStripe ( {
103
+ setupIntentId,
104
+ redirectStatus,
105
+ } ) ;
99
106
}
100
- const setupIntentId = params . get ( "setup_intent" ) ! ;
101
- window . history . replaceState ( { } , "" , location . pathname ) ;
102
- ( async ( ) => {
103
- const pendingSubscription = { pendingSince : Date . now ( ) } ;
104
- try {
105
- setPendingStripeSubscription ( pendingSubscription ) ;
106
- // Pick a good initial value for the Stripe usage limit (base_limit * team_size)
107
- // FIXME: Should we ask the customer to confirm or edit this default limit?
108
- let limit = BASE_USAGE_LIMIT_FOR_STRIPE_USERS ;
109
- if ( attrId ?. kind === "team" && currentOrg ) {
110
- limit = BASE_USAGE_LIMIT_FOR_STRIPE_USERS * currentOrg . members . length ;
111
- }
112
- const newLimit = await getGitpodService ( ) . server . subscribeToStripe ( attributionId , setupIntentId , limit ) ;
113
- if ( newLimit ) {
114
- setUsageLimit ( newLimit ) ;
115
- }
107
+ // eslint-disable-next-line react-hooks/exhaustive-deps
108
+ } , [ ] ) ;
116
109
117
- //refresh every 5 secs until we get a subscriptionId
118
- const interval = setInterval ( async ( ) => {
119
- try {
120
- const subscriptionId = await refreshSubscriptionDetails ( attributionId ) ;
121
- if ( subscriptionId ) {
122
- setPendingStripeSubscription ( undefined ) ;
123
- clearInterval ( interval ) ;
124
- }
125
- } catch ( error ) {
126
- console . error ( error ) ;
127
- }
128
- } , 1000 ) ;
129
- } catch ( error ) {
130
- console . error ( "Could not subscribe to Stripe" , error ) ;
131
- setPendingStripeSubscription ( undefined ) ;
132
- setErrorMessage (
133
- `Could not subscribe: ${
134
- error ?. message || String ( error )
135
- } Contact [email protected] if you believe this is a system error.`,
136
- ) ;
110
+ const subscribeToStripe = useCallback (
111
+ ( stripeParams : { setupIntentId : string ; redirectStatus : string } ) => {
112
+ if ( ! attributionId ) {
113
+ return ;
137
114
}
138
- } ) ( ) ;
139
- } , [ attrId ?. kind , attributionId , currentOrg , location . pathname , location . search , refreshSubscriptionDetails ] ) ;
115
+ const { setupIntentId, redirectStatus } = stripeParams ;
116
+ if ( redirectStatus !== "succeeded" ) {
117
+ // TODO(gpl) We have to handle external validation errors (3DS, e.g.) here
118
+ return ;
119
+ }
120
+
121
+ // Guard against multiple execution following the pattern here: https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application
122
+ if ( didAlreadyCallSubscribe ) {
123
+ console . log ( "didAlreadyCallSubscribe, skipping this time." ) ;
124
+ return ;
125
+ }
126
+ didAlreadyCallSubscribe = true ;
127
+ console . log ( "didAlreadyCallSubscribe false, first run." ) ;
128
+
129
+ window . history . replaceState ( { } , "" , location . pathname ) ;
130
+ ( async ( ) => {
131
+ const pendingSubscription = { pendingSince : Date . now ( ) } ;
132
+ try {
133
+ setPendingStripeSubscription ( pendingSubscription ) ;
134
+ // Pick a good initial value for the Stripe usage limit (base_limit * team_size)
135
+ // FIXME: Should we ask the customer to confirm or edit this default limit?
136
+ let limit = BASE_USAGE_LIMIT_FOR_STRIPE_USERS ;
137
+ if ( attrId ?. kind === "team" && currentOrg ) {
138
+ limit = BASE_USAGE_LIMIT_FOR_STRIPE_USERS * currentOrg . members . length ;
139
+ }
140
+ const newLimit = await getGitpodService ( ) . server . subscribeToStripe (
141
+ attributionId ,
142
+ setupIntentId ,
143
+ limit ,
144
+ ) ;
145
+ if ( newLimit ) {
146
+ setUsageLimit ( newLimit ) ;
147
+ }
148
+
149
+ //refresh every 5 secs until we get a subscriptionId
150
+ const interval = setInterval ( async ( ) => {
151
+ try {
152
+ const subscriptionId = await refreshSubscriptionDetails ( attributionId ) ;
153
+ if ( subscriptionId ) {
154
+ setPendingStripeSubscription ( undefined ) ;
155
+ clearInterval ( interval ) ;
156
+ }
157
+ } catch ( error ) {
158
+ console . error ( error ) ;
159
+ }
160
+ } , 1000 ) ;
161
+ } catch ( error ) {
162
+ console . error ( "Could not subscribe to Stripe" , error ) ;
163
+ setPendingStripeSubscription ( undefined ) ;
164
+ setErrorMessage (
165
+ `Could not subscribe: ${
166
+ error ?. message || String ( error )
167
+ } Contact [email protected] if you believe this is a system error.`,
168
+ ) ;
169
+ }
170
+ } ) ( ) ;
171
+ } ,
172
+ [ attrId ?. kind , attributionId , currentOrg , location . pathname , refreshSubscriptionDetails ] ,
173
+ ) ;
140
174
141
175
const showSpinner = ! attributionId || isLoadingStripeSubscription || ! ! pendingStripeSubscription ;
142
176
const showBalance = ! showSpinner ;
0 commit comments