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

61 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 12:42 +0000

1import uuid 

2 

3import structlog 

4 

5from polar.account.repository import AccountRepository 

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

7from polar.integrations.stripe.utils import get_expandable_id 

8from polar.kit.utils import generate_uuid 

9from polar.logging import Logger 

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

11from polar.models.transaction import PlatformFeeType, TransactionType 

12from polar.organization.repository import OrganizationRepository 

13from polar.organization.service import organization as organization_service 

14from polar.postgres import AsyncSession 

15 

16from .base import BaseTransactionService, BaseTransactionServiceError 

17 

18log: Logger = structlog.get_logger() 

19 

20 

21class BalanceTransactionError(BaseTransactionServiceError): ... 21 ↛ 24line 21 didn't jump to line 24 because

22 

23 

24class PaymentTransactionForChargeDoesNotExist(BalanceTransactionError): 

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

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): 

32 async def create_balance( 

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( 

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( 

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( 

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)