Coverage for polar/payout/tasks.py: 38%

53 statements  

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

1import uuid 

2 

3import sentry_sdk 

4import stripe as stripe_lib 

5import structlog 

6 

7from polar.enums import AccountType 

8from polar.exceptions import PolarTaskError 

9from polar.logging import Logger 

10from polar.worker import AsyncSessionMaker, CronTrigger, TaskPriority, actor 

11 

12from .repository import PayoutRepository 

13from .service import PayoutAlreadyTriggered 

14from .service import payout as payout_service 

15 

16log: Logger = structlog.get_logger() 

17 

18 

19class PayoutTaskError(PolarTaskError): ... 19 ↛ 22line 19 didn't jump to line 22 because

20 

21 

22class PayoutDoesNotExist(PayoutTaskError): 

23 def __init__(self, payout_id: uuid.UUID) -> None: 

24 self.payout_id = payout_id 

25 message = f"The payout with id {payout_id} does not exist." 

26 super().__init__(message) 

27 

28 

29@actor(actor_name="payout.created", priority=TaskPriority.LOW) 

30async def payout_created(payout_id: uuid.UUID) -> None: 

31 async with AsyncSessionMaker() as session: 

32 repository = PayoutRepository(session) 

33 payout = await repository.get_by_id( 

34 payout_id, options=repository.get_eager_options() 

35 ) 

36 if payout is None: 

37 raise PayoutDoesNotExist(payout_id) 

38 

39 if payout.processor == AccountType.stripe: 

40 await payout_service.transfer_stripe(session, payout) 

41 

42 

43@actor( 

44 actor_name="payout.trigger_stripe_payouts", 

45 cron_trigger=CronTrigger(minute=15), 

46 priority=TaskPriority.LOW, 

47) 

48async def trigger_stripe_payouts() -> None: 

49 async with AsyncSessionMaker() as session: 

50 await payout_service.trigger_stripe_payouts(session) 

51 

52 

53@actor(actor_name="payout.trigger_stripe_payout", priority=TaskPriority.LOW) 

54async def trigger_payout(payout_id: uuid.UUID) -> None: 

55 async with AsyncSessionMaker() as session: 

56 repository = PayoutRepository(session) 

57 payout = await repository.get_by_id( 

58 payout_id, options=repository.get_eager_options() 

59 ) 

60 if payout is None: 

61 raise PayoutDoesNotExist(payout_id) 

62 

63 try: 

64 await payout_service.trigger_stripe_payout(session, payout) 

65 except PayoutAlreadyTriggered: 

66 # Swallow it, since it's likely a task that's being retried 

67 # while the payout has already been triggered. 

68 pass 

69 except stripe_lib.InvalidRequestError as e: 

70 # Capture exception in Sentry for debugging purposes 

71 sentry_sdk.capture_exception( 

72 e, 

73 extras={"payout_id": str(payout_id)}, 

74 ) 

75 # Do not raise an error here: we know it happens often, because Stripe 

76 # has many hidden rules on payout creation that we cannot control. 

77 pass 

78 

79 

80@actor(actor_name="payout.invoice", priority=TaskPriority.LOW) 

81async def order_invoice(payout_id: uuid.UUID) -> None: 

82 async with AsyncSessionMaker() as session: 

83 repository = PayoutRepository(session) 

84 payout = await repository.get_by_id( 

85 payout_id, options=repository.get_eager_options() 

86 ) 

87 if payout is None: 

88 raise PayoutDoesNotExist(payout_id) 

89 

90 await payout_service.generate_invoice(session, payout)