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 36 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
99 changes: 97 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 @@ -4503,7 +4519,7 @@ pub struct PaymentsRetrieveRequest {

/// 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 +4604,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 @@ -6234,6 +6250,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
12 changes: 12 additions & 0 deletions crates/common_utils/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,28 @@ 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 {
connector: Option<String>,
payment_id: Option<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))
}
}
2 changes: 1 addition & 1 deletion crates/diesel_models/src/payment_attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ pub struct PaymentAttemptUpdateInternal {
pub browser_info: Option<serde_json::Value>,
// payment_token: Option<String>,
pub error_code: Option<String>,
// connector_metadata: Option<serde_json::Value>,
pub connector_metadata: Option<serde_json::Value>,
jarnura marked this conversation as resolved.
Show resolved Hide resolved
// payment_method_data: Option<serde_json::Value>,
// payment_experience: Option<storage_enums::PaymentExperience>,
// preprocessing_step_id: Option<String>,
Expand Down
27 changes: 27 additions & 0 deletions crates/diesel_models/src/query/payment_attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,33 @@ impl PaymentAttempt {
.await
}

#[cfg(feature = "v2")]
pub async fn find_by_merchant_id_connector_txn_id(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub async fn find_by_merchant_id_connector_txn_id(
pub async fn find_by_merchant_id_connector_transaction_id(

conn: &PgPooledConn,
merchant_id: &common_utils::id_type::MerchantId,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be a profile based

connector_txn_id: &str,
) -> StorageResult<Self> {
let (txn_id, txn_data) = common_utils::types::ConnectorTransactionId::form_id_and_data(
connector_txn_id.to_string(),
);
let connector_transaction_id = txn_id
.get_txn_id(txn_data.as_ref())
.change_context(DatabaseError::Others)
.attach_printable_lazy(|| {
format!(
"Failed to retrieve txn_id for ({:?}, {:?})",
txn_id, txn_data
)
})?;
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
conn,
dsl::merchant_id
.eq(merchant_id.to_owned())
.and(dsl::connector_payment_id.eq(connector_transaction_id.to_owned())),
)
.await
}

#[cfg(feature = "v1")]
pub async fn find_by_merchant_id_attempt_id(
conn: &PgPooledConn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ pub trait PaymentAttemptInterface {
storage_scheme: storage_enums::MerchantStorageScheme,
) -> error_stack::Result<PaymentAttempt, errors::StorageError>;

#[cfg(feature = "v2")]
async fn find_payment_attempt_by_merchant_id_connector_txn_id(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async fn find_payment_attempt_by_merchant_id_connector_txn_id(
async fn find_payment_attempt_by_merchant_id_connector_transaction_id(

&self,
key_manager_state: &KeyManagerState,
merchant_key_store: &MerchantKeyStore,
merchant_id: &id_type::MerchantId,
connector_txn_id: &str,
_storage_scheme: storage_enums::MerchantStorageScheme,
) -> CustomResult<PaymentAttempt, errors::StorageError>;

#[cfg(feature = "v1")]
async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id(
&self,
Expand Down Expand Up @@ -1298,6 +1308,7 @@ pub enum PaymentAttemptUpdate {
connector_payment_id: Option<String>,
updated_by: String,
redirection_data: Option<router_response_types::RedirectForm>,
connector_metadata: Option<serde_json::Value>,
},
/// Update the payment attempt after force syncing with the connector
SyncUpdate {
Expand Down Expand Up @@ -1932,6 +1943,7 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
connector_payment_id: None,
connector: Some(connector),
redirection_data: None,
connector_metadata: None,
},
PaymentAttemptUpdate::ErrorUpdate {
status,
Expand All @@ -1952,12 +1964,14 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
connector_payment_id,
connector: None,
redirection_data: None,
connector_metadata: None,
},
PaymentAttemptUpdate::ConfirmIntentResponse {
status,
connector_payment_id,
updated_by,
redirection_data,
connector_metadata,
} => Self {
status: Some(status),
error_message: None,
Expand All @@ -1973,6 +1987,7 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
connector: None,
redirection_data: redirection_data
.map(diesel_models::payment_attempt::RedirectForm::from),
connector_metadata,
},
PaymentAttemptUpdate::SyncUpdate { status, updated_by } => Self {
status: Some(status),
Expand All @@ -1988,6 +2003,7 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
connector_payment_id: None,
connector: None,
redirection_data: None,
connector_metadata: None,
},
}
}
Expand Down
Loading
Loading