feat(connector): [Globepay] Add Refund and Refund Sync flow (#1706)

Signed-off-by: chikke srujan <121822803+srujanchikke@users.noreply.github.com>
Co-authored-by: Jagan <jaganelavarasan@gmail.com>
This commit is contained in:
chikke srujan
2023-07-14 20:28:13 +05:30
committed by GitHub
parent c34a049506
commit c72a592e5e
5 changed files with 225 additions and 66 deletions

View File

@ -947,7 +947,7 @@ pub struct BankDebitBilling {
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum WalletData { pub enum WalletData {
/// The wallet data for Ali Pay QrCode /// The wallet data for Ali Pay QrCode
AliPay(Box<AliPay>), AliPayQr(Box<AliPayQr>),
/// The wallet data for Ali Pay redirect /// The wallet data for Ali Pay redirect
AliPayRedirect(AliPayRedirection), AliPayRedirect(AliPayRedirection),
/// The wallet data for Ali Pay HK redirect /// The wallet data for Ali Pay HK redirect
@ -977,6 +977,8 @@ pub enum WalletData {
WeChatPayRedirect(Box<WeChatPayRedirection>), WeChatPayRedirect(Box<WeChatPayRedirection>),
/// The wallet data for WeChat Pay /// The wallet data for WeChat Pay
WeChatPay(Box<WeChatPay>), WeChatPay(Box<WeChatPay>),
/// The wallet data for WeChat Pay Display QrCode
WeChatPayQr(Box<WeChatPayQr>),
} }
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
@ -1019,11 +1021,14 @@ pub struct WeChatPayRedirection {}
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct WeChatPay {} pub struct WeChatPay {}
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct WeChatPayQr {}
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct PaypalRedirection {} pub struct PaypalRedirection {}
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct AliPay {} pub struct AliPayQr {}
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct AliPayRedirection {} pub struct AliPayRedirection {}

View File

@ -327,22 +327,152 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
for Globepay for Globepay
{ {
fn get_headers(
&self,
req: &types::RefundsRouterData<api::Execute>,
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::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let query_params = get_globlepay_query_params(&req.connector_auth_type)?;
Ok(format!(
"{}api/v1.0/gateway/partners/{}/orders/{}/refunds/{}{query_params}",
self.base_url(connectors),
get_partner_code(&req.connector_auth_type)?,
req.payment_id,
req.request.refund_id
))
}
fn get_request_body(
&self,
req: &types::RefundsRouterData<api::Execute>,
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
let connector_req = globepay::GlobepayRefundRequest::try_from(req)?;
let globepay_req = types::RequestBody::log_and_get_request_body(
&connector_req,
utils::Encode::<globepay::GlobepayRefundRequest>::encode_to_string_of_json,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(globepay_req))
}
fn build_request( fn build_request(
&self, &self,
_req: &types::RefundsRouterData<api::Execute>, req: &types::RefundsRouterData<api::Execute>,
_connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> { ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("Refund".to_string()).into()) Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Put)
.url(&types::RefundExecuteType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(types::RefundExecuteType::get_headers(
self, req, connectors,
)?)
.body(types::RefundExecuteType::get_request_body(self, req)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::RefundsRouterData<api::Execute>,
res: Response,
) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
let response: globepay::GlobepayRefundResponse = res
.response
.parse_struct("Globalpay RefundResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
}
fn get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
} }
} }
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Globepay { impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Globepay {
fn get_headers(
&self,
req: &types::RefundSyncRouterData,
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::RefundSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let query_params = get_globlepay_query_params(&req.connector_auth_type)?;
Ok(format!(
"{}api/v1.0/gateway/partners/{}/orders/{}/refunds/{}{query_params}",
self.base_url(connectors),
get_partner_code(&req.connector_auth_type)?,
req.payment_id,
req.request.refund_id
))
}
fn build_request( fn build_request(
&self, &self,
_req: &types::RefundSyncRouterData, req: &types::RefundSyncRouterData,
_connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> { ) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("Refund Sync".to_string()).into()) Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Get)
.url(&types::RefundSyncType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(types::RefundSyncType::get_headers(self, req, connectors)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::RefundSyncRouterData,
res: Response,
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
let response: globepay::GlobepayRefundResponse = res
.response
.parse_struct("Globalpay RefundResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
}
fn get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
} }
} }

View File

@ -8,6 +8,7 @@ use crate::{
core::errors, core::errors,
types::{self, api, storage::enums}, types::{self, api, storage::enums},
}; };
type Error = error_stack::Report<errors::ConnectorError>;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct GlobepayPaymentsRequest { pub struct GlobepayPaymentsRequest {
@ -24,12 +25,12 @@ pub enum GlobepayChannel {
} }
impl TryFrom<&types::PaymentsAuthorizeRouterData> for GlobepayPaymentsRequest { impl TryFrom<&types::PaymentsAuthorizeRouterData> for GlobepayPaymentsRequest {
type Error = error_stack::Report<errors::ConnectorError>; type Error = Error;
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> { fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
let channel: GlobepayChannel = match &item.request.payment_method_data { let channel: GlobepayChannel = match &item.request.payment_method_data {
api::PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { api::PaymentMethodData::Wallet(ref wallet_data) => match wallet_data {
api::WalletData::AliPay(_) => GlobepayChannel::Alipay, api::WalletData::AliPayQr(_) => GlobepayChannel::Alipay,
api::WalletData::WeChatPay(_) => GlobepayChannel::Wechat, api::WalletData::WeChatPayQr(_) => GlobepayChannel::Wechat,
_ => Err(errors::ConnectorError::NotImplemented( _ => Err(errors::ConnectorError::NotImplemented(
"Payment method".to_string(), "Payment method".to_string(),
))?, ))?,
@ -54,7 +55,7 @@ pub struct GlobepayAuthType {
} }
impl TryFrom<&types::ConnectorAuthType> for GlobepayAuthType { impl TryFrom<&types::ConnectorAuthType> for GlobepayAuthType {
type Error = error_stack::Report<errors::ConnectorError>; type Error = Error;
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> { fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
match auth_type { match auth_type {
types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self {
@ -110,13 +111,14 @@ pub enum GlobepayReturnCode {
NotPermitted, NotPermitted,
InvalidChannel, InvalidChannel,
DuplicateOrderId, DuplicateOrderId,
OrderNotPaid,
} }
impl<F, T> impl<F, T>
TryFrom<types::ResponseRouterData<F, GlobepayPaymentsResponse, T, types::PaymentsResponseData>> TryFrom<types::ResponseRouterData<F, GlobepayPaymentsResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData> for types::RouterData<F, T, types::PaymentsResponseData>
{ {
type Error = error_stack::Report<errors::ConnectorError>; type Error = Error;
fn try_from( fn try_from(
item: types::ResponseRouterData< item: types::ResponseRouterData<
F, F,
@ -206,7 +208,7 @@ impl<F, T>
TryFrom<types::ResponseRouterData<F, GlobepaySyncResponse, T, types::PaymentsResponseData>> TryFrom<types::ResponseRouterData<F, GlobepaySyncResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData> for types::RouterData<F, T, types::PaymentsResponseData>
{ {
type Error = error_stack::Report<errors::ConnectorError>; type Error = Error;
fn try_from( fn try_from(
item: types::ResponseRouterData<F, GlobepaySyncResponse, T, types::PaymentsResponseData>, item: types::ResponseRouterData<F, GlobepaySyncResponse, T, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
@ -260,75 +262,82 @@ fn get_error_response(
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct GlobepayRefundRequest { pub struct GlobepayRefundRequest {
pub amount: i64, pub fee: i64,
} }
impl<F> TryFrom<&types::RefundsRouterData<F>> for GlobepayRefundRequest { impl<F> TryFrom<&types::RefundsRouterData<F>> for GlobepayRefundRequest {
type Error = error_stack::Report<errors::ConnectorError>; type Error = Error;
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> { fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
amount: item.request.refund_amount, fee: item.request.refund_amount,
}) })
} }
} }
#[allow(dead_code)] #[derive(Debug, Deserialize)]
#[derive(Debug, Serialize, Default, Deserialize, Clone)] pub enum GlobepayRefundStatus {
pub enum RefundStatus { Waiting,
Succeeded, CreateFailed,
Failed, Failed,
#[default] Success,
Processing, Finished,
Change,
} }
impl From<RefundStatus> for enums::RefundStatus { impl From<GlobepayRefundStatus> for enums::RefundStatus {
fn from(item: RefundStatus) -> Self { fn from(item: GlobepayRefundStatus) -> Self {
match item { match item {
RefundStatus::Succeeded => Self::Success, GlobepayRefundStatus::Finished => Self::Success, //FINISHED: Refund success(funds has already been returned to user's account)
RefundStatus::Failed => Self::Failure, GlobepayRefundStatus::Failed
RefundStatus::Processing => Self::Pending, | GlobepayRefundStatus::CreateFailed
| GlobepayRefundStatus::Change => Self::Failure, //CHANGE: Refund can not return to user's account. Manual operation is required
GlobepayRefundStatus::Waiting | GlobepayRefundStatus::Success => Self::Pending, // SUCCESS: Submission succeeded, but refund is not yet complete. Waiting = Submission succeeded, but refund is not yet complete.
} }
} }
} }
#[derive(Default, Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Deserialize)]
pub struct RefundResponse { pub struct GlobepayRefundResponse {
id: String, pub result_code: Option<GlobepayRefundStatus>,
status: RefundStatus, pub refund_id: Option<String>,
pub return_code: GlobepayReturnCode,
pub return_msg: Option<String>,
} }
impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>> impl<T> TryFrom<types::RefundsResponseRouterData<T, GlobepayRefundResponse>>
for types::RefundsRouterData<api::Execute> for types::RefundsRouterData<T>
{ {
type Error = error_stack::Report<errors::ConnectorError>; type Error = Error;
fn try_from( fn try_from(
item: types::RefundsResponseRouterData<api::Execute, RefundResponse>, item: types::RefundsResponseRouterData<T, GlobepayRefundResponse>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
if item.response.return_code == GlobepayReturnCode::Success {
let globepay_refund_id = item
.response
.refund_id
.ok_or(errors::ConnectorError::ResponseHandlingFailed)?;
let globepay_refund_status = item
.response
.result_code
.ok_or(errors::ConnectorError::ResponseHandlingFailed)?;
Ok(Self { Ok(Self {
response: Ok(types::RefundsResponseData { response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.id.to_string(), connector_refund_id: globepay_refund_id,
refund_status: enums::RefundStatus::from(item.response.status), refund_status: enums::RefundStatus::from(globepay_refund_status),
}), }),
..item.data ..item.data
}) })
} } else {
}
impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
for types::RefundsRouterData<api::RSync>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::RefundsResponseRouterData<api::RSync, RefundResponse>,
) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
response: Ok(types::RefundsResponseData { response: Err(get_error_response(
connector_refund_id: item.response.id.to_string(), item.response.return_code,
refund_status: enums::RefundStatus::from(item.response.status), item.response.return_msg,
}), item.http_code,
)),
..item.data ..item.data
}) })
} }
}
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]

View File

@ -167,12 +167,13 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::disputes::DisputeResponsePaymentsRetrieve, api_models::disputes::DisputeResponsePaymentsRetrieve,
api_models::payments::AddressDetails, api_models::payments::AddressDetails,
api_models::payments::BankDebitData, api_models::payments::BankDebitData,
api_models::payments::AliPay, api_models::payments::AliPayQr,
api_models::payments::AliPayRedirection, api_models::payments::AliPayRedirection,
api_models::payments::AliPayHkRedirection, api_models::payments::AliPayHkRedirection,
api_models::payments::MbWayRedirection, api_models::payments::MbWayRedirection,
api_models::payments::MobilePayRedirection, api_models::payments::MobilePayRedirection,
api_models::payments::WeChatPayRedirection, api_models::payments::WeChatPayRedirection,
api_models::payments::WeChatPayQr,
api_models::payments::BankDebitBilling, api_models::payments::BankDebitBilling,
api_models::payments::CryptoData, api_models::payments::CryptoData,
api_models::payments::RewardData, api_models::payments::RewardData,

View File

@ -1739,10 +1739,10 @@
} }
} }
}, },
"AliPay": { "AliPayHkRedirection": {
"type": "object" "type": "object"
}, },
"AliPayHkRedirection": { "AliPayQr": {
"type": "object" "type": "object"
}, },
"AliPayRedirection": { "AliPayRedirection": {
@ -8761,11 +8761,11 @@
{ {
"type": "object", "type": "object",
"required": [ "required": [
"ali_pay" "ali_pay_qr"
], ],
"properties": { "properties": {
"ali_pay": { "ali_pay_qr": {
"$ref": "#/components/schemas/AliPay" "$ref": "#/components/schemas/AliPayQr"
} }
} }
}, },
@ -8933,12 +8933,26 @@
"$ref": "#/components/schemas/WeChatPay" "$ref": "#/components/schemas/WeChatPay"
} }
} }
},
{
"type": "object",
"required": [
"we_chat_pay_qr"
],
"properties": {
"we_chat_pay_qr": {
"$ref": "#/components/schemas/WeChatPayQr"
}
}
} }
] ]
}, },
"WeChatPay": { "WeChatPay": {
"type": "object" "type": "object"
}, },
"WeChatPayQr": {
"type": "object"
},
"WeChatPayRedirection": { "WeChatPayRedirection": {
"type": "object" "type": "object"
}, },