tenseleyflow/shithub / 4f220ec

Browse files

Add billing settings data helpers

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
4f220ece8822e56657c331bab699c30cbd397228
Parents
b9f1094
Tree
fcd95d0

5 changed files

StatusFile+-
M internal/billing/billing.go 25 0
M internal/billing/billing_test.go 22 0
M internal/billing/queries/billing.sql 9 0
M internal/billing/sqlc/billing.sql.go 17 0
M internal/billing/sqlc/querier.go 1 0
internal/billing/billing.gomodified
@@ -263,6 +263,17 @@ func MarkWebhookEventFailed(ctx context.Context, deps Deps, providerEventID, pro
263263
 	})
264264
 }
265265
 
266
+func GetWebhookEventReceipt(ctx context.Context, deps Deps, providerEventID string) (billingdb.BillingWebhookEvent, error) {
267
+	if err := validateDeps(deps); err != nil {
268
+		return billingdb.BillingWebhookEvent{}, err
269
+	}
270
+	providerEventID = strings.TrimSpace(providerEventID)
271
+	if providerEventID == "" {
272
+		return billingdb.BillingWebhookEvent{}, ErrWebhookEventID
273
+	}
274
+	return billingdb.New().GetWebhookEventReceipt(ctx, deps.Pool, providerEventID)
275
+}
276
+
266277
 func UpsertInvoice(ctx context.Context, deps Deps, snap InvoiceSnapshot) (billingdb.BillingInvoice, error) {
267278
 	if err := validateDeps(deps); err != nil {
268279
 		return billingdb.BillingInvoice{}, err
@@ -363,6 +374,20 @@ func CountBillableOrgMembers(ctx context.Context, deps Deps, orgID int64) (int,
363374
 	return int(n), nil
364375
 }
365376
 
377
+func CountPendingOrgInvitations(ctx context.Context, deps Deps, orgID int64) (int, error) {
378
+	if err := validateDeps(deps); err != nil {
379
+		return 0, err
380
+	}
381
+	if orgID == 0 {
382
+		return 0, ErrOrgIDRequired
383
+	}
384
+	n, err := billingdb.New().CountPendingOrgInvitations(ctx, deps.Pool, orgID)
385
+	if err != nil {
386
+		return 0, err
387
+	}
388
+	return int(n), nil
389
+}
390
+
366391
 func MarkPastDue(ctx context.Context, deps Deps, orgID int64, graceUntil time.Time, lastWebhookEventID string) (State, error) {
367392
 	if err := validateDeps(deps); err != nil {
368393
 		return State{}, err
internal/billing/billing_test.gomodified
@@ -185,6 +185,13 @@ func TestRecordWebhookEventIsIdempotent(t *testing.T) {
185185
 	if _, err := billing.MarkWebhookEventProcessed(ctx, deps, event.ProviderEventID); err != nil {
186186
 		t.Fatalf("MarkWebhookEventProcessed: %v", err)
187187
 	}
188
+	receipt, err := billing.GetWebhookEventReceipt(ctx, deps, event.ProviderEventID)
189
+	if err != nil {
190
+		t.Fatalf("GetWebhookEventReceipt: %v", err)
191
+	}
192
+	if receipt.ProviderEventID != event.ProviderEventID || !receipt.ProcessedAt.Valid {
193
+		t.Fatalf("unexpected receipt lookup: %+v", receipt)
194
+	}
188195
 	dup, created, err = billing.RecordWebhookEvent(ctx, deps, event)
189196
 	if err != nil {
190197
 		t.Fatalf("RecordWebhookEvent after processed: %v", err)
@@ -231,6 +238,21 @@ func TestSyncSeatSnapshotUpdatesBillingState(t *testing.T) {
231238
 	if count != 1 {
232239
 		t.Fatalf("billable members: got %d, want 1", count)
233240
 	}
241
+
242
+	if _, err := deps.Pool.Exec(ctx, `
243
+		INSERT INTO org_invitations (org_id, target_email, role, token_hash, expires_at)
244
+		VALUES ($1, 'pending@example.com', 'member', '\x010203', now() + interval '1 day'),
245
+		       ($1, 'expired@example.com', 'member', '\x040506', now() - interval '1 day')
246
+	`, org.ID); err != nil {
247
+		t.Fatalf("insert invitations: %v", err)
248
+	}
249
+	pending, err := billing.CountPendingOrgInvitations(ctx, deps, org.ID)
250
+	if err != nil {
251
+		t.Fatalf("CountPendingOrgInvitations: %v", err)
252
+	}
253
+	if pending != 1 {
254
+		t.Fatalf("pending invitations: got %d, want 1", pending)
255
+	}
234256
 }
235257
 
236258
 func TestStripeLookupsAndInvoiceSnapshot(t *testing.T) {
internal/billing/queries/billing.sqlmodified
@@ -231,6 +231,15 @@ SELECT count(*)::integer
231231
 FROM org_members
232232
 WHERE org_id = $1;
233233
 
234
+-- name: CountPendingOrgInvitations :one
235
+SELECT count(*)::integer
236
+FROM org_invitations
237
+WHERE org_id = $1
238
+  AND accepted_at IS NULL
239
+  AND declined_at IS NULL
240
+  AND canceled_at IS NULL
241
+  AND expires_at > now();
242
+
234243
 -- ─── billing_invoices ──────────────────────────────────────────────
235244
 
236245
 -- name: UpsertInvoice :one
internal/billing/sqlc/billing.sql.gomodified
@@ -255,6 +255,23 @@ func (q *Queries) CountBillableOrgMembers(ctx context.Context, db DBTX, orgID in
255255
 	return column_1, err
256256
 }
257257
 
258
+const countPendingOrgInvitations = `-- name: CountPendingOrgInvitations :one
259
+SELECT count(*)::integer
260
+FROM org_invitations
261
+WHERE org_id = $1
262
+  AND accepted_at IS NULL
263
+  AND declined_at IS NULL
264
+  AND canceled_at IS NULL
265
+  AND expires_at > now()
266
+`
267
+
268
+func (q *Queries) CountPendingOrgInvitations(ctx context.Context, db DBTX, orgID int64) (int32, error) {
269
+	row := db.QueryRow(ctx, countPendingOrgInvitations, orgID)
270
+	var column_1 int32
271
+	err := row.Scan(&column_1)
272
+	return column_1, err
273
+}
274
+
258275
 const createSeatSnapshot = `-- name: CreateSeatSnapshot :one
259276
 
260277
 WITH snapshot AS (
internal/billing/sqlc/querier.gomodified
@@ -14,6 +14,7 @@ type Querier interface {
1414
 	ApplySubscriptionSnapshot(ctx context.Context, db DBTX, arg ApplySubscriptionSnapshotParams) (ApplySubscriptionSnapshotRow, error)
1515
 	ClearBillingLock(ctx context.Context, db DBTX, orgID int64) (ClearBillingLockRow, error)
1616
 	CountBillableOrgMembers(ctx context.Context, db DBTX, orgID int64) (int32, error)
17
+	CountPendingOrgInvitations(ctx context.Context, db DBTX, orgID int64) (int32, error)
1718
 	// ─── billing_seat_snapshots ────────────────────────────────────────
1819
 	CreateSeatSnapshot(ctx context.Context, db DBTX, arg CreateSeatSnapshotParams) (CreateSeatSnapshotRow, error)
1920
 	// ─── billing_webhook_events ────────────────────────────────────────