Coverage for polar/transaction/service/balance.py: 36%

61 statements  

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

1import uuid 1a

2 

3import structlog 1a

4 

5from polar.account.repository import AccountRepository 1a

6from polar.integrations.stripe.service import stripe as stripe_service 1a

7from polar.integrations.stripe.utils import get_expandable_id 1a

8from polar.kit.utils import generate_uuid 1a

9from polar.logging import Logger 1a

10from polar.models import Account, IssueReward, Order, Pledge, Transaction 1a

11from polar.models.transaction import PlatformFeeType, TransactionType 1a

12from polar.organization.repository import OrganizationRepository 1a

13from polar.organization.service import organization as organization_service 1a

14from polar.postgres import AsyncSession 1a

15 

16from .base import BaseTransactionService, BaseTransactionServiceError 1a

17 

18log: Logger = structlog.get_logger() 1a

19 

20 

21class BalanceTransactionError(BaseTransactionServiceError): ... 1a

22 

23 

24class PaymentTransactionForChargeDoesNotExist(BalanceTransactionError): 1a

25 def __init__(self, charge_id: str) -> None: 1a

26 self.charge_id = charge_id 

27 message = f"No payment transaction exist for charge {charge_id}." 

28 super().__init__(message) 

29 

30 

31class BalanceTransactionService(BaseTransactionService): 1a

32 async def create_balance( 1a

33 self, 

34 session: AsyncSession, 

35 *, 

36 source_account: Account | None, 

37 destination_account: Account | None, 

38 amount: int, 

39 payment_transaction: Transaction | None = None, 

40 pledge: Pledge | None = None, 

41 order: Order | None = None, 

42 issue_reward: IssueReward | None = None, 

43 platform_fee_type: PlatformFeeType | None = None, 

44 ) -> tuple[Transaction, Transaction]: 

45 currency = "usd" # FIXME: Main Polar currency 

46 

47 balance_correlation_key = str(uuid.uuid4()) 

48 

49 outgoing_transaction = Transaction( 

50 id=generate_uuid(), 

51 account=source_account, 

52 type=TransactionType.balance, 

53 currency=currency, 

54 amount=-amount, # Subtract the amount 

55 account_currency=currency, 

56 account_amount=-amount, 

57 tax_amount=0, 

58 balance_correlation_key=balance_correlation_key, 

59 pledge=pledge, 

60 issue_reward=issue_reward, 

61 order=order, 

62 payment_transaction=payment_transaction, 

63 platform_fee_type=platform_fee_type, 

64 ) 

65 incoming_transaction = Transaction( 

66 id=generate_uuid(), 

67 account=destination_account, 

68 type=TransactionType.balance, 

69 currency=currency, 

70 amount=amount, # Add the amount 

71 account_currency=currency, 

72 account_amount=amount, 

73 tax_amount=0, 

74 balance_correlation_key=balance_correlation_key, 

75 pledge=pledge, 

76 issue_reward=issue_reward, 

77 order=order, 

78 payment_transaction=payment_transaction, 

79 platform_fee_type=platform_fee_type, 

80 ) 

81 

82 session.add(outgoing_transaction) 

83 session.add(incoming_transaction) 

84 await session.flush() 

85 

86 if destination_account is not None: 

87 # Check organization review threshold instead of account 

88 organization_repository = OrganizationRepository.from_session(session) 

89 organizations = await organization_repository.get_all_by_account( 

90 destination_account.id 

91 ) 

92 for organization in organizations: 

93 await organization_service.check_review_threshold(session, organization) 

94 

95 return (outgoing_transaction, incoming_transaction) 

96 

97 async def create_balance_from_charge( 1a

98 self, 

99 session: AsyncSession, 

100 *, 

101 source_account: Account | None, 

102 destination_account: Account | None, 

103 charge_id: str, 

104 amount: int, 

105 pledge: Pledge | None = None, 

106 order: Order | None = None, 

107 issue_reward: IssueReward | None = None, 

108 ) -> tuple[Transaction, Transaction]: 

109 payment_transaction = await self.get_by( 

110 session, type=TransactionType.payment, charge_id=charge_id 

111 ) 

112 if payment_transaction is None: 

113 raise PaymentTransactionForChargeDoesNotExist(charge_id) 

114 

115 return await self.create_balance( 

116 session, 

117 source_account=source_account, 

118 destination_account=destination_account, 

119 payment_transaction=payment_transaction, 

120 amount=amount, 

121 pledge=pledge, 

122 order=order, 

123 issue_reward=issue_reward, 

124 ) 

125 

126 async def create_balance_from_payment_intent( 1a

127 self, 

128 session: AsyncSession, 

129 *, 

130 source_account: Account | None, 

131 destination_account: Account | None, 

132 payment_intent_id: str, 

133 amount: int, 

134 pledge: Pledge | None = None, 

135 order: Order | None = None, 

136 issue_reward: IssueReward | None = None, 

137 ) -> tuple[Transaction, Transaction]: 

138 payment_intent = await stripe_service.retrieve_intent(payment_intent_id) 

139 assert payment_intent.latest_charge is not None 

140 charge_id = get_expandable_id(payment_intent.latest_charge) 

141 

142 return await self.create_balance_from_charge( 

143 session, 

144 source_account=source_account, 

145 destination_account=destination_account, 

146 charge_id=charge_id, 

147 amount=amount, 

148 pledge=pledge, 

149 order=order, 

150 issue_reward=issue_reward, 

151 ) 

152 

153 async def create_reversal_balance( 1a

154 self, 

155 session: AsyncSession, 

156 *, 

157 balance_transactions: tuple[Transaction, Transaction], 

158 amount: int, 

159 platform_fee_type: PlatformFeeType | None = None, 

160 outgoing_incurred_by: Transaction | None = None, 

161 incoming_incurred_by: Transaction | None = None, 

162 ) -> tuple[Transaction, Transaction]: 

163 currency = "usd" # FIXME: Main Polar currency 

164 

165 outgoing, incoming = balance_transactions 

166 source_account_id = incoming.account_id 

167 assert source_account_id is not None 

168 account_repository = AccountRepository.from_session(session) 

169 source_account = await account_repository.get_by_id(source_account_id) 

170 assert source_account is not None 

171 

172 balance_correlation_key = str(uuid.uuid4()) 

173 

174 outgoing_reversal = Transaction( 

175 id=generate_uuid(), 

176 account=source_account, # User account 

177 type=TransactionType.balance, 

178 currency=currency, 

179 amount=-amount, # Subtract the amount 

180 account_currency=currency, 

181 account_amount=-amount, 

182 tax_amount=0, 

183 balance_correlation_key=balance_correlation_key, 

184 platform_fee_type=platform_fee_type, 

185 pledge_id=outgoing.pledge_id, 

186 issue_reward_id=outgoing.issue_reward_id, 

187 order_id=outgoing.order_id, 

188 balance_reversal_transaction=incoming, 

189 incurred_by_transaction=outgoing_incurred_by, 

190 ) 

191 incoming_reversal = Transaction( 

192 id=generate_uuid(), 

193 account=None, # Polar account 

194 type=TransactionType.balance, 

195 currency=currency, 

196 amount=amount, # Add the amount 

197 account_currency=currency, 

198 account_amount=amount, 

199 tax_amount=0, 

200 balance_correlation_key=balance_correlation_key, 

201 platform_fee_type=platform_fee_type, 

202 pledge_id=outgoing.pledge_id, 

203 issue_reward_id=outgoing.issue_reward_id, 

204 order_id=outgoing.order_id, 

205 balance_reversal_transaction=outgoing, 

206 incurred_by_transaction=incoming_incurred_by, 

207 ) 

208 

209 session.add(outgoing_reversal) 

210 session.add(incoming_reversal) 

211 await session.flush() 

212 

213 return (outgoing_reversal, incoming_reversal) 

214 

215 

216balance_transaction = BalanceTransactionService(Transaction) 1a