mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
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:
@ -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 {}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,75 +262,82 @@ 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)]
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user