mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 17:47:54 +08:00
feat(connector): [Bambora APAC] add mandate flow (#5376)
This commit is contained in:
@ -186,7 +186,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/"
|
||||
applepay.base_url = "https://apple-pay-gateway.apple.com/"
|
||||
authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api"
|
||||
bambora.base_url = "https://api.na.bambora.com"
|
||||
bamboraapac.base_url = "https://demo.bambora.co.nz/interface/api/dts.asmx"
|
||||
bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api"
|
||||
bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/"
|
||||
billwerk.base_url = "https://api.reepay.com/"
|
||||
billwerk.secondary_base_url = "https://card.reepay.com/"
|
||||
|
||||
@ -26,7 +26,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/"
|
||||
applepay.base_url = "https://apple-pay-gateway.apple.com/"
|
||||
authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api"
|
||||
bambora.base_url = "https://api.na.bambora.com"
|
||||
bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api/dts.asmx"
|
||||
bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api"
|
||||
bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/"
|
||||
billwerk.base_url = "https://api.reepay.com/"
|
||||
billwerk.secondary_base_url = "https://card.reepay.com/"
|
||||
|
||||
@ -30,7 +30,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/"
|
||||
applepay.base_url = "https://apple-pay-gateway.apple.com/"
|
||||
authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api"
|
||||
bambora.base_url = "https://api.na.bambora.com"
|
||||
bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api/dts.asmx"
|
||||
bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api"
|
||||
bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/"
|
||||
billwerk.base_url = "https://api.reepay.com/"
|
||||
billwerk.secondary_base_url = "https://card.reepay.com/"
|
||||
|
||||
@ -182,7 +182,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/"
|
||||
applepay.base_url = "https://apple-pay-gateway.apple.com/"
|
||||
authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api"
|
||||
bambora.base_url = "https://api.na.bambora.com"
|
||||
bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api/dts.asmx"
|
||||
bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api"
|
||||
bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/"
|
||||
billwerk.base_url = "https://api.reepay.com/"
|
||||
billwerk.secondary_base_url = "https://card.reepay.com/"
|
||||
|
||||
@ -115,7 +115,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/"
|
||||
applepay.base_url = "https://apple-pay-gateway.apple.com/"
|
||||
authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api"
|
||||
bambora.base_url = "https://api.na.bambora.com"
|
||||
bamboraapac.base_url = "https://demo.bambora.co.nz/interface/api/dts.asmx"
|
||||
bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api"
|
||||
bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/"
|
||||
billwerk.base_url = "https://api.reepay.com/"
|
||||
billwerk.secondary_base_url = "https://card.reepay.com/"
|
||||
|
||||
@ -92,6 +92,22 @@ impl ConnectorValidation for Bamboraapac {
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_mandate_payment(
|
||||
&self,
|
||||
_pm_type: Option<enums::PaymentMethodType>,
|
||||
pm_data: types::domain::payments::PaymentMethodData,
|
||||
) -> CustomResult<(), errors::ConnectorError> {
|
||||
let connector = self.id();
|
||||
match pm_data {
|
||||
types::domain::payments::PaymentMethodData::Card(_) => Ok(()),
|
||||
_ => Err(errors::ConnectorError::NotSupported {
|
||||
message: "mandate payment".to_string(),
|
||||
connector,
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorCommon for Bamboraapac {
|
||||
@ -167,6 +183,85 @@ impl
|
||||
types::PaymentsResponseData,
|
||||
> for Bamboraapac
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::SetupMandateRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::SetupMandateRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!("{}/sipp.asmx", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::SetupMandateRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<RequestContent, errors::ConnectorError> {
|
||||
let connector_req = bamboraapac::get_setup_mandate_body(req)?;
|
||||
|
||||
Ok(RequestContent::RawBytes(connector_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::SetupMandateRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::SetupMandateType::get_url(self, req, connectors)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::SetupMandateType::get_headers(self, req, connectors)?)
|
||||
.set_body(types::SetupMandateType::get_request_body(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::SetupMandateRouterData,
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
res: Response,
|
||||
) -> CustomResult<types::SetupMandateRouterData, errors::ConnectorError> {
|
||||
let response_data = html_to_xml_string_conversion(
|
||||
String::from_utf8(res.response.to_vec())
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?,
|
||||
);
|
||||
|
||||
let response = response_data
|
||||
.parse_xml::<bamboraapac::BamboraapacMandateResponse>()
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
event_builder.map(|i| i.set_response_body(&response));
|
||||
router_env::logger::info!(connector_response=?response);
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res, event_builder)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
||||
@ -189,7 +284,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
|
||||
_req: &types::PaymentsAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(self.base_url(connectors).to_string())
|
||||
Ok(format!("{}/dts.asmx", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
@ -284,7 +379,7 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
|
||||
_req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(self.base_url(connectors).to_string())
|
||||
Ok(format!("{}/dts.asmx", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
@ -368,7 +463,7 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
|
||||
_req: &types::PaymentsCaptureRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(self.base_url(connectors).to_string())
|
||||
Ok(format!("{}/dts.asmx", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
@ -467,7 +562,7 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
|
||||
_req: &types::RefundExecuteRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(self.base_url(connectors).to_string())
|
||||
Ok(format!("{}/dts.asmx", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
@ -560,7 +655,7 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
|
||||
_req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(self.base_url(connectors).to_string())
|
||||
Ok(format!("{}/dts.asmx", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
|
||||
@ -5,7 +5,7 @@ use masking::{PeekInterface, Secret};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connector::utils::{self, CardData, RouterData},
|
||||
connector::utils::{self, CardData, PaymentsAuthorizeRequestData, RouterData},
|
||||
core::errors,
|
||||
types::{self, domain, storage::enums, transformers::ForeignFrom},
|
||||
};
|
||||
@ -94,6 +94,25 @@ fn get_card_data(req: &types::PaymentsAuthorizeRouterData) -> Result<String, Err
|
||||
let card_holder_name = req.get_billing_full_name()?;
|
||||
let card_data = match &req.request.payment_method_data {
|
||||
domain::PaymentMethodData::Card(card) => {
|
||||
if req.request.setup_future_usage == Some(enums::FutureUsage::OffSession) {
|
||||
format!(
|
||||
r#"
|
||||
<CreditCard Registered="False">
|
||||
<TokeniseAlgorithmID>2</TokeniseAlgorithmID>
|
||||
<CardNumber>{}</CardNumber>
|
||||
<ExpM>{}</ExpM>
|
||||
<ExpY>{}</ExpY>
|
||||
<CVN>{}</CVN>
|
||||
<CardHolderName>{}</CardHolderName>
|
||||
</CreditCard>
|
||||
"#,
|
||||
card.card_number.get_card_no(),
|
||||
card.card_exp_month.peek(),
|
||||
card.get_expiry_year_4_digit().peek(),
|
||||
card.card_cvc.peek(),
|
||||
card_holder_name.peek(),
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
r#"
|
||||
<CreditCard Registered="False">
|
||||
@ -111,6 +130,18 @@ fn get_card_data(req: &types::PaymentsAuthorizeRouterData) -> Result<String, Err
|
||||
card_holder_name.peek(),
|
||||
)
|
||||
}
|
||||
}
|
||||
domain::PaymentMethodData::MandatePayment => {
|
||||
format!(
|
||||
r#"
|
||||
<CreditCard>
|
||||
<TokeniseAlgorithmID>2</TokeniseAlgorithmID>
|
||||
<CardNumber>{}</CardNumber>
|
||||
</CreditCard>
|
||||
"#,
|
||||
req.request.get_connector_mandate_id()?
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
return Err(errors::ConnectorError::NotImplemented(
|
||||
utils::get_unimplemented_payment_method_error_message("Bambora APAC"),
|
||||
@ -182,6 +213,7 @@ pub struct SubmitSinglePaymentResult {
|
||||
pub struct PaymentResponse {
|
||||
response_code: u8,
|
||||
receipt: String,
|
||||
credit_card_token: Option<String>,
|
||||
declined_code: Option<String>,
|
||||
declined_message: Option<String>,
|
||||
}
|
||||
@ -234,6 +266,23 @@ impl<F>
|
||||
.submit_single_payment_result
|
||||
.response
|
||||
.receipt;
|
||||
|
||||
let mandate_reference =
|
||||
if item.data.request.setup_future_usage == Some(enums::FutureUsage::OffSession) {
|
||||
let connector_mandate_id = item
|
||||
.response
|
||||
.body
|
||||
.submit_single_payment_response
|
||||
.submit_single_payment_result
|
||||
.response
|
||||
.credit_card_token;
|
||||
Some(types::MandateReference {
|
||||
connector_mandate_id,
|
||||
payment_method_id: None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// transaction approved
|
||||
if response_code == 0 {
|
||||
Ok(Self {
|
||||
@ -243,7 +292,7 @@ impl<F>
|
||||
connector_transaction_id.to_owned(),
|
||||
),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
mandate_reference,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: Some(connector_transaction_id),
|
||||
@ -288,6 +337,159 @@ impl<F>
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_setup_mandate_body(req: &types::SetupMandateRouterData) -> Result<Vec<u8>, Error> {
|
||||
let card_holder_name = req.get_billing_full_name()?;
|
||||
let auth_details = BamboraapacAuthType::try_from(&req.connector_auth_type)?;
|
||||
let body = match &req.request.payment_method_data {
|
||||
domain::PaymentMethodData::Card(card) => {
|
||||
format!(
|
||||
r#"
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
||||
xmlns:sipp="http://www.ippayments.com.au/interface/api/sipp">
|
||||
<soapenv:Header/>
|
||||
<soapenv:Body>
|
||||
<sipp:TokeniseCreditCard>
|
||||
<sipp:tokeniseCreditCardXML>
|
||||
<![CDATA[
|
||||
<TokeniseCreditCard>
|
||||
<CardNumber>{}</CardNumber>
|
||||
<ExpM>{}</ExpM>
|
||||
<ExpY>{}</ExpY>
|
||||
<CardHolderName>{}</CardHolderName>
|
||||
<TokeniseAlgorithmID>2</TokeniseAlgorithmID>
|
||||
<UserName>{}</UserName>
|
||||
<Password>{}</Password>
|
||||
</TokeniseCreditCard>
|
||||
]]>
|
||||
</sipp:tokeniseCreditCardXML>
|
||||
</sipp:TokeniseCreditCard>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
"#,
|
||||
card.card_number.get_card_no(),
|
||||
card.card_exp_month.peek(),
|
||||
card.get_expiry_year_4_digit().peek(),
|
||||
card_holder_name.peek(),
|
||||
auth_details.username.peek(),
|
||||
auth_details.password.peek(),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
return Err(errors::ConnectorError::NotImplemented(
|
||||
utils::get_unimplemented_payment_method_error_message("Bambora APAC"),
|
||||
))?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(body.as_bytes().to_vec())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename = "Envelope")]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct BamboraapacMandateResponse {
|
||||
body: MandateBodyResponse,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct MandateBodyResponse {
|
||||
tokenise_credit_card_response: TokeniseCreditCardResponse,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct TokeniseCreditCardResponse {
|
||||
tokenise_credit_card_result: TokeniseCreditCardResult,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct TokeniseCreditCardResult {
|
||||
tokenise_credit_card_response: MandateResponseBody,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct MandateResponseBody {
|
||||
return_value: u8,
|
||||
token: Option<String>,
|
||||
}
|
||||
|
||||
impl<F>
|
||||
TryFrom<
|
||||
types::ResponseRouterData<
|
||||
F,
|
||||
BamboraapacMandateResponse,
|
||||
types::SetupMandateRequestData,
|
||||
types::PaymentsResponseData,
|
||||
>,
|
||||
> for types::RouterData<F, types::SetupMandateRequestData, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<
|
||||
F,
|
||||
BamboraapacMandateResponse,
|
||||
types::SetupMandateRequestData,
|
||||
types::PaymentsResponseData,
|
||||
>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let response_code = item
|
||||
.response
|
||||
.body
|
||||
.tokenise_credit_card_response
|
||||
.tokenise_credit_card_result
|
||||
.tokenise_credit_card_response
|
||||
.return_value;
|
||||
|
||||
let connector_mandate_id = item
|
||||
.response
|
||||
.body
|
||||
.tokenise_credit_card_response
|
||||
.tokenise_credit_card_result
|
||||
.tokenise_credit_card_response
|
||||
.token
|
||||
.ok_or(errors::ConnectorError::MissingConnectorMandateID)?;
|
||||
|
||||
// transaction approved
|
||||
if response_code == 0 {
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::Charged,
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::NoResponseId,
|
||||
redirection_data: None,
|
||||
mandate_reference: Some(types::MandateReference {
|
||||
connector_mandate_id: Some(connector_mandate_id),
|
||||
payment_method_id: None,
|
||||
}),
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
incremental_authorization_allowed: None,
|
||||
charge_id: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
// transaction failed
|
||||
else {
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::Failure,
|
||||
response: Err(types::ErrorResponse {
|
||||
status_code: item.http_code,
|
||||
code: consts::NO_ERROR_CODE.to_string(),
|
||||
message: consts::NO_ERROR_MESSAGE.to_string(),
|
||||
reason: None,
|
||||
attempt_status: None,
|
||||
connector_transaction_id: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// capture body in soap format
|
||||
pub fn get_capture_body(
|
||||
req: &BamboraapacRouterData<&types::PaymentsCaptureRouterData>,
|
||||
|
||||
@ -80,7 +80,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/"
|
||||
applepay.base_url = "https://apple-pay-gateway.apple.com/"
|
||||
authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api"
|
||||
bambora.base_url = "https://api.na.bambora.com"
|
||||
bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api/dts.asmx"
|
||||
bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api"
|
||||
bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/"
|
||||
billwerk.base_url = "https://api.reepay.com/"
|
||||
billwerk.secondary_base_url = "https://card.reepay.com/"
|
||||
|
||||
Reference in New Issue
Block a user