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

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,
}
}
}