mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 09:38:33 +08:00
feat: Implement subscriptions workflow and incoming webhook support (#9400)
Co-authored-by: Prajjwal kumar <write2prajjwal@gmail.com> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Prajjwal Kumar <prajjwal.kumar@juspay.in>
This commit is contained in:
@ -67,6 +67,7 @@ pub enum IncomingWebhookEvent {
|
|||||||
#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
|
#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
|
||||||
RecoveryInvoiceCancel,
|
RecoveryInvoiceCancel,
|
||||||
SetupWebhook,
|
SetupWebhook,
|
||||||
|
InvoiceGenerated,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IncomingWebhookEvent {
|
impl IncomingWebhookEvent {
|
||||||
@ -300,6 +301,7 @@ impl From<IncomingWebhookEvent> for WebhookFlow {
|
|||||||
| IncomingWebhookEvent::RecoveryPaymentPending
|
| IncomingWebhookEvent::RecoveryPaymentPending
|
||||||
| IncomingWebhookEvent::RecoveryPaymentSuccess => Self::Recovery,
|
| IncomingWebhookEvent::RecoveryPaymentSuccess => Self::Recovery,
|
||||||
IncomingWebhookEvent::SetupWebhook => Self::Setup,
|
IncomingWebhookEvent::SetupWebhook => Self::Setup,
|
||||||
|
IncomingWebhookEvent::InvoiceGenerated => Self::Subscription,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -341,6 +343,7 @@ pub enum ObjectReferenceId {
|
|||||||
PayoutId(PayoutIdType),
|
PayoutId(PayoutIdType),
|
||||||
#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
|
#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
|
||||||
InvoiceId(InvoiceIdType),
|
InvoiceId(InvoiceIdType),
|
||||||
|
SubscriptionId(common_utils::id_type::SubscriptionId),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
|
#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
|
||||||
@ -388,7 +391,12 @@ impl ObjectReferenceId {
|
|||||||
common_utils::errors::ValidationError::IncorrectValueProvided {
|
common_utils::errors::ValidationError::IncorrectValueProvided {
|
||||||
field_name: "PaymentId is required but received InvoiceId",
|
field_name: "PaymentId is required but received InvoiceId",
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
|
Self::SubscriptionId(_) => Err(
|
||||||
|
common_utils::errors::ValidationError::IncorrectValueProvided {
|
||||||
|
field_name: "PaymentId is required but received SubscriptionId",
|
||||||
|
},
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,6 @@ use common_utils::{
|
|||||||
request::{Method, Request, RequestBuilder, RequestContent},
|
request::{Method, Request, RequestBuilder, RequestContent},
|
||||||
types::{AmountConvertor, MinorUnit, MinorUnitForConnector},
|
types::{AmountConvertor, MinorUnit, MinorUnitForConnector},
|
||||||
};
|
};
|
||||||
#[cfg(feature = "v1")]
|
|
||||||
use error_stack::report;
|
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
|
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
|
||||||
use hyperswitch_domain_models::{revenue_recovery, router_data_v2::RouterDataV2};
|
use hyperswitch_domain_models::{revenue_recovery, router_data_v2::RouterDataV2};
|
||||||
@ -1319,17 +1317,25 @@ impl webhooks::IncomingWebhook for Chargebee {
|
|||||||
chargebee::ChargebeeInvoiceBody::get_invoice_webhook_data_from_body(request.body)
|
chargebee::ChargebeeInvoiceBody::get_invoice_webhook_data_from_body(request.body)
|
||||||
.change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
|
.change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
|
||||||
Ok(api_models::webhooks::ObjectReferenceId::InvoiceId(
|
Ok(api_models::webhooks::ObjectReferenceId::InvoiceId(
|
||||||
api_models::webhooks::InvoiceIdType::ConnectorInvoiceId(webhook.content.invoice.id),
|
api_models::webhooks::InvoiceIdType::ConnectorInvoiceId(
|
||||||
|
webhook.content.invoice.id.get_string_repr().to_string(),
|
||||||
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
#[cfg(any(feature = "v1", not(all(feature = "revenue_recovery", feature = "v2"))))]
|
#[cfg(any(feature = "v1", not(all(feature = "revenue_recovery", feature = "v2"))))]
|
||||||
fn get_webhook_object_reference_id(
|
fn get_webhook_object_reference_id(
|
||||||
&self,
|
&self,
|
||||||
_request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
||||||
) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
|
) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
|
||||||
Err(report!(errors::ConnectorError::WebhooksNotImplemented))
|
let webhook =
|
||||||
|
chargebee::ChargebeeInvoiceBody::get_invoice_webhook_data_from_body(request.body)
|
||||||
|
.change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
|
||||||
|
|
||||||
|
let subscription_id = webhook.content.invoice.subscription_id;
|
||||||
|
Ok(api_models::webhooks::ObjectReferenceId::SubscriptionId(
|
||||||
|
subscription_id,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
|
|
||||||
fn get_webhook_event_type(
|
fn get_webhook_event_type(
|
||||||
&self,
|
&self,
|
||||||
request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
||||||
@ -1340,13 +1346,6 @@ impl webhooks::IncomingWebhook for Chargebee {
|
|||||||
let event = api_models::webhooks::IncomingWebhookEvent::from(webhook.event_type);
|
let event = api_models::webhooks::IncomingWebhookEvent::from(webhook.event_type);
|
||||||
Ok(event)
|
Ok(event)
|
||||||
}
|
}
|
||||||
#[cfg(any(feature = "v1", not(all(feature = "revenue_recovery", feature = "v2"))))]
|
|
||||||
fn get_webhook_event_type(
|
|
||||||
&self,
|
|
||||||
_request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
|
||||||
) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
|
|
||||||
Err(report!(errors::ConnectorError::WebhooksNotImplemented))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_webhook_resource_object(
|
fn get_webhook_resource_object(
|
||||||
&self,
|
&self,
|
||||||
@ -1375,6 +1374,36 @@ impl webhooks::IncomingWebhook for Chargebee {
|
|||||||
transformers::ChargebeeInvoiceBody::get_invoice_webhook_data_from_body(request.body)?;
|
transformers::ChargebeeInvoiceBody::get_invoice_webhook_data_from_body(request.body)?;
|
||||||
revenue_recovery::RevenueRecoveryInvoiceData::try_from(webhook)
|
revenue_recovery::RevenueRecoveryInvoiceData::try_from(webhook)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_subscription_mit_payment_data(
|
||||||
|
&self,
|
||||||
|
request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
||||||
|
) -> CustomResult<
|
||||||
|
hyperswitch_domain_models::router_flow_types::SubscriptionMitPaymentData,
|
||||||
|
errors::ConnectorError,
|
||||||
|
> {
|
||||||
|
let webhook_body =
|
||||||
|
transformers::ChargebeeInvoiceBody::get_invoice_webhook_data_from_body(request.body)
|
||||||
|
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)
|
||||||
|
.attach_printable("Failed to parse Chargebee invoice webhook body")?;
|
||||||
|
|
||||||
|
let chargebee_mit_data = transformers::ChargebeeMitPaymentData::try_from(webhook_body)
|
||||||
|
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)
|
||||||
|
.attach_printable("Failed to extract MIT payment data from Chargebee webhook")?;
|
||||||
|
|
||||||
|
// Convert Chargebee-specific data to generic domain model
|
||||||
|
Ok(
|
||||||
|
hyperswitch_domain_models::router_flow_types::SubscriptionMitPaymentData {
|
||||||
|
invoice_id: chargebee_mit_data.invoice_id,
|
||||||
|
amount_due: chargebee_mit_data.amount_due,
|
||||||
|
currency_code: chargebee_mit_data.currency_code,
|
||||||
|
status: chargebee_mit_data.status.map(|s| s.into()),
|
||||||
|
customer_id: chargebee_mit_data.customer_id,
|
||||||
|
subscription_id: chargebee_mit_data.subscription_id,
|
||||||
|
first_invoice: chargebee_mit_data.first_invoice,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static CHARGEBEE_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
|
static CHARGEBEE_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use common_enums::{connector_enums, enums};
|
|||||||
use common_utils::{
|
use common_utils::{
|
||||||
errors::CustomResult,
|
errors::CustomResult,
|
||||||
ext_traits::ByteSliceExt,
|
ext_traits::ByteSliceExt,
|
||||||
id_type::{CustomerId, SubscriptionId},
|
id_type::{CustomerId, InvoiceId, SubscriptionId},
|
||||||
pii::{self, Email},
|
pii::{self, Email},
|
||||||
types::MinorUnit,
|
types::MinorUnit,
|
||||||
};
|
};
|
||||||
@ -461,17 +461,21 @@ pub enum ChargebeeEventType {
|
|||||||
PaymentSucceeded,
|
PaymentSucceeded,
|
||||||
PaymentFailed,
|
PaymentFailed,
|
||||||
InvoiceDeleted,
|
InvoiceDeleted,
|
||||||
|
InvoiceGenerated,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct ChargebeeInvoiceData {
|
pub struct ChargebeeInvoiceData {
|
||||||
// invoice id
|
// invoice id
|
||||||
pub id: String,
|
pub id: InvoiceId,
|
||||||
pub total: MinorUnit,
|
pub total: MinorUnit,
|
||||||
pub currency_code: enums::Currency,
|
pub currency_code: enums::Currency,
|
||||||
pub status: Option<ChargebeeInvoiceStatus>,
|
pub status: Option<ChargebeeInvoiceStatus>,
|
||||||
pub billing_address: Option<ChargebeeInvoiceBillingAddress>,
|
pub billing_address: Option<ChargebeeInvoiceBillingAddress>,
|
||||||
pub linked_payments: Option<Vec<ChargebeeInvoicePayments>>,
|
pub linked_payments: Option<Vec<ChargebeeInvoicePayments>>,
|
||||||
|
pub customer_id: CustomerId,
|
||||||
|
pub subscription_id: SubscriptionId,
|
||||||
|
pub first_invoice: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
@ -585,7 +589,35 @@ impl ChargebeeInvoiceBody {
|
|||||||
Ok(webhook_body)
|
Ok(webhook_body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Structure to extract MIT payment data from invoice_generated webhook
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ChargebeeMitPaymentData {
|
||||||
|
pub invoice_id: InvoiceId,
|
||||||
|
pub amount_due: MinorUnit,
|
||||||
|
pub currency_code: enums::Currency,
|
||||||
|
pub status: Option<ChargebeeInvoiceStatus>,
|
||||||
|
pub customer_id: CustomerId,
|
||||||
|
pub subscription_id: SubscriptionId,
|
||||||
|
pub first_invoice: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<ChargebeeInvoiceBody> for ChargebeeMitPaymentData {
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
|
||||||
|
fn try_from(webhook_body: ChargebeeInvoiceBody) -> Result<Self, Self::Error> {
|
||||||
|
let invoice = webhook_body.content.invoice;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
invoice_id: invoice.id,
|
||||||
|
amount_due: invoice.total,
|
||||||
|
currency_code: invoice.currency_code,
|
||||||
|
status: invoice.status,
|
||||||
|
customer_id: invoice.customer_id,
|
||||||
|
subscription_id: invoice.subscription_id,
|
||||||
|
first_invoice: invoice.first_invoice.unwrap_or(false),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
pub struct ChargebeeMandateDetails {
|
pub struct ChargebeeMandateDetails {
|
||||||
pub customer_id: String,
|
pub customer_id: String,
|
||||||
pub mandate_id: String,
|
pub mandate_id: String,
|
||||||
@ -620,8 +652,9 @@ impl TryFrom<ChargebeeWebhookBody> for revenue_recovery::RevenueRecoveryAttemptD
|
|||||||
fn try_from(item: ChargebeeWebhookBody) -> Result<Self, Self::Error> {
|
fn try_from(item: ChargebeeWebhookBody) -> Result<Self, Self::Error> {
|
||||||
let amount = item.content.transaction.amount;
|
let amount = item.content.transaction.amount;
|
||||||
let currency = item.content.transaction.currency_code.to_owned();
|
let currency = item.content.transaction.currency_code.to_owned();
|
||||||
let merchant_reference_id =
|
let merchant_reference_id = common_utils::id_type::PaymentReferenceId::from_str(
|
||||||
common_utils::id_type::PaymentReferenceId::from_str(&item.content.invoice.id)
|
item.content.invoice.id.get_string_repr(),
|
||||||
|
)
|
||||||
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
|
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
|
||||||
let connector_transaction_id = item
|
let connector_transaction_id = item
|
||||||
.content
|
.content
|
||||||
@ -746,6 +779,19 @@ impl From<ChargebeeEventType> for api_models::webhooks::IncomingWebhookEvent {
|
|||||||
ChargebeeEventType::PaymentSucceeded => Self::RecoveryPaymentSuccess,
|
ChargebeeEventType::PaymentSucceeded => Self::RecoveryPaymentSuccess,
|
||||||
ChargebeeEventType::PaymentFailed => Self::RecoveryPaymentFailure,
|
ChargebeeEventType::PaymentFailed => Self::RecoveryPaymentFailure,
|
||||||
ChargebeeEventType::InvoiceDeleted => Self::RecoveryInvoiceCancel,
|
ChargebeeEventType::InvoiceDeleted => Self::RecoveryInvoiceCancel,
|
||||||
|
ChargebeeEventType::InvoiceGenerated => Self::InvoiceGenerated,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "v1")]
|
||||||
|
impl From<ChargebeeEventType> for api_models::webhooks::IncomingWebhookEvent {
|
||||||
|
fn from(event: ChargebeeEventType) -> Self {
|
||||||
|
match event {
|
||||||
|
ChargebeeEventType::PaymentSucceeded => Self::PaymentIntentSuccess,
|
||||||
|
ChargebeeEventType::PaymentFailed => Self::PaymentIntentFailure,
|
||||||
|
ChargebeeEventType::InvoiceDeleted => Self::EventNotSupported,
|
||||||
|
ChargebeeEventType::InvoiceGenerated => Self::InvoiceGenerated,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -754,8 +800,9 @@ impl From<ChargebeeEventType> for api_models::webhooks::IncomingWebhookEvent {
|
|||||||
impl TryFrom<ChargebeeInvoiceBody> for revenue_recovery::RevenueRecoveryInvoiceData {
|
impl TryFrom<ChargebeeInvoiceBody> for revenue_recovery::RevenueRecoveryInvoiceData {
|
||||||
type Error = error_stack::Report<errors::ConnectorError>;
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
fn try_from(item: ChargebeeInvoiceBody) -> Result<Self, Self::Error> {
|
fn try_from(item: ChargebeeInvoiceBody) -> Result<Self, Self::Error> {
|
||||||
let merchant_reference_id =
|
let merchant_reference_id = common_utils::id_type::PaymentReferenceId::from_str(
|
||||||
common_utils::id_type::PaymentReferenceId::from_str(&item.content.invoice.id)
|
item.content.invoice.id.get_string_repr(),
|
||||||
|
)
|
||||||
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
|
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
|
||||||
|
|
||||||
// The retry count will never exceed u16 limit in a billing connector. It can have maximum of 12 in case of charge bee so its ok to suppress this
|
// The retry count will never exceed u16 limit in a billing connector. It can have maximum of 12 in case of charge bee so its ok to suppress this
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
use common_enums::connector_enums::InvoiceStatus;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SubscriptionCreate;
|
pub struct SubscriptionCreate;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -8,3 +9,15 @@ pub struct GetSubscriptionPlanPrices;
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GetSubscriptionEstimate;
|
pub struct GetSubscriptionEstimate;
|
||||||
|
|
||||||
|
/// Generic structure for subscription MIT (Merchant Initiated Transaction) payment data
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SubscriptionMitPaymentData {
|
||||||
|
pub invoice_id: common_utils::id_type::InvoiceId,
|
||||||
|
pub amount_due: common_utils::types::MinorUnit,
|
||||||
|
pub currency_code: common_enums::enums::Currency,
|
||||||
|
pub status: Option<InvoiceStatus>,
|
||||||
|
pub customer_id: common_utils::id_type::CustomerId,
|
||||||
|
pub subscription_id: common_utils::id_type::SubscriptionId,
|
||||||
|
pub first_invoice: bool,
|
||||||
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ pub struct SubscriptionCreateResponse {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize)]
|
#[derive(Debug, Clone, serde::Serialize)]
|
||||||
pub struct SubscriptionInvoiceData {
|
pub struct SubscriptionInvoiceData {
|
||||||
pub id: String,
|
pub id: id_type::InvoiceId,
|
||||||
pub total: MinorUnit,
|
pub total: MinorUnit,
|
||||||
pub currency_code: Currency,
|
pub currency_code: Currency,
|
||||||
pub status: Option<common_enums::connector_enums::InvoiceStatus>,
|
pub status: Option<common_enums::connector_enums::InvoiceStatus>,
|
||||||
|
|||||||
@ -416,6 +416,18 @@ impl IncomingWebhook for ConnectorEnum {
|
|||||||
Self::New(connector) => connector.get_revenue_recovery_attempt_details(request),
|
Self::New(connector) => connector.get_revenue_recovery_attempt_details(request),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn get_subscription_mit_payment_data(
|
||||||
|
&self,
|
||||||
|
request: &IncomingWebhookRequestDetails<'_>,
|
||||||
|
) -> CustomResult<
|
||||||
|
hyperswitch_domain_models::router_flow_types::SubscriptionMitPaymentData,
|
||||||
|
errors::ConnectorError,
|
||||||
|
> {
|
||||||
|
match self {
|
||||||
|
Self::Old(connector) => connector.get_subscription_mit_payment_data(request),
|
||||||
|
Self::New(connector) => connector.get_subscription_mit_payment_data(request),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectorRedirectResponse for ConnectorEnum {
|
impl ConnectorRedirectResponse for ConnectorEnum {
|
||||||
|
|||||||
@ -301,4 +301,18 @@ pub trait IncomingWebhook: ConnectorCommon + Sync {
|
|||||||
)
|
)
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// get subscription MIT payment data from webhook
|
||||||
|
fn get_subscription_mit_payment_data(
|
||||||
|
&self,
|
||||||
|
_request: &IncomingWebhookRequestDetails<'_>,
|
||||||
|
) -> CustomResult<
|
||||||
|
hyperswitch_domain_models::router_flow_types::SubscriptionMitPaymentData,
|
||||||
|
errors::ConnectorError,
|
||||||
|
> {
|
||||||
|
Err(errors::ConnectorError::NotImplemented(
|
||||||
|
"get_subscription_mit_payment_data method".to_string(),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,7 +42,7 @@ pub async fn create_subscription(
|
|||||||
let billing_handler =
|
let billing_handler =
|
||||||
BillingHandler::create(&state, &merchant_context, customer, profile.clone()).await?;
|
BillingHandler::create(&state, &merchant_context, customer, profile.clone()).await?;
|
||||||
|
|
||||||
let subscription_handler = SubscriptionHandler::new(&state, &merchant_context, profile);
|
let subscription_handler = SubscriptionHandler::new(&state, &merchant_context);
|
||||||
let mut subscription = subscription_handler
|
let mut subscription = subscription_handler
|
||||||
.create_subscription_entry(
|
.create_subscription_entry(
|
||||||
subscription_id,
|
subscription_id,
|
||||||
@ -50,10 +50,11 @@ pub async fn create_subscription(
|
|||||||
billing_handler.connector_data.connector_name,
|
billing_handler.connector_data.connector_name,
|
||||||
billing_handler.merchant_connector_id.clone(),
|
billing_handler.merchant_connector_id.clone(),
|
||||||
request.merchant_reference_id.clone(),
|
request.merchant_reference_id.clone(),
|
||||||
|
&profile.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.attach_printable("subscriptions: failed to create subscription entry")?;
|
.attach_printable("subscriptions: failed to create subscription entry")?;
|
||||||
let invoice_handler = subscription.get_invoice_handler();
|
let invoice_handler = subscription.get_invoice_handler(profile.clone());
|
||||||
let payment = invoice_handler
|
let payment = invoice_handler
|
||||||
.create_payment_with_confirm_false(subscription.handler.state, &request)
|
.create_payment_with_confirm_false(subscription.handler.state, &request)
|
||||||
.await
|
.await
|
||||||
@ -107,7 +108,7 @@ pub async fn create_and_confirm_subscription(
|
|||||||
|
|
||||||
let billing_handler =
|
let billing_handler =
|
||||||
BillingHandler::create(&state, &merchant_context, customer, profile.clone()).await?;
|
BillingHandler::create(&state, &merchant_context, customer, profile.clone()).await?;
|
||||||
let subscription_handler = SubscriptionHandler::new(&state, &merchant_context, profile.clone());
|
let subscription_handler = SubscriptionHandler::new(&state, &merchant_context);
|
||||||
let mut subs_handler = subscription_handler
|
let mut subs_handler = subscription_handler
|
||||||
.create_subscription_entry(
|
.create_subscription_entry(
|
||||||
subscription_id.clone(),
|
subscription_id.clone(),
|
||||||
@ -115,10 +116,11 @@ pub async fn create_and_confirm_subscription(
|
|||||||
billing_handler.connector_data.connector_name,
|
billing_handler.connector_data.connector_name,
|
||||||
billing_handler.merchant_connector_id.clone(),
|
billing_handler.merchant_connector_id.clone(),
|
||||||
request.merchant_reference_id.clone(),
|
request.merchant_reference_id.clone(),
|
||||||
|
&profile.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.attach_printable("subscriptions: failed to create subscription entry")?;
|
.attach_printable("subscriptions: failed to create subscription entry")?;
|
||||||
let invoice_handler = subs_handler.get_invoice_handler();
|
let invoice_handler = subs_handler.get_invoice_handler(profile.clone());
|
||||||
|
|
||||||
let _customer_create_response = billing_handler
|
let _customer_create_response = billing_handler
|
||||||
.create_customer_on_connector(
|
.create_customer_on_connector(
|
||||||
@ -210,9 +212,9 @@ pub async fn confirm_subscription(
|
|||||||
.await
|
.await
|
||||||
.attach_printable("subscriptions: failed to find customer")?;
|
.attach_printable("subscriptions: failed to find customer")?;
|
||||||
|
|
||||||
let handler = SubscriptionHandler::new(&state, &merchant_context, profile.clone());
|
let handler = SubscriptionHandler::new(&state, &merchant_context);
|
||||||
let mut subscription_entry = handler.find_subscription(subscription_id).await?;
|
let mut subscription_entry = handler.find_subscription(subscription_id).await?;
|
||||||
let invoice_handler = subscription_entry.get_invoice_handler();
|
let invoice_handler = subscription_entry.get_invoice_handler(profile.clone());
|
||||||
let invoice = invoice_handler
|
let invoice = invoice_handler
|
||||||
.get_latest_invoice(&state)
|
.get_latest_invoice(&state)
|
||||||
.await
|
.await
|
||||||
@ -230,8 +232,8 @@ pub async fn confirm_subscription(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let billing_handler =
|
let billing_handler =
|
||||||
BillingHandler::create(&state, &merchant_context, customer, profile).await?;
|
BillingHandler::create(&state, &merchant_context, customer, profile.clone()).await?;
|
||||||
let invoice_handler = subscription_entry.get_invoice_handler();
|
let invoice_handler = subscription_entry.get_invoice_handler(profile);
|
||||||
let subscription = subscription_entry.subscription.clone();
|
let subscription = subscription_entry.subscription.clone();
|
||||||
|
|
||||||
let _customer_create_response = billing_handler
|
let _customer_create_response = billing_handler
|
||||||
@ -296,13 +298,13 @@ pub async fn get_subscription(
|
|||||||
profile_id: common_utils::id_type::ProfileId,
|
profile_id: common_utils::id_type::ProfileId,
|
||||||
subscription_id: common_utils::id_type::SubscriptionId,
|
subscription_id: common_utils::id_type::SubscriptionId,
|
||||||
) -> RouterResponse<SubscriptionResponse> {
|
) -> RouterResponse<SubscriptionResponse> {
|
||||||
let profile =
|
let _profile =
|
||||||
SubscriptionHandler::find_business_profile(&state, &merchant_context, &profile_id)
|
SubscriptionHandler::find_business_profile(&state, &merchant_context, &profile_id)
|
||||||
.await
|
.await
|
||||||
.attach_printable(
|
.attach_printable(
|
||||||
"subscriptions: failed to find business profile in get_subscription",
|
"subscriptions: failed to find business profile in get_subscription",
|
||||||
)?;
|
)?;
|
||||||
let handler = SubscriptionHandler::new(&state, &merchant_context, profile);
|
let handler = SubscriptionHandler::new(&state, &merchant_context);
|
||||||
let subscription = handler
|
let subscription = handler
|
||||||
.find_subscription(subscription_id)
|
.find_subscription(subscription_id)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@ -14,24 +14,23 @@ use hyperswitch_domain_models::{
|
|||||||
use masking::Secret;
|
use masking::Secret;
|
||||||
|
|
||||||
use super::errors;
|
use super::errors;
|
||||||
use crate::{core::subscription::invoice_handler::InvoiceHandler, routes::SessionState};
|
use crate::{
|
||||||
|
core::{errors::StorageErrorExt, subscription::invoice_handler::InvoiceHandler},
|
||||||
|
db::CustomResult,
|
||||||
|
routes::SessionState,
|
||||||
|
types::domain,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct SubscriptionHandler<'a> {
|
pub struct SubscriptionHandler<'a> {
|
||||||
pub state: &'a SessionState,
|
pub state: &'a SessionState,
|
||||||
pub merchant_context: &'a MerchantContext,
|
pub merchant_context: &'a MerchantContext,
|
||||||
pub profile: hyperswitch_domain_models::business_profile::Profile,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SubscriptionHandler<'a> {
|
impl<'a> SubscriptionHandler<'a> {
|
||||||
pub fn new(
|
pub fn new(state: &'a SessionState, merchant_context: &'a MerchantContext) -> Self {
|
||||||
state: &'a SessionState,
|
|
||||||
merchant_context: &'a MerchantContext,
|
|
||||||
profile: hyperswitch_domain_models::business_profile::Profile,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
state,
|
state,
|
||||||
merchant_context,
|
merchant_context,
|
||||||
profile,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +42,7 @@ impl<'a> SubscriptionHandler<'a> {
|
|||||||
billing_processor: connector_enums::Connector,
|
billing_processor: connector_enums::Connector,
|
||||||
merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId,
|
merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId,
|
||||||
merchant_reference_id: Option<String>,
|
merchant_reference_id: Option<String>,
|
||||||
|
profile: &hyperswitch_domain_models::business_profile::Profile,
|
||||||
) -> errors::RouterResult<SubscriptionWithHandler<'_>> {
|
) -> errors::RouterResult<SubscriptionWithHandler<'_>> {
|
||||||
let store = self.state.store.clone();
|
let store = self.state.store.clone();
|
||||||
let db = store.as_ref();
|
let db = store.as_ref();
|
||||||
@ -61,7 +61,7 @@ impl<'a> SubscriptionHandler<'a> {
|
|||||||
.clone(),
|
.clone(),
|
||||||
customer_id.clone(),
|
customer_id.clone(),
|
||||||
None,
|
None,
|
||||||
self.profile.get_id().clone(),
|
profile.get_id().clone(),
|
||||||
merchant_reference_id,
|
merchant_reference_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -76,7 +76,6 @@ impl<'a> SubscriptionHandler<'a> {
|
|||||||
Ok(SubscriptionWithHandler {
|
Ok(SubscriptionWithHandler {
|
||||||
handler: self,
|
handler: self,
|
||||||
subscription: new_subscription,
|
subscription: new_subscription,
|
||||||
profile: self.profile.clone(),
|
|
||||||
merchant_account: self.merchant_context.get_merchant_account().clone(),
|
merchant_account: self.merchant_context.get_merchant_account().clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -145,7 +144,6 @@ impl<'a> SubscriptionHandler<'a> {
|
|||||||
Ok(SubscriptionWithHandler {
|
Ok(SubscriptionWithHandler {
|
||||||
handler: self,
|
handler: self,
|
||||||
subscription,
|
subscription,
|
||||||
profile: self.profile.clone(),
|
|
||||||
merchant_account: self.merchant_context.get_merchant_account().clone(),
|
merchant_account: self.merchant_context.get_merchant_account().clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -153,7 +151,6 @@ impl<'a> SubscriptionHandler<'a> {
|
|||||||
pub struct SubscriptionWithHandler<'a> {
|
pub struct SubscriptionWithHandler<'a> {
|
||||||
pub handler: &'a SubscriptionHandler<'a>,
|
pub handler: &'a SubscriptionHandler<'a>,
|
||||||
pub subscription: diesel_models::subscription::Subscription,
|
pub subscription: diesel_models::subscription::Subscription,
|
||||||
pub profile: hyperswitch_domain_models::business_profile::Profile,
|
|
||||||
pub merchant_account: hyperswitch_domain_models::merchant_account::MerchantAccount,
|
pub merchant_account: hyperswitch_domain_models::merchant_account::MerchantAccount,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,11 +234,83 @@ impl SubscriptionWithHandler<'_> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_invoice_handler(&self) -> InvoiceHandler {
|
pub fn get_invoice_handler(
|
||||||
|
&self,
|
||||||
|
profile: hyperswitch_domain_models::business_profile::Profile,
|
||||||
|
) -> InvoiceHandler {
|
||||||
InvoiceHandler {
|
InvoiceHandler {
|
||||||
subscription: self.subscription.clone(),
|
subscription: self.subscription.clone(),
|
||||||
merchant_account: self.merchant_account.clone(),
|
merchant_account: self.merchant_account.clone(),
|
||||||
profile: self.profile.clone(),
|
profile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn get_mca(
|
||||||
|
&mut self,
|
||||||
|
connector_name: &str,
|
||||||
|
) -> CustomResult<domain::MerchantConnectorAccount, errors::ApiErrorResponse> {
|
||||||
|
let db = self.handler.state.store.as_ref();
|
||||||
|
let key_manager_state = &(self.handler.state).into();
|
||||||
|
|
||||||
|
match &self.subscription.merchant_connector_id {
|
||||||
|
Some(merchant_connector_id) => {
|
||||||
|
#[cfg(feature = "v1")]
|
||||||
|
{
|
||||||
|
db.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
|
||||||
|
key_manager_state,
|
||||||
|
self.handler
|
||||||
|
.merchant_context
|
||||||
|
.get_merchant_account()
|
||||||
|
.get_id(),
|
||||||
|
merchant_connector_id,
|
||||||
|
self.handler.merchant_context.get_merchant_key_store(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(
|
||||||
|
errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
|
||||||
|
id: merchant_connector_id.get_string_repr().to_string(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "v2")]
|
||||||
|
{
|
||||||
|
//get mca using id
|
||||||
|
let _ = key_manager_state;
|
||||||
|
let _ = connector_name;
|
||||||
|
let _ = merchant_context.get_merchant_key_store();
|
||||||
|
let _ = subscription.profile_id;
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Fallback to profile-based lookup when merchant_connector_id is not set
|
||||||
|
#[cfg(feature = "v1")]
|
||||||
|
{
|
||||||
|
db.find_merchant_connector_account_by_profile_id_connector_name(
|
||||||
|
key_manager_state,
|
||||||
|
&self.subscription.profile_id,
|
||||||
|
connector_name,
|
||||||
|
self.handler.merchant_context.get_merchant_key_store(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(
|
||||||
|
errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
|
||||||
|
id: format!(
|
||||||
|
"profile_id {} and connector_name {connector_name}",
|
||||||
|
self.subscription.profile_id.get_string_repr()
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "v2")]
|
||||||
|
{
|
||||||
|
//get mca using id
|
||||||
|
let _ = key_manager_state;
|
||||||
|
let _ = connector_name;
|
||||||
|
let _ = self.handler.merchant_context.get_merchant_key_store();
|
||||||
|
let _ = self.subscription.profile_id;
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,11 @@ use std::{str::FromStr, time::Instant};
|
|||||||
use actix_web::FromRequest;
|
use actix_web::FromRequest;
|
||||||
#[cfg(feature = "payouts")]
|
#[cfg(feature = "payouts")]
|
||||||
use api_models::payouts as payout_models;
|
use api_models::payouts as payout_models;
|
||||||
use api_models::webhooks::{self, WebhookResponseTracker};
|
use api_models::{
|
||||||
|
enums::Connector,
|
||||||
|
webhooks::{self, WebhookResponseTracker},
|
||||||
|
};
|
||||||
|
pub use common_enums::{connector_enums::InvoiceStatus, enums::ProcessTrackerRunner};
|
||||||
use common_utils::{
|
use common_utils::{
|
||||||
errors::ReportSwitchExt,
|
errors::ReportSwitchExt,
|
||||||
events::ApiEventsType,
|
events::ApiEventsType,
|
||||||
@ -30,7 +34,9 @@ use crate::{
|
|||||||
errors::{self, ConnectorErrorExt, CustomResult, RouterResponse, StorageErrorExt},
|
errors::{self, ConnectorErrorExt, CustomResult, RouterResponse, StorageErrorExt},
|
||||||
metrics, payment_methods,
|
metrics, payment_methods,
|
||||||
payments::{self, tokenization},
|
payments::{self, tokenization},
|
||||||
refunds, relay, unified_connector_service, utils as core_utils,
|
refunds, relay,
|
||||||
|
subscription::subscription_handler::SubscriptionHandler,
|
||||||
|
unified_connector_service, utils as core_utils,
|
||||||
webhooks::{network_tokenization_incoming, utils::construct_webhook_router_data},
|
webhooks::{network_tokenization_incoming, utils::construct_webhook_router_data},
|
||||||
},
|
},
|
||||||
db::StorageInterface,
|
db::StorageInterface,
|
||||||
@ -253,6 +259,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>(
|
|||||||
&request_details,
|
&request_details,
|
||||||
merchant_context.get_merchant_account().get_id(),
|
merchant_context.get_merchant_account().get_id(),
|
||||||
merchant_connector_account
|
merchant_connector_account
|
||||||
|
.clone()
|
||||||
.and_then(|mca| mca.connector_webhook_details.clone()),
|
.and_then(|mca| mca.connector_webhook_details.clone()),
|
||||||
&connector_name,
|
&connector_name,
|
||||||
)
|
)
|
||||||
@ -342,6 +349,11 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>(
|
|||||||
&webhook_processing_result.transform_data,
|
&webhook_processing_result.transform_data,
|
||||||
&final_request_details,
|
&final_request_details,
|
||||||
is_relay_webhook,
|
is_relay_webhook,
|
||||||
|
merchant_connector_account
|
||||||
|
.ok_or(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
|
||||||
|
id: connector_name_or_mca_id.to_string(),
|
||||||
|
})?
|
||||||
|
.merchant_connector_id,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@ -524,12 +536,13 @@ async fn process_webhook_business_logic(
|
|||||||
webhook_transform_data: &Option<Box<unified_connector_service::WebhookTransformData>>,
|
webhook_transform_data: &Option<Box<unified_connector_service::WebhookTransformData>>,
|
||||||
request_details: &IncomingWebhookRequestDetails<'_>,
|
request_details: &IncomingWebhookRequestDetails<'_>,
|
||||||
is_relay_webhook: bool,
|
is_relay_webhook: bool,
|
||||||
|
billing_connector_mca_id: common_utils::id_type::MerchantConnectorAccountId,
|
||||||
) -> errors::RouterResult<WebhookResponseTracker> {
|
) -> errors::RouterResult<WebhookResponseTracker> {
|
||||||
let object_ref_id = connector
|
let object_ref_id = connector
|
||||||
.get_webhook_object_reference_id(request_details)
|
.get_webhook_object_reference_id(request_details)
|
||||||
.switch()
|
.switch()
|
||||||
.attach_printable("Could not find object reference id in incoming webhook body")?;
|
.attach_printable("Could not find object reference id in incoming webhook body")?;
|
||||||
let connector_enum = api_models::enums::Connector::from_str(connector_name)
|
let connector_enum = Connector::from_str(connector_name)
|
||||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||||
field_name: "connector",
|
field_name: "connector",
|
||||||
})
|
})
|
||||||
@ -814,6 +827,21 @@ async fn process_webhook_business_logic(
|
|||||||
.await
|
.await
|
||||||
.attach_printable("Incoming webhook flow for payouts failed"),
|
.attach_printable("Incoming webhook flow for payouts failed"),
|
||||||
|
|
||||||
|
api::WebhookFlow::Subscription => Box::pin(subscription_incoming_webhook_flow(
|
||||||
|
state.clone(),
|
||||||
|
req_state,
|
||||||
|
merchant_context.clone(),
|
||||||
|
business_profile,
|
||||||
|
webhook_details,
|
||||||
|
source_verified,
|
||||||
|
connector,
|
||||||
|
request_details,
|
||||||
|
event_type,
|
||||||
|
billing_connector_mca_id,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.attach_printable("Incoming webhook flow for subscription failed"),
|
||||||
|
|
||||||
_ => Err(errors::ApiErrorResponse::InternalServerError)
|
_ => Err(errors::ApiErrorResponse::InternalServerError)
|
||||||
.attach_printable("Unsupported Flow Type received in incoming webhooks"),
|
.attach_printable("Unsupported Flow Type received in incoming webhooks"),
|
||||||
}
|
}
|
||||||
@ -845,7 +873,7 @@ fn handle_incoming_webhook_error(
|
|||||||
logger::error!(?error, "Incoming webhook flow failed");
|
logger::error!(?error, "Incoming webhook flow failed");
|
||||||
|
|
||||||
// fetch the connector enum from the connector name
|
// fetch the connector enum from the connector name
|
||||||
let connector_enum = api_models::connector_enums::Connector::from_str(connector_name)
|
let connector_enum = Connector::from_str(connector_name)
|
||||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||||
field_name: "connector",
|
field_name: "connector",
|
||||||
})
|
})
|
||||||
@ -2533,3 +2561,92 @@ fn insert_mandate_details(
|
|||||||
)?;
|
)?;
|
||||||
Ok(connector_mandate_details)
|
Ok(connector_mandate_details)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
async fn subscription_incoming_webhook_flow(
|
||||||
|
state: SessionState,
|
||||||
|
_req_state: ReqState,
|
||||||
|
merchant_context: domain::MerchantContext,
|
||||||
|
business_profile: domain::Profile,
|
||||||
|
_webhook_details: api::IncomingWebhookDetails,
|
||||||
|
source_verified: bool,
|
||||||
|
connector_enum: &ConnectorEnum,
|
||||||
|
request_details: &IncomingWebhookRequestDetails<'_>,
|
||||||
|
event_type: webhooks::IncomingWebhookEvent,
|
||||||
|
billing_connector_mca_id: common_utils::id_type::MerchantConnectorAccountId,
|
||||||
|
) -> CustomResult<WebhookResponseTracker, errors::ApiErrorResponse> {
|
||||||
|
// Only process invoice_generated events for MIT payments
|
||||||
|
if event_type != webhooks::IncomingWebhookEvent::InvoiceGenerated {
|
||||||
|
return Ok(WebhookResponseTracker::NoEffect);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !source_verified {
|
||||||
|
logger::error!("Webhook source verification failed for subscription webhook flow");
|
||||||
|
return Err(report!(
|
||||||
|
errors::ApiErrorResponse::WebhookAuthenticationFailed
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let connector_name = connector_enum.id().to_string();
|
||||||
|
|
||||||
|
let connector = Connector::from_str(&connector_name)
|
||||||
|
.change_context(errors::ConnectorError::InvalidConnectorName)
|
||||||
|
.change_context(errors::ApiErrorResponse::IncorrectConnectorNameGiven)
|
||||||
|
.attach_printable_lazy(|| format!("unable to parse connector name {connector_name}"))?;
|
||||||
|
|
||||||
|
let mit_payment_data = connector_enum
|
||||||
|
.get_subscription_mit_payment_data(request_details)
|
||||||
|
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||||
|
.attach_printable("Failed to extract MIT payment data from subscription webhook")?;
|
||||||
|
|
||||||
|
if mit_payment_data.first_invoice {
|
||||||
|
return Ok(WebhookResponseTracker::NoEffect);
|
||||||
|
}
|
||||||
|
|
||||||
|
let profile_id = business_profile.get_id().clone();
|
||||||
|
|
||||||
|
let profile =
|
||||||
|
SubscriptionHandler::find_business_profile(&state, &merchant_context, &profile_id)
|
||||||
|
.await
|
||||||
|
.attach_printable(
|
||||||
|
"subscriptions: failed to find business profile in get_subscription",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let handler = SubscriptionHandler::new(&state, &merchant_context);
|
||||||
|
|
||||||
|
let subscription_id = mit_payment_data.subscription_id.clone();
|
||||||
|
|
||||||
|
let subscription_with_handler = handler
|
||||||
|
.find_subscription(subscription_id)
|
||||||
|
.await
|
||||||
|
.attach_printable("subscriptions: failed to get subscription entry in get_subscription")?;
|
||||||
|
|
||||||
|
let invoice_handler = subscription_with_handler.get_invoice_handler(profile);
|
||||||
|
|
||||||
|
let payment_method_id = subscription_with_handler
|
||||||
|
.subscription
|
||||||
|
.payment_method_id
|
||||||
|
.clone()
|
||||||
|
.ok_or(errors::ApiErrorResponse::GenericNotFoundError {
|
||||||
|
message: "No payment method found for subscription".to_string(),
|
||||||
|
})
|
||||||
|
.attach_printable("No payment method found for subscription")?;
|
||||||
|
|
||||||
|
logger::info!("Payment method ID found: {}", payment_method_id);
|
||||||
|
|
||||||
|
let _invoice_new = invoice_handler
|
||||||
|
.create_invoice_entry(
|
||||||
|
&state,
|
||||||
|
billing_connector_mca_id.clone(),
|
||||||
|
None,
|
||||||
|
mit_payment_data.amount_due,
|
||||||
|
mit_payment_data.currency_code,
|
||||||
|
InvoiceStatus::PaymentPending,
|
||||||
|
connector,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(WebhookResponseTracker::NoEffect)
|
||||||
|
}
|
||||||
|
|||||||
@ -1456,6 +1456,7 @@ impl RecoveryAction {
|
|||||||
| webhooks::IncomingWebhookEvent::PayoutCreated
|
| webhooks::IncomingWebhookEvent::PayoutCreated
|
||||||
| webhooks::IncomingWebhookEvent::PayoutExpired
|
| webhooks::IncomingWebhookEvent::PayoutExpired
|
||||||
| webhooks::IncomingWebhookEvent::PayoutReversed
|
| webhooks::IncomingWebhookEvent::PayoutReversed
|
||||||
|
| webhooks::IncomingWebhookEvent::InvoiceGenerated
|
||||||
| webhooks::IncomingWebhookEvent::SetupWebhook => {
|
| webhooks::IncomingWebhookEvent::SetupWebhook => {
|
||||||
common_types::payments::RecoveryAction::InvalidAction
|
common_types::payments::RecoveryAction::InvalidAction
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,6 +44,8 @@ use serde_json::Value;
|
|||||||
use tracing_futures::Instrument;
|
use tracing_futures::Instrument;
|
||||||
|
|
||||||
pub use self::ext_traits::{OptionExt, ValidateCall};
|
pub use self::ext_traits::{OptionExt, ValidateCall};
|
||||||
|
#[cfg(feature = "v1")]
|
||||||
|
use crate::core::subscription::subscription_handler::SubscriptionHandler;
|
||||||
use crate::{
|
use crate::{
|
||||||
consts,
|
consts,
|
||||||
core::{
|
core::{
|
||||||
@ -459,7 +461,6 @@ pub async fn get_mca_from_payment_intent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "payouts")]
|
#[cfg(feature = "payouts")]
|
||||||
pub async fn get_mca_from_payout_attempt(
|
pub async fn get_mca_from_payout_attempt(
|
||||||
state: &SessionState,
|
state: &SessionState,
|
||||||
@ -639,6 +640,22 @@ pub async fn get_mca_from_object_reference_id(
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
webhooks::ObjectReferenceId::SubscriptionId(subscription_id_type) => {
|
||||||
|
#[cfg(feature = "v1")]
|
||||||
|
{
|
||||||
|
let subscription_handler = SubscriptionHandler::new(state, merchant_context);
|
||||||
|
let mut subscription_with_handler = subscription_handler
|
||||||
|
.find_subscription(subscription_id_type)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
subscription_with_handler.get_mca(connector_name).await
|
||||||
|
}
|
||||||
|
#[cfg(feature = "v2")]
|
||||||
|
{
|
||||||
|
let _db = db;
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
#[cfg(feature = "payouts")]
|
#[cfg(feature = "payouts")]
|
||||||
webhooks::ObjectReferenceId::PayoutId(payout_id_type) => {
|
webhooks::ObjectReferenceId::PayoutId(payout_id_type) => {
|
||||||
get_mca_from_payout_attempt(state, merchant_context, payout_id_type, connector_name)
|
get_mca_from_payout_attempt(state, merchant_context, payout_id_type, connector_name)
|
||||||
|
|||||||
Reference in New Issue
Block a user