mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +08:00
feat(connector): [Bluesnap] Add support for ApplePay (#1178)
This commit is contained in:
committed by
GitHub
parent
ed22b2af76
commit
919c03e679
@ -494,7 +494,83 @@ impl api::PaymentSession for Bluesnap {}
|
||||
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
|
||||
for Bluesnap
|
||||
{
|
||||
//TODO: implement sessions flow
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
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::PaymentsSessionRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!(
|
||||
"{}{}",
|
||||
self.base_url(connectors),
|
||||
"services/2/wallets"
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let connector_req = bluesnap::BluesnapCreateWalletToken::try_from(req)?;
|
||||
let bluesnap_req =
|
||||
utils::Encode::<bluesnap::BluesnapCreateWalletToken>::encode_to_string_of_json(
|
||||
&connector_req,
|
||||
)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(bluesnap_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsSessionType::get_url(self, req, connectors)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::PaymentsSessionType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.body(types::PaymentsSessionType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsSessionRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsSessionRouterData, errors::ConnectorError> {
|
||||
let response: bluesnap::BluesnapWalletTokenResponse = res
|
||||
.response
|
||||
.parse_struct("BluesnapWalletTokenResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl api::PaymentAuthorize for Bluesnap {}
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
use api_models::enums as api_enums;
|
||||
use base64::Engine;
|
||||
use common_utils::{
|
||||
ext_traits::{StringExt, ValueExt},
|
||||
ext_traits::{ByteSliceExt, StringExt, ValueExt},
|
||||
pii::Email,
|
||||
};
|
||||
use error_stack::ResultExt;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use masking::ExposeInterface;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connector::utils,
|
||||
connector::utils::{self, RouterData},
|
||||
consts,
|
||||
core::errors,
|
||||
pii::Secret,
|
||||
types::{self, api, storage::enums, transformers::ForeignTryFrom},
|
||||
utils::Encode,
|
||||
utils::{Encode, OptionExt},
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq)]
|
||||
@ -26,6 +28,15 @@ pub struct BluesnapPaymentsRequest {
|
||||
three_d_secure: Option<BluesnapThreeDSecureInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BluesnapCreateWalletToken {
|
||||
wallet_type: String,
|
||||
validation_url: Secret<String>,
|
||||
domain_name: String,
|
||||
display_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BluesnapThreeDSecureInfo {
|
||||
@ -74,6 +85,56 @@ pub enum BluesnapWalletTypes {
|
||||
ApplePay,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EncodedPaymentToken {
|
||||
billing_contact: BillingDetails,
|
||||
token: ApplepayPaymentData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BillingDetails {
|
||||
country_code: Option<api_enums::CountryAlpha2>,
|
||||
address_lines: Option<Vec<Secret<String>>>,
|
||||
family_name: Option<Secret<String>>,
|
||||
given_name: Option<Secret<String>>,
|
||||
postal_code: Option<Secret<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ApplepayPaymentData {
|
||||
payment_data: ApplePayEncodedPaymentData,
|
||||
payment_method: ApplepayPaymentMethod,
|
||||
transaction_identifier: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ApplepayPaymentMethod {
|
||||
display_name: String,
|
||||
network: String,
|
||||
#[serde(rename = "type")]
|
||||
pm_type: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
|
||||
pub struct ApplePayEncodedPaymentData {
|
||||
data: String,
|
||||
header: Option<ApplepayHeader>,
|
||||
signature: String,
|
||||
version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ApplepayHeader {
|
||||
ephemeral_public_key: Secret<String>,
|
||||
public_key_hash: Secret<String>,
|
||||
transaction_id: Secret<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for BluesnapPaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||
@ -104,13 +165,64 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for BluesnapPaymentsRequest {
|
||||
}))
|
||||
}
|
||||
api_models::payments::WalletData::ApplePay(payment_method_data) => {
|
||||
let apple_pay_object =
|
||||
Encode::<BluesnapApplePayObject>::encode_to_string_of_json(
|
||||
&BluesnapApplePayObject {
|
||||
token: payment_method_data,
|
||||
let apple_pay_payment_data = consts::BASE64_ENGINE
|
||||
.decode(payment_method_data.payment_data)
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
|
||||
let apple_pay_payment_data: ApplePayEncodedPaymentData = apple_pay_payment_data
|
||||
[..]
|
||||
.parse_struct("ApplePayEncodedPaymentData")
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
|
||||
let billing = item
|
||||
.address
|
||||
.billing
|
||||
.to_owned()
|
||||
.get_required_value("billing")
|
||||
.change_context(errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "billing",
|
||||
})?;
|
||||
|
||||
let billing_address = billing
|
||||
.address
|
||||
.get_required_value("billing_address")
|
||||
.change_context(errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "billing",
|
||||
})?;
|
||||
|
||||
let mut address = Vec::new();
|
||||
if let Some(add) = billing_address.line1.to_owned() {
|
||||
address.push(add)
|
||||
}
|
||||
if let Some(add) = billing_address.line2.to_owned() {
|
||||
address.push(add)
|
||||
}
|
||||
if let Some(add) = billing_address.line3.to_owned() {
|
||||
address.push(add)
|
||||
}
|
||||
|
||||
let apple_pay_object = Encode::<EncodedPaymentToken>::encode_to_string_of_json(
|
||||
&EncodedPaymentToken {
|
||||
token: ApplepayPaymentData {
|
||||
payment_data: apple_pay_payment_data,
|
||||
payment_method: payment_method_data
|
||||
.payment_method
|
||||
.to_owned()
|
||||
.into(),
|
||||
transaction_identifier: payment_method_data.transaction_identifier,
|
||||
},
|
||||
billing_contact: BillingDetails {
|
||||
country_code: billing_address.country,
|
||||
address_lines: Some(address),
|
||||
family_name: billing_address.last_name.to_owned(),
|
||||
given_name: billing_address.first_name.to_owned(),
|
||||
postal_code: billing_address.zip,
|
||||
},
|
||||
},
|
||||
)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
|
||||
Ok(PaymentMethodDetails::Wallet(BluesnapWallet {
|
||||
wallet_type: BluesnapWalletTypes::ApplePay,
|
||||
encoded_payment_token: consts::BASE64_ENGINE.encode(apple_pay_object),
|
||||
@ -134,6 +246,94 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for BluesnapPaymentsRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<api_models::payments::ApplepayPaymentMethod> for ApplepayPaymentMethod {
|
||||
fn from(item: api_models::payments::ApplepayPaymentMethod) -> Self {
|
||||
Self {
|
||||
display_name: item.display_name,
|
||||
network: item.network,
|
||||
pm_type: item.pm_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsSessionRouterData> for BluesnapCreateWalletToken {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsSessionRouterData) -> Result<Self, Self::Error> {
|
||||
let apple_pay_metadata = item.get_connector_meta()?.expose();
|
||||
let applepay_metadata = apple_pay_metadata
|
||||
.parse_value::<api_models::payments::ApplepaySessionTokenData>(
|
||||
"ApplepaySessionTokenData",
|
||||
)
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
Ok(Self {
|
||||
wallet_type: "APPLE_PAY".to_string(),
|
||||
validation_url: consts::APPLEPAY_VALIDATION_URL.to_string().into(),
|
||||
domain_name: applepay_metadata.data.session_token_data.initiative_context,
|
||||
display_name: Some(applepay_metadata.data.session_token_data.display_name),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenResponse>>
|
||||
for types::PaymentsSessionRouterData
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::PaymentsSessionResponseRouterData<BluesnapWalletTokenResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let response = &item.response;
|
||||
|
||||
let wallet_token = consts::BASE64_ENGINE
|
||||
.decode(response.wallet_token.clone().expose())
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
|
||||
let session_response: api_models::payments::ApplePaySessionResponse = wallet_token[..]
|
||||
.parse_struct("ApplePayResponse")
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
|
||||
let metadata = item.data.get_connector_meta()?.expose();
|
||||
let applepay_metadata = metadata
|
||||
.parse_value::<api_models::payments::ApplepaySessionTokenData>(
|
||||
"ApplepaySessionTokenData",
|
||||
)
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
|
||||
Ok(Self {
|
||||
response: Ok(types::PaymentsResponseData::SessionResponse {
|
||||
session_token: types::api::SessionToken::ApplePay(Box::new(
|
||||
api_models::payments::ApplepaySessionTokenResponse {
|
||||
session_token_data: session_response,
|
||||
payment_request_data: api_models::payments::ApplePayPaymentRequest {
|
||||
country_code: item.data.get_billing_country()?,
|
||||
currency_code: item.data.request.currency.to_string(),
|
||||
total: api_models::payments::AmountInfo {
|
||||
label: applepay_metadata.data.payment_request_data.label,
|
||||
total_type: "final".to_string(),
|
||||
amount: item.data.request.amount.to_string(),
|
||||
},
|
||||
merchant_capabilities: applepay_metadata
|
||||
.data
|
||||
.payment_request_data
|
||||
.merchant_capabilities,
|
||||
supported_networks: applepay_metadata
|
||||
.data
|
||||
.payment_request_data
|
||||
.supported_networks,
|
||||
merchant_identifier: applepay_metadata
|
||||
.data
|
||||
.session_token_data
|
||||
.merchant_identifier,
|
||||
},
|
||||
connector: "bluesnap".to_string(),
|
||||
},
|
||||
)),
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for BluesnapPaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||
@ -374,6 +574,13 @@ pub struct BluesnapPaymentsResponse {
|
||||
card_transaction_type: BluesnapTxnType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BluesnapWalletTokenResponse {
|
||||
wallet_type: String,
|
||||
wallet_token: Secret<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Refund {
|
||||
|
||||
@ -27,3 +27,7 @@ pub(crate) const BASE64_ENGINE_URL_SAFE: base64::engine::GeneralPurpose =
|
||||
|
||||
pub(crate) const API_KEY_LENGTH: usize = 64;
|
||||
pub(crate) const PUB_SUB_CHANNEL: &str = "hyperswitch_invalidate";
|
||||
|
||||
// Apple Pay validation url
|
||||
pub(crate) const APPLEPAY_VALIDATION_URL: &str =
|
||||
"https://apple-pay-gateway-cert.apple.com/paymentservices/startSession";
|
||||
|
||||
@ -376,11 +376,13 @@ where
|
||||
for (connector, payment_method_type, business_sub_label) in
|
||||
connector_and_supporting_payment_method_type
|
||||
{
|
||||
match api::ConnectorData::get_connector_by_name(
|
||||
connectors,
|
||||
&connector,
|
||||
api::GetToken::from(payment_method_type),
|
||||
) {
|
||||
let connector_type = get_connector_type_for_session_token(
|
||||
payment_method_type,
|
||||
request,
|
||||
connector.to_owned(),
|
||||
);
|
||||
match api::ConnectorData::get_connector_by_name(connectors, &connector, connector_type)
|
||||
{
|
||||
Ok(connector_data) => session_connector_data.push(api::SessionConnectorData {
|
||||
payment_method_type,
|
||||
connector: connector_data,
|
||||
@ -407,3 +409,19 @@ impl From<api_models::enums::PaymentMethodType> for api::GetToken {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_connector_type_for_session_token(
|
||||
payment_method_type: api_models::enums::PaymentMethodType,
|
||||
_request: &api::PaymentsSessionRequest,
|
||||
connector: String,
|
||||
) -> api::GetToken {
|
||||
if payment_method_type == api_models::enums::PaymentMethodType::ApplePay {
|
||||
if connector == *"bluesnap" {
|
||||
api::GetToken::Connector
|
||||
} else {
|
||||
api::GetToken::ApplePayMetadata
|
||||
}
|
||||
} else {
|
||||
api::GetToken::from(payment_method_type)
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ use std::marker::PhantomData;
|
||||
pub use api_models::enums::Connector;
|
||||
use common_utils::{pii, pii::Email};
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use masking::Secret;
|
||||
|
||||
use self::{api::payments, storage::enums as storage_enums};
|
||||
pub use crate::core::payments::PaymentAddress;
|
||||
@ -244,7 +245,7 @@ pub struct AuthorizeSessionTokenData {
|
||||
pub struct ConnectorCustomerData {
|
||||
pub description: Option<String>,
|
||||
pub email: Option<Email>,
|
||||
pub phone: Option<masking::Secret<String>>,
|
||||
pub phone: Option<Secret<String>>,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user