Coverage for polar/refund/service.py: 19%

286 statements  

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

1from collections.abc import Sequence 1a

2from typing import Any, Literal, TypeAlias 1a

3from uuid import UUID 1a

4 

5import stripe as stripe_lib 1a

6import structlog 1a

7from sqlalchemy import Select, UnaryExpression, asc, desc, select 1a

8from sqlalchemy.dialects import postgresql 1a

9 

10from polar.auth.models import AuthSubject, is_organization, is_user 1a

11from polar.benefit.grant.service import benefit_grant as benefit_grant_service 1a

12from polar.customer.repository import CustomerRepository 1a

13from polar.event.service import event as event_service 1a

14from polar.event.system import OrderRefundedMetadata, SystemEvent, build_system_event 1a

15from polar.exceptions import PolarError, PolarRequestValidationError, ResourceNotFound 1a

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

17from polar.kit.db.postgres import AsyncSession 1a

18from polar.kit.pagination import PaginationParams, paginate 1a

19from polar.kit.services import ResourceServiceReader 1a

20from polar.kit.sorting import Sorting 1a

21from polar.kit.utils import utc_now 1a

22from polar.logging import Logger 1a

23from polar.models import ( 1a

24 Order, 

25 Organization, 

26 Pledge, 

27 Transaction, 

28 User, 

29 UserOrganization, 

30) 

31from polar.models.refund import Refund, RefundReason, RefundStatus 1a

32from polar.models.webhook_endpoint import WebhookEventType 1a

33from polar.order.repository import OrderRepository 1a

34from polar.order.service import order as order_service 1a

35from polar.organization.repository import OrganizationRepository 1a

36from polar.pledge.service import pledge as pledge_service 1a

37from polar.transaction.service.payment import ( 1a

38 payment_transaction as payment_transaction_service, 

39) 

40from polar.transaction.service.refund import ( 1a

41 RefundTransactionAlreadyExistsError, 

42 RefundTransactionDoesNotExistError, 

43) 

44from polar.transaction.service.refund import ( 1a

45 refund_transaction as refund_transaction_service, 

46) 

47from polar.wallet.service import wallet as wallet_service 1a

48from polar.webhook.service import webhook as webhook_service 1a

49 

50from .schemas import InternalRefundCreate, RefundCreate 1a

51from .sorting import RefundSortProperty 1a

52 

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

54 

55ChargeID: TypeAlias = str 1a

56RefundTransaction: TypeAlias = Transaction 1a

57RefundedResources: TypeAlias = tuple[ 1a

58 ChargeID, RefundTransaction, Order | None, Pledge | None 

59] 

60Created: TypeAlias = bool 1a

61RefundAmount: TypeAlias = int 1a

62RefundTaxAmount: TypeAlias = int 1a

63FullRefund: TypeAlias = bool 1a

64 

65 

66class RefundError(PolarError): ... 1a

67 

68 

69class RefundUnknownPayment(ResourceNotFound): 1a

70 def __init__( 1a

71 self, id: str | UUID, payment_type: Literal["charge", "order", "pledge"] 

72 ) -> None: 

73 self.id = id 

74 message = f"Refund issued for unknown {payment_type}: {id}" 

75 super().__init__(message, 404) 

76 

77 

78class RefundedAlready(RefundError): 1a

79 def __init__(self, order: Order) -> None: 1a

80 self.order = order 

81 message = f"Order is already fully refunded: {order.id}" 

82 super().__init__(message, 403) 

83 

84 

85class RefundAmountTooHigh(RefundError): 1a

86 def __init__(self, order: Order) -> None: 1a

87 self.order = order 

88 message = ( 

89 f"Refund amount exceeds remaining order balance: {order.refundable_amount}" 

90 ) 

91 super().__init__(message, 400) 

92 

93 

94class RevokeSubscriptionBenefitsProhibited(RefundError): 1a

95 def __init__(self) -> None: 1a

96 message = "Subscription benefits can only be revoked upon cancellation" 

97 super().__init__(message, 400) 

98 

99 

100class RefundService(ResourceServiceReader[Refund]): 1a

101 async def get_list( 1a

102 self, 

103 session: AsyncSession, 

104 auth_subject: AuthSubject[User | Organization], 

105 *, 

106 id: Sequence[UUID] | None = None, 

107 organization_id: Sequence[UUID] | None = None, 

108 order_id: Sequence[UUID] | None = None, 

109 subscription_id: Sequence[UUID] | None = None, 

110 customer_id: Sequence[UUID] | None = None, 

111 succeeded: bool | None = None, 

112 pagination: PaginationParams, 

113 sorting: list[Sorting[RefundSortProperty]] = [ 

114 (RefundSortProperty.created_at, True) 

115 ], 

116 ) -> tuple[Sequence[Refund], int]: 

117 statement = self._get_readable_refund_statement(auth_subject) 

118 if id is not None: 

119 statement = statement.where(Refund.id.in_(id)) 

120 

121 if organization_id is not None: 

122 statement = statement.where(Refund.organization_id.in_(organization_id)) 

123 

124 if order_id is not None: 

125 statement = statement.where(Refund.order_id.in_(order_id)) 

126 

127 if subscription_id is not None: 

128 statement = statement.where(Refund.subscription_id.in_(subscription_id)) 

129 

130 if customer_id is not None: 

131 statement = statement.where(Refund.customer_id.in_(customer_id)) 

132 

133 if succeeded: 

134 statement = statement.where(Refund.status == RefundStatus.succeeded) 

135 

136 order_by_clauses: list[UnaryExpression[Any]] = [] 

137 for criterion, is_desc in sorting: 

138 clause_function = desc if is_desc else asc 

139 if criterion == RefundSortProperty.created_at: 

140 order_by_clauses.append(clause_function(Refund.created_at)) 

141 elif criterion == RefundSortProperty.amount: 

142 order_by_clauses.append(clause_function(Refund.amount)) 

143 

144 statement = statement.order_by(*order_by_clauses) 

145 return await paginate(session, statement, pagination=pagination) 

146 

147 async def user_create( 1a

148 self, 

149 session: AsyncSession, 

150 auth_subject: AuthSubject[User | Organization], 

151 create_schema: RefundCreate, 

152 ) -> Refund: 

153 order_repository = OrderRepository.from_session(session) 

154 order = await order_repository.get_one_or_none( 

155 order_repository.get_readable_statement(auth_subject) 

156 .where(Order.id == create_schema.order_id) 

157 .options(*order_repository.get_eager_options()) 

158 ) 

159 if not order: 

160 raise PolarRequestValidationError( 

161 [ 

162 { 

163 "type": "value_error", 

164 "loc": ("body", "order_id"), 

165 "msg": "Order not found", 

166 "input": create_schema.order_id, 

167 } 

168 ] 

169 ) 

170 

171 return await self.create(session, order, create_schema=create_schema) 

172 

173 async def create( 1a

174 self, 

175 session: AsyncSession, 

176 order: Order, 

177 create_schema: RefundCreate, 

178 ) -> Refund: 

179 if order.refunded: 

180 raise RefundedAlready(order) 

181 

182 is_subscription = order.subscription_id is not None 

183 if create_schema.revoke_benefits and is_subscription: 

184 raise RevokeSubscriptionBenefitsProhibited() 

185 

186 refund_amount = create_schema.amount 

187 refund_tax_amount = self.calculate_tax(order, create_schema.amount) 

188 payment = await payment_transaction_service.get_by_order_id(session, order.id) 

189 if not (payment and payment.charge_id): 

190 raise RefundUnknownPayment(order.id, payment_type="order") 

191 

192 refund_total = refund_amount + refund_tax_amount 

193 stripe_metadata = dict( 

194 order_id=str(order.id), 

195 charge_id=str(payment.charge_id), 

196 amount=str(create_schema.amount), 

197 refund_amount=str(refund_amount), 

198 refund_tax_amount=str(refund_tax_amount), 

199 revoke_benefits="1" if create_schema.revoke_benefits else "0", 

200 ) 

201 

202 try: 

203 stripe_refund = await stripe_service.create_refund( 

204 charge_id=payment.charge_id, 

205 amount=refund_total, 

206 reason=RefundReason.to_stripe(create_schema.reason), 

207 metadata=stripe_metadata, 

208 ) 

209 except stripe_lib.InvalidRequestError as e: 

210 if e.code == "charge_already_refunded": 

211 log.warning("refund.attempted_already_refunded", order_id=order.id) 

212 raise RefundedAlready(order) 

213 else: 

214 raise e 

215 

216 internal_create_schema = self.build_create_schema_from_stripe( 

217 stripe_refund, 

218 order=order, 

219 ) 

220 internal_create_schema.reason = create_schema.reason 

221 internal_create_schema.comment = create_schema.comment 

222 internal_create_schema.revoke_benefits = create_schema.revoke_benefits 

223 internal_create_schema.metadata = create_schema.metadata 

224 refund = await self._create( 

225 session, 

226 internal_create_schema, 

227 charge_id=payment.charge_id, 

228 payment=payment, 

229 order=order, 

230 ) 

231 

232 if refund.revoke_benefits: 

233 await self.enqueue_benefits_revokation(session, order) 

234 

235 return refund 

236 

237 async def upsert_from_stripe( 1a

238 self, session: AsyncSession, stripe_refund: stripe_lib.Refund 

239 ) -> Refund: 

240 refund = await self.get_by(session, processor_id=stripe_refund.id) 

241 if refund: 

242 return await self.update_from_stripe(session, refund, stripe_refund) 

243 return await self.create_from_stripe(session, stripe_refund) 

244 

245 async def create_from_stripe( 1a

246 self, 

247 session: AsyncSession, 

248 stripe_refund: stripe_lib.Refund, 

249 ) -> Refund: 

250 resources = await self._get_resources(session, stripe_refund) 

251 charge_id, payment, order, pledge = resources 

252 

253 internal_create_schema = self.build_create_schema_from_stripe( 

254 stripe_refund, 

255 order=order, 

256 pledge=pledge, 

257 ) 

258 return await self._create( 

259 session, 

260 internal_create_schema, 

261 charge_id=charge_id, 

262 payment=payment, 

263 order=order, 

264 pledge=pledge, 

265 ) 

266 

267 async def update_from_stripe( 1a

268 self, 

269 session: AsyncSession, 

270 refund: Refund, 

271 stripe_refund: stripe_lib.Refund, 

272 ) -> Refund: 

273 resources = await self._get_resources(session, stripe_refund) 

274 charge_id, payment, order, pledge = resources 

275 updated = self.build_create_schema_from_stripe( 

276 stripe_refund, 

277 order=order, 

278 pledge=pledge, 

279 ) 

280 

281 had_succeeded = refund.succeeded 

282 

283 # Reference: https://docs.stripe.com/refunds#see-also 

284 # Only `metadata` and `destination_details` should update according to 

285 # docs, but a pending refund can surely become `succeeded`, `canceled` or `failed` 

286 refund.status = updated.status 

287 refund.failure_reason = updated.failure_reason 

288 refund.destination_details = updated.destination_details 

289 refund.processor_receipt_number = updated.processor_receipt_number 

290 session.add(refund) 

291 

292 transitioned_to_succeeded = refund.succeeded and not had_succeeded 

293 

294 if transitioned_to_succeeded: 

295 refund_transaction = await self._create_refund_transaction( 

296 session, 

297 charge_id=charge_id, 

298 refund=refund, 

299 payment=payment, 

300 order=order, 

301 pledge=pledge, 

302 ) 

303 # Double check transition by ensuring ledger entry was made 

304 transitioned_to_succeeded = refund_transaction is not None 

305 

306 reverted = had_succeeded and refund.status in { 

307 RefundStatus.canceled, 

308 RefundStatus.failed, 

309 } 

310 if reverted: 

311 await self._revert_refund_transaction( 

312 session, 

313 charge_id=charge_id, 

314 refund=refund, 

315 payment=payment, 

316 order=order, 

317 pledge=pledge, 

318 ) 

319 

320 await session.flush() 

321 log.info( 

322 "refund.updated", 

323 id=refund.id, 

324 amount=refund.amount, 

325 tax_amount=refund.tax_amount, 

326 order_id=refund.order_id, 

327 reason=refund.reason, 

328 processor=refund.processor, 

329 processor_id=refund.processor_id, 

330 ) 

331 if order is None: 

332 return refund 

333 

334 organization_repository = OrganizationRepository.from_session(session) 

335 organization = await organization_repository.get_by_id(order.organization.id) 

336 if organization is None: 

337 return refund 

338 

339 await self._on_updated(session, organization, refund) 

340 if transitioned_to_succeeded: 

341 await self._on_succeeded(session, organization, order) 

342 return refund 

343 

344 async def enqueue_benefits_revokation( 1a

345 self, session: AsyncSession, order: Order 

346 ) -> None: 

347 customer_repository = CustomerRepository.from_session(session) 

348 customer = await customer_repository.get_by_id( 

349 order.customer_id, include_deleted=True 

350 ) 

351 if customer is None: 

352 return 

353 

354 if not order.product: 

355 return 

356 

357 await benefit_grant_service.enqueue_benefits_grants( 

358 session, 

359 task="revoke", 

360 customer=customer, 

361 product=order.product, 

362 order=order, 

363 ) 

364 

365 def calculate_tax( 1a

366 self, 

367 order: Order, 

368 refund_amount: int, 

369 ) -> int: 

370 if refund_amount > order.refundable_amount: 

371 raise RefundAmountTooHigh(order) 

372 

373 # Trigger full refund of remaining balance 

374 if refund_amount == order.refundable_amount: 

375 return order.refundable_tax_amount 

376 

377 ratio = order.tax_amount / order.net_amount 

378 tax_amount = round(refund_amount * ratio) 

379 return tax_amount 

380 

381 def calculate_tax_from_stripe( 1a

382 self, 

383 order: Order, 

384 stripe_amount: int, 

385 ) -> tuple[RefundAmount, RefundTaxAmount]: 

386 if stripe_amount == order.remaining_balance: 

387 return order.refundable_amount, order.refundable_tax_amount 

388 

389 if not order.taxed: 

390 return stripe_amount, 0 

391 

392 # Reverse engineer taxes from Stripe amount (always inclusive) 

393 refunded_tax_amount = abs( 

394 round((order.tax_amount * stripe_amount) / order.total_amount) 

395 ) 

396 refunded_amount = stripe_amount - refunded_tax_amount 

397 return refunded_amount, refunded_tax_amount 

398 

399 def build_create_schema_from_stripe( 1a

400 self, 

401 stripe_refund: stripe_lib.Refund, 

402 *, 

403 order: Order | None = None, 

404 pledge: Pledge | None = None, 

405 ) -> InternalRefundCreate: 

406 order_id = None 

407 subscription_id = None 

408 customer_id = None 

409 organization_id = None 

410 refunded_amount = stripe_refund.amount 

411 refunded_tax_amount = 0 # Default since pledges don't have VAT 

412 pledge_id = pledge.id if pledge else None 

413 

414 if order: 

415 order_id = order.id 

416 subscription_id = order.subscription_id 

417 customer_id = order.customer_id 

418 organization_id = order.organization.id 

419 refunded_amount, refunded_tax_amount = self.calculate_tax_from_stripe( 

420 order, 

421 stripe_amount=stripe_refund.amount, 

422 ) 

423 

424 schema = InternalRefundCreate.from_stripe( 

425 stripe_refund, 

426 refunded_amount=refunded_amount, 

427 refunded_tax_amount=refunded_tax_amount, 

428 order_id=order_id, 

429 subscription_id=subscription_id, 

430 customer_id=customer_id, 

431 organization_id=organization_id, 

432 pledge_id=pledge_id, 

433 ) 

434 return schema 

435 

436 def build_instance_from_stripe( 1a

437 self, 

438 stripe_refund: stripe_lib.Refund, 

439 *, 

440 order: Order | None = None, 

441 pledge: Pledge | None = None, 

442 ) -> Refund: 

443 internal_create_schema = self.build_create_schema_from_stripe( 

444 stripe_refund, 

445 order=order, 

446 pledge=pledge, 

447 ) 

448 instance = Refund(**internal_create_schema.model_dump()) 

449 return instance 

450 

451 ############################################################################### 

452 # INTERNALS 

453 ############################################################################### 

454 

455 async def _create( 1a

456 self, 

457 session: AsyncSession, 

458 internal_create_schema: InternalRefundCreate, 

459 *, 

460 charge_id: str, 

461 payment: Transaction, 

462 order: Order | None = None, 

463 pledge: Pledge | None = None, 

464 ) -> Refund: 

465 # Upsert to handle race condition from Stripe `refund.created`. 

466 # Could be fired standalone from manual support operations in Stripe dashboard. 

467 statement = ( 

468 postgresql.insert(Refund) 

469 .values(**internal_create_schema.model_dump(by_alias=True)) 

470 .on_conflict_do_update( 

471 index_elements=[Refund.processor_id], 

472 # Only update `modified_at` as race conditions from API & 

473 # webhook creation should only contain the same data. 

474 set_=dict( 

475 modified_at=utc_now(), 

476 ), 

477 ) 

478 .returning(Refund) 

479 .execution_options(populate_existing=True) 

480 ) 

481 res = await session.execute(statement) 

482 instance = res.scalars().one() 

483 # Avoid processing creation twice, i.e updated vs. inserted 

484 if instance.modified_at: 

485 return instance 

486 

487 if instance.succeeded: 

488 await self._create_refund_transaction( 

489 session, 

490 charge_id=charge_id, 

491 refund=instance, 

492 payment=payment, 

493 order=order, 

494 pledge=pledge, 

495 ) 

496 

497 await session.flush() 

498 log.info( 

499 "refund.create", 

500 id=instance.id, 

501 amount=instance.amount, 

502 tax_amount=instance.tax_amount, 

503 order_id=instance.order_id, 

504 pledge_id=instance.pledge_id, 

505 reason=instance.reason, 

506 processor=instance.processor, 

507 processor_id=instance.processor_id, 

508 ) 

509 if order is None: 

510 return instance 

511 

512 organization = order.organization 

513 

514 await self._on_created(session, organization, instance) 

515 if instance.succeeded: 

516 await self._on_succeeded(session, organization, order) 

517 return instance 

518 

519 async def _create_refund_transaction( 1a

520 self, 

521 session: AsyncSession, 

522 *, 

523 charge_id: str, 

524 refund: Refund, 

525 payment: Transaction, 

526 order: Order | None = None, 

527 pledge: Pledge | None = None, 

528 ) -> Transaction | None: 

529 try: 

530 transaction = await refund_transaction_service.create( 

531 session, 

532 charge_id=charge_id, 

533 payment_transaction=payment, 

534 refund=refund, 

535 ) 

536 except RefundTransactionAlreadyExistsError: 

537 return None 

538 

539 if order: 

540 await order_service.update_refunds( 

541 session, 

542 order, 

543 refunded_amount=refund.amount, 

544 refunded_tax_amount=refund.tax_amount, 

545 ) 

546 

547 # Send order.updated webhook 

548 await order_service.send_webhook( 

549 session, order, WebhookEventType.order_updated 

550 ) 

551 

552 # Revert the tax transaction in the tax processor ledger 

553 if order.tax_transaction_processor_id and order.tax_amount > 0: 

554 if refund.total_amount == order.total_amount: 

555 tax_transaction_processor = ( 

556 await stripe_service.revert_tax_transaction( 

557 order.tax_transaction_processor_id, 

558 mode="full", 

559 reference=str(refund.id), 

560 ) 

561 ) 

562 else: 

563 tax_transaction_processor = ( 

564 await stripe_service.revert_tax_transaction( 

565 order.tax_transaction_processor_id, 

566 mode="partial", 

567 reference=str(refund.id), 

568 amount=-refund.total_amount, 

569 ) 

570 ) 

571 refund.tax_transaction_processor_id = tax_transaction_processor.id 

572 session.add(refund) 

573 

574 elif pledge and pledge.payment_id and payment.charge_id: 

575 await pledge_service.refund_by_payment_id( 

576 session=session, 

577 payment_id=pledge.payment_id, 

578 amount=refund.amount, 

579 transaction_id=payment.charge_id, 

580 ) 

581 

582 return transaction 

583 

584 async def _revert_refund_transaction( 1a

585 self, 

586 session: AsyncSession, 

587 *, 

588 charge_id: str, 

589 refund: Refund, 

590 payment: Transaction, 

591 order: Order | None = None, 

592 pledge: Pledge | None = None, 

593 ) -> None: 

594 try: 

595 transaction = await refund_transaction_service.revert( 

596 session, 

597 charge_id=charge_id, 

598 payment_transaction=payment, 

599 refund=refund, 

600 ) 

601 except RefundTransactionDoesNotExistError: 

602 return None 

603 

604 if order: 

605 await order_service.update_refunds( 

606 session, 

607 order, 

608 refunded_amount=-refund.amount, 

609 refunded_tax_amount=-refund.tax_amount, 

610 ) 

611 

612 # Send order.updated webhook 

613 await order_service.send_webhook( 

614 session, order, WebhookEventType.order_updated 

615 ) 

616 

617 async def _on_created( 1a

618 self, 

619 session: AsyncSession, 

620 organization: Organization, 

621 refund: Refund, 

622 ) -> None: 

623 await webhook_service.send( 

624 session, organization, WebhookEventType.refund_created, refund 

625 ) 

626 

627 async def _on_succeeded( 1a

628 self, 

629 session: AsyncSession, 

630 organization: Organization, 

631 order: Order, 

632 ) -> None: 

633 # Reduce positive customer balance 

634 customer_balance = await wallet_service.get_billing_wallet_balance( 

635 session, order.customer, order.currency 

636 ) 

637 if customer_balance > 0: 

638 reduction_amount = min( 

639 customer_balance, order.refunded_amount + order.refunded_tax_amount 

640 ) 

641 await wallet_service.create_balance_transaction( 

642 session, order.customer, -reduction_amount, order.currency, order=order 

643 ) 

644 

645 await event_service.create_event( 

646 session, 

647 build_system_event( 

648 SystemEvent.order_refunded, 

649 customer=order.customer, 

650 organization=order.organization, 

651 metadata=OrderRefundedMetadata( 

652 order_id=str(order.id), 

653 refunded_amount=order.refunded_amount, 

654 currency=order.currency, 

655 ), 

656 ), 

657 ) 

658 

659 # Send order.refunded 

660 await webhook_service.send( 

661 session, organization, WebhookEventType.order_refunded, order 

662 ) 

663 

664 async def _on_updated( 1a

665 self, 

666 session: AsyncSession, 

667 organization: Organization, 

668 refund: Refund, 

669 ) -> None: 

670 await webhook_service.send( 

671 session, organization, WebhookEventType.refund_updated, refund 

672 ) 

673 

674 async def _get_resources( 1a

675 self, 

676 session: AsyncSession, 

677 refund: stripe_lib.Refund, 

678 ) -> RefundedResources: 

679 if not refund.charge: 

680 raise RefundUnknownPayment(refund.id, payment_type="charge") 

681 

682 charge_id = str(refund.charge) 

683 payment_intent = str(refund.payment_intent) if refund.payment_intent else None 

684 payment = await payment_transaction_service.get_by_charge_id(session, charge_id) 

685 if payment is None: 

686 raise RefundUnknownPayment(charge_id, payment_type="charge") 

687 

688 if payment.order_id: 

689 order_repository = OrderRepository.from_session(session) 

690 order = await order_repository.get_by_id( 

691 payment.order_id, options=order_repository.get_eager_options() 

692 ) 

693 if not order: 

694 raise RefundUnknownPayment(payment.order_id, payment_type="order") 

695 

696 return (charge_id, payment, order, None) 

697 

698 if not (payment.pledge_id and payment_intent): 

699 raise RefundUnknownPayment(payment.id, payment_type="charge") 

700 

701 pledge = await pledge_service.get_by_payment_id( 

702 session, 

703 payment_id=payment_intent, 

704 ) 

705 if pledge is None: 

706 raise RefundUnknownPayment(payment.pledge_id, payment_type="pledge") 

707 

708 return (charge_id, payment, None, pledge) 

709 

710 def _get_readable_refund_statement( 1a

711 self, auth_subject: AuthSubject[User | Organization] 

712 ) -> Select[tuple[Refund]]: 

713 statement = select(Refund).where( 

714 Refund.deleted_at.is_(None), 

715 # We only care about order refunds in our API 

716 Refund.order_id.is_not(None), 

717 ) 

718 

719 if is_user(auth_subject): 

720 user = auth_subject.subject 

721 statement = statement.where( 

722 Refund.organization_id.in_( 

723 select(UserOrganization.organization_id).where( 

724 UserOrganization.user_id == user.id, 

725 UserOrganization.deleted_at.is_(None), 

726 ) 

727 ) 

728 ) 

729 elif is_organization(auth_subject): 

730 statement = statement.where( 

731 Refund.organization_id == auth_subject.subject.id 

732 ) 

733 

734 return statement 

735 

736 

737refund = RefundService(Refund) 1a