Skip to content

Commit

Permalink
feat(payment_v2): implement payments sync (#6464)
Browse files Browse the repository at this point in the history
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
  • Loading branch information
Narayanbhat166 and hyperswitch-bot[bot] authored Nov 11, 2024
1 parent 0a506b1 commit 42bdf47
Show file tree
Hide file tree
Showing 65 changed files with 2,453 additions and 496 deletions.
53 changes: 6 additions & 47 deletions api-reference-v2/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -14314,6 +14314,7 @@
"type": "object",
"required": [
"id",
"status",
"amount_details",
"client_secret",
"capture_method",
Expand All @@ -14331,6 +14332,9 @@
"type": "string",
"description": "Global Payment Id for the payment"
},
"status": {
"$ref": "#/components/schemas/IntentStatus"
},
"amount_details": {
"$ref": "#/components/schemas/AmountDetailsResponse"
},
Expand Down Expand Up @@ -15049,56 +15053,11 @@
},
"PaymentsRetrieveRequest": {
"type": "object",
"required": [
"resource_id",
"force_sync"
],
"description": "Request for Payment Status",
"properties": {
"resource_id": {
"type": "string",
"description": "The type of ID (ex: payment intent id, payment attempt id or connector txn id)"
},
"merchant_id": {
"type": "string",
"description": "The identifier for the Merchant Account.",
"nullable": true
},
"force_sync": {
"type": "boolean",
"description": "Decider to enable or disable the connector call for retrieve request"
},
"param": {
"type": "string",
"description": "The parameters passed to a retrieve request",
"nullable": true
},
"connector": {
"type": "string",
"description": "The name of the connector",
"nullable": true
},
"merchant_connector_details": {
"allOf": [
{
"$ref": "#/components/schemas/MerchantConnectorDetailsWrap"
}
],
"nullable": true
},
"client_secret": {
"type": "string",
"description": "This is a token which expires after 15 minutes, used from the client to authenticate and create sessions from the SDK",
"nullable": true
},
"expand_captures": {
"type": "boolean",
"description": "If enabled provides list of captures linked to latest attempt",
"nullable": true
},
"expand_attempts": {
"type": "boolean",
"description": "If enabled provides list of attempts linked to payment intent",
"nullable": true
"description": "A boolean used to indicate if the payment status should be fetched from the connector\nIf this is set to true, the status will be fetched from the connector"
}
}
},
Expand Down
9 changes: 9 additions & 0 deletions crates/api_models/src/events/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,15 @@ impl ApiEventMetric for PaymentsConfirmIntentResponse {
}
}

#[cfg(feature = "v2")]
impl ApiEventMetric for super::PaymentsRetrieveResponse {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Payment {
payment_id: self.id.clone(),
})
}
}

#[cfg(feature = "v1")]
impl ApiEventMetric for PaymentsResponse {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Expand Down
84 changes: 84 additions & 0 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,10 @@ pub struct PaymentsIntentResponse {
#[schema(value_type = String)]
pub id: id_type::GlobalPaymentId,

/// The status of the payment
#[schema(value_type = IntentStatus, example = "succeeded")]
pub status: common_enums::IntentStatus,

/// The amount details for the payment
pub amount_details: AmountDetailsResponse,

Expand Down Expand Up @@ -4484,6 +4488,19 @@ pub struct PaymentsConfirmIntentRequest {
pub browser_info: Option<common_utils::types::BrowserInformation>,
}

// Serialize is implemented because, this will be serialized in the api events.
// Usually request types should not have serialize implemented.
//
/// Request for Payment Status
#[cfg(feature = "v2")]
#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct PaymentsRetrieveRequest {
/// A boolean used to indicate if the payment status should be fetched from the connector
/// If this is set to true, the status will be fetched from the connector
#[serde(default)]
pub force_sync: bool,
}

/// Error details for the payment
#[cfg(feature = "v2")]
#[derive(Debug, serde::Serialize, ToSchema)]
Expand Down Expand Up @@ -4568,6 +4585,72 @@ pub struct PaymentsConfirmIntentResponse {
pub error: Option<ErrorDetails>,
}

// TODO: have a separate response for detailed, summarized
/// Response for Payment Intent Confirm
#[cfg(feature = "v2")]
#[derive(Debug, serde::Serialize, ToSchema)]
pub struct PaymentsRetrieveResponse {
/// Unique identifier for the payment. This ensures idempotency for multiple payments
/// that have been done by a single merchant.
#[schema(
min_length = 32,
max_length = 64,
example = "12345_pay_01926c58bc6e77c09e809964e72af8c8",
value_type = String,
)]
pub id: id_type::GlobalPaymentId,

#[schema(value_type = IntentStatus, example = "succeeded")]
pub status: api_enums::IntentStatus,

/// Amount related information for this payment and attempt
pub amount: ConfirmIntentAmountDetailsResponse,

/// The connector used for the payment
#[schema(example = "stripe")]
pub connector: Option<String>,

/// It's a token used for client side verification.
#[schema(value_type = String)]
pub client_secret: common_utils::types::ClientSecret,

/// Time when the payment was created
#[schema(example = "2022-09-10T10:11:12Z")]
#[serde(with = "common_utils::custom_serde::iso8601")]
pub created: PrimitiveDateTime,

/// The payment method information provided for making a payment
#[schema(value_type = Option<PaymentMethodDataResponseWithBilling>)]
#[serde(serialize_with = "serialize_payment_method_data_response")]
pub payment_method_data: Option<PaymentMethodDataResponseWithBilling>,

/// The payment method type for this payment attempt
#[schema(value_type = Option<PaymentMethod>, example = "wallet")]
pub payment_method_type: Option<api_enums::PaymentMethod>,

#[schema(value_type = Option<PaymentMethodType>, example = "apple_pay")]
pub payment_method_subtype: Option<api_enums::PaymentMethodType>,

/// A unique identifier for a payment provided by the connector
#[schema(value_type = Option<String>, example = "993672945374576J")]
pub connector_transaction_id: Option<String>,

/// reference(Identifier) to the payment at connector side
#[schema(value_type = Option<String>, example = "993672945374576J")]
pub connector_reference_id: Option<String>,

/// Identifier of the connector ( merchant connector account ) which was chosen to make the payment
#[schema(value_type = Option<String>)]
pub merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,

/// The browser information used for this payment
#[schema(value_type = Option<BrowserInformation>)]
pub browser_info: Option<common_utils::types::BrowserInformation>,

/// Error details for the payment if any
pub error: Option<ErrorDetails>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[cfg(feature = "v2")]
pub struct PaymentStartRedirectionRequest {
Expand Down Expand Up @@ -5081,6 +5164,7 @@ pub struct PaymentsResponseForm {
pub order_id: String,
}

#[cfg(feature = "v1")]
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
pub struct PaymentsRetrieveRequest {
/// The type of ID (ex: payment intent id, payment attempt id or connector txn id)
Expand Down
22 changes: 22 additions & 0 deletions crates/common_enums/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1297,6 +1297,28 @@ pub enum IntentStatus {
PartiallyCapturedAndCapturable,
}

impl IntentStatus {
/// Indicates whether the syncing with the connector should be allowed or not
pub fn should_force_sync_with_connector(&self) -> bool {
match self {
// Confirm has not happened yet
Self::RequiresConfirmation
| Self::RequiresPaymentMethod
// Once the status is success, failed or cancelled need not force sync with the connector
| Self::Succeeded
| Self::Failed
| Self::Cancelled
| Self::PartiallyCaptured
| Self::RequiresCapture => false,
Self::Processing
| Self::RequiresCustomerAction
| Self::RequiresMerchantAction
| Self::PartiallyCapturedAndCapturable
=> true,
}
}
}

/// Indicates that you intend to make future payments with the payment methods used for this Payment. Providing this parameter will attach the payment method to the Customer, if present, after the Payment is confirmed and any required actions from the user are complete.
/// - On_session - Payment method saved only at hyperswitch when consent is provided by the user. CVV will asked during the returning user payment
/// - Off_session - Payment method saved at both hyperswitch and Processor when consent is provided by the user. No input is required during the returning user payment.
Expand Down
40 changes: 39 additions & 1 deletion crates/common_enums/src/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use std::fmt::{Display, Formatter};

use serde::{Deserialize, Serialize};

use crate::enums::{Country, CountryAlpha2, CountryAlpha3, PaymentMethod, PaymentMethodType};
use crate::enums::{
AttemptStatus, Country, CountryAlpha2, CountryAlpha3, IntentStatus, PaymentMethod,
PaymentMethodType,
};

impl Display for NumericCountryCodeParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Expand Down Expand Up @@ -2065,6 +2068,41 @@ impl super::PresenceOfCustomerDuringPayment {
}
}

impl From<AttemptStatus> for IntentStatus {
fn from(s: AttemptStatus) -> Self {
match s {
AttemptStatus::Charged | AttemptStatus::AutoRefunded => Self::Succeeded,

AttemptStatus::ConfirmationAwaited => Self::RequiresConfirmation,
AttemptStatus::PaymentMethodAwaited => Self::RequiresPaymentMethod,

AttemptStatus::Authorized => Self::RequiresCapture,
AttemptStatus::AuthenticationPending | AttemptStatus::DeviceDataCollectionPending => {
Self::RequiresCustomerAction
}
AttemptStatus::Unresolved => Self::RequiresMerchantAction,

AttemptStatus::PartialCharged => Self::PartiallyCaptured,
AttemptStatus::PartialChargedAndChargeable => Self::PartiallyCapturedAndCapturable,
AttemptStatus::Started
| AttemptStatus::AuthenticationSuccessful
| AttemptStatus::Authorizing
| AttemptStatus::CodInitiated
| AttemptStatus::VoidInitiated
| AttemptStatus::CaptureInitiated
| AttemptStatus::Pending => Self::Processing,

AttemptStatus::AuthenticationFailed
| AttemptStatus::AuthorizationFailed
| AttemptStatus::VoidFailed
| AttemptStatus::RouterDeclined
| AttemptStatus::CaptureFailed
| AttemptStatus::Failure => Self::Failed,
AttemptStatus::Voided => Self::Cancelled,
}
}
}

#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
Expand Down
10 changes: 10 additions & 0 deletions crates/common_utils/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,16 @@ impl Url {
pub fn get_string_repr(&self) -> &str {
self.0.as_str()
}

/// wrap the url::Url in Url type
pub fn wrap(url: url::Url) -> Self {
Self(url)
}

/// Get the inner url
pub fn into_inner(self) -> url::Url {
self.0
}
}

impl<DB> ToSql<sql_types::Text, DB> for Url
Expand Down
9 changes: 9 additions & 0 deletions crates/diesel_models/src/kv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize};

#[cfg(feature = "v2")]
use crate::payment_attempt::PaymentAttemptUpdateInternal;
#[cfg(feature = "v2")]
use crate::payment_intent::PaymentIntentUpdateInternal;
use crate::{
address::{Address, AddressNew, AddressUpdateInternal},
customers::{Customer, CustomerNew, CustomerUpdateInternal},
Expand Down Expand Up @@ -108,9 +110,16 @@ impl DBOperation {
Insertable::Mandate(m) => DBResult::Mandate(Box::new(m.insert(conn).await?)),
},
Self::Update { updatable } => match *updatable {
#[cfg(feature = "v1")]
Updateable::PaymentIntentUpdate(a) => {
DBResult::PaymentIntent(Box::new(a.orig.update(conn, a.update_data).await?))
}
#[cfg(feature = "v2")]
Updateable::PaymentIntentUpdate(a) => DBResult::PaymentIntent(Box::new(
a.orig
.update(conn, PaymentIntentUpdateInternal::from(a.update_data))
.await?,
)),
#[cfg(feature = "v1")]
Updateable::PaymentAttemptUpdate(a) => DBResult::PaymentAttempt(Box::new(
a.orig.update_with_attempt_id(conn, a.update_data).await?,
Expand Down
Loading

0 comments on commit 42bdf47

Please sign in to comment.