mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(connector): [Zen] add apple pay redirect flow support for zen connector (#1383)
This commit is contained in:
@ -804,6 +804,8 @@ pub enum WalletData {
|
||||
AliPay(AliPayRedirection),
|
||||
/// The wallet data for Apple pay
|
||||
ApplePay(ApplePayWalletData),
|
||||
/// Wallet data for apple pay redirect flow
|
||||
ApplePayRedirect(Box<ApplePayRedirectData>),
|
||||
/// The wallet data for Google pay
|
||||
GooglePay(GooglePayWalletData),
|
||||
MbWay(Box<MbWayRedirection>),
|
||||
@ -831,6 +833,9 @@ pub struct GooglePayWalletData {
|
||||
pub tokenization_data: GpayTokenizationData,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
|
||||
pub struct ApplePayRedirectData {}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
|
||||
pub struct WeChatPayRedirection {}
|
||||
|
||||
|
||||
@ -47,6 +47,12 @@ impl api::Refund for Zen {}
|
||||
impl api::RefundExecute for Zen {}
|
||||
impl api::RefundSync for Zen {}
|
||||
|
||||
impl Zen {
|
||||
fn get_default_header() -> (String, request::Maskable<String>) {
|
||||
("request-id".to_string(), Uuid::new_v4().to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Zen
|
||||
where
|
||||
Self: ConnectorIntegration<Flow, Request, Response>,
|
||||
@ -56,13 +62,10 @@ where
|
||||
req: &types::RouterData<Flow, Request, Response>,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
let mut headers = vec![
|
||||
(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
self.get_content_type().to_string().into(),
|
||||
),
|
||||
("request-id".to_string(), Uuid::new_v4().to_string().into()),
|
||||
];
|
||||
let mut headers = vec![(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
self.get_content_type().to_string().into(),
|
||||
)];
|
||||
|
||||
let mut auth_header = self.get_auth_header(&req.connector_auth_type)?;
|
||||
headers.append(&mut auth_header);
|
||||
@ -147,7 +150,17 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
let mut headers = self.build_headers(req, connectors)?;
|
||||
let api_headers = match req.request.payment_method_data {
|
||||
api_models::payments::PaymentMethodData::Wallet(
|
||||
api_models::payments::WalletData::ApplePayRedirect(_),
|
||||
) => None,
|
||||
_ => Some(Self::get_default_header()),
|
||||
};
|
||||
if let Some(api_header) = api_headers {
|
||||
headers.push(api_header)
|
||||
}
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
@ -156,10 +169,16 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsAuthorizeRouterData,
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!("{}v1/transactions", self.base_url(connectors),))
|
||||
let endpoint = match &req.request.payment_method_data {
|
||||
api_models::payments::PaymentMethodData::Wallet(
|
||||
api_models::payments::WalletData::ApplePayRedirect(_),
|
||||
) => "api/checkouts",
|
||||
_ => "v1/transactions",
|
||||
};
|
||||
Ok(format!("{}{}", self.base_url(connectors), endpoint))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
@ -224,7 +243,9 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
let mut headers = self.build_headers(req, connectors)?;
|
||||
headers.push(Self::get_default_header());
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
@ -290,11 +311,37 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
|
||||
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
|
||||
for Zen
|
||||
{
|
||||
fn build_request(
|
||||
&self,
|
||||
_req: &types::RouterData<
|
||||
api::Capture,
|
||||
types::PaymentsCaptureData,
|
||||
types::PaymentsResponseData,
|
||||
>,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::FlowNotSupported {
|
||||
flow: "Capture".to_owned(),
|
||||
connector: "Zen".to_owned(),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
|
||||
for Zen
|
||||
{
|
||||
fn build_request(
|
||||
&self,
|
||||
_req: &types::RouterData<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::FlowNotSupported {
|
||||
flow: "Void".to_owned(),
|
||||
connector: "Zen".to_owned(),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Zen {
|
||||
@ -303,7 +350,9 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
let mut headers = self.build_headers(req, connectors)?;
|
||||
headers.push(Self::get_default_header());
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
@ -378,7 +427,9 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
|
||||
req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
let mut headers = self.build_headers(req, connectors)?;
|
||||
headers.push(Self::get_default_header());
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
@ -525,8 +576,8 @@ impl api::IncomingWebhook for Zen {
|
||||
.change_context(errors::ConnectorError::WebhookSignatureNotFound)?;
|
||||
Ok(match &webhook_body.transaction_type {
|
||||
ZenWebhookTxnType::TrtPurchase => api_models::webhooks::ObjectReferenceId::PaymentId(
|
||||
api_models::payments::PaymentIdType::ConnectorTransactionId(
|
||||
webhook_body.transaction_id,
|
||||
api_models::payments::PaymentIdType::PaymentAttemptId(
|
||||
webhook_body.merchant_transaction_id,
|
||||
),
|
||||
),
|
||||
ZenWebhookTxnType::TrtRefund => api_models::webhooks::ObjectReferenceId::RefundId(
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
use std::net::IpAddr;
|
||||
|
||||
use api_models::payments::{ApplePayRedirectData, Card, GooglePayWalletData};
|
||||
use cards::CardNumber;
|
||||
use common_utils::pii::Email;
|
||||
use masking::Secret;
|
||||
use common_utils::{ext_traits::ValueExt, pii::Email};
|
||||
use error_stack::ResultExt;
|
||||
use masking::{PeekInterface, Secret};
|
||||
use ring::digest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::Display;
|
||||
|
||||
use crate::{
|
||||
connector::utils::{
|
||||
@ -11,7 +15,7 @@ use crate::{
|
||||
},
|
||||
core::errors,
|
||||
services::{self, Method},
|
||||
types::{self, api, storage::enums, transformers::ForeignTryFrom},
|
||||
types::{self, api, storage::enums, transformers::ForeignTryFrom, BrowserInformation},
|
||||
};
|
||||
|
||||
// Auth Struct
|
||||
@ -34,7 +38,7 @@ impl TryFrom<&types::ConnectorAuthType> for ZenAuthType {
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ZenPaymentsRequest {
|
||||
pub struct ApiRequest {
|
||||
merchant_transaction_id: String,
|
||||
payment_channel: ZenPaymentChannels,
|
||||
amount: String,
|
||||
@ -46,6 +50,27 @@ pub struct ZenPaymentsRequest {
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ZenPaymentsRequest {
|
||||
ApiRequest(Box<ApiRequest>),
|
||||
CheckoutRequest(Box<CheckoutRequest>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CheckoutRequest {
|
||||
amount: String,
|
||||
currency: enums::Currency,
|
||||
custom_ipn_url: String,
|
||||
items: Vec<ZenItemObject>,
|
||||
merchant_transaction_id: String,
|
||||
signature: Option<Secret<String>>,
|
||||
specified_payment_channel: ZenPaymentChannels,
|
||||
terminal_uuid: Secret<String>,
|
||||
url_redirect: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Display, Serialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum ZenPaymentChannels {
|
||||
@ -113,109 +138,250 @@ pub struct ZenItemObject {
|
||||
line_amount_total: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SessionObject {
|
||||
pub terminal_uuid: Option<String>,
|
||||
pub pay_wall_secret: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<(&types::PaymentsAuthorizeRouterData, &Card)> for ZenPaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(value: (&types::PaymentsAuthorizeRouterData, &Card)) -> Result<Self, Self::Error> {
|
||||
let (item, ccard) = value;
|
||||
let browser_info = item.request.get_browser_info()?;
|
||||
let ip = browser_info.get_ip_address()?;
|
||||
let browser_details = get_browser_details(&browser_info)?;
|
||||
let amount = utils::to_currency_base_unit(item.request.amount, item.request.currency)?;
|
||||
let payment_specific_data = ZenPaymentData {
|
||||
browser_details,
|
||||
//Connector Specific for cards
|
||||
payment_type: ZenPaymentTypes::Onetime,
|
||||
token: None,
|
||||
card: Some(ZenCardDetails {
|
||||
number: ccard.card_number.clone(),
|
||||
expiry_date: ccard.get_card_expiry_month_year_2_digit_with_delimiter("".to_owned()),
|
||||
cvv: ccard.card_cvc.clone(),
|
||||
}),
|
||||
descriptor: item.get_description()?.chars().take(24).collect(),
|
||||
return_verify_url: item.request.router_return_url.clone(),
|
||||
};
|
||||
Ok(Self::ApiRequest(Box::new(ApiRequest {
|
||||
merchant_transaction_id: item.attempt_id.clone(),
|
||||
payment_channel: ZenPaymentChannels::PclCard,
|
||||
currency: item.request.currency,
|
||||
payment_specific_data,
|
||||
customer: get_customer(item, ip)?,
|
||||
custom_ipn_url: item.request.get_webhook_url()?,
|
||||
items: get_item_object(item, amount.clone())?,
|
||||
amount,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(&types::PaymentsAuthorizeRouterData, &GooglePayWalletData)> for ZenPaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
(item, gpay_pay_redirect_data): (&types::PaymentsAuthorizeRouterData, &GooglePayWalletData),
|
||||
) -> Result<Self, Self::Error> {
|
||||
let amount = utils::to_currency_base_unit(item.request.amount, item.request.currency)?;
|
||||
let browser_info = item.request.get_browser_info()?;
|
||||
let browser_details = get_browser_details(&browser_info)?;
|
||||
let ip = browser_info.get_ip_address()?;
|
||||
let payment_specific_data = ZenPaymentData {
|
||||
browser_details,
|
||||
//Connector Specific for wallet
|
||||
payment_type: ZenPaymentTypes::ExternalPaymentToken,
|
||||
token: Some(gpay_pay_redirect_data.tokenization_data.token.clone()),
|
||||
card: None,
|
||||
descriptor: item.get_description()?.chars().take(24).collect(),
|
||||
return_verify_url: item.request.router_return_url.clone(),
|
||||
};
|
||||
Ok(Self::ApiRequest(Box::new(ApiRequest {
|
||||
merchant_transaction_id: item.attempt_id.clone(),
|
||||
payment_channel: ZenPaymentChannels::PclGooglepay,
|
||||
currency: item.request.currency,
|
||||
payment_specific_data,
|
||||
customer: get_customer(item, ip)?,
|
||||
custom_ipn_url: item.request.get_webhook_url()?,
|
||||
items: get_item_object(item, amount.clone())?,
|
||||
amount,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
impl
|
||||
TryFrom<(
|
||||
&types::PaymentsAuthorizeRouterData,
|
||||
&Box<ApplePayRedirectData>,
|
||||
)> for ZenPaymentsRequest
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
(item, _apple_pay_redirect_data): (
|
||||
&types::PaymentsAuthorizeRouterData,
|
||||
&Box<ApplePayRedirectData>,
|
||||
),
|
||||
) -> Result<Self, Self::Error> {
|
||||
let amount = utils::to_currency_base_unit(item.request.amount, item.request.currency)?;
|
||||
let connector_meta = item.get_connector_meta()?;
|
||||
let session: SessionObject = connector_meta
|
||||
.parse_value("SessionObject")
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
let terminal_uuid = session
|
||||
.terminal_uuid
|
||||
.clone()
|
||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
let mut checkout_request = CheckoutRequest {
|
||||
merchant_transaction_id: item.attempt_id.clone(),
|
||||
specified_payment_channel: ZenPaymentChannels::PclApplepay,
|
||||
currency: item.request.currency,
|
||||
custom_ipn_url: item.request.get_webhook_url()?,
|
||||
items: get_item_object(item, amount.clone())?,
|
||||
amount,
|
||||
terminal_uuid: Secret::new(terminal_uuid),
|
||||
signature: None,
|
||||
url_redirect: item.request.get_return_url()?,
|
||||
};
|
||||
checkout_request.signature = Some(get_checkout_signature(&checkout_request, &session)?);
|
||||
Ok(Self::CheckoutRequest(Box::new(checkout_request)))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_checkout_signature(
|
||||
checkout_request: &CheckoutRequest,
|
||||
session: &SessionObject,
|
||||
) -> Result<Secret<String>, error_stack::Report<errors::ConnectorError>> {
|
||||
let pay_wall_secret = session
|
||||
.pay_wall_secret
|
||||
.clone()
|
||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
let mut signature_data = get_signature_data(checkout_request);
|
||||
signature_data.push_str(&pay_wall_secret);
|
||||
let payload_digest = digest::digest(&digest::SHA256, signature_data.as_bytes());
|
||||
let mut signature = hex::encode(payload_digest);
|
||||
signature.push_str(";sha256");
|
||||
Ok(Secret::new(signature))
|
||||
}
|
||||
|
||||
/// Fields should be in alphabetical order
|
||||
fn get_signature_data(checkout_request: &CheckoutRequest) -> String {
|
||||
let specified_payment_channel = match checkout_request.specified_payment_channel {
|
||||
ZenPaymentChannels::PclCard => "pcl_card",
|
||||
ZenPaymentChannels::PclGooglepay => "pcl_googlepay",
|
||||
ZenPaymentChannels::PclApplepay => "pcl_applepay",
|
||||
};
|
||||
let mut signature_data = vec![
|
||||
format!("amount={}", checkout_request.amount),
|
||||
format!("currency={}", checkout_request.currency),
|
||||
format!("customipnurl={}", checkout_request.custom_ipn_url),
|
||||
];
|
||||
for index in 0..checkout_request.items.len() {
|
||||
let prefix = format!("items[{index}].");
|
||||
signature_data.push(format!(
|
||||
"{prefix}lineamounttotal={}",
|
||||
checkout_request.items[index].line_amount_total
|
||||
));
|
||||
signature_data.push(format!(
|
||||
"{prefix}name={}",
|
||||
checkout_request.items[index].name
|
||||
));
|
||||
signature_data.push(format!(
|
||||
"{prefix}price={}",
|
||||
checkout_request.items[index].price
|
||||
));
|
||||
signature_data.push(format!(
|
||||
"{prefix}quantity={}",
|
||||
checkout_request.items[index].quantity
|
||||
));
|
||||
}
|
||||
signature_data.push(format!(
|
||||
"merchanttransactionid={}",
|
||||
checkout_request.merchant_transaction_id
|
||||
));
|
||||
signature_data.push(format!(
|
||||
"specifiedpaymentchannel={specified_payment_channel}"
|
||||
));
|
||||
signature_data.push(format!(
|
||||
"terminaluuid={}",
|
||||
checkout_request.terminal_uuid.peek()
|
||||
));
|
||||
signature_data.push(format!("urlredirect={}", checkout_request.url_redirect));
|
||||
let signature = signature_data.join("&");
|
||||
signature.to_lowercase()
|
||||
}
|
||||
|
||||
fn get_customer(
|
||||
item: &types::PaymentsAuthorizeRouterData,
|
||||
ip: IpAddr,
|
||||
) -> Result<ZenCustomerDetails, error_stack::Report<errors::ConnectorError>> {
|
||||
Ok(ZenCustomerDetails {
|
||||
email: item.request.get_email()?,
|
||||
ip,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_item_object(
|
||||
item: &types::PaymentsAuthorizeRouterData,
|
||||
amount: String,
|
||||
) -> Result<Vec<ZenItemObject>, error_stack::Report<errors::ConnectorError>> {
|
||||
let order_details = item.request.get_order_details()?;
|
||||
Ok(vec![ZenItemObject {
|
||||
name: order_details.product_name,
|
||||
price: amount.clone(),
|
||||
quantity: 1,
|
||||
line_amount_total: amount,
|
||||
}])
|
||||
}
|
||||
|
||||
fn get_browser_details(
|
||||
browser_info: &BrowserInformation,
|
||||
) -> Result<ZenBrowserDetails, error_stack::Report<errors::ConnectorError>> {
|
||||
let window_size = match (browser_info.screen_height, browser_info.screen_width) {
|
||||
(250, 400) => "01",
|
||||
(390, 400) => "02",
|
||||
(500, 600) => "03",
|
||||
(600, 400) => "04",
|
||||
_ => "05",
|
||||
}
|
||||
.to_string();
|
||||
Ok(ZenBrowserDetails {
|
||||
color_depth: browser_info.color_depth.to_string(),
|
||||
java_enabled: browser_info.java_enabled,
|
||||
lang: browser_info.language.clone(),
|
||||
screen_height: browser_info.screen_height.to_string(),
|
||||
screen_width: browser_info.screen_width.to_string(),
|
||||
timezone: browser_info.time_zone.to_string(),
|
||||
accept_header: browser_info.accept_header.clone(),
|
||||
window_size,
|
||||
user_agent: browser_info.user_agent.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for ZenPaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||
let browser_info = item.request.get_browser_info()?;
|
||||
let order_details = item.request.get_order_details()?;
|
||||
let ip = browser_info.get_ip_address()?;
|
||||
|
||||
let window_size = match (browser_info.screen_height, browser_info.screen_width) {
|
||||
(250, 400) => "01",
|
||||
(390, 400) => "02",
|
||||
(500, 600) => "03",
|
||||
(600, 400) => "04",
|
||||
_ => "05",
|
||||
}
|
||||
.to_string();
|
||||
let browser_details = ZenBrowserDetails {
|
||||
color_depth: browser_info.color_depth.to_string(),
|
||||
java_enabled: browser_info.java_enabled,
|
||||
lang: browser_info.language,
|
||||
screen_height: browser_info.screen_height.to_string(),
|
||||
screen_width: browser_info.screen_width.to_string(),
|
||||
timezone: browser_info.time_zone.to_string(),
|
||||
accept_header: browser_info.accept_header,
|
||||
window_size,
|
||||
user_agent: browser_info.user_agent,
|
||||
};
|
||||
let (payment_specific_data, payment_channel) = match &item.request.payment_method_data {
|
||||
api::PaymentMethodData::Card(ccard) => Ok((
|
||||
ZenPaymentData {
|
||||
browser_details,
|
||||
//Connector Specific for cards
|
||||
payment_type: ZenPaymentTypes::Onetime,
|
||||
token: None,
|
||||
card: Some(ZenCardDetails {
|
||||
number: ccard.card_number.clone(),
|
||||
expiry_date: ccard
|
||||
.get_card_expiry_month_year_2_digit_with_delimiter("".to_owned()),
|
||||
cvv: ccard.card_cvc.clone(),
|
||||
}),
|
||||
descriptor: item.get_description()?.chars().take(24).collect(),
|
||||
return_verify_url: item.request.router_return_url.clone(),
|
||||
},
|
||||
//Connector Specific for cards
|
||||
ZenPaymentChannels::PclCard,
|
||||
)),
|
||||
api::PaymentMethodData::Wallet(wallet_data) => match wallet_data {
|
||||
api_models::payments::WalletData::GooglePay(data) => Ok((
|
||||
ZenPaymentData {
|
||||
browser_details,
|
||||
//Connector Specific for wallet
|
||||
payment_type: ZenPaymentTypes::ExternalPaymentToken,
|
||||
token: Some(data.tokenization_data.token.clone()),
|
||||
card: None,
|
||||
descriptor: item.get_description()?.chars().take(24).collect(),
|
||||
return_verify_url: item.request.router_return_url.clone(),
|
||||
},
|
||||
ZenPaymentChannels::PclGooglepay,
|
||||
)),
|
||||
api_models::payments::WalletData::ApplePay(data) => Ok((
|
||||
ZenPaymentData {
|
||||
browser_details,
|
||||
//Connector Specific for wallet
|
||||
payment_type: ZenPaymentTypes::ExternalPaymentToken,
|
||||
token: Some(data.payment_data.clone()),
|
||||
card: None,
|
||||
descriptor: item.get_description()?.chars().take(24).collect(),
|
||||
return_verify_url: item.request.router_return_url.clone(),
|
||||
},
|
||||
ZenPaymentChannels::PclApplepay,
|
||||
)),
|
||||
match &item.request.payment_method_data {
|
||||
api_models::payments::PaymentMethodData::Card(card) => Self::try_from((item, card)),
|
||||
api_models::payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data {
|
||||
api_models::payments::WalletData::ApplePayRedirect(apple_pay_redirect_data) => {
|
||||
Self::try_from((item, apple_pay_redirect_data))
|
||||
}
|
||||
api_models::payments::WalletData::GooglePay(gpay_redirect_data) => {
|
||||
Self::try_from((item, gpay_redirect_data))
|
||||
}
|
||||
_ => Err(errors::ConnectorError::NotImplemented(
|
||||
"payment method".to_string(),
|
||||
)),
|
||||
))?,
|
||||
},
|
||||
_ => Err(errors::ConnectorError::NotImplemented(
|
||||
"payment method".to_string(),
|
||||
)),
|
||||
}?;
|
||||
let order_amount =
|
||||
utils::to_currency_base_unit(item.request.amount, item.request.currency)?;
|
||||
Ok(Self {
|
||||
merchant_transaction_id: item.payment_id.clone(),
|
||||
payment_channel,
|
||||
amount: order_amount.clone(),
|
||||
currency: item.request.currency,
|
||||
payment_specific_data,
|
||||
customer: ZenCustomerDetails {
|
||||
email: item.request.get_email()?,
|
||||
ip,
|
||||
},
|
||||
custom_ipn_url: item.request.get_webhook_url()?,
|
||||
items: vec![ZenItemObject {
|
||||
name: order_details.product_name,
|
||||
price: order_amount.clone(),
|
||||
quantity: 1,
|
||||
line_amount_total: order_amount,
|
||||
}],
|
||||
})
|
||||
))?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PaymentsResponse
|
||||
#[derive(Debug, Default, Deserialize, Clone, PartialEq, strum::Display)]
|
||||
#[derive(Debug, Default, Deserialize, Clone, strum::Display)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum ZenPaymentStatus {
|
||||
Authorized,
|
||||
@ -247,12 +413,25 @@ impl ForeignTryFrom<(ZenPaymentStatus, Option<ZenActions>)> for enums::AttemptSt
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ZenPaymentsResponse {
|
||||
pub struct ApiResponse {
|
||||
status: ZenPaymentStatus,
|
||||
id: String,
|
||||
merchant_action: Option<ZenMerchantAction>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ZenPaymentsResponse {
|
||||
ApiResponse(ApiResponse),
|
||||
CheckoutResponse(CheckoutResponse),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CheckoutResponse {
|
||||
redirect_url: url::Url,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ZenMerchantAction {
|
||||
@ -278,7 +457,33 @@ impl<F, T>
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, ZenPaymentsResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let redirection_data_action = item.response.merchant_action.map(|merchant_action| {
|
||||
match item.response {
|
||||
ZenPaymentsResponse::ApiResponse(response) => {
|
||||
Self::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: item.data,
|
||||
http_code: item.http_code,
|
||||
})
|
||||
}
|
||||
ZenPaymentsResponse::CheckoutResponse(response) => {
|
||||
Self::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: item.data,
|
||||
http_code: item.http_code,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T> TryFrom<types::ResponseRouterData<F, ApiResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
value: types::ResponseRouterData<F, ApiResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let redirection_data_action = value.response.merchant_action.map(|merchant_action| {
|
||||
(
|
||||
services::RedirectForm::from((merchant_action.data.redirect_url, Method::Get)),
|
||||
merchant_action.action,
|
||||
@ -290,15 +495,40 @@ impl<F, T>
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::foreign_try_from((item.response.status, action))?,
|
||||
status: enums::AttemptStatus::foreign_try_from((value.response.status, action))?,
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(value.response.id),
|
||||
redirection_data,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
}),
|
||||
..item.data
|
||||
..value.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T> TryFrom<types::ResponseRouterData<F, CheckoutResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
value: types::ResponseRouterData<F, CheckoutResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let redirection_data = Some(services::RedirectForm::from((
|
||||
value.response.redirect_url,
|
||||
Method::Get,
|
||||
)));
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::AuthenticationPending,
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::NoResponseId,
|
||||
redirection_data,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
}),
|
||||
..value.data
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -389,9 +619,11 @@ impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ZenWebhookBody {
|
||||
#[serde(rename = "transactionId")]
|
||||
pub id: String,
|
||||
pub merchant_transaction_id: String,
|
||||
pub amount: String,
|
||||
pub currency: String,
|
||||
@ -403,15 +635,16 @@ pub struct ZenWebhookSignature {
|
||||
pub hash: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ZenWebhookObjectReference {
|
||||
#[serde(rename = "type")]
|
||||
pub transaction_type: ZenWebhookTxnType,
|
||||
pub transaction_id: String,
|
||||
pub merchant_transaction_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ZenWebhookEventType {
|
||||
#[serde(rename = "type")]
|
||||
@ -420,7 +653,7 @@ pub struct ZenWebhookEventType {
|
||||
pub status: ZenPaymentStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum ZenWebhookTxnType {
|
||||
TrtPurchase,
|
||||
|
||||
Reference in New Issue
Block a user