Coverage for functions \ flipdare \ payments \ data \ payment_intent_codes.py: 72%

76 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-05-08 12:22 +1000

1#!/usr/bin/env python 

2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved. 

3# 

4# This file is part of Flipdare's proprietary software and contains 

5# confidential and copyrighted material. Unauthorised copying, 

6# modification, distribution, or use of this file is strictly 

7# prohibited without prior written permission from Flipdare Pty Ltd. 

8# 

9# This software includes third-party components licensed under MIT, 

10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details. 

11# 

12 

13from __future__ import annotations 

14 

15from typing import Any, ClassVar, Literal, get_args 

16from stripe import PaymentIntent as StripePaymentIntent 

17from flipdare.generated.shared.payment.payment_event_status import PaymentEventStatus 

18 

19__all__ = ["StripeDeclineCode", "StripeCancellationCode", "StripeIntentErrorCode"] 

20 

21 

22class StripeDeclineCode: 

23 __slots__ = () 

24 

25 @staticmethod 

26 def from_intent( 

27 intent: StripePaymentIntent, 

28 ) -> PaymentEventStatus | None: 

29 error = getattr(intent, "last_payment_error", None) 

30 if error is None: 

31 return None 

32 code = getattr(error, "code", None) 

33 if code is None: 

34 return None 

35 decline_code = getattr(error, "decline_code", None) 

36 if decline_code is None: 

37 return None 

38 

39 message = getattr(error, "message", "") or "" 

40 # Check for insufficient funds indicators 

41 if decline_code == "insufficient_funds" or ( 

42 code == "card_declined" and "insufficient" in message.lower() 

43 ): 

44 

45 # Suggest customer try a different payment method 

46 # or contact their bank 

47 return PaymentEventStatus.INSUFFICIENT_FUNDS 

48 

49 elif code == "balance_insufficient": 

50 # This applies when charging from Stripe balance 

51 return PaymentEventStatus.INSUFFICIENT_FUNDS 

52 

53 # we recheck on error, 

54 # for the other codes, retrying would be pointless. 

55 if code in ["card_declined", "bank_account_declined"]: 

56 # Check the specific decline reason 

57 if decline_code == "insufficient_funds": 

58 return PaymentEventStatus.INSUFFICIENT_FUNDS 

59 elif decline_code == "generic_decline": 

60 return PaymentEventStatus.ERROR 

61 elif decline_code in {"incorrect_cvc", "expired_card"}: 

62 return PaymentEventStatus.CARD_ISSUE 

63 else: 

64 return PaymentEventStatus.ERROR 

65 

66 return None 

67 

68 

69class StripeCancellationCode: 

70 __slots__ = () 

71 

72 EXPIRED: ClassVar = Literal[ 

73 "abandoned", 

74 "automatic", 

75 ] 

76 DUPLICATE: ClassVar = Literal["duplicate",] 

77 

78 FRAUD: ClassVar = Literal["fraudulent",] 

79 

80 @staticmethod 

81 def from_intent( 

82 intent: StripePaymentIntent, 

83 check_for_expired: bool = True, 

84 check_for_duplicate: bool = True, 

85 check_for_fraud: bool = True, 

86 ) -> PaymentEventStatus | None: 

87 cancellation_reason = getattr(intent, "cancellation_reason", None) 

88 if cancellation_reason is None: 

89 return None 

90 

91 if check_for_expired and _check(cancellation_reason, StripeCancellationCode.EXPIRED): 

92 return PaymentEventStatus.EXPIRED 

93 

94 if check_for_duplicate and _check(cancellation_reason, StripeCancellationCode.DUPLICATE): 

95 return PaymentEventStatus.DUPLICATE 

96 

97 if check_for_fraud and _check(cancellation_reason, StripeCancellationCode.FRAUD): 

98 return PaymentEventStatus.FRAUDULENT 

99 

100 return None 

101 

102 

103class StripeIntentErrorCode: 

104 __slots__ = () 

105 

106 REFUND: ClassVar = Literal["charge_already_refunded",] 

107 

108 CAPTURED: ClassVar = Literal["charge_already_captured",] 

109 

110 DISPUTE: ClassVar = Literal["charge_disputed",] 

111 

112 DECLINED: ClassVar = Literal[ 

113 "payment_method_customer_decline", 

114 "payment_method_provider_decline", 

115 "card_declined", 

116 "bank_account_declined", 

117 "insufficient_funds", 

118 "lost_card", 

119 "stolen_card", 

120 "expired_card", 

121 "processing_error", 

122 "balance_insufficient", 

123 ] 

124 

125 EXPIRED: ClassVar = Literal[ 

126 "payment_method_provider_timeout", 

127 "payment_intent_payment_attempt_expired", 

128 "payment_intent_payment_attempt_failed", 

129 "abandoned", 

130 "charge_expired_for_capture", 

131 "capture_charge_authorization_expired", 

132 "setup_intent_setup_attempt_expired", 

133 ] 

134 

135 API: ClassVar = Literal[ 

136 "setup_attempt_failed", 

137 "setup_intent_authentication_failure", 

138 "setup_intent_invalid_parameter", 

139 "setup_intent_mandate_invalid", 

140 "setup_intent_mobile_wallet_unsupported", 

141 "setup_intent_setup_attempt_expired", 

142 "setup_intent_unexpected_state", 

143 "secret_key_required", 

144 "rate_limit", 

145 "livemode_mismatch", 

146 "card_decline_rate_limit_exceeded", 

147 "capture_unauthorized_payment", 

148 "payment_intent_mandate_invalid", 

149 "payment_intent_rate_limit_exceeded", 

150 "payment_intent_unexpected_state", 

151 "amount_too_large", 

152 "amount_too_small", 

153 "api_key_expired", 

154 "platform_account_required", 

155 "platform_api_key_expired", 

156 ] 

157 

158 @staticmethod 

159 def from_intent( 

160 intent: StripePaymentIntent, 

161 check_for_refund: bool = True, 

162 check_for_capture: bool = True, 

163 check_for_dispute: bool = True, 

164 check_for_declined: bool = True, 

165 check_for_expired: bool = True, 

166 ) -> PaymentEventStatus | None: 

167 error = getattr(intent, "last_payment_error", None) 

168 code = getattr(error, "code", None) if error else None 

169 

170 if check_for_refund and _check(code, StripeIntentErrorCode.REFUND): 

171 return PaymentEventStatus.REFUNDED 

172 

173 if check_for_capture and _check(code, StripeIntentErrorCode.CAPTURED): 

174 return PaymentEventStatus.ALREADY_CAPTURED 

175 

176 if check_for_dispute and _check(code, StripeIntentErrorCode.DISPUTE): 

177 return PaymentEventStatus.DISPUTED 

178 

179 if check_for_declined and _check(code, StripeIntentErrorCode.DECLINED): 

180 return PaymentEventStatus.DECLINED 

181 

182 if check_for_expired and _check(code, StripeIntentErrorCode.EXPIRED): 

183 return PaymentEventStatus.EXPIRED 

184 

185 return None 

186 

187 

188def _check(last_payment_error: str | None, codes: Any) -> bool: 

189 if last_payment_error is None: 

190 return False 

191 

192 return last_payment_error in get_args(codes)