Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api-reference/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"v1/payments/payments--cancel-post-capture",
"v1/payments/payments--capture",
"v1/payments/payments--incremental-authorization",
"v1/payments/payments--extend-authorization",
"v1/payments/payments--session-token",
"v1/payments/payments-link--retrieve",
"v1/payments/payments--list",
Expand Down
60 changes: 60 additions & 0 deletions api-reference/v1/openapi_spec_v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,62 @@
]
}
},
"/payments/{payment_id}/extend_authorization": {
"post": {
"tags": [
"Payments"
],
"summary": "Payments - Extended Authorization",
"description": "Extended authorization is available for payments currently in the `requires_capture` status\nCall this endpoint to increase the authorization validity period",
"operationId": "Extend authorization period for a Payment",
"parameters": [
{
"name": "payment_id",
"in": "path",
"description": "The identifier for payment",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PaymentsExtendAuthorizationRequest"
},
"examples": {
"Increase the authorization validity period with minimal fields": {
"value": {}
}
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Extended authorization for the payment"
},
"400": {
"description": "Missing mandatory fields",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenericErrorResponseOpenApi"
}
}
}
}
},
"security": [
{
"api_key": []
}
]
}
},
"/payments/list": {
"get": {
"tags": [
Expand Down Expand Up @@ -24542,6 +24598,10 @@
}
}
},
"PaymentsExtendAuthorizationRequest": {
"type": "object",
"description": "Request to extend the authorization period for a payment"
},
"PaymentsExternalAuthenticationRequest": {
"type": "object",
"required": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
openapi: post /payments/{payment_id}/extend_authorization
---
19 changes: 14 additions & 5 deletions crates/api_models/src/events/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ use crate::{
PaymentListResponseV2, PaymentsApproveRequest, PaymentsCancelPostCaptureRequest,
PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest,
PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse,
PaymentsExternalAuthenticationRequest, PaymentsExternalAuthenticationResponse,
PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest,
PaymentsManualUpdateResponse, PaymentsPostSessionTokensRequest,
PaymentsPostSessionTokensResponse, PaymentsRejectRequest, PaymentsRetrieveRequest,
PaymentsStartRequest, PaymentsUpdateMetadataRequest, PaymentsUpdateMetadataResponse,
PaymentsExtendAuthorizationRequest, PaymentsExternalAuthenticationRequest,
PaymentsExternalAuthenticationResponse, PaymentsIncrementalAuthorizationRequest,
PaymentsManualUpdateRequest, PaymentsManualUpdateResponse,
PaymentsPostSessionTokensRequest, PaymentsPostSessionTokensResponse, PaymentsRejectRequest,
PaymentsRetrieveRequest, PaymentsStartRequest, PaymentsUpdateMetadataRequest,
PaymentsUpdateMetadataResponse,
},
};

Expand Down Expand Up @@ -143,6 +144,14 @@ impl ApiEventMetric for PaymentsCancelPostCaptureRequest {
})
}
}
#[cfg(feature = "v1")]
impl ApiEventMetric for PaymentsExtendAuthorizationRequest {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Payment {
payment_id: self.payment_id.clone(),
})
}
}

#[cfg(feature = "v1")]
impl ApiEventMetric for PaymentsApproveRequest {
Expand Down
8 changes: 8 additions & 0 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8411,6 +8411,14 @@ pub struct PaymentsCancelPostCaptureRequest {
pub cancellation_reason: Option<String>,
}

#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
/// Request to extend the authorization period for a payment
pub struct PaymentsExtendAuthorizationRequest {
/// The identifier for the payment
#[serde(skip)]
pub payment_id: id_type::PaymentId,
}

#[derive(Default, Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)]
pub struct PaymentsIncrementalAuthorizationRequest {
/// The identifier for the payment
Expand Down
6 changes: 5 additions & 1 deletion crates/api_models/src/webhooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ pub enum IncomingWebhookEvent {
MandateActive,
MandateRevoked,
EndpointVerification,
ExtendedAuthorization,
ExtendAuthorizationFailed,
ExternalAuthenticationARes,
FrmApproved,
FrmRejected,
Expand Down Expand Up @@ -265,7 +267,9 @@ impl From<IncomingWebhookEvent> for WebhookFlow {
| IncomingWebhookEvent::PaymentIntentAuthorizationFailure
| IncomingWebhookEvent::PaymentIntentCaptureSuccess
| IncomingWebhookEvent::PaymentIntentCaptureFailure
| IncomingWebhookEvent::PaymentIntentExpired => Self::Payment,
| IncomingWebhookEvent::PaymentIntentExpired
| IncomingWebhookEvent::ExtendedAuthorization
| IncomingWebhookEvent::ExtendAuthorizationFailed => Self::Payment,
IncomingWebhookEvent::EventNotSupported => Self::ReturnResponse,
IncomingWebhookEvent::RefundSuccess | IncomingWebhookEvent::RefundFailure => {
Self::Refund
Expand Down
24 changes: 24 additions & 0 deletions crates/common_enums/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,30 @@ pub enum AuthorizationStatus {
Unresolved,
}

#[derive(
Default,
Clone,
Debug,
Eq,
PartialEq,
serde::Deserialize,
serde::Serialize,
strum::Display,
strum::EnumString,
ToSchema,
Hash,
)]
#[router_derive::diesel_enum(storage_type = "text")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum ExtendedAuthorizationStatus {
Success,
Failure,
// Processing state is before calling connector
#[default]
Processing,
}

#[derive(
Clone,
Debug,
Expand Down
128 changes: 118 additions & 10 deletions crates/hyperswitch_connectors/src/connectors/adyen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,19 @@ use hyperswitch_domain_models::{
router_flow_types::{
access_token_auth::AccessTokenAuth,
payments::{
Authorize, Capture, PSync, PaymentMethodToken, PreProcessing, Session, SetupMandate,
Void,
Authorize, Capture, ExtendAuthorization, PSync, PaymentMethodToken, PreProcessing,
Session, SetupMandate, Void,
},
refunds::{Execute, RSync},
Accept, Defend, Evidence, GiftCardBalanceCheck, Retrieve, Upload,
},
router_request_types::{
AcceptDisputeRequestData, AccessTokenRequestData, DefendDisputeRequestData,
GiftCardBalanceCheckRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData,
PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, PaymentsSessionData,
PaymentsSyncData, RefundsData, RetrieveFileRequestData, SetupMandateRequestData,
SubmitEvidenceRequestData, SyncRequestType, UploadFileRequestData,
PaymentsCancelData, PaymentsCaptureData, PaymentsExtendAuthorizationData,
PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, RefundsData,
RetrieveFileRequestData, SetupMandateRequestData, SubmitEvidenceRequestData,
SyncRequestType, UploadFileRequestData,
},
router_response_types::{
AcceptDisputeResponse, ConnectorInfo, DefendDisputeResponse,
Expand All @@ -42,8 +43,9 @@ use hyperswitch_domain_models::{
},
types::{
PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
PaymentsGiftCardBalanceCheckRouterData, PaymentsPreProcessingRouterData,
PaymentsSyncRouterData, RefundsRouterData, SetupMandateRouterData,
PaymentsExtendAuthorizationRouterData, PaymentsGiftCardBalanceCheckRouterData,
PaymentsPreProcessingRouterData, PaymentsSyncRouterData, RefundsRouterData,
SetupMandateRouterData,
},
};
#[cfg(feature = "payouts")]
Expand All @@ -69,9 +71,10 @@ use hyperswitch_interfaces::{
disputes, errors,
events::connector_api_logs::ConnectorEvent,
types::{
AcceptDisputeType, DefendDisputeType, PaymentsAuthorizeType, PaymentsCaptureType,
PaymentsGiftCardBalanceCheckType, PaymentsPreProcessingType, PaymentsSyncType,
PaymentsVoidType, RefundExecuteType, Response, SetupMandateType, SubmitEvidenceType,
AcceptDisputeType, DefendDisputeType, ExtendedAuthorizationType, PaymentsAuthorizeType,
PaymentsCaptureType, PaymentsGiftCardBalanceCheckType, PaymentsPreProcessingType,
PaymentsSyncType, PaymentsVoidType, RefundExecuteType, Response, SetupMandateType,
SubmitEvidenceType,
},
webhooks::{IncomingWebhook, IncomingWebhookFlowError, IncomingWebhookRequestDetails},
};
Expand Down Expand Up @@ -1580,6 +1583,111 @@ impl ConnectorIntegration<PoEligibility, PayoutsData, PayoutsResponseData> for A
}
}

impl api::PaymentExtendAuthorization for Adyen {}
impl
ConnectorIntegration<ExtendAuthorization, PaymentsExtendAuthorizationData, PaymentsResponseData>
for Adyen
{
fn get_headers(
&self,
req: &PaymentsExtendAuthorizationRouterData,
_connectors: &Connectors,
) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
let mut header = vec![(
headers::CONTENT_TYPE.to_string(),
self.common_get_content_type().to_string().into(),
)];
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
header.append(&mut api_key);
Ok(header)
}

fn get_content_type(&self) -> &'static str {
self.common_get_content_type()
}

fn get_url(
&self,
req: &PaymentsExtendAuthorizationRouterData,
connectors: &Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let id = req.request.connector_transaction_id.as_str();
let endpoint = build_env_specific_endpoint(
self.base_url(connectors),
req.test_mode,
&req.connector_meta_data,
)?;
Ok(format!(
"{endpoint}{ADYEN_API_VERSION}/payments/{id}/amountUpdates"
))
}

fn get_request_body(
&self,
req: &PaymentsExtendAuthorizationRouterData,
_connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let amount = convert_amount(
self.amount_converter,
req.request.minor_amount,
req.request.currency,
)?;

let connector_router_data = adyen::AdyenRouterData::try_from((amount, req))?;
let connector_req =
adyen::AdyenExtendAuthorizationRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
}

fn build_request(
&self,
req: &PaymentsExtendAuthorizationRouterData,
connectors: &Connectors,
) -> CustomResult<Option<Request>, errors::ConnectorError> {
Ok(Some(
RequestBuilder::new()
.method(Method::Post)
.url(&ExtendedAuthorizationType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(ExtendedAuthorizationType::get_headers(
self, req, connectors,
)?)
.set_body(ExtendedAuthorizationType::get_request_body(
self, req, connectors,
)?)
.build(),
))
}

fn handle_response(
&self,
data: &PaymentsExtendAuthorizationRouterData,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<PaymentsExtendAuthorizationRouterData, errors::ConnectorError> {
let response: adyen::AdyenExtendAuthorizationResponse = res
.response
.parse_struct("Adyen AdyenExtendAuthorizationResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
RouterData::try_from(ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}

fn get_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res, event_builder)
}
}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoFulfill, PayoutsData, PayoutsResponseData> for Adyen {
fn get_url(
Expand Down
Loading
Loading