@@ -92,7 +92,7 @@ WITH state AS ( |
| 92 | 92 | ELSE org_billing_states.grace_until |
| 93 | 93 | END, |
| 94 | 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 |
| 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 | 96 | ), org_update AS ( |
| 97 | 97 | UPDATE orgs |
| 98 | 98 | SET plan = $2::org_plan, |
@@ -100,7 +100,7 @@ WITH state AS ( |
| 100 | 100 | WHERE id = $1::bigint |
| 101 | 101 | RETURNING id |
| 102 | 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 FROM state |
| 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 | 104 | ` |
| 105 | 105 | |
| 106 | 106 | type ApplySubscriptionSnapshotParams struct { |
@@ -139,6 +139,7 @@ type ApplySubscriptionSnapshotRow struct { |
| 139 | 139 | LastWebhookEventID string |
| 140 | 140 | CreatedAt pgtype.Timestamptz |
| 141 | 141 | UpdatedAt pgtype.Timestamptz |
| 142 | + LastEventAt pgtype.Timestamptz |
| 142 | 143 | } |
| 143 | 144 | |
| 144 | 145 | func (q *Queries) ApplySubscriptionSnapshot(ctx context.Context, db DBTX, arg ApplySubscriptionSnapshotParams) (ApplySubscriptionSnapshotRow, error) { |
@@ -178,6 +179,7 @@ func (q *Queries) ApplySubscriptionSnapshot(ctx context.Context, db DBTX, arg Ap |
| 178 | 179 | &i.LastWebhookEventID, |
| 179 | 180 | &i.CreatedAt, |
| 180 | 181 | &i.UpdatedAt, |
| 182 | + &i.LastEventAt, |
| 181 | 183 | ) |
| 182 | 184 | return i, err |
| 183 | 185 | } |
@@ -260,7 +262,7 @@ WITH state AS ( |
| 260 | 262 | ELSE user_billing_states.grace_until |
| 261 | 263 | END, |
| 262 | 264 | updated_at = now() |
| 263 | | - 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 |
| 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 |
| 264 | 266 | ), user_update AS ( |
| 265 | 267 | UPDATE users |
| 266 | 268 | SET plan = $2::user_plan, |
@@ -268,7 +270,7 @@ WITH state AS ( |
| 268 | 270 | WHERE id = $1::bigint |
| 269 | 271 | RETURNING id |
| 270 | 272 | ) |
| 271 | | -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 FROM state |
| 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 |
| 272 | 274 | ` |
| 273 | 275 | |
| 274 | 276 | type ApplyUserSubscriptionSnapshotParams struct { |
@@ -305,6 +307,7 @@ type ApplyUserSubscriptionSnapshotRow struct { |
| 305 | 307 | LastWebhookEventID string |
| 306 | 308 | CreatedAt pgtype.Timestamptz |
| 307 | 309 | UpdatedAt pgtype.Timestamptz |
| 310 | + LastEventAt pgtype.Timestamptz |
| 308 | 311 | } |
| 309 | 312 | |
| 310 | 313 | // Mirrors ApplySubscriptionSnapshot for orgs minus the seat columns |
@@ -345,6 +348,7 @@ func (q *Queries) ApplyUserSubscriptionSnapshot(ctx context.Context, db DBTX, ar |
| 345 | 348 | &i.LastWebhookEventID, |
| 346 | 349 | &i.CreatedAt, |
| 347 | 350 | &i.UpdatedAt, |
| 351 | + &i.LastEventAt, |
| 348 | 352 | ) |
| 349 | 353 | return i, err |
| 350 | 354 | } |
@@ -365,7 +369,7 @@ WITH state AS ( |
| 365 | 369 | grace_until = NULL, |
| 366 | 370 | updated_at = now() |
| 367 | 371 | WHERE org_id = $1 |
| 368 | | - 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 |
| 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 |
| 369 | 373 | ), org_update AS ( |
| 370 | 374 | UPDATE orgs |
| 371 | 375 | SET plan = state.plan, |
@@ -374,7 +378,7 @@ WITH state AS ( |
| 374 | 378 | WHERE orgs.id = state.org_id |
| 375 | 379 | RETURNING orgs.id |
| 376 | 380 | ) |
| 377 | | -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 FROM state |
| 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 |
| 378 | 382 | ` |
| 379 | 383 | |
| 380 | 384 | type ClearBillingLockRow struct { |
@@ -399,6 +403,7 @@ type ClearBillingLockRow struct { |
| 399 | 403 | LastWebhookEventID string |
| 400 | 404 | CreatedAt pgtype.Timestamptz |
| 401 | 405 | UpdatedAt pgtype.Timestamptz |
| 406 | + LastEventAt pgtype.Timestamptz |
| 402 | 407 | } |
| 403 | 408 | |
| 404 | 409 | func (q *Queries) ClearBillingLock(ctx context.Context, db DBTX, orgID int64) (ClearBillingLockRow, error) { |
@@ -426,6 +431,7 @@ func (q *Queries) ClearBillingLock(ctx context.Context, db DBTX, orgID int64) (C |
| 426 | 431 | &i.LastWebhookEventID, |
| 427 | 432 | &i.CreatedAt, |
| 428 | 433 | &i.UpdatedAt, |
| 434 | + &i.LastEventAt, |
| 429 | 435 | ) |
| 430 | 436 | return i, err |
| 431 | 437 | } |
@@ -446,7 +452,7 @@ WITH state AS ( |
| 446 | 452 | grace_until = NULL, |
| 447 | 453 | updated_at = now() |
| 448 | 454 | WHERE user_id = $1 |
| 449 | | - 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 |
| 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 |
| 450 | 456 | ), user_update AS ( |
| 451 | 457 | UPDATE users |
| 452 | 458 | SET plan = state.plan, |
@@ -455,7 +461,7 @@ WITH state AS ( |
| 455 | 461 | WHERE users.id = state.user_id |
| 456 | 462 | RETURNING users.id |
| 457 | 463 | ) |
| 458 | | -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 FROM state |
| 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 |
| 459 | 465 | ` |
| 460 | 466 | |
| 461 | 467 | type ClearUserBillingLockRow struct { |
@@ -478,6 +484,7 @@ type ClearUserBillingLockRow struct { |
| 478 | 484 | LastWebhookEventID string |
| 479 | 485 | CreatedAt pgtype.Timestamptz |
| 480 | 486 | UpdatedAt pgtype.Timestamptz |
| 487 | + LastEventAt pgtype.Timestamptz |
| 481 | 488 | } |
| 482 | 489 | |
| 483 | 490 | func (q *Queries) ClearUserBillingLock(ctx context.Context, db DBTX, userID int64) (ClearUserBillingLockRow, error) { |
@@ -503,6 +510,7 @@ func (q *Queries) ClearUserBillingLock(ctx context.Context, db DBTX, userID int6 |
| 503 | 510 | &i.LastWebhookEventID, |
| 504 | 511 | &i.CreatedAt, |
| 505 | 512 | &i.UpdatedAt, |
| 513 | + &i.LastEventAt, |
| 506 | 514 | ) |
| 507 | 515 | return i, err |
| 508 | 516 | } |
@@ -667,7 +675,7 @@ func (q *Queries) CreateWebhookEventReceipt(ctx context.Context, db DBTX, arg Cr |
| 667 | 675 | const getOrgBillingState = `-- name: GetOrgBillingState :one |
| 668 | 676 | |
| 669 | 677 | |
| 670 | | -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 FROM org_billing_states WHERE org_id = $1 |
| 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 |
| 671 | 679 | ` |
| 672 | 680 | |
| 673 | 681 | // SPDX-License-Identifier: AGPL-3.0-or-later |
@@ -697,12 +705,13 @@ func (q *Queries) GetOrgBillingState(ctx context.Context, db DBTX, orgID int64) |
| 697 | 705 | &i.LastWebhookEventID, |
| 698 | 706 | &i.CreatedAt, |
| 699 | 707 | &i.UpdatedAt, |
| 708 | + &i.LastEventAt, |
| 700 | 709 | ) |
| 701 | 710 | return i, err |
| 702 | 711 | } |
| 703 | 712 | |
| 704 | 713 | const getOrgBillingStateByStripeCustomer = `-- name: GetOrgBillingStateByStripeCustomer :one |
| 705 | | -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 FROM org_billing_states |
| 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 |
| 706 | 715 | WHERE provider = 'stripe' |
| 707 | 716 | AND stripe_customer_id = $1 |
| 708 | 717 | ` |
@@ -732,12 +741,13 @@ func (q *Queries) GetOrgBillingStateByStripeCustomer(ctx context.Context, db DBT |
| 732 | 741 | &i.LastWebhookEventID, |
| 733 | 742 | &i.CreatedAt, |
| 734 | 743 | &i.UpdatedAt, |
| 744 | + &i.LastEventAt, |
| 735 | 745 | ) |
| 736 | 746 | return i, err |
| 737 | 747 | } |
| 738 | 748 | |
| 739 | 749 | const getOrgBillingStateByStripeSubscription = `-- name: GetOrgBillingStateByStripeSubscription :one |
| 740 | | -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 FROM org_billing_states |
| 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 |
| 741 | 751 | WHERE provider = 'stripe' |
| 742 | 752 | AND stripe_subscription_id = $1 |
| 743 | 753 | ` |
@@ -767,13 +777,14 @@ func (q *Queries) GetOrgBillingStateByStripeSubscription(ctx context.Context, db |
| 767 | 777 | &i.LastWebhookEventID, |
| 768 | 778 | &i.CreatedAt, |
| 769 | 779 | &i.UpdatedAt, |
| 780 | + &i.LastEventAt, |
| 770 | 781 | ) |
| 771 | 782 | return i, err |
| 772 | 783 | } |
| 773 | 784 | |
| 774 | 785 | const getUserBillingState = `-- name: GetUserBillingState :one |
| 775 | 786 | |
| 776 | | -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 FROM user_billing_states WHERE user_id = $1 |
| 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 |
| 777 | 788 | ` |
| 778 | 789 | |
| 779 | 790 | // ─── user_billing_states (PRO03) ────────────────────────────────── |
@@ -800,12 +811,13 @@ func (q *Queries) GetUserBillingState(ctx context.Context, db DBTX, userID int64 |
| 800 | 811 | &i.LastWebhookEventID, |
| 801 | 812 | &i.CreatedAt, |
| 802 | 813 | &i.UpdatedAt, |
| 814 | + &i.LastEventAt, |
| 803 | 815 | ) |
| 804 | 816 | return i, err |
| 805 | 817 | } |
| 806 | 818 | |
| 807 | 819 | const getUserBillingStateByStripeCustomer = `-- name: GetUserBillingStateByStripeCustomer :one |
| 808 | | -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 FROM user_billing_states |
| 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 |
| 809 | 821 | WHERE provider = 'stripe' |
| 810 | 822 | AND stripe_customer_id = $1 |
| 811 | 823 | ` |
@@ -833,12 +845,13 @@ func (q *Queries) GetUserBillingStateByStripeCustomer(ctx context.Context, db DB |
| 833 | 845 | &i.LastWebhookEventID, |
| 834 | 846 | &i.CreatedAt, |
| 835 | 847 | &i.UpdatedAt, |
| 848 | + &i.LastEventAt, |
| 836 | 849 | ) |
| 837 | 850 | return i, err |
| 838 | 851 | } |
| 839 | 852 | |
| 840 | 853 | const getUserBillingStateByStripeSubscription = `-- name: GetUserBillingStateByStripeSubscription :one |
| 841 | | -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 FROM user_billing_states |
| 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 |
| 842 | 855 | WHERE provider = 'stripe' |
| 843 | 856 | AND stripe_subscription_id = $1 |
| 844 | 857 | ` |
@@ -866,6 +879,7 @@ func (q *Queries) GetUserBillingStateByStripeSubscription(ctx context.Context, d |
| 866 | 879 | &i.LastWebhookEventID, |
| 867 | 880 | &i.CreatedAt, |
| 868 | 881 | &i.UpdatedAt, |
| 882 | + &i.LastEventAt, |
| 869 | 883 | ) |
| 870 | 884 | return i, err |
| 871 | 885 | } |
@@ -896,6 +910,49 @@ func (q *Queries) GetWebhookEventReceipt(ctx context.Context, db DBTX, providerE |
| 896 | 910 | return i, err |
| 897 | 911 | } |
| 898 | 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 | + |
| 899 | 956 | const listFailedWebhookEvents = `-- name: ListFailedWebhookEvents :many |
| 900 | 957 | SELECT id, provider, provider_event_id, event_type, api_version, |
| 901 | 958 | received_at, processed_at, processing_attempts, process_error, |
@@ -1137,7 +1194,7 @@ WITH state AS ( |
| 1137 | 1194 | last_webhook_event_id = $1::text, |
| 1138 | 1195 | updated_at = now() |
| 1139 | 1196 | WHERE org_id = $2::bigint |
| 1140 | | - 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 |
| 1197 | + 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 |
| 1141 | 1198 | ), org_update AS ( |
| 1142 | 1199 | UPDATE orgs |
| 1143 | 1200 | SET plan = 'free', |
@@ -1145,7 +1202,7 @@ WITH state AS ( |
| 1145 | 1202 | WHERE id = $2::bigint |
| 1146 | 1203 | RETURNING id |
| 1147 | 1204 | ) |
| 1148 | | -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 FROM state |
| 1205 | +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 |
| 1149 | 1206 | ` |
| 1150 | 1207 | |
| 1151 | 1208 | type MarkCanceledParams struct { |
@@ -1175,6 +1232,7 @@ type MarkCanceledRow struct { |
| 1175 | 1232 | LastWebhookEventID string |
| 1176 | 1233 | CreatedAt pgtype.Timestamptz |
| 1177 | 1234 | UpdatedAt pgtype.Timestamptz |
| 1235 | + LastEventAt pgtype.Timestamptz |
| 1178 | 1236 | } |
| 1179 | 1237 | |
| 1180 | 1238 | func (q *Queries) MarkCanceled(ctx context.Context, db DBTX, arg MarkCanceledParams) (MarkCanceledRow, error) { |
@@ -1202,6 +1260,7 @@ func (q *Queries) MarkCanceled(ctx context.Context, db DBTX, arg MarkCanceledPar |
| 1202 | 1260 | &i.LastWebhookEventID, |
| 1203 | 1261 | &i.CreatedAt, |
| 1204 | 1262 | &i.UpdatedAt, |
| 1263 | + &i.LastEventAt, |
| 1205 | 1264 | ) |
| 1206 | 1265 | return i, err |
| 1207 | 1266 | } |
@@ -1216,7 +1275,7 @@ UPDATE org_billing_states |
| 1216 | 1275 | last_webhook_event_id = $2::text, |
| 1217 | 1276 | updated_at = now() |
| 1218 | 1277 | WHERE org_id = $3::bigint |
| 1219 | | -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 |
| 1278 | +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 |
| 1220 | 1279 | ` |
| 1221 | 1280 | |
| 1222 | 1281 | type MarkPastDueParams struct { |
@@ -1250,6 +1309,7 @@ func (q *Queries) MarkPastDue(ctx context.Context, db DBTX, arg MarkPastDueParam |
| 1250 | 1309 | &i.LastWebhookEventID, |
| 1251 | 1310 | &i.CreatedAt, |
| 1252 | 1311 | &i.UpdatedAt, |
| 1312 | + &i.LastEventAt, |
| 1253 | 1313 | ) |
| 1254 | 1314 | return i, err |
| 1255 | 1315 | } |
@@ -1275,7 +1335,7 @@ WITH state AS ( |
| 1275 | 1335 | last_webhook_event_id = $1::text, |
| 1276 | 1336 | updated_at = now() |
| 1277 | 1337 | WHERE org_id = $2::bigint |
| 1278 | | - 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 |
| 1338 | + 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 |
| 1279 | 1339 | ), org_update AS ( |
| 1280 | 1340 | UPDATE orgs |
| 1281 | 1341 | SET plan = state.plan, |
@@ -1284,7 +1344,7 @@ WITH state AS ( |
| 1284 | 1344 | WHERE orgs.id = state.org_id |
| 1285 | 1345 | RETURNING orgs.id |
| 1286 | 1346 | ) |
| 1287 | | -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 FROM state |
| 1347 | +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 |
| 1288 | 1348 | ` |
| 1289 | 1349 | |
| 1290 | 1350 | type MarkPaymentSucceededParams struct { |
@@ -1314,6 +1374,7 @@ type MarkPaymentSucceededRow struct { |
| 1314 | 1374 | LastWebhookEventID string |
| 1315 | 1375 | CreatedAt pgtype.Timestamptz |
| 1316 | 1376 | UpdatedAt pgtype.Timestamptz |
| 1377 | + LastEventAt pgtype.Timestamptz |
| 1317 | 1378 | } |
| 1318 | 1379 | |
| 1319 | 1380 | func (q *Queries) MarkPaymentSucceeded(ctx context.Context, db DBTX, arg MarkPaymentSucceededParams) (MarkPaymentSucceededRow, error) { |
@@ -1341,6 +1402,7 @@ func (q *Queries) MarkPaymentSucceeded(ctx context.Context, db DBTX, arg MarkPay |
| 1341 | 1402 | &i.LastWebhookEventID, |
| 1342 | 1403 | &i.CreatedAt, |
| 1343 | 1404 | &i.UpdatedAt, |
| 1405 | + &i.LastEventAt, |
| 1344 | 1406 | ) |
| 1345 | 1407 | return i, err |
| 1346 | 1408 | } |
@@ -1358,7 +1420,7 @@ WITH state AS ( |
| 1358 | 1420 | last_webhook_event_id = $1::text, |
| 1359 | 1421 | updated_at = now() |
| 1360 | 1422 | WHERE user_id = $2::bigint |
| 1361 | | - 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 |
| 1423 | + 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 |
| 1362 | 1424 | ), user_update AS ( |
| 1363 | 1425 | UPDATE users |
| 1364 | 1426 | SET plan = 'free', |
@@ -1366,7 +1428,7 @@ WITH state AS ( |
| 1366 | 1428 | WHERE id = $2::bigint |
| 1367 | 1429 | RETURNING id |
| 1368 | 1430 | ) |
| 1369 | | -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 FROM state |
| 1431 | +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 |
| 1370 | 1432 | ` |
| 1371 | 1433 | |
| 1372 | 1434 | type MarkUserCanceledParams struct { |
@@ -1394,6 +1456,7 @@ type MarkUserCanceledRow struct { |
| 1394 | 1456 | LastWebhookEventID string |
| 1395 | 1457 | CreatedAt pgtype.Timestamptz |
| 1396 | 1458 | UpdatedAt pgtype.Timestamptz |
| 1459 | + LastEventAt pgtype.Timestamptz |
| 1397 | 1460 | } |
| 1398 | 1461 | |
| 1399 | 1462 | func (q *Queries) MarkUserCanceled(ctx context.Context, db DBTX, arg MarkUserCanceledParams) (MarkUserCanceledRow, error) { |
@@ -1419,6 +1482,7 @@ func (q *Queries) MarkUserCanceled(ctx context.Context, db DBTX, arg MarkUserCan |
| 1419 | 1482 | &i.LastWebhookEventID, |
| 1420 | 1483 | &i.CreatedAt, |
| 1421 | 1484 | &i.UpdatedAt, |
| 1485 | + &i.LastEventAt, |
| 1422 | 1486 | ) |
| 1423 | 1487 | return i, err |
| 1424 | 1488 | } |
@@ -1433,7 +1497,7 @@ UPDATE user_billing_states |
| 1433 | 1497 | last_webhook_event_id = $2::text, |
| 1434 | 1498 | updated_at = now() |
| 1435 | 1499 | WHERE user_id = $3::bigint |
| 1436 | | -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 |
| 1500 | +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 |
| 1437 | 1501 | ` |
| 1438 | 1502 | |
| 1439 | 1503 | type MarkUserPastDueParams struct { |
@@ -1465,6 +1529,7 @@ func (q *Queries) MarkUserPastDue(ctx context.Context, db DBTX, arg MarkUserPast |
| 1465 | 1529 | &i.LastWebhookEventID, |
| 1466 | 1530 | &i.CreatedAt, |
| 1467 | 1531 | &i.UpdatedAt, |
| 1532 | + &i.LastEventAt, |
| 1468 | 1533 | ) |
| 1469 | 1534 | return i, err |
| 1470 | 1535 | } |
@@ -1490,7 +1555,7 @@ WITH state AS ( |
| 1490 | 1555 | last_webhook_event_id = $1::text, |
| 1491 | 1556 | updated_at = now() |
| 1492 | 1557 | WHERE user_id = $2::bigint |
| 1493 | | - 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 |
| 1558 | + 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 |
| 1494 | 1559 | ), user_update AS ( |
| 1495 | 1560 | UPDATE users |
| 1496 | 1561 | SET plan = state.plan, |
@@ -1499,7 +1564,7 @@ WITH state AS ( |
| 1499 | 1564 | WHERE users.id = state.user_id |
| 1500 | 1565 | RETURNING users.id |
| 1501 | 1566 | ) |
| 1502 | | -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 FROM state |
| 1567 | +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 |
| 1503 | 1568 | ` |
| 1504 | 1569 | |
| 1505 | 1570 | type MarkUserPaymentSucceededParams struct { |
@@ -1527,6 +1592,7 @@ type MarkUserPaymentSucceededRow struct { |
| 1527 | 1592 | LastWebhookEventID string |
| 1528 | 1593 | CreatedAt pgtype.Timestamptz |
| 1529 | 1594 | UpdatedAt pgtype.Timestamptz |
| 1595 | + LastEventAt pgtype.Timestamptz |
| 1530 | 1596 | } |
| 1531 | 1597 | |
| 1532 | 1598 | func (q *Queries) MarkUserPaymentSucceeded(ctx context.Context, db DBTX, arg MarkUserPaymentSucceededParams) (MarkUserPaymentSucceededRow, error) { |
@@ -1552,6 +1618,7 @@ func (q *Queries) MarkUserPaymentSucceeded(ctx context.Context, db DBTX, arg Mar |
| 1552 | 1618 | &i.LastWebhookEventID, |
| 1553 | 1619 | &i.CreatedAt, |
| 1554 | 1620 | &i.UpdatedAt, |
| 1621 | + &i.LastEventAt, |
| 1555 | 1622 | ) |
| 1556 | 1623 | return i, err |
| 1557 | 1624 | } |
@@ -1627,7 +1694,7 @@ ON CONFLICT (org_id) DO UPDATE |
| 1627 | 1694 | SET stripe_customer_id = EXCLUDED.stripe_customer_id, |
| 1628 | 1695 | provider = 'stripe', |
| 1629 | 1696 | updated_at = now() |
| 1630 | | -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 |
| 1697 | +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 |
| 1631 | 1698 | ` |
| 1632 | 1699 | |
| 1633 | 1700 | type SetStripeCustomerParams struct { |
@@ -1660,6 +1727,7 @@ func (q *Queries) SetStripeCustomer(ctx context.Context, db DBTX, arg SetStripeC |
| 1660 | 1727 | &i.LastWebhookEventID, |
| 1661 | 1728 | &i.CreatedAt, |
| 1662 | 1729 | &i.UpdatedAt, |
| 1730 | + &i.LastEventAt, |
| 1663 | 1731 | ) |
| 1664 | 1732 | return i, err |
| 1665 | 1733 | } |
@@ -1671,7 +1739,7 @@ ON CONFLICT (user_id) DO UPDATE |
| 1671 | 1739 | SET stripe_customer_id = EXCLUDED.stripe_customer_id, |
| 1672 | 1740 | provider = 'stripe', |
| 1673 | 1741 | updated_at = now() |
| 1674 | | -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 |
| 1742 | +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 |
| 1675 | 1743 | ` |
| 1676 | 1744 | |
| 1677 | 1745 | type SetUserStripeCustomerParams struct { |
@@ -1702,6 +1770,7 @@ func (q *Queries) SetUserStripeCustomer(ctx context.Context, db DBTX, arg SetUse |
| 1702 | 1770 | &i.LastWebhookEventID, |
| 1703 | 1771 | &i.CreatedAt, |
| 1704 | 1772 | &i.UpdatedAt, |
| 1773 | + &i.LastEventAt, |
| 1705 | 1774 | ) |
| 1706 | 1775 | return i, err |
| 1707 | 1776 | } |
@@ -1730,6 +1799,42 @@ func (q *Queries) SetWebhookEventSubject(ctx context.Context, db DBTX, arg SetWe |
| 1730 | 1799 | return err |
| 1731 | 1800 | } |
| 1732 | 1801 | |
| 1802 | +const touchOrgBillingLastEventAt = `-- name: TouchOrgBillingLastEventAt :exec |
| 1803 | +UPDATE org_billing_states |
| 1804 | + SET last_event_at = GREATEST(COALESCE(last_event_at, $1::timestamptz), $1::timestamptz) |
| 1805 | + WHERE org_id = $2::bigint |
| 1806 | +` |
| 1807 | + |
| 1808 | +type TouchOrgBillingLastEventAtParams struct { |
| 1809 | + EventAt pgtype.Timestamptz |
| 1810 | + OrgID int64 |
| 1811 | +} |
| 1812 | + |
| 1813 | +// PRO08 D4: bump last_event_at on successful apply. Conditional so |
| 1814 | +// a fresh apply driven by an out-of-order-but-recent retry doesn't |
| 1815 | +// regress the timestamp (GREATEST). NULL last_event_at acquires the |
| 1816 | +// incoming value. |
| 1817 | +func (q *Queries) TouchOrgBillingLastEventAt(ctx context.Context, db DBTX, arg TouchOrgBillingLastEventAtParams) error { |
| 1818 | + _, err := db.Exec(ctx, touchOrgBillingLastEventAt, arg.EventAt, arg.OrgID) |
| 1819 | + return err |
| 1820 | +} |
| 1821 | + |
| 1822 | +const touchUserBillingLastEventAt = `-- name: TouchUserBillingLastEventAt :exec |
| 1823 | +UPDATE user_billing_states |
| 1824 | + SET last_event_at = GREATEST(COALESCE(last_event_at, $1::timestamptz), $1::timestamptz) |
| 1825 | + WHERE user_id = $2::bigint |
| 1826 | +` |
| 1827 | + |
| 1828 | +type TouchUserBillingLastEventAtParams struct { |
| 1829 | + EventAt pgtype.Timestamptz |
| 1830 | + UserID int64 |
| 1831 | +} |
| 1832 | + |
| 1833 | +func (q *Queries) TouchUserBillingLastEventAt(ctx context.Context, db DBTX, arg TouchUserBillingLastEventAtParams) error { |
| 1834 | + _, err := db.Exec(ctx, touchUserBillingLastEventAt, arg.EventAt, arg.UserID) |
| 1835 | + return err |
| 1836 | +} |
| 1837 | + |
| 1733 | 1838 | const tryAcquireWebhookEventLock = `-- name: TryAcquireWebhookEventLock :one |
| 1734 | 1839 | SELECT pg_try_advisory_xact_lock(hashtext($1)::bigint) AS acquired |
| 1735 | 1840 | ` |