Coverage for polar/payout/repository.py: 34%

52 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-12-05 15:52 +0000

1from collections.abc import Sequence 1a

2from datetime import timedelta 1a

3from uuid import UUID 1a

4 

5from sqlalchemy import Select, false 1a

6from sqlalchemy.orm import joinedload 1a

7 

8from polar.auth.models import AuthSubject, Organization, User, is_organization, is_user 1a

9from polar.config import settings 1a

10from polar.enums import AccountType 1a

11from polar.kit.repository import ( 1a

12 Options, 

13 RepositoryBase, 

14 RepositorySoftDeletionIDMixin, 

15 RepositorySoftDeletionMixin, 

16 RepositorySortingMixin, 

17 SortingClause, 

18) 

19from polar.kit.utils import utc_now 1a

20from polar.models import Account, Payout, Transaction 1a

21from polar.models.payout import PayoutStatus 1a

22from polar.payout.sorting import PayoutSortProperty 1a

23 

24 

25class PayoutRepository( 1a

26 RepositorySoftDeletionIDMixin[Payout, UUID], 

27 RepositorySoftDeletionMixin[Payout], 

28 RepositorySortingMixin[Payout, PayoutSortProperty], 

29 RepositoryBase[Payout], 

30): 

31 model = Payout 1a

32 sorting_enum = PayoutSortProperty 1a

33 

34 async def count_by_account(self, account: UUID) -> int: 1a

35 statement = self.get_base_statement().where(Payout.account_id == account) 

36 return await self.count(statement) 

37 

38 async def get_by_processor_id( 1a

39 self, 

40 processor: AccountType, 

41 processor_id: str, 

42 *, 

43 options: Options = (), 

44 ) -> Payout | None: 

45 statement = ( 

46 self.get_base_statement() 

47 .where( 

48 Payout.processor == processor, 

49 Payout.processor_id == processor_id, 

50 ) 

51 .options(*options) 

52 ) 

53 return await self.get_one_or_none(statement) 

54 

55 async def get_all_stripe_pending( 1a

56 self, delay: timedelta = settings.ACCOUNT_PAYOUT_DELAY 

57 ) -> Sequence[Payout]: 

58 statement = ( 

59 self.get_base_statement() 

60 .distinct(Payout.account_id) 

61 .where( 

62 Payout.processor == AccountType.stripe, 

63 Payout.status == PayoutStatus.pending, 

64 Payout.processor_id.is_(None), 

65 Payout.created_at < utc_now() - delay, 

66 ) 

67 .order_by(Payout.account_id.asc(), Payout.created_at.asc()) 

68 ) 

69 return await self.get_all(statement) 

70 

71 async def get_by_account_and_invoice_number( 1a

72 self, account: UUID, invoice_number: str 

73 ) -> Payout | None: 

74 statement = self.get_base_statement().where( 

75 Payout.account_id == account, 

76 Payout.invoice_number == invoice_number, 

77 ) 

78 return await self.get_one_or_none(statement) 

79 

80 def get_eager_options(self) -> Options: 1a

81 return ( 

82 joinedload(Payout.account), 

83 joinedload(Payout.transaction).selectinload( 

84 Transaction.incurred_transactions 

85 ), 

86 ) 

87 

88 def get_readable_statement( 1a

89 self, auth_subject: AuthSubject[User | Organization] 

90 ) -> Select[tuple[Payout]]: 

91 statement = self.get_base_statement() 

92 

93 if is_user(auth_subject): 

94 user = auth_subject.subject 

95 statement = statement.join(Payout.account).where( 

96 Account.admin_id == user.id 

97 ) 

98 elif is_organization(auth_subject): 

99 # Only the admin of the account can access it 

100 statement = statement.where(false()) 

101 

102 return statement 

103 

104 def get_sorting_clause(self, property: PayoutSortProperty) -> SortingClause: 1a

105 match property: 

106 case PayoutSortProperty.created_at: 

107 return Payout.created_at 

108 case PayoutSortProperty.amount: 

109 return Payout.amount 

110 case PayoutSortProperty.fees_amount: 

111 return Payout.fees_amount 

112 case PayoutSortProperty.status: 

113 return Payout.status 

114 case PayoutSortProperty.paid_at: 

115 return Payout.paid_at 

116 case PayoutSortProperty.account_id: 

117 return Payout.account_id