@@ -16,11 +16,9 @@ import (
16
16
)
17
17
18
18
const (
19
- metronomeBaseUrl = "https://api.metronome.com/v1/"
20
- defaultCollectionMethod = "charge_automatically"
21
- defaultGrantCredits = 5000
22
- defaultGrantName = "Starter Credits"
23
- defaultGrantExpiryMonths = 1
19
+ metronomeBaseUrl = "https://api.metronome.com/v1/"
20
+ defaultCollectionMethod = "charge_automatically"
21
+ defaultMaxRetries = 10
24
22
)
25
23
26
24
// MetronomeClient is the client used to call the Metronome API
@@ -97,7 +95,7 @@ func (m MetronomeClient) createCustomer(ctx context.Context, userEmail string, p
97
95
Data types.Customer `json:"data"`
98
96
}
99
97
100
- err = do (http .MethodPost , path , m . ApiKey , customer , & result )
98
+ _ , err = m . do (http .MethodPost , path , customer , & result )
101
99
if err != nil {
102
100
return customerID , telemetry .Error (ctx , span , err , "error creating customer" )
103
101
}
@@ -131,7 +129,7 @@ func (m MetronomeClient) addCustomerPlan(ctx context.Context, customerID uuid.UU
131
129
} `json:"data"`
132
130
}
133
131
134
- err = do (http .MethodPost , path , m . ApiKey , req , & result )
132
+ _ , err = m . do (http .MethodPost , path , req , & result )
135
133
if err != nil {
136
134
return customerPlanID , telemetry .Error (ctx , span , err , "failed to add customer to plan" )
137
135
}
@@ -154,7 +152,7 @@ func (m MetronomeClient) ListCustomerPlan(ctx context.Context, customerID uuid.U
154
152
Data []types.Plan `json:"data"`
155
153
}
156
154
157
- err = do (http .MethodGet , path , m . ApiKey , nil , & result )
155
+ _ , err = m . do (http .MethodGet , path , nil , & result )
158
156
if err != nil {
159
157
return plan , telemetry .Error (ctx , span , err , "failed to list customer plans" )
160
158
}
@@ -186,7 +184,7 @@ func (m MetronomeClient) EndCustomerPlan(ctx context.Context, customerID uuid.UU
186
184
EndingBeforeUTC : endBefore ,
187
185
}
188
186
189
- err = do (http .MethodPost , path , m . ApiKey , req , nil )
187
+ _ , err = m . do (http .MethodPost , path , req , nil )
190
188
if err != nil {
191
189
return telemetry .Error (ctx , span , err , "failed to end customer plan" )
192
190
}
@@ -215,7 +213,7 @@ func (m MetronomeClient) ListCustomerCredits(ctx context.Context, customerID uui
215
213
Data []types.CreditGrant `json:"data"`
216
214
}
217
215
218
- err = do (http .MethodPost , path , m . ApiKey , req , & result )
216
+ _ , err = m . do (http .MethodPost , path , req , & result )
219
217
if err != nil {
220
218
return credits , telemetry .Error (ctx , span , err , "failed to list customer credits" )
221
219
}
@@ -251,61 +249,93 @@ func (m MetronomeClient) GetCustomerDashboard(ctx context.Context, customerID uu
251
249
Data map [string ]string `json:"data"`
252
250
}
253
251
254
- err = do (http .MethodPost , path , m . ApiKey , req , & result )
252
+ _ , err = m . do (http .MethodPost , path , req , & result )
255
253
if err != nil {
256
254
return url , telemetry .Error (ctx , span , err , "failed to get embeddable dashboard" )
257
255
}
258
256
259
257
return result .Data ["url" ], nil
260
258
}
261
259
262
- func do (method string , path string , apiKey string , body interface {}, data interface {}) (err error ) {
260
+ // IngestEvents sends a list of billing events to Metronome's ingest endpoint
261
+ func (m MetronomeClient ) IngestEvents (ctx context.Context , events []types.BillingEvent ) (err error ) {
262
+ path := "ingest"
263
+
264
+ var currentAttempts int
265
+ for currentAttempts < defaultMaxRetries {
266
+ statusCode , err := m .do (http .MethodPost , path , events , nil )
267
+ // Check errors that are not from error http codes
268
+ if statusCode == 0 && err != nil {
269
+ return err
270
+ }
271
+
272
+ if statusCode == http .StatusForbidden || statusCode == http .StatusUnauthorized {
273
+ return fmt .Errorf ("unauthorized" )
274
+ }
275
+
276
+ // 400 responses should not be retried
277
+ if statusCode == http .StatusBadRequest {
278
+ return fmt .Errorf ("malformed billing events" )
279
+ }
280
+
281
+ // Any other status code can be safely retried
282
+ if statusCode == 200 {
283
+ return nil
284
+ }
285
+ currentAttempts ++
286
+ }
287
+
288
+ return fmt .Errorf ("max number of retry attempts reached with no success" )
289
+ }
290
+
291
+ func (m MetronomeClient ) do (method string , path string , body interface {}, data interface {}) (statusCode int , err error ) {
263
292
client := http.Client {}
264
293
endpoint , err := url .JoinPath (metronomeBaseUrl , path )
265
294
if err != nil {
266
- return err
295
+ return statusCode , err
267
296
}
268
297
269
298
var bodyJson []byte
270
299
if body != nil {
271
300
bodyJson , err = json .Marshal (body )
272
301
if err != nil {
273
- return err
302
+ return statusCode , err
274
303
}
275
304
}
276
305
277
306
req , err := http .NewRequest (method , endpoint , bytes .NewBuffer (bodyJson ))
278
307
if err != nil {
279
- return err
308
+ return statusCode , err
280
309
}
281
- bearer := "Bearer " + apiKey
310
+ bearer := "Bearer " + m . ApiKey
282
311
req .Header .Set ("Authorization" , bearer )
283
312
req .Header .Set ("Content-Type" , "application/json" )
284
313
285
314
resp , err := client .Do (req )
286
315
if err != nil {
287
- return err
316
+ return statusCode , err
288
317
}
318
+ statusCode = resp .StatusCode
289
319
290
320
if resp .StatusCode != http .StatusOK {
291
321
// If there is an error, try to decode the message
292
322
var message map [string ]string
293
323
err = json .NewDecoder (resp .Body ).Decode (& message )
294
324
if err != nil {
295
- return fmt .Errorf ("status code %d received, couldn't process response message" , resp .StatusCode )
325
+ return statusCode , fmt .Errorf ("status code %d received, couldn't process response message" , resp .StatusCode )
296
326
}
297
327
_ = resp .Body .Close ()
298
328
299
- return fmt .Errorf ("status code %d received, response message: %v" , resp .StatusCode , message )
329
+ return statusCode , fmt .Errorf ("status code %d received, response message: %v" , resp .StatusCode , message )
300
330
}
301
331
302
332
if data != nil {
303
333
err = json .NewDecoder (resp .Body ).Decode (data )
304
334
if err != nil {
305
- return err
335
+ return statusCode , err
306
336
}
307
337
}
308
338
_ = resp .Body .Close ()
309
339
310
- return nil
340
+ return statusCode , nil
311
341
}
0 commit comments