feat: support gpay and applepay session response for all connectors (#839)

Co-authored-by: Arun Raj M <jarnura47@gmail.com>
This commit is contained in:
Sangamesh Kulkarni
2023-04-21 02:52:40 +05:30
committed by GitHub
parent 465933ba72
commit d23e14c57a
11 changed files with 293 additions and 648 deletions

10
Cargo.lock generated
View File

@ -2767,7 +2767,7 @@ dependencies = [
[[package]]
name = "opentelemetry"
version = "0.18.0"
source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
dependencies = [
"opentelemetry_api",
"opentelemetry_sdk",
@ -2776,7 +2776,7 @@ dependencies = [
[[package]]
name = "opentelemetry-otlp"
version = "0.11.0"
source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
dependencies = [
"async-trait",
"futures",
@ -2793,7 +2793,7 @@ dependencies = [
[[package]]
name = "opentelemetry-proto"
version = "0.1.0"
source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
dependencies = [
"futures",
"futures-util",
@ -2805,7 +2805,7 @@ dependencies = [
[[package]]
name = "opentelemetry_api"
version = "0.18.0"
source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
dependencies = [
"fnv",
"futures-channel",
@ -2820,7 +2820,7 @@ dependencies = [
[[package]]
name = "opentelemetry_sdk"
version = "0.18.0"
source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658"
dependencies = [
"async-trait",
"crossbeam-channel",

View File

@ -668,16 +668,6 @@ pub enum RoutableConnectors {
Worldpay,
}
/// Wallets which support obtaining session object
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum SupportedWallets {
Paypal,
ApplePay,
Klarna,
Gpay,
}
/// Name of banks supported by Hyperswitch
#[derive(
Clone,

View File

@ -1339,8 +1339,8 @@ pub struct PaymentsSessionRequest {
/// This is a token which expires after 15 minutes, used from the client to authenticate and create sessions from the SDK
pub client_secret: String,
/// The list of the supported wallets
#[schema(value_type = Vec<SupportedWallets>)]
pub wallets: Vec<api_enums::SupportedWallets>,
#[schema(value_type = Vec<PaymentMethodType>)]
pub wallets: Vec<api_enums::PaymentMethodType>,
/// Merchant connector details used to make payments.
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}
@ -1412,6 +1412,44 @@ pub struct GpaySessionTokenData {
pub data: GpayMetaData,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplepaySessionRequest {
pub merchant_identifier: String,
pub display_name: String,
pub initiative: String,
pub initiative_context: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ApplepaySessionTokenData {
#[serde(rename = "apple_pay")]
pub data: ApplePayMetadata,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ApplePayMetadata {
pub payment_request_data: PaymentRequestMetadata,
pub session_token_data: SessionTokenInfo,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PaymentRequestMetadata {
pub supported_networks: Vec<String>,
pub merchant_capabilities: Vec<String>,
pub label: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SessionTokenInfo {
pub certificate: String,
pub certificate_keys: String,
pub merchant_identifier: String,
pub display_name: String,
pub initiative: String,
pub initiative_context: String,
}
#[derive(Debug, Clone, serde::Serialize, ToSchema)]
#[serde(tag = "wallet_name")]
#[serde(rename_all = "snake_case")]
@ -1435,6 +1473,7 @@ pub struct GpaySessionTokenResponse {
pub allowed_payment_methods: Vec<GpayAllowedPaymentMethods>,
/// The transaction info Google Pay requires
pub transaction_info: GpayTransactionInfo,
pub connector: String,
}
#[derive(Debug, Clone, serde::Serialize, ToSchema)]
@ -1460,9 +1499,11 @@ pub struct ApplepaySessionTokenResponse {
pub session_token_data: ApplePaySessionResponse,
/// Payment request object for Apple Pay
pub payment_request_data: ApplePayPaymentRequest,
pub connector: String,
}
#[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePaySessionResponse {
/// Timestamp at which session is requested
pub epoch_timestamp: u64,
@ -1501,6 +1542,7 @@ pub struct ApplePayPaymentRequest {
pub merchant_capabilities: Vec<String>,
/// The list of supported networks
pub supported_networks: Vec<String>,
pub merchant_identifier: String,
}
#[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)]
@ -1514,6 +1556,13 @@ pub struct AmountInfo {
pub amount: String,
}
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplepayErrorResponse {
pub status_code: String,
pub status_message: String,
}
#[derive(Default, Debug, serde::Serialize, Clone, ToSchema)]
pub struct PaymentsSessionResponse {
/// The identifier for the payment

View File

@ -1,7 +1,6 @@
pub mod aci;
pub mod adyen;
pub mod airwallex;
pub mod applepay;
pub mod authorizedotnet;
pub mod bambora;
pub mod bluesnap;
@ -32,11 +31,11 @@ pub mod worldpay;
pub mod mollie;
pub use self::{
aci::Aci, adyen::Adyen, airwallex::Airwallex, applepay::Applepay,
authorizedotnet::Authorizedotnet, bambora::Bambora, bluesnap::Bluesnap, braintree::Braintree,
checkout::Checkout, coinbase::Coinbase, cybersource::Cybersource, dlocal::Dlocal,
fiserv::Fiserv, forte::Forte, globalpay::Globalpay, klarna::Klarna, mollie::Mollie,
multisafepay::Multisafepay, nexinets::Nexinets, nuvei::Nuvei, opennode::Opennode,
payeezy::Payeezy, paypal::Paypal, payu::Payu, rapyd::Rapyd, shift4::Shift4, stripe::Stripe,
trustpay::Trustpay, worldline::Worldline, worldpay::Worldpay,
aci::Aci, adyen::Adyen, airwallex::Airwallex, authorizedotnet::Authorizedotnet,
bambora::Bambora, bluesnap::Bluesnap, braintree::Braintree, checkout::Checkout,
coinbase::Coinbase, cybersource::Cybersource, dlocal::Dlocal, fiserv::Fiserv, forte::Forte,
globalpay::Globalpay, klarna::Klarna, mollie::Mollie, multisafepay::Multisafepay,
nexinets::Nexinets, nuvei::Nuvei, opennode::Opennode, payeezy::Payeezy, paypal::Paypal,
payu::Payu, rapyd::Rapyd, shift4::Shift4, stripe::Stripe, trustpay::Trustpay,
worldline::Worldline, worldpay::Worldpay,
};

View File

@ -1,273 +0,0 @@
mod transformers;
use std::fmt::Debug;
use common_utils::ext_traits::ValueExt;
use error_stack::{IntoReport, ResultExt};
use self::transformers as applepay;
use crate::{
configs::settings,
core::errors::{self, CustomResult},
headers, services,
types::{
self,
api::{self, ConnectorCommon},
},
utils::{self, BytesExt, OptionExt},
};
#[derive(Debug, Clone)]
pub struct Applepay;
impl ConnectorCommon for Applepay {
fn id(&self) -> &'static str {
"applepay"
}
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
connectors.applepay.base_url.as_ref()
}
}
impl api::Payment for Applepay {}
impl api::PaymentAuthorize for Applepay {}
impl api::PaymentSync for Applepay {}
impl api::PaymentVoid for Applepay {}
impl api::PaymentCapture for Applepay {}
impl api::PreVerify for Applepay {}
impl api::PaymentSession for Applepay {}
impl api::ConnectorAccessToken for Applepay {}
impl api::PaymentToken for Applepay {}
impl
services::ConnectorIntegration<
api::PaymentMethodToken,
types::PaymentMethodTokenizationData,
types::PaymentsResponseData,
> for Applepay
{
// Not Implemented (R)
}
impl
services::ConnectorIntegration<
api::AccessTokenAuth,
types::AccessTokenRequestData,
types::AccessToken,
> for Applepay
{
// Not Implemented (R)
}
impl
services::ConnectorIntegration<
api::Verify,
types::VerifyRequestData,
types::PaymentsResponseData,
> for Applepay
{
}
impl
services::ConnectorIntegration<
api::Capture,
types::PaymentsCaptureData,
types::PaymentsResponseData,
> for Applepay
{
}
impl
services::ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
for Applepay
{
}
impl
services::ConnectorIntegration<
api::Authorize,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
> for Applepay
{
}
impl
services::ConnectorIntegration<
api::Void,
types::PaymentsCancelData,
types::PaymentsResponseData,
> for Applepay
{
}
#[async_trait::async_trait]
impl
services::ConnectorIntegration<
api::Session,
types::PaymentsSessionData,
types::PaymentsResponseData,
> for Applepay
{
fn get_headers(
&self,
_req: &types::PaymentsSessionRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
let header = vec![(
headers::CONTENT_TYPE.to_string(),
types::PaymentsSessionType::get_content_type(self).to_string(),
)];
Ok(header)
}
fn get_url(
&self,
_req: &types::PaymentsSessionRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
"{}{}",
self.base_url(connectors),
"paymentservices/paymentSession"
))
}
fn get_request_body(
&self,
req: &types::PaymentsSessionRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let connector_req = applepay::ApplepaySessionRequest::try_from(req)?;
let req = utils::Encode::<applepay::ApplepaySessionRequest>::encode_to_string_of_json(
&connector_req,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(req))
}
fn build_request(
&self,
req: &types::PaymentsSessionRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let request = 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)?)
.add_certificate(types::PaymentsSessionType::get_certificate(self, req)?)
.add_certificate_key(types::PaymentsSessionType::get_certificate_key(self, req)?)
.build();
Ok(Some(request))
}
fn handle_response(
&self,
data: &types::PaymentsSessionRouterData,
res: types::Response,
) -> CustomResult<types::PaymentsSessionRouterData, errors::ConnectorError> {
let response: applepay::ApplepaySessionTokenResponse = res
.response
.parse_struct("ApplepaySessionResponse")
.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)
}
fn get_error_response(
&self,
res: types::Response,
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
let response: applepay::ErrorResponse = res
.response
.parse_struct("ErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
Ok(types::ErrorResponse {
status_code: res.status_code,
code: response.status_code,
message: response.status_message,
reason: None,
})
}
fn get_certificate(
&self,
req: &types::PaymentsSessionRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let metadata = req
.connector_meta_data
.to_owned()
.get_required_value("connector_meta_data")
.change_context(errors::ConnectorError::NoConnectorMetaData)?;
let metadata: transformers::ApplePayMetadata = metadata
.parse_value("ApplePayMetaData")
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(metadata.session_token_data.certificate))
}
fn get_certificate_key(
&self,
req: &types::PaymentsSessionRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let metadata = req
.connector_meta_data
.to_owned()
.get_required_value("connector_meta_data")
.change_context(errors::ConnectorError::NoConnectorMetaData)?;
let metadata: transformers::ApplePayMetadata = metadata
.parse_value("ApplePayMetaData")
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(metadata.session_token_data.certificate_keys))
}
}
impl api::Refund for Applepay {}
impl api::RefundExecute for Applepay {}
impl api::RefundSync for Applepay {}
impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
for Applepay
{
}
impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
for Applepay
{
}
#[async_trait::async_trait]
impl api::IncomingWebhook for Applepay {
fn get_webhook_object_reference_id(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
}
fn get_webhook_event_type(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
}
fn get_webhook_resource_object(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<serde_json::Value, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
}
}

View File

@ -1,228 +0,0 @@
use api_models::payments;
use common_utils::ext_traits::ValueExt;
use error_stack::ResultExt;
use masking::{Deserialize, Serialize};
use crate::{connector::utils, core::errors, types, utils::OptionExt};
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplepaySessionRequest {
merchant_identifier: String,
display_name: String,
initiative: String,
initiative_context: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplepaySessionTokenResponse {
pub epoch_timestamp: u64,
pub expires_at: u64,
pub merchant_session_identifier: String,
pub nonce: String,
pub merchant_identifier: String,
pub domain_name: String,
pub display_name: String,
pub signature: String,
pub operational_analytics_identifier: String,
pub retries: u8,
pub psp_id: String,
}
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorResponse {
pub status_code: String,
pub status_message: String,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ApplePayMetadata {
pub payment_request_data: PaymentRequestMetadata,
pub session_token_data: SessionRequest,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PaymentRequestMetadata {
pub supported_networks: Vec<String>,
pub merchant_capabilities: Vec<String>,
pub label: String,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct SessionRequest {
pub certificate: String,
pub certificate_keys: String,
pub merchant_identifier: String,
pub display_name: String,
pub initiative: String,
pub initiative_context: String,
}
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct PaymentRequest {
pub apple_pay_merchant_id: String,
pub country_code: api_models::enums::CountryCode,
pub currency_code: String,
pub total: AmountInfo,
pub merchant_capabilities: Vec<String>,
pub supported_networks: Vec<String>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct AmountInfo {
pub label: String,
#[serde(rename = "type")]
pub label_type: String,
pub amount: String,
}
impl TryFrom<&types::PaymentsSessionRouterData> for ApplepaySessionRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsSessionRouterData) -> Result<Self, Self::Error> {
let metadata = item
.connector_meta_data
.to_owned()
.get_required_value("connector_meta_data")
.change_context(errors::ConnectorError::NoConnectorMetaData)?;
let metadata: ApplePayMetadata = metadata
.parse_value("ApplePayMetadata")
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Self {
merchant_identifier: metadata.session_token_data.merchant_identifier,
display_name: metadata.session_token_data.display_name,
initiative: metadata.session_token_data.initiative,
initiative_context: metadata.session_token_data.initiative_context,
})
}
}
impl<F>
TryFrom<
types::ResponseRouterData<
F,
ApplepaySessionTokenResponse,
types::PaymentsSessionData,
types::PaymentsResponseData,
>,
> for types::RouterData<F, types::PaymentsSessionData, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
ApplepaySessionTokenResponse,
types::PaymentsSessionData,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
let metadata = item
.data
.connector_meta_data
.to_owned()
.get_required_value("connector_meta_data")
.change_context(errors::ConnectorError::NoConnectorMetaData)?;
let metadata: ApplePayMetadata = metadata
.parse_value("ApplePayMetadata")
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
let amount_info = AmountInfo {
label: metadata.payment_request_data.label,
label_type: "final".to_string(),
amount: utils::to_currency_base_unit(
item.data.request.amount,
item.data.request.currency,
)?,
};
let payment_request = PaymentRequest {
country_code: item
.data
.request
.country
.to_owned()
.get_required_value("country_code")
.change_context(errors::ConnectorError::MissingRequiredField {
field_name: "country_code",
})?,
currency_code: item.data.request.currency.to_string(),
total: amount_info,
merchant_capabilities: metadata.payment_request_data.merchant_capabilities,
supported_networks: metadata.payment_request_data.supported_networks,
apple_pay_merchant_id: metadata.session_token_data.merchant_identifier,
};
let applepay_session = ApplepaySessionTokenResponse {
epoch_timestamp: item.response.epoch_timestamp,
expires_at: item.response.expires_at,
merchant_session_identifier: item.response.merchant_session_identifier,
nonce: item.response.nonce,
merchant_identifier: item.response.merchant_identifier,
domain_name: item.response.domain_name,
display_name: item.response.display_name,
signature: item.response.signature,
operational_analytics_identifier: item.response.operational_analytics_identifier,
retries: item.response.retries,
psp_id: item.response.psp_id,
};
Ok(Self {
response: Ok(types::PaymentsResponseData::SessionResponse {
session_token: {
api_models::payments::SessionToken::ApplePay(Box::new(
payments::ApplepaySessionTokenResponse {
session_token_data: applepay_session.into(),
payment_request_data: payment_request.into(),
},
))
},
}),
..item.data
})
}
}
impl From<PaymentRequest> for payments::ApplePayPaymentRequest {
fn from(value: PaymentRequest) -> Self {
Self {
country_code: value.country_code,
currency_code: value.currency_code,
total: value.total.into(),
merchant_capabilities: value.merchant_capabilities,
supported_networks: value.supported_networks,
}
}
}
impl From<AmountInfo> for payments::AmountInfo {
fn from(value: AmountInfo) -> Self {
Self {
label: value.label,
total_type: value.label_type,
amount: value.amount,
}
}
}
impl From<ApplepaySessionTokenResponse> for payments::ApplePaySessionResponse {
fn from(value: ApplepaySessionTokenResponse) -> Self {
Self {
epoch_timestamp: value.epoch_timestamp,
expires_at: value.expires_at,
merchant_session_identifier: value.merchant_session_identifier,
nonce: value.nonce,
merchant_identifier: value.merchant_identifier,
domain_name: value.domain_name,
display_name: value.display_name,
signature: value.signature,
operational_analytics_identifier: value.operational_analytics_identifier,
retries: value.retries,
psp_id: value.psp_id,
}
}
}

View File

@ -88,7 +88,6 @@ macro_rules! default_imp_for_complete_authorize{
default_imp_for_complete_authorize!(
connector::Aci,
connector::Adyen,
connector::Applepay,
connector::Authorizedotnet,
connector::Bambora,
connector::Bluesnap,
@ -132,7 +131,6 @@ macro_rules! default_imp_for_connector_redirect_response{
default_imp_for_connector_redirect_response!(
connector::Aci,
connector::Adyen,
connector::Applepay,
connector::Authorizedotnet,
connector::Bambora,
connector::Bluesnap,
@ -166,7 +164,6 @@ default_imp_for_connector_request_id!(
connector::Aci,
connector::Adyen,
connector::Airwallex,
connector::Applepay,
connector::Authorizedotnet,
connector::Bambora,
connector::Bluesnap,

View File

@ -1,17 +1,20 @@
use api_models::payments as payment_types;
use async_trait::async_trait;
use error_stack::ResultExt;
use common_utils::ext_traits::ByteSliceExt;
use error_stack::{report, ResultExt};
use super::{ConstructFlowSpecificData, Feature};
use crate::{
connector,
core::{
errors::{self, ConnectorErrorExt, RouterResult},
payments::{self, access_token, transformers, PaymentData},
},
headers,
routes::{self, metrics},
services,
types::{self, api, storage},
utils::OptionExt,
utils::{self, OptionExt},
};
#[async_trait]
@ -73,8 +76,159 @@ impl Feature<api::Session, types::PaymentsSessionData> for types::PaymentsSessio
}
}
fn mk_applepay_session_request(
state: &routes::AppState,
router_data: &types::PaymentsSessionRouterData,
) -> RouterResult<(services::Request, payment_types::ApplepaySessionTokenData)> {
let connector_metadata = router_data.connector_meta_data.clone();
let applepay_metadata = connector_metadata
.parse_value::<payment_types::ApplepaySessionTokenData>("ApplepaySessionTokenData")
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
field_name: "connector_metadata".to_string(),
expected_format: "applepay_metadata_format".to_string(),
})?;
let request = payment_types::ApplepaySessionRequest {
merchant_identifier: applepay_metadata
.data
.session_token_data
.merchant_identifier
.clone(),
display_name: applepay_metadata
.data
.session_token_data
.display_name
.clone(),
initiative: applepay_metadata.data.session_token_data.initiative.clone(),
initiative_context: applepay_metadata
.data
.session_token_data
.initiative_context
.clone(),
};
let applepay_session_request =
utils::Encode::<payment_types::ApplepaySessionRequest>::encode_to_string_of_json(&request)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to encode ApplePay session request to a string of json")?;
let mut url = state.conf.connectors.applepay.base_url.to_owned();
url.push_str("paymentservices/paymentSession");
let session_request = services::RequestBuilder::new()
.method(services::Method::Post)
.url(url.as_str())
.attach_default_headers()
.headers(vec![(
headers::CONTENT_TYPE.to_string(),
"application/json".to_string(),
)])
.body(Some(applepay_session_request))
.add_certificate(Some(
applepay_metadata
.data
.session_token_data
.certificate
.clone(),
))
.add_certificate_key(Some(
applepay_metadata
.data
.session_token_data
.certificate_keys
.clone(),
))
.build();
Ok((session_request, applepay_metadata))
}
async fn create_applepay_session_token(
state: &routes::AppState,
router_data: &types::PaymentsSessionRouterData,
connector: &api::ConnectorData,
) -> RouterResult<types::PaymentsSessionRouterData> {
let (applepay_session_request, applepay_metadata) =
mk_applepay_session_request(state, router_data)?;
let response = services::call_connector_api(state, applepay_session_request)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failure in calling connector api")?;
let session_response: payment_types::ApplePaySessionResponse = match response {
Ok(resp) => resp
.response
.parse_struct("ApplePaySessionResponse")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to parse ApplePaySessionResponse struct"),
Err(err) => {
let error_response: payment_types::ApplepayErrorResponse = err
.response
.parse_struct("ApplepayErrorResponse")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to parse ApplepayErrorResponse struct")?;
Err(
report!(errors::ApiErrorResponse::InternalServerError).attach_printable(format!(
"Failed with {} status code and the error response is {:?}",
err.status_code, error_response
)),
)
}
}?;
let amount_info = payment_types::AmountInfo {
label: applepay_metadata.data.payment_request_data.label,
total_type: "final".to_string(),
amount: connector::utils::to_currency_base_unit(
router_data.request.amount,
router_data.request.currency,
)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to convert currency to base unit")?,
};
let applepay_payment_request = payment_types::ApplePayPaymentRequest {
country_code: router_data
.request
.country
.to_owned()
.get_required_value("country_code")
.change_context(errors::ApiErrorResponse::MissingRequiredField {
field_name: "country_code",
})?,
currency_code: router_data.request.currency.to_string(),
total: amount_info,
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,
};
let response_router_data = types::PaymentsSessionRouterData {
response: Ok(types::PaymentsResponseData::SessionResponse {
session_token: payment_types::SessionToken::ApplePay(Box::new(
payment_types::ApplepaySessionTokenResponse {
session_token_data: session_response,
payment_request_data: applepay_payment_request,
connector: connector.connector_name.to_string(),
},
)),
}),
..router_data.clone()
};
Ok(response_router_data)
}
fn create_gpay_session_token(
router_data: &types::PaymentsSessionRouterData,
connector: &api::ConnectorData,
) -> RouterResult<types::PaymentsSessionRouterData> {
let connector_metadata = router_data.connector_meta_data.clone();
@ -105,6 +259,7 @@ fn create_gpay_session_token(
merchant_info: gpay_data.data.merchant_info,
allowed_payment_methods: gpay_data.data.allowed_payment_methods,
transaction_info,
connector: connector.connector_name.to_string(),
},
)),
}),
@ -124,7 +279,10 @@ impl types::PaymentsSessionRouterData {
call_connector_action: payments::CallConnectorAction,
) -> RouterResult<Self> {
match connector.get_token {
api::GetToken::Metadata => create_gpay_session_token(self),
api::GetToken::GpayMetadata => create_gpay_session_token(self, connector),
api::GetToken::ApplePayMetadata => {
create_applepay_session_token(state, self, connector).await
}
api::GetToken::Connector => {
let connector_integration: services::BoxedConnectorIntegration<
'_,

View File

@ -1,4 +1,4 @@
use std::{collections::HashSet, marker::PhantomData};
use std::marker::PhantomData;
use api_models::admin::PaymentMethodsEnabled;
use async_trait::async_trait;
@ -18,7 +18,7 @@ use crate::{
pii::Secret,
routes::AppState,
types::{
api::{self, enums as api_enums, PaymentIdTypeExt},
api::{self, PaymentIdTypeExt},
storage::{self, enums as storage_enums},
transformers::ForeignInto,
},
@ -293,8 +293,6 @@ where
let connectors = &state.conf.connectors;
let db = &state.store;
let supported_connectors: &Vec<String> = state.conf.connectors.supported.wallets.as_ref();
let connector_accounts = db
.find_merchant_connector_account_by_merchant_id_and_disabled_list(
&merchant_account.merchant_id,
@ -304,127 +302,73 @@ where
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Database error when querying for merchant connector accounts")?;
let normal_connector_names: HashSet<String> = connector_accounts
.iter()
.filter(|connector_account| {
connector_account
.payment_methods_enabled
.clone()
.unwrap_or_default()
.iter()
.any(|payment_method| {
let parsed_payment_method_result: Result<
PaymentMethodsEnabled,
error_stack::Report<errors::ParsingError>,
> = payment_method.clone().parse_value("payment_method");
let mut connector_and_supporting_payment_method_type = Vec::new();
match parsed_payment_method_result {
Ok(parsed_payment_method) => parsed_payment_method
.payment_method_types
.map(|payment_method_types| {
payment_method_types.iter().any(|payment_method_type| {
matches!(
payment_method_type.payment_experience,
Some(api_models::enums::PaymentExperience::InvokeSdkClient)
)
})
})
.unwrap_or(false),
Err(parsing_error) => {
logger::debug!(session_token_parsing_error=?parsing_error);
false
for connector_account in connector_accounts {
let payment_methods = connector_account
.payment_methods_enabled
.unwrap_or_default();
for payment_method in payment_methods {
let parsed_payment_method_result: Result<
PaymentMethodsEnabled,
error_stack::Report<errors::ParsingError>,
> = payment_method.clone().parse_value("payment_method");
match parsed_payment_method_result {
Ok(parsed_payment_method) => {
let payment_method_types = parsed_payment_method
.payment_method_types
.unwrap_or_default();
for payment_method_type in payment_method_types {
if matches!(
payment_method_type.payment_experience,
Some(api_models::enums::PaymentExperience::InvokeSdkClient)
) {
let connector_and_wallet = (
connector_account.connector_name.to_owned(),
payment_method_type.payment_method_type,
);
connector_and_supporting_payment_method_type
.push(connector_and_wallet);
}
}
})
})
.map(|filtered_connector| filtered_connector.connector_name.clone())
.collect();
}
Err(parsing_error) => {
logger::debug!(session_token_parsing_error=?parsing_error);
}
}
}
}
// Parse the payment methods enabled to check if the merchant has enabled googlepay ( wallet ) using that connector.
// A single connector can support creating session token from metadata as well as by calling the connector.
let session_token_from_metadata_connectors = connector_accounts
.iter()
.filter(|connector_account| {
connector_account
.payment_methods_enabled
.clone()
.unwrap_or_default()
.iter()
.any(|payment_method| {
let parsed_payment_method_result: Result<
PaymentMethodsEnabled,
error_stack::Report<errors::ParsingError>,
> = payment_method.clone().parse_value("payment_method");
let requested_payment_method_types = request.wallets.clone();
match parsed_payment_method_result {
Ok(parsed_payment_method) => parsed_payment_method
.payment_method_types
.map(|payment_method_types| {
payment_method_types.iter().any(|payment_method_type| {
matches!(
payment_method_type.payment_method_type,
api_models::enums::PaymentMethodType::GooglePay
)
})
})
.unwrap_or(false),
Err(parsing_error) => {
logger::debug!(session_token_parsing_error=?parsing_error);
false
}
}
})
})
.map(|filtered_connector| filtered_connector.connector_name.clone())
.collect::<HashSet<String>>();
let given_wallets = request.wallets.clone();
let connectors_data = if !given_wallets.is_empty() {
// Create connectors for provided wallets
let mut connectors_data = Vec::with_capacity(supported_connectors.len());
for wallet in given_wallets {
let (connector_name, connector_type) = match wallet {
api_enums::SupportedWallets::Gpay => ("adyen", api::GetToken::Metadata),
api_enums::SupportedWallets::ApplePay => ("applepay", api::GetToken::Connector),
api_enums::SupportedWallets::Paypal => ("braintree", api::GetToken::Connector),
api_enums::SupportedWallets::Klarna => ("klarna", api::GetToken::Connector),
};
// Check if merchant has enabled the required merchant connector account
if session_token_from_metadata_connectors.contains(connector_name)
|| normal_connector_names.contains(connector_name)
let connectors_data = if !requested_payment_method_types.is_empty() {
let mut connectors_data = Vec::new();
for payment_method_type in requested_payment_method_types {
for connector_and_payment_method_type in
&connector_and_supporting_payment_method_type
{
connectors_data.push(api::ConnectorData::get_connector_by_name(
connectors,
connector_name,
connector_type,
)?);
if connector_and_payment_method_type.1 == payment_method_type {
let connector_details = api::ConnectorData::get_connector_by_name(
connectors,
connector_and_payment_method_type.0.as_str(),
api::GetToken::from(connector_and_payment_method_type.1),
)?;
connectors_data.push(connector_details);
}
}
}
connectors_data
} else {
// Create connectors for all enabled wallets
let mut connectors_data = Vec::with_capacity(
normal_connector_names.len() + session_token_from_metadata_connectors.len(),
);
let mut connectors_data = Vec::new();
for connector_name in normal_connector_names {
let connector_data = api::ConnectorData::get_connector_by_name(
for connector_and_payment_method_type in connector_and_supporting_payment_method_type {
let connector_details = api::ConnectorData::get_connector_by_name(
connectors,
&connector_name,
api::GetToken::Connector,
connector_and_payment_method_type.0.as_str(),
api::GetToken::from(connector_and_payment_method_type.1),
)?;
connectors_data.push(connector_data);
}
for connector_name in session_token_from_metadata_connectors {
let connector_data = api::ConnectorData::get_connector_by_name(
connectors,
&connector_name,
api::GetToken::Metadata,
)?;
connectors_data.push(connector_data);
connectors_data.push(connector_details);
}
connectors_data
};
@ -432,3 +376,13 @@ where
Ok(api::ConnectorChoice::SessionMultiple(connectors_data))
}
}
impl From<api_models::enums::PaymentMethodType> for api::GetToken {
fn from(value: api_models::enums::PaymentMethodType) -> Self {
match value {
api_models::enums::PaymentMethodType::GooglePay => Self::GpayMetadata,
api_models::enums::PaymentMethodType::ApplePay => Self::ApplePayMetadata,
_ => Self::Connector,
}
}
}

View File

@ -140,7 +140,6 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::enums::AuthenticationType,
api_models::enums::Connector,
api_models::enums::PaymentMethod,
api_models::enums::SupportedWallets,
api_models::enums::PaymentMethodIssuerCode,
api_models::enums::MandateStatus,
api_models::enums::PaymentExperience,

View File

@ -131,9 +131,10 @@ type BoxedConnector = Box<&'static (dyn Connector + Sync)>;
// Normal flow will call the connector and follow the flow specific operations (capture, authorize)
// SessionTokenFromMetadata will avoid calling the connector instead create the session token ( for sdk )
#[derive(Clone)]
#[derive(Clone, Eq, PartialEq)]
pub enum GetToken {
Metadata,
GpayMetadata,
ApplePayMetadata,
Connector,
}
@ -189,7 +190,6 @@ impl ConnectorData {
"aci" => Ok(Box::new(&connector::Aci)),
"adyen" => Ok(Box::new(&connector::Adyen)),
"airwallex" => Ok(Box::new(&connector::Airwallex)),
"applepay" => Ok(Box::new(&connector::Applepay)),
"authorizedotnet" => Ok(Box::new(&connector::Authorizedotnet)),
"bambora" => Ok(Box::new(&connector::Bambora)),
"bluesnap" => Ok(Box::new(&connector::Bluesnap)),