syrup_rail_postgres/subscription_billing_service/
enrollment.rs1use super::*;
2
3impl SubscriptionBillingService {
4 pub async fn enroll(
10 &self,
11 command: EnrollSubscription,
12 ) -> Result<SubscriptionEnrollmentPaymentResult, SubscriptionBillingServiceError> {
13 match self.preflight(&command).await? {
14 SubscriptionEnrollmentPreflightOutcome::Continue => {}
15 SubscriptionEnrollmentPreflightOutcome::Replay(attempt) => {
16 return self.payment_result(*attempt).await;
17 }
18 SubscriptionEnrollmentPreflightOutcome::IdempotencyConflict => {
19 return Err(SubscriptionBillingServiceError::IdempotencyConflict);
20 }
21 }
22
23 self.admit_subscriber_mutation(
24 command.billing_scope_id(),
25 command.subscriber_id(),
26 EndUserMutationOperation::SubscriptionInitial,
27 )
28 .await?;
29
30 let (account, gateway) = self
31 .resolve_active_gateway(
32 command.billing_scope_id(),
33 command.gateway_configuration_id(),
34 )
35 .await?;
36 let mut reservation = SubscriptionEnrollmentReservation::from_command(
37 &command,
38 &gateway,
39 self.required_gateway_account_mode,
40 )
41 .map_err(map_reservation_build_error)?;
42
43 let attempt = match self.reserve(&reservation).await? {
44 SubscriptionEnrollmentReservationOutcome::Reserved(attempt)
45 | SubscriptionEnrollmentReservationOutcome::Replay(attempt)
46 if attempt.status() == PaymentAttemptStatus::Pending
47 && attempt.state().timestamps().submitted_at().is_none() =>
48 {
49 attempt
50 }
51 SubscriptionEnrollmentReservationOutcome::Replay(attempt) => {
52 return self.payment_result(attempt).await;
53 }
54 SubscriptionEnrollmentReservationOutcome::IdempotencyConflict => {
55 return Err(SubscriptionBillingServiceError::IdempotencyConflict);
56 }
57 SubscriptionEnrollmentReservationOutcome::Rejected(
58 SubscriptionEnrollmentReservationRejection::GatewayAccountModeChanged,
59 ) => {
60 return Err(SubscriptionBillingServiceError::GatewayConfigurationChanged);
61 }
62 SubscriptionEnrollmentReservationOutcome::Rejected(reason) => {
63 return Err(SubscriptionBillingServiceError::ReservationRejected(reason));
64 }
65 SubscriptionEnrollmentReservationOutcome::Reserved(_) => {
66 return Err(SubscriptionBillingServiceError::InvalidState(
67 INVALID_SERVICE_STATE,
68 ));
69 }
70 };
71 if reservation.identity().attempt_id() != attempt.identity().attempt_id() {
72 reservation = SubscriptionEnrollmentReservation::from_command_for_attempt(
73 &command,
74 &gateway,
75 attempt.identity().attempt_id(),
76 self.required_gateway_account_mode,
77 )
78 .map_err(map_reservation_build_error)?;
79 }
80
81 if let Some(scope) = self.active_cooldown(&account).await? {
82 return self
83 .resolve_subscriber_readiness_failure(
84 SubscriberInitiatedReservation::Initial(&reservation),
85 SubscriberReadinessFailure::Cooldown(scope),
86 OutcomeResolutionBoundary::Prepared,
87 )
88 .await;
89 }
90 let verified_gateway = match subscriber_gateway_readiness(
91 &gateway,
92 self.required_gateway_account_mode,
93 )
94 .await
95 {
96 Ok(verified_gateway) => verified_gateway,
97 Err(failure) => {
98 return self
99 .resolve_subscriber_readiness_failure(
100 SubscriberInitiatedReservation::Initial(&reservation),
101 failure,
102 OutcomeResolutionBoundary::Prepared,
103 )
104 .await;
105 }
106 };
107
108 let admission = match admit_subscription_enrollment_submission(
109 &self.pool,
110 self.offers.as_ref(),
111 &reservation,
112 )
113 .await?
114 {
115 SubscriptionEnrollmentAdmissionOutcome::Admitted(admission) => *admission,
116 SubscriptionEnrollmentAdmissionOutcome::AlreadyAdmitted(attempt) => {
117 return self.payment_result(attempt).await;
118 }
119 SubscriptionEnrollmentAdmissionOutcome::Rejected {
120 reason: SubscriptionEnrollmentSubmissionRejection::GatewayAccountModeChanged,
121 ..
122 } => {
123 return Err(SubscriptionBillingServiceError::GatewayConfigurationChanged);
124 }
125 SubscriptionEnrollmentAdmissionOutcome::Rejected { reason, .. } => {
126 return Err(SubscriptionBillingServiceError::SubmissionRejected(reason));
127 }
128 };
129
130 if let Some(scope) = self.active_cooldown(&account).await? {
131 return self
132 .resolve_subscriber_readiness_failure(
133 SubscriberInitiatedReservation::Initial(&reservation),
134 SubscriberReadinessFailure::Cooldown(scope),
135 OutcomeResolutionBoundary::AdmittedNotSubmitted,
136 )
137 .await;
138 }
139 match submit_admitted_subscription_enrollment(
140 &self.pool,
141 self.coordinator.as_ref(),
142 admission,
143 &command,
144 verified_gateway,
145 )
146 .await?
147 {
148 SubscriptionEnrollmentProviderResult::Payment(payment) => Ok(payment),
149 SubscriptionEnrollmentProviderResult::NotSubmitted { error, .. } => {
150 Err(SubscriptionBillingServiceError::GatewayNotSubmitted(error))
151 }
152 }
153 }
154
155 pub(super) async fn preflight(
156 &self,
157 command: &EnrollSubscription,
158 ) -> Result<SubscriptionEnrollmentPreflightOutcome, SubscriptionBillingServiceError> {
159 let mut transaction = self.pool.begin().await?;
160 let outcome =
161 preflight_subscription_enrollment_in_transaction(&mut transaction, command).await?;
162 transaction.commit().await?;
163 Ok(outcome)
164 }
165
166 pub(super) async fn reserve(
167 &self,
168 reservation: &SubscriptionEnrollmentReservation,
169 ) -> Result<SubscriptionEnrollmentReservationOutcome, SubscriptionBillingServiceError> {
170 let mut transaction = self.pool.begin().await?;
171 let outcome = reserve_subscription_enrollment_in_transaction(
172 &mut transaction,
173 self.offers.as_ref(),
174 reservation,
175 )
176 .await?;
177 transaction.commit().await?;
178 Ok(outcome)
179 }
180
181 pub(super) async fn payment_result(
182 &self,
183 attempt: PaymentAttempt,
184 ) -> Result<SubscriptionEnrollmentPaymentResult, SubscriptionBillingServiceError> {
185 let mut transaction = self.pool.begin().await?;
186 let result = payment_result_for_attempt(&mut transaction, attempt).await?;
187 transaction.commit().await?;
188 Ok(result)
189 }
190}