1use std::{error::Error, fmt};
2
3use async_trait::async_trait;
4use chrono::{DateTime, Utc};
5use sqlx::{PgConnection, PgPool, Postgres, Row, Transaction, postgres::PgRow};
6use syrup_rail::{
7 ActorId, AttemptReviewCursor, AttemptReviewPage, BillingEventSubject, BillingScopeId,
8 CurrencyCode, ExternalReversalAttestation, ExternalReversalHostChargeRelease,
9 ExternalReversalKind, ExternalReversalReason, GatewayAccountId, GatewayDiagnostic,
10 GatewayOrderId, GatewayPaymentDescriptor, GatewayPaymentMethodReference, GatewayTransactionId,
11 HostChargeTargetId, ManualAttemptFailureOutcome, ManualFailureHostCharge, Money,
12 OperatorReviewPageLimit, PaymentAttempt, PaymentAttemptId, PaymentAttemptKind,
13 PaymentAttemptStatus, PaymentResolutionCode, PlanKey, ProcessorCharge, ProcessorChargeId,
14 ProcessorChargeProgression, ProcessorChargeReviewCursor, ProcessorChargeReviewItem,
15 ProcessorChargeReviewPage, ProcessorChargeRole, ProcessorEvidence, SubscriberId,
16 review_required_attempt_can_be_manually_failed, review_required_manual_failure_evidence,
17};
18use thiserror::Error;
19use uuid::Uuid;
20
21#[cfg(test)]
22use syrup_rail::BillingEvent;
23
24use crate::attempts::{
25 lock_payment_attempt_by_id_on_connection, lock_subscription_aggregate,
26 payment_attempt_from_row, set_enrollment_timeouts,
27};
28use crate::host_error::{BoxError, RedactedHostErrorSource};
29use crate::processor_charge_persistence::{
30 attestation_by_charge, attestation_matches_source, expected_final_resolution_code,
31 expected_prior_resolution_code, parse_charge_state_code, parse_kind, parse_progression,
32 parse_role, processor_charge_from_row,
33};
34use crate::renewal_failure::{
35 RenewalFailureApplication, RenewalFailureStoreError, apply_resolved_automatic_renewal_failure,
36};
37use crate::transactions::{
38 BillingEventWriteError, BillingTransactionCoordinator, BillingTransactionError,
39};
40
41mod external_reversal;
42mod manual_failure;
43mod pages;
44
45pub use external_reversal::{
46 ExternalReversalAttestationOutcome, ExternalReversalHostStore, ExternalReversalHostStoreError,
47 ExternalReversalHostTransitionOutcome, attest_external_reversal,
48};
49pub use manual_failure::{
50 ManualAttemptFailureHostStore, ManualAttemptFailureHostStoreError,
51 ManualAttemptFailureHostTransitionOutcome, fail_review_required_attempt,
52};
53pub use pages::{attempt_review_page, processor_charge_review_page};
54
55#[cfg(test)]
56use external_reversal::{charge_locator, lock_processor_charge};
57
58pub(crate) const INVALID_OPERATOR_STATE: &str = "canonical operator review state is invalid";
59
60#[derive(Debug, Error)]
61pub enum OperatorReviewError {
62 #[error("operator review storage operation failed")]
63 Sql(#[from] sqlx::Error),
64 #[error("{0}")]
65 InvalidState(&'static str),
66 #[error(transparent)]
67 Host(#[from] ExternalReversalHostStoreError),
68 #[error(transparent)]
69 ManualFailureHost(#[from] ManualAttemptFailureHostStoreError),
70 #[error(transparent)]
71 BillingTransaction(#[from] BillingTransactionError),
72 #[error(transparent)]
73 BillingEvent(#[from] BillingEventWriteError),
74}
75
76impl From<crate::PaymentAttemptStoreError> for OperatorReviewError {
77 fn from(error: crate::PaymentAttemptStoreError) -> Self {
78 match error {
79 crate::PaymentAttemptStoreError::Sql(error) => Self::Sql(error),
80 crate::PaymentAttemptStoreError::InvalidState(_) => {
81 Self::InvalidState(INVALID_OPERATOR_STATE)
82 }
83 }
84 }
85}
86
87impl From<RenewalFailureStoreError> for OperatorReviewError {
88 fn from(error: RenewalFailureStoreError) -> Self {
89 match error {
90 RenewalFailureStoreError::Sql(error) => Self::Sql(error),
91 RenewalFailureStoreError::Attempt(error) => error.into(),
92 RenewalFailureStoreError::InvalidState(_) => Self::InvalidState(INVALID_OPERATOR_STATE),
93 }
94 }
95}
96
97#[cfg(test)]
98mod tests;