syrup_rail_postgres/subscription_billing_service/
error_disposition.rs1use std::time::Duration;
2
3use syrup_rail::{
4 GatewayError, GatewayNotSubmittedError, GatewayResolutionError, HostChargeTargetRejection,
5 SubscriptionEnrollmentReservationRejection, SubscriptionEnrollmentSubmissionRejection,
6 SubscriptionPaymentMethodReplacementRejection,
7 SubscriptionPaymentMethodReplacementSubmissionRejection,
8 SubscriptionRecoveryReservationRejection, SubscriptionRecoverySubmissionRejection,
9 SubscriptionRenewalReservationRejection,
10};
11
12use super::{
13 GatewayMutationCooldownScope, SubscriptionBillingServiceError,
14 SubscriptionBillingServiceErrorDisposition,
15};
16
17impl SubscriptionBillingServiceError {
18 pub const fn disposition(&self) -> SubscriptionBillingServiceErrorDisposition {
23 match self {
24 Self::StorageTemporarilyUnavailable(_) => {
25 SubscriptionBillingServiceErrorDisposition::TemporarilyUnavailable
26 }
27 Self::Sql(_)
28 | Self::Attempt(_)
29 | Self::Application(_)
30 | Self::HostChargeApplication(_)
31 | Self::HostChargeStore(_)
32 | Self::BillingTransaction(_)
33 | Self::BillingEvent(_)
34 | Self::ResolvedGatewayIdentityMismatch
35 | Self::InvalidState(_) => SubscriptionBillingServiceErrorDisposition::Internal,
36 Self::Cancellation(error) => cancellation_error_disposition(error),
37 Self::Discount(error) => discount_error_disposition(error),
38 Self::HostChargeUnavailable => {
39 SubscriptionBillingServiceErrorDisposition::Misconfigured
40 }
41 Self::IdempotencyConflict => SubscriptionBillingServiceErrorDisposition::Conflict,
42 Self::AdmissionDenied { .. } | Self::AdmissionTimeout | Self::AdmissionUnavailable => {
43 SubscriptionBillingServiceErrorDisposition::TemporarilyUnavailable
44 }
45 Self::GatewayConfigurationChanged => {
46 SubscriptionBillingServiceErrorDisposition::Conflict
47 }
48 Self::GatewayResolution(error) => gateway_resolution_disposition(*error),
49 Self::GatewayMutationCooldown { scope } => match scope {
50 GatewayMutationCooldownScope::Account | GatewayMutationCooldownScope::Provider => {
51 SubscriptionBillingServiceErrorDisposition::TemporarilyUnavailable
52 }
53 },
54 Self::ReservationRejected(reason) => {
55 enrollment_reservation_rejection_disposition(*reason)
56 }
57 Self::SubmissionRejected(reason) => {
58 enrollment_submission_rejection_disposition(*reason)
59 }
60 Self::HostChargeReservationRejected(reason)
61 | Self::HostChargeSubmissionRejected(reason) => {
62 host_charge_rejection_disposition(*reason)
63 }
64 Self::RecoveryReservationRejected(reason) => {
65 recovery_reservation_rejection_disposition(*reason)
66 }
67 Self::RecoverySubmissionRejected(reason) => {
68 recovery_submission_rejection_disposition(*reason)
69 }
70 Self::RenewalReservationRejected(reason) => {
71 renewal_reservation_rejection_disposition(*reason)
72 }
73 Self::PaymentMethodReplacementReservationRejected(reason) => {
74 payment_method_replacement_reservation_rejection_disposition(*reason)
75 }
76 Self::PaymentMethodReplacementSubmissionRejected(reason) => {
77 payment_method_replacement_submission_rejection_disposition(*reason)
78 }
79 Self::GatewayNotSubmitted(error) => gateway_not_submitted_disposition(error),
80 Self::GatewayReadiness(error) => gateway_readiness_disposition(error),
81 }
82 }
83
84 pub const fn is_retryable(&self) -> bool {
93 matches!(
94 self.disposition(),
95 SubscriptionBillingServiceErrorDisposition::TemporarilyUnavailable
96 )
97 }
98
99 pub const fn is_conflict(&self) -> bool {
105 matches!(
106 self.disposition(),
107 SubscriptionBillingServiceErrorDisposition::Conflict
108 )
109 }
110
111 pub const fn retry_after(&self) -> Option<Duration> {
117 match self {
118 Self::AdmissionDenied { retry_after } => Some(*retry_after),
119 Self::Sql(_)
120 | Self::StorageTemporarilyUnavailable(_)
121 | Self::Attempt(_)
122 | Self::Application(_)
123 | Self::HostChargeApplication(_)
124 | Self::HostChargeStore(_)
125 | Self::Cancellation(_)
126 | Self::Discount(_)
127 | Self::BillingTransaction(_)
128 | Self::BillingEvent(_)
129 | Self::HostChargeUnavailable
130 | Self::IdempotencyConflict
131 | Self::AdmissionTimeout
132 | Self::AdmissionUnavailable
133 | Self::GatewayConfigurationChanged
134 | Self::GatewayResolution(_)
135 | Self::ResolvedGatewayIdentityMismatch
136 | Self::GatewayMutationCooldown { .. }
137 | Self::ReservationRejected(_)
138 | Self::SubmissionRejected(_)
139 | Self::HostChargeReservationRejected(_)
140 | Self::HostChargeSubmissionRejected(_)
141 | Self::RecoveryReservationRejected(_)
142 | Self::RecoverySubmissionRejected(_)
143 | Self::RenewalReservationRejected(_)
144 | Self::PaymentMethodReplacementReservationRejected(_)
145 | Self::PaymentMethodReplacementSubmissionRejected(_)
146 | Self::GatewayNotSubmitted(_)
147 | Self::GatewayReadiness(_)
148 | Self::InvalidState(_) => None,
149 }
150 }
151}
152
153const fn cancellation_error_disposition(
154 error: &crate::SubscriptionCancellationError,
155) -> SubscriptionBillingServiceErrorDisposition {
156 match error {
157 crate::SubscriptionCancellationError::Sql(_)
158 | crate::SubscriptionCancellationError::InvalidState(_) => {
159 SubscriptionBillingServiceErrorDisposition::Internal
160 }
161 }
162}
163
164const fn discount_error_disposition(
165 error: &crate::SubscriptionDiscountOperationError,
166) -> SubscriptionBillingServiceErrorDisposition {
167 match error {
168 crate::SubscriptionDiscountOperationError::Sql(_)
169 | crate::SubscriptionDiscountOperationError::InvalidState(_) => {
170 SubscriptionBillingServiceErrorDisposition::Internal
171 }
172 crate::SubscriptionDiscountOperationError::OfferUnavailable
173 | crate::SubscriptionDiscountOperationError::InvalidConfiguration
174 | crate::SubscriptionDiscountOperationError::LimitedDiscountCadence => {
175 SubscriptionBillingServiceErrorDisposition::Misconfigured
176 }
177 crate::SubscriptionDiscountOperationError::OfferPlanMismatch => {
178 SubscriptionBillingServiceErrorDisposition::Conflict
179 }
180 }
181}
182
183const fn gateway_resolution_disposition(
184 error: GatewayResolutionError,
185) -> SubscriptionBillingServiceErrorDisposition {
186 match error {
187 GatewayResolutionError::NotFound | GatewayResolutionError::InvalidConfiguration => {
188 SubscriptionBillingServiceErrorDisposition::Misconfigured
189 }
190 GatewayResolutionError::ConfigurationChanged => {
191 SubscriptionBillingServiceErrorDisposition::Conflict
192 }
193 GatewayResolutionError::Unavailable => {
194 SubscriptionBillingServiceErrorDisposition::TemporarilyUnavailable
195 }
196 }
197}
198
199const fn gateway_readiness_disposition(
200 error: &GatewayError,
201) -> SubscriptionBillingServiceErrorDisposition {
202 match error {
203 GatewayError::RequestRejected(_) => SubscriptionBillingServiceErrorDisposition::Rejected,
204 GatewayError::Malformed(_) => SubscriptionBillingServiceErrorDisposition::Internal,
207 GatewayError::Configuration(_) => SubscriptionBillingServiceErrorDisposition::Misconfigured,
208 GatewayError::Unavailable(_) | GatewayError::RateLimited(_) => {
209 SubscriptionBillingServiceErrorDisposition::TemporarilyUnavailable
210 }
211 }
212}
213
214const fn gateway_not_submitted_disposition(
215 error: &GatewayNotSubmittedError,
216) -> SubscriptionBillingServiceErrorDisposition {
217 match error {
218 GatewayNotSubmittedError::RequestRejected(_) => {
219 SubscriptionBillingServiceErrorDisposition::Rejected
220 }
221 GatewayNotSubmittedError::Malformed(_) => {
225 SubscriptionBillingServiceErrorDisposition::Internal
226 }
227 GatewayNotSubmittedError::Configuration(_) => {
228 SubscriptionBillingServiceErrorDisposition::Misconfigured
229 }
230 GatewayNotSubmittedError::AccountModeMismatch { .. } => {
231 SubscriptionBillingServiceErrorDisposition::Misconfigured
232 }
233 GatewayNotSubmittedError::AccountModeVerification(error) => {
234 gateway_readiness_disposition(error)
235 }
236 GatewayNotSubmittedError::NotTransmitted(_) | GatewayNotSubmittedError::RateLimited(_) => {
237 SubscriptionBillingServiceErrorDisposition::TemporarilyUnavailable
238 }
239 }
240}
241
242const fn enrollment_reservation_rejection_disposition(
243 rejection: SubscriptionEnrollmentReservationRejection,
244) -> SubscriptionBillingServiceErrorDisposition {
245 match rejection {
246 SubscriptionEnrollmentReservationRejection::CurrentSubscription
247 | SubscriptionEnrollmentReservationRejection::ActiveGrant
248 | SubscriptionEnrollmentReservationRejection::UnresolvedProcessorCharge
249 | SubscriptionEnrollmentReservationRejection::AttemptInProgress => {
250 SubscriptionBillingServiceErrorDisposition::Rejected
251 }
252 SubscriptionEnrollmentReservationRejection::EnrollmentTermsChanged
253 | SubscriptionEnrollmentReservationRejection::GatewayConfigurationChanged
254 | SubscriptionEnrollmentReservationRejection::GatewayAccountModeChanged => {
255 SubscriptionBillingServiceErrorDisposition::Conflict
256 }
257 }
258}
259
260const fn enrollment_submission_rejection_disposition(
261 rejection: SubscriptionEnrollmentSubmissionRejection,
262) -> SubscriptionBillingServiceErrorDisposition {
263 match rejection {
264 SubscriptionEnrollmentSubmissionRejection::BillingStateChanged
265 | SubscriptionEnrollmentSubmissionRejection::EnrollmentTermsChanged
266 | SubscriptionEnrollmentSubmissionRejection::GatewayConfigurationChanged
267 | SubscriptionEnrollmentSubmissionRejection::GatewayAccountModeChanged => {
268 SubscriptionBillingServiceErrorDisposition::Conflict
269 }
270 }
271}
272
273const fn host_charge_rejection_disposition(
274 rejection: HostChargeTargetRejection,
275) -> SubscriptionBillingServiceErrorDisposition {
276 match rejection {
277 HostChargeTargetRejection::TargetUnavailable | HostChargeTargetRejection::LedgerUnsafe => {
278 SubscriptionBillingServiceErrorDisposition::Rejected
279 }
280 HostChargeTargetRejection::ChargeChanged => {
281 SubscriptionBillingServiceErrorDisposition::Conflict
282 }
283 }
284}
285
286const fn recovery_reservation_rejection_disposition(
287 rejection: SubscriptionRecoveryReservationRejection,
288) -> SubscriptionBillingServiceErrorDisposition {
289 match rejection {
290 SubscriptionRecoveryReservationRejection::SubscriptionNotFound
291 | SubscriptionRecoveryReservationRejection::PaymentNotDue
292 | SubscriptionRecoveryReservationRejection::AttemptInProgress
293 | SubscriptionRecoveryReservationRejection::PaymentMethodUpdateInProgress => {
294 SubscriptionBillingServiceErrorDisposition::Rejected
295 }
296 SubscriptionRecoveryReservationRejection::GatewayConfigurationChanged
297 | SubscriptionRecoveryReservationRejection::GatewayAccountModeChanged => {
298 SubscriptionBillingServiceErrorDisposition::Conflict
299 }
300 }
301}
302
303const fn recovery_submission_rejection_disposition(
304 rejection: SubscriptionRecoverySubmissionRejection,
305) -> SubscriptionBillingServiceErrorDisposition {
306 match rejection {
307 SubscriptionRecoverySubmissionRejection::BillingStateChanged
308 | SubscriptionRecoverySubmissionRejection::GatewayConfigurationChanged => {
309 SubscriptionBillingServiceErrorDisposition::Conflict
310 }
311 }
312}
313
314const fn renewal_reservation_rejection_disposition(
315 rejection: SubscriptionRenewalReservationRejection,
316) -> SubscriptionBillingServiceErrorDisposition {
317 match rejection {
318 SubscriptionRenewalReservationRejection::SubscriptionNotFound
319 | SubscriptionRenewalReservationRejection::PaymentNotDue
320 | SubscriptionRenewalReservationRejection::AttemptInProgress
321 | SubscriptionRenewalReservationRejection::PaymentMethodUpdateInProgress
322 | SubscriptionRenewalReservationRejection::RetryBlocked => {
323 SubscriptionBillingServiceErrorDisposition::Rejected
324 }
325 SubscriptionRenewalReservationRejection::GatewayAccountModeChanged
326 | SubscriptionRenewalReservationRejection::GatewayConfigurationChanged => {
327 SubscriptionBillingServiceErrorDisposition::Conflict
328 }
329 }
330}
331
332const fn payment_method_replacement_reservation_rejection_disposition(
333 rejection: SubscriptionPaymentMethodReplacementRejection,
334) -> SubscriptionBillingServiceErrorDisposition {
335 match rejection {
336 SubscriptionPaymentMethodReplacementRejection::SubscriptionNotFound
337 | SubscriptionPaymentMethodReplacementRejection::SubscriptionIneligible
338 | SubscriptionPaymentMethodReplacementRejection::ChargeAttemptInProgress
339 | SubscriptionPaymentMethodReplacementRejection::PaymentMethodUpdateInProgress => {
340 SubscriptionBillingServiceErrorDisposition::Rejected
341 }
342 SubscriptionPaymentMethodReplacementRejection::GatewayConfigurationChanged
343 | SubscriptionPaymentMethodReplacementRejection::GatewayAccountModeChanged => {
344 SubscriptionBillingServiceErrorDisposition::Conflict
345 }
346 }
347}
348
349const fn payment_method_replacement_submission_rejection_disposition(
350 rejection: SubscriptionPaymentMethodReplacementSubmissionRejection,
351) -> SubscriptionBillingServiceErrorDisposition {
352 match rejection {
353 SubscriptionPaymentMethodReplacementSubmissionRejection::BillingStateChanged
354 | SubscriptionPaymentMethodReplacementSubmissionRejection::GatewayConfigurationChanged => {
355 SubscriptionBillingServiceErrorDisposition::Conflict
356 }
357 }
358}