Coverage for app/backend/src/couchers/models/donations.py: 100%
38 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-03 11:56 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-03 11:56 +0000
1import enum
2from datetime import datetime
3from typing import TYPE_CHECKING
5from sqlalchemy import BigInteger, ColumnElement, DateTime, Enum, Float, ForeignKey, Integer, String, func
6from sqlalchemy.orm import Mapped, mapped_column, relationship
8from couchers.models.base import Base
10if TYPE_CHECKING:
11 from couchers.models.users import User
14class DonationType(enum.Enum):
15 one_time = enum.auto()
16 monthly = enum.auto()
17 yearly = enum.auto()
20class InvoiceType(enum.Enum):
21 on_platform = enum.auto()
22 external_shop = enum.auto()
25class DonationInitiation(Base, kw_only=True):
26 """
27 Whenever someone initiates a donation through the platform
28 """
30 __tablename__ = "donation_initiations"
32 # reserved up front with next_id()
33 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=True)
34 created: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), init=False)
35 user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True)
37 amount: Mapped[int] = mapped_column(Integer)
38 stripe_checkout_session_id: Mapped[str] = mapped_column(String)
40 donation_type: Mapped[DonationType] = mapped_column(Enum(DonationType))
41 source: Mapped[str | None] = mapped_column(String, default=None)
43 user: Mapped[User] = relationship(init=False, backref="donation_initiations")
45 @classmethod
46 def next_id(cls) -> ColumnElement[int]:
47 """
48 Reserve the next value from this table's id sequence, you can call this at the start of the transaction, then
49 do something with it, and insert it later as the primary key
50 """
51 return func.nextval(func.pg_get_serial_sequence(cls.__tablename__, "id"))
54class Invoice(Base, kw_only=True):
55 """
56 Successful donations, both one-off and recurring
58 Triggered by `payment_intent.succeeded` webhook
59 """
61 __tablename__ = "invoices"
63 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False)
64 created: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), init=False)
65 user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
67 amount: Mapped[float] = mapped_column(Float)
69 stripe_payment_intent_id: Mapped[str] = mapped_column(String, unique=True)
70 stripe_receipt_url: Mapped[str] = mapped_column(String)
72 invoice_type: Mapped[InvoiceType] = mapped_column(Enum(InvoiceType))
74 # null for payments that predate us passing the initiation id to Stripe
75 donation_initiation_id: Mapped[int | None] = mapped_column(
76 ForeignKey("donation_initiations.id"), index=True, default=None
77 )
78 donation_type: Mapped[DonationType | None] = mapped_column(Enum(DonationType), default=None)
80 user: Mapped[User] = relationship(init=False, backref="invoices")