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
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 12:42 +0000
1import uuid
3import structlog
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
16from .base import BaseTransactionService, BaseTransactionServiceError
18log: Logger = structlog.get_logger()
21class BalanceTransactionError(BaseTransactionServiceError): ... 21 ↛ 24line 21 didn't jump to line 24 because
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)
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
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(
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(
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(
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)