feat(connector): [PayMe] Implement preprocessing flow for cards (#1904)

This commit is contained in:
Sangamesh Kulkarni
2023-08-10 23:30:55 +05:30
committed by GitHub
parent 5bc7592af3
commit 38b9c077b7
17 changed files with 406 additions and 188 deletions

View File

@ -2407,6 +2407,10 @@ pub struct ApplepaySessionTokenResponse {
pub delayed_session_token: bool,
/// The next action for the sdk (ex: calling confirm or sync call)
pub sdk_next_action: SdkNextAction,
/// The connector transaction id
pub connector_reference_id: Option<String>,
/// The public key id is to invoke third party sdk
pub connector_sdk_public_key: Option<String>,
}
#[derive(Debug, Eq, PartialEq, serde::Serialize, Clone, ToSchema)]
@ -2489,9 +2493,9 @@ pub struct ApplePayPaymentRequest {
/// Represents the total for the payment.
pub total: AmountInfo,
/// The list of merchant capabilities(ex: whether capable of 3ds or no-3ds)
pub merchant_capabilities: Vec<String>,
pub merchant_capabilities: Option<Vec<String>>,
/// The list of supported networks
pub supported_networks: Vec<String>,
pub supported_networks: Option<Vec<String>>,
pub merchant_identifier: Option<String>,
}

View File

@ -392,14 +392,18 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenRespons
total_type: Some("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_capabilities: Some(
applepay_metadata
.data
.payment_request_data
.merchant_capabilities,
),
supported_networks: Some(
applepay_metadata
.data
.payment_request_data
.supported_networks,
),
merchant_identifier: Some(
applepay_metadata
.data
@ -414,6 +418,8 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenRespons
next_action: payments::NextActionCall::Confirm,
}
},
connector_reference_id: None,
connector_sdk_public_key: None,
},
)),
}),

View File

@ -9,11 +9,8 @@ use transformers as payme;
use crate::{
configs::settings,
core::{
errors::{self, CustomResult},
payments,
},
headers, routes,
core::errors::{self, CustomResult},
headers,
services::{self, request, ConnectorIntegration},
types::{
self,
@ -86,12 +83,18 @@ impl ConnectorCommon for Payme {
res.response
.parse_struct("PaymeErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let status_code = match res.status_code {
500..=511 => 200,
_ => res.status_code,
};
Ok(ErrorResponse {
status_code: res.status_code,
code: response.code,
message: response.message,
reason: response.reason,
status_code,
code: response.status_error_code.to_string(),
message: response.status_error_details.clone(),
reason: Some(format!(
"{}, additional info: {}",
response.status_error_details, response.status_additional_info
)),
})
}
}
@ -101,26 +104,18 @@ impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::Payme
{
}
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
for Payme
{
}
impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
for Payme
{
}
impl api::PaymentsPreProcessing for Payme {}
impl
ConnectorIntegration<
api::InitPayment,
types::PaymentsAuthorizeData,
api::PreProcessing,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
> for Payme
{
fn get_headers(
&self,
req: &types::PaymentsInitRouterData,
req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
self.build_headers(req, connectors)
@ -132,11 +127,7 @@ impl
fn get_url(
&self,
_req: &types::RouterData<
api::InitPayment,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
>,
_req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!("{}api/generate-sale", self.base_url(connectors)))
@ -144,7 +135,7 @@ impl
fn get_request_body(
&self,
req: &types::PaymentsInitRouterData,
req: &types::PaymentsPreProcessingRouterData,
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
let req_obj = payme::GenerateSaleRequest::try_from(req)?;
let payme_req = types::RequestBody::log_and_get_request_body(
@ -157,37 +148,32 @@ impl
fn build_request(
&self,
req: &types::PaymentsInitRouterData,
req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
let req = Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsInitType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(types::PaymentsInitType::get_headers(self, req, connectors)?)
.body(types::PaymentsInitType::get_request_body(self, req)?)
.headers(types::PaymentsPreProcessingType::get_headers(
self, req, connectors,
)?)
.url(&types::PaymentsPreProcessingType::get_url(
self, req, connectors,
)?)
.body(types::PaymentsPreProcessingType::get_request_body(
self, req,
)?)
.build(),
))
);
Ok(req)
}
fn handle_response(
&self,
data: &types::PaymentsInitRouterData,
data: &types::PaymentsPreProcessingRouterData,
res: Response,
) -> CustomResult<
types::RouterData<
api::InitPayment,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
>,
errors::ConnectorError,
>
where
api::InitPayment: Clone,
types::PaymentsAuthorizeData: Clone,
types::PaymentsResponseData: Clone,
{
) -> CustomResult<types::PaymentsPreProcessingRouterData, errors::ConnectorError> {
let response: payme::GenerateSaleResponse = res
.response
.parse_struct("Payme GenerateSaleResponse")
@ -205,44 +191,29 @@ impl
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
fn get_5xx_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
// we are always getting 500 in error scenarios
self.build_error_response(res)
}
}
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
for Payme
{
}
impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
for Payme
{
}
#[async_trait::async_trait]
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
for Payme
{
async fn execute_pretasks(
&self,
router_data: &mut types::PaymentsAuthorizeRouterData,
app_state: &routes::AppState,
) -> CustomResult<(), errors::ConnectorError> {
if router_data.request.mandate_id.is_none() {
let integ: Box<
&(dyn ConnectorIntegration<
api::InitPayment,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
> + Send
+ Sync
+ 'static),
> = Box::new(&Self);
let init_data = &types::PaymentsInitRouterData::from((
&router_data.to_owned(),
router_data.request.clone(),
));
let init_res = services::execute_connector_processing_step(
app_state,
integ,
init_data,
payments::CallConnectorAction::Trigger,
None,
)
.await?;
router_data.request.related_transaction_id = init_res.request.related_transaction_id;
}
Ok(())
}
fn get_headers(
&self,
req: &types::PaymentsAuthorizeRouterData,
@ -324,6 +295,14 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
fn get_5xx_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
// we are always getting 500 in error scenarios
self.build_error_response(res)
}
}
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
@ -401,6 +380,14 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
http_code: res.status_code,
})
}
fn get_5xx_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
// we are always getting 500 in error scenarios
self.build_error_response(res)
}
}
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
@ -479,6 +466,14 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
fn get_5xx_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
// we are always getting 500 in error scenarios
self.build_error_response(res)
}
}
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
@ -570,6 +565,14 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
fn get_5xx_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
// we are always getting 500 in error scenarios
self.build_error_response(res)
}
}
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Payme {
@ -651,6 +654,14 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
fn get_5xx_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
// we are always getting 500 in error scenarios
self.build_error_response(res)
}
}
#[async_trait::async_trait]

View File

@ -6,13 +6,15 @@ use serde::{Deserialize, Serialize};
use crate::{
connector::utils::{
missing_field_err, AddressDetailsData, CardData, PaymentsAuthorizeRequestData,
PaymentsSyncRequestData, RouterData,
self, missing_field_err, AddressDetailsData, CardData, PaymentsAuthorizeRequestData,
PaymentsPreProcessingData, PaymentsSyncRequestData, RouterData,
},
core::errors,
types::{self, api, storage::enums, MandateReference},
};
const LANGUAGE: &str = "en";
#[derive(Debug, Serialize)]
pub struct PayRequest {
buyer_name: Secret<String>,
@ -20,6 +22,7 @@ pub struct PayRequest {
payme_sale_id: String,
#[serde(flatten)]
card: PaymeCard,
language: String,
}
#[derive(Debug, Serialize)]
@ -32,6 +35,7 @@ pub struct MandateRequest {
seller_payme_id: Secret<String>,
sale_callback_url: String,
buyer_key: Secret<String>,
language: String,
}
#[derive(Debug, Serialize)]
@ -71,6 +75,7 @@ pub struct GenerateSaleRequest {
seller_payme_id: Secret<String>,
sale_callback_url: String,
sale_payment_method: SalePaymentMethod,
language: String,
}
#[derive(Debug, Deserialize)]
@ -183,11 +188,12 @@ pub enum SaleType {
#[serde(rename_all = "kebab-case")]
pub enum SalePaymentMethod {
CreditCard,
ApplePay,
}
impl TryFrom<&types::PaymentsInitRouterData> for GenerateSaleRequest {
impl TryFrom<&types::PaymentsPreProcessingRouterData> for GenerateSaleRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsInitRouterData) -> Result<Self, Self::Error> {
fn try_from(item: &types::PaymentsPreProcessingRouterData) -> Result<Self, Self::Error> {
let sale_type = SaleType::try_from(item)?;
let seller_payme_id = PaymeAuthType::try_from(&item.connector_auth_type)?.seller_payme_id;
let order_details = item.request.get_order_details()?;
@ -196,44 +202,67 @@ impl TryFrom<&types::PaymentsInitRouterData> for GenerateSaleRequest {
.ok_or_else(missing_field_err("order_details"))?
.product_name
.clone();
let pmd = item
.request
.payment_method_data
.to_owned()
.ok_or_else(missing_field_err("payment_method_data"))?;
Ok(Self {
currency: item.request.currency,
sale_type,
sale_price: item.request.amount,
transaction_id: item.payment_id.clone(),
product_name,
sale_return_url: item.request.get_return_url()?,
seller_payme_id,
sale_price: item.request.get_amount()?,
currency: item.request.get_currency()?,
product_name,
sale_payment_method: SalePaymentMethod::try_from(&pmd)?,
sale_type,
transaction_id: item.payment_id.clone(),
sale_return_url: item.request.get_return_url()?,
sale_callback_url: item.request.get_webhook_url()?,
sale_payment_method: SalePaymentMethod::try_from(&item.request.payment_method_data)?,
language: LANGUAGE.to_string(),
})
}
}
impl TryFrom<&types::PaymentsInitRouterData> for SaleType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(value: &types::PaymentsInitRouterData) -> Result<Self, Self::Error> {
let sale_type = if value.request.setup_mandate_details.is_some() {
// First mandate
Self::Token
} else {
// Normal payments
match value.request.is_auto_capture()? {
true => Self::Sale,
false => Self::Authorize,
}
};
Ok(sale_type)
}
}
impl TryFrom<&PaymentMethodData> for SalePaymentMethod {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &PaymentMethodData) -> Result<Self, Self::Error> {
match item {
PaymentMethodData::Card(_) => Ok(Self::CreditCard),
PaymentMethodData::Wallet(_)
| PaymentMethodData::PayLater(_)
PaymentMethodData::Wallet(wallet_data) => match wallet_data {
api_models::payments::WalletData::ApplePay(_) => Ok(Self::ApplePay),
api_models::payments::WalletData::AliPayQr(_)
| api_models::payments::WalletData::AliPayRedirect(_)
| api_models::payments::WalletData::AliPayHkRedirect(_)
| api_models::payments::WalletData::MomoRedirect(_)
| api_models::payments::WalletData::KakaoPayRedirect(_)
| api_models::payments::WalletData::GoPayRedirect(_)
| api_models::payments::WalletData::GcashRedirect(_)
| api_models::payments::WalletData::ApplePayRedirect(_)
| api_models::payments::WalletData::ApplePayThirdPartySdk(_)
| api_models::payments::WalletData::DanaRedirect {}
| api_models::payments::WalletData::GooglePay(_)
| api_models::payments::WalletData::GooglePayRedirect(_)
| api_models::payments::WalletData::GooglePayThirdPartySdk(_)
| api_models::payments::WalletData::MbWayRedirect(_)
| api_models::payments::WalletData::MobilePayRedirect(_)
| api_models::payments::WalletData::PaypalRedirect(_)
| api_models::payments::WalletData::PaypalSdk(_)
| api_models::payments::WalletData::SamsungPay(_)
| api_models::payments::WalletData::TwintRedirect {}
| api_models::payments::WalletData::VippsRedirect {}
| api_models::payments::WalletData::TouchNGoRedirect(_)
| api_models::payments::WalletData::WeChatPayRedirect(_)
| api_models::payments::WalletData::WeChatPayQr(_)
| api_models::payments::WalletData::CashappQr(_)
| api_models::payments::WalletData::SwishQr(_) => {
Err(errors::ConnectorError::NotSupported {
message: "Wallet".to_string(),
connector: "payme",
payment_experience: "Redirection".to_string(),
}
.into())
}
},
PaymentMethodData::PayLater(_)
| PaymentMethodData::BankRedirect(_)
| PaymentMethodData::BankDebit(_)
| PaymentMethodData::BankTransfer(_)
@ -288,6 +317,81 @@ impl TryFrom<&types::RefundSyncRouterData> for PaymeQueryTransactionRequest {
}
}
impl<F>
TryFrom<
types::ResponseRouterData<
F,
GenerateSaleResponse,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
>,
> for types::RouterData<F, types::PaymentsPreProcessingData, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
GenerateSaleResponse,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
let currency_code = item.data.request.get_currency()?;
let amount = item.data.request.get_amount()?;
let amount_in_base_unit = utils::to_currency_base_unit(amount, currency_code)?;
let pmd = item.data.request.payment_method_data.to_owned();
let session_token = match pmd {
Some(PaymentMethodData::Wallet(
api_models::payments::WalletData::ApplePayThirdPartySdk(_),
)) => Some(api_models::payments::SessionToken::ApplePay(Box::new(
api_models::payments::ApplepaySessionTokenResponse {
session_token_data:
api_models::payments::ApplePaySessionResponse::NoSessionResponse,
payment_request_data: Some(api_models::payments::ApplePayPaymentRequest {
country_code: item.data.get_billing_country()?,
currency_code,
total: api_models::payments::AmountInfo {
label: "Apple Pay".to_string(),
total_type: None,
amount: amount_in_base_unit,
},
merchant_capabilities: None,
supported_networks: None,
merchant_identifier: None,
}),
connector: "payme".to_string(),
delayed_session_token: true,
sdk_next_action: api_models::payments::SdkNextAction {
next_action: api_models::payments::NextActionCall::Sync,
},
connector_reference_id: Some(item.response.payme_sale_id.to_owned()),
connector_sdk_public_key: Some(
PaymeAuthType::try_from(&item.data.connector_auth_type)?
.payme_public_key
.expose(),
),
},
))),
_ => None,
};
Ok(Self {
// We don't get any status from payme, so defaulting it to pending
status: enums::AttemptStatus::Pending,
preprocessing_id: Some(item.response.payme_sale_id.to_owned()),
response: Ok(types::PaymentsResponseData::PreProcessingResponse {
pre_processing_id: types::PreprocessingResponseId::ConnectorTransactionId(
item.response.payme_sale_id,
),
connector_metadata: None,
session_token,
connector_response_reference_id: None,
}),
..item.data
})
}
}
impl TryFrom<&types::PaymentsAuthorizeRouterData> for MandateRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
@ -307,6 +411,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for MandateRequest {
seller_payme_id,
sale_callback_url: item.request.get_webhook_url()?,
buyer_key: Secret::new(item.request.get_connector_mandate_id()?),
language: LANGUAGE.to_string(),
})
}
}
@ -324,7 +429,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PayRequest {
};
let buyer_email = item.request.get_email()?;
let buyer_name = item.get_billing_address()?.get_full_name()?;
let payme_sale_id = item.request.related_transaction_id.clone().ok_or(
let payme_sale_id = item.preprocessing_id.to_owned().ok_or(
errors::ConnectorError::MissingConnectorRelatedTransactionID {
id: "payme_sale_id".to_string(),
},
@ -334,6 +439,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PayRequest {
buyer_email,
buyer_name,
payme_sale_id,
language: LANGUAGE.to_string(),
})
}
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
@ -344,7 +450,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PayRequest {
// Auth Struct
pub struct PaymeAuthType {
#[allow(dead_code)]
pub(super) payme_client_key: Secret<String>,
pub(super) payme_public_key: Secret<String>,
pub(super) seller_payme_id: Secret<String>,
}
@ -354,13 +460,30 @@ impl TryFrom<&types::ConnectorAuthType> for PaymeAuthType {
match auth_type {
types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self {
seller_payme_id: api_key.to_owned(),
payme_client_key: key1.to_owned(),
payme_public_key: key1.to_owned(),
}),
_ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
}
}
}
impl TryFrom<&types::PaymentsPreProcessingRouterData> for SaleType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(value: &types::PaymentsPreProcessingRouterData) -> Result<Self, Self::Error> {
let sale_type = if value.request.setup_mandate_details.is_some() {
// First mandate
Self::Token
} else {
// Normal payments
match value.request.is_auto_capture()? {
true => Self::Sale,
false => Self::Authorize,
}
};
Ok(sale_type)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum SaleStatus {
@ -420,44 +543,6 @@ pub struct PaymeMetadata {
payme_transaction_id: String,
}
impl<F>
TryFrom<
types::ResponseRouterData<
F,
GenerateSaleResponse,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
>,
> for types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
GenerateSaleResponse,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
Ok(Self {
status: enums::AttemptStatus::Authorizing,
request: types::PaymentsAuthorizeData {
related_transaction_id: Some(item.response.payme_sale_id.clone()),
..item.data.request
},
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.payme_sale_id),
redirection_data: None,
mandate_reference: None,
connector_metadata: None,
network_txn_id: None,
connector_response_reference_id: None,
}),
..item.data
})
}
}
#[derive(Debug, Serialize)]
pub struct PaymentCaptureRequest {
payme_sale_id: String,
@ -481,6 +566,7 @@ pub struct PaymeRefundRequest {
sale_refund_amount: i64,
payme_sale_id: String,
seller_payme_id: Secret<String>,
language: String,
}
impl<F> TryFrom<&types::RefundsRouterData<F>> for PaymeRefundRequest {
@ -491,6 +577,7 @@ impl<F> TryFrom<&types::RefundsRouterData<F>> for PaymeRefundRequest {
payme_sale_id: item.request.connector_transaction_id.clone(),
seller_payme_id: auth_type.seller_payme_id,
sale_refund_amount: item.request.refund_amount,
language: LANGUAGE.to_string(),
})
}
}
@ -579,9 +666,9 @@ impl<F, T>
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct PaymeErrorResponse {
pub status_code: u16,
pub code: String,
pub message: String,
pub reason: Option<String>,
pub status_error_details: String,
pub status_additional_info: String,
pub status_error_code: u16,
}
#[derive(Debug, Serialize, Deserialize)]

View File

@ -1048,8 +1048,10 @@ pub fn get_apple_pay_session<F, T>(
payment_request_data: Some(api_models::payments::ApplePayPaymentRequest {
country_code: apple_pay_init_result.country_code,
currency_code: apple_pay_init_result.currency_code,
supported_networks: apple_pay_init_result.supported_networks.clone(),
merchant_capabilities: apple_pay_init_result.merchant_capabilities.clone(),
supported_networks: Some(apple_pay_init_result.supported_networks.clone()),
merchant_capabilities: Some(
apple_pay_init_result.merchant_capabilities.clone(),
),
total: apple_pay_init_result.total.into(),
merchant_identifier: None,
}),
@ -1060,6 +1062,8 @@ pub fn get_apple_pay_session<F, T>(
next_action: api_models::payments::NextActionCall::Sync,
}
},
connector_reference_id: None,
connector_sdk_public_key: None,
},
))),
connector_response_reference_id: None,

View File

@ -193,6 +193,10 @@ pub trait PaymentsPreProcessingData {
fn get_payment_method_type(&self) -> Result<diesel_models::enums::PaymentMethodType, Error>;
fn get_currency(&self) -> Result<diesel_models::enums::Currency, Error>;
fn get_amount(&self) -> Result<i64, Error>;
fn is_auto_capture(&self) -> Result<bool, Error>;
fn get_order_details(&self) -> Result<Vec<OrderDetailsWithAmount>, Error>;
fn get_webhook_url(&self) -> Result<String, Error>;
fn get_return_url(&self) -> Result<String, Error>;
}
impl PaymentsPreProcessingData for types::PaymentsPreProcessingData {
@ -210,6 +214,28 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData {
fn get_amount(&self) -> Result<i64, Error> {
self.amount.ok_or_else(missing_field_err("amount"))
}
fn is_auto_capture(&self) -> Result<bool, Error> {
match self.capture_method {
Some(diesel_models::enums::CaptureMethod::Automatic) | None => Ok(true),
Some(diesel_models::enums::CaptureMethod::Manual) => Ok(false),
Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()),
}
}
fn get_order_details(&self) -> Result<Vec<OrderDetailsWithAmount>, Error> {
self.order_details
.clone()
.ok_or_else(missing_field_err("order_details"))
}
fn get_webhook_url(&self) -> Result<String, Error> {
self.webhook_url
.clone()
.ok_or_else(missing_field_err("webhook_url"))
}
fn get_return_url(&self) -> Result<String, Error> {
self.router_return_url
.clone()
.ok_or_else(missing_field_err("return_url"))
}
}
pub trait PaymentsAuthorizeRequestData {

View File

@ -843,7 +843,7 @@ async fn complete_preprocessing_steps_if_required<F, Req>(
state: &AppState,
connector: &api::ConnectorData,
payment_data: &PaymentData<F>,
router_data: types::RouterData<F, Req, types::PaymentsResponseData>,
mut router_data: types::RouterData<F, Req, types::PaymentsResponseData>,
should_continue_payment: bool,
) -> RouterResult<(types::RouterData<F, Req, types::PaymentsResponseData>, bool)>
where
@ -871,7 +871,7 @@ where
_ => (router_data, should_continue_payment),
},
Some(api_models::payments::PaymentMethodData::Wallet(_)) => {
if connector.connector_name.to_string() == *"trustpay" {
if is_preprocessing_required_for_wallets(connector.connector_name.to_string()) {
(
router_data.preprocessing_steps(state, connector).await?,
false,
@ -880,12 +880,27 @@ where
(router_data, should_continue_payment)
}
}
Some(api_models::payments::PaymentMethodData::Card(_)) => {
if connector.connector_name == types::Connector::Payme {
router_data = router_data.preprocessing_steps(state, connector).await?;
let is_error_in_response = router_data.response.is_err();
// If is_error_in_response is true, should_continue_payment should be false, we should throw the error
(router_data, !is_error_in_response)
} else {
(router_data, should_continue_payment)
}
}
_ => (router_data, should_continue_payment),
};
Ok(router_data_and_should_continue_payment)
}
pub fn is_preprocessing_required_for_wallets(connector_name: String) -> bool {
connector_name == *"trustpay" || connector_name == *"payme"
}
fn is_payment_method_tokenization_enabled_for_connector(
state: &AppState,
connector_name: &str,

View File

@ -745,7 +745,6 @@ default_imp_for_pre_processing_steps!(
connector::Opennode,
connector::Payeezy,
connector::Paypal,
connector::Payme,
connector::Payu,
connector::Powertranz,
connector::Rapyd,

View File

@ -360,6 +360,11 @@ impl TryFrom<types::PaymentsAuthorizeData> for types::PaymentsPreProcessingData
email: data.email,
currency: Some(data.currency),
payment_method_type: data.payment_method_type,
setup_mandate_details: data.setup_mandate_details,
capture_method: data.capture_method,
order_details: data.order_details,
router_return_url: data.router_return_url,
webhook_url: data.webhook_url,
})
}
}

View File

@ -192,14 +192,18 @@ async fn create_applepay_session_token(
})?,
currency_code: router_data.request.currency,
total: amount_info,
merchant_capabilities: applepay_metadata
.data
.payment_request_data
.merchant_capabilities,
supported_networks: applepay_metadata
.data
.payment_request_data
.supported_networks,
merchant_capabilities: Some(
applepay_metadata
.data
.payment_request_data
.merchant_capabilities,
),
supported_networks: Some(
applepay_metadata
.data
.payment_request_data
.supported_networks,
),
merchant_identifier: Some(
applepay_metadata
.data
@ -267,6 +271,8 @@ fn create_apple_pay_session_response(
connector: connector_name,
delayed_session_token: delayed_response,
sdk_next_action: { payment_types::SdkNextAction { next_action } },
connector_reference_id: None,
connector_sdk_public_key: None,
},
)),
}),

View File

@ -2449,7 +2449,7 @@ pub fn router_data_type_conversion<F1, F2, Req1, Req2, Res1, Res2>(
attempt_id: router_data.attempt_id,
access_token: router_data.access_token,
session_token: router_data.session_token,
reference_id: None,
reference_id: router_data.reference_id,
payment_method_token: router_data.payment_method_token,
customer_id: router_data.customer_id,
connector_customer: router_data.connector_customer,

View File

@ -614,7 +614,9 @@ where
payment_attempt
.connector
.as_ref()
.map(|connector| matches!(connector.as_str(), "trustpay"))
.map(|connector| {
matches!(connector.as_str(), "trustpay") || matches!(connector.as_str(), "payme")
})
.and_then(|is_connector_supports_third_party_sdk| {
if is_connector_supports_third_party_sdk {
payment_attempt
@ -1100,6 +1102,39 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsPreProce
fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
let payment_data = additional_data.payment_data;
let payment_method_data = payment_data.payment_method_data;
let router_base_url = &additional_data.router_base_url;
let attempt = &payment_data.payment_attempt;
let connector_name = &additional_data.connector_name;
let order_details = payment_data
.payment_intent
.order_details
.map(|order_details| {
order_details
.iter()
.map(|data| {
data.to_owned()
.parse_value("OrderDetailsWithAmount")
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "OrderDetailsWithAmount",
})
.attach_printable("Unable to parse OrderDetailsWithAmount")
})
.collect::<Result<Vec<_>, _>>()
})
.transpose()?;
let webhook_url = Some(helpers::create_webhook_url(
router_base_url,
&attempt.merchant_id,
connector_name,
));
let router_return_url = Some(helpers::create_redirect_url(
router_base_url,
attempt,
connector_name,
payment_data.creds_identifier.as_deref(),
));
Ok(Self {
payment_method_data,
@ -1107,6 +1142,11 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsPreProce
currency: Some(payment_data.currency),
amount: Some(payment_data.amount.into()),
payment_method_type: payment_data.payment_attempt.payment_method_type,
setup_mandate_details: payment_data.setup_mandate,
capture_method: payment_data.payment_attempt.capture_method,
order_details,
router_return_url,
webhook_url,
})
}
}

View File

@ -364,6 +364,11 @@ pub struct PaymentsPreProcessingData {
pub email: Option<Email>,
pub currency: Option<storage_enums::Currency>,
pub payment_method_type: Option<storage_enums::PaymentMethodType>,
pub setup_mandate_details: Option<payments::MandateData>,
pub capture_method: Option<storage_enums::CaptureMethod>,
pub order_details: Option<Vec<api_models::payments::OrderDetailsWithAmount>>,
pub router_return_url: Option<String>,
pub webhook_url: Option<String>,
}
#[derive(Debug, Clone)]