Go · 73575 bytes Raw Blame History
1 // Code generated by sqlc. DO NOT EDIT.
2 // versions:
3 // sqlc v1.31.1
4 // source: billing.sql
5
6 package billingdb
7
8 import (
9 "context"
10
11 "github.com/jackc/pgx/v5/pgtype"
12 )
13
14 const applySubscriptionSnapshot = `-- name: ApplySubscriptionSnapshot :one
15 WITH state AS (
16 INSERT INTO org_billing_states (
17 org_id,
18 provider,
19 plan,
20 subscription_status,
21 stripe_subscription_id,
22 stripe_subscription_item_id,
23 current_period_start,
24 current_period_end,
25 cancel_at_period_end,
26 trial_end,
27 canceled_at,
28 last_webhook_event_id,
29 past_due_at,
30 locked_at,
31 lock_reason,
32 grace_until
33 )
34 VALUES (
35 $1::bigint,
36 'stripe',
37 $2::org_plan,
38 $3::billing_subscription_status,
39 $4::text,
40 $5::text,
41 $6::timestamptz,
42 $7::timestamptz,
43 $8::boolean,
44 $9::timestamptz,
45 $10::timestamptz,
46 $11::text,
47 CASE
48 WHEN $3::billing_subscription_status = 'past_due' THEN now()
49 ELSE NULL
50 END,
51 NULL,
52 NULL,
53 NULL
54 )
55 ON CONFLICT (org_id) DO UPDATE
56 SET plan = EXCLUDED.plan,
57 subscription_status = EXCLUDED.subscription_status,
58 stripe_subscription_id = EXCLUDED.stripe_subscription_id,
59 stripe_subscription_item_id = EXCLUDED.stripe_subscription_item_id,
60 current_period_start = EXCLUDED.current_period_start,
61 current_period_end = EXCLUDED.current_period_end,
62 cancel_at_period_end = EXCLUDED.cancel_at_period_end,
63 trial_end = EXCLUDED.trial_end,
64 canceled_at = EXCLUDED.canceled_at,
65 last_webhook_event_id = EXCLUDED.last_webhook_event_id,
66 past_due_at = CASE
67 WHEN EXCLUDED.subscription_status = 'past_due' THEN COALESCE(org_billing_states.past_due_at, now())
68 ELSE NULL
69 END,
70 -- PRO08 D1: never unconditionally NULL the lock columns.
71 -- past_due -> preserve any existing lock (MarkPastDue
72 -- sets fresh grace_until on the invoice.payment_failed
73 -- path; if that hasn't arrived yet, leave NULL).
74 -- active / trialing recovering from past_due/unpaid -> clear.
75 -- any other transition -> preserve existing values.
76 locked_at = CASE
77 WHEN EXCLUDED.subscription_status = 'past_due' THEN COALESCE(org_billing_states.locked_at, now())
78 WHEN EXCLUDED.subscription_status IN ('active', 'trialing')
79 AND org_billing_states.subscription_status IN ('past_due', 'unpaid', 'incomplete') THEN NULL
80 ELSE org_billing_states.locked_at
81 END,
82 lock_reason = CASE
83 WHEN EXCLUDED.subscription_status = 'past_due' THEN COALESCE(org_billing_states.lock_reason, 'past_due'::billing_lock_reason)
84 WHEN EXCLUDED.subscription_status IN ('active', 'trialing')
85 AND org_billing_states.subscription_status IN ('past_due', 'unpaid', 'incomplete') THEN NULL
86 ELSE org_billing_states.lock_reason
87 END,
88 grace_until = CASE
89 WHEN EXCLUDED.subscription_status = 'past_due' THEN org_billing_states.grace_until
90 WHEN EXCLUDED.subscription_status IN ('active', 'trialing')
91 AND org_billing_states.subscription_status IN ('past_due', 'unpaid', 'incomplete') THEN NULL
92 ELSE org_billing_states.grace_until
93 END,
94 updated_at = now()
95 RETURNING org_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, billable_seats, seat_snapshot_at, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at
96 ), org_update AS (
97 UPDATE orgs
98 SET plan = $2::org_plan,
99 updated_at = now()
100 WHERE id = $1::bigint
101 RETURNING id
102 )
103 SELECT org_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, billable_seats, seat_snapshot_at, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM state
104 `
105
106 type ApplySubscriptionSnapshotParams struct {
107 OrgID int64
108 Plan OrgPlan
109 SubscriptionStatus BillingSubscriptionStatus
110 StripeSubscriptionID pgtype.Text
111 StripeSubscriptionItemID pgtype.Text
112 CurrentPeriodStart pgtype.Timestamptz
113 CurrentPeriodEnd pgtype.Timestamptz
114 CancelAtPeriodEnd bool
115 TrialEnd pgtype.Timestamptz
116 CanceledAt pgtype.Timestamptz
117 LastWebhookEventID string
118 }
119
120 type ApplySubscriptionSnapshotRow struct {
121 OrgID int64
122 Provider BillingProvider
123 StripeCustomerID pgtype.Text
124 StripeSubscriptionID pgtype.Text
125 StripeSubscriptionItemID pgtype.Text
126 Plan OrgPlan
127 SubscriptionStatus BillingSubscriptionStatus
128 BillableSeats int32
129 SeatSnapshotAt pgtype.Timestamptz
130 CurrentPeriodStart pgtype.Timestamptz
131 CurrentPeriodEnd pgtype.Timestamptz
132 CancelAtPeriodEnd bool
133 TrialEnd pgtype.Timestamptz
134 PastDueAt pgtype.Timestamptz
135 CanceledAt pgtype.Timestamptz
136 LockedAt pgtype.Timestamptz
137 LockReason NullBillingLockReason
138 GraceUntil pgtype.Timestamptz
139 LastWebhookEventID string
140 CreatedAt pgtype.Timestamptz
141 UpdatedAt pgtype.Timestamptz
142 LastEventAt pgtype.Timestamptz
143 }
144
145 func (q *Queries) ApplySubscriptionSnapshot(ctx context.Context, db DBTX, arg ApplySubscriptionSnapshotParams) (ApplySubscriptionSnapshotRow, error) {
146 row := db.QueryRow(ctx, applySubscriptionSnapshot,
147 arg.OrgID,
148 arg.Plan,
149 arg.SubscriptionStatus,
150 arg.StripeSubscriptionID,
151 arg.StripeSubscriptionItemID,
152 arg.CurrentPeriodStart,
153 arg.CurrentPeriodEnd,
154 arg.CancelAtPeriodEnd,
155 arg.TrialEnd,
156 arg.CanceledAt,
157 arg.LastWebhookEventID,
158 )
159 var i ApplySubscriptionSnapshotRow
160 err := row.Scan(
161 &i.OrgID,
162 &i.Provider,
163 &i.StripeCustomerID,
164 &i.StripeSubscriptionID,
165 &i.StripeSubscriptionItemID,
166 &i.Plan,
167 &i.SubscriptionStatus,
168 &i.BillableSeats,
169 &i.SeatSnapshotAt,
170 &i.CurrentPeriodStart,
171 &i.CurrentPeriodEnd,
172 &i.CancelAtPeriodEnd,
173 &i.TrialEnd,
174 &i.PastDueAt,
175 &i.CanceledAt,
176 &i.LockedAt,
177 &i.LockReason,
178 &i.GraceUntil,
179 &i.LastWebhookEventID,
180 &i.CreatedAt,
181 &i.UpdatedAt,
182 &i.LastEventAt,
183 )
184 return i, err
185 }
186
187 const applyUserSubscriptionSnapshot = `-- name: ApplyUserSubscriptionSnapshot :one
188 WITH state AS (
189 INSERT INTO user_billing_states (
190 user_id,
191 provider,
192 plan,
193 subscription_status,
194 stripe_subscription_id,
195 stripe_subscription_item_id,
196 current_period_start,
197 current_period_end,
198 cancel_at_period_end,
199 trial_end,
200 canceled_at,
201 last_webhook_event_id,
202 past_due_at,
203 locked_at,
204 lock_reason,
205 grace_until
206 )
207 VALUES (
208 $1::bigint,
209 'stripe',
210 $2::user_plan,
211 $3::billing_subscription_status,
212 $4::text,
213 $5::text,
214 $6::timestamptz,
215 $7::timestamptz,
216 $8::boolean,
217 $9::timestamptz,
218 $10::timestamptz,
219 $11::text,
220 CASE
221 WHEN $3::billing_subscription_status = 'past_due' THEN now()
222 ELSE NULL
223 END,
224 NULL,
225 NULL,
226 NULL
227 )
228 ON CONFLICT (user_id) DO UPDATE
229 SET plan = EXCLUDED.plan,
230 subscription_status = EXCLUDED.subscription_status,
231 stripe_subscription_id = EXCLUDED.stripe_subscription_id,
232 stripe_subscription_item_id = EXCLUDED.stripe_subscription_item_id,
233 current_period_start = EXCLUDED.current_period_start,
234 current_period_end = EXCLUDED.current_period_end,
235 cancel_at_period_end = EXCLUDED.cancel_at_period_end,
236 trial_end = EXCLUDED.trial_end,
237 canceled_at = EXCLUDED.canceled_at,
238 last_webhook_event_id = EXCLUDED.last_webhook_event_id,
239 past_due_at = CASE
240 WHEN EXCLUDED.subscription_status = 'past_due' THEN COALESCE(user_billing_states.past_due_at, now())
241 ELSE NULL
242 END,
243 -- PRO08 D1: never unconditionally NULL the lock columns
244 -- (mirror of the org-side fix). The Mark* paths own
245 -- transitions into/out of the locked state.
246 locked_at = CASE
247 WHEN EXCLUDED.subscription_status = 'past_due' THEN COALESCE(user_billing_states.locked_at, now())
248 WHEN EXCLUDED.subscription_status IN ('active', 'trialing')
249 AND user_billing_states.subscription_status IN ('past_due', 'unpaid', 'incomplete') THEN NULL
250 ELSE user_billing_states.locked_at
251 END,
252 lock_reason = CASE
253 WHEN EXCLUDED.subscription_status = 'past_due' THEN COALESCE(user_billing_states.lock_reason, 'past_due'::billing_lock_reason)
254 WHEN EXCLUDED.subscription_status IN ('active', 'trialing')
255 AND user_billing_states.subscription_status IN ('past_due', 'unpaid', 'incomplete') THEN NULL
256 ELSE user_billing_states.lock_reason
257 END,
258 grace_until = CASE
259 WHEN EXCLUDED.subscription_status = 'past_due' THEN user_billing_states.grace_until
260 WHEN EXCLUDED.subscription_status IN ('active', 'trialing')
261 AND user_billing_states.subscription_status IN ('past_due', 'unpaid', 'incomplete') THEN NULL
262 ELSE user_billing_states.grace_until
263 END,
264 updated_at = now()
265 RETURNING user_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at
266 ), user_update AS (
267 UPDATE users
268 SET plan = $2::user_plan,
269 updated_at = now()
270 WHERE id = $1::bigint
271 RETURNING id
272 )
273 SELECT user_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM state
274 `
275
276 type ApplyUserSubscriptionSnapshotParams struct {
277 UserID int64
278 Plan UserPlan
279 SubscriptionStatus BillingSubscriptionStatus
280 StripeSubscriptionID pgtype.Text
281 StripeSubscriptionItemID pgtype.Text
282 CurrentPeriodStart pgtype.Timestamptz
283 CurrentPeriodEnd pgtype.Timestamptz
284 CancelAtPeriodEnd bool
285 TrialEnd pgtype.Timestamptz
286 CanceledAt pgtype.Timestamptz
287 LastWebhookEventID string
288 }
289
290 type ApplyUserSubscriptionSnapshotRow struct {
291 UserID int64
292 Provider BillingProvider
293 StripeCustomerID pgtype.Text
294 StripeSubscriptionID pgtype.Text
295 StripeSubscriptionItemID pgtype.Text
296 Plan UserPlan
297 SubscriptionStatus BillingSubscriptionStatus
298 CurrentPeriodStart pgtype.Timestamptz
299 CurrentPeriodEnd pgtype.Timestamptz
300 CancelAtPeriodEnd bool
301 TrialEnd pgtype.Timestamptz
302 PastDueAt pgtype.Timestamptz
303 CanceledAt pgtype.Timestamptz
304 LockedAt pgtype.Timestamptz
305 LockReason NullBillingLockReason
306 GraceUntil pgtype.Timestamptz
307 LastWebhookEventID string
308 CreatedAt pgtype.Timestamptz
309 UpdatedAt pgtype.Timestamptz
310 LastEventAt pgtype.Timestamptz
311 }
312
313 // Mirrors ApplySubscriptionSnapshot for orgs minus the seat columns
314 // and with `user_plan` as the plan enum. The same CTE pattern keeps
315 // users.plan and user_billing_states.plan atomic.
316 func (q *Queries) ApplyUserSubscriptionSnapshot(ctx context.Context, db DBTX, arg ApplyUserSubscriptionSnapshotParams) (ApplyUserSubscriptionSnapshotRow, error) {
317 row := db.QueryRow(ctx, applyUserSubscriptionSnapshot,
318 arg.UserID,
319 arg.Plan,
320 arg.SubscriptionStatus,
321 arg.StripeSubscriptionID,
322 arg.StripeSubscriptionItemID,
323 arg.CurrentPeriodStart,
324 arg.CurrentPeriodEnd,
325 arg.CancelAtPeriodEnd,
326 arg.TrialEnd,
327 arg.CanceledAt,
328 arg.LastWebhookEventID,
329 )
330 var i ApplyUserSubscriptionSnapshotRow
331 err := row.Scan(
332 &i.UserID,
333 &i.Provider,
334 &i.StripeCustomerID,
335 &i.StripeSubscriptionID,
336 &i.StripeSubscriptionItemID,
337 &i.Plan,
338 &i.SubscriptionStatus,
339 &i.CurrentPeriodStart,
340 &i.CurrentPeriodEnd,
341 &i.CancelAtPeriodEnd,
342 &i.TrialEnd,
343 &i.PastDueAt,
344 &i.CanceledAt,
345 &i.LockedAt,
346 &i.LockReason,
347 &i.GraceUntil,
348 &i.LastWebhookEventID,
349 &i.CreatedAt,
350 &i.UpdatedAt,
351 &i.LastEventAt,
352 )
353 return i, err
354 }
355
356 const clearBillingLock = `-- name: ClearBillingLock :one
357 WITH state AS (
358 UPDATE org_billing_states
359 SET plan = CASE
360 WHEN subscription_status = 'canceled' THEN 'free'
361 ELSE plan
362 END,
363 subscription_status = CASE
364 WHEN subscription_status = 'canceled' THEN 'none'
365 ELSE subscription_status
366 END,
367 locked_at = NULL,
368 lock_reason = NULL,
369 grace_until = NULL,
370 updated_at = now()
371 WHERE org_id = $1
372 RETURNING org_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, billable_seats, seat_snapshot_at, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at
373 ), org_update AS (
374 UPDATE orgs
375 SET plan = state.plan,
376 updated_at = now()
377 FROM state
378 WHERE orgs.id = state.org_id
379 RETURNING orgs.id
380 )
381 SELECT org_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, billable_seats, seat_snapshot_at, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM state
382 `
383
384 type ClearBillingLockRow struct {
385 OrgID int64
386 Provider BillingProvider
387 StripeCustomerID pgtype.Text
388 StripeSubscriptionID pgtype.Text
389 StripeSubscriptionItemID pgtype.Text
390 Plan OrgPlan
391 SubscriptionStatus BillingSubscriptionStatus
392 BillableSeats int32
393 SeatSnapshotAt pgtype.Timestamptz
394 CurrentPeriodStart pgtype.Timestamptz
395 CurrentPeriodEnd pgtype.Timestamptz
396 CancelAtPeriodEnd bool
397 TrialEnd pgtype.Timestamptz
398 PastDueAt pgtype.Timestamptz
399 CanceledAt pgtype.Timestamptz
400 LockedAt pgtype.Timestamptz
401 LockReason NullBillingLockReason
402 GraceUntil pgtype.Timestamptz
403 LastWebhookEventID string
404 CreatedAt pgtype.Timestamptz
405 UpdatedAt pgtype.Timestamptz
406 LastEventAt pgtype.Timestamptz
407 }
408
409 func (q *Queries) ClearBillingLock(ctx context.Context, db DBTX, orgID int64) (ClearBillingLockRow, error) {
410 row := db.QueryRow(ctx, clearBillingLock, orgID)
411 var i ClearBillingLockRow
412 err := row.Scan(
413 &i.OrgID,
414 &i.Provider,
415 &i.StripeCustomerID,
416 &i.StripeSubscriptionID,
417 &i.StripeSubscriptionItemID,
418 &i.Plan,
419 &i.SubscriptionStatus,
420 &i.BillableSeats,
421 &i.SeatSnapshotAt,
422 &i.CurrentPeriodStart,
423 &i.CurrentPeriodEnd,
424 &i.CancelAtPeriodEnd,
425 &i.TrialEnd,
426 &i.PastDueAt,
427 &i.CanceledAt,
428 &i.LockedAt,
429 &i.LockReason,
430 &i.GraceUntil,
431 &i.LastWebhookEventID,
432 &i.CreatedAt,
433 &i.UpdatedAt,
434 &i.LastEventAt,
435 )
436 return i, err
437 }
438
439 const clearUserBillingLock = `-- name: ClearUserBillingLock :one
440 WITH state AS (
441 UPDATE user_billing_states
442 SET plan = CASE
443 WHEN subscription_status = 'canceled' THEN 'free'
444 ELSE plan
445 END,
446 subscription_status = CASE
447 WHEN subscription_status = 'canceled' THEN 'none'
448 ELSE subscription_status
449 END,
450 locked_at = NULL,
451 lock_reason = NULL,
452 grace_until = NULL,
453 updated_at = now()
454 WHERE user_id = $1
455 RETURNING user_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at
456 ), user_update AS (
457 UPDATE users
458 SET plan = state.plan,
459 updated_at = now()
460 FROM state
461 WHERE users.id = state.user_id
462 RETURNING users.id
463 )
464 SELECT user_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM state
465 `
466
467 type ClearUserBillingLockRow struct {
468 UserID int64
469 Provider BillingProvider
470 StripeCustomerID pgtype.Text
471 StripeSubscriptionID pgtype.Text
472 StripeSubscriptionItemID pgtype.Text
473 Plan UserPlan
474 SubscriptionStatus BillingSubscriptionStatus
475 CurrentPeriodStart pgtype.Timestamptz
476 CurrentPeriodEnd pgtype.Timestamptz
477 CancelAtPeriodEnd bool
478 TrialEnd pgtype.Timestamptz
479 PastDueAt pgtype.Timestamptz
480 CanceledAt pgtype.Timestamptz
481 LockedAt pgtype.Timestamptz
482 LockReason NullBillingLockReason
483 GraceUntil pgtype.Timestamptz
484 LastWebhookEventID string
485 CreatedAt pgtype.Timestamptz
486 UpdatedAt pgtype.Timestamptz
487 LastEventAt pgtype.Timestamptz
488 }
489
490 func (q *Queries) ClearUserBillingLock(ctx context.Context, db DBTX, userID int64) (ClearUserBillingLockRow, error) {
491 row := db.QueryRow(ctx, clearUserBillingLock, userID)
492 var i ClearUserBillingLockRow
493 err := row.Scan(
494 &i.UserID,
495 &i.Provider,
496 &i.StripeCustomerID,
497 &i.StripeSubscriptionID,
498 &i.StripeSubscriptionItemID,
499 &i.Plan,
500 &i.SubscriptionStatus,
501 &i.CurrentPeriodStart,
502 &i.CurrentPeriodEnd,
503 &i.CancelAtPeriodEnd,
504 &i.TrialEnd,
505 &i.PastDueAt,
506 &i.CanceledAt,
507 &i.LockedAt,
508 &i.LockReason,
509 &i.GraceUntil,
510 &i.LastWebhookEventID,
511 &i.CreatedAt,
512 &i.UpdatedAt,
513 &i.LastEventAt,
514 )
515 return i, err
516 }
517
518 const countBillableOrgMembers = `-- name: CountBillableOrgMembers :one
519 SELECT count(*)::integer
520 FROM org_members
521 WHERE org_id = $1
522 `
523
524 func (q *Queries) CountBillableOrgMembers(ctx context.Context, db DBTX, orgID int64) (int32, error) {
525 row := db.QueryRow(ctx, countBillableOrgMembers, orgID)
526 var column_1 int32
527 err := row.Scan(&column_1)
528 return column_1, err
529 }
530
531 const countPendingOrgInvitations = `-- name: CountPendingOrgInvitations :one
532 SELECT count(*)::integer
533 FROM org_invitations
534 WHERE org_id = $1
535 AND accepted_at IS NULL
536 AND declined_at IS NULL
537 AND canceled_at IS NULL
538 AND expires_at > now()
539 `
540
541 func (q *Queries) CountPendingOrgInvitations(ctx context.Context, db DBTX, orgID int64) (int32, error) {
542 row := db.QueryRow(ctx, countPendingOrgInvitations, orgID)
543 var column_1 int32
544 err := row.Scan(&column_1)
545 return column_1, err
546 }
547
548 const createSeatSnapshot = `-- name: CreateSeatSnapshot :one
549
550 WITH snapshot AS (
551 INSERT INTO billing_seat_snapshots (
552 org_id,
553 provider,
554 stripe_subscription_id,
555 active_members,
556 billable_seats,
557 source
558 )
559 VALUES (
560 $1::bigint,
561 'stripe',
562 $2::text,
563 $3::integer,
564 $4::integer,
565 $5::text
566 )
567 RETURNING id, org_id, provider, stripe_subscription_id, active_members, billable_seats, source, captured_at
568 ), state AS (
569 INSERT INTO org_billing_states (org_id, billable_seats, seat_snapshot_at)
570 SELECT org_id, billable_seats, captured_at FROM snapshot
571 ON CONFLICT (org_id) DO UPDATE
572 SET billable_seats = EXCLUDED.billable_seats,
573 seat_snapshot_at = EXCLUDED.seat_snapshot_at,
574 updated_at = now()
575 RETURNING org_id
576 )
577 SELECT id, org_id, provider, stripe_subscription_id, active_members, billable_seats, source, captured_at FROM snapshot
578 `
579
580 type CreateSeatSnapshotParams struct {
581 OrgID int64
582 StripeSubscriptionID pgtype.Text
583 ActiveMembers int32
584 BillableSeats int32
585 Source string
586 }
587
588 type CreateSeatSnapshotRow struct {
589 ID int64
590 OrgID int64
591 Provider BillingProvider
592 StripeSubscriptionID pgtype.Text
593 ActiveMembers int32
594 BillableSeats int32
595 Source string
596 CapturedAt pgtype.Timestamptz
597 }
598
599 // ─── billing_seat_snapshots ────────────────────────────────────────
600 func (q *Queries) CreateSeatSnapshot(ctx context.Context, db DBTX, arg CreateSeatSnapshotParams) (CreateSeatSnapshotRow, error) {
601 row := db.QueryRow(ctx, createSeatSnapshot,
602 arg.OrgID,
603 arg.StripeSubscriptionID,
604 arg.ActiveMembers,
605 arg.BillableSeats,
606 arg.Source,
607 )
608 var i CreateSeatSnapshotRow
609 err := row.Scan(
610 &i.ID,
611 &i.OrgID,
612 &i.Provider,
613 &i.StripeSubscriptionID,
614 &i.ActiveMembers,
615 &i.BillableSeats,
616 &i.Source,
617 &i.CapturedAt,
618 )
619 return i, err
620 }
621
622 const createWebhookEventReceipt = `-- name: CreateWebhookEventReceipt :one
623
624 INSERT INTO billing_webhook_events (
625 provider,
626 provider_event_id,
627 event_type,
628 api_version,
629 payload
630 )
631 VALUES (
632 'stripe',
633 $1::text,
634 $2::text,
635 $3::text,
636 $4::jsonb
637 )
638 ON CONFLICT (provider, provider_event_id) DO NOTHING
639 RETURNING id, provider, provider_event_id, event_type, api_version, payload, received_at, processed_at, process_error, processing_attempts, subject_kind, subject_id
640 `
641
642 type CreateWebhookEventReceiptParams struct {
643 ProviderEventID string
644 EventType string
645 ApiVersion string
646 Payload []byte
647 }
648
649 // ─── billing_webhook_events ────────────────────────────────────────
650 func (q *Queries) CreateWebhookEventReceipt(ctx context.Context, db DBTX, arg CreateWebhookEventReceiptParams) (BillingWebhookEvent, error) {
651 row := db.QueryRow(ctx, createWebhookEventReceipt,
652 arg.ProviderEventID,
653 arg.EventType,
654 arg.ApiVersion,
655 arg.Payload,
656 )
657 var i BillingWebhookEvent
658 err := row.Scan(
659 &i.ID,
660 &i.Provider,
661 &i.ProviderEventID,
662 &i.EventType,
663 &i.ApiVersion,
664 &i.Payload,
665 &i.ReceivedAt,
666 &i.ProcessedAt,
667 &i.ProcessError,
668 &i.ProcessingAttempts,
669 &i.SubjectKind,
670 &i.SubjectID,
671 )
672 return i, err
673 }
674
675 const getOrgBillingState = `-- name: GetOrgBillingState :one
676
677
678 SELECT org_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, billable_seats, seat_snapshot_at, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM org_billing_states WHERE org_id = $1
679 `
680
681 // SPDX-License-Identifier: AGPL-3.0-or-later
682 // ─── org_billing_states ────────────────────────────────────────────
683 func (q *Queries) GetOrgBillingState(ctx context.Context, db DBTX, orgID int64) (OrgBillingState, error) {
684 row := db.QueryRow(ctx, getOrgBillingState, orgID)
685 var i OrgBillingState
686 err := row.Scan(
687 &i.OrgID,
688 &i.Provider,
689 &i.StripeCustomerID,
690 &i.StripeSubscriptionID,
691 &i.StripeSubscriptionItemID,
692 &i.Plan,
693 &i.SubscriptionStatus,
694 &i.BillableSeats,
695 &i.SeatSnapshotAt,
696 &i.CurrentPeriodStart,
697 &i.CurrentPeriodEnd,
698 &i.CancelAtPeriodEnd,
699 &i.TrialEnd,
700 &i.PastDueAt,
701 &i.CanceledAt,
702 &i.LockedAt,
703 &i.LockReason,
704 &i.GraceUntil,
705 &i.LastWebhookEventID,
706 &i.CreatedAt,
707 &i.UpdatedAt,
708 &i.LastEventAt,
709 )
710 return i, err
711 }
712
713 const getOrgBillingStateByStripeCustomer = `-- name: GetOrgBillingStateByStripeCustomer :one
714 SELECT org_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, billable_seats, seat_snapshot_at, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM org_billing_states
715 WHERE provider = 'stripe'
716 AND stripe_customer_id = $1
717 `
718
719 func (q *Queries) GetOrgBillingStateByStripeCustomer(ctx context.Context, db DBTX, stripeCustomerID pgtype.Text) (OrgBillingState, error) {
720 row := db.QueryRow(ctx, getOrgBillingStateByStripeCustomer, stripeCustomerID)
721 var i OrgBillingState
722 err := row.Scan(
723 &i.OrgID,
724 &i.Provider,
725 &i.StripeCustomerID,
726 &i.StripeSubscriptionID,
727 &i.StripeSubscriptionItemID,
728 &i.Plan,
729 &i.SubscriptionStatus,
730 &i.BillableSeats,
731 &i.SeatSnapshotAt,
732 &i.CurrentPeriodStart,
733 &i.CurrentPeriodEnd,
734 &i.CancelAtPeriodEnd,
735 &i.TrialEnd,
736 &i.PastDueAt,
737 &i.CanceledAt,
738 &i.LockedAt,
739 &i.LockReason,
740 &i.GraceUntil,
741 &i.LastWebhookEventID,
742 &i.CreatedAt,
743 &i.UpdatedAt,
744 &i.LastEventAt,
745 )
746 return i, err
747 }
748
749 const getOrgBillingStateByStripeSubscription = `-- name: GetOrgBillingStateByStripeSubscription :one
750 SELECT org_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, billable_seats, seat_snapshot_at, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM org_billing_states
751 WHERE provider = 'stripe'
752 AND stripe_subscription_id = $1
753 `
754
755 func (q *Queries) GetOrgBillingStateByStripeSubscription(ctx context.Context, db DBTX, stripeSubscriptionID pgtype.Text) (OrgBillingState, error) {
756 row := db.QueryRow(ctx, getOrgBillingStateByStripeSubscription, stripeSubscriptionID)
757 var i OrgBillingState
758 err := row.Scan(
759 &i.OrgID,
760 &i.Provider,
761 &i.StripeCustomerID,
762 &i.StripeSubscriptionID,
763 &i.StripeSubscriptionItemID,
764 &i.Plan,
765 &i.SubscriptionStatus,
766 &i.BillableSeats,
767 &i.SeatSnapshotAt,
768 &i.CurrentPeriodStart,
769 &i.CurrentPeriodEnd,
770 &i.CancelAtPeriodEnd,
771 &i.TrialEnd,
772 &i.PastDueAt,
773 &i.CanceledAt,
774 &i.LockedAt,
775 &i.LockReason,
776 &i.GraceUntil,
777 &i.LastWebhookEventID,
778 &i.CreatedAt,
779 &i.UpdatedAt,
780 &i.LastEventAt,
781 )
782 return i, err
783 }
784
785 const getUserBillingState = `-- name: GetUserBillingState :one
786
787 SELECT user_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM user_billing_states WHERE user_id = $1
788 `
789
790 // ─── user_billing_states (PRO03) ──────────────────────────────────
791 func (q *Queries) GetUserBillingState(ctx context.Context, db DBTX, userID int64) (UserBillingState, error) {
792 row := db.QueryRow(ctx, getUserBillingState, userID)
793 var i UserBillingState
794 err := row.Scan(
795 &i.UserID,
796 &i.Provider,
797 &i.StripeCustomerID,
798 &i.StripeSubscriptionID,
799 &i.StripeSubscriptionItemID,
800 &i.Plan,
801 &i.SubscriptionStatus,
802 &i.CurrentPeriodStart,
803 &i.CurrentPeriodEnd,
804 &i.CancelAtPeriodEnd,
805 &i.TrialEnd,
806 &i.PastDueAt,
807 &i.CanceledAt,
808 &i.LockedAt,
809 &i.LockReason,
810 &i.GraceUntil,
811 &i.LastWebhookEventID,
812 &i.CreatedAt,
813 &i.UpdatedAt,
814 &i.LastEventAt,
815 )
816 return i, err
817 }
818
819 const getUserBillingStateByStripeCustomer = `-- name: GetUserBillingStateByStripeCustomer :one
820 SELECT user_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM user_billing_states
821 WHERE provider = 'stripe'
822 AND stripe_customer_id = $1
823 `
824
825 func (q *Queries) GetUserBillingStateByStripeCustomer(ctx context.Context, db DBTX, stripeCustomerID pgtype.Text) (UserBillingState, error) {
826 row := db.QueryRow(ctx, getUserBillingStateByStripeCustomer, stripeCustomerID)
827 var i UserBillingState
828 err := row.Scan(
829 &i.UserID,
830 &i.Provider,
831 &i.StripeCustomerID,
832 &i.StripeSubscriptionID,
833 &i.StripeSubscriptionItemID,
834 &i.Plan,
835 &i.SubscriptionStatus,
836 &i.CurrentPeriodStart,
837 &i.CurrentPeriodEnd,
838 &i.CancelAtPeriodEnd,
839 &i.TrialEnd,
840 &i.PastDueAt,
841 &i.CanceledAt,
842 &i.LockedAt,
843 &i.LockReason,
844 &i.GraceUntil,
845 &i.LastWebhookEventID,
846 &i.CreatedAt,
847 &i.UpdatedAt,
848 &i.LastEventAt,
849 )
850 return i, err
851 }
852
853 const getUserBillingStateByStripeSubscription = `-- name: GetUserBillingStateByStripeSubscription :one
854 SELECT user_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM user_billing_states
855 WHERE provider = 'stripe'
856 AND stripe_subscription_id = $1
857 `
858
859 func (q *Queries) GetUserBillingStateByStripeSubscription(ctx context.Context, db DBTX, stripeSubscriptionID pgtype.Text) (UserBillingState, error) {
860 row := db.QueryRow(ctx, getUserBillingStateByStripeSubscription, stripeSubscriptionID)
861 var i UserBillingState
862 err := row.Scan(
863 &i.UserID,
864 &i.Provider,
865 &i.StripeCustomerID,
866 &i.StripeSubscriptionID,
867 &i.StripeSubscriptionItemID,
868 &i.Plan,
869 &i.SubscriptionStatus,
870 &i.CurrentPeriodStart,
871 &i.CurrentPeriodEnd,
872 &i.CancelAtPeriodEnd,
873 &i.TrialEnd,
874 &i.PastDueAt,
875 &i.CanceledAt,
876 &i.LockedAt,
877 &i.LockReason,
878 &i.GraceUntil,
879 &i.LastWebhookEventID,
880 &i.CreatedAt,
881 &i.UpdatedAt,
882 &i.LastEventAt,
883 )
884 return i, err
885 }
886
887 const getWebhookEventReceipt = `-- name: GetWebhookEventReceipt :one
888 SELECT id, provider, provider_event_id, event_type, api_version, payload, received_at, processed_at, process_error, processing_attempts, subject_kind, subject_id FROM billing_webhook_events
889 WHERE provider = 'stripe'
890 AND provider_event_id = $1
891 `
892
893 func (q *Queries) GetWebhookEventReceipt(ctx context.Context, db DBTX, providerEventID string) (BillingWebhookEvent, error) {
894 row := db.QueryRow(ctx, getWebhookEventReceipt, providerEventID)
895 var i BillingWebhookEvent
896 err := row.Scan(
897 &i.ID,
898 &i.Provider,
899 &i.ProviderEventID,
900 &i.EventType,
901 &i.ApiVersion,
902 &i.Payload,
903 &i.ReceivedAt,
904 &i.ProcessedAt,
905 &i.ProcessError,
906 &i.ProcessingAttempts,
907 &i.SubjectKind,
908 &i.SubjectID,
909 )
910 return i, err
911 }
912
913 const isOrgBillingEventStale = `-- name: IsOrgBillingEventStale :one
914 SELECT COALESCE(last_event_at > $1::timestamptz, false)::boolean AS stale
915 FROM org_billing_states
916 WHERE org_id = $2::bigint
917 `
918
919 type IsOrgBillingEventStaleParams struct {
920 EventAt pgtype.Timestamptz
921 OrgID int64
922 }
923
924 // PRO08 D4: returns true when an incoming Stripe event's timestamp
925 // is older than the last event we've already applied for this org.
926 // Stripe doesn't guarantee delivery order across retries; without
927 // this guard a stale `subscription.updated[active]` could re-activate
928 // a canceled subscription. Returns false when no prior event has
929 // been recorded (last_event_at IS NULL) — the first event is never
930 // stale.
931 func (q *Queries) IsOrgBillingEventStale(ctx context.Context, db DBTX, arg IsOrgBillingEventStaleParams) (bool, error) {
932 row := db.QueryRow(ctx, isOrgBillingEventStale, arg.EventAt, arg.OrgID)
933 var stale bool
934 err := row.Scan(&stale)
935 return stale, err
936 }
937
938 const isUserBillingEventStale = `-- name: IsUserBillingEventStale :one
939 SELECT COALESCE(last_event_at > $1::timestamptz, false)::boolean AS stale
940 FROM user_billing_states
941 WHERE user_id = $2::bigint
942 `
943
944 type IsUserBillingEventStaleParams struct {
945 EventAt pgtype.Timestamptz
946 UserID int64
947 }
948
949 func (q *Queries) IsUserBillingEventStale(ctx context.Context, db DBTX, arg IsUserBillingEventStaleParams) (bool, error) {
950 row := db.QueryRow(ctx, isUserBillingEventStale, arg.EventAt, arg.UserID)
951 var stale bool
952 err := row.Scan(&stale)
953 return stale, err
954 }
955
956 const listFailedWebhookEvents = `-- name: ListFailedWebhookEvents :many
957 SELECT id, provider, provider_event_id, event_type, api_version,
958 received_at, processed_at, processing_attempts, process_error,
959 subject_kind, subject_id
960 FROM billing_webhook_events
961 WHERE provider = 'stripe'
962 AND (
963 process_error <> ''
964 OR (processed_at IS NULL AND processing_attempts > 0)
965 )
966 ORDER BY received_at DESC
967 LIMIT $1
968 `
969
970 type ListFailedWebhookEventsRow struct {
971 ID int64
972 Provider BillingProvider
973 ProviderEventID string
974 EventType string
975 ApiVersion string
976 ReceivedAt pgtype.Timestamptz
977 ProcessedAt pgtype.Timestamptz
978 ProcessingAttempts int32
979 ProcessError string
980 SubjectKind NullBillingSubjectKind
981 SubjectID pgtype.Int8
982 }
983
984 // Operator query for "events we received but failed to process."
985 // A row is "failed" when it has a non-empty process_error OR when
986 // it has never been processed (processed_at NULL) and has at least
987 // one processing attempt. Rows that are merely new and untouched
988 // (attempts=0, processed_at NULL, no error) are excluded.
989 func (q *Queries) ListFailedWebhookEvents(ctx context.Context, db DBTX, limit int32) ([]ListFailedWebhookEventsRow, error) {
990 rows, err := db.Query(ctx, listFailedWebhookEvents, limit)
991 if err != nil {
992 return nil, err
993 }
994 defer rows.Close()
995 items := []ListFailedWebhookEventsRow{}
996 for rows.Next() {
997 var i ListFailedWebhookEventsRow
998 if err := rows.Scan(
999 &i.ID,
1000 &i.Provider,
1001 &i.ProviderEventID,
1002 &i.EventType,
1003 &i.ApiVersion,
1004 &i.ReceivedAt,
1005 &i.ProcessedAt,
1006 &i.ProcessingAttempts,
1007 &i.ProcessError,
1008 &i.SubjectKind,
1009 &i.SubjectID,
1010 ); err != nil {
1011 return nil, err
1012 }
1013 items = append(items, i)
1014 }
1015 if err := rows.Err(); err != nil {
1016 return nil, err
1017 }
1018 return items, nil
1019 }
1020
1021 const listInvoicesForOrg = `-- name: ListInvoicesForOrg :many
1022 SELECT id, org_id, provider, stripe_invoice_id, stripe_customer_id, stripe_subscription_id, status, number, currency, amount_due_cents, amount_paid_cents, amount_remaining_cents, hosted_invoice_url, invoice_pdf_url, period_start, period_end, due_at, paid_at, voided_at, created_at, updated_at, subject_kind, subject_id, refunded_at FROM billing_invoices
1023 WHERE subject_kind = 'org' AND subject_id = $1
1024 ORDER BY created_at DESC, id DESC
1025 LIMIT $2
1026 `
1027
1028 type ListInvoicesForOrgParams struct {
1029 SubjectID int64
1030 Limit int32
1031 }
1032
1033 // PRO03: filters on the polymorphic subject columns so the index
1034 // billing_invoices_subject_created_idx services this query. The
1035 // legacy `org_id` column is kept populated by UpsertInvoice for the
1036 // transitional window; this query no longer reads it.
1037 func (q *Queries) ListInvoicesForOrg(ctx context.Context, db DBTX, arg ListInvoicesForOrgParams) ([]BillingInvoice, error) {
1038 rows, err := db.Query(ctx, listInvoicesForOrg, arg.SubjectID, arg.Limit)
1039 if err != nil {
1040 return nil, err
1041 }
1042 defer rows.Close()
1043 items := []BillingInvoice{}
1044 for rows.Next() {
1045 var i BillingInvoice
1046 if err := rows.Scan(
1047 &i.ID,
1048 &i.OrgID,
1049 &i.Provider,
1050 &i.StripeInvoiceID,
1051 &i.StripeCustomerID,
1052 &i.StripeSubscriptionID,
1053 &i.Status,
1054 &i.Number,
1055 &i.Currency,
1056 &i.AmountDueCents,
1057 &i.AmountPaidCents,
1058 &i.AmountRemainingCents,
1059 &i.HostedInvoiceUrl,
1060 &i.InvoicePdfUrl,
1061 &i.PeriodStart,
1062 &i.PeriodEnd,
1063 &i.DueAt,
1064 &i.PaidAt,
1065 &i.VoidedAt,
1066 &i.CreatedAt,
1067 &i.UpdatedAt,
1068 &i.SubjectKind,
1069 &i.SubjectID,
1070 &i.RefundedAt,
1071 ); err != nil {
1072 return nil, err
1073 }
1074 items = append(items, i)
1075 }
1076 if err := rows.Err(); err != nil {
1077 return nil, err
1078 }
1079 return items, nil
1080 }
1081
1082 const listInvoicesForSubject = `-- name: ListInvoicesForSubject :many
1083 SELECT id, org_id, provider, stripe_invoice_id, stripe_customer_id, stripe_subscription_id, status, number, currency, amount_due_cents, amount_paid_cents, amount_remaining_cents, hosted_invoice_url, invoice_pdf_url, period_start, period_end, due_at, paid_at, voided_at, created_at, updated_at, subject_kind, subject_id, refunded_at FROM billing_invoices
1084 WHERE subject_kind = $1::billing_subject_kind
1085 AND subject_id = $2::bigint
1086 ORDER BY created_at DESC, id DESC
1087 LIMIT $3::integer
1088 `
1089
1090 type ListInvoicesForSubjectParams struct {
1091 SubjectKind BillingSubjectKind
1092 SubjectID int64
1093 Lim int32
1094 }
1095
1096 // Polymorphic invoice listing for PRO04+ callers. The org-flavored
1097 // ListInvoicesForOrg above is the same query with subject_kind
1098 // hard-coded; this surface lets a user-side caller pass kind='user'
1099 // without forking the helper.
1100 func (q *Queries) ListInvoicesForSubject(ctx context.Context, db DBTX, arg ListInvoicesForSubjectParams) ([]BillingInvoice, error) {
1101 rows, err := db.Query(ctx, listInvoicesForSubject, arg.SubjectKind, arg.SubjectID, arg.Lim)
1102 if err != nil {
1103 return nil, err
1104 }
1105 defer rows.Close()
1106 items := []BillingInvoice{}
1107 for rows.Next() {
1108 var i BillingInvoice
1109 if err := rows.Scan(
1110 &i.ID,
1111 &i.OrgID,
1112 &i.Provider,
1113 &i.StripeInvoiceID,
1114 &i.StripeCustomerID,
1115 &i.StripeSubscriptionID,
1116 &i.Status,
1117 &i.Number,
1118 &i.Currency,
1119 &i.AmountDueCents,
1120 &i.AmountPaidCents,
1121 &i.AmountRemainingCents,
1122 &i.HostedInvoiceUrl,
1123 &i.InvoicePdfUrl,
1124 &i.PeriodStart,
1125 &i.PeriodEnd,
1126 &i.DueAt,
1127 &i.PaidAt,
1128 &i.VoidedAt,
1129 &i.CreatedAt,
1130 &i.UpdatedAt,
1131 &i.SubjectKind,
1132 &i.SubjectID,
1133 &i.RefundedAt,
1134 ); err != nil {
1135 return nil, err
1136 }
1137 items = append(items, i)
1138 }
1139 if err := rows.Err(); err != nil {
1140 return nil, err
1141 }
1142 return items, nil
1143 }
1144
1145 const listSeatSnapshotsForOrg = `-- name: ListSeatSnapshotsForOrg :many
1146 SELECT id, org_id, provider, stripe_subscription_id, active_members, billable_seats, source, captured_at FROM billing_seat_snapshots
1147 WHERE org_id = $1
1148 ORDER BY captured_at DESC, id DESC
1149 LIMIT $2
1150 `
1151
1152 type ListSeatSnapshotsForOrgParams struct {
1153 OrgID int64
1154 Limit int32
1155 }
1156
1157 func (q *Queries) ListSeatSnapshotsForOrg(ctx context.Context, db DBTX, arg ListSeatSnapshotsForOrgParams) ([]BillingSeatSnapshot, error) {
1158 rows, err := db.Query(ctx, listSeatSnapshotsForOrg, arg.OrgID, arg.Limit)
1159 if err != nil {
1160 return nil, err
1161 }
1162 defer rows.Close()
1163 items := []BillingSeatSnapshot{}
1164 for rows.Next() {
1165 var i BillingSeatSnapshot
1166 if err := rows.Scan(
1167 &i.ID,
1168 &i.OrgID,
1169 &i.Provider,
1170 &i.StripeSubscriptionID,
1171 &i.ActiveMembers,
1172 &i.BillableSeats,
1173 &i.Source,
1174 &i.CapturedAt,
1175 ); err != nil {
1176 return nil, err
1177 }
1178 items = append(items, i)
1179 }
1180 if err := rows.Err(); err != nil {
1181 return nil, err
1182 }
1183 return items, nil
1184 }
1185
1186 const markCanceled = `-- name: MarkCanceled :one
1187 WITH state AS (
1188 UPDATE org_billing_states
1189 SET plan = 'free',
1190 subscription_status = 'canceled',
1191 canceled_at = COALESCE(canceled_at, now()),
1192 locked_at = now(),
1193 lock_reason = 'canceled',
1194 grace_until = NULL,
1195 cancel_at_period_end = false,
1196 last_webhook_event_id = $1::text,
1197 updated_at = now()
1198 WHERE org_id = $2::bigint
1199 RETURNING org_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, billable_seats, seat_snapshot_at, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at
1200 ), org_update AS (
1201 UPDATE orgs
1202 SET plan = 'free',
1203 updated_at = now()
1204 WHERE id = $2::bigint
1205 RETURNING id
1206 )
1207 SELECT org_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, billable_seats, seat_snapshot_at, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM state
1208 `
1209
1210 type MarkCanceledParams struct {
1211 LastWebhookEventID string
1212 OrgID int64
1213 }
1214
1215 type MarkCanceledRow struct {
1216 OrgID int64
1217 Provider BillingProvider
1218 StripeCustomerID pgtype.Text
1219 StripeSubscriptionID pgtype.Text
1220 StripeSubscriptionItemID pgtype.Text
1221 Plan OrgPlan
1222 SubscriptionStatus BillingSubscriptionStatus
1223 BillableSeats int32
1224 SeatSnapshotAt pgtype.Timestamptz
1225 CurrentPeriodStart pgtype.Timestamptz
1226 CurrentPeriodEnd pgtype.Timestamptz
1227 CancelAtPeriodEnd bool
1228 TrialEnd pgtype.Timestamptz
1229 PastDueAt pgtype.Timestamptz
1230 CanceledAt pgtype.Timestamptz
1231 LockedAt pgtype.Timestamptz
1232 LockReason NullBillingLockReason
1233 GraceUntil pgtype.Timestamptz
1234 LastWebhookEventID string
1235 CreatedAt pgtype.Timestamptz
1236 UpdatedAt pgtype.Timestamptz
1237 LastEventAt pgtype.Timestamptz
1238 }
1239
1240 func (q *Queries) MarkCanceled(ctx context.Context, db DBTX, arg MarkCanceledParams) (MarkCanceledRow, error) {
1241 row := db.QueryRow(ctx, markCanceled, arg.LastWebhookEventID, arg.OrgID)
1242 var i MarkCanceledRow
1243 err := row.Scan(
1244 &i.OrgID,
1245 &i.Provider,
1246 &i.StripeCustomerID,
1247 &i.StripeSubscriptionID,
1248 &i.StripeSubscriptionItemID,
1249 &i.Plan,
1250 &i.SubscriptionStatus,
1251 &i.BillableSeats,
1252 &i.SeatSnapshotAt,
1253 &i.CurrentPeriodStart,
1254 &i.CurrentPeriodEnd,
1255 &i.CancelAtPeriodEnd,
1256 &i.TrialEnd,
1257 &i.PastDueAt,
1258 &i.CanceledAt,
1259 &i.LockedAt,
1260 &i.LockReason,
1261 &i.GraceUntil,
1262 &i.LastWebhookEventID,
1263 &i.CreatedAt,
1264 &i.UpdatedAt,
1265 &i.LastEventAt,
1266 )
1267 return i, err
1268 }
1269
1270 const markInvoiceRefunded = `-- name: MarkInvoiceRefunded :one
1271 UPDATE billing_invoices
1272 SET status = 'refunded',
1273 refunded_at = COALESCE(refunded_at, now()),
1274 updated_at = now()
1275 WHERE provider = 'stripe'
1276 AND stripe_invoice_id = $1::text
1277 RETURNING id, org_id, provider, stripe_invoice_id, stripe_customer_id, stripe_subscription_id, status, number, currency, amount_due_cents, amount_paid_cents, amount_remaining_cents, hosted_invoice_url, invoice_pdf_url, period_start, period_end, due_at, paid_at, voided_at, created_at, updated_at, subject_kind, subject_id, refunded_at
1278 `
1279
1280 // PRO08 D2: surface a Stripe-side refund in shithub. Stripe leaves
1281 // the invoice.status='paid' after a refund and fires a charge.refunded
1282 // event; this helper flips the shithub-side row to 'refunded' so the
1283 // billing settings UI shows the refunded state.
1284 //
1285 // A NULL refunded_at means "no refund seen"; the value is set on the
1286 // first call and preserved on subsequent calls (refund partial → full
1287 // doesn't move the wall-clock timestamp).
1288 func (q *Queries) MarkInvoiceRefunded(ctx context.Context, db DBTX, stripeInvoiceID string) (BillingInvoice, error) {
1289 row := db.QueryRow(ctx, markInvoiceRefunded, stripeInvoiceID)
1290 var i BillingInvoice
1291 err := row.Scan(
1292 &i.ID,
1293 &i.OrgID,
1294 &i.Provider,
1295 &i.StripeInvoiceID,
1296 &i.StripeCustomerID,
1297 &i.StripeSubscriptionID,
1298 &i.Status,
1299 &i.Number,
1300 &i.Currency,
1301 &i.AmountDueCents,
1302 &i.AmountPaidCents,
1303 &i.AmountRemainingCents,
1304 &i.HostedInvoiceUrl,
1305 &i.InvoicePdfUrl,
1306 &i.PeriodStart,
1307 &i.PeriodEnd,
1308 &i.DueAt,
1309 &i.PaidAt,
1310 &i.VoidedAt,
1311 &i.CreatedAt,
1312 &i.UpdatedAt,
1313 &i.SubjectKind,
1314 &i.SubjectID,
1315 &i.RefundedAt,
1316 )
1317 return i, err
1318 }
1319
1320 const markPastDue = `-- name: MarkPastDue :one
1321 UPDATE org_billing_states
1322 SET subscription_status = 'past_due',
1323 past_due_at = COALESCE(past_due_at, now()),
1324 locked_at = now(),
1325 lock_reason = 'past_due',
1326 grace_until = $1::timestamptz,
1327 last_webhook_event_id = $2::text,
1328 updated_at = now()
1329 WHERE org_id = $3::bigint
1330 RETURNING org_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, billable_seats, seat_snapshot_at, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at
1331 `
1332
1333 type MarkPastDueParams struct {
1334 GraceUntil pgtype.Timestamptz
1335 LastWebhookEventID string
1336 OrgID int64
1337 }
1338
1339 func (q *Queries) MarkPastDue(ctx context.Context, db DBTX, arg MarkPastDueParams) (OrgBillingState, error) {
1340 row := db.QueryRow(ctx, markPastDue, arg.GraceUntil, arg.LastWebhookEventID, arg.OrgID)
1341 var i OrgBillingState
1342 err := row.Scan(
1343 &i.OrgID,
1344 &i.Provider,
1345 &i.StripeCustomerID,
1346 &i.StripeSubscriptionID,
1347 &i.StripeSubscriptionItemID,
1348 &i.Plan,
1349 &i.SubscriptionStatus,
1350 &i.BillableSeats,
1351 &i.SeatSnapshotAt,
1352 &i.CurrentPeriodStart,
1353 &i.CurrentPeriodEnd,
1354 &i.CancelAtPeriodEnd,
1355 &i.TrialEnd,
1356 &i.PastDueAt,
1357 &i.CanceledAt,
1358 &i.LockedAt,
1359 &i.LockReason,
1360 &i.GraceUntil,
1361 &i.LastWebhookEventID,
1362 &i.CreatedAt,
1363 &i.UpdatedAt,
1364 &i.LastEventAt,
1365 )
1366 return i, err
1367 }
1368
1369 const markPaymentSucceeded = `-- name: MarkPaymentSucceeded :one
1370 WITH state AS (
1371 UPDATE org_billing_states
1372 SET plan = CASE
1373 WHEN subscription_status IN ('past_due', 'unpaid', 'incomplete') THEN 'team'
1374 ELSE plan
1375 END,
1376 subscription_status = CASE
1377 WHEN subscription_status IN ('past_due', 'unpaid', 'incomplete') THEN 'active'
1378 ELSE subscription_status
1379 END,
1380 past_due_at = CASE
1381 WHEN subscription_status IN ('past_due', 'unpaid', 'incomplete') THEN NULL
1382 ELSE past_due_at
1383 END,
1384 locked_at = NULL,
1385 lock_reason = NULL,
1386 grace_until = NULL,
1387 last_webhook_event_id = $1::text,
1388 updated_at = now()
1389 WHERE org_id = $2::bigint
1390 RETURNING org_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, billable_seats, seat_snapshot_at, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at
1391 ), org_update AS (
1392 UPDATE orgs
1393 SET plan = state.plan,
1394 updated_at = now()
1395 FROM state
1396 WHERE orgs.id = state.org_id
1397 RETURNING orgs.id
1398 )
1399 SELECT org_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, billable_seats, seat_snapshot_at, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM state
1400 `
1401
1402 type MarkPaymentSucceededParams struct {
1403 LastWebhookEventID string
1404 OrgID int64
1405 }
1406
1407 type MarkPaymentSucceededRow struct {
1408 OrgID int64
1409 Provider BillingProvider
1410 StripeCustomerID pgtype.Text
1411 StripeSubscriptionID pgtype.Text
1412 StripeSubscriptionItemID pgtype.Text
1413 Plan OrgPlan
1414 SubscriptionStatus BillingSubscriptionStatus
1415 BillableSeats int32
1416 SeatSnapshotAt pgtype.Timestamptz
1417 CurrentPeriodStart pgtype.Timestamptz
1418 CurrentPeriodEnd pgtype.Timestamptz
1419 CancelAtPeriodEnd bool
1420 TrialEnd pgtype.Timestamptz
1421 PastDueAt pgtype.Timestamptz
1422 CanceledAt pgtype.Timestamptz
1423 LockedAt pgtype.Timestamptz
1424 LockReason NullBillingLockReason
1425 GraceUntil pgtype.Timestamptz
1426 LastWebhookEventID string
1427 CreatedAt pgtype.Timestamptz
1428 UpdatedAt pgtype.Timestamptz
1429 LastEventAt pgtype.Timestamptz
1430 }
1431
1432 func (q *Queries) MarkPaymentSucceeded(ctx context.Context, db DBTX, arg MarkPaymentSucceededParams) (MarkPaymentSucceededRow, error) {
1433 row := db.QueryRow(ctx, markPaymentSucceeded, arg.LastWebhookEventID, arg.OrgID)
1434 var i MarkPaymentSucceededRow
1435 err := row.Scan(
1436 &i.OrgID,
1437 &i.Provider,
1438 &i.StripeCustomerID,
1439 &i.StripeSubscriptionID,
1440 &i.StripeSubscriptionItemID,
1441 &i.Plan,
1442 &i.SubscriptionStatus,
1443 &i.BillableSeats,
1444 &i.SeatSnapshotAt,
1445 &i.CurrentPeriodStart,
1446 &i.CurrentPeriodEnd,
1447 &i.CancelAtPeriodEnd,
1448 &i.TrialEnd,
1449 &i.PastDueAt,
1450 &i.CanceledAt,
1451 &i.LockedAt,
1452 &i.LockReason,
1453 &i.GraceUntil,
1454 &i.LastWebhookEventID,
1455 &i.CreatedAt,
1456 &i.UpdatedAt,
1457 &i.LastEventAt,
1458 )
1459 return i, err
1460 }
1461
1462 const markUserCanceled = `-- name: MarkUserCanceled :one
1463 WITH state AS (
1464 UPDATE user_billing_states
1465 SET plan = 'free',
1466 subscription_status = 'canceled',
1467 canceled_at = COALESCE(canceled_at, now()),
1468 locked_at = now(),
1469 lock_reason = 'canceled',
1470 grace_until = NULL,
1471 cancel_at_period_end = false,
1472 last_webhook_event_id = $1::text,
1473 updated_at = now()
1474 WHERE user_id = $2::bigint
1475 RETURNING user_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at
1476 ), user_update AS (
1477 UPDATE users
1478 SET plan = 'free',
1479 updated_at = now()
1480 WHERE id = $2::bigint
1481 RETURNING id
1482 )
1483 SELECT user_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM state
1484 `
1485
1486 type MarkUserCanceledParams struct {
1487 LastWebhookEventID string
1488 UserID int64
1489 }
1490
1491 type MarkUserCanceledRow struct {
1492 UserID int64
1493 Provider BillingProvider
1494 StripeCustomerID pgtype.Text
1495 StripeSubscriptionID pgtype.Text
1496 StripeSubscriptionItemID pgtype.Text
1497 Plan UserPlan
1498 SubscriptionStatus BillingSubscriptionStatus
1499 CurrentPeriodStart pgtype.Timestamptz
1500 CurrentPeriodEnd pgtype.Timestamptz
1501 CancelAtPeriodEnd bool
1502 TrialEnd pgtype.Timestamptz
1503 PastDueAt pgtype.Timestamptz
1504 CanceledAt pgtype.Timestamptz
1505 LockedAt pgtype.Timestamptz
1506 LockReason NullBillingLockReason
1507 GraceUntil pgtype.Timestamptz
1508 LastWebhookEventID string
1509 CreatedAt pgtype.Timestamptz
1510 UpdatedAt pgtype.Timestamptz
1511 LastEventAt pgtype.Timestamptz
1512 }
1513
1514 func (q *Queries) MarkUserCanceled(ctx context.Context, db DBTX, arg MarkUserCanceledParams) (MarkUserCanceledRow, error) {
1515 row := db.QueryRow(ctx, markUserCanceled, arg.LastWebhookEventID, arg.UserID)
1516 var i MarkUserCanceledRow
1517 err := row.Scan(
1518 &i.UserID,
1519 &i.Provider,
1520 &i.StripeCustomerID,
1521 &i.StripeSubscriptionID,
1522 &i.StripeSubscriptionItemID,
1523 &i.Plan,
1524 &i.SubscriptionStatus,
1525 &i.CurrentPeriodStart,
1526 &i.CurrentPeriodEnd,
1527 &i.CancelAtPeriodEnd,
1528 &i.TrialEnd,
1529 &i.PastDueAt,
1530 &i.CanceledAt,
1531 &i.LockedAt,
1532 &i.LockReason,
1533 &i.GraceUntil,
1534 &i.LastWebhookEventID,
1535 &i.CreatedAt,
1536 &i.UpdatedAt,
1537 &i.LastEventAt,
1538 )
1539 return i, err
1540 }
1541
1542 const markUserPastDue = `-- name: MarkUserPastDue :one
1543 UPDATE user_billing_states
1544 SET subscription_status = 'past_due',
1545 past_due_at = COALESCE(past_due_at, now()),
1546 locked_at = now(),
1547 lock_reason = 'past_due',
1548 grace_until = $1::timestamptz,
1549 last_webhook_event_id = $2::text,
1550 updated_at = now()
1551 WHERE user_id = $3::bigint
1552 RETURNING user_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at
1553 `
1554
1555 type MarkUserPastDueParams struct {
1556 GraceUntil pgtype.Timestamptz
1557 LastWebhookEventID string
1558 UserID int64
1559 }
1560
1561 func (q *Queries) MarkUserPastDue(ctx context.Context, db DBTX, arg MarkUserPastDueParams) (UserBillingState, error) {
1562 row := db.QueryRow(ctx, markUserPastDue, arg.GraceUntil, arg.LastWebhookEventID, arg.UserID)
1563 var i UserBillingState
1564 err := row.Scan(
1565 &i.UserID,
1566 &i.Provider,
1567 &i.StripeCustomerID,
1568 &i.StripeSubscriptionID,
1569 &i.StripeSubscriptionItemID,
1570 &i.Plan,
1571 &i.SubscriptionStatus,
1572 &i.CurrentPeriodStart,
1573 &i.CurrentPeriodEnd,
1574 &i.CancelAtPeriodEnd,
1575 &i.TrialEnd,
1576 &i.PastDueAt,
1577 &i.CanceledAt,
1578 &i.LockedAt,
1579 &i.LockReason,
1580 &i.GraceUntil,
1581 &i.LastWebhookEventID,
1582 &i.CreatedAt,
1583 &i.UpdatedAt,
1584 &i.LastEventAt,
1585 )
1586 return i, err
1587 }
1588
1589 const markUserPaymentSucceeded = `-- name: MarkUserPaymentSucceeded :one
1590 WITH state AS (
1591 UPDATE user_billing_states
1592 SET plan = CASE
1593 WHEN subscription_status IN ('past_due', 'unpaid', 'incomplete') THEN 'pro'
1594 ELSE plan
1595 END,
1596 subscription_status = CASE
1597 WHEN subscription_status IN ('past_due', 'unpaid', 'incomplete') THEN 'active'
1598 ELSE subscription_status
1599 END,
1600 past_due_at = CASE
1601 WHEN subscription_status IN ('past_due', 'unpaid', 'incomplete') THEN NULL
1602 ELSE past_due_at
1603 END,
1604 locked_at = NULL,
1605 lock_reason = NULL,
1606 grace_until = NULL,
1607 last_webhook_event_id = $1::text,
1608 updated_at = now()
1609 WHERE user_id = $2::bigint
1610 RETURNING user_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at
1611 ), user_update AS (
1612 UPDATE users
1613 SET plan = state.plan,
1614 updated_at = now()
1615 FROM state
1616 WHERE users.id = state.user_id
1617 RETURNING users.id
1618 )
1619 SELECT user_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at FROM state
1620 `
1621
1622 type MarkUserPaymentSucceededParams struct {
1623 LastWebhookEventID string
1624 UserID int64
1625 }
1626
1627 type MarkUserPaymentSucceededRow struct {
1628 UserID int64
1629 Provider BillingProvider
1630 StripeCustomerID pgtype.Text
1631 StripeSubscriptionID pgtype.Text
1632 StripeSubscriptionItemID pgtype.Text
1633 Plan UserPlan
1634 SubscriptionStatus BillingSubscriptionStatus
1635 CurrentPeriodStart pgtype.Timestamptz
1636 CurrentPeriodEnd pgtype.Timestamptz
1637 CancelAtPeriodEnd bool
1638 TrialEnd pgtype.Timestamptz
1639 PastDueAt pgtype.Timestamptz
1640 CanceledAt pgtype.Timestamptz
1641 LockedAt pgtype.Timestamptz
1642 LockReason NullBillingLockReason
1643 GraceUntil pgtype.Timestamptz
1644 LastWebhookEventID string
1645 CreatedAt pgtype.Timestamptz
1646 UpdatedAt pgtype.Timestamptz
1647 LastEventAt pgtype.Timestamptz
1648 }
1649
1650 func (q *Queries) MarkUserPaymentSucceeded(ctx context.Context, db DBTX, arg MarkUserPaymentSucceededParams) (MarkUserPaymentSucceededRow, error) {
1651 row := db.QueryRow(ctx, markUserPaymentSucceeded, arg.LastWebhookEventID, arg.UserID)
1652 var i MarkUserPaymentSucceededRow
1653 err := row.Scan(
1654 &i.UserID,
1655 &i.Provider,
1656 &i.StripeCustomerID,
1657 &i.StripeSubscriptionID,
1658 &i.StripeSubscriptionItemID,
1659 &i.Plan,
1660 &i.SubscriptionStatus,
1661 &i.CurrentPeriodStart,
1662 &i.CurrentPeriodEnd,
1663 &i.CancelAtPeriodEnd,
1664 &i.TrialEnd,
1665 &i.PastDueAt,
1666 &i.CanceledAt,
1667 &i.LockedAt,
1668 &i.LockReason,
1669 &i.GraceUntil,
1670 &i.LastWebhookEventID,
1671 &i.CreatedAt,
1672 &i.UpdatedAt,
1673 &i.LastEventAt,
1674 )
1675 return i, err
1676 }
1677
1678 const markWebhookEventFailed = `-- name: MarkWebhookEventFailed :one
1679 UPDATE billing_webhook_events
1680 SET process_error = $2,
1681 processing_attempts = processing_attempts + 1
1682 WHERE provider = 'stripe'
1683 AND provider_event_id = $1
1684 RETURNING id, provider, provider_event_id, event_type, api_version, payload, received_at, processed_at, process_error, processing_attempts, subject_kind, subject_id
1685 `
1686
1687 type MarkWebhookEventFailedParams struct {
1688 ProviderEventID string
1689 ProcessError string
1690 }
1691
1692 func (q *Queries) MarkWebhookEventFailed(ctx context.Context, db DBTX, arg MarkWebhookEventFailedParams) (BillingWebhookEvent, error) {
1693 row := db.QueryRow(ctx, markWebhookEventFailed, arg.ProviderEventID, arg.ProcessError)
1694 var i BillingWebhookEvent
1695 err := row.Scan(
1696 &i.ID,
1697 &i.Provider,
1698 &i.ProviderEventID,
1699 &i.EventType,
1700 &i.ApiVersion,
1701 &i.Payload,
1702 &i.ReceivedAt,
1703 &i.ProcessedAt,
1704 &i.ProcessError,
1705 &i.ProcessingAttempts,
1706 &i.SubjectKind,
1707 &i.SubjectID,
1708 )
1709 return i, err
1710 }
1711
1712 const markWebhookEventProcessed = `-- name: MarkWebhookEventProcessed :one
1713 UPDATE billing_webhook_events
1714 SET processed_at = now(),
1715 process_error = '',
1716 processing_attempts = processing_attempts + 1
1717 WHERE provider = 'stripe'
1718 AND provider_event_id = $1
1719 RETURNING id, provider, provider_event_id, event_type, api_version, payload, received_at, processed_at, process_error, processing_attempts, subject_kind, subject_id
1720 `
1721
1722 func (q *Queries) MarkWebhookEventProcessed(ctx context.Context, db DBTX, providerEventID string) (BillingWebhookEvent, error) {
1723 row := db.QueryRow(ctx, markWebhookEventProcessed, providerEventID)
1724 var i BillingWebhookEvent
1725 err := row.Scan(
1726 &i.ID,
1727 &i.Provider,
1728 &i.ProviderEventID,
1729 &i.EventType,
1730 &i.ApiVersion,
1731 &i.Payload,
1732 &i.ReceivedAt,
1733 &i.ProcessedAt,
1734 &i.ProcessError,
1735 &i.ProcessingAttempts,
1736 &i.SubjectKind,
1737 &i.SubjectID,
1738 )
1739 return i, err
1740 }
1741
1742 const setStripeCustomer = `-- name: SetStripeCustomer :one
1743 INSERT INTO org_billing_states (org_id, provider, stripe_customer_id)
1744 VALUES ($1, 'stripe', $2)
1745 ON CONFLICT (org_id) DO UPDATE
1746 SET stripe_customer_id = EXCLUDED.stripe_customer_id,
1747 provider = 'stripe',
1748 updated_at = now()
1749 RETURNING org_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, billable_seats, seat_snapshot_at, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at
1750 `
1751
1752 type SetStripeCustomerParams struct {
1753 OrgID int64
1754 StripeCustomerID pgtype.Text
1755 }
1756
1757 func (q *Queries) SetStripeCustomer(ctx context.Context, db DBTX, arg SetStripeCustomerParams) (OrgBillingState, error) {
1758 row := db.QueryRow(ctx, setStripeCustomer, arg.OrgID, arg.StripeCustomerID)
1759 var i OrgBillingState
1760 err := row.Scan(
1761 &i.OrgID,
1762 &i.Provider,
1763 &i.StripeCustomerID,
1764 &i.StripeSubscriptionID,
1765 &i.StripeSubscriptionItemID,
1766 &i.Plan,
1767 &i.SubscriptionStatus,
1768 &i.BillableSeats,
1769 &i.SeatSnapshotAt,
1770 &i.CurrentPeriodStart,
1771 &i.CurrentPeriodEnd,
1772 &i.CancelAtPeriodEnd,
1773 &i.TrialEnd,
1774 &i.PastDueAt,
1775 &i.CanceledAt,
1776 &i.LockedAt,
1777 &i.LockReason,
1778 &i.GraceUntil,
1779 &i.LastWebhookEventID,
1780 &i.CreatedAt,
1781 &i.UpdatedAt,
1782 &i.LastEventAt,
1783 )
1784 return i, err
1785 }
1786
1787 const setUserStripeCustomer = `-- name: SetUserStripeCustomer :one
1788 INSERT INTO user_billing_states (user_id, provider, stripe_customer_id)
1789 VALUES ($1, 'stripe', $2)
1790 ON CONFLICT (user_id) DO UPDATE
1791 SET stripe_customer_id = EXCLUDED.stripe_customer_id,
1792 provider = 'stripe',
1793 updated_at = now()
1794 RETURNING user_id, provider, stripe_customer_id, stripe_subscription_id, stripe_subscription_item_id, plan, subscription_status, current_period_start, current_period_end, cancel_at_period_end, trial_end, past_due_at, canceled_at, locked_at, lock_reason, grace_until, last_webhook_event_id, created_at, updated_at, last_event_at
1795 `
1796
1797 type SetUserStripeCustomerParams struct {
1798 UserID int64
1799 StripeCustomerID pgtype.Text
1800 }
1801
1802 func (q *Queries) SetUserStripeCustomer(ctx context.Context, db DBTX, arg SetUserStripeCustomerParams) (UserBillingState, error) {
1803 row := db.QueryRow(ctx, setUserStripeCustomer, arg.UserID, arg.StripeCustomerID)
1804 var i UserBillingState
1805 err := row.Scan(
1806 &i.UserID,
1807 &i.Provider,
1808 &i.StripeCustomerID,
1809 &i.StripeSubscriptionID,
1810 &i.StripeSubscriptionItemID,
1811 &i.Plan,
1812 &i.SubscriptionStatus,
1813 &i.CurrentPeriodStart,
1814 &i.CurrentPeriodEnd,
1815 &i.CancelAtPeriodEnd,
1816 &i.TrialEnd,
1817 &i.PastDueAt,
1818 &i.CanceledAt,
1819 &i.LockedAt,
1820 &i.LockReason,
1821 &i.GraceUntil,
1822 &i.LastWebhookEventID,
1823 &i.CreatedAt,
1824 &i.UpdatedAt,
1825 &i.LastEventAt,
1826 )
1827 return i, err
1828 }
1829
1830 const setWebhookEventSubject = `-- name: SetWebhookEventSubject :exec
1831 UPDATE billing_webhook_events
1832 SET subject_kind = $1::billing_subject_kind,
1833 subject_id = $2::bigint
1834 WHERE provider = 'stripe'
1835 AND provider_event_id = $3::text
1836 `
1837
1838 type SetWebhookEventSubjectParams struct {
1839 SubjectKind BillingSubjectKind
1840 SubjectID int64
1841 ProviderEventID string
1842 }
1843
1844 // Records the resolved subject on the receipt row after a successful
1845 // subject-resolution step. Called from the apply path before guard +
1846 // state mutation so the receipt carries the audit trail even if the
1847 // subsequent apply fails. Migration 0075's CHECK constraint enforces
1848 // both-or-neither; callers must pass a non-zero subject.
1849 func (q *Queries) SetWebhookEventSubject(ctx context.Context, db DBTX, arg SetWebhookEventSubjectParams) error {
1850 _, err := db.Exec(ctx, setWebhookEventSubject, arg.SubjectKind, arg.SubjectID, arg.ProviderEventID)
1851 return err
1852 }
1853
1854 const touchOrgBillingLastEventAt = `-- name: TouchOrgBillingLastEventAt :exec
1855 UPDATE org_billing_states
1856 SET last_event_at = GREATEST(COALESCE(last_event_at, $1::timestamptz), $1::timestamptz)
1857 WHERE org_id = $2::bigint
1858 `
1859
1860 type TouchOrgBillingLastEventAtParams struct {
1861 EventAt pgtype.Timestamptz
1862 OrgID int64
1863 }
1864
1865 // PRO08 D4: bump last_event_at on successful apply. Conditional so
1866 // a fresh apply driven by an out-of-order-but-recent retry doesn't
1867 // regress the timestamp (GREATEST). NULL last_event_at acquires the
1868 // incoming value.
1869 func (q *Queries) TouchOrgBillingLastEventAt(ctx context.Context, db DBTX, arg TouchOrgBillingLastEventAtParams) error {
1870 _, err := db.Exec(ctx, touchOrgBillingLastEventAt, arg.EventAt, arg.OrgID)
1871 return err
1872 }
1873
1874 const touchUserBillingLastEventAt = `-- name: TouchUserBillingLastEventAt :exec
1875 UPDATE user_billing_states
1876 SET last_event_at = GREATEST(COALESCE(last_event_at, $1::timestamptz), $1::timestamptz)
1877 WHERE user_id = $2::bigint
1878 `
1879
1880 type TouchUserBillingLastEventAtParams struct {
1881 EventAt pgtype.Timestamptz
1882 UserID int64
1883 }
1884
1885 func (q *Queries) TouchUserBillingLastEventAt(ctx context.Context, db DBTX, arg TouchUserBillingLastEventAtParams) error {
1886 _, err := db.Exec(ctx, touchUserBillingLastEventAt, arg.EventAt, arg.UserID)
1887 return err
1888 }
1889
1890 const tryAcquireWebhookEventLock = `-- name: TryAcquireWebhookEventLock :one
1891 SELECT pg_try_advisory_xact_lock(hashtext($1)::bigint) AS acquired
1892 `
1893
1894 // PRO08 A3: transaction-scoped advisory lock keyed on the hash of
1895 // the provider_event_id. Two concurrent webhook deliveries for the
1896 // same event_id race past CreateWebhookEventReceipt before either has
1897 // marked it processed; without serialization, both proceed to apply
1898 // and double-mutate state. This lock makes the apply path mutually
1899 // exclusive per event. Returns true when acquired; false means
1900 // another worker holds it — caller should let Stripe retry.
1901 //
1902 // pg_try_advisory_xact_lock takes a bigint; hashtext returns int4
1903 // which sign-extends safely. The lock auto-releases at txn end.
1904 func (q *Queries) TryAcquireWebhookEventLock(ctx context.Context, db DBTX, hashtext string) (bool, error) {
1905 row := db.QueryRow(ctx, tryAcquireWebhookEventLock, hashtext)
1906 var acquired bool
1907 err := row.Scan(&acquired)
1908 return acquired, err
1909 }
1910
1911 const upsertInvoice = `-- name: UpsertInvoice :one
1912
1913 INSERT INTO billing_invoices (
1914 org_id,
1915 subject_kind,
1916 subject_id,
1917 provider,
1918 stripe_invoice_id,
1919 stripe_customer_id,
1920 stripe_subscription_id,
1921 status,
1922 number,
1923 currency,
1924 amount_due_cents,
1925 amount_paid_cents,
1926 amount_remaining_cents,
1927 hosted_invoice_url,
1928 invoice_pdf_url,
1929 period_start,
1930 period_end,
1931 due_at,
1932 paid_at,
1933 voided_at
1934 )
1935 VALUES (
1936 $1::bigint,
1937 'org'::billing_subject_kind,
1938 $1::bigint,
1939 'stripe',
1940 $2::text,
1941 $3::text,
1942 $4::text,
1943 $5::billing_invoice_status,
1944 $6::text,
1945 $7::text,
1946 $8::bigint,
1947 $9::bigint,
1948 $10::bigint,
1949 $11::text,
1950 $12::text,
1951 $13::timestamptz,
1952 $14::timestamptz,
1953 $15::timestamptz,
1954 $16::timestamptz,
1955 $17::timestamptz
1956 )
1957 ON CONFLICT (provider, stripe_invoice_id) DO UPDATE
1958 SET org_id = EXCLUDED.org_id,
1959 stripe_customer_id = EXCLUDED.stripe_customer_id,
1960 stripe_subscription_id = EXCLUDED.stripe_subscription_id,
1961 status = EXCLUDED.status,
1962 number = EXCLUDED.number,
1963 currency = EXCLUDED.currency,
1964 amount_due_cents = EXCLUDED.amount_due_cents,
1965 amount_paid_cents = EXCLUDED.amount_paid_cents,
1966 amount_remaining_cents = EXCLUDED.amount_remaining_cents,
1967 hosted_invoice_url = EXCLUDED.hosted_invoice_url,
1968 invoice_pdf_url = EXCLUDED.invoice_pdf_url,
1969 period_start = EXCLUDED.period_start,
1970 period_end = EXCLUDED.period_end,
1971 due_at = EXCLUDED.due_at,
1972 paid_at = EXCLUDED.paid_at,
1973 voided_at = EXCLUDED.voided_at,
1974 updated_at = now()
1975 RETURNING id, org_id, provider, stripe_invoice_id, stripe_customer_id, stripe_subscription_id, status, number, currency, amount_due_cents, amount_paid_cents, amount_remaining_cents, hosted_invoice_url, invoice_pdf_url, period_start, period_end, due_at, paid_at, voided_at, created_at, updated_at, subject_kind, subject_id, refunded_at
1976 `
1977
1978 type UpsertInvoiceParams struct {
1979 OrgID int64
1980 StripeInvoiceID string
1981 StripeCustomerID string
1982 StripeSubscriptionID pgtype.Text
1983 Status BillingInvoiceStatus
1984 Number string
1985 Currency string
1986 AmountDueCents int64
1987 AmountPaidCents int64
1988 AmountRemainingCents int64
1989 HostedInvoiceUrl string
1990 InvoicePdfUrl string
1991 PeriodStart pgtype.Timestamptz
1992 PeriodEnd pgtype.Timestamptz
1993 DueAt pgtype.Timestamptz
1994 PaidAt pgtype.Timestamptz
1995 VoidedAt pgtype.Timestamptz
1996 }
1997
1998 // ─── billing_invoices ──────────────────────────────────────────────
1999 // PRO03: writes both legacy `org_id` and polymorphic
2000 // `(subject_kind, subject_id)`. Callers continue to bind org_id only;
2001 // the subject columns are derived. After PRO04 migrates all callers
2002 // to the polymorphic shape, a follow-up migration drops `org_id` and
2003 // this query loses the legacy column from its INSERT list.
2004 func (q *Queries) UpsertInvoice(ctx context.Context, db DBTX, arg UpsertInvoiceParams) (BillingInvoice, error) {
2005 row := db.QueryRow(ctx, upsertInvoice,
2006 arg.OrgID,
2007 arg.StripeInvoiceID,
2008 arg.StripeCustomerID,
2009 arg.StripeSubscriptionID,
2010 arg.Status,
2011 arg.Number,
2012 arg.Currency,
2013 arg.AmountDueCents,
2014 arg.AmountPaidCents,
2015 arg.AmountRemainingCents,
2016 arg.HostedInvoiceUrl,
2017 arg.InvoicePdfUrl,
2018 arg.PeriodStart,
2019 arg.PeriodEnd,
2020 arg.DueAt,
2021 arg.PaidAt,
2022 arg.VoidedAt,
2023 )
2024 var i BillingInvoice
2025 err := row.Scan(
2026 &i.ID,
2027 &i.OrgID,
2028 &i.Provider,
2029 &i.StripeInvoiceID,
2030 &i.StripeCustomerID,
2031 &i.StripeSubscriptionID,
2032 &i.Status,
2033 &i.Number,
2034 &i.Currency,
2035 &i.AmountDueCents,
2036 &i.AmountPaidCents,
2037 &i.AmountRemainingCents,
2038 &i.HostedInvoiceUrl,
2039 &i.InvoicePdfUrl,
2040 &i.PeriodStart,
2041 &i.PeriodEnd,
2042 &i.DueAt,
2043 &i.PaidAt,
2044 &i.VoidedAt,
2045 &i.CreatedAt,
2046 &i.UpdatedAt,
2047 &i.SubjectKind,
2048 &i.SubjectID,
2049 &i.RefundedAt,
2050 )
2051 return i, err
2052 }
2053
2054 const upsertInvoiceForSubject = `-- name: UpsertInvoiceForSubject :one
2055 INSERT INTO billing_invoices (
2056 subject_kind,
2057 subject_id,
2058 provider,
2059 stripe_invoice_id,
2060 stripe_customer_id,
2061 stripe_subscription_id,
2062 status,
2063 number,
2064 currency,
2065 amount_due_cents,
2066 amount_paid_cents,
2067 amount_remaining_cents,
2068 hosted_invoice_url,
2069 invoice_pdf_url,
2070 period_start,
2071 period_end,
2072 due_at,
2073 paid_at,
2074 voided_at
2075 )
2076 VALUES (
2077 $1::billing_subject_kind,
2078 $2::bigint,
2079 'stripe',
2080 $3::text,
2081 $4::text,
2082 $5::text,
2083 $6::billing_invoice_status,
2084 $7::text,
2085 $8::text,
2086 $9::bigint,
2087 $10::bigint,
2088 $11::bigint,
2089 $12::text,
2090 $13::text,
2091 $14::timestamptz,
2092 $15::timestamptz,
2093 $16::timestamptz,
2094 $17::timestamptz,
2095 $18::timestamptz
2096 )
2097 ON CONFLICT (provider, stripe_invoice_id) DO UPDATE
2098 SET subject_kind = EXCLUDED.subject_kind,
2099 subject_id = EXCLUDED.subject_id,
2100 stripe_customer_id = EXCLUDED.stripe_customer_id,
2101 stripe_subscription_id = EXCLUDED.stripe_subscription_id,
2102 status = EXCLUDED.status,
2103 number = EXCLUDED.number,
2104 currency = EXCLUDED.currency,
2105 amount_due_cents = EXCLUDED.amount_due_cents,
2106 amount_paid_cents = EXCLUDED.amount_paid_cents,
2107 amount_remaining_cents = EXCLUDED.amount_remaining_cents,
2108 hosted_invoice_url = EXCLUDED.hosted_invoice_url,
2109 invoice_pdf_url = EXCLUDED.invoice_pdf_url,
2110 period_start = EXCLUDED.period_start,
2111 period_end = EXCLUDED.period_end,
2112 due_at = EXCLUDED.due_at,
2113 paid_at = EXCLUDED.paid_at,
2114 voided_at = EXCLUDED.voided_at,
2115 updated_at = now()
2116 RETURNING id, org_id, provider, stripe_invoice_id, stripe_customer_id, stripe_subscription_id, status, number, currency, amount_due_cents, amount_paid_cents, amount_remaining_cents, hosted_invoice_url, invoice_pdf_url, period_start, period_end, due_at, paid_at, voided_at, created_at, updated_at, subject_kind, subject_id, refunded_at
2117 `
2118
2119 type UpsertInvoiceForSubjectParams struct {
2120 SubjectKind BillingSubjectKind
2121 SubjectID int64
2122 StripeInvoiceID string
2123 StripeCustomerID string
2124 StripeSubscriptionID pgtype.Text
2125 Status BillingInvoiceStatus
2126 Number string
2127 Currency string
2128 AmountDueCents int64
2129 AmountPaidCents int64
2130 AmountRemainingCents int64
2131 HostedInvoiceUrl string
2132 InvoicePdfUrl string
2133 PeriodStart pgtype.Timestamptz
2134 PeriodEnd pgtype.Timestamptz
2135 DueAt pgtype.Timestamptz
2136 PaidAt pgtype.Timestamptz
2137 VoidedAt pgtype.Timestamptz
2138 }
2139
2140 // PRO04 polymorphic invoice upsert. Writes (subject_kind,
2141 // subject_id) directly; org_id stays NULL for user-kind rows (per
2142 // the 0074 migration's nullable change). The existing
2143 // UpsertInvoice query stays as the org-kind path during the
2144 // transitional deploy — both can coexist because the UNIQUE
2145 // (provider, stripe_invoice_id) prevents duplicate rows.
2146 func (q *Queries) UpsertInvoiceForSubject(ctx context.Context, db DBTX, arg UpsertInvoiceForSubjectParams) (BillingInvoice, error) {
2147 row := db.QueryRow(ctx, upsertInvoiceForSubject,
2148 arg.SubjectKind,
2149 arg.SubjectID,
2150 arg.StripeInvoiceID,
2151 arg.StripeCustomerID,
2152 arg.StripeSubscriptionID,
2153 arg.Status,
2154 arg.Number,
2155 arg.Currency,
2156 arg.AmountDueCents,
2157 arg.AmountPaidCents,
2158 arg.AmountRemainingCents,
2159 arg.HostedInvoiceUrl,
2160 arg.InvoicePdfUrl,
2161 arg.PeriodStart,
2162 arg.PeriodEnd,
2163 arg.DueAt,
2164 arg.PaidAt,
2165 arg.VoidedAt,
2166 )
2167 var i BillingInvoice
2168 err := row.Scan(
2169 &i.ID,
2170 &i.OrgID,
2171 &i.Provider,
2172 &i.StripeInvoiceID,
2173 &i.StripeCustomerID,
2174 &i.StripeSubscriptionID,
2175 &i.Status,
2176 &i.Number,
2177 &i.Currency,
2178 &i.AmountDueCents,
2179 &i.AmountPaidCents,
2180 &i.AmountRemainingCents,
2181 &i.HostedInvoiceUrl,
2182 &i.InvoicePdfUrl,
2183 &i.PeriodStart,
2184 &i.PeriodEnd,
2185 &i.DueAt,
2186 &i.PaidAt,
2187 &i.VoidedAt,
2188 &i.CreatedAt,
2189 &i.UpdatedAt,
2190 &i.SubjectKind,
2191 &i.SubjectID,
2192 &i.RefundedAt,
2193 )
2194 return i, err
2195 }
2196