Skip to content

Commit

Permalink
refactor(connector): airwallex convert init payment to preprocessing (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
hrithikesh026 authored Jun 3, 2024
1 parent 67f017f commit e5da133
Show file tree
Hide file tree
Showing 22 changed files with 851 additions and 55 deletions.
3 changes: 3 additions & 0 deletions crates/api_models/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ impl Connector {
Self::Checkout | Self::Nmi| Self::Cybersource => true,
}
}
pub fn is_pre_processing_required_before_authorize(&self) -> bool {
matches!(self, Self::Airwallex)
}
}

#[derive(
Expand Down
60 changes: 17 additions & 43 deletions crates/router/src/connector/airwallex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
payments,
},
events::connector_api_logs::ConnectorEvent,
headers, logger, routes,
headers, logger,
services::{
self,
request::{self, Mask},
Expand All @@ -27,7 +27,6 @@ use crate::{
types::{
self,
api::{self, ConnectorCommon, ConnectorCommonExt},
transformers::ForeignFrom,
ErrorResponse, Response, RouterData,
},
utils::{crypto, BytesExt},
Expand Down Expand Up @@ -123,6 +122,7 @@ impl ConnectorValidation for Airwallex {
}

impl api::Payment for Airwallex {}
impl api::PaymentsPreProcessing for Airwallex {}
impl api::PaymentsCompleteAuthorize for Airwallex {}
impl api::MandateSetup for Airwallex {}
impl
Expand Down Expand Up @@ -242,14 +242,14 @@ impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, t

impl
ConnectorIntegration<
api::InitPayment,
types::PaymentsAuthorizeData,
api::PreProcessing,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
> for Airwallex
{
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)
Expand All @@ -261,7 +261,7 @@ impl

fn get_url(
&self,
_req: &types::PaymentsInitRouterData,
_req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
Expand All @@ -273,7 +273,7 @@ impl

fn get_request_body(
&self,
req: &types::PaymentsInitRouterData,
req: &types::PaymentsPreProcessingRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let req_obj = airwallex::AirwallexIntentRequest::try_from(req)?;
Expand All @@ -282,16 +282,20 @@ impl

fn build_request(
&self,
req: &types::PaymentsInitRouterData,
req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsInitType::get_url(self, req, connectors)?)
.url(&types::PaymentsPreProcessingType::get_url(
self, req, connectors,
)?)
.attach_default_headers()
.headers(types::PaymentsInitType::get_headers(self, req, connectors)?)
.set_body(types::PaymentsInitType::get_request_body(
.headers(types::PaymentsPreProcessingType::get_headers(
self, req, connectors,
)?)
.set_body(types::PaymentsPreProcessingType::get_request_body(
self, req, connectors,
)?)
.build(),
Expand All @@ -300,10 +304,10 @@ impl

fn handle_response(
&self,
data: &types::PaymentsInitRouterData,
data: &types::PaymentsPreProcessingRouterData,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<types::PaymentsInitRouterData, errors::ConnectorError> {
) -> CustomResult<types::PaymentsPreProcessingRouterData, errors::ConnectorError> {
let response: airwallex::AirwallexPaymentsResponse = res
.response
.parse_struct("airwallex AirwallexPaymentsResponse")
Expand Down Expand Up @@ -335,36 +339,6 @@ impl api::PaymentAuthorize for Airwallex {}
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
for Airwallex
{
async fn execute_pretasks(
&self,
router_data: &mut types::PaymentsAuthorizeRouterData,
app_state: &routes::AppState,
) -> CustomResult<(), errors::ConnectorError> {
let integ: Box<
&(dyn ConnectorIntegration<
api::InitPayment,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
> + Send
+ Sync
+ 'static),
> = Box::new(&Self);
let authorize_data = &types::PaymentsInitRouterData::foreign_from((
&router_data.to_owned(),
router_data.request.clone(),
));
let resp = services::execute_connector_processing_step(
app_state,
integ,
authorize_data,
payments::CallConnectorAction::Trigger,
None,
)
.await?;
router_data.reference_id = resp.reference_id;
Ok(())
}

fn get_headers(
&self,
req: &types::PaymentsAuthorizeRouterData,
Expand Down
21 changes: 17 additions & 4 deletions crates/router/src/connector/airwallex/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,26 @@ pub struct AirwallexIntentRequest {
//ID created in merchant's order system that corresponds to this PaymentIntent.
merchant_order_id: String,
}
impl TryFrom<&types::PaymentsInitRouterData> for AirwallexIntentRequest {
impl TryFrom<&types::PaymentsPreProcessingRouterData> for AirwallexIntentRequest {
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> {
// amount and currency will always be Some since PaymentsPreProcessingData is constructed using PaymentsAuthorizeData
let amount = item
.request
.amount
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "amount",
})?;
let currency =
item.request
.currency
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "currency",
})?;
Ok(Self {
request_id: Uuid::new_v4().to_string(),
amount: utils::to_currency_base_unit(item.request.amount, item.request.currency)?,
currency: item.request.currency,
amount: utils::to_currency_base_unit(amount, currency)?,
currency,
merchant_order_id: item.connector_request_reference_id.clone(),
})
}
Expand Down
8 changes: 8 additions & 0 deletions crates/router/src/core/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1885,6 +1885,14 @@ where
dyn api::Connector:
services::api::ConnectorIntegration<F, Req, router_types::PaymentsResponseData>,
{
if !is_operation_complete_authorize(&operation)
&& connector
.connector_name
.is_pre_processing_required_before_authorize()
{
router_data = router_data.preprocessing_steps(state, connector).await?;
return Ok((router_data, should_continue_payment));
}
//TODO: For ACH transfers, if preprocessing_step is not required for connectors encountered in future, add the check
let router_data_and_should_continue_payment = match payment_data.payment_method_data.clone() {
Some(api_models::payments::PaymentMethodData::BankTransfer(data)) => match data.deref() {
Expand Down
1 change: 0 additions & 1 deletion crates/router/src/core/payments/flows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,6 @@ impl<const T: u8>

default_imp_for_pre_processing_steps!(
connector::Aci,
connector::Airwallex,
connector::Authorizedotnet,
connector::Bambora,
connector::Billwerk,
Expand Down
7 changes: 4 additions & 3 deletions crates/router/src/core/payments/flows/authorize_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,13 +322,14 @@ pub async fn authorize_preprocessing_steps<F: Clone>(
),
],
);

let authorize_router_data = helpers::router_data_type_conversion::<_, F, _, _, _, _>(
let mut authorize_router_data = helpers::router_data_type_conversion::<_, F, _, _, _, _>(
resp.clone(),
router_data.request.to_owned(),
resp.response,
);

if connector.connector_name == api_models::enums::Connector::Airwallex {
authorize_router_data.reference_id = resp.reference_id;
}
Ok(authorize_router_data)
} else {
Ok(router_data.clone())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"childrenOrder": [
"Payments - Create",
"Payments - Capture",
"Payments - Retrieve"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"eventOrder": ["event.test.js"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Validate status 2xx
pm.test("[POST]::/payments/:id/capture - Status code is 2xx", function () {
pm.response.to.be.success;
});

// Validate if response header has matching content-type
pm.test(
"[POST]::/payments/:id/capture - Content-Type is application/json",
function () {
pm.expect(pm.response.headers.get("Content-Type")).to.include(
"application/json",
);
},
);

// Validate if response has JSON Body
pm.test("[POST]::/payments/:id/capture - Response has JSON Body", function () {
pm.response.to.have.jsonBody();
});

// Set response object as internal variable
let jsonData = {};
try {
jsonData = pm.response.json();
} catch (e) {}

// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id
if (jsonData?.payment_id) {
pm.collectionVariables.set("payment_id", jsonData.payment_id);
console.log(
"- use {{payment_id}} as collection variable for value",
jsonData.payment_id,
);
} else {
console.log(
"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.",
);
}

// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id
if (jsonData?.mandate_id) {
pm.collectionVariables.set("mandate_id", jsonData.mandate_id);
console.log(
"- use {{mandate_id}} as collection variable for value",
jsonData.mandate_id,
);
} else {
console.log(
"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.",
);
}

// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret
if (jsonData?.client_secret) {
pm.collectionVariables.set("client_secret", jsonData.client_secret);
console.log(
"- use {{client_secret}} as collection variable for value",
jsonData.client_secret,
);
} else {
console.log(
"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.",
);
}

// Response body should have value "succeeded" for "status"
if (jsonData?.status) {
pm.test(
"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'partially_captured'",
function () {
pm.expect(jsonData.status).to.eql("partially_captured");
},
);
}

// Response body should have value "6540" for "amount"
if (jsonData?.amount) {
pm.test(
"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'",
function () {
pm.expect(jsonData.amount).to.eql(6540);
},
);
}

// Response body should have value "6000" for "amount_received"
if (jsonData?.amount_received) {
pm.test(
"[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6000'",
function () {
pm.expect(jsonData.amount_received).to.eql(6000);
},
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Accept",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"options": {
"raw": {
"language": "json"
}
},
"raw_json_formatted": {
"amount_to_capture": 6000,
"statement_descriptor_name": "Joseph",
"statement_descriptor_suffix": "JS"
}
},
"url": {
"raw": "{{baseUrl}}/payments/:id/capture",
"host": ["{{baseUrl}}"],
"path": ["payments", ":id", "capture"],
"variable": [
{
"key": "id",
"value": "{{payment_id}}",
"description": "(Required) unique payment id"
}
]
},
"description": "To capture the funds for an uncaptured payment"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"eventOrder": ["event.test.js"]
}
Loading

0 comments on commit e5da133

Please sign in to comment.