@@ -1019,7 +1019,7 @@ func (q *Queries) ListFailedWebhookEvents(ctx context.Context, db DBTX, limit in |
| 1019 | 1019 | } |
| 1020 | 1020 | |
| 1021 | 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 FROM billing_invoices |
| 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 | 1023 | WHERE subject_kind = 'org' AND subject_id = $1 |
| 1024 | 1024 | ORDER BY created_at DESC, id DESC |
| 1025 | 1025 | LIMIT $2 |
@@ -1067,6 +1067,7 @@ func (q *Queries) ListInvoicesForOrg(ctx context.Context, db DBTX, arg ListInvoi |
| 1067 | 1067 | &i.UpdatedAt, |
| 1068 | 1068 | &i.SubjectKind, |
| 1069 | 1069 | &i.SubjectID, |
| 1070 | + &i.RefundedAt, |
| 1070 | 1071 | ); err != nil { |
| 1071 | 1072 | return nil, err |
| 1072 | 1073 | } |
@@ -1079,7 +1080,7 @@ func (q *Queries) ListInvoicesForOrg(ctx context.Context, db DBTX, arg ListInvoi |
| 1079 | 1080 | } |
| 1080 | 1081 | |
| 1081 | 1082 | const listInvoicesForSubject = `-- name: ListInvoicesForSubject :many |
| 1082 | | -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 FROM billing_invoices |
| 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 |
| 1083 | 1084 | WHERE subject_kind = $1::billing_subject_kind |
| 1084 | 1085 | AND subject_id = $2::bigint |
| 1085 | 1086 | ORDER BY created_at DESC, id DESC |
@@ -1129,6 +1130,7 @@ func (q *Queries) ListInvoicesForSubject(ctx context.Context, db DBTX, arg ListI |
| 1129 | 1130 | &i.UpdatedAt, |
| 1130 | 1131 | &i.SubjectKind, |
| 1131 | 1132 | &i.SubjectID, |
| 1133 | + &i.RefundedAt, |
| 1132 | 1134 | ); err != nil { |
| 1133 | 1135 | return nil, err |
| 1134 | 1136 | } |
@@ -1265,6 +1267,56 @@ func (q *Queries) MarkCanceled(ctx context.Context, db DBTX, arg MarkCanceledPar |
| 1265 | 1267 | return i, err |
| 1266 | 1268 | } |
| 1267 | 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 | + |
| 1268 | 1320 | const markPastDue = `-- name: MarkPastDue :one |
| 1269 | 1321 | UPDATE org_billing_states |
| 1270 | 1322 | SET subscription_status = 'past_due', |
@@ -1920,7 +1972,7 @@ ON CONFLICT (provider, stripe_invoice_id) DO UPDATE |
| 1920 | 1972 | paid_at = EXCLUDED.paid_at, |
| 1921 | 1973 | voided_at = EXCLUDED.voided_at, |
| 1922 | 1974 | updated_at = now() |
| 1923 | | -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 |
| 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 |
| 1924 | 1976 | ` |
| 1925 | 1977 | |
| 1926 | 1978 | type UpsertInvoiceParams struct { |
@@ -1994,6 +2046,7 @@ func (q *Queries) UpsertInvoice(ctx context.Context, db DBTX, arg UpsertInvoiceP |
| 1994 | 2046 | &i.UpdatedAt, |
| 1995 | 2047 | &i.SubjectKind, |
| 1996 | 2048 | &i.SubjectID, |
| 2049 | + &i.RefundedAt, |
| 1997 | 2050 | ) |
| 1998 | 2051 | return i, err |
| 1999 | 2052 | } |
@@ -2060,7 +2113,7 @@ ON CONFLICT (provider, stripe_invoice_id) DO UPDATE |
| 2060 | 2113 | paid_at = EXCLUDED.paid_at, |
| 2061 | 2114 | voided_at = EXCLUDED.voided_at, |
| 2062 | 2115 | updated_at = now() |
| 2063 | | -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 |
| 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 |
| 2064 | 2117 | ` |
| 2065 | 2118 | |
| 2066 | 2119 | type UpsertInvoiceForSubjectParams struct { |
@@ -2136,6 +2189,7 @@ func (q *Queries) UpsertInvoiceForSubject(ctx context.Context, db DBTX, arg Upse |
| 2136 | 2189 | &i.UpdatedAt, |
| 2137 | 2190 | &i.SubjectKind, |
| 2138 | 2191 | &i.SubjectID, |
| 2192 | + &i.RefundedAt, |
| 2139 | 2193 | ) |
| 2140 | 2194 | return i, err |
| 2141 | 2195 | } |