feat(connector): [Authorizedotnet] implement Capture flow and webhooks for Authorizedotnet (#1171)

Signed-off-by: chikke srujan <121822803+srujanchikke@users.noreply.github.com>
Co-authored-by: Jagan <jaganelavarasan@gmail.com>
This commit is contained in:
chikke srujan
2023-05-18 20:11:52 +05:30
committed by GitHub
parent 9cc1ceec69
commit 2d49ce56de
3 changed files with 1085 additions and 473 deletions

View File

@ -3,6 +3,7 @@ mod transformers;
use std::fmt::Debug;
use common_utils::{crypto, ext_traits::ByteSliceExt};
use error_stack::{IntoReport, ResultExt};
use transformers as authorizedotnet;
@ -10,11 +11,12 @@ use crate::{
configs::settings,
consts,
core::errors::{self, CustomResult},
db::StorageInterface,
headers,
services::{self, logger},
services::{self, ConnectorIntegration},
types::{
self,
api::{self, ConnectorCommon},
api::{self, ConnectorCommon, ConnectorCommonExt},
},
utils::{self, BytesExt},
};
@ -22,6 +24,22 @@ use crate::{
#[derive(Debug, Clone)]
pub struct Authorizedotnet;
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Authorizedotnet
where
Self: ConnectorIntegration<Flow, Request, Response>,
{
fn build_headers(
&self,
_req: &types::RouterData<Flow, Request, Response>,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
Ok(vec![(
headers::CONTENT_TYPE.to_string(),
self.get_content_type().to_string(),
)])
}
}
impl ConnectorCommon for Authorizedotnet {
fn id(&self) -> &'static str {
"authorizedotnet"
@ -46,7 +64,7 @@ impl api::ConnectorAccessToken for Authorizedotnet {}
impl api::PaymentToken for Authorizedotnet {}
impl
services::ConnectorIntegration<
ConnectorIntegration<
api::PaymentMethodToken,
types::PaymentMethodTokenizationData,
types::PaymentsResponseData,
@ -55,62 +73,121 @@ impl
// Not Implemented (R)
}
impl
services::ConnectorIntegration<
api::Session,
types::PaymentsSessionData,
types::PaymentsResponseData,
> for Authorizedotnet
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
for Authorizedotnet
{
// Not Implemented (R)
}
impl
services::ConnectorIntegration<
api::AccessTokenAuth,
types::AccessTokenRequestData,
types::AccessToken,
> for Authorizedotnet
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
for Authorizedotnet
{
// Not Implemented (R)
}
impl api::PreVerify for Authorizedotnet {}
impl
services::ConnectorIntegration<
api::Verify,
types::VerifyRequestData,
types::PaymentsResponseData,
> for Authorizedotnet
impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
for Authorizedotnet
{
// Issue: #173
}
impl
services::ConnectorIntegration<
api::Capture,
types::PaymentsCaptureData,
types::PaymentsResponseData,
> for Authorizedotnet
{
// Not Implemented (R)
}
impl
services::ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
for Authorizedotnet
{
fn get_headers(
&self,
_req: &types::PaymentsSyncRouterData,
_connectors: &settings::Connectors,
req: &types::PaymentsCaptureRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
self.common_get_content_type()
}
fn get_url(
&self,
_req: &types::PaymentsCaptureRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(self.base_url(connectors).to_string())
}
fn get_request_body(
&self,
req: &types::PaymentsCaptureRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let connector_req = authorizedotnet::CancelOrCaptureTransactionRequest::try_from(req)?;
let authorizedotnet_req =
utils::Encode::<authorizedotnet::CancelOrCaptureTransactionRequest>::encode_to_string_of_json(
&connector_req,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(authorizedotnet_req))
}
fn build_request(
&self,
req: &types::PaymentsCaptureRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsCaptureType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(types::PaymentsCaptureType::get_headers(
self, req, connectors,
)?)
.body(types::PaymentsCaptureType::get_request_body(self, req)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::PaymentsCaptureRouterData,
res: types::Response,
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
use bytes::Buf;
// Handle the case where response bytes contains U+FEFF (BOM) character sent by connector
let encoding = encoding_rs::UTF_8;
let intermediate_response = encoding.decode_with_bom_removal(res.response.chunk());
let intermediate_response =
bytes::Bytes::copy_from_slice(intermediate_response.0.as_bytes());
let response: authorizedotnet::AuthorizedotnetPaymentsResponse = intermediate_response
.parse_struct("AuthorizedotnetPaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
}
fn get_error_response(
&self,
res: types::Response,
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
get_error_response(res)
}
}
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
for Authorizedotnet
{
fn get_headers(
&self,
req: &types::PaymentsSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
// This connector does not require an auth header, the authentication details are sent in the request body
Ok(vec![(
headers::CONTENT_TYPE.to_string(),
types::PaymentsSyncType::get_content_type(self).to_string(),
)])
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
@ -175,7 +252,6 @@ impl
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
@ -186,23 +262,16 @@ impl
}
}
impl
services::ConnectorIntegration<
api::Authorize,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
> for Authorizedotnet
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
for Authorizedotnet
{
fn get_headers(
&self,
_req: &types::PaymentsAuthorizeRouterData,
_connectors: &settings::Connectors,
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
// This connector does not require an auth header, the authentication details are sent in the request body
Ok(vec![(
headers::CONTENT_TYPE.to_string(),
types::PaymentsAuthorizeType::get_content_type(self).to_string(),
)])
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
@ -221,7 +290,6 @@ impl
&self,
req: &types::PaymentsAuthorizeRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
logger::debug!(request=?req);
let connector_req = authorizedotnet::CreateTransactionRequest::try_from(req)?;
let authorizedotnet_req =
utils::Encode::<authorizedotnet::CreateTransactionRequest>::encode_to_string_of_json(
@ -261,7 +329,6 @@ impl
res: types::Response,
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
use bytes::Buf;
logger::debug!(authorizedotnetpayments_create_response=?res);
// Handle the case where response bytes contains U+FEFF (BOM) character sent by connector
let encoding = encoding_rs::UTF_8;
@ -272,40 +339,30 @@ impl
let response: authorizedotnet::AuthorizedotnetPaymentsResponse = intermediate_response
.parse_struct("AuthorizedotnetPaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseDeserializationFailed)
}
fn get_error_response(
&self,
res: types::Response,
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
logger::debug!(authorizedotnetpayments_create_error_response=?res);
get_error_response(res)
}
}
impl
services::ConnectorIntegration<
api::Void,
types::PaymentsCancelData,
types::PaymentsResponseData,
> for Authorizedotnet
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
for Authorizedotnet
{
fn get_headers(
&self,
_req: &types::PaymentsCancelRouterData,
_connectors: &settings::Connectors,
req: &types::PaymentsCancelRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
Ok(vec![(
headers::CONTENT_TYPE.to_string(),
types::PaymentsAuthorizeType::get_content_type(self).to_string(),
)])
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
@ -324,9 +381,9 @@ impl
&self,
req: &types::PaymentsCancelRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let connector_req = authorizedotnet::CancelTransactionRequest::try_from(req)?;
let connector_req = authorizedotnet::CancelOrCaptureTransactionRequest::try_from(req)?;
let authorizedotnet_req =
utils::Encode::<authorizedotnet::CancelTransactionRequest>::encode_to_string_of_json(
utils::Encode::<authorizedotnet::CancelOrCaptureTransactionRequest>::encode_to_string_of_json(
&connector_req,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
@ -361,17 +418,15 @@ impl
let intermediate_response =
bytes::Bytes::copy_from_slice(intermediate_response.0.as_bytes());
let response: authorizedotnet::AuthorizedotnetPaymentsResponse = intermediate_response
let response: authorizedotnet::AuthorizedotnetVoidResponse = intermediate_response
.parse_struct("AuthorizedotnetPaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
logger::debug!(authorizedotnetpayments_create_response=?response);
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseDeserializationFailed)
}
fn get_error_response(
@ -386,19 +441,16 @@ impl api::Refund for Authorizedotnet {}
impl api::RefundExecute for Authorizedotnet {}
impl api::RefundSync for Authorizedotnet {}
impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
for Authorizedotnet
{
fn get_headers(
&self,
_req: &types::RefundsRouterData<api::Execute>,
_connectors: &settings::Connectors,
req: &types::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
// This connector does not require an auth header, the authentication details are sent in the request body
Ok(vec![(
headers::CONTENT_TYPE.to_string(),
types::PaymentsAuthorizeType::get_content_type(self).to_string(),
)])
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
@ -417,7 +469,6 @@ impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::Ref
&self,
req: &types::RefundsRouterData<api::Execute>,
) -> CustomResult<Option<String>, errors::ConnectorError> {
logger::debug!(refund_request=?req);
let connector_req = authorizedotnet::CreateRefundRequest::try_from(req)?;
let authorizedotnet_req =
utils::Encode::<authorizedotnet::CreateRefundRequest>::encode_to_string_of_json(
@ -450,7 +501,6 @@ impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::Ref
res: types::Response,
) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
use bytes::Buf;
logger::debug!(response=?res);
// Handle the case where response bytes contains U+FEFF (BOM) character sent by connector
let encoding = encoding_rs::UTF_8;
@ -461,14 +511,12 @@ impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::Ref
let response: authorizedotnet::AuthorizedotnetRefundResponse = intermediate_response
.parse_struct("AuthorizedotnetRefundResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
logger::info!(response=?res);
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
@ -479,19 +527,16 @@ impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::Ref
}
}
impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
for Authorizedotnet
{
fn get_headers(
&self,
_req: &types::RefundsRouterData<api::RSync>,
_connectors: &settings::Connectors,
req: &types::RefundsRouterData<api::RSync>,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
// This connector does not require an auth header, the authentication details are sent in the request body
Ok(vec![(
headers::CONTENT_TYPE.to_string(),
types::RefundSyncType::get_content_type(self).to_string(),
)])
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
@ -556,7 +601,6 @@ impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::Refun
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
@ -569,25 +613,109 @@ impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::Refun
#[async_trait::async_trait]
impl api::IncomingWebhook for Authorizedotnet {
fn get_webhook_object_reference_id(
fn get_webhook_source_verification_algorithm(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> {
Ok(Box::new(crypto::HmacSha512))
}
fn get_webhook_source_verification_signature(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
let security_header = request
.headers
.get("X-ANET-Signature")
.map(|header_value| {
header_value
.to_str()
.map(String::from)
.map_err(|_| errors::ConnectorError::WebhookSignatureNotFound)
.into_report()
})
.ok_or(errors::ConnectorError::WebhookSignatureNotFound)
.into_report()??
.to_lowercase();
let (_, sig_value) = security_header
.split_once('=')
.ok_or(errors::ConnectorError::WebhookSourceVerificationFailed)
.into_report()?;
hex::decode(sig_value)
.into_report()
.change_context(errors::ConnectorError::WebhookSignatureNotFound)
}
fn get_webhook_source_verification_message(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
_merchant_id: &str,
_secret: &[u8],
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
Ok(request.body.to_vec())
}
fn get_webhook_object_reference_id(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
let details: authorizedotnet::AuthorizedotnetWebhookObjectId = request
.body
.parse_struct("AuthorizedotnetWebhookObjectId")
.change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
match details.event_type {
authorizedotnet::AuthorizedotnetWebhookEvent::RefundCreated => {
Ok(api_models::webhooks::ObjectReferenceId::RefundId(
api_models::webhooks::RefundIdType::ConnectorRefundId(
authorizedotnet::get_trans_id(details)?,
),
))
}
_ => Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
api_models::payments::PaymentIdType::ConnectorTransactionId(
authorizedotnet::get_trans_id(details)?,
),
)),
}
}
async fn get_webhook_source_verification_merchant_secret(
&self,
db: &dyn StorageInterface,
merchant_id: &str,
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
let key = format!("whsec_verification_{}_{}", self.id(), merchant_id);
let secret = match db.find_config_by_key(&key).await {
Ok(config) => Some(config),
Err(e) => {
crate::logger::warn!("Unable to fetch merchant webhook secret from DB: {:#?}", e);
None
}
};
Ok(secret
.map(|conf| conf.config.into_bytes())
.unwrap_or_default())
}
fn get_webhook_event_type(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
let details: authorizedotnet::AuthorizedotnetWebhookEventType = request
.body
.parse_struct("AuthorizedotnetWebhookEventType")
.change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;
Ok(api::IncomingWebhookEvent::from(details.event_type))
}
fn get_webhook_resource_object(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<serde_json::Value, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
let payload = serde_json::to_value(request.body)
.into_report()
.change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;
Ok(payload)
}
}
@ -603,23 +731,33 @@ fn get_error_response(
.parse_struct("AuthorizedotnetPaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
logger::info!(response=?response);
Ok(response
.transaction_response
.errors
.and_then(|errors| {
errors.into_iter().next().map(|error| types::ErrorResponse {
code: error.error_code,
message: error.error_text,
match response.transaction_response {
Some(transaction_response) => Ok({
transaction_response
.errors
.and_then(|errors| {
errors.into_iter().next().map(|error| types::ErrorResponse {
code: error.error_code,
message: error.error_text,
reason: None,
status_code,
})
})
.unwrap_or_else(|| types::ErrorResponse {
code: consts::NO_ERROR_CODE.to_string(),
message: consts::NO_ERROR_MESSAGE.to_string(),
reason: None,
status_code,
})
}),
None => {
let message = &response.messages.message[0].text;
Ok(types::ErrorResponse {
code: consts::NO_ERROR_CODE.to_string(),
message: message.to_string(),
reason: None,
status_code,
})
})
.unwrap_or_else(|| types::ErrorResponse {
code: consts::NO_ERROR_CODE.to_string(),
message: consts::NO_ERROR_MESSAGE.to_string(),
reason: None,
status_code,
}))
}
}
}

View File

@ -3,7 +3,7 @@ use error_stack::ResultExt;
use serde::{Deserialize, Serialize};
use crate::{
connector::utils::{CardData, RefundsRequestData},
connector::utils::{CardData, PaymentsSyncRequestData, RefundsRequestData},
core::errors,
types::{self, api, storage::enums},
utils::OptionExt,
@ -13,6 +13,10 @@ use crate::{
pub enum TransactionType {
#[serde(rename = "authCaptureTransaction")]
Payment,
#[serde(rename = "authOnlyTransaction")]
Authorization,
#[serde(rename = "priorAuthCaptureTransaction")]
Capture,
#[serde(rename = "refundTransaction")]
Refund,
#[serde(rename = "voidTransaction")]
@ -191,8 +195,9 @@ struct AuthorizationIndicator {
#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct TransactionVoidRequest {
struct TransactionVoidOrCaptureRequest {
transaction_type: TransactionType,
amount: Option<i64>,
ref_trans_id: String,
}
@ -205,9 +210,9 @@ pub struct AuthorizedotnetPaymentsRequest {
#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct AuthorizedotnetPaymentCancelRequest {
pub struct AuthorizedotnetPaymentCancelOrCaptureRequest {
merchant_authentication: MerchantAuthentication,
transaction_request: TransactionVoidRequest,
transaction_request: TransactionVoidOrCaptureRequest,
}
#[derive(Debug, Serialize, PartialEq)]
@ -219,8 +224,8 @@ pub struct CreateTransactionRequest {
#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CancelTransactionRequest {
create_transaction_request: AuthorizedotnetPaymentCancelRequest,
pub struct CancelOrCaptureTransactionRequest {
create_transaction_request: AuthorizedotnetPaymentCancelOrCaptureRequest,
}
#[derive(Debug, Serialize, PartialEq, Eq)]
@ -249,7 +254,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for CreateTransactionRequest {
authorization_indicator: c.into(),
});
let transaction_request = TransactionRequest {
transaction_type: TransactionType::Payment,
transaction_type: TransactionType::from(item.request.capture_method),
amount: item.request.amount,
payment: payment_details,
currency_code: item.request.currency.to_string(),
@ -269,10 +274,11 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for CreateTransactionRequest {
}
}
impl TryFrom<&types::PaymentsCancelRouterData> for CancelTransactionRequest {
impl TryFrom<&types::PaymentsCancelRouterData> for CancelOrCaptureTransactionRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
let transaction_request = TransactionVoidRequest {
let transaction_request = TransactionVoidOrCaptureRequest {
amount: item.request.amount,
transaction_type: TransactionType::Void,
ref_trans_id: item.request.connector_transaction_id.to_string(),
};
@ -280,7 +286,27 @@ impl TryFrom<&types::PaymentsCancelRouterData> for CancelTransactionRequest {
let merchant_authentication = MerchantAuthentication::try_from(&item.connector_auth_type)?;
Ok(Self {
create_transaction_request: AuthorizedotnetPaymentCancelRequest {
create_transaction_request: AuthorizedotnetPaymentCancelOrCaptureRequest {
merchant_authentication,
transaction_request,
},
})
}
}
impl TryFrom<&types::PaymentsCaptureRouterData> for CancelOrCaptureTransactionRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
let transaction_request = TransactionVoidOrCaptureRequest {
amount: Some(item.request.amount_to_capture),
transaction_type: TransactionType::Capture,
ref_trans_id: item.request.connector_transaction_id.to_string(),
};
let merchant_authentication = MerchantAuthentication::try_from(&item.connector_auth_type)?;
Ok(Self {
create_transaction_request: AuthorizedotnetPaymentCancelOrCaptureRequest {
merchant_authentication,
transaction_request,
},
@ -306,7 +332,7 @@ pub type AuthorizedotnetRefundStatus = AuthorizedotnetPaymentStatus;
impl From<AuthorizedotnetPaymentStatus> for enums::AttemptStatus {
fn from(item: AuthorizedotnetPaymentStatus) -> Self {
match item {
AuthorizedotnetPaymentStatus::Approved => Self::Charged,
AuthorizedotnetPaymentStatus::Approved => Self::Pending,
AuthorizedotnetPaymentStatus::Declined | AuthorizedotnetPaymentStatus::Error => {
Self::Failure
}
@ -316,9 +342,9 @@ impl From<AuthorizedotnetPaymentStatus> for enums::AttemptStatus {
}
#[derive(Debug, Clone, Deserialize, PartialEq)]
struct ResponseMessage {
pub struct ResponseMessage {
code: String,
text: String,
pub text: String,
}
#[derive(Debug, Clone, Deserialize, PartialEq)]
@ -331,14 +357,14 @@ enum ResultCode {
#[serde(rename_all = "camelCase")]
pub struct ResponseMessages {
result_code: ResultCode,
message: Vec<ResponseMessage>,
pub message: Vec<ResponseMessage>,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub(super) struct ErrorMessage {
pub(super) error_code: String,
pub(super) error_text: String,
pub struct ErrorMessage {
pub error_code: String,
pub error_text: String,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
@ -356,10 +382,51 @@ pub struct TransactionResponse {
#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct AuthorizedotnetPaymentsResponse {
pub transaction_response: TransactionResponse,
pub transaction_response: Option<TransactionResponse>,
pub messages: ResponseMessages,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthorizedotnetVoidResponse {
pub transaction_response: Option<VoidResponse>,
pub messages: ResponseMessages,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VoidResponse {
response_code: AuthorizedotnetVoidStatus,
auth_code: String,
#[serde(rename = "transId")]
transaction_id: String,
network_trans_id: Option<String>,
pub account_number: Option<String>,
pub errors: Option<Vec<ErrorMessage>>,
}
#[derive(Debug, Clone, Deserialize)]
pub enum AuthorizedotnetVoidStatus {
#[serde(rename = "1")]
Approved,
#[serde(rename = "2")]
Declined,
#[serde(rename = "3")]
Error,
#[serde(rename = "4")]
HeldForReview,
}
impl From<AuthorizedotnetVoidStatus> for enums::AttemptStatus {
fn from(item: AuthorizedotnetVoidStatus) -> Self {
match item {
AuthorizedotnetVoidStatus::Approved => Self::VoidInitiated,
AuthorizedotnetVoidStatus::Declined | AuthorizedotnetVoidStatus::Error => Self::Failure,
AuthorizedotnetVoidStatus::HeldForReview => Self::Pending,
}
}
}
impl<F, T>
TryFrom<
types::ResponseRouterData<
@ -379,50 +446,115 @@ impl<F, T>
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
let status = enums::AttemptStatus::from(item.response.transaction_response.response_code);
let error = item
.response
.transaction_response
.errors
.and_then(|errors| {
errors.into_iter().next().map(|error| types::ErrorResponse {
code: error.error_code,
message: error.error_text,
reason: None,
status_code: item.http_code,
match &item.response.transaction_response {
Some(transaction_response) => {
let status = enums::AttemptStatus::from(transaction_response.response_code.clone());
let error = transaction_response.errors.as_ref().and_then(|errors| {
errors.iter().next().map(|error| types::ErrorResponse {
code: error.error_code.clone(),
message: error.error_text.clone(),
reason: None,
status_code: item.http_code,
})
});
let metadata = transaction_response
.account_number
.as_ref()
.map(|acc_no| {
Encode::<'_, PaymentDetails>::encode_to_value(
&construct_refund_payment_details(acc_no.clone()),
)
})
.transpose()
.change_context(errors::ConnectorError::MissingRequiredField {
field_name: "connector_metadata",
})?;
Ok(Self {
status,
response: match error {
Some(err) => Err(err),
None => Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(
transaction_response.transaction_id.clone(),
),
redirection_data: None,
mandate_reference: None,
connector_metadata: metadata,
network_txn_id: transaction_response.network_trans_id.clone(),
}),
},
..item.data
})
});
}
None => Ok(Self {
status: enums::AttemptStatus::Failure,
response: Err(get_err_response(item.http_code, item.response.messages)),
..item.data
}),
}
}
}
let metadata = item
.response
.transaction_response
.account_number
.map(|acc_no| {
Encode::<'_, PaymentDetails>::encode_to_value(&construct_refund_payment_details(
acc_no,
))
})
.transpose()
.change_context(errors::ConnectorError::MissingRequiredField {
field_name: "connector_metadata",
})?;
Ok(Self {
status,
response: match error {
Some(err) => Err(err),
None => Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(
item.response.transaction_response.transaction_id,
),
redirection_data: None,
mandate_reference: None,
connector_metadata: metadata,
network_txn_id: item.response.transaction_response.network_trans_id,
}),
},
..item.data
})
impl<F, T>
TryFrom<
types::ResponseRouterData<F, AuthorizedotnetVoidResponse, T, types::PaymentsResponseData>,
> for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
AuthorizedotnetVoidResponse,
T,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
match &item.response.transaction_response {
Some(transaction_response) => {
let status = enums::AttemptStatus::from(transaction_response.response_code.clone());
let error = transaction_response.errors.as_ref().and_then(|errors| {
errors.iter().next().map(|error| types::ErrorResponse {
code: error.error_code.clone(),
message: error.error_text.clone(),
reason: None,
status_code: item.http_code,
})
});
let metadata = transaction_response
.account_number
.as_ref()
.map(|acc_no| {
Encode::<'_, PaymentDetails>::encode_to_value(
&construct_refund_payment_details(acc_no.clone()),
)
})
.transpose()
.change_context(errors::ConnectorError::MissingRequiredField {
field_name: "connector_metadata",
})?;
Ok(Self {
status,
response: match error {
Some(err) => Err(err),
None => Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(
transaction_response.transaction_id.clone(),
),
redirection_data: None,
mandate_reference: None,
connector_metadata: metadata,
network_txn_id: transaction_response.network_trans_id.clone(),
}),
},
..item.data
})
}
None => Ok(Self {
status: enums::AttemptStatus::Failure,
response: Err(get_err_response(item.http_code, item.response.messages)),
..item.data
}),
}
}
}
@ -571,22 +703,11 @@ impl TryFrom<&types::PaymentsSyncRouterData> for AuthorizedotnetCreateSyncReques
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsSyncRouterData) -> Result<Self, Self::Error> {
let transaction_id = item
.response
.as_ref()
.ok()
.map(|payment_response_data| match payment_response_data {
types::PaymentsResponseData::TransactionResponse { resource_id, .. } => {
resource_id.get_connector_transaction_id()
}
_ => Err(error_stack::report!(
errors::ValidationError::MissingRequiredField {
field_name: "transaction_id".to_string()
}
)),
})
.transpose()
.change_context(errors::ConnectorError::ResponseHandlingFailed)?;
let transaction_id = Some(
item.request
.get_connector_transaction_id()
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?,
);
let merchant_authentication = MerchantAuthentication::try_from(&item.connector_auth_type)?;
@ -612,6 +733,8 @@ pub enum SyncStatus {
Voided,
CouldNotVoid,
GeneralError,
#[serde(rename = "FDSPendingReview")]
FDSPendingReview,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -623,7 +746,8 @@ pub struct SyncTransactionResponse {
#[derive(Debug, Deserialize)]
pub struct AuthorizedotnetSyncResponse {
transaction: SyncTransactionResponse,
transaction: Option<SyncTransactionResponse>,
messages: ResponseMessages,
}
impl From<SyncStatus> for enums::RefundStatus {
@ -639,9 +763,9 @@ impl From<SyncStatus> for enums::RefundStatus {
impl From<SyncStatus> for enums::AttemptStatus {
fn from(transaction_status: SyncStatus) -> Self {
match transaction_status {
SyncStatus::SettledSuccessfully | SyncStatus::CapturedPendingSettlement => {
Self::Charged
}
SyncStatus::SettledSuccessfully => Self::Charged,
SyncStatus::CapturedPendingSettlement => Self::CaptureInitiated,
SyncStatus::AuthorizedPendingCapture => Self::Authorized,
SyncStatus::Declined => Self::AuthenticationFailed,
SyncStatus::Voided => Self::Voided,
SyncStatus::CouldNotVoid => Self::VoidFailed,
@ -659,14 +783,22 @@ impl TryFrom<types::RefundsResponseRouterData<api::RSync, AuthorizedotnetSyncRes
fn try_from(
item: types::RefundsResponseRouterData<api::RSync, AuthorizedotnetSyncResponse>,
) -> Result<Self, Self::Error> {
let refund_status = enums::RefundStatus::from(item.response.transaction.transaction_status);
Ok(Self {
response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.transaction.transaction_id.clone(),
refund_status,
match item.response.transaction {
Some(transaction) => {
let refund_status = enums::RefundStatus::from(transaction.transaction_status);
Ok(Self {
response: Ok(types::RefundsResponseData {
connector_refund_id: transaction.transaction_id,
refund_status,
}),
..item.data
})
}
None => Ok(Self {
response: Err(get_err_response(item.http_code, item.response.messages)),
..item.data
}),
..item.data
})
}
}
}
@ -685,21 +817,28 @@ impl<F, Req>
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
let payment_status =
enums::AttemptStatus::from(item.response.transaction.transaction_status);
Ok(Self {
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(
item.response.transaction.transaction_id,
),
redirection_data: None,
mandate_reference: None,
connector_metadata: None,
network_txn_id: None,
match item.response.transaction {
Some(transaction) => {
let payment_status = enums::AttemptStatus::from(transaction.transaction_status);
Ok(Self {
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(
transaction.transaction_id,
),
redirection_data: None,
mandate_reference: None,
connector_metadata: None,
network_txn_id: None,
}),
status: payment_status,
..item.data
})
}
None => Ok(Self {
response: Err(get_err_response(item.http_code, item.response.messages)),
..item.data
}),
status: payment_status,
..item.data
})
}
}
}
@ -724,3 +863,78 @@ fn construct_refund_payment_details(masked_number: String) -> PaymentDetails {
card_code: None,
})
}
impl From<Option<enums::CaptureMethod>> for TransactionType {
fn from(capture_method: Option<enums::CaptureMethod>) -> Self {
match capture_method {
Some(enums::CaptureMethod::Manual) => Self::Authorization,
_ => Self::Payment,
}
}
}
fn get_err_response(status_code: u16, message: ResponseMessages) -> types::ErrorResponse {
types::ErrorResponse {
code: message.message[0].code.clone(),
message: message.message[0].text.clone(),
reason: None,
status_code,
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthorizedotnetWebhookObjectId {
pub webhook_id: String,
pub event_type: AuthorizedotnetWebhookEvent,
pub payload: AuthorizedotnetWebhookPayload,
}
#[derive(Debug, Deserialize)]
pub struct AuthorizedotnetWebhookPayload {
pub id: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthorizedotnetWebhookEventType {
pub event_type: AuthorizedotnetWebhookEvent,
}
#[derive(Debug, Deserialize)]
pub enum AuthorizedotnetWebhookEvent {
#[serde(rename = "net.authorize.payment.authorization.created")]
AuthorizationCreated,
#[serde(rename = "net.authorize.payment.priorAuthCapture.created")]
PriorAuthCapture,
#[serde(rename = "net.authorize.payment.authcapture.created")]
AuthCapCreated,
#[serde(rename = "net.authorize.payment.capture.created")]
CaptureCreated,
#[serde(rename = "net.authorize.payment.void.created")]
VoidCreated,
#[serde(rename = "net.authorize.payment.refund.created")]
RefundCreated,
}
impl From<AuthorizedotnetWebhookEvent> for api::IncomingWebhookEvent {
fn from(event_type: AuthorizedotnetWebhookEvent) -> Self {
match event_type {
AuthorizedotnetWebhookEvent::AuthorizationCreated
| AuthorizedotnetWebhookEvent::PriorAuthCapture
| AuthorizedotnetWebhookEvent::AuthCapCreated
| AuthorizedotnetWebhookEvent::CaptureCreated
| AuthorizedotnetWebhookEvent::VoidCreated => Self::PaymentIntentSuccess,
AuthorizedotnetWebhookEvent::RefundCreated => Self::RefundSuccess,
}
}
}
pub fn get_trans_id(
details: AuthorizedotnetWebhookObjectId,
) -> Result<String, errors::ConnectorError> {
details
.payload
.id
.ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)
}