Coverage for polar/customer_portal/endpoints/customer.py: 68%

45 statements  

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

1from fastapi import Depends, Request 1a

2from pydantic import UUID4 1a

3from sse_starlette import EventSourceResponse 1a

4 

5from polar.eventstream.endpoints import subscribe 1a

6from polar.eventstream.service import Receivers 1a

7from polar.exceptions import ResourceNotFound 1a

8from polar.kit.pagination import ListResource, PaginationParamsQuery 1a

9from polar.models import Customer 1a

10from polar.openapi import APITag 1a

11from polar.payment_method.service import PaymentMethodInUseByActiveSubscription 1a

12from polar.postgres import AsyncSession, get_db_session 1a

13from polar.redis import Redis, get_redis 1a

14from polar.routing import APIRouter 1a

15 

16from .. import auth 1a

17from ..schemas.customer import ( 1a

18 CustomerPaymentMethod, 

19 CustomerPaymentMethodConfirm, 

20 CustomerPaymentMethodCreate, 

21 CustomerPaymentMethodCreateResponse, 

22 CustomerPaymentMethodTypeAdapter, 

23 CustomerPortalCustomer, 

24 CustomerPortalCustomerUpdate, 

25) 

26from ..service.customer import CustomerNotReady 1a

27from ..service.customer import customer as customer_service 1a

28 

29router = APIRouter(prefix="/customers", tags=["customers", APITag.public]) 1a

30 

31 

32@router.get("/stream", include_in_schema=False) 1a

33async def stream( 1a

34 request: Request, 

35 auth_subject: auth.CustomerPortalRead, 

36 session: AsyncSession = Depends(get_db_session), 

37 redis: Redis = Depends(get_redis), 

38) -> EventSourceResponse: 

39 receivers = Receivers(customer_id=auth_subject.subject.id) 

40 channels = receivers.get_channels() 

41 return EventSourceResponse(subscribe(redis, channels, request)) 

42 

43 

44@router.get("/me", summary="Get Customer", response_model=CustomerPortalCustomer) 1a

45async def get(auth_subject: auth.CustomerPortalRead) -> Customer: 1a

46 """Get authenticated customer.""" 

47 return auth_subject.subject 

48 

49 

50@router.patch( 1a

51 "/me", 

52 summary="Update Customer", 

53 responses={ 

54 200: {"description": "Customer updated."}, 

55 }, 

56 response_model=CustomerPortalCustomer, 

57) 

58async def update( 1a

59 customer_update: CustomerPortalCustomerUpdate, 

60 auth_subject: auth.CustomerPortalWrite, 

61 session: AsyncSession = Depends(get_db_session), 

62) -> Customer: 

63 """Update authenticated customer.""" 

64 return await customer_service.update(session, auth_subject.subject, customer_update) 

65 

66 

67@router.get( 1a

68 "/me/payment-methods", 

69 summary="List Customer Payment Methods", 

70 response_model=ListResource[CustomerPaymentMethod], 

71) 

72async def list_payment_methods( 1a

73 auth_subject: auth.CustomerPortalRead, 

74 pagination: PaginationParamsQuery, 

75 session: AsyncSession = Depends(get_db_session), 

76) -> ListResource[CustomerPaymentMethod]: 

77 """Get saved payment methods of the authenticated customer.""" 

78 results, count = await customer_service.list_payment_methods( 

79 session, auth_subject, pagination=pagination 

80 ) 

81 return ListResource.from_paginated_results( 

82 [ 

83 CustomerPaymentMethodTypeAdapter.validate_python(result) 

84 for result in results 

85 ], 

86 count, 

87 pagination, 

88 ) 

89 

90 

91@router.post( 1a

92 "/me/payment-methods", 

93 summary="Add Customer Payment Method", 

94 status_code=201, 

95 responses={ 

96 201: {"description": "Payment method created or setup initiated."}, 

97 }, 

98 response_model=CustomerPaymentMethodCreateResponse, 

99) 

100async def add_payment_method( 1a

101 auth_subject: auth.CustomerPortalRead, 

102 payment_method_create: CustomerPaymentMethodCreate, 

103 session: AsyncSession = Depends(get_db_session), 

104) -> CustomerPaymentMethodCreateResponse: 

105 """Add a payment method to the authenticated customer.""" 

106 return await customer_service.add_payment_method( 

107 session, auth_subject.subject, payment_method_create 

108 ) 

109 

110 

111@router.post( 1a

112 "/me/payment-methods/confirm", 

113 summary="Confirm Customer Payment Method", 

114 status_code=201, 

115 responses={ 

116 201: {"description": "Payment method created or setup initiated."}, 

117 400: { 

118 "description": "Customer is not ready to confirm a payment method.", 

119 "model": CustomerNotReady.schema(), 

120 }, 

121 }, 

122 response_model=CustomerPaymentMethodCreateResponse, 

123) 

124async def confirm_payment_method( 1a

125 auth_subject: auth.CustomerPortalRead, 

126 payment_method_confirm: CustomerPaymentMethodConfirm, 

127 session: AsyncSession = Depends(get_db_session), 

128) -> CustomerPaymentMethodCreateResponse: 

129 """Confirm a payment method for the authenticated customer.""" 

130 return await customer_service.confirm_payment_method( 

131 session, auth_subject.subject, payment_method_confirm 

132 ) 

133 

134 

135@router.delete( 1a

136 "/me/payment-methods/{id}", 

137 summary="Delete Customer Payment Method", 

138 status_code=204, 

139 responses={ 

140 204: {"description": "Payment method deleted."}, 

141 400: { 

142 "description": "Payment method is used by active subscription(s).", 

143 "model": PaymentMethodInUseByActiveSubscription.schema(), 

144 }, 

145 404: { 

146 "description": "Payment method not found.", 

147 "model": ResourceNotFound.schema(), 

148 }, 

149 }, 

150) 

151async def delete_payment_method( 1a

152 id: UUID4, 

153 auth_subject: auth.CustomerPortalRead, 

154 session: AsyncSession = Depends(get_db_session), 

155) -> None: 

156 """Delete a payment method from the authenticated customer.""" 

157 payment_method = await customer_service.get_payment_method( 

158 session, auth_subject, id 

159 ) 

160 if payment_method is None: 

161 raise ResourceNotFound() 

162 await customer_service.delete_payment_method(session, payment_method)