Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(router): add payment incoming webhooks support for v2 #6551

Open
wants to merge 65 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
110a041
feat: implement payments retrieve for v2
Narayanbhat166 Oct 29, 2024
579cec1
Merge branch 'main' into payment_sync_v2
Narayanbhat166 Oct 29, 2024
d098629
refactor: remove direct attempt access in call_connector_service
Narayanbhat166 Oct 29, 2024
186136c
chore: run formatter
hyperswitch-bot[bot] Oct 29, 2024
a5d0526
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Oct 29, 2024
d040fbc
chore: rename PaymentStatusRequest to PaymentRetrieveRequest
Narayanbhat166 Oct 30, 2024
c5e4df2
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Oct 30, 2024
ef71de3
chore: cargo clippy
Narayanbhat166 Oct 30, 2024
e50a071
refactor: use separate update enums for sync update
Narayanbhat166 Oct 30, 2024
0643a08
Merge branch 'main' into payment_sync_v2
Narayanbhat166 Oct 31, 2024
575ef18
chore: run formatter
hyperswitch-bot[bot] Oct 31, 2024
0e93355
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Oct 31, 2024
ebe079f
chore: cargo clippy
Narayanbhat166 Oct 31, 2024
ba7daba
Merge branch 'main' into payment_sync_v2
Narayanbhat166 Nov 4, 2024
1c5212c
refactor: address PR comments
Narayanbhat166 Nov 4, 2024
5709519
chore: run formatter
hyperswitch-bot[bot] Nov 4, 2024
56dab7e
refactor: move updatable trackers objects to a trait on router_data
Narayanbhat166 Nov 4, 2024
95b51f4
chore: cargo clippy
Narayanbhat166 Nov 4, 2024
940fbe0
refactor: change payments retrieve to get api and added jwt auth
Narayanbhat166 Nov 4, 2024
e832d29
refactor: allow force sync for PartiallyCapturedAndCapturable status
Narayanbhat166 Nov 4, 2024
f8ed350
chore: add a todo comment
Narayanbhat166 Nov 4, 2024
f26679e
Merge branch 'main' into payment_sync_v2
Narayanbhat166 Nov 5, 2024
252bd8e
chore: cargo cilipy
Narayanbhat166 Nov 5, 2024
002b060
Merge branch 'main' into payment_sync_v2
Narayanbhat166 Nov 5, 2024
b85b6f6
refactor: remove client secret auth and add header auth
Narayanbhat166 Nov 5, 2024
62c4479
chore: remove unused struct
Narayanbhat166 Nov 5, 2024
3248d77
Merge branch 'main' into payment_sync_v2
Narayanbhat166 Nov 6, 2024
76d872a
Merge branch 'main' into payment_sync_v2
Narayanbhat166 Nov 8, 2024
cc54284
chore: cargo clippy_v2
Narayanbhat166 Nov 8, 2024
d050efb
Merge branch 'main' into payment_sync_v2
Narayanbhat166 Nov 9, 2024
301bd59
refactor: rename authentication data to redirect form
Narayanbhat166 Nov 9, 2024
ee63776
chore: run formatter
hyperswitch-bot[bot] Nov 9, 2024
4475dab
wip: add payment sync PR and fix errors
Narayanbhat166 Nov 11, 2024
f5cd386
wip: add payment sync PR and fix errors
Narayanbhat166 Nov 11, 2024
c7feceb
chore: fix cargo run failure
hrithikesh026 Nov 11, 2024
400b356
refactor: fix errors in authentication
Narayanbhat166 Nov 11, 2024
5a6eb53
chore: cargo clippy
Narayanbhat166 Nov 11, 2024
9887b71
chore: update mintlify
Narayanbhat166 Nov 12, 2024
9a1b053
Merge branch 'main' into add_payment_sync_openapi
Narayanbhat166 Nov 12, 2024
223babf
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Nov 12, 2024
9163455
chore: cargo clippy
Narayanbhat166 Nov 12, 2024
a1bff1a
resolve conflicts
hrithikesh026 Nov 11, 2024
84e1300
chore: remove unwanted feature flagging
Narayanbhat166 Nov 12, 2024
fcd73a0
refactor: fix errors in authentication
Narayanbhat166 Nov 11, 2024
1a26d2f
wip: add payments finish redirection
Narayanbhat166 Nov 12, 2024
7671603
wip: add payments finish redirection checkpoint 2
Narayanbhat166 Nov 12, 2024
0177677
feat: add payments redirection endpoint
Narayanbhat166 Nov 12, 2024
6a9518a
refactor: test and fix minor bugs in creating finish redirection url
Narayanbhat166 Nov 12, 2024
ad1df13
chore: add status to finish redirection
Narayanbhat166 Nov 12, 2024
ddece54
refactor: move get trackers to payments_operation from payments_opera…
Narayanbhat166 Nov 13, 2024
58d24e5
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Nov 13, 2024
70f3a26
refactor: get call connector action from connector
Narayanbhat166 Nov 13, 2024
d0fcf8d
chore: run formatter
hyperswitch-bot[bot] Nov 13, 2024
76ca26f
chore: cargo clippy
Narayanbhat166 Nov 13, 2024
401515f
add payment incoming webhooks support for v2
sai-harsha-vardhan Nov 13, 2024
5311abc
resolve conflicts
sai-harsha-vardhan Nov 13, 2024
907fbc2
resolve comments
sai-harsha-vardhan Nov 13, 2024
5dbfaba
resolve conflicts
sai-harsha-vardhan Nov 13, 2024
776def6
chore: change printables and strings
Narayanbhat166 Nov 13, 2024
70915d1
chore: remove dependency graph
Narayanbhat166 Nov 13, 2024
df4d0d9
chore: remove unwanted files
Narayanbhat166 Nov 13, 2024
94aa0da
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Nov 13, 2024
c2747c4
resolve conflicts
sai-harsha-vardhan Nov 13, 2024
597da4d
resolve comments
sai-harsha-vardhan Nov 14, 2024
d887550
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Nov 14, 2024
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
8 changes: 7 additions & 1 deletion api-reference-v2/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -13392,7 +13392,8 @@
],
"nullable": true
}
}
},
"additionalProperties": false
},
"PaymentsConfirmIntentResponse": {
"type": "object",
Expand Down Expand Up @@ -15126,6 +15127,11 @@
"force_sync": {
"type": "boolean",
"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"
},
"param": {
"type": "string",
"description": "These are the query params that are sent in case of redirect response.\nThese can be ingested by the connector to take necessary actions.",
"nullable": true
}
}
},
Expand Down
6 changes: 3 additions & 3 deletions crates/api_models/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2007,7 +2007,7 @@ pub struct ProfileCreate {

/// The URL to redirect after the completion of the operation
#[schema(value_type = Option<String>, max_length = 255, example = "https://www.example.com/success")]
pub return_url: Option<url::Url>,
pub return_url: Option<common_utils::types::Url>,

/// A boolean value to indicate if payment response hash needs to be enabled
#[schema(default = true, example = true)]
Expand Down Expand Up @@ -2244,7 +2244,7 @@ pub struct ProfileResponse {

/// The URL to redirect after the completion of the operation
#[schema(value_type = Option<String>, max_length = 255, example = "https://www.example.com/success")]
pub return_url: Option<String>,
pub return_url: Option<common_utils::types::Url>,

/// A boolean value to indicate if payment response hash needs to be enabled
#[schema(default = true, example = true)]
Expand Down Expand Up @@ -2474,7 +2474,7 @@ pub struct ProfileUpdate {

/// The URL to redirect after the completion of the operation
#[schema(value_type = Option<String>, max_length = 255, example = "https://www.example.com/success")]
pub return_url: Option<url::Url>,
pub return_url: Option<common_utils::types::Url>,

/// A boolean value to indicate if payment response hash needs to be enabled
#[schema(default = true, example = true)]
Expand Down
111 changes: 109 additions & 2 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3652,6 +3652,7 @@ pub struct PaymentMethodDataResponseWithBilling {
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, ToSchema)]
#[cfg(feature = "v1")]
pub enum PaymentIdType {
/// The identifier for payment intent
PaymentIntentId(id_type::PaymentId),
Expand All @@ -3663,6 +3664,20 @@ pub enum PaymentIdType {
PreprocessingId(String),
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, ToSchema)]
#[cfg(feature = "v2")]
pub enum PaymentIdType {
/// The identifier for payment intent
PaymentIntentId(id_type::GlobalPaymentId),
/// The identifier for connector transaction
ConnectorTransactionId(String),
/// The identifier for payment attempt
PaymentAttemptId(String),
jarnura marked this conversation as resolved.
Show resolved Hide resolved
/// The identifier for preprocessing step
PreprocessingId(String),
}

#[cfg(feature = "v1")]
impl fmt::Display for PaymentIdType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Expand All @@ -3687,6 +3702,7 @@ impl fmt::Display for PaymentIdType {
}
}

#[cfg(feature = "v1")]
impl Default for PaymentIdType {
fn default() -> Self {
Self::PaymentIntentId(Default::default())
Expand Down Expand Up @@ -4459,6 +4475,7 @@ pub struct PaymentsResponse {
/// Request for Payment Intent Confirm
#[cfg(feature = "v2")]
#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)]
#[serde(deny_unknown_fields)]
pub struct PaymentsConfirmIntentRequest {
/// The URL to which you want the user to be redirected after the completion of the payment operation
/// If this url is not passed, the url configured in the business profile will be used
Expand Down Expand Up @@ -4499,11 +4516,15 @@ pub struct PaymentsRetrieveRequest {
/// If this is set to true, the status will be fetched from the connector
#[serde(default)]
pub force_sync: bool,

/// These are the query params that are sent in case of redirect response.
/// These can be ingested by the connector to take necessary actions.
pub param: Option<String>,
}

/// Error details for the payment
#[cfg(feature = "v2")]
#[derive(Debug, serde::Serialize, ToSchema)]
#[derive(Debug, serde::Serialize, Clone, ToSchema)]
pub struct ErrorDetails {
/// The error code
pub code: String,
Expand Down Expand Up @@ -4588,7 +4609,7 @@ pub struct PaymentsConfirmIntentResponse {
// TODO: have a separate response for detailed, summarized
/// Response for Payment Intent Confirm
#[cfg(feature = "v2")]
#[derive(Debug, serde::Serialize, ToSchema)]
#[derive(Debug, serde::Serialize, Clone, ToSchema)]
pub struct PaymentsRetrieveResponse {
/// Unique identifier for the payment. This ensures idempotency for multiple payments
/// that have been done by a single merchant.
Expand Down Expand Up @@ -5147,6 +5168,7 @@ pub struct PgRedirectResponse {
pub amount: Option<MinorUnit>,
}

#[cfg(feature = "v1")]
#[derive(Debug, serde::Serialize, PartialEq, Eq, serde::Deserialize)]
pub struct RedirectionResponse {
pub return_url: String,
Expand All @@ -5156,6 +5178,12 @@ pub struct RedirectionResponse {
pub headers: Vec<(String, String)>,
}

#[cfg(feature = "v2")]
#[derive(Debug, serde::Serialize, PartialEq, Eq, serde::Deserialize)]
pub struct RedirectionResponse {
pub return_url_with_query_params: String,
}

#[derive(Debug, serde::Deserialize)]
pub struct PaymentsResponseForm {
pub transaction_id: String,
Expand Down Expand Up @@ -6234,6 +6262,85 @@ pub struct FrmMessage {
pub frm_error: Option<String>,
}

#[cfg(feature = "v2")]
mod payment_id_type {
use std::{borrow::Cow, fmt};

use serde::{
de::{self, Visitor},
Deserializer,
};

use super::PaymentIdType;

struct PaymentIdVisitor;
struct OptionalPaymentIdVisitor;

impl<'de> Visitor<'de> for PaymentIdVisitor {
type Value = PaymentIdType;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("payment id")
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
common_utils::id_type::GlobalPaymentId::try_from(Cow::Owned(value.to_string()))
.map_err(de::Error::custom)
.map(PaymentIdType::PaymentIntentId)
}
}

impl<'de> Visitor<'de> for OptionalPaymentIdVisitor {
type Value = Option<PaymentIdType>;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("payment id")
}

fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(PaymentIdVisitor).map(Some)
}

fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}

fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
}

#[allow(dead_code)]
pub(crate) fn deserialize<'a, D>(deserializer: D) -> Result<PaymentIdType, D::Error>
where
D: Deserializer<'a>,
{
deserializer.deserialize_any(PaymentIdVisitor)
}

pub(crate) fn deserialize_option<'a, D>(
deserializer: D,
) -> Result<Option<PaymentIdType>, D::Error>
where
D: Deserializer<'a>,
{
deserializer.deserialize_option(OptionalPaymentIdVisitor)
}
}

#[cfg(feature = "v1")]
mod payment_id_type {
use std::{borrow::Cow, fmt};

Expand Down
51 changes: 51 additions & 0 deletions crates/api_models/src/webhooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,25 +76,45 @@ pub enum WebhookFlow {
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
/// This enum tells about the affect a webhook had on an object
pub enum WebhookResponseTracker {
#[cfg(feature = "v1")]
Payment {
payment_id: common_utils::id_type::PaymentId,
status: common_enums::IntentStatus,
},
#[cfg(feature = "v2")]
Payment {
payment_id: common_utils::id_type::GlobalPaymentId,
status: common_enums::IntentStatus,
},
#[cfg(feature = "payouts")]
Payout {
payout_id: String,
status: common_enums::PayoutStatus,
},
#[cfg(feature = "v1")]
Refund {
payment_id: common_utils::id_type::PaymentId,
refund_id: String,
status: common_enums::RefundStatus,
},
#[cfg(feature = "v2")]
Refund {
payment_id: common_utils::id_type::GlobalPaymentId,
refund_id: String,
status: common_enums::RefundStatus,
},
#[cfg(feature = "v1")]
Dispute {
dispute_id: String,
payment_id: common_utils::id_type::PaymentId,
status: common_enums::DisputeStatus,
},
#[cfg(feature = "v2")]
Dispute {
dispute_id: String,
payment_id: common_utils::id_type::GlobalPaymentId,
status: common_enums::DisputeStatus,
},
Mandate {
mandate_id: String,
status: common_enums::MandateStatus,
Expand All @@ -103,6 +123,7 @@ pub enum WebhookResponseTracker {
}

impl WebhookResponseTracker {
#[cfg(feature = "v1")]
pub fn get_payment_id(&self) -> Option<common_utils::id_type::PaymentId> {
match self {
Self::Payment { payment_id, .. }
Expand All @@ -113,6 +134,18 @@ impl WebhookResponseTracker {
Self::Payout { .. } => None,
}
}

#[cfg(feature = "v2")]
pub fn get_payment_id(&self) -> Option<common_utils::id_type::GlobalPaymentId> {
match self {
Self::Payment { payment_id, .. }
| Self::Refund { payment_id, .. }
| Self::Dispute { payment_id, .. } => Some(payment_id.to_owned()),
Self::NoEffect | Self::Mandate { .. } => None,
#[cfg(feature = "payouts")]
Self::Payout { .. } => None,
}
}
}

impl From<IncomingWebhookEvent> for WebhookFlow {
Expand Down Expand Up @@ -227,6 +260,7 @@ pub struct OutgoingWebhook {

#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(tag = "type", content = "object", rename_all = "snake_case")]
#[cfg(feature = "v1")]
pub enum OutgoingWebhookContent {
#[schema(value_type = PaymentsResponse, title = "PaymentsResponse")]
PaymentDetails(Box<payments::PaymentsResponse>),
Expand All @@ -241,6 +275,23 @@ pub enum OutgoingWebhookContent {
PayoutDetails(Box<payouts::PayoutCreateResponse>),
}

#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(tag = "type", content = "object", rename_all = "snake_case")]
#[cfg(feature = "v2")]
pub enum OutgoingWebhookContent {
#[schema(value_type = PaymentsResponse, title = "PaymentsResponse")]
PaymentDetails(Box<payments::PaymentsRetrieveResponse>),
#[schema(value_type = RefundResponse, title = "RefundResponse")]
RefundDetails(Box<refunds::RefundResponse>),
#[schema(value_type = DisputeResponse, title = "DisputeResponse")]
DisputeDetails(Box<disputes::DisputeResponse>),
#[schema(value_type = MandateResponse, title = "MandateResponse")]
MandateDetails(Box<mandates::MandateResponse>),
#[cfg(feature = "payouts")]
#[schema(value_type = PayoutCreateResponse, title = "PayoutCreateResponse")]
PayoutDetails(Box<payouts::PayoutCreateResponse>),
}

#[derive(Debug, Clone, Serialize)]
pub struct ConnectorWebhookSecrets {
pub secret: Vec<u8>,
Expand Down
3 changes: 3 additions & 0 deletions crates/common_utils/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ pub const MAX_STATEMENT_DESCRIPTOR_LENGTH: u16 = 22;
/// Payout flow identifier used for performing GSM operations
pub const PAYOUT_FLOW_STR: &str = "payout_flow";

/// length of the publishable key
pub const PUBLISHABLE_KEY_LENGTH: u16 = 39;

/// The number of bytes allocated for the hashed connector transaction ID.
/// Total number of characters equals CONNECTOR_TRANSACTION_ID_HASH_BYTES times 2.
pub const CONNECTOR_TRANSACTION_ID_HASH_BYTES: usize = 25;
11 changes: 11 additions & 0 deletions crates/common_utils/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,27 @@ pub enum ApiEventsType {
PaymentMethodList {
payment_id: Option<String>,
},
#[cfg(feature = "v1")]
Webhooks {
connector: String,
payment_id: Option<id_type::PaymentId>,
},
#[cfg(feature = "v2")]
Webhooks {
connector: id_type::MerchantConnectorAccountId,
payment_id: Option<id_type::GlobalPaymentId>,
},
Routing,
ResourceListAPI,
#[cfg(feature = "v1")]
PaymentRedirectionResponse {
connector: Option<String>,
payment_id: Option<id_type::PaymentId>,
},
#[cfg(feature = "v2")]
PaymentRedirectionResponse {
payment_id: id_type::GlobalPaymentId,
},
Gsm,
// TODO: This has to be removed once the corresponding apiEventTypes are created
Miscellaneous,
Expand Down
13 changes: 13 additions & 0 deletions crates/common_utils/src/id_type/global_id/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,16 @@ impl GlobalAttemptId {
self.0.get_string_repr()
}
}

impl TryFrom<std::borrow::Cow<'static, str>> for GlobalAttemptId {
type Error = error_stack::Report<errors::ValidationError>;
fn try_from(value: std::borrow::Cow<'static, str>) -> Result<Self, Self::Error> {
use error_stack::ResultExt;
let global_attempt_id = super::GlobalId::from_string(value).change_context(
errors::ValidationError::IncorrectValueProvided {
field_name: "payment_id",
},
)?;
Ok(Self(global_attempt_id))
}
}
Loading
Loading