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")]
pub enum WalletData {
/// The wallet data for Ali Pay QrCode
AliPay(Box<AliPay>),
AliPayQr(Box<AliPayQr>),
/// The wallet data for Ali Pay redirect
AliPayRedirect(AliPayRedirection),
/// The wallet data for Ali Pay HK redirect
@ -977,6 +977,8 @@ pub enum WalletData {
WeChatPayRedirect(Box<WeChatPayRedirection>),
/// The wallet data for WeChat Pay
WeChatPay(Box<WeChatPay>),
/// The wallet data for WeChat Pay Display QrCode
WeChatPayQr(Box<WeChatPayQr>),
}
#[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)]
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)]
pub struct PaypalRedirection {}
#[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)]
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>
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(
&self,
_req: &types::RefundsRouterData<api::Execute>,
_connectors: &settings::Connectors,
req: &types::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> 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 {
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(
&self,
_req: &types::RefundSyncRouterData,
_connectors: &settings::Connectors,
req: &types::RefundSyncRouterData,
connectors: &settings::Connectors,
) -> 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,
types::{self, api, storage::enums},
};
type Error = error_stack::Report<errors::ConnectorError>;
#[derive(Debug, Serialize)]
pub struct GlobepayPaymentsRequest {
@ -24,12 +25,12 @@ pub enum GlobepayChannel {
}
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> {
let channel: GlobepayChannel = match &item.request.payment_method_data {
api::PaymentMethodData::Wallet(ref wallet_data) => match wallet_data {
api::WalletData::AliPay(_) => GlobepayChannel::Alipay,
api::WalletData::WeChatPay(_) => GlobepayChannel::Wechat,
api::WalletData::AliPayQr(_) => GlobepayChannel::Alipay,
api::WalletData::WeChatPayQr(_) => GlobepayChannel::Wechat,
_ => Err(errors::ConnectorError::NotImplemented(
"Payment method".to_string(),
))?,
@ -54,7 +55,7 @@ pub struct 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> {
match auth_type {
types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self {
@ -110,13 +111,14 @@ pub enum GlobepayReturnCode {
NotPermitted,
InvalidChannel,
DuplicateOrderId,
OrderNotPaid,
}
impl<F, T>
TryFrom<types::ResponseRouterData<F, GlobepayPaymentsResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
type Error = Error;
fn try_from(
item: types::ResponseRouterData<
F,
@ -206,7 +208,7 @@ impl<F, T>
TryFrom<types::ResponseRouterData<F, GlobepaySyncResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
type Error = Error;
fn try_from(
item: types::ResponseRouterData<F, GlobepaySyncResponse, T, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> {
@ -260,76 +262,83 @@ fn get_error_response(
#[derive(Debug, Serialize)]
pub struct GlobepayRefundRequest {
pub amount: i64,
pub fee: i64,
}
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> {
Ok(Self {
amount: item.request.refund_amount,
fee: item.request.refund_amount,
})
}
}
#[allow(dead_code)]
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
pub enum RefundStatus {
Succeeded,
#[derive(Debug, Deserialize)]
pub enum GlobepayRefundStatus {
Waiting,
CreateFailed,
Failed,
#[default]
Processing,
Success,
Finished,
Change,
}
impl From<RefundStatus> for enums::RefundStatus {
fn from(item: RefundStatus) -> Self {
impl From<GlobepayRefundStatus> for enums::RefundStatus {
fn from(item: GlobepayRefundStatus) -> Self {
match item {
RefundStatus::Succeeded => Self::Success,
RefundStatus::Failed => Self::Failure,
RefundStatus::Processing => Self::Pending,
GlobepayRefundStatus::Finished => Self::Success, //FINISHED: Refund success(funds has already been returned to user's account)
GlobepayRefundStatus::Failed
| 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)]
pub struct RefundResponse {
id: String,
status: RefundStatus,
#[derive(Debug, Deserialize)]
pub struct GlobepayRefundResponse {
pub result_code: Option<GlobepayRefundStatus>,
pub refund_id: Option<String>,
pub return_code: GlobepayReturnCode,
pub return_msg: Option<String>,
}
impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
for types::RefundsRouterData<api::Execute>
impl<T> TryFrom<types::RefundsResponseRouterData<T, GlobepayRefundResponse>>
for types::RefundsRouterData<T>
{
type Error = error_stack::Report<errors::ConnectorError>;
type Error = Error;
fn try_from(
item: types::RefundsResponseRouterData<api::Execute, RefundResponse>,
item: types::RefundsResponseRouterData<T, GlobepayRefundResponse>,
) -> 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 {
response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.id.to_string(),
refund_status: enums::RefundStatus::from(item.response.status),
connector_refund_id: globepay_refund_id,
refund_status: enums::RefundStatus::from(globepay_refund_status),
}),
..item.data
})
}
}
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> {
} else {
Ok(Self {
response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.id.to_string(),
refund_status: enums::RefundStatus::from(item.response.status),
}),
response: Err(get_error_response(
item.response.return_code,
item.response.return_msg,
item.http_code,
)),
..item.data
})
}
}
}
#[derive(Debug, Deserialize)]
pub struct GlobepayErrorResponse {

View File

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

View File

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