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:
Sakil Mostak
2023-08-01 18:27:56 +05:30
committed by GitHub
parent e3a33bb5c2
commit 801946f29f
9 changed files with 246 additions and 3 deletions

View File

@ -1187,7 +1187,7 @@ pub struct RewardData {
pub struct BoletoVoucherData { pub struct BoletoVoucherData {
/// The shopper's social security number /// The shopper's social security number
#[schema(value_type = Option<String>)] #[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)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
@ -1371,6 +1371,11 @@ pub enum NextActionData {
#[schema(value_type = String)] #[schema(value_type = String)]
image_data_url: Url, 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)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
@ -1382,6 +1387,14 @@ pub struct BankTransferNextStepsData {
pub receiver: ReceiverDetails, 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)] #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct QrCodeNextStepsInstruction { pub struct QrCodeNextStepsInstruction {
pub image_data_url: Url, pub image_data_url: Url,

View File

@ -770,6 +770,9 @@ pub enum StripeNextAction {
QrCodeInformation { QrCodeInformation {
image_data_url: url::Url, image_data_url: url::Url,
}, },
DisplayVoucherInformation {
voucher_details: payments::VoucherNextStepData,
},
} }
pub(crate) fn into_stripe_next_action( pub(crate) fn into_stripe_next_action(
@ -796,6 +799,9 @@ pub(crate) fn into_stripe_next_action(
payments::NextActionData::QrCodeInformation { image_data_url } => { payments::NextActionData::QrCodeInformation { image_data_url } => {
StripeNextAction::QrCodeInformation { image_data_url } StripeNextAction::QrCodeInformation { image_data_url }
} }
payments::NextActionData::DisplayVoucherInformation { voucher_details } => {
StripeNextAction::DisplayVoucherInformation { voucher_details }
}
}) })
} }

View File

@ -371,6 +371,9 @@ pub enum StripeNextAction {
QrCodeInformation { QrCodeInformation {
image_data_url: url::Url, image_data_url: url::Url,
}, },
DisplayVoucherInformation {
voucher_details: payments::VoucherNextStepData,
},
} }
pub(crate) fn into_stripe_next_action( pub(crate) fn into_stripe_next_action(
@ -397,6 +400,9 @@ pub(crate) fn into_stripe_next_action(
payments::NextActionData::QrCodeInformation { image_data_url } => { payments::NextActionData::QrCodeInformation { image_data_url } => {
StripeNextAction::QrCodeInformation { image_data_url } StripeNextAction::QrCodeInformation { image_data_url }
} }
payments::NextActionData::DisplayVoucherInformation { voucher_details } => {
StripeNextAction::DisplayVoucherInformation { voucher_details }
}
}) })
} }

View File

@ -122,6 +122,7 @@ pub struct AdyenPaymentRequest<'a> {
shopper_name: Option<ShopperName>, shopper_name: Option<ShopperName>,
shopper_locale: Option<String>, shopper_locale: Option<String>,
shopper_email: Option<Email>, shopper_email: Option<Email>,
social_security_number: Option<Secret<String>>,
telephone_number: Option<Secret<String>>, telephone_number: Option<Secret<String>>,
billing_address: Option<Address>, billing_address: Option<Address>,
delivery_address: Option<Address>, delivery_address: Option<Address>,
@ -155,6 +156,7 @@ pub enum AdyenStatus {
Received, Received,
RedirectShopper, RedirectShopper,
Refused, Refused,
PresentToShopper,
#[cfg(feature = "payouts")] #[cfg(feature = "payouts")]
#[serde(rename = "[payout-confirm-received]")] #[serde(rename = "[payout-confirm-received]")]
PayoutConfirmReceived, PayoutConfirmReceived,
@ -177,7 +179,7 @@ impl ForeignFrom<(bool, AdyenStatus)> for storage_enums::AttemptStatus {
fn foreign_from((is_manual_capture, adyen_status): (bool, AdyenStatus)) -> Self { fn foreign_from((is_manual_capture, adyen_status): (bool, AdyenStatus)) -> Self {
match adyen_status { match adyen_status {
AdyenStatus::AuthenticationFinished => Self::AuthenticationSuccessful, AdyenStatus::AuthenticationFinished => Self::AuthenticationSuccessful,
AdyenStatus::AuthenticationNotRequired => Self::Pending, AdyenStatus::AuthenticationNotRequired | AdyenStatus::PresentToShopper => Self::Pending,
AdyenStatus::Authorised => match is_manual_capture { AdyenStatus::Authorised => match is_manual_capture {
true => Self::Authorized, true => Self::Authorized,
// In case of Automatic capture Authorized is the final status of the payment // In case of Automatic capture Authorized is the final status of the payment
@ -236,6 +238,7 @@ pub struct AdyenThreeDS {
#[serde(untagged)] #[serde(untagged)]
pub enum AdyenPaymentResponse { pub enum AdyenPaymentResponse {
Response(Response), Response(Response),
PresentToShopper(AdyenPtsResponse),
NextActionResponse(NextActionResponse), NextActionResponse(NextActionResponse),
RedirectionErrorResponse(RedirectionErrorResponse), RedirectionErrorResponse(RedirectionErrorResponse),
} }
@ -268,6 +271,16 @@ pub struct NextActionResponse {
refusal_reason_code: Option<String>, 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)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AdyenNextAction { pub struct AdyenNextAction {
@ -281,6 +294,20 @@ pub struct AdyenNextAction {
qr_code_data: Option<String>, 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)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum ActionType { pub enum ActionType {
@ -288,6 +315,7 @@ pub enum ActionType {
Await, Await,
#[serde(rename = "qrCode")] #[serde(rename = "qrCode")]
QrCode, QrCode,
Voucher,
} }
#[derive(Default, Debug, Clone, Serialize, Deserialize)] #[derive(Default, Debug, Clone, Serialize, Deserialize)]
@ -313,6 +341,8 @@ pub enum AdyenPaymentMethod<'a> {
BancontactCard(Box<BancontactCardData>), BancontactCard(Box<BancontactCardData>),
Bizum(Box<BankRedirectionPMData>), Bizum(Box<BankRedirectionPMData>),
Blik(Box<BlikRedirectionData>), Blik(Box<BlikRedirectionData>),
#[serde(rename = "boletobancario")]
Boleto,
ClearPay(Box<AdyenPayLaterData>), ClearPay(Box<AdyenPayLaterData>),
Dana(Box<DanaWalletData>), Dana(Box<DanaWalletData>),
Eps(Box<BankRedirectionWithIssuer<'a>>), Eps(Box<BankRedirectionWithIssuer<'a>>),
@ -1047,6 +1077,9 @@ impl<'a> TryFrom<&types::PaymentsAuthorizeRouterData> for AdyenPaymentRequest<'a
api_models::payments::PaymentMethodData::BankDebit(ref bank_debit) => { api_models::payments::PaymentMethodData::BankDebit(ref bank_debit) => {
AdyenPaymentRequest::try_from((item, 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 { _ => Err(errors::ConnectorError::NotSupported {
message: format!("{:?}", item.request.payment_method_type), message: format!("{:?}", item.request.payment_method_type),
connector: "Adyen", 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> { impl<'a> TryFrom<&api_models::payments::BankDebitData> for AdyenPaymentMethod<'a> {
type Error = Error; type Error = Error;
fn try_from( 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> { impl<'a> TryFrom<&api::Card> for AdyenPaymentMethod<'a> {
type Error = Error; type Error = Error;
fn try_from(card: &api::Card) -> Result<Self, Self::Error> { fn try_from(card: &api::Card) -> Result<Self, Self::Error> {
@ -1731,6 +1783,7 @@ impl<'a>
shopper_name: None, shopper_name: None,
shopper_email: None, shopper_email: None,
shopper_locale: None, shopper_locale: None,
social_security_number: None,
billing_address: None, billing_address: None,
delivery_address: None, delivery_address: None,
country_code: None, country_code: None,
@ -1770,6 +1823,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::Card)> for AdyenPay
shopper_name: None, shopper_name: None,
shopper_email: None, shopper_email: None,
shopper_locale: None, shopper_locale: None,
social_security_number: None,
billing_address: None, billing_address: None,
delivery_address: None, delivery_address: None,
country_code: None, country_code: None,
@ -1818,6 +1872,7 @@ impl<'a>
shopper_name: None, shopper_name: None,
shopper_locale: None, shopper_locale: None,
shopper_email: item.request.email.clone(), shopper_email: item.request.email.clone(),
social_security_number: None,
telephone_number: None, telephone_number: None,
billing_address: None, billing_address: None,
delivery_address: None, delivery_address: None,
@ -1830,6 +1885,56 @@ impl<'a>
Ok(request) 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> impl<'a>
TryFrom<( TryFrom<(
@ -1871,6 +1976,7 @@ impl<'a>
shopper_name: None, shopper_name: None,
shopper_email: item.request.email.clone(), shopper_email: item.request.email.clone(),
shopper_locale, shopper_locale,
social_security_number: None,
billing_address: None, billing_address: None,
delivery_address: None, delivery_address: None,
country_code: country, country_code: country,
@ -1956,6 +2062,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::WalletData)>
shopper_name: None, shopper_name: None,
shopper_email, shopper_email,
shopper_locale: None, shopper_locale: None,
social_security_number: None,
billing_address: None, billing_address: None,
delivery_address: None, delivery_address: None,
country_code: None, country_code: None,
@ -2005,6 +2112,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::PayLaterData)>
shopper_name, shopper_name,
shopper_email, shopper_email,
shopper_locale: None, shopper_locale: None,
social_security_number: None,
billing_address, billing_address,
delivery_address, delivery_address,
country_code, country_code,
@ -2170,6 +2278,58 @@ pub fn get_next_action_response(
Ok((status, error, payments_response_data)) 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( pub fn get_redirection_error_response(
response: RedirectionErrorResponse, response: RedirectionErrorResponse,
is_manual_capture: bool, is_manual_capture: bool,
@ -2199,6 +2359,7 @@ pub fn get_redirection_error_response(
network_txn_id: None, network_txn_id: None,
connector_response_reference_id: None, connector_response_reference_id: None,
}; };
Ok((status, error, payments_response_data)) Ok((status, error, payments_response_data))
} }
@ -2257,6 +2418,9 @@ impl<F, Req>
let item = items.0; let item = items.0;
let is_manual_capture = items.1; let is_manual_capture = items.1;
let (status, error, payment_response_data) = match item.response { 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) => { AdyenPaymentResponse::Response(response) => {
get_adyen_response(response, is_manual_capture, item.http_code)? get_adyen_response(response, is_manual_capture, item.http_code)?
} }

View File

@ -429,7 +429,8 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize {
api_models::payments::NextActionData::RedirectToUrl { redirect_to_url } => Some(redirect_to_url), api_models::payments::NextActionData::RedirectToUrl { redirect_to_url } => Some(redirect_to_url),
api_models::payments::NextActionData::DisplayBankTransferInformation { .. } => None, api_models::payments::NextActionData::DisplayBankTransferInformation { .. } => None,
api_models::payments::NextActionData::ThirdPartySdkSessionToken { .. } => 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) .ok_or(errors::ApiErrorResponse::InternalServerError)
.into_report() .into_report()

View File

@ -1442,6 +1442,9 @@ pub(crate) fn validate_payment_method_fields_present(
) | ( ) | (
api_enums::PaymentMethod::Upi, api_enums::PaymentMethod::Upi,
api::PaymentMethodData::Upi(..) api::PaymentMethodData::Upi(..)
) | (
api_enums::PaymentMethod::Voucher,
api::PaymentMethodData::Voucher(..)
) )
) | None ) | None
), ),

View File

@ -373,11 +373,14 @@ where
let bank_transfer_next_steps = let bank_transfer_next_steps =
bank_transfer_next_steps_check(payment_attempt.clone())?; 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 = let next_action_containing_qr_code =
qr_code_next_steps_check(payment_attempt.clone())?; qr_code_next_steps_check(payment_attempt.clone())?;
if payment_intent.status == enums::IntentStatus::RequiresCustomerAction if payment_intent.status == enums::IntentStatus::RequiresCustomerAction
|| bank_transfer_next_steps.is_some() || bank_transfer_next_steps.is_some()
|| next_action_voucher.is_some()
|| next_action_containing_qr_code.is_some() || next_action_containing_qr_code.is_some()
{ {
next_action_response = bank_transfer_next_steps next_action_response = bank_transfer_next_steps
@ -386,6 +389,11 @@ where
bank_transfer_steps_and_charges_details: bank_transfer, 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| { .or(next_action_containing_qr_code.map(|qr_code_data| {
api_models::payments::NextActionData::QrCodeInformation { api_models::payments::NextActionData::QrCodeInformation {
image_data_url: qr_code_data.image_data_url, 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) 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( pub fn change_order_details_to_new_type(
order_amount: i64, order_amount: i64,
order_details: api_models::payments::OrderDetails, order_details: api_models::payments::OrderDetails,

View File

@ -196,6 +196,7 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::payments::VoucherData, api_models::payments::VoucherData,
api_models::payments::BoletoVoucherData, api_models::payments::BoletoVoucherData,
api_models::payments::Address, api_models::payments::Address,
api_models::payments::VoucherData,
api_models::payments::BankRedirectData, api_models::payments::BankRedirectData,
api_models::payments::BankRedirectBilling, api_models::payments::BankRedirectBilling,
api_models::payments::BankRedirectBilling, api_models::payments::BankRedirectBilling,

View File

@ -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": { "discriminator": {