mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +08:00
feat(Connector): [VOLT] Add support for Payments Webhooks (#3155)
This commit is contained in:
@ -2,11 +2,13 @@ pub mod transformers;
|
|||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use common_utils::request::RequestContent;
|
use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent};
|
||||||
use error_stack::{IntoReport, ResultExt};
|
use error_stack::{IntoReport, ResultExt};
|
||||||
use masking::{ExposeInterface, PeekInterface};
|
use masking::{ExposeInterface, PeekInterface};
|
||||||
use transformers as volt;
|
use transformers as volt;
|
||||||
|
|
||||||
|
use self::transformers::webhook_headers;
|
||||||
|
use super::utils;
|
||||||
use crate::{
|
use crate::{
|
||||||
configs::settings,
|
configs::settings,
|
||||||
core::errors::{self, CustomResult},
|
core::errors::{self, CustomResult},
|
||||||
@ -398,7 +400,7 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
|
|||||||
data: &types::PaymentsSyncRouterData,
|
data: &types::PaymentsSyncRouterData,
|
||||||
res: Response,
|
res: Response,
|
||||||
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
|
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
|
||||||
let response: volt::VoltPsyncResponse = res
|
let response: volt::VoltPaymentsResponseData = res
|
||||||
.response
|
.response
|
||||||
.parse_struct("volt PaymentsSyncResponse")
|
.parse_struct("volt PaymentsSyncResponse")
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
@ -586,24 +588,93 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl api::IncomingWebhook for Volt {
|
impl api::IncomingWebhook for Volt {
|
||||||
fn get_webhook_object_reference_id(
|
fn get_webhook_source_verification_algorithm(
|
||||||
&self,
|
&self,
|
||||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
_request: &api::IncomingWebhookRequestDetails<'_>,
|
||||||
|
) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> {
|
||||||
|
Ok(Box::new(crypto::HmacSha256))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_webhook_source_verification_signature(
|
||||||
|
&self,
|
||||||
|
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||||
|
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
|
||||||
|
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
|
||||||
|
let signature =
|
||||||
|
utils::get_header_key_value(webhook_headers::X_VOLT_SIGNED, request.headers)
|
||||||
|
.change_context(errors::ConnectorError::WebhookSignatureNotFound)?;
|
||||||
|
|
||||||
|
hex::decode(signature)
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::ConnectorError::WebhookVerificationSecretInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_webhook_source_verification_message(
|
||||||
|
&self,
|
||||||
|
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||||
|
_merchant_id: &str,
|
||||||
|
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
|
||||||
|
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
|
||||||
|
let x_volt_timed =
|
||||||
|
utils::get_header_key_value(webhook_headers::X_VOLT_TIMED, request.headers)?;
|
||||||
|
let user_agent = utils::get_header_key_value(webhook_headers::USER_AGENT, request.headers)?;
|
||||||
|
let version = user_agent
|
||||||
|
.split('/')
|
||||||
|
.last()
|
||||||
|
.ok_or(errors::ConnectorError::WebhookSourceVerificationFailed)?;
|
||||||
|
Ok(format!(
|
||||||
|
"{}|{}|{}",
|
||||||
|
String::from_utf8_lossy(request.body),
|
||||||
|
x_volt_timed,
|
||||||
|
version
|
||||||
|
)
|
||||||
|
.into_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_webhook_object_reference_id(
|
||||||
|
&self,
|
||||||
|
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||||
) -> CustomResult<api::webhooks::ObjectReferenceId, errors::ConnectorError> {
|
) -> CustomResult<api::webhooks::ObjectReferenceId, errors::ConnectorError> {
|
||||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
let webhook_body: volt::VoltWebhookBodyReference = request
|
||||||
|
.body
|
||||||
|
.parse_struct("VoltWebhookBodyReference")
|
||||||
|
.change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
|
||||||
|
let reference = match webhook_body.merchant_internal_reference {
|
||||||
|
Some(merchant_internal_reference) => {
|
||||||
|
api_models::payments::PaymentIdType::PaymentAttemptId(merchant_internal_reference)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
api_models::payments::PaymentIdType::ConnectorTransactionId(webhook_body.payment)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
|
||||||
|
reference,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_webhook_event_type(
|
fn get_webhook_event_type(
|
||||||
&self,
|
&self,
|
||||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||||
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
|
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
|
||||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
if request.body.is_empty() {
|
||||||
|
Ok(api::IncomingWebhookEvent::EndpointVerification)
|
||||||
|
} else {
|
||||||
|
let payload: volt::VoltWebhookBodyEventType = request
|
||||||
|
.body
|
||||||
|
.parse_struct("VoltWebhookBodyEventType")
|
||||||
|
.change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;
|
||||||
|
Ok(api::IncomingWebhookEvent::from(payload.status))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_webhook_resource_object(
|
fn get_webhook_resource_object(
|
||||||
&self,
|
&self,
|
||||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||||
) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
|
) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
|
||||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
let details: volt::VoltWebhookObjectResource = request
|
||||||
|
.body
|
||||||
|
.parse_struct("VoltWebhookObjectResource")
|
||||||
|
.change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;
|
||||||
|
Ok(Box::new(details))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
connector::utils::{self, AddressDetailsData, RouterData},
|
connector::utils::{self, AddressDetailsData, RouterData},
|
||||||
|
consts,
|
||||||
core::errors,
|
core::errors,
|
||||||
services,
|
services,
|
||||||
types::{self, api, storage::enums as storage_enums},
|
types::{self, api, storage::enums as storage_enums},
|
||||||
@ -41,6 +42,12 @@ impl<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod webhook_headers {
|
||||||
|
pub const X_VOLT_SIGNED: &str = "X-Volt-Signed";
|
||||||
|
pub const X_VOLT_TIMED: &str = "X-Volt-Timed";
|
||||||
|
pub const USER_AGENT: &str = "User-Agent";
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VoltPaymentsRequest {
|
pub struct VoltPaymentsRequest {
|
||||||
@ -50,7 +57,6 @@ pub struct VoltPaymentsRequest {
|
|||||||
transaction_type: TransactionType,
|
transaction_type: TransactionType,
|
||||||
merchant_internal_reference: String,
|
merchant_internal_reference: String,
|
||||||
shopper: ShopperDetails,
|
shopper: ShopperDetails,
|
||||||
notification_url: Option<String>,
|
|
||||||
payment_success_url: Option<String>,
|
payment_success_url: Option<String>,
|
||||||
payment_failure_url: Option<String>,
|
payment_failure_url: Option<String>,
|
||||||
payment_pending_url: Option<String>,
|
payment_pending_url: Option<String>,
|
||||||
@ -91,7 +97,6 @@ impl TryFrom<&VoltRouterData<&types::PaymentsAuthorizeRouterData>> for VoltPayme
|
|||||||
let payment_failure_url = item.router_data.request.router_return_url.clone();
|
let payment_failure_url = item.router_data.request.router_return_url.clone();
|
||||||
let payment_pending_url = item.router_data.request.router_return_url.clone();
|
let payment_pending_url = item.router_data.request.router_return_url.clone();
|
||||||
let payment_cancel_url = item.router_data.request.router_return_url.clone();
|
let payment_cancel_url = item.router_data.request.router_return_url.clone();
|
||||||
let notification_url = item.router_data.request.webhook_url.clone();
|
|
||||||
let address = item.router_data.get_billing_address()?;
|
let address = item.router_data.get_billing_address()?;
|
||||||
let shopper = ShopperDetails {
|
let shopper = ShopperDetails {
|
||||||
email: item.router_data.request.email.clone(),
|
email: item.router_data.request.email.clone(),
|
||||||
@ -109,7 +114,6 @@ impl TryFrom<&VoltRouterData<&types::PaymentsAuthorizeRouterData>> for VoltPayme
|
|||||||
payment_failure_url,
|
payment_failure_url,
|
||||||
payment_pending_url,
|
payment_pending_url,
|
||||||
payment_cancel_url,
|
payment_cancel_url,
|
||||||
notification_url,
|
|
||||||
shopper,
|
shopper,
|
||||||
transaction_type,
|
transaction_type,
|
||||||
})
|
})
|
||||||
@ -291,8 +295,9 @@ impl<F, T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Serialize, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
#[derive(strum::Display)]
|
||||||
pub enum VoltPaymentStatus {
|
pub enum VoltPaymentStatus {
|
||||||
NewPayment,
|
NewPayment,
|
||||||
Completed,
|
Completed,
|
||||||
@ -309,7 +314,15 @@ pub enum VoltPaymentStatus {
|
|||||||
Failed,
|
Failed,
|
||||||
Settled,
|
Settled,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum VoltPaymentsResponseData {
|
||||||
|
WebhookResponse(VoltWebhookObjectResource),
|
||||||
|
PsyncResponse(VoltPsyncResponse),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VoltPsyncResponse {
|
pub struct VoltPsyncResponse {
|
||||||
status: VoltPaymentStatus,
|
status: VoltPaymentStatus,
|
||||||
@ -317,29 +330,102 @@ pub struct VoltPsyncResponse {
|
|||||||
merchant_internal_reference: Option<String>,
|
merchant_internal_reference: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F, T> TryFrom<types::ResponseRouterData<F, VoltPsyncResponse, T, types::PaymentsResponseData>>
|
impl<F, T>
|
||||||
|
TryFrom<types::ResponseRouterData<F, VoltPaymentsResponseData, T, types::PaymentsResponseData>>
|
||||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||||
{
|
{
|
||||||
type Error = error_stack::Report<errors::ConnectorError>;
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
fn try_from(
|
fn try_from(
|
||||||
item: types::ResponseRouterData<F, VoltPsyncResponse, T, types::PaymentsResponseData>,
|
item: types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
VoltPaymentsResponseData,
|
||||||
|
T,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
match item.response {
|
||||||
status: enums::AttemptStatus::from(item.response.status),
|
VoltPaymentsResponseData::PsyncResponse(payment_response) => {
|
||||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
let status = enums::AttemptStatus::from(payment_response.status.clone());
|
||||||
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()),
|
Ok(Self {
|
||||||
redirection_data: None,
|
status,
|
||||||
mandate_reference: None,
|
response: if is_payment_failure(status) {
|
||||||
connector_metadata: None,
|
Err(types::ErrorResponse {
|
||||||
network_txn_id: None,
|
code: payment_response.status.clone().to_string(),
|
||||||
connector_response_reference_id: item
|
message: payment_response.status.clone().to_string(),
|
||||||
.response
|
reason: Some(payment_response.status.to_string()),
|
||||||
.merchant_internal_reference
|
status_code: item.http_code,
|
||||||
.or(Some(item.response.id)),
|
attempt_status: None,
|
||||||
incremental_authorization_allowed: None,
|
connector_transaction_id: Some(payment_response.id),
|
||||||
}),
|
})
|
||||||
..item.data
|
} else {
|
||||||
})
|
Ok(types::PaymentsResponseData::TransactionResponse {
|
||||||
|
resource_id: types::ResponseId::ConnectorTransactionId(
|
||||||
|
payment_response.id.clone(),
|
||||||
|
),
|
||||||
|
redirection_data: None,
|
||||||
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
|
network_txn_id: None,
|
||||||
|
connector_response_reference_id: payment_response
|
||||||
|
.merchant_internal_reference
|
||||||
|
.or(Some(payment_response.id)),
|
||||||
|
incremental_authorization_allowed: None,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
..item.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
VoltPaymentsResponseData::WebhookResponse(webhook_response) => {
|
||||||
|
let detailed_status = webhook_response.detailed_status.clone();
|
||||||
|
let status = enums::AttemptStatus::from(webhook_response.status);
|
||||||
|
Ok(Self {
|
||||||
|
status,
|
||||||
|
response: if is_payment_failure(status) {
|
||||||
|
Err(types::ErrorResponse {
|
||||||
|
code: detailed_status
|
||||||
|
.clone()
|
||||||
|
.map(|volt_status| volt_status.to_string())
|
||||||
|
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_owned()),
|
||||||
|
message: detailed_status
|
||||||
|
.clone()
|
||||||
|
.map(|volt_status| volt_status.to_string())
|
||||||
|
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_owned()),
|
||||||
|
reason: detailed_status
|
||||||
|
.clone()
|
||||||
|
.map(|volt_status| volt_status.to_string()),
|
||||||
|
status_code: item.http_code,
|
||||||
|
attempt_status: None,
|
||||||
|
connector_transaction_id: Some(webhook_response.payment.clone()),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(types::PaymentsResponseData::TransactionResponse {
|
||||||
|
resource_id: types::ResponseId::ConnectorTransactionId(
|
||||||
|
webhook_response.payment.clone(),
|
||||||
|
),
|
||||||
|
redirection_data: None,
|
||||||
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
|
network_txn_id: None,
|
||||||
|
connector_response_reference_id: webhook_response
|
||||||
|
.merchant_internal_reference
|
||||||
|
.or(Some(webhook_response.payment)),
|
||||||
|
incremental_authorization_allowed: None,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
..item.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<VoltWebhookStatus> for enums::AttemptStatus {
|
||||||
|
fn from(status: VoltWebhookStatus) -> Self {
|
||||||
|
match status {
|
||||||
|
VoltWebhookStatus::Completed | VoltWebhookStatus::Received => Self::Charged,
|
||||||
|
VoltWebhookStatus::Failed | VoltWebhookStatus::NotReceived => Self::Failure,
|
||||||
|
VoltWebhookStatus::Pending => Self::Pending,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,6 +491,68 @@ impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||||
|
pub struct VoltWebhookBodyReference {
|
||||||
|
pub payment: String,
|
||||||
|
pub merchant_internal_reference: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct VoltWebhookBodyEventType {
|
||||||
|
pub status: VoltWebhookStatus,
|
||||||
|
pub detailed_status: Option<VoltDetailedStatus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct VoltWebhookObjectResource {
|
||||||
|
pub payment: String,
|
||||||
|
pub merchant_internal_reference: Option<String>,
|
||||||
|
pub status: VoltWebhookStatus,
|
||||||
|
pub detailed_status: Option<VoltDetailedStatus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum VoltWebhookStatus {
|
||||||
|
Completed,
|
||||||
|
Failed,
|
||||||
|
Pending,
|
||||||
|
Received,
|
||||||
|
NotReceived,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
#[derive(strum::Display)]
|
||||||
|
pub enum VoltDetailedStatus {
|
||||||
|
RefusedByRisk,
|
||||||
|
RefusedByBank,
|
||||||
|
ErrorAtBank,
|
||||||
|
CancelledByUser,
|
||||||
|
AbandonedByUser,
|
||||||
|
Failed,
|
||||||
|
Completed,
|
||||||
|
BankRedirect,
|
||||||
|
DelayedAtBank,
|
||||||
|
AwaitingCheckoutAuthorisation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<VoltWebhookStatus> for api::IncomingWebhookEvent {
|
||||||
|
fn from(status: VoltWebhookStatus) -> Self {
|
||||||
|
match status {
|
||||||
|
VoltWebhookStatus::Completed | VoltWebhookStatus::Received => {
|
||||||
|
Self::PaymentIntentSuccess
|
||||||
|
}
|
||||||
|
VoltWebhookStatus::Failed | VoltWebhookStatus::NotReceived => {
|
||||||
|
Self::PaymentIntentFailure
|
||||||
|
}
|
||||||
|
VoltWebhookStatus::Pending => Self::PaymentIntentProcessing,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct VoltErrorResponse {
|
pub struct VoltErrorResponse {
|
||||||
pub exception: VoltErrorException,
|
pub exception: VoltErrorException,
|
||||||
@ -429,3 +577,32 @@ pub struct VoltErrorList {
|
|||||||
pub property: String,
|
pub property: String,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_payment_failure(status: enums::AttemptStatus) -> bool {
|
||||||
|
match status {
|
||||||
|
common_enums::AttemptStatus::AuthenticationFailed
|
||||||
|
| common_enums::AttemptStatus::AuthorizationFailed
|
||||||
|
| common_enums::AttemptStatus::CaptureFailed
|
||||||
|
| common_enums::AttemptStatus::VoidFailed
|
||||||
|
| common_enums::AttemptStatus::Failure => true,
|
||||||
|
common_enums::AttemptStatus::Started
|
||||||
|
| common_enums::AttemptStatus::RouterDeclined
|
||||||
|
| common_enums::AttemptStatus::AuthenticationPending
|
||||||
|
| common_enums::AttemptStatus::AuthenticationSuccessful
|
||||||
|
| common_enums::AttemptStatus::Authorized
|
||||||
|
| common_enums::AttemptStatus::Charged
|
||||||
|
| common_enums::AttemptStatus::Authorizing
|
||||||
|
| common_enums::AttemptStatus::CodInitiated
|
||||||
|
| common_enums::AttemptStatus::Voided
|
||||||
|
| common_enums::AttemptStatus::VoidInitiated
|
||||||
|
| common_enums::AttemptStatus::CaptureInitiated
|
||||||
|
| common_enums::AttemptStatus::AutoRefunded
|
||||||
|
| common_enums::AttemptStatus::PartialCharged
|
||||||
|
| common_enums::AttemptStatus::PartialChargedAndChargeable
|
||||||
|
| common_enums::AttemptStatus::Unresolved
|
||||||
|
| common_enums::AttemptStatus::Pending
|
||||||
|
| common_enums::AttemptStatus::PaymentMethodAwaited
|
||||||
|
| common_enums::AttemptStatus::ConfirmationAwaited
|
||||||
|
| common_enums::AttemptStatus::DeviceDataCollectionPending => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user