-- name: CreatePayment :one INSERT INTO core.payments ( invoice_id, billing_account_id, payment_method_id, amount, currency, status, failed_at ) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *; -- name: GetPaymentByID :one SELECT * FROM core.payments WHERE payment_id = $1; -- name: GetPaymentsByInvoiceID :many SELECT * FROM core.payments WHERE invoice_id = $1 ORDER BY created_at DESC; -- name: ListPaymentsPage :many -- Operator payments view, paged and searched (operator-list-scale UX-4; -- operator-billing-views D8). No status facet: the operator-list-scale -- spec's "Status filters exist where a status vocabulary exists" -- requirement enumerates grants/invoices/subscriptions/organizations, not -- payments, so this query offers search + pagination only. Carries -- ba.org_id so the handler can resolve and link the organization without a -- cross-module join (see billing_accounts.sql's ListBillingAccountsPage -- comment for why). -- -- sqlc.narg(q): NULL matches every row; set, a case-insensitive substring -- match against the billing account's name (the view's leading identifier -- pre-reorder) or its organization via sqlc.narg(org_ids), pre-resolved in -- Go (operator_billing.go: matchingOrgIDs). -- Joins to core.invoices (not left -- invoice_id is NOT NULL on -- core.payments) to carry invoice_number: invoice-numbers D3 puts the -- number in the title of this view's "View invoice" cross-reference. -- sqlc.narg(exclude_ids): NULL excludes nothing; set, the rows whose id is -- in the array are left out of the page AND out of count(*) OVER(), so the -- pager's total counts only what renders. The operator billing views hand -- it the ids whose stripe mapping records the environment the API key is -- not in (stripe-environment-stamp D7), resolved in the stripe store the -- same way org_ids and invoice_ids are resolved in Go, because no query -- here crosses that module boundary. -- count(*) OVER() carries the true total for the filtered set (design D2). SELECT p.payment_id, p.invoice_id, p.billing_account_id, ba.org_id, p.payment_method_id, p.amount, p.currency, p.status, p.failed_at, p.created_at, p.updated_at, ba.name as billing_account_name, pm.pm_type as payment_method_type, pm.card_brand, pm.card_last4, inv.invoice_number, count(*) OVER() AS total_count FROM core.payments p JOIN core.accounts ba ON p.billing_account_id = ba.billing_account_id LEFT JOIN core.payment_methods pm ON p.payment_method_id = pm.payment_method_id JOIN core.invoices inv ON p.invoice_id = inv.invoice_id WHERE (sqlc.narg(q)::text IS NULL OR ba.name ILIKE '%' || sqlc.narg(q)::text || '%' OR ba.org_id = ANY(sqlc.narg(org_ids)::uuid[])) AND (sqlc.narg(exclude_ids)::uuid[] IS NULL OR p.payment_id <> ALL(sqlc.narg(exclude_ids)::uuid[])) ORDER BY p.created_at DESC LIMIT sqlc.arg(page_limit)::int OFFSET sqlc.arg(page_offset)::int; -- name: CountPaymentsPage :one -- The same predicates as ListPaymentsPage, without paging: the caller runs it twice, -- once with sqlc.narg(exclude_ids) and once without, and the difference is -- how many rows the environment filter holds back from the view the -- operator is looking at. The billing views' absence line reports that -- number under the search and the facet in force -- (stripe-environment-stamp D7), which the page query's count(*) OVER() -- cannot give, because it counts the rows that were shown. SELECT count(*)::bigint AS total_count FROM core.payments p JOIN core.accounts ba ON p.billing_account_id = ba.billing_account_id JOIN core.invoices inv ON p.invoice_id = inv.invoice_id WHERE (sqlc.narg(q)::text IS NULL OR ba.name ILIKE '%' || sqlc.narg(q)::text || '%' OR ba.org_id = ANY(sqlc.narg(org_ids)::uuid[])) AND (sqlc.narg(exclude_ids)::uuid[] IS NULL OR p.payment_id <> ALL(sqlc.narg(exclude_ids)::uuid[])); -- name: ListRecentPayments :many -- Recent payments for the operator landing activity timeline. Joins to -- core.accounts to resolve the org_id (same schema, safe for sqlc). SELECT p.payment_id, p.invoice_id, p.billing_account_id, ba.org_id, p.amount, p.currency, p.status, p.created_at FROM core.payments p JOIN core.accounts ba ON ba.billing_account_id = p.billing_account_id ORDER BY p.created_at DESC LIMIT $1;