syrup_rail_postgres/
mode_verified_gateway.rs1use std::fmt;
2
3use syrup_rail::{
4 GatewayAccountMode, GatewayDiagnostic, GatewayError, GatewayMutationError,
5 GatewayNotSubmittedError, GatewayPaymentOutcome, GatewaySaleRequest,
6 GatewayStorePaymentMethodRequest, PaymentAttemptIdentity, ResolvedGateway,
7};
8use thiserror::Error;
9
10#[derive(Error)]
12#[non_exhaustive]
13pub enum GatewayAccountModeVerificationError {
14 #[error("gateway account mode does not match the required deployment mode")]
16 AccountModeMismatch {
17 required: GatewayAccountMode,
19 observed: GatewayAccountMode,
21 },
22 #[error("gateway account mode query failed")]
24 Gateway(#[source] GatewayError),
25}
26
27impl fmt::Debug for GatewayAccountModeVerificationError {
28 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
29 match self {
30 Self::AccountModeMismatch { required, observed } => formatter
31 .debug_struct("GatewayAccountModeVerificationError::AccountModeMismatch")
32 .field("required", required)
33 .field("observed", observed)
34 .finish(),
35 Self::Gateway(error) => formatter
36 .debug_tuple("GatewayAccountModeVerificationError::Gateway")
37 .field(error)
38 .finish(),
39 }
40 }
41}
42
43#[must_use = "a verified gateway must be consumed by an admitted provider submission"]
56pub struct ModeVerifiedGateway<'gateway> {
57 gateway: &'gateway ResolvedGateway,
58 required_mode: GatewayAccountMode,
59}
60
61impl fmt::Debug for ModeVerifiedGateway<'_> {
62 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
63 formatter
64 .debug_struct("ModeVerifiedGateway")
65 .field("required_mode", &self.required_mode)
66 .finish_non_exhaustive()
67 }
68}
69
70#[must_use = "an attempt-verified gateway must perform its provider submission"]
71pub(crate) struct AttemptVerifiedGateway<'gateway> {
72 gateway: &'gateway ResolvedGateway,
73 required_mode: GatewayAccountMode,
74}
75
76pub(crate) fn gateway_account_mode_mismatch_detail() -> GatewayDiagnostic {
77 GatewayDiagnostic::new(
78 "Payment was not submitted because the payment processor account mode did not match this deployment.",
79 )
80}
81
82fn normalize_adapter_mutation_error(error: GatewayMutationError) -> GatewayMutationError {
83 match error {
84 GatewayMutationError::NotSubmitted(GatewayNotSubmittedError::AccountModeMismatch {
85 detail,
86 ..
87 }) => GatewayMutationError::NotSubmitted(GatewayNotSubmittedError::Malformed(detail)),
88 GatewayMutationError::NotSubmitted(GatewayNotSubmittedError::AccountModeVerification(
89 error,
90 )) => GatewayMutationError::NotSubmitted(GatewayNotSubmittedError::Malformed(
91 error.detail().clone(),
92 )),
93 other => other,
94 }
95}
96
97impl<'gateway> ModeVerifiedGateway<'gateway> {
98 pub(crate) const fn resolved_gateway(&self) -> &'gateway ResolvedGateway {
99 self.gateway
100 }
101
102 pub(crate) fn authorize_attempt(
103 self,
104 identity: &PaymentAttemptIdentity,
105 ) -> Option<AttemptVerifiedGateway<'gateway>> {
106 (self.required_mode == identity.required_gateway_account_mode()
107 && self.gateway.billing_scope_id() == identity.billing_scope_id()
108 && self.gateway.gateway_account_id() == identity.gateway_account_id()
109 && self.gateway.gateway_configuration_id() == identity.gateway_configuration_id())
110 .then_some(AttemptVerifiedGateway {
111 gateway: self.gateway,
112 required_mode: self.required_mode,
113 })
114 }
115}
116
117impl AttemptVerifiedGateway<'_> {
118 pub(crate) const fn provider_key(&self) -> &syrup_rail::GatewayProviderKey {
119 self.gateway.provider_key()
120 }
121
122 async fn verify_current_mode(&self) -> Result<(), GatewayMutationError> {
123 let observed = self.gateway.account_mode().await.map_err(|error| {
124 GatewayMutationError::NotSubmitted(GatewayNotSubmittedError::AccountModeVerification(
125 error,
126 ))
127 })?;
128 if observed != self.required_mode {
129 return Err(GatewayMutationError::NotSubmitted(
130 GatewayNotSubmittedError::AccountModeMismatch {
131 required: self.required_mode,
132 observed,
133 detail: gateway_account_mode_mismatch_detail(),
134 },
135 ));
136 }
137 Ok(())
138 }
139
140 pub(crate) async fn sale(
141 self,
142 request: GatewaySaleRequest,
143 ) -> Result<GatewayPaymentOutcome, GatewayMutationError> {
144 self.verify_current_mode().await?;
145 self.gateway
146 .sale(request)
147 .await
148 .map_err(normalize_adapter_mutation_error)
149 }
150
151 pub(crate) async fn store_payment_method(
152 self,
153 request: GatewayStorePaymentMethodRequest,
154 ) -> Result<GatewayPaymentOutcome, GatewayMutationError> {
155 self.verify_current_mode().await?;
156 self.gateway
157 .store_payment_method(request)
158 .await
159 .map_err(normalize_adapter_mutation_error)
160 }
161}
162
163pub async fn verify_gateway_account_mode(
170 gateway: &ResolvedGateway,
171 required_mode: GatewayAccountMode,
172) -> Result<ModeVerifiedGateway<'_>, GatewayAccountModeVerificationError> {
173 let observed_mode = gateway
174 .account_mode()
175 .await
176 .map_err(GatewayAccountModeVerificationError::Gateway)?;
177 if observed_mode != required_mode {
178 return Err(GatewayAccountModeVerificationError::AccountModeMismatch {
179 required: required_mode,
180 observed: observed_mode,
181 });
182 }
183 Ok(ModeVerifiedGateway {
184 gateway,
185 required_mode,
186 })
187}