mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 19:42:27 +08:00
feat(connector): [Adyen] Implement Boleto Bancario in Vouchers and Add support for Voucher in Next Action (#1657)
Co-authored-by: Pa1NarK <69745008+pixincreate@users.noreply.github.com>
This commit is contained in:
@ -1187,7 +1187,7 @@ pub struct RewardData {
|
||||
pub struct BoletoVoucherData {
|
||||
/// The shopper's social security number
|
||||
#[schema(value_type = Option<String>)]
|
||||
social_security_number: Option<Secret<String>>,
|
||||
pub social_security_number: Option<Secret<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
@ -1371,6 +1371,11 @@ pub enum NextActionData {
|
||||
#[schema(value_type = String)]
|
||||
image_data_url: Url,
|
||||
},
|
||||
/// Contains the download url and the reference number for transaction
|
||||
DisplayVoucherInformation {
|
||||
#[schema(value_type = String)]
|
||||
voucher_details: VoucherNextStepData,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
@ -1382,6 +1387,14 @@ pub struct BankTransferNextStepsData {
|
||||
pub receiver: ReceiverDetails,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct VoucherNextStepData {
|
||||
/// Reference number required for the transaction
|
||||
pub reference: String,
|
||||
/// Url to download the payment instruction
|
||||
pub download_url: Option<Url>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct QrCodeNextStepsInstruction {
|
||||
pub image_data_url: Url,
|
||||
|
||||
@ -770,6 +770,9 @@ pub enum StripeNextAction {
|
||||
QrCodeInformation {
|
||||
image_data_url: url::Url,
|
||||
},
|
||||
DisplayVoucherInformation {
|
||||
voucher_details: payments::VoucherNextStepData,
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) fn into_stripe_next_action(
|
||||
@ -796,6 +799,9 @@ pub(crate) fn into_stripe_next_action(
|
||||
payments::NextActionData::QrCodeInformation { image_data_url } => {
|
||||
StripeNextAction::QrCodeInformation { image_data_url }
|
||||
}
|
||||
payments::NextActionData::DisplayVoucherInformation { voucher_details } => {
|
||||
StripeNextAction::DisplayVoucherInformation { voucher_details }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -371,6 +371,9 @@ pub enum StripeNextAction {
|
||||
QrCodeInformation {
|
||||
image_data_url: url::Url,
|
||||
},
|
||||
DisplayVoucherInformation {
|
||||
voucher_details: payments::VoucherNextStepData,
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) fn into_stripe_next_action(
|
||||
@ -397,6 +400,9 @@ pub(crate) fn into_stripe_next_action(
|
||||
payments::NextActionData::QrCodeInformation { image_data_url } => {
|
||||
StripeNextAction::QrCodeInformation { image_data_url }
|
||||
}
|
||||
payments::NextActionData::DisplayVoucherInformation { voucher_details } => {
|
||||
StripeNextAction::DisplayVoucherInformation { voucher_details }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -122,6 +122,7 @@ pub struct AdyenPaymentRequest<'a> {
|
||||
shopper_name: Option<ShopperName>,
|
||||
shopper_locale: Option<String>,
|
||||
shopper_email: Option<Email>,
|
||||
social_security_number: Option<Secret<String>>,
|
||||
telephone_number: Option<Secret<String>>,
|
||||
billing_address: Option<Address>,
|
||||
delivery_address: Option<Address>,
|
||||
@ -155,6 +156,7 @@ pub enum AdyenStatus {
|
||||
Received,
|
||||
RedirectShopper,
|
||||
Refused,
|
||||
PresentToShopper,
|
||||
#[cfg(feature = "payouts")]
|
||||
#[serde(rename = "[payout-confirm-received]")]
|
||||
PayoutConfirmReceived,
|
||||
@ -177,7 +179,7 @@ impl ForeignFrom<(bool, AdyenStatus)> for storage_enums::AttemptStatus {
|
||||
fn foreign_from((is_manual_capture, adyen_status): (bool, AdyenStatus)) -> Self {
|
||||
match adyen_status {
|
||||
AdyenStatus::AuthenticationFinished => Self::AuthenticationSuccessful,
|
||||
AdyenStatus::AuthenticationNotRequired => Self::Pending,
|
||||
AdyenStatus::AuthenticationNotRequired | AdyenStatus::PresentToShopper => Self::Pending,
|
||||
AdyenStatus::Authorised => match is_manual_capture {
|
||||
true => Self::Authorized,
|
||||
// In case of Automatic capture Authorized is the final status of the payment
|
||||
@ -236,6 +238,7 @@ pub struct AdyenThreeDS {
|
||||
#[serde(untagged)]
|
||||
pub enum AdyenPaymentResponse {
|
||||
Response(Response),
|
||||
PresentToShopper(AdyenPtsResponse),
|
||||
NextActionResponse(NextActionResponse),
|
||||
RedirectionErrorResponse(RedirectionErrorResponse),
|
||||
}
|
||||
@ -268,6 +271,16 @@ pub struct NextActionResponse {
|
||||
refusal_reason_code: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AdyenPtsResponse {
|
||||
psp_reference: String,
|
||||
result_code: AdyenStatus,
|
||||
action: AdyenPtsAction,
|
||||
refusal_reason: Option<String>,
|
||||
refusal_reason_code: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AdyenNextAction {
|
||||
@ -281,6 +294,20 @@ pub struct AdyenNextAction {
|
||||
qr_code_data: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AdyenPtsAction {
|
||||
reference: String,
|
||||
download_url: Option<Url>,
|
||||
payment_method_type: Option<String>,
|
||||
expires_at: Option<String>,
|
||||
initial_amount: Option<Amount>,
|
||||
pass_creation_token: Option<String>,
|
||||
total_amount: Option<Amount>,
|
||||
#[serde(rename = "type")]
|
||||
type_of_response: Option<ActionType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ActionType {
|
||||
@ -288,6 +315,7 @@ pub enum ActionType {
|
||||
Await,
|
||||
#[serde(rename = "qrCode")]
|
||||
QrCode,
|
||||
Voucher,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
@ -313,6 +341,8 @@ pub enum AdyenPaymentMethod<'a> {
|
||||
BancontactCard(Box<BancontactCardData>),
|
||||
Bizum(Box<BankRedirectionPMData>),
|
||||
Blik(Box<BlikRedirectionData>),
|
||||
#[serde(rename = "boletobancario")]
|
||||
Boleto,
|
||||
ClearPay(Box<AdyenPayLaterData>),
|
||||
Dana(Box<DanaWalletData>),
|
||||
Eps(Box<BankRedirectionWithIssuer<'a>>),
|
||||
@ -1047,6 +1077,9 @@ impl<'a> TryFrom<&types::PaymentsAuthorizeRouterData> for AdyenPaymentRequest<'a
|
||||
api_models::payments::PaymentMethodData::BankDebit(ref bank_debit) => {
|
||||
AdyenPaymentRequest::try_from((item, bank_debit))
|
||||
}
|
||||
api_models::payments::PaymentMethodData::Voucher(ref voucher_data) => {
|
||||
AdyenPaymentRequest::try_from((item, voucher_data))
|
||||
}
|
||||
_ => Err(errors::ConnectorError::NotSupported {
|
||||
message: format!("{:?}", item.request.payment_method_type),
|
||||
connector: "Adyen",
|
||||
@ -1230,6 +1263,15 @@ fn get_payout_card_details(payout_method_data: &PayoutMethodData) -> Option<Payo
|
||||
}
|
||||
}
|
||||
|
||||
fn get_social_security_number(
|
||||
voucher_data: &api_models::payments::VoucherData,
|
||||
) -> Option<Secret<String>> {
|
||||
match voucher_data {
|
||||
payments::VoucherData::Boleto(boleto_data) => boleto_data.social_security_number.clone(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&api_models::payments::BankDebitData> for AdyenPaymentMethod<'a> {
|
||||
type Error = Error;
|
||||
fn try_from(
|
||||
@ -1289,6 +1331,16 @@ impl<'a> TryFrom<&api_models::payments::BankDebitData> for AdyenPaymentMethod<'a
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&api_models::payments::VoucherData> for AdyenPaymentMethod<'a> {
|
||||
type Error = Error;
|
||||
fn try_from(voucher_data: &api_models::payments::VoucherData) -> Result<Self, Self::Error> {
|
||||
match voucher_data {
|
||||
payments::VoucherData::Boleto { .. } => Ok(AdyenPaymentMethod::Boleto),
|
||||
_ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&api::Card> for AdyenPaymentMethod<'a> {
|
||||
type Error = Error;
|
||||
fn try_from(card: &api::Card) -> Result<Self, Self::Error> {
|
||||
@ -1731,6 +1783,7 @@ impl<'a>
|
||||
shopper_name: None,
|
||||
shopper_email: None,
|
||||
shopper_locale: None,
|
||||
social_security_number: None,
|
||||
billing_address: None,
|
||||
delivery_address: None,
|
||||
country_code: None,
|
||||
@ -1770,6 +1823,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::Card)> for AdyenPay
|
||||
shopper_name: None,
|
||||
shopper_email: None,
|
||||
shopper_locale: None,
|
||||
social_security_number: None,
|
||||
billing_address: None,
|
||||
delivery_address: None,
|
||||
country_code: None,
|
||||
@ -1818,6 +1872,7 @@ impl<'a>
|
||||
shopper_name: None,
|
||||
shopper_locale: None,
|
||||
shopper_email: item.request.email.clone(),
|
||||
social_security_number: None,
|
||||
telephone_number: None,
|
||||
billing_address: None,
|
||||
delivery_address: None,
|
||||
@ -1830,6 +1885,56 @@ impl<'a>
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
impl<'a>
|
||||
TryFrom<(
|
||||
&types::PaymentsAuthorizeRouterData,
|
||||
&api_models::payments::VoucherData,
|
||||
)> for AdyenPaymentRequest<'a>
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(
|
||||
value: (
|
||||
&types::PaymentsAuthorizeRouterData,
|
||||
&api_models::payments::VoucherData,
|
||||
),
|
||||
) -> Result<Self, Self::Error> {
|
||||
let (item, voucher_data) = value;
|
||||
let amount = get_amount_data(item);
|
||||
let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?;
|
||||
let shopper_interaction = AdyenShopperInteraction::from(item);
|
||||
let recurring_processing_model = get_recurring_processing_model(item)?.0;
|
||||
let browser_info = get_browser_info(item)?;
|
||||
let additional_data = get_additional_data(item);
|
||||
let payment_method = AdyenPaymentMethod::try_from(voucher_data)?;
|
||||
let return_url = item.request.get_return_url()?;
|
||||
let social_security_number = get_social_security_number(voucher_data);
|
||||
let request = AdyenPaymentRequest {
|
||||
amount,
|
||||
merchant_account: auth_type.merchant_account,
|
||||
payment_method,
|
||||
reference: item.payment_id.to_string(),
|
||||
return_url,
|
||||
browser_info,
|
||||
shopper_interaction,
|
||||
recurring_processing_model,
|
||||
additional_data,
|
||||
shopper_name: None,
|
||||
shopper_locale: None,
|
||||
shopper_email: item.request.email.clone(),
|
||||
social_security_number,
|
||||
telephone_number: None,
|
||||
billing_address: None,
|
||||
delivery_address: None,
|
||||
country_code: None,
|
||||
line_items: None,
|
||||
shopper_reference: None,
|
||||
store_payment_method: None,
|
||||
channel: None,
|
||||
};
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a>
|
||||
TryFrom<(
|
||||
@ -1871,6 +1976,7 @@ impl<'a>
|
||||
shopper_name: None,
|
||||
shopper_email: item.request.email.clone(),
|
||||
shopper_locale,
|
||||
social_security_number: None,
|
||||
billing_address: None,
|
||||
delivery_address: None,
|
||||
country_code: country,
|
||||
@ -1956,6 +2062,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::WalletData)>
|
||||
shopper_name: None,
|
||||
shopper_email,
|
||||
shopper_locale: None,
|
||||
social_security_number: None,
|
||||
billing_address: None,
|
||||
delivery_address: None,
|
||||
country_code: None,
|
||||
@ -2005,6 +2112,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::PayLaterData)>
|
||||
shopper_name,
|
||||
shopper_email,
|
||||
shopper_locale: None,
|
||||
social_security_number: None,
|
||||
billing_address,
|
||||
delivery_address,
|
||||
country_code,
|
||||
@ -2170,6 +2278,58 @@ pub fn get_next_action_response(
|
||||
Ok((status, error, payments_response_data))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AdyenMetaData {
|
||||
download_url: Option<Url>,
|
||||
reference: String,
|
||||
}
|
||||
|
||||
pub fn get_present_to_shopper_response(
|
||||
response: AdyenPtsResponse,
|
||||
is_manual_capture: bool,
|
||||
status_code: u16,
|
||||
) -> errors::CustomResult<
|
||||
(
|
||||
storage_enums::AttemptStatus,
|
||||
Option<types::ErrorResponse>,
|
||||
types::PaymentsResponseData,
|
||||
),
|
||||
errors::ConnectorError,
|
||||
> {
|
||||
let status =
|
||||
storage_enums::AttemptStatus::foreign_from((is_manual_capture, response.result_code));
|
||||
let error = if response.refusal_reason.is_some() || response.refusal_reason_code.is_some() {
|
||||
Some(types::ErrorResponse {
|
||||
code: response
|
||||
.refusal_reason_code
|
||||
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
|
||||
message: response
|
||||
.refusal_reason
|
||||
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
|
||||
reason: None,
|
||||
status_code,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let metadata = serde_json::json!(AdyenMetaData {
|
||||
download_url: response.action.download_url,
|
||||
reference: response.action.reference,
|
||||
});
|
||||
|
||||
let payments_response_data = types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(response.psp_reference),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: Some(metadata),
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
};
|
||||
|
||||
Ok((status, error, payments_response_data))
|
||||
}
|
||||
|
||||
pub fn get_redirection_error_response(
|
||||
response: RedirectionErrorResponse,
|
||||
is_manual_capture: bool,
|
||||
@ -2199,6 +2359,7 @@ pub fn get_redirection_error_response(
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
};
|
||||
|
||||
Ok((status, error, payments_response_data))
|
||||
}
|
||||
|
||||
@ -2257,6 +2418,9 @@ impl<F, Req>
|
||||
let item = items.0;
|
||||
let is_manual_capture = items.1;
|
||||
let (status, error, payment_response_data) = match item.response {
|
||||
AdyenPaymentResponse::PresentToShopper(response) => {
|
||||
get_present_to_shopper_response(response, is_manual_capture, item.http_code)?
|
||||
}
|
||||
AdyenPaymentResponse::Response(response) => {
|
||||
get_adyen_response(response, is_manual_capture, item.http_code)?
|
||||
}
|
||||
|
||||
@ -429,7 +429,8 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize {
|
||||
api_models::payments::NextActionData::RedirectToUrl { redirect_to_url } => Some(redirect_to_url),
|
||||
api_models::payments::NextActionData::DisplayBankTransferInformation { .. } => None,
|
||||
api_models::payments::NextActionData::ThirdPartySdkSessionToken { .. } => None,
|
||||
api_models::payments::NextActionData::QrCodeInformation{..} => None
|
||||
api_models::payments::NextActionData::QrCodeInformation{..} => None,
|
||||
api_models::payments::NextActionData::DisplayVoucherInformation{ .. } => None,
|
||||
})
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||
.into_report()
|
||||
|
||||
@ -1442,6 +1442,9 @@ pub(crate) fn validate_payment_method_fields_present(
|
||||
) | (
|
||||
api_enums::PaymentMethod::Upi,
|
||||
api::PaymentMethodData::Upi(..)
|
||||
) | (
|
||||
api_enums::PaymentMethod::Voucher,
|
||||
api::PaymentMethodData::Voucher(..)
|
||||
)
|
||||
) | None
|
||||
),
|
||||
|
||||
@ -373,11 +373,14 @@ where
|
||||
let bank_transfer_next_steps =
|
||||
bank_transfer_next_steps_check(payment_attempt.clone())?;
|
||||
|
||||
let next_action_voucher = voucher_next_steps_check(payment_attempt.clone())?;
|
||||
|
||||
let next_action_containing_qr_code =
|
||||
qr_code_next_steps_check(payment_attempt.clone())?;
|
||||
|
||||
if payment_intent.status == enums::IntentStatus::RequiresCustomerAction
|
||||
|| bank_transfer_next_steps.is_some()
|
||||
|| next_action_voucher.is_some()
|
||||
|| next_action_containing_qr_code.is_some()
|
||||
{
|
||||
next_action_response = bank_transfer_next_steps
|
||||
@ -386,6 +389,11 @@ where
|
||||
bank_transfer_steps_and_charges_details: bank_transfer,
|
||||
}
|
||||
})
|
||||
.or(next_action_voucher.map(|voucher_data| {
|
||||
api_models::payments::NextActionData::DisplayVoucherInformation {
|
||||
voucher_details: voucher_data,
|
||||
}
|
||||
}))
|
||||
.or(next_action_containing_qr_code.map(|qr_code_data| {
|
||||
api_models::payments::NextActionData::QrCodeInformation {
|
||||
image_data_url: qr_code_data.image_data_url,
|
||||
@ -686,6 +694,28 @@ pub fn bank_transfer_next_steps_check(
|
||||
Ok(bank_transfer_next_step)
|
||||
}
|
||||
|
||||
pub fn voucher_next_steps_check(
|
||||
payment_attempt: storage::PaymentAttempt,
|
||||
) -> RouterResult<Option<api_models::payments::VoucherNextStepData>> {
|
||||
let voucher_next_step = if let Some(diesel_models::enums::PaymentMethod::Voucher) =
|
||||
payment_attempt.payment_method
|
||||
{
|
||||
let voucher_next_steps: Option<api_models::payments::VoucherNextStepData> = payment_attempt
|
||||
.connector_metadata
|
||||
.map(|metadata| {
|
||||
metadata
|
||||
.parse_value("NextStepsRequirements")
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to parse the Value to NextRequirements struct")
|
||||
})
|
||||
.transpose()?;
|
||||
voucher_next_steps
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(voucher_next_step)
|
||||
}
|
||||
|
||||
pub fn change_order_details_to_new_type(
|
||||
order_amount: i64,
|
||||
order_details: api_models::payments::OrderDetails,
|
||||
|
||||
@ -196,6 +196,7 @@ Never share your secret api keys. Keep them guarded and secure.
|
||||
api_models::payments::VoucherData,
|
||||
api_models::payments::BoletoVoucherData,
|
||||
api_models::payments::Address,
|
||||
api_models::payments::VoucherData,
|
||||
api_models::payments::BankRedirectData,
|
||||
api_models::payments::BankRedirectBilling,
|
||||
api_models::payments::BankRedirectBilling,
|
||||
|
||||
@ -6376,6 +6376,25 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"description": "Contains the download url and the reference number for transaction",
|
||||
"required": [
|
||||
"voucher_details",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"voucher_details": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"display_voucher_information"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"discriminator": {
|
||||
|
||||
Reference in New Issue
Block a user