Coverage for polar/transaction/service/balance.py: 36%
61 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-05 17:15 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-05 17:15 +0000
1import uuid 1a
3import structlog 1a
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
16from .base import BaseTransactionService, BaseTransactionServiceError 1a
18log: Logger = structlog.get_logger() 1a
21class BalanceTransactionError(BaseTransactionServiceError): ... 1a
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)
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
47 balance_correlation_key = str(uuid.uuid4())
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 )
82 session.add(outgoing_transaction)
83 session.add(incoming_transaction)
84 await session.flush()
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)
95 return (outgoing_transaction, incoming_transaction)
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)
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 )
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)
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 )
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
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
172 balance_correlation_key = str(uuid.uuid4())
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 )
209 session.add(outgoing_reversal)
210 session.add(incoming_reversal)
211 await session.flush()
213 return (outgoing_reversal, incoming_reversal)
216balance_transaction = BalanceTransactionService(Transaction) 1a