feat(connector): add payment create, sync, capture, refund, void, rsync support for globalpay (#322)

This commit is contained in:
Jagan
2023-01-11 19:33:07 +05:30
committed by GitHub
parent 1e04719ac3
commit 4fdc768081
26 changed files with 2550 additions and 163 deletions

15
Cargo.lock generated
View File

@ -378,6 +378,19 @@ dependencies = [
"futures-core",
]
[[package]]
name = "async-compression"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a"
dependencies = [
"flate2",
"futures-core",
"memchr",
"pin-project-lite",
"tokio",
]
[[package]]
name = "async-stream"
version = "0.3.3"
@ -3044,6 +3057,7 @@ version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
dependencies = [
"async-compression",
"base64 0.13.1",
"bytes",
"encoding_rs",
@ -3067,6 +3081,7 @@ dependencies = [
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tokio-util 0.7.4",
"tower-service",
"url",
"wasm-bindgen",

View File

@ -37,8 +37,8 @@ locker_decryption_key1 = ""
locker_decryption_key2 = ""
[connectors.supported]
wallets = ["klarna", "braintree", "applepay"]
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "aci", "shift4", "cybersource", "worldpay"]
wallets = ["klarna","braintree","applepay"]
cards = ["stripe","adyen","authorizedotnet","checkout","braintree","aci","shift4","cybersource", "worldpay", "globalpay"]
[refund]
max_attempts = 10
@ -80,6 +80,9 @@ base_url = "https://api.shift4.com/"
[connectors.worldpay]
base_url = "http://localhost:9090/"
[connectors.globalpay]
base_url = "https://apis.sandbox.globalpay.com/ucp/"
[scheduler]
stream = "SCHEDULER_STREAM"
consumer_group = "SCHEDULER_GROUP"

View File

@ -140,10 +140,13 @@ base_url = "https://api.shift4.com/"
[connectors.worldpay]
base_url = "https://try.access.worldpay.com/"
[connectors.globalpay]
base_url = "https://apis.sandbox.globalpay.com/ucp/"
# This data is used to call respective connectors for wallets and cards
[connectors.supported]
wallets = ["klarna", "braintree", "applepay"]
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "cybersource"]
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "cybersource", "shift4", "worldpay", "globalpay"]
# Scheduler settings provides a point to modify the behaviour of scheduler flow.
# It defines the the streams/queues name and configuration as well as event selection variables

View File

@ -89,6 +89,9 @@ base_url = "https://api.shift4.com/"
[connectors.worldpay]
base_url = "https://try.access.worldpay.com/"
[connectors.globalpay]
base_url = "https://apis.sandbox.globalpay.com/ucp/"
[connectors.supported]
wallets = ["klarna", "braintree", "applepay"]
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "shift4", "cybersource", "worldpay"]
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "shift4", "cybersource", "worldpay", "globalpay"]

View File

@ -53,7 +53,10 @@ impl ConnectorCommon for {{project-name | downcase | pascal_case}} {
}
fn get_auth_header(&self,_auth_type:&types::ConnectorAuthType)-> CustomResult<Vec<(String,String)>,errors::ConnectorError> {
todo!()
let auth: {{project-name | downcase | pascal_case}}::{{project-name | downcase | pascal_case}}AuthType = auth_type
.try_into()
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)])
}
}
@ -86,14 +89,14 @@ impl
{
fn get_headers(
&self,
_req: &types::PaymentsSyncRouterData,
_connectors: &settings::Connectors,
req: &types::PaymentsSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
todo!()
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
todo!()
self.common_get_content_type()
}
fn get_url(
@ -109,14 +112,20 @@ impl
_req: &types::PaymentsSyncRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
todo!()
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Get)
.url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
.headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
.build(),
))
}
fn get_error_response(
&self,
_res: Bytes,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
todo!()
self.build_error_response(res)
}
fn handle_response(
@ -124,7 +133,17 @@ impl
_data: &types::PaymentsSyncRouterData,
_res: Response,
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
todo!()
logger::debug!(payment_sync_response=?res);
let response: {{project-name | downcase}}:: {{project-name | downcase | pascal_case}}PaymentsResponse = res
.response
.parse_struct("{{project-name | downcase}} PaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
}
@ -139,13 +158,21 @@ impl
{
fn get_headers(
&self,
_req: &types::PaymentsCaptureRouterData,
_connectors: &settings::Connectors,
req: &types::PaymentsCaptureRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
todo!()
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> {
todo!()
}
@ -161,7 +188,15 @@ impl
_req: &types::PaymentsCaptureRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
todo!()
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsCaptureType::get_url(self, req, connectors)?)
.headers(types::PaymentsCaptureType::get_headers(
self, req, connectors,
)?)
.build(),
))
}
fn handle_response(
@ -169,22 +204,25 @@ impl
_data: &types::PaymentsCaptureRouterData,
_res: Response,
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
todo!()
let response: {{project-name | downcase }}::{{project-name | downcase | pascal_case}}PaymentsResponse = res
.response
.parse_struct("{{project-name | downcase | pascal_case}} PaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
logger::debug!({{project-name | downcase}}payments_create_response=?response);
types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
}
fn get_url(
&self,
_req: &types::PaymentsCaptureRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
todo!()
.try_into()
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
_res: Bytes,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
todo!()
self.build_error_response(res)
}
}
@ -208,12 +246,12 @@ impl
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
> for {{project-name | downcase | pascal_case}} {
fn get_headers(&self, _req: &types::PaymentsAuthorizeRouterData,_connectors: &settings::Connectors,) -> CustomResult<Vec<(String, String)>,errors::ConnectorError> {
todo!()
fn get_headers(&self, req: &types::PaymentsAuthorizeRouterData, connectors: &settings::Connectors,) -> CustomResult<Vec<(String, String)>,errors::ConnectorError> {
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
todo!()
self.common_get_content_type()
}
fn get_url(&self, _req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors,) -> CustomResult<String,errors::ConnectorError> {
@ -226,6 +264,25 @@ impl
Ok(Some({{project-name | downcase}}_req))
}
fn build_request(
&self,
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsAuthorizeType::get_url(
self, req, connectors,
)?)
.headers(types::PaymentsAuthorizeType::get_headers(
self, req, connectors,
)?)
.body(types::PaymentsAuthorizeType::get_request_body(self, req)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::PaymentsAuthorizeRouterData,
@ -242,8 +299,8 @@ impl
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(&self, _res: Bytes) -> CustomResult<ErrorResponse,errors::ConnectorError> {
todo!()
fn get_error_response(&self, res: Bytes) -> CustomResult<ErrorResponse,errors::ConnectorError> {
self.build_error_response(res)
}
}
@ -257,12 +314,12 @@ impl
types::RefundsData,
types::RefundsResponseData,
> for {{project-name | downcase | pascal_case}} {
fn get_headers(&self, _req: &types::RefundsRouterData<api::Execute>,_connectors: &settings::Connectors,) -> CustomResult<Vec<(String,String)>,errors::ConnectorError> {
todo!()
fn get_headers(&self, req: &types::RefundsRouterData<api::Execute>, connectors: &settings::Connectors,) -> CustomResult<Vec<(String,String)>,errors::ConnectorError> {
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
todo!()
self.common_get_content_type()
}
fn get_url(&self, _req: &types::RefundsRouterData<api::Execute>, _connectors: &settings::Connectors,) -> CustomResult<String,errors::ConnectorError> {
@ -300,25 +357,40 @@ impl
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(&self, _res: Bytes) -> CustomResult<ErrorResponse,errors::ConnectorError> {
todo!()
fn get_error_response(&self, res: Bytes) -> CustomResult<ErrorResponse,errors::ConnectorError> {
self.build_error_response(res)
}
}
impl
ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for {{project-name | downcase | pascal_case}} {
fn get_headers(&self, _req: &types::RefundSyncRouterData,_connectors: &settings::Connectors,) -> CustomResult<Vec<(String, String)>,errors::ConnectorError> {
todo!()
fn get_headers(&self, req: &types::RefundSyncRouterData,connectors: &settings::Connectors,) -> CustomResult<Vec<(String, String)>,errors::ConnectorError> {
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
todo!()
self.common_get_content_type()
}
fn get_url(&self, _req: &types::RefundSyncRouterData,_connectors: &settings::Connectors,) -> CustomResult<String,errors::ConnectorError> {
todo!()
}
fn build_request(
&self,
req: &types::RefundSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Get)
.url(&types::RefundSyncType::get_url(self, req, connectors)?)
.headers(types::RefundSyncType::get_headers(self, req, connectors)?)
.body(types::RefundSyncType::get_request_body(self, req)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::RefundSyncRouterData,
@ -335,8 +407,8 @@ impl
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(&self, _res: Bytes) -> CustomResult<ErrorResponse,errors::ConnectorError> {
todo!()
fn get_error_response(&self, res: Bytes) -> CustomResult<ErrorResponse,errors::ConnectorError> {
self.build_error_response(res)
}
}

View File

@ -82,11 +82,10 @@ async fn should_refund_succeeded_payment() {
let response = connector.make_payment(None).await;
//try refund for previous payment
if let Some(transaction_id) = utils::get_connector_transaction_id(response) {
let transaction_id = utils::get_connector_transaction_id(response).unwrap();
let response = connector.refund_payment(transaction_id, None).await;
assert_eq!(
response.response.unwrap().refund_status,
enums::RefundStatus::Success,
);
}
}

View File

@ -36,9 +36,9 @@ pub enum {{project-name | downcase | pascal_case}}PaymentStatus {
impl From<{{project-name | downcase | pascal_case}}PaymentStatus> for enums::AttemptStatus {
fn from(item: {{project-name | downcase | pascal_case}}PaymentStatus) -> Self {
match item {
{{project-name | downcase | pascal_case}}PaymentStatus::Succeeded => enums::AttemptStatus::Charged,
{{project-name | downcase | pascal_case}}PaymentStatus::Failed => enums::AttemptStatus::Failure,
{{project-name | downcase | pascal_case}}PaymentStatus::Processing => enums::AttemptStatus::Authorizing,
{{project-name | downcase | pascal_case}}PaymentStatus::Succeeded => Self::Charged,
{{project-name | downcase | pascal_case}}PaymentStatus::Failed => Self::Failure,
{{project-name | downcase | pascal_case}}PaymentStatus::Processing => Self::Authorizing,
}
}
}
@ -47,10 +47,19 @@ impl From<{{project-name | downcase | pascal_case}}PaymentStatus> for enums::Att
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct {{project-name | downcase | pascal_case}}PaymentsResponse {}
impl TryFrom<types::PaymentsResponseRouterData<{{project-name | downcase | pascal_case}}PaymentsResponse>> for types::PaymentsAuthorizeRouterData {
impl<F,T> TryFrom<types::ResponseRouterData<F, {{project-name | downcase | pascal_case}}PaymentsResponse, T, types::PaymentsResponseData>> for types::RouterData<F, T, types::PaymentsResponseData> {
type Error = error_stack::Report<errors::ParsingError>;
fn try_from(_item: types::PaymentsResponseRouterData<{{project-name | downcase | pascal_case}}PaymentsResponse>) -> Result<Self,Self::Error> {
todo!()
fn try_from(item: types::ResponseRouterData<F, {{project-name | downcase | pascal_case}}PaymentsResponse, T, types::PaymentsResponseData>) -> Result<Self,Self::Error> {
Ok(Self {
status: enums::AttemptStatus::from(item.response.status),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
redirection_data: None,
redirect: false,
mandate_reference: None,
}),
..item.data
})
}
}

View File

@ -503,6 +503,7 @@ pub enum Connector {
Cybersource,
#[default]
Dummy,
Globalpay,
Klarna,
Shift4,
Stripe,

View File

@ -53,7 +53,7 @@ nanoid = "0.4.0"
num_cpus = "1.15.0"
once_cell = "1.17.0"
rand = "0.8.5"
reqwest = { version = "0.11.13", features = ["json", "native-tls"] }
reqwest = { version = "0.11.13", features = ["json", "native-tls", "gzip"] }
ring = "0.16.20"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.91"

View File

@ -135,6 +135,7 @@ pub struct Connectors {
pub shift4: ConnectorParams,
pub stripe: ConnectorParams,
pub supported: SupportedConnectors,
pub globalpay: ConnectorParams,
pub worldpay: ConnectorParams,
pub applepay: ConnectorParams,
}

View File

@ -5,13 +5,15 @@ pub mod authorizedotnet;
pub mod braintree;
pub mod checkout;
pub mod cybersource;
pub mod globalpay;
pub mod klarna;
pub mod shift4;
pub mod stripe;
pub mod utils;
pub mod worldpay;
pub use self::{
aci::Aci, adyen::Adyen, applepay::Applepay, authorizedotnet::Authorizedotnet,
braintree::Braintree, checkout::Checkout, cybersource::Cybersource, klarna::Klarna,
shift4::Shift4, stripe::Stripe, worldpay::Worldpay,
braintree::Braintree, checkout::Checkout, cybersource::Cybersource, globalpay::Globalpay,
klarna::Klarna, shift4::Shift4, stripe::Stripe, worldpay::Worldpay,
};

View File

@ -0,0 +1,605 @@
mod requests;
mod response;
mod transformers;
use std::fmt::Debug;
use bytes::Bytes;
use error_stack::{IntoReport, ResultExt};
use self::{
requests::GlobalpayPaymentsRequest, response::GlobalpayPaymentsResponse,
transformers as globalpay,
};
use crate::{
configs::settings,
core::{
errors::{self, CustomResult},
payments,
},
headers, logger,
services::{self, ConnectorIntegration},
types::{
self,
api::{self, ConnectorCommon, ConnectorCommonExt},
ErrorResponse, Response,
},
utils::{self, BytesExt},
};
#[derive(Debug, Clone)]
pub struct Globalpay;
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Globalpay
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> {
let mut headers = vec![
(
headers::CONTENT_TYPE.to_string(),
self.get_content_type().to_string(),
),
("X-GP-Version".to_string(), "2021-03-22".to_string()),
];
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
headers.append(&mut api_key);
Ok(headers)
}
}
impl ConnectorCommon for Globalpay {
fn id(&self) -> &'static str {
"globalpay"
}
fn common_get_content_type(&self) -> &'static str {
"application/json"
}
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
connectors.globalpay.base_url.as_ref()
}
fn get_auth_header(
&self,
auth_type: &types::ConnectorAuthType,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
let auth: globalpay::GlobalpayAuthType = auth_type
.try_into()
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)])
}
fn build_error_response(
&self,
res: Bytes,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: globalpay::GlobalpayErrorResponse = res
.parse_struct("Globalpay ErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
Ok(ErrorResponse {
code: response.error_code,
message: response.detailed_error_description,
reason: None,
})
}
}
impl api::Payment for Globalpay {}
impl api::PreVerify for Globalpay {}
impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
for Globalpay
{
}
impl api::PaymentVoid for Globalpay {}
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
for Globalpay
{
fn get_headers(
&self,
req: &types::PaymentsCancelRouterData,
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::PaymentsCancelRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
"{}/transactions/{}/reversal",
self.base_url(connectors),
req.request.connector_transaction_id
))
}
fn build_request(
&self,
req: &types::PaymentsCancelRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsVoidType::get_url(self, req, connectors)?)
.headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
.body(types::PaymentsVoidType::get_request_body(self, req)?)
.build(),
))
}
fn get_request_body(
&self,
req: &types::PaymentsCancelRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let globalpay_req = utils::Encode::<GlobalpayPaymentsRequest>::convert_and_encode(req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(globalpay_req))
}
fn handle_response(
&self,
data: &types::PaymentsCancelRouterData,
res: Response,
) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> {
let response: GlobalpayPaymentsResponse = res
.response
.parse_struct("Globalpay PaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
logger::debug!(globalpaypayments_create_response=?response);
types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
}
.try_into()
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Bytes,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl api::PaymentSync for Globalpay {}
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
for Globalpay
{
fn get_headers(
&self,
req: &types::PaymentsSyncRouterData,
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::PaymentsSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
"{}transactions/{}",
self.base_url(connectors),
req.request
.connector_transaction_id
.get_connector_transaction_id()
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?
))
}
fn build_request(
&self,
req: &types::PaymentsSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Get)
.url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
.headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
.build(),
))
}
fn get_error_response(
&self,
res: Bytes,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
fn handle_response(
&self,
data: &types::PaymentsSyncRouterData,
res: Response,
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
logger::debug!(payment_sync_response=?res);
let response: GlobalpayPaymentsResponse = res
.response
.parse_struct("globalpay PaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
}
impl api::PaymentCapture for Globalpay {}
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
for Globalpay
{
fn get_headers(
&self,
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(format!(
"{}/transactions/{}/capture",
self.base_url(connectors),
req.request.connector_transaction_id
))
}
fn get_request_body(
&self,
req: &types::PaymentsCaptureRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let globalpay_req = utils::Encode::<GlobalpayPaymentsRequest>::convert_and_encode(req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(globalpay_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)?)
.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: Response,
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
let response: GlobalpayPaymentsResponse = res
.response
.parse_struct("Globalpay PaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
logger::debug!(globalpaypayments_create_response=?response);
types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
}
.try_into()
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Bytes,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl api::PaymentSession for Globalpay {}
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
for Globalpay
{
}
impl api::PaymentAuthorize for Globalpay {}
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
for Globalpay
{
fn get_headers(
&self,
req: &types::PaymentsAuthorizeRouterData,
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::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!("{}transactions", self.base_url(connectors)))
}
fn get_request_body(
&self,
req: &types::PaymentsAuthorizeRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let globalpay_req = utils::Encode::<GlobalpayPaymentsRequest>::convert_and_encode(req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(globalpay_req))
}
fn build_request(
&self,
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsAuthorizeType::get_url(
self, req, connectors,
)?)
.headers(types::PaymentsAuthorizeType::get_headers(
self, req, connectors,
)?)
.body(types::PaymentsAuthorizeType::get_request_body(self, req)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::PaymentsAuthorizeRouterData,
res: Response,
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
let response: GlobalpayPaymentsResponse = res
.response
.parse_struct("Globalpay PaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
logger::debug!(globalpaypayments_create_response=?response);
types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
}
.try_into()
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Bytes,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl api::Refund for Globalpay {}
impl api::RefundExecute for Globalpay {}
impl api::RefundSync for Globalpay {}
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
for Globalpay
{
fn get_headers(
&self,
req: &types::RefundsRouterData<api::Execute>,
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::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
"{}transactions/{}/refund",
self.base_url(connectors),
req.request.connector_transaction_id
))
}
fn get_request_body(
&self,
req: &types::RefundsRouterData<api::Execute>,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let globalpay_req =
utils::Encode::<requests::GlobalpayRefundRequest>::convert_and_encode(req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(globalpay_req))
}
fn build_request(
&self,
req: &types::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let request = services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::RefundExecuteType::get_url(self, req, connectors)?)
.headers(types::RefundExecuteType::get_headers(
self, req, connectors,
)?)
.body(types::RefundExecuteType::get_request_body(self, req)?)
.build();
Ok(Some(request))
}
fn handle_response(
&self,
data: &types::RefundsRouterData<api::Execute>,
res: Response,
) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
logger::debug!(target: "router::connector::globalpay", response=?res);
let response: GlobalpayPaymentsResponse = res
.response
.parse_struct("globalpay RefundResponse")
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
}
.try_into()
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Bytes,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
for Globalpay
{
fn get_headers(
&self,
req: &types::RefundSyncRouterData,
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::RefundSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
"{}transactions/{}",
self.base_url(connectors),
req.request.connector_transaction_id
))
}
fn build_request(
&self,
req: &types::RefundSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Get)
.url(&types::RefundSyncType::get_url(self, req, connectors)?)
.headers(types::RefundSyncType::get_headers(self, req, connectors)?)
.body(types::RefundSyncType::get_request_body(self, req)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::RefundSyncRouterData,
res: Response,
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
logger::debug!(target: "router::connector::globalpay", response=?res);
let response: GlobalpayPaymentsResponse = res
.response
.parse_struct("globalpay RefundResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
}
.try_into()
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Bytes,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
#[async_trait::async_trait]
impl api::IncomingWebhook for Globalpay {
fn get_webhook_object_reference_id(
&self,
_body: &[u8],
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
}
fn get_webhook_event_type(
&self,
_body: &[u8],
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
}
fn get_webhook_resource_object(
&self,
_body: &[u8],
) -> CustomResult<serde_json::Value, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
}
}
impl services::ConnectorRedirectResponse for Globalpay {
fn get_flow_type(
&self,
_query_params: &str,
) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> {
Ok(payments::CallConnectorAction::Trigger)
}
}

View File

@ -0,0 +1,805 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Serialize)]
pub struct GlobalpayPaymentsRequest {
/// A meaningful label for the merchant account set by Global Payments.
pub account_name: String,
/// The amount to transfer between Payer and Merchant for a SALE or a REFUND. It is always
/// represented in the lowest denomiation of the related currency.
pub amount: Option<String>,
/// Indicates if the merchant would accept an authorization for an amount less than the
/// requested amount. This is available for CP channel
/// only where the balance not authorized can be processed again using a different card.
pub authorization_mode: Option<AuthorizationMode>,
/// Indicates whether the transaction is to be captured automatically, later or later using
/// more than 1 partial capture.
pub capture_mode: Option<CaptureMode>,
/// The amount of the transaction that relates to cashback.It is always represented in the
/// lowest denomiation of the related currency.
pub cashback_amount: Option<String>,
/// Describes whether the transaction was processed in a face to face(CP) scenario or a
/// Customer Not Present (CNP) scenario.
pub channel: Channel,
/// The amount that reflects the charge the merchant applied to the transaction for availing
/// of a more convenient purchase.It is always represented in the lowest denomiation of the
/// related currency.
pub convenience_amount: Option<String>,
/// The country in ISO-3166-1(alpha-2 code) format.
pub country: String,
/// The currency of the amount in ISO-4217(alpha-3)
pub currency: String,
pub currency_conversion: Option<CurrencyConversion>,
/// Merchant defined field to describe the transaction.
pub description: Option<String>,
pub device: Option<Device>,
/// The amount of the gratuity for a transaction.It is always represented in the lowest
/// denomiation of the related currency.
pub gratuity_amount: Option<String>,
/// Indicates whether the Merchant or the Payer initiated the creation of a transaction.
pub initiator: Option<Initiator>,
/// Indicates the source IP Address of the system used to create the transaction.
pub ip_address: Option<String>,
/// Indicates the language the transaction was executed in. In the format ISO-639-1 (alpha-2)
/// or ISO-639-1 (alpha-2)_ISO-3166(alpha-2)
pub language: Option<Language>,
pub lodging: Option<Lodging>,
/// Indicates to Global Payments where the merchant wants to receive notifications of certain
/// events that occur on the Global Payments system.
pub notifications: Option<Notifications>,
pub order: Option<Order>,
/// The merchant's payer reference for the transaction
pub payer_reference: Option<String>,
pub payment_method: PaymentMethod,
/// Merchant defined field to reference the transaction.
pub reference: String,
/// A merchant defined reference for the location that created the transaction.
pub site_reference: Option<String>,
/// Stored data information used to create a transaction.
pub stored_credential: Option<StoredCredential>,
/// The amount that reflects the additional charge the merchant applied to the transaction
/// for using a specific payment method.It is always represented in the lowest denomiation of
/// the related currency.
pub surcharge_amount: Option<String>,
/// Indicates the total or expected total of captures that will executed against a
/// transaction flagged as being captured multiple times.
pub total_capture_count: Option<i64>,
/// Describes whether the transaction is a SALE, that moves funds from Payer to Merchant, or
/// a REFUND where funds move from Merchant to Payer.
#[serde(rename = "type")]
pub globalpay_payments_request_type: Option<GlobalpayPaymentsRequestType>,
/// The merchant's user reference for the transaction. This represents the person who
/// processed the transaction on the merchant's behalf like a clerk or cashier reference.
pub user_reference: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CurrencyConversion {
/// A unique identifier generated by Global Payments to identify the currency conversion. It
/// can be used to reference a currency conversion when processing a sale or a refund
/// transaction.
pub id: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Device {
pub capabilities: Option<Capabilities>,
pub entry_modes: Option<Vec<Vec<DeviceEntryMode>>>,
/// Describes whether a device prompts a payer for a gratuity when the payer is entering
/// their payment method details to the device.
pub gratuity_prompt_mode: Option<GratuityPromptMode>,
/// Describes the receipts a device prints when processing a transaction.
pub print_receipt_mode: Option<PrintReceiptMode>,
/// The sequence number from the device used to align with processing platform.
pub sequence_number: Option<String>,
/// A unique identifier for the physical device. This value persists with the device even if
/// it is repurposed.
pub serial_number: Option<String>,
/// The time from the device in ISO8601 format
pub time: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Capabilities {
pub authorization_modes: Option<Vec<AuthorizationMode>>,
/// The number of lines that can be used to display information on the device.
pub display_line_count: Option<f64>,
pub enabled_response: Option<Vec<EnabledResponse>>,
pub entry_modes: Option<Vec<CapabilitiesEntryMode>>,
pub fraud: Option<Vec<AuthorizationMode>>,
pub mobile: Option<Vec<Mobile>>,
pub payer_verifications: Option<Vec<PayerVerification>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Lodging {
/// A reference that identifies the booking reference for a lodging stay.
pub booking_reference: Option<String>,
/// The amount charged for one nights lodging.
pub daily_rate_amount: Option<String>,
/// A reference that identifies the booking reference for a lodging stay.
pub date_checked_in: Option<String>,
/// The check out date for a lodging stay.
pub date_checked_out: Option<String>,
/// The total number of days of the lodging stay.
pub duration_days: Option<f64>,
#[serde(rename = "lodging.charge_items")]
pub lodging_charge_items: Option<Vec<LodgingChargeItem>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LodgingChargeItem {
pub payment_method_program_codes: Option<Vec<PaymentMethodProgramCode>>,
/// A reference that identifies the charge item, such as a lodging folio number.
pub reference: Option<String>,
/// The total amount for the list of charge types for a charge item.
pub total_amount: Option<String>,
pub types: Option<Vec<TypeElement>>,
}
/// Indicates to Global Payments where the merchant wants to receive notifications of certain
/// events that occur on the Global Payments system.
#[derive(Debug, Serialize, Deserialize)]
pub struct Notifications {
/// The merchant URL that will receive the notification when the customer has completed the
/// authentication.
pub challenge_return_url: Option<String>,
/// The merchant URL that will receive the notification when the customer has completed the
/// authentication when the authentication is decoupled and separate to the purchase.
pub decoupled_challenge_return_url: Option<String>,
/// The merchant URL to return the payer to, once the payer has completed payment using the
/// payment method. This returns control of the payer's payment experience to the merchant.
pub return_url: Option<String>,
/// The merchant URL to notify the merchant of the latest status of the transaction.
pub status_url: Option<String>,
/// The merchant URL that will receive the notification when the 3DS ACS successfully gathers
/// de ice informatiSon and tonotification_configurations.cordingly.
pub three_ds_method_return_url: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Order {
/// Merchant defined field common to all transactions that are part of the same order.
pub reference: Option<String>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PaymentMethod {
pub apm: Option<Apm>,
pub authentication: Option<Authentication>,
pub bank_transfer: Option<BankTransfer>,
pub card: Option<Card>,
pub digital_wallet: Option<DigitalWallet>,
pub encryption: Option<Encryption>,
/// Indicates how the payment method information was obtained by the Merchant for this
/// transaction.
pub entry_mode: PaymentMethodEntryMode,
/// Indicates whether to execute the fingerprint signature functionality.
pub fingerprint_mode: Option<FingerprintMode>,
/// Specify the first name of the owner of the payment method.
pub first_name: Option<String>,
/// Unique Global Payments generated id used to reference a stored payment method on the
/// Global Payments system. Often referred to as the payment method token. This value can be
/// used instead of payment method details such as a card number and expiry date.
pub id: Option<String>,
/// Specify the surname of the owner of the payment method.
pub last_name: Option<String>,
/// The full name of the owner of the payment method.
pub name: Option<String>,
/// Contains the value a merchant wishes to appear on the payer's payment method statement
/// for this transaction
pub narrative: Option<String>,
/// Indicates whether to store the card as part of a transaction.
pub storage_mode: Option<CardStorageMode>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Apm {
/// A string used to identify the payment method provider being used to execute this
/// transaction.
pub provider: Option<ApmProvider>,
}
/// Information outlining the degree of authentication executed related to a transaction.
#[derive(Debug, Serialize, Deserialize)]
pub struct Authentication {
/// Information outlining the degree of 3D Secure authentication executed.
pub three_ds: Option<ThreeDs>,
/// A message authentication code that is used to confirm the security and integrity of the
/// messaging to Global Payments.
pub mac: Option<String>,
}
/// Information outlining the degree of 3D Secure authentication executed.
#[derive(Debug, Serialize, Deserialize)]
pub struct ThreeDs {
/// The reference created by the 3DSecure Directory Server to identify the specific
/// authentication attempt.
pub ds_trans_reference: Option<String>,
/// An indication of the degree of the authentication and liability shift obtained for this
/// transaction. It is determined during the 3D Secure process. 2 or 1 for Mastercard
/// indicates the merchant has a liability shift. 5 or 6 for Visa or Amex indicates the
/// merchant has a liability shift. However for Amex if the payer is not enrolled the eci may
/// still be 6 but liability shift has not bee achieved.
pub eci: Option<String>,
/// Indicates if any exemptions apply to this transaction.
pub exempt_status: Option<ExemptStatus>,
/// Indicates the version of 3DS
pub message_version: Option<String>,
/// The reference created by the 3DSecure provider to identify the specific authentication
/// attempt.
pub server_trans_reference: Option<String>,
/// The authentication value created as part of the 3D Secure process.
pub value: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BankTransfer {
/// The number or reference for the payer's bank account.
pub account_number: Option<String>,
pub bank: Option<Bank>,
/// The number or reference for the check
pub check_reference: Option<String>,
/// The type of bank account associated with the payer's bank account.
pub number_type: Option<NumberType>,
/// Indicates how the transaction was authorized by the merchant.
pub sec_code: Option<SecCode>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Bank {
pub address: Option<Address>,
/// The local identifier code for the bank.
pub code: Option<String>,
/// The name of the bank.
pub name: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Address {
/// Merchant defined field common to all transactions that are part of the same order.
pub city: Option<String>,
/// The country in ISO-3166-1(alpha-2 code) format.
pub country: Option<String>,
/// First line of the address.
pub line_1: Option<String>,
/// Second line of the address.
pub line_2: Option<String>,
/// Third line of the address.
pub line_3: Option<String>,
/// The city or town of the address.
pub postal_code: Option<String>,
/// The state or region of the address. ISO 3166-2 minus the country code itself. For
/// example, US Illinois = IL, or in the case of GB counties Wiltshire = WI or Aberdeenshire
/// = ABD
pub state: Option<String>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Card {
/// The card providers description of their card product.
pub account_type: Option<String>,
/// Code generated when the card is successfully authorized.
pub authcode: Option<String>,
/// First line of the address associated with the card.
pub avs_address: Option<String>,
/// Postal code of the address associated with the card.
pub avs_postal_code: Option<String>,
/// The unique reference created by the brands/schemes to uniquely identify the transaction.
pub brand_reference: Option<String>,
/// Indicates if a fallback mechanism was used to obtain the card information when EMV/chip
/// did not work as expected.
pub chip_condition: Option<ChipCondition>,
/// The numeric value printed on the physical card.
pub cvv: String,
/// Card Verification Value Indicator sent by the Merchant indicating the CVV
/// availability.
pub cvv_indicator: CvvIndicator,
/// The 2 digit expiry date month of the card.
pub expiry_month: String,
/// The 2 digit expiry date year of the card.
pub expiry_year: String,
/// Indicates whether the card is a debit or credit card.
pub funding: Option<Funding>,
/// The the card account number used to authorize the transaction. Also known as PAN.
pub number: String,
/// Contains the pin block info, relating to the pin code the Payer entered.
pub pin_block: Option<String>,
/// The full card tag data for an EMV/chip card transaction.
pub tag: Option<String>,
/// Data from magnetic stripe of a card
pub track: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DigitalWallet {
/// First line of the address associated with the card.
pub avs_address: Option<String>,
/// Postal code of the address associated with the card.
pub avs_postal_code: Option<String>,
/// The authentication value use to verify the validity of the digit wallet transaction.
pub cryptogram: Option<String>,
/// The numeric value printed on the physical card.
pub cvv: Option<String>,
/// Card Verification Value Indicator sent by the Merchant indicating the CVV
/// availability.
pub cvv_indicator: Option<CvvIndicator>,
/// An indication of the degree of the authentication and liability shift obtained for this
/// transaction. It is determined during the 3D Secure process. 2 or 1 for Mastercard
/// indicates the merchant has a liability shift. 5 or 6 for Visa or Amex indicates the
/// merchant has a liability shift. However for Amex if the payer is not enrolled the eci may
/// still be 6 but liability shift has not bee achieved.
pub eci: Option<String>,
/// The 2 digit expiry date month of the card.
pub expiry_month: Option<String>,
/// The 2 digit expiry date year of the card.
pub expiry_year: Option<String>,
/// Identifies who provides the digital wallet for the Payer.
pub provider: Option<DigitalWalletProvider>,
/// A token that represents, or is the payment method, stored with the digital wallet.
pub token: Option<String>,
/// Indicates if the actual card number or a token is being used to process the
/// transaction.
pub token_format: Option<TokenFormat>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Encryption {
/// The encryption info used when sending encrypted card data to Global Payments.
pub info: Option<String>,
/// The encryption method used when sending encrypted card data to Global Payments.
pub method: Option<Method>,
/// The version of encryption being used.
pub version: Option<String>,
}
/// Stored data information used to create a transaction.
#[derive(Debug, Serialize, Deserialize)]
pub struct StoredCredential {
/// Indicates the transaction processing model being executed when using stored
/// credentials.
pub model: Option<Model>,
/// The reason stored credentials are being used to to create a transaction.
pub reason: Option<Reason>,
/// Indiciates the order of this transaction in the sequence of a planned repeating
/// transaction processing model.
pub sequence: Option<Sequence>,
}
/// Indicates if the merchant would accept an authorization for an amount less than the
/// requested amount. This is available for CP channel
/// only where the balance not authorized can be processed again using a different card.
///
/// Describes the instruction a device can indicate to the clerk in the case of fraud.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AuthorizationMode {
/// Indicates merchant would accept an authorization for an amount less than the
/// requested amount.
/// pub example: PARTIAL
///
///
/// Describes whether the device can process partial authorizations.
Partial,
}
/// Indicates whether the transaction is to be captured automatically, later or later using
/// more than 1 partial capture.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CaptureMode {
/// If a transaction is authorized, funds will exchange between the payer and
/// merchant automatically and as soon as possible.
Auto,
/// If a transaction is authorized, funds will not exchange between the payer and
/// merchant automatically and will require a subsequent separate action to capture that
/// transaction and start the funding process. Only one successful capture is permitted.
Later,
/// If a transaction is authorized, funds will not exchange between the payer
/// and merchant automatically. One or more subsequent separate capture actions are required
/// to capture that transaction in parts and start the funding process for the part captured.
/// One or many successful capture are permitted once the total amount captured is within a
/// range of the original authorized amount.'
Multiple,
}
/// Describes whether the transaction was processed in a face to face(CP) scenario or a
/// Customer Not Present (CNP) scenario.
#[derive(Debug, Default, Serialize, Deserialize)]
pub enum Channel {
#[default]
#[serde(rename = "CNP")]
/// A Customer NOT Present transaction is when the payer and the merchant are not
/// together when exchanging payment method information to fulfill a transaction. e.g. a
/// transaction executed from a merchant's website or over the phone
CustomerNotPresent,
#[serde(rename = "CP")]
/// A Customer Present transaction is when the payer and the merchant are in direct
/// face to face contact when exchanging payment method information to fulfill a transaction.
/// e.g. in a store and paying at the counter that is attended by a clerk.
CustomerPresent,
}
/// Describes the data the device can handle when it receives a response for a card
/// authorization.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum EnabledResponse {
Avs,
BrandReference,
Cvv,
MaskedNumberLast4,
}
/// Describes the entry mode capabilities a device has.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CapabilitiesEntryMode {
Chip,
Contactless,
ContactlessSwipe,
Manual,
Swipe,
}
/// Describes the mobile features a device has
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Mobile {
IntegratedCardReader,
SeparateCardReader,
}
/// Describes the capabilities a device has to verify a payer.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PayerVerification {
ContactlessSignature,
PayerDevice,
Pinpad,
}
/// Describes the allowed entry modes to obtain payment method information from the payer as
/// part of a transaction request.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum DeviceEntryMode {
Chip,
Contactless,
Manual,
Swipe,
}
/// Describes whether a device prompts a payer for a gratuity when the payer is entering
/// their payment method details to the device.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum GratuityPromptMode {
NotRequired,
Prompt,
}
/// Describes the receipts a device prints when processing a transaction.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PrintReceiptMode {
Both,
Merchant,
None,
Payer,
}
/// Describes whether the transaction is a SALE, that moves funds from Payer to Merchant, or
/// a REFUND where funds move from Merchant to Payer.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum GlobalpayPaymentsRequestType {
/// indicates the movement, or the attempt to move, funds from merchant to the
/// payer.
Refund,
/// indicates the movement, or the attempt to move, funds from payer to a
/// merchant.
Sale,
}
/// Indicates whether the Merchant or the Payer initiated the creation of a transaction.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Initiator {
/// The transaction was initated by the merchant, who is getting paid by the
/// payer.'
Merchant,
/// The transaction was initated by the customer who is paying the merchant.
Payer,
}
/// Indicates the language the transaction was executed in. In the format ISO-639-1 (alpha-2)
/// or ISO-639-1 (alpha-2)_ISO-3166(alpha-2)
#[derive(Debug, Serialize, Deserialize)]
pub enum Language {
#[serde(rename = "fr")]
Fr,
#[serde(rename = "fr_CA")]
FrCa,
#[serde(rename = "ISO-639(alpha-2)")]
Iso639Alpha2,
#[serde(rename = "ISO-639(alpha-2)_ISO-3166(alpha-2)")]
Iso639alpha2Iso3166alpha2,
}
/// Describes the payment method programs, typically run by card brands such as Amex, Visa
/// and MC.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PaymentMethodProgramCode {
AssuredReservation,
CardDeposit,
Other,
Purchase,
}
/// Describes the types of charges associated with a transaction. This can be one or more
/// than more charge type.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TypeElement {
GiftShop,
Laundry,
MiniBar,
NoShow,
Other,
Phone,
Restaurant,
}
/// A string used to identify the payment method provider being used to execute this
/// transaction.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ApmProvider {
Giropay,
Ideal,
Paypal,
Sofort,
Testpay,
}
/// Indicates if any exemptions apply to this transaction.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ExemptStatus {
LowValue,
ScaDelegation,
SecureCorporatePayment,
TransactionRiskAnalysis,
TrustedMerchant,
}
/// The type of bank account associated with the payer's bank account.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NumberType {
Checking,
Savings,
}
/// Indicates how the transaction was authorized by the merchant.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum SecCode {
/// Cash Concentration or Disbursement - Can be either a credit or debit application
/// where funds are wither distributed or consolidated between corporate entities.
#[serde(rename = "CCD")]
CashConcentrationOrDisbursement,
/// Point of Sale Entry - Point of sale debit applications non-shared (POS)
/// environment. These transactions are most often initiated by the consumer via a plastic
/// access card. This is only support for normal ACH transactions
#[serde(rename = "POP")]
PointOfSaleEntry,
/// Prearranged Payment and Deposits - used to credit or debit a consumer account.
/// Popularity used for payroll direct deposits and pre-authorized bill payments.
#[serde(rename = "PPD")]
PrearrangedPaymentAndDeposits,
/// Telephone-Initiated Entry - Used for the origination of a single entry debit
/// transaction to a consumer's account pursuant to a verbal authorization obtained from the
/// consumer via the telephone.
#[serde(rename = "TEL")]
TelephoneInitiatedEntry,
/// Internet (Web)-Initiated Entry - Used for the origination of debit entries
/// (either Single or Recurring Entry) to a consumer's account pursuant to a to an
/// authorization that is obtained from the Receiver via the Internet.
#[serde(rename = "WEB")]
WebInitiatedEntry,
}
/// Indicates if a fallback mechanism was used to obtain the card information when EMV/chip
/// did not work as expected.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ChipCondition {
/// indicates the previous transaction with this card failed.
PrevFailed,
/// indicates the previous transaction with this card was a success.
PrevSuccess,
}
/// Card Verification Value Indicator sent by the Merchant indicating the CVV
/// availability.
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CvvIndicator {
/// indicates the cvv is present but cannot be read.
Illegible,
/// indicates the cvv is not present on the card.
NotPresent,
#[default]
/// indicates the cvv is present.
Present,
}
/// Indicates whether the card is a debit or credit card.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Funding {
/// indicates the card is an, Electronic Benefits Transfer, for cash
/// benefits.
CashBenefits,
/// indicates the card is a credit card where the funds may be available on credit
/// to the payer to fulfill the transaction amount.
Credit,
/// indicates the card is a debit card where the funds may be present in an account
/// to fulfill the transaction amount.
Debit,
/// indicates the card is an, Electronic Benefits Transfer, for food stamps.
FoodStamp,
/// indicates the card is a prepaid card where the funds are loaded to the card
/// account to fulfill the transaction amount. Unlike a debit card, a prepaid is not linked
/// to a bank account.
Prepaid,
}
/// Identifies who provides the digital wallet for the Payer.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum DigitalWalletProvider {
Applepay,
PayByGoogle,
}
/// Indicates if the actual card number or a token is being used to process the
/// transaction.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TokenFormat {
/// The value in the digital wallet token field is a real card number
/// (PAN)
CardNumber,
/// The value in the digital wallet token field is a temporary token in the
/// format of a card number (PAN) but is not a real card number.
CardToken,
}
/// The encryption method used when sending encrypted card data to Global Payments.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Method {
Ksn,
Ktb,
}
/// Indicates how the payment method information was obtained by the Merchant for this
/// transaction.
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PaymentMethodEntryMode {
/// A CP channel entry mode where the payment method information was obtained from a
/// chip. E.g. card is inserted into a device to read the chip.
Chip,
/// A CP channel entry mode where the payment method information was
/// obtained by bringing the payment method to close proximity of a device. E.g. tap a cardon
/// or near a device to exchange card information.
ContactlessChip,
/// A CP channel entry mode where the payment method information was
/// obtained by bringing the payment method to close proximity of a device and also swiping
/// the card. E.g. tap a card on or near a device and swipe it through device to exchange
/// card information
ContactlessSwipe,
#[default]
/// A CNP channel entry mode where the payment method was obtained via a browser.
Ecom,
/// A CNP channel entry mode where the payment method was obtained via an
/// application and applies to digital wallets only.
InApp,
/// A CNP channel entry mode where the payment method was obtained via postal mail.
Mail,
/// A CP channel entry mode where the payment method information was obtained by
/// manually keying the payment method information into the device.
Manual,
/// A CNP channel entry mode where the payment method information was obtained over
/// the phone or via postal mail.
Moto,
/// A CNP channel entry mode where the payment method was obtained over the
/// phone.
Phone,
/// A CP channel entry mode where the payment method information was obtained from
/// swiping a magnetic strip. E.g. card's magnetic strip is swiped through a device to read
/// the card information.
Swipe,
}
/// Indicates whether to execute the fingerprint signature functionality.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum FingerprintMode {
/// Always check and create the fingerprint value regardless of the result of the
/// card authorization.
Always,
/// Always check and create the fingerprint value when the card authorization
/// is successful.
OnSuccess,
}
/// Indicates whether to store the card as part of a transaction.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CardStorageMode {
/// /// The card information is always stored irrespective of whether the payment
/// method authorization was successful or not.
Always,
/// The card information is only stored if the payment method authorization was
/// successful.
OnSuccess,
}
/// Indicates the transaction processing model being executed when using stored
/// credentials.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Model {
/// The transaction is a repeat transaction initiated by the merchant and
/// taken using the payment method stored with the merchant, as part of an agreed schedule of
/// transactions and where the amount is known and agreed in advanced. For example the
/// payment in full of a good in fixed installments over a defined period of time.'
Installment,
/// The transaction is a repeat transaction initiated by the merchant and taken
/// using the payment method stored with the merchant, as part of an agreed schedule of
/// transactions.
Recurring,
/// The transaction is a repeat transaction initiated by the merchant and
/// taken using the payment method stored with the merchant, as part of an agreed schedule of
/// transactions. The amount taken is based on the usage by the payer of the good or service.
/// for example a monthly mobile phone bill.
Subscription,
/// the transaction is adhoc or unscheduled. For example a payer visiting a
/// merchant to make purchase using the payment method stored with the merchant.
Unscheduled,
}
/// The reason stored credentials are being used to to create a transaction.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Reason {
Delayed,
Incremental,
NoShow,
Reauthorization,
Resubmission,
}
/// Indiciates the order of this transaction in the sequence of a planned repeating
/// transaction processing model.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Sequence {
First,
Last,
Subsequent,
}
#[derive(Default, Debug, Serialize)]
pub struct GlobalpayRefundRequest {
pub amount: String,
}

View File

@ -0,0 +1,363 @@
use serde::{Deserialize, Serialize};
use super::requests;
#[derive(Debug, Serialize, Deserialize)]
pub struct GlobalpayPaymentsResponse {
/// A unique identifier for the merchant account set by Global Payments.
pub account_id: Option<String>,
/// A meaningful label for the merchant account set by Global Payments.
pub account_name: Option<String>,
/// Information about the Action executed.
pub action: Option<Action>,
/// The amount to transfer between Payer and Merchant for a SALE or a REFUND. It is always
/// represented in the lowest denomiation of the related currency.
pub amount: Option<String>,
/// Indicates if the merchant would accept an authorization for an amount less than the
/// requested amount. This is available for CP channel
/// only where the balance not authorized can be processed again using a different card.
pub authorization_mode: Option<requests::AuthorizationMode>,
/// A Global Payments created reference that uniquely identifies the batch.
pub batch_id: Option<String>,
/// Indicates whether the transaction is to be captured automatically, later or later using
/// more than 1 partial capture.
pub capture_mode: Option<requests::CaptureMode>,
/// Describes whether the transaction was processed in a face to face(CP) scenario or a
/// Customer Not Present (CNP) scenario.
pub channel: Option<requests::Channel>,
/// The country in ISO-3166-1(alpha-2 code) format.
pub country: Option<String>,
/// The currency of the amount in ISO-4217(alpha-3)
pub currency: Option<String>,
/// Information relating to a currency conversion.
pub currency_conversion: Option<requests::CurrencyConversion>,
/// A unique identifier generated by Global Payments to identify the transaction.
pub id: String,
/// A unique identifier for the merchant set by Global Payments.
pub merchant_id: Option<String>,
/// A meaningful label for the merchant set by Global Payments.
pub merchant_name: Option<String>,
pub payment_method: Option<PaymentMethod>,
/// Merchant defined field to reference the transaction.
pub reference: Option<String>,
/// Indicates where a transaction is in its lifecycle.
pub status: GlobalpayPaymentStatus,
/// Global Payments time indicating when the object was created in ISO-8601 format.
pub time_created: Option<String>,
/// Describes whether the transaction is a SALE, that moves funds from Payer to Merchant, or
/// a REFUND where funds move from Merchant to Payer.
#[serde(rename = "type")]
pub globalpay_payments_response_type: Option<requests::GlobalpayPaymentsRequestType>,
}
/// Information about the Action executed.
#[derive(Debug, Serialize, Deserialize)]
pub struct Action {
/// The id of the app that was used to create the token.
pub app_id: Option<String>,
/// The name of the app the user gave to the application.
pub app_name: Option<String>,
/// A unique identifier for the object created by Global Payments. The first 3 characters
/// identifies the resource an id relates to.
pub id: Option<String>,
/// The result of the action executed.
pub result_code: Option<ResultCode>,
/// Global Payments time indicating when the object was created in ISO-8601 format.
pub time_created: Option<String>,
/// Indicates the action taken.
#[serde(rename = "type")]
pub action_type: Option<String>,
}
/// Information relating to a currency conversion.
#[derive(Debug, Serialize, Deserialize)]
pub struct CurrencyConversion {
/// The percentage commission taken for providing the currency conversion.
pub commission_percentage: Option<String>,
/// The exchange rate used to convert one currency to another.
pub conversion_rate: Option<String>,
/// The source of the base exchange rate was obtained to execute the currency conversion.
pub exchange_rate_source: Option<String>,
/// The time the base exchange rate was obtained from the source.
pub exchange_source_time: Option<String>,
/// The exchange rate used to convert one currency to another.
pub margin_rate_percentage: Option<String>,
/// The amount that will affect the payer's account.
pub payer_amount: Option<String>,
/// The currency of the amount that will affect the payer's account.
pub payer_currency: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PaymentMethod {
/// Data associated with the response of an APM transaction.
pub apm: Option<Apm>,
/// Information outlining the degree of authentication executed related to a transaction.
pub authentication: Option<Authentication>,
pub bank_transfer: Option<BankTransfer>,
pub card: Option<Card>,
pub digital_wallet: Option<requests::DigitalWallet>,
/// Indicates how the payment method information was obtained by the Merchant for this
/// transaction.
pub entry_mode: Option<requests::PaymentMethodEntryMode>,
/// If enabled, this field contains the unique fingerprint signature for that payment method
/// for that merchant. If the payment method is seen again this same value is generated. For
/// cards the primary account number is checked only. The expiry date or the CVV is not used
/// for this check.
pub fingerprint: Option<String>,
/// If enabled, this field indicates whether the payment method has been seen before or is
/// new.
/// * EXISTS - Indicates that the payment method was seen on the platform before by this
/// merchant.
/// * NEW - Indicates that the payment method was not seen on the platform before by this
/// merchant.
pub fingerprint_presence_indicator: Option<String>,
/// Unique Global Payments generated id used to reference a stored payment method on the
/// Global Payments system. Often referred to as the payment method token. This value can be
/// used instead of payment method details such as a card number and expiry date.
pub id: Option<String>,
/// Result message from the payment method provider corresponding to the result code.
pub message: Option<String>,
/// Result code from the payment method provider.
pub result: Option<String>,
}
/// Data associated with the response of an APM transaction.
#[derive(Debug, Serialize, Deserialize)]
pub struct Apm {
pub bank: Option<Bank>,
/// A string generated by the payment method that represents to what degree the merchant is
/// funded for the transaction.
pub fund_status: Option<FundStatus>,
pub mandate: Option<Mandate>,
/// Indicates that a redirect to the payment method is not required. Some payment methods
/// (for example, SEPA DirectDebit) provide the option to redirect the customer to a page to
/// display additional information about the payment.
pub optional_redirect: Option<f64>,
/// A string used to identify the payment method provider being used to execute this
/// transaction.
pub provider: Option<ApmProvider>,
/// A name of the payer from the payment method system.
pub provider_payer_name: Option<String>,
/// The time the payment method provider created the transaction at on their system.
pub provider_time_created: Option<String>,
/// The reference the payment method provider created for the transaction.
pub provider_transaction_reference: Option<String>,
/// URL to redirect the payer from the merchant's system to the payment method's system.
pub redirect_url: Option<String>,
/// A string generated by the payment method to represent the session created on the payment
/// method's platform to facilitate the creation of a transaction.
pub session_token: Option<String>,
/// Indicates to instruct the payer to wait for an update at the time the transaction is
/// being executed or that an update will be given later.
pub wait_notification: Option<f64>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Bank {
/// The local identifier of the bank account.
pub account_number: Option<String>,
/// The local identifier of the bank.
pub code: Option<String>,
/// The international identifier of the bank account.
pub iban: Option<String>,
/// The international identifier code for the bank.
pub identifier_code: Option<String>,
/// The name assocaited with the bank account
pub name: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Mandate {
/// The reference to identify the mandate.
pub code: Option<String>,
}
/// Information outlining the degree of authentication executed related to a transaction.
#[derive(Debug, Serialize, Deserialize)]
pub struct Authentication {
/// Information outlining the degree of 3D Secure authentication executed.
pub three_ds: Option<ThreeDs>,
}
/// Information outlining the degree of 3D Secure authentication executed.
#[derive(Debug, Serialize, Deserialize)]
pub struct ThreeDs {
/// The result of the three_ds value validation by the brands or issuing bank.
pub value_result: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BankTransfer {
/// The last 4 characters of the local reference for a bank account number.
pub masked_number_last4: Option<String>,
/// The name of the bank.
pub name: Option<String>,
/// The type of bank account associated with the payer's bank account.
pub number_type: Option<NumberType>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Card {
/// Code generated when the card is successfully authorized.
pub authcode: Option<String>,
/// The recommended AVS action to be taken by the agent processing the card transaction.
pub avs_action: Option<String>,
/// The result of the AVS address check.
pub avs_address_result: Option<String>,
/// The result of the AVS postal code check.
pub avs_postal_code_result: Option<String>,
/// Indicates the card brand that issued the card.
pub brand: Option<Brand>,
/// The unique reference created by the brands/schemes to uniquely identify the transaction.
pub brand_reference: Option<String>,
/// The time returned by the card brand indicating when the transaction was processed on
/// their system.
pub brand_time_reference: Option<String>,
/// The result of the CVV check.
pub cvv_result: Option<String>,
/// Masked card number with last 4 digits showing.
pub masked_number_last4: Option<String>,
/// The result codes directly from the card issuer.
pub provider: Option<ProviderClass>,
/// The card EMV tag response data from the card issuer for a contactless or chip card
/// transaction.
pub tag_response: Option<String>,
}
/// The result codes directly from the card issuer.
#[derive(Debug, Serialize, Deserialize)]
pub struct ProviderClass {
/// The result code of the AVS address check from the card issuer.
#[serde(rename = "card.provider.avs_address_result")]
pub card_provider_avs_address_result: Option<String>,
/// The result of the AVS postal code check from the card issuer..
#[serde(rename = "card.provider.avs_postal_code_result")]
pub card_provider_avs_postal_code_result: Option<String>,
/// The result code of the AVS check from the card issuer.
#[serde(rename = "card.provider.avs_result")]
pub card_provider_avs_result: Option<String>,
/// The result code of the CVV check from the card issuer.
#[serde(rename = "card.provider.cvv_result")]
pub card_provider_cvv_result: Option<String>,
/// Result code from the card issuer.
#[serde(rename = "card.provider.result")]
pub card_provider_result: Option<String>,
}
/// The result of the action executed.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ResultCode {
Declined,
Success,
}
/// A string generated by the payment method that represents to what degree the merchant is
/// funded for the transaction.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum FundStatus {
Missing,
NotExpected,
Received,
Waiting,
}
/// A string used to identify the payment method provider being used to execute this
/// transaction.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ApmProvider {
Giropay,
Ideal,
Paypal,
Sofort,
Testpay,
}
/// The type of bank account associated with the payer's bank account.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NumberType {
Checking,
Savings,
}
/// The recommended AVS action to be taken by the agent processing the card transaction.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AvsAction {
Accept,
Decline,
Prompt,
}
/// The result of the AVS address check.
///
/// The result of the AVS postal code check.
///
/// The result of the CVV check.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum GlobalPayResult {
Matched,
NotChecked,
NotMatched,
}
/// Indicates the card brand that issued the card.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Brand {
Amex,
Cup,
Diners,
Discover,
Jcb,
Mastercard,
Visa,
}
/// If enabled, this field indicates whether the payment method has been seen before or is
/// new.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum FingerprintPresenceIndicator {
/// Indicates that the payment method was seen on the platform before by this
/// merchant.
Exists,
/// Indicates that the payment method was not seen on the platform before by this
/// merchant.
New,
}
/// Indicates where a transaction is in its lifecycle.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum GlobalpayPaymentStatus {
/// A Transaction has been successfully authorized and captured. The funding
/// process will commence once the transaction remains in this status.
Captured,
/// A Transaction where the payment method provider declined the transfer of
/// funds between the payer and the merchant.
Declined,
/// A Transaction where the funds have transferred between payer and merchant as
/// expected.
Funded,
/// A Transaction has been successfully initiated. An update on its status is
/// expected via a separate asynchronous notification to a webhook.
Initiated,
/// A Transaction has been sent to the payment method provider and are waiting
/// for a result.
Pending,
/// A Transaction has been approved but a capture request is required to
/// commence the movement of funds.
Preauthorized,
/// A Transaction where the funds were expected to transfer between payer and
/// merchant but the transfer was rejected during the funding process. This rarely happens
/// but when it does it is usually addressed by Global Payments operations.
Rejected,
/// A Transaction that had a status of PENDING, PREAUTHORIZED or CAPTURED has
/// subsequently been reversed which voids/cancels a transaction before it is funded.
Reversed,
}

View File

@ -0,0 +1,188 @@
use error_stack::ResultExt;
use serde::{Deserialize, Serialize};
use super::{
requests::{self, GlobalpayPaymentsRequest},
response::{GlobalpayPaymentStatus, GlobalpayPaymentsResponse},
};
use crate::{
connector::utils::{self, CardData, PaymentsRequestData},
core::errors,
types::{self, api, storage::enums},
utils::OptionExt,
};
impl TryFrom<&types::PaymentsAuthorizeRouterData> for GlobalpayPaymentsRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
let metadata = item
.connector_meta_data
.to_owned()
.get_required_value("connector_meta_data")
.change_context(errors::ConnectorError::NoConnectorMetaData)?;
let account_name = metadata
.as_object()
.and_then(|o| o.get("account_name"))
.map(|o| o.to_string())
.ok_or_else(utils::missing_field_err("connector_meta.account_name"))?;
let card = item.get_card()?;
Ok(Self {
account_name,
amount: Some(item.request.amount.to_string()),
currency: item.request.currency.to_string(),
reference: item.get_attempt_id()?,
country: item.get_billing_country()?,
capture_mode: item.request.capture_method.map(|f| match f {
enums::CaptureMethod::Manual => requests::CaptureMode::Later,
_ => requests::CaptureMode::Auto,
}),
payment_method: requests::PaymentMethod {
card: Some(requests::Card {
number: card.get_card_number(),
expiry_month: card.get_card_expiry_month(),
expiry_year: card.get_card_expiry_year_2_digit(),
cvv: card.get_card_cvc(),
..Default::default()
}),
..Default::default()
},
..Default::default()
})
}
}
impl TryFrom<&types::PaymentsCaptureRouterData> for GlobalpayPaymentsRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(value: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
Ok(Self {
amount: value
.request
.amount_to_capture
.map(|amount| amount.to_string()),
..Default::default()
})
}
}
impl TryFrom<&types::PaymentsCancelRouterData> for GlobalpayPaymentsRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(_value: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
Ok(Self::default())
}
}
pub struct GlobalpayAuthType {
pub api_key: String,
}
impl TryFrom<&types::ConnectorAuthType> for GlobalpayAuthType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
match auth_type {
types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
api_key: api_key.to_string(),
}),
_ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
}
}
}
impl From<GlobalpayPaymentStatus> for enums::AttemptStatus {
fn from(item: GlobalpayPaymentStatus) -> Self {
match item {
GlobalpayPaymentStatus::Captured | GlobalpayPaymentStatus::Funded => Self::Charged,
GlobalpayPaymentStatus::Declined | GlobalpayPaymentStatus::Rejected => Self::Failure,
GlobalpayPaymentStatus::Preauthorized => Self::Authorized,
GlobalpayPaymentStatus::Reversed => Self::Voided,
GlobalpayPaymentStatus::Initiated | GlobalpayPaymentStatus::Pending => Self::Pending,
}
}
}
impl From<GlobalpayPaymentStatus> for enums::RefundStatus {
fn from(item: GlobalpayPaymentStatus) -> Self {
match item {
GlobalpayPaymentStatus::Captured | GlobalpayPaymentStatus::Funded => Self::Success,
GlobalpayPaymentStatus::Declined | GlobalpayPaymentStatus::Rejected => Self::Failure,
GlobalpayPaymentStatus::Initiated | GlobalpayPaymentStatus::Pending => Self::Pending,
_ => Self::Pending,
}
}
}
impl<F, T>
TryFrom<types::ResponseRouterData<F, GlobalpayPaymentsResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ParsingError>;
fn try_from(
item: types::ResponseRouterData<
F,
GlobalpayPaymentsResponse,
T,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
Ok(Self {
status: enums::AttemptStatus::from(item.response.status),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),
..item.data
})
}
}
impl<F> TryFrom<&types::RefundsRouterData<F>> for requests::GlobalpayRefundRequest {
type Error = error_stack::Report<errors::ParsingError>;
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
Ok(Self {
amount: item.request.refund_amount.to_string(),
})
}
}
impl TryFrom<types::RefundsResponseRouterData<api::Execute, GlobalpayPaymentsResponse>>
for types::RefundExecuteRouterData
{
type Error = error_stack::Report<errors::ParsingError>;
fn try_from(
item: types::RefundsResponseRouterData<api::Execute, GlobalpayPaymentsResponse>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.id,
refund_status: enums::RefundStatus::from(item.response.status),
}),
..item.data
})
}
}
impl TryFrom<types::RefundsResponseRouterData<api::RSync, GlobalpayPaymentsResponse>>
for types::RefundsRouterData<api::RSync>
{
type Error = error_stack::Report<errors::ParsingError>;
fn try_from(
item: types::RefundsResponseRouterData<api::RSync, GlobalpayPaymentsResponse>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.id,
refund_status: enums::RefundStatus::from(item.response.status),
}),
..item.data
})
}
}
#[derive(Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct GlobalpayErrorResponse {
pub error_code: String,
pub detailed_error_code: String,
pub detailed_error_description: String,
}

View File

@ -0,0 +1,72 @@
use crate::{
core::errors,
pii::PeekInterface,
types::{self, api},
};
pub fn missing_field_err(
message: &str,
) -> Box<dyn Fn() -> error_stack::Report<errors::ConnectorError> + '_> {
Box::new(|| {
errors::ConnectorError::MissingRequiredField {
field_name: message.to_string(),
}
.into()
})
}
type Error = error_stack::Report<errors::ConnectorError>;
pub trait PaymentsRequestData {
fn get_attempt_id(&self) -> Result<String, Error>;
fn get_billing_country(&self) -> Result<String, Error>;
fn get_card(&self) -> Result<api::CCard, Error>;
}
pub trait CardData {
fn get_card_number(&self) -> String;
fn get_card_expiry_month(&self) -> String;
fn get_card_expiry_year(&self) -> String;
fn get_card_expiry_year_2_digit(&self) -> String;
fn get_card_cvc(&self) -> String;
}
impl CardData for api::CCard {
fn get_card_number(&self) -> String {
self.card_number.peek().clone()
}
fn get_card_expiry_month(&self) -> String {
self.card_exp_month.peek().clone()
}
fn get_card_expiry_year(&self) -> String {
self.card_exp_year.peek().clone()
}
fn get_card_expiry_year_2_digit(&self) -> String {
let year = self.card_exp_year.peek().clone();
year[year.len() - 2..].to_string()
}
fn get_card_cvc(&self) -> String {
self.card_cvc.peek().clone()
}
}
impl PaymentsRequestData for types::PaymentsAuthorizeRouterData {
fn get_attempt_id(&self) -> Result<String, Error> {
self.attempt_id
.clone()
.ok_or_else(missing_field_err("attempt_id"))
}
fn get_billing_country(&self) -> Result<String, Error> {
self.address
.billing
.clone()
.and_then(|a| a.address)
.and_then(|ad| ad.country)
.ok_or_else(missing_field_err("billing.country"))
}
fn get_card(&self) -> Result<api::CCard, Error> {
match self.request.payment_method_data.clone() {
api::PaymentMethod::Card(card) => Ok(card),
_ => Err(missing_field_err("card")()),
}
}
}

View File

@ -150,6 +150,7 @@ impl ConnectorData {
"cybersource" => Ok(Box::new(&connector::Cybersource)),
"shift4" => Ok(Box::new(&connector::Shift4)),
"worldpay" => Ok(Box::new(&connector::Worldpay)),
"globalpay" => Ok(Box::new(&connector::Globalpay)),
_ => Err(report!(errors::UnexpectedError)
.attach_printable(format!("invalid connector name: {connector_name}")))
.change_context(errors::ConnectorError::InvalidConnectorName)

View File

@ -6,6 +6,7 @@ pub(crate) struct ConnectorAuthentication {
pub aci: Option<BodyKey>,
pub authorizedotnet: Option<BodyKey>,
pub checkout: Option<BodyKey>,
pub globalpay: Option<HeaderKey>,
pub shift4: Option<HeaderKey>,
pub worldpay: Option<HeaderKey>,
}

View File

@ -0,0 +1,193 @@
use std::{thread::sleep, time::Duration};
use futures::future::OptionFuture;
use masking::Secret;
use router::types::{
self,
api::{self},
storage::enums,
};
use serde_json::json;
use crate::{
connector_auth,
utils::{self, ConnectorActions, PaymentInfo},
};
struct Globalpay;
impl ConnectorActions for Globalpay {}
impl utils::Connector for Globalpay {
fn get_data(&self) -> types::api::ConnectorData {
use router::connector::Globalpay;
types::api::ConnectorData {
connector: Box::new(&Globalpay),
connector_name: types::Connector::Globalpay,
get_token: types::api::GetToken::Connector,
}
}
fn get_auth_token(&self) -> types::ConnectorAuthType {
types::ConnectorAuthType::from(
connector_auth::ConnectorAuthentication::new()
.globalpay
.expect("Missing connector authentication configuration"),
)
}
fn get_name(&self) -> String {
"globalpay".to_string()
}
fn get_connector_meta(&self) -> Option<serde_json::Value> {
Some(json!({"account_name": "transaction_processing"}))
}
}
fn get_default_payment_info() -> Option<PaymentInfo> {
Some(PaymentInfo {
address: Some(types::PaymentAddress {
billing: Some(api::Address {
address: Some(api::AddressDetails {
country: Some("US".to_string()),
..Default::default()
}),
phone: None,
}),
..Default::default()
}),
auth_type: None,
})
}
#[actix_web::test]
async fn should_only_authorize_payment() {
let response = Globalpay {}
.authorize_payment(None, get_default_payment_info())
.await;
assert_eq!(response.status, enums::AttemptStatus::Authorized);
}
#[actix_web::test]
async fn should_authorize_and_capture_payment() {
let response = Globalpay {}
.make_payment(None, get_default_payment_info())
.await;
assert_eq!(response.status, enums::AttemptStatus::Charged);
}
#[actix_web::test]
async fn should_capture_already_authorized_payment() {
let connector = Globalpay {};
let authorize_response = connector
.authorize_payment(None, get_default_payment_info())
.await;
assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
let txn_id = utils::get_connector_transaction_id(authorize_response);
let response: OptionFuture<_> = txn_id
.map(|transaction_id| async move {
connector
.capture_payment(transaction_id, None, get_default_payment_info())
.await
.status
})
.into();
assert_eq!(response.await, Some(enums::AttemptStatus::Charged));
}
#[actix_web::test]
async fn should_sync_payment() {
let connector = Globalpay {};
let authorize_response = connector
.authorize_payment(None, get_default_payment_info())
.await;
let txn_id = utils::get_connector_transaction_id(authorize_response);
sleep(Duration::from_secs(5)); // to avoid 404 error as globalpay takes some time to process the new transaction
let response = connector
.sync_payment(
Some(types::PaymentsSyncData {
connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
txn_id.unwrap(),
),
encoded_data: None,
}),
None,
)
.await;
assert_eq!(response.status, enums::AttemptStatus::Authorized,);
}
#[actix_web::test]
async fn should_fail_payment_for_incorrect_cvc() {
let response = Globalpay {}
.make_payment(
Some(types::PaymentsAuthorizeData {
payment_method_data: types::api::PaymentMethod::Card(api::CCard {
card_number: Secret::new("4024007134364842".to_string()),
..utils::CCardType::default().0
}),
..utils::PaymentAuthorizeType::default().0
}),
get_default_payment_info(),
)
.await;
let x = response.status;
assert_eq!(x, enums::AttemptStatus::Failure);
}
#[actix_web::test]
async fn should_refund_succeeded_payment() {
let connector = Globalpay {};
//make a successful payment
let response = connector
.make_payment(None, get_default_payment_info())
.await;
//try refund for previous payment
let transaction_id = utils::get_connector_transaction_id(response).unwrap();
let response = connector
.refund_payment(transaction_id, None, get_default_payment_info())
.await;
assert_eq!(
response.response.unwrap().refund_status,
enums::RefundStatus::Success,
);
}
#[actix_web::test]
async fn should_void_already_authorized_payment() {
let connector = Globalpay {};
let authorize_response = connector
.authorize_payment(None, get_default_payment_info())
.await;
assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
let txn_id = utils::get_connector_transaction_id(authorize_response);
let response: OptionFuture<_> = txn_id
.map(|transaction_id| async move {
connector
.void_payment(transaction_id, None, None)
.await
.status
})
.into();
assert_eq!(response.await, Some(enums::AttemptStatus::Voided));
}
#[actix_web::test]
async fn should_sync_refund() {
let connector = Globalpay {};
let response = connector
.make_payment(None, get_default_payment_info())
.await;
let transaction_id = utils::get_connector_transaction_id(response).unwrap();
connector
.refund_payment(transaction_id.clone(), None, get_default_payment_info())
.await;
sleep(Duration::from_secs(5)); // to avoid 404 error as globalpay takes some time to process the new transaction
let response = connector
.sync_refund(transaction_id, None, get_default_payment_info())
.await;
assert_eq!(
response.response.unwrap().refund_status,
enums::RefundStatus::Success,
);
}

View File

@ -4,6 +4,7 @@ mod aci;
mod authorizedotnet;
mod checkout;
mod connector_auth;
mod globalpay;
mod shift4;
mod utils;
mod worldpay;

View File

@ -18,3 +18,6 @@ api_key = "Bearer MyApiKey"
[worldpay]
api_key = "Bearer MyApiKey"
[globalpay]
api_key = "Bearer MyApiKey"

View File

@ -34,25 +34,28 @@ impl utils::Connector for Shift4 {
#[actix_web::test]
async fn should_only_authorize_payment() {
let response = Shift4 {}.authorize_payment(None).await;
let response = Shift4 {}.authorize_payment(None, None).await;
assert_eq!(response.status, enums::AttemptStatus::Authorized);
}
#[actix_web::test]
async fn should_authorize_and_capture_payment() {
let response = Shift4 {}.make_payment(None).await;
let response = Shift4 {}.make_payment(None, None).await;
assert_eq!(response.status, enums::AttemptStatus::Charged);
}
#[actix_web::test]
async fn should_capture_already_authorized_payment() {
let connector = Shift4 {};
let authorize_response = connector.authorize_payment(None).await;
let authorize_response = connector.authorize_payment(None, None).await;
assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
let txn_id = utils::get_connector_transaction_id(authorize_response);
let response: OptionFuture<_> = txn_id
.map(|transaction_id| async move {
connector.capture_payment(transaction_id, None).await.status
connector
.capture_payment(transaction_id, None, None)
.await
.status
})
.into();
assert_eq!(response.await, Some(enums::AttemptStatus::Charged));
@ -61,13 +64,16 @@ async fn should_capture_already_authorized_payment() {
#[actix_web::test]
async fn should_fail_payment_for_incorrect_cvc() {
let response = Shift4 {}
.make_payment(Some(types::PaymentsAuthorizeData {
.make_payment(
Some(types::PaymentsAuthorizeData {
payment_method_data: types::api::PaymentMethod::Card(api::CCard {
card_number: Secret::new("4024007134364842".to_string()),
..utils::CCardType::default().0
}),
..utils::PaymentAuthorizeType::default().0
}))
}),
None,
)
.await;
let x = response.response.unwrap_err();
assert_eq!(
@ -80,11 +86,11 @@ async fn should_fail_payment_for_incorrect_cvc() {
async fn should_refund_succeeded_payment() {
let connector = Shift4 {};
//make a successful payment
let response = connector.make_payment(None).await;
let response = connector.make_payment(None, None).await;
//try refund for previous payment
if let Some(transaction_id) = utils::get_connector_transaction_id(response) {
let response = connector.refund_payment(transaction_id, None).await;
let response = connector.refund_payment(transaction_id, None, None).await;
assert_eq!(
response.response.unwrap().refund_status,
enums::RefundStatus::Success,

View File

@ -14,6 +14,15 @@ pub trait Connector {
fn get_data(&self) -> types::api::ConnectorData;
fn get_auth_token(&self) -> types::ConnectorAuthType;
fn get_name(&self) -> String;
fn get_connector_meta(&self) -> Option<serde_json::Value> {
None
}
}
#[derive(Debug, Default, Clone)]
pub struct PaymentInfo {
pub address: Option<PaymentAddress>,
pub auth_type: Option<enums::AuthenticationType>,
}
#[async_trait]
@ -21,29 +30,27 @@ pub trait ConnectorActions: Connector {
async fn authorize_payment(
&self,
payment_data: Option<types::PaymentsAuthorizeData>,
payment_info: Option<PaymentInfo>,
) -> types::PaymentsAuthorizeRouterData {
let integration = self.get_data().connector.get_connector_integration();
let request = generate_data(
self.get_name(),
self.get_auth_token(),
enums::AuthenticationType::NoThreeDs,
let request = self.generate_data(
payment_data.unwrap_or_else(|| types::PaymentsAuthorizeData {
capture_method: Some(storage_models::enums::CaptureMethod::Manual),
..PaymentAuthorizeType::default().0
}),
payment_info,
);
call_connector(request, integration).await
}
async fn make_payment(
&self,
payment_data: Option<types::PaymentsAuthorizeData>,
payment_info: Option<PaymentInfo>,
) -> types::PaymentsAuthorizeRouterData {
let integration = self.get_data().connector.get_connector_integration();
let request = generate_data(
self.get_name(),
self.get_auth_token(),
enums::AuthenticationType::NoThreeDs,
let request = self.generate_data(
payment_data.unwrap_or_else(|| PaymentAuthorizeType::default().0),
payment_info,
);
call_connector(request, integration).await
}
@ -51,13 +58,12 @@ pub trait ConnectorActions: Connector {
async fn sync_payment(
&self,
payment_data: Option<types::PaymentsSyncData>,
payment_info: Option<PaymentInfo>,
) -> types::PaymentsSyncRouterData {
let integration = self.get_data().connector.get_connector_integration();
let request = generate_data(
self.get_name(),
self.get_auth_token(),
enums::AuthenticationType::NoThreeDs,
let request = self.generate_data(
payment_data.unwrap_or_else(|| PaymentSyncType::default().0),
payment_info,
);
call_connector(request, integration).await
}
@ -66,18 +72,17 @@ pub trait ConnectorActions: Connector {
&self,
transaction_id: String,
payment_data: Option<types::PaymentsCaptureData>,
payment_info: Option<PaymentInfo>,
) -> types::PaymentsCaptureRouterData {
let integration = self.get_data().connector.get_connector_integration();
let request = generate_data(
self.get_name(),
self.get_auth_token(),
enums::AuthenticationType::NoThreeDs,
let request = self.generate_data(
payment_data.unwrap_or(types::PaymentsCaptureData {
amount_to_capture: Some(100),
connector_transaction_id: transaction_id,
currency: enums::Currency::USD,
amount: 100,
}),
payment_info,
);
call_connector(request, integration).await
}
@ -86,16 +91,15 @@ pub trait ConnectorActions: Connector {
&self,
transaction_id: String,
payment_data: Option<types::PaymentsCancelData>,
payment_info: Option<PaymentInfo>,
) -> types::PaymentsCancelRouterData {
let integration = self.get_data().connector.get_connector_integration();
let request = generate_data(
self.get_name(),
self.get_auth_token(),
enums::AuthenticationType::NoThreeDs,
let request = self.generate_data(
payment_data.unwrap_or(types::PaymentsCancelData {
connector_transaction_id: transaction_id,
cancellation_reason: Some("Test cancellation".to_string()),
}),
payment_info,
);
call_connector(request, integration).await
}
@ -104,12 +108,10 @@ pub trait ConnectorActions: Connector {
&self,
transaction_id: String,
payment_data: Option<types::RefundsData>,
payment_info: Option<PaymentInfo>,
) -> types::RefundExecuteRouterData {
let integration = self.get_data().connector.get_connector_integration();
let request = generate_data(
self.get_name(),
self.get_auth_token(),
enums::AuthenticationType::NoThreeDs,
let request = self.generate_data(
payment_data.unwrap_or_else(|| types::RefundsData {
amount: 100,
currency: enums::Currency::USD,
@ -119,6 +121,7 @@ pub trait ConnectorActions: Connector {
connector_metadata: None,
reason: None,
}),
payment_info,
);
call_connector(request, integration).await
}
@ -127,12 +130,10 @@ pub trait ConnectorActions: Connector {
&self,
transaction_id: String,
payment_data: Option<types::RefundsData>,
payment_info: Option<PaymentInfo>,
) -> types::RefundSyncRouterData {
let integration = self.get_data().connector.get_connector_integration();
let request = generate_data(
self.get_name(),
self.get_auth_token(),
enums::AuthenticationType::NoThreeDs,
let request = self.generate_data(
payment_data.unwrap_or_else(|| types::RefundsData {
amount: 100,
currency: enums::Currency::USD,
@ -142,9 +143,42 @@ pub trait ConnectorActions: Connector {
connector_metadata: None,
reason: None,
}),
payment_info,
);
call_connector(request, integration).await
}
fn generate_data<Flow, Req: From<Req>, Res>(
&self,
req: Req,
info: Option<PaymentInfo>,
) -> types::RouterData<Flow, Req, Res> {
types::RouterData {
flow: PhantomData,
merchant_id: self.get_name(),
connector: self.get_name(),
payment_id: uuid::Uuid::new_v4().to_string(),
attempt_id: Some(uuid::Uuid::new_v4().to_string()),
status: enums::AttemptStatus::default(),
router_return_url: None,
auth_type: info
.clone()
.map_or(enums::AuthenticationType::NoThreeDs, |a| {
a.auth_type
.map_or(enums::AuthenticationType::NoThreeDs, |a| a)
}),
payment_method: enums::PaymentMethodType::Card,
connector_auth_type: self.get_auth_token(),
description: Some("This is a test".to_string()),
return_url: None,
request: req,
response: Err(types::ErrorResponse::default()),
payment_method_id: None,
address: info.map_or(PaymentAddress::default(), |a| a.address.unwrap()),
connector_meta_data: self.get_connector_meta(),
amount_captured: None,
}
}
}
async fn call_connector<
@ -268,31 +302,3 @@ pub fn get_connector_transaction_id(
Err(_) => None,
}
}
fn generate_data<Flow, Req: From<Req>, Res>(
connector: String,
connector_auth_type: types::ConnectorAuthType,
auth_type: enums::AuthenticationType,
req: Req,
) -> types::RouterData<Flow, Req, Res> {
types::RouterData {
flow: PhantomData,
merchant_id: connector.clone(),
connector,
payment_id: uuid::Uuid::new_v4().to_string(),
attempt_id: Some(uuid::Uuid::new_v4().to_string()),
status: enums::AttemptStatus::default(),
router_return_url: None,
auth_type,
payment_method: enums::PaymentMethodType::Card,
connector_auth_type,
description: Some("This is a test".to_string()),
return_url: None,
request: req,
response: Err(types::ErrorResponse::default()),
payment_method_id: None,
address: PaymentAddress::default(),
connector_meta_data: None,
amount_captured: None,
}
}

View File

@ -48,7 +48,7 @@ impl utils::Connector for Worldpay {
async fn should_authorize_card_payment() {
let conn = Worldpay {};
let _mock = conn.start_server(get_mock_config()).await;
let response = conn.authorize_payment(None).await;
let response = conn.authorize_payment(None, None).await;
assert_eq!(response.status, enums::AttemptStatus::Authorized);
assert_eq!(
utils::get_connector_transaction_id(response),
@ -62,13 +62,16 @@ async fn should_authorize_gpay_payment() {
let conn = Worldpay {};
let _mock = conn.start_server(get_mock_config()).await;
let response = conn
.authorize_payment(Some(types::PaymentsAuthorizeData {
.authorize_payment(
Some(types::PaymentsAuthorizeData {
payment_method_data: types::api::PaymentMethod::Wallet(api::WalletData {
issuer_name: api_enums::WalletIssuer::GooglePay,
token: Some("someToken".to_string()),
}),
..utils::PaymentAuthorizeType::default().0
}))
}),
None,
)
.await;
assert_eq!(response.status, enums::AttemptStatus::Authorized);
assert_eq!(
@ -83,13 +86,16 @@ async fn should_authorize_applepay_payment() {
let conn = Worldpay {};
let _mock = conn.start_server(get_mock_config()).await;
let response = conn
.authorize_payment(Some(types::PaymentsAuthorizeData {
.authorize_payment(
Some(types::PaymentsAuthorizeData {
payment_method_data: types::api::PaymentMethod::Wallet(api::WalletData {
issuer_name: api_enums::WalletIssuer::ApplePay,
token: Some("someToken".to_string()),
}),
..utils::PaymentAuthorizeType::default().0
}))
}),
None,
)
.await;
assert_eq!(response.status, enums::AttemptStatus::Authorized);
assert_eq!(
@ -103,12 +109,15 @@ async fn should_authorize_applepay_payment() {
async fn should_capture_already_authorized_payment() {
let connector = Worldpay {};
let _mock = connector.start_server(get_mock_config()).await;
let authorize_response = connector.authorize_payment(None).await;
let authorize_response = connector.authorize_payment(None, None).await;
assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
let txn_id = utils::get_connector_transaction_id(authorize_response);
let response: OptionFuture<_> = txn_id
.map(|transaction_id| async move {
connector.capture_payment(transaction_id, None).await.status
connector
.capture_payment(transaction_id, None, None)
.await
.status
})
.into();
assert_eq!(response.await, Some(enums::AttemptStatus::Charged));
@ -120,12 +129,15 @@ async fn should_sync_payment() {
let connector = Worldpay {};
let _mock = connector.start_server(get_mock_config()).await;
let response = connector
.sync_payment(Some(types::PaymentsSyncData {
.sync_payment(
Some(types::PaymentsSyncData {
connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
"112233".to_string(),
),
encoded_data: None,
}))
}),
None,
)
.await;
assert_eq!(response.status, enums::AttemptStatus::Authorized,);
}
@ -135,13 +147,15 @@ async fn should_sync_payment() {
async fn should_void_already_authorized_payment() {
let connector = Worldpay {};
let _mock = connector.start_server(get_mock_config()).await;
let authorize_response = connector.authorize_payment(None).await;
let authorize_response = connector.authorize_payment(None, None).await;
assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
let txn_id = utils::get_connector_transaction_id(authorize_response);
let response: OptionFuture<_> =
txn_id
let response: OptionFuture<_> = txn_id
.map(|transaction_id| async move {
connector.void_payment(transaction_id, None).await.status
connector
.void_payment(transaction_id, None, None)
.await
.status
})
.into();
assert_eq!(response.await, Some(enums::AttemptStatus::Voided));
@ -152,9 +166,11 @@ async fn should_void_already_authorized_payment() {
async fn should_fail_capture_for_invalid_payment() {
let connector = Worldpay {};
let _mock = connector.start_server(get_mock_config()).await;
let authorize_response = connector.authorize_payment(None).await;
let authorize_response = connector.authorize_payment(None, None).await;
assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
let response = connector.capture_payment("12345".to_string(), None).await;
let response = connector
.capture_payment("12345".to_string(), None, None)
.await;
let err = response.response.unwrap_err();
assert_eq!(
err.message,
@ -169,11 +185,11 @@ async fn should_refund_succeeded_payment() {
let connector = Worldpay {};
let _mock = connector.start_server(get_mock_config()).await;
//make a successful payment
let response = connector.make_payment(None).await;
let response = connector.make_payment(None, None).await;
//try refund for previous payment
let transaction_id = utils::get_connector_transaction_id(response).unwrap();
let response = connector.refund_payment(transaction_id, None).await;
let response = connector.refund_payment(transaction_id, None, None).await;
assert_eq!(
response.response.unwrap().refund_status,
enums::RefundStatus::Success,
@ -185,7 +201,9 @@ async fn should_refund_succeeded_payment() {
async fn should_sync_refund() {
let connector = Worldpay {};
let _mock = connector.start_server(get_mock_config()).await;
let response = connector.sync_refund("654321".to_string(), None).await;
let response = connector
.sync_refund("654321".to_string(), None, None)
.await;
assert_eq!(
response.response.unwrap().refund_status,
enums::RefundStatus::Success,

View File

@ -64,6 +64,18 @@ base_url = "http://stripe-mock:12111/"
[connectors.braintree]
base_url = "https://api.sandbox.braintreegateway.com/"
[connectors.cybersource]
base_url = "https://apitest.cybersource.com/"
[connectors.shift4]
base_url = "https://api.shift4.com/"
[connectors.worldpay]
base_url = "https://try.access.worldpay.com/"
[connectors.globalpay]
base_url = "https://apis.sandbox.globalpay.com/ucp/"
[connectors.applepay]
base_url = "https://apple-pay-gateway.apple.com/"
@ -71,5 +83,5 @@ base_url = "https://apple-pay-gateway.apple.com/"
base_url = "https://api-na.playground.klarna.com/"
[connectors.supported]
wallets = ["klarna","braintree","applepay"]
cards = ["stripe","adyen","authorizedotnet","checkout","braintree"]
wallets = ["klarna", "braintree", "applepay"]
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "cybersource", "shift4", "worldpay", "globalpay"]

View File

@ -11,7 +11,7 @@ fi
cd $SCRIPT/..
# remove template files if already created for this connector
rm -rf $conn/$pg $conn/$pg.rs
git checkout $conn.rs $src/types/api.rs $src/configs/settings.rs config/Development.toml config/docker_compose.toml crates/api_models/src/enums.rs
git checkout $conn.rs $src/types/api.rs $src/configs/settings.rs config/Development.toml config/docker_compose.toml config/config.example.toml loadtest/config/Development.toml crates/router/src/configs/defaults.toml crates/api_models/src/enums.rs
# add enum for this connector in required places
sed -i'' -e "s/pub use self::{/pub mod ${pg};\n\npub use self::{/" $conn.rs
sed -i'' -e "s/};/${pg}::${pgc},\n};/" $conn.rs
@ -21,9 +21,14 @@ sed -i'' -e "s/\[scheduler\]/[connectors.${pg}]\nbase_url = \"\"\n\n[scheduler]/
sed -r -i'' -e "s/cards = \[(.*)\]/cards = [\1, \"${pg}\"]/" config/Development.toml
sed -i'' -e "s/\[connectors.supported\]/[connectors.${pg}]\nbase_url = ""\n\n[connectors.supported]/" config/docker_compose.toml
sed -r -i'' -e "s/cards = \[(.*)\]/cards = [\1, \"${pg}\"]/" config/docker_compose.toml
sed -i'' -e "s/\[connectors.supported\]/[connectors.${pg}]\nbase_url = ""\n\n[connectors.supported]/" config/config.example.toml
sed -r -i'' -e "s/cards = \[(.*)\]/cards = [\1, \"${pg}\"]/" config/config.example.toml
sed -i'' -e "s/\[connectors.supported\]/[connectors.${pg}]\nbase_url = ""\n\n[connectors.supported]/" loadtest/config/Development.toml
sed -r -i'' -e "s/cards = \[(.*)\]/cards = [\1, \"${pg}\"]/" loadtest/config/Development.toml
sed -i'' -e "s/\[connectors.supported\]/[connectors.${pg}]\nbase_url = ""\n\n[connectors.supported]/" crates/router/src/configs/defaults.toml
sed -i'' -e "s/Dummy,/Dummy,\n\t${pgc},/" crates/api_models/src/enums.rs
# remove temporary files created in above step
rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e config/Development.toml-e config/docker_compose.toml-e crates/api_models/src/enums.rs-e
rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e config/Development.toml-e config/docker_compose.toml-e config/config.example.toml-e loadtest/config/Development.toml-e crates/router/src/configs/defaults.toml-e crates/api_models/src/enums.rs-e
cd $conn/
# generate template files for the connector
cargo install cargo-generate