mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
feat(subscription): add support to create subscription with trial plans (#9721)
Co-authored-by: Ankit Kumar Gupta <ankit.gupta.001@juspay.in> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Gaurav Rawat <104276743+GauravRawat369@users.noreply.github.com>
This commit is contained in:
@ -5025,7 +5025,7 @@ pub enum NextActionType {
|
||||
RedirectInsidePopup,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum NextActionData {
|
||||
/// Contains the url for redirection flow
|
||||
@ -5097,7 +5097,7 @@ pub enum NextActionData {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(tag = "method_key")]
|
||||
pub enum IframeData {
|
||||
#[serde(rename = "threeDSMethodData")]
|
||||
@ -5115,7 +5115,7 @@ pub enum IframeData {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct ThreeDsData {
|
||||
/// ThreeDS authentication url - to initiate authentication
|
||||
pub three_ds_authentication_url: String,
|
||||
@ -5131,7 +5131,7 @@ pub struct ThreeDsData {
|
||||
pub directory_server_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum ThreeDsMethodData {
|
||||
AcsThreeDsMethodData {
|
||||
@ -5148,7 +5148,7 @@ pub enum ThreeDsMethodData {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub enum ThreeDsMethodKey {
|
||||
#[serde(rename = "threeDSMethodData")]
|
||||
ThreeDsMethodData,
|
||||
@ -5156,7 +5156,7 @@ pub enum ThreeDsMethodKey {
|
||||
JWT,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct PollConfigResponse {
|
||||
/// Poll Id
|
||||
pub poll_id: String,
|
||||
@ -7761,7 +7761,7 @@ pub struct GooglePayTokenizationParameters {
|
||||
pub stripe_version: Option<Secret<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(tag = "wallet_name")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SessionToken {
|
||||
@ -7819,7 +7819,7 @@ pub struct HyperswitchVaultSessionDetails {
|
||||
pub profile_id: Secret<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub struct PazeSessionTokenResponse {
|
||||
/// Paze Client ID
|
||||
@ -7839,7 +7839,7 @@ pub struct PazeSessionTokenResponse {
|
||||
pub email_address: Option<Email>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum GpaySessionTokenResponse {
|
||||
/// Google pay response involving third party sdk
|
||||
@ -7848,7 +7848,7 @@ pub enum GpaySessionTokenResponse {
|
||||
GooglePaySession(GooglePaySessionResponse),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub struct GooglePayThirdPartySdk {
|
||||
/// Identifier for the delayed session response
|
||||
@ -7859,7 +7859,7 @@ pub struct GooglePayThirdPartySdk {
|
||||
pub sdk_next_action: SdkNextAction,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub struct GooglePaySessionResponse {
|
||||
/// The merchant info
|
||||
@ -7884,7 +7884,7 @@ pub struct GooglePaySessionResponse {
|
||||
pub secrets: Option<SecretInfoToInitiateSdk>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub struct SamsungPaySessionTokenResponse {
|
||||
/// Samsung Pay API version
|
||||
@ -7908,13 +7908,13 @@ pub struct SamsungPaySessionTokenResponse {
|
||||
pub shipping_address_required: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum SamsungPayProtocolType {
|
||||
Protocol3ds,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub struct SamsungPayMerchantPaymentInformation {
|
||||
/// Merchant name, this will be displayed on the Samsung Pay screen
|
||||
@ -7926,7 +7926,7 @@ pub struct SamsungPayMerchantPaymentInformation {
|
||||
pub country_code: api_enums::CountryAlpha2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub struct SamsungPayAmountDetails {
|
||||
#[serde(rename = "option")]
|
||||
@ -7941,7 +7941,7 @@ pub struct SamsungPayAmountDetails {
|
||||
pub total_amount: StringMajorUnit,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum SamsungPayAmountFormat {
|
||||
/// Display the total amount only
|
||||
@ -7950,14 +7950,14 @@ pub enum SamsungPayAmountFormat {
|
||||
FormatTotalEstimatedAmount,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub struct GpayShippingAddressParameters {
|
||||
/// Is shipping phone number required
|
||||
pub phone_number_required: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub struct KlarnaSessionTokenResponse {
|
||||
/// The session token for Klarna
|
||||
@ -7985,7 +7985,7 @@ pub struct PaypalTransactionInfo {
|
||||
pub total_price: StringMajorUnit,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub struct PaypalSessionTokenResponse {
|
||||
/// Name of the connector
|
||||
@ -8000,14 +8000,14 @@ pub struct PaypalSessionTokenResponse {
|
||||
pub transaction_info: Option<PaypalTransactionInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub struct OpenBankingSessionToken {
|
||||
/// The session token for OpenBanking Connectors
|
||||
pub open_banking_session_token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub struct ApplepaySessionTokenResponse {
|
||||
/// Session object for Apple Pay
|
||||
@ -8030,7 +8030,7 @@ pub struct ApplepaySessionTokenResponse {
|
||||
pub connector_merchant_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, serde::Serialize, Clone, ToSchema)]
|
||||
#[derive(Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Clone, ToSchema)]
|
||||
pub struct SdkNextAction {
|
||||
/// The type of next action
|
||||
pub next_action: NextActionCall,
|
||||
@ -8051,7 +8051,7 @@ pub enum NextActionCall {
|
||||
AwaitMerchantCallback,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum ApplePaySessionResponse {
|
||||
/// We get this session response, when third party sdk is involved
|
||||
@ -8090,7 +8090,7 @@ pub struct NoThirdPartySdkSessionResponse {
|
||||
pub psp_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct ThirdPartySdkSessionResponse {
|
||||
pub secrets: SecretInfoToInitiateSdk,
|
||||
}
|
||||
@ -8211,7 +8211,7 @@ pub struct ApplepayErrorResponse {
|
||||
pub status_message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct AmazonPaySessionTokenResponse {
|
||||
/// Amazon Pay merchant account identifier
|
||||
pub merchant_id: String,
|
||||
@ -8235,7 +8235,7 @@ pub struct AmazonPaySessionTokenResponse {
|
||||
pub delivery_options: Vec<AmazonPayDeliveryOptions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub enum AmazonPayPaymentIntent {
|
||||
/// Create a Charge Permission to authorize and capture funds at a later time
|
||||
Confirm,
|
||||
@ -9291,7 +9291,7 @@ pub struct ExtendedCardInfoResponse {
|
||||
pub payload: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct ClickToPaySessionResponse {
|
||||
pub dpa_id: String,
|
||||
pub dpa_name: String,
|
||||
@ -9951,7 +9951,7 @@ pub struct RecordAttemptErrorDetails {
|
||||
pub network_error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, ToSchema)]
|
||||
pub struct NullObject;
|
||||
|
||||
impl Serialize for NullObject {
|
||||
|
||||
@ -6,7 +6,7 @@ use utoipa::ToSchema;
|
||||
use crate::{
|
||||
enums as api_enums,
|
||||
mandates::RecurringDetails,
|
||||
payments::{Address, PaymentMethodDataRequest},
|
||||
payments::{Address, NextActionData, PaymentMethodDataRequest},
|
||||
};
|
||||
|
||||
/// Request payload for creating a subscription.
|
||||
@ -216,6 +216,7 @@ pub struct ConfirmSubscriptionPaymentDetails {
|
||||
pub payment_method_type: Option<api_enums::PaymentMethodType>,
|
||||
pub payment_method_data: PaymentMethodDataRequest,
|
||||
pub customer_acceptance: Option<CustomerAcceptance>,
|
||||
pub payment_type: Option<api_enums::PaymentType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
@ -224,6 +225,7 @@ pub struct CreateSubscriptionPaymentDetails {
|
||||
pub setup_future_usage: Option<api_enums::FutureUsage>,
|
||||
pub capture_method: Option<api_enums::CaptureMethod>,
|
||||
pub authentication_type: Option<api_enums::AuthenticationType>,
|
||||
pub payment_type: Option<api_enums::PaymentType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
@ -236,6 +238,7 @@ pub struct PaymentDetails {
|
||||
pub return_url: Option<common_utils::types::Url>,
|
||||
pub capture_method: Option<api_enums::CaptureMethod>,
|
||||
pub authentication_type: Option<api_enums::AuthenticationType>,
|
||||
pub payment_type: Option<api_enums::PaymentType>,
|
||||
}
|
||||
|
||||
// Creating new type for PaymentRequest API call as usage of api_models::PaymentsRequest will result in invalid payment request during serialization
|
||||
@ -247,6 +250,7 @@ pub struct CreatePaymentsRequestData {
|
||||
pub customer_id: Option<common_utils::id_type::CustomerId>,
|
||||
pub billing: Option<Address>,
|
||||
pub shipping: Option<Address>,
|
||||
pub profile_id: Option<common_utils::id_type::ProfileId>,
|
||||
pub setup_future_usage: Option<api_enums::FutureUsage>,
|
||||
pub return_url: Option<common_utils::types::Url>,
|
||||
pub capture_method: Option<api_enums::CaptureMethod>,
|
||||
@ -257,10 +261,12 @@ pub struct CreatePaymentsRequestData {
|
||||
pub struct ConfirmPaymentsRequestData {
|
||||
pub billing: Option<Address>,
|
||||
pub shipping: Option<Address>,
|
||||
pub profile_id: Option<common_utils::id_type::ProfileId>,
|
||||
pub payment_method: api_enums::PaymentMethod,
|
||||
pub payment_method_type: Option<api_enums::PaymentMethodType>,
|
||||
pub payment_method_data: PaymentMethodDataRequest,
|
||||
pub customer_acceptance: Option<CustomerAcceptance>,
|
||||
pub payment_type: Option<api_enums::PaymentType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, ToSchema)]
|
||||
@ -271,6 +277,7 @@ pub struct CreateAndConfirmPaymentsRequestData {
|
||||
pub confirm: bool,
|
||||
pub billing: Option<Address>,
|
||||
pub shipping: Option<Address>,
|
||||
pub profile_id: Option<common_utils::id_type::ProfileId>,
|
||||
pub setup_future_usage: Option<api_enums::FutureUsage>,
|
||||
pub return_url: Option<common_utils::types::Url>,
|
||||
pub capture_method: Option<api_enums::CaptureMethod>,
|
||||
@ -279,6 +286,7 @@ pub struct CreateAndConfirmPaymentsRequestData {
|
||||
pub payment_method_type: Option<api_enums::PaymentMethodType>,
|
||||
pub payment_method_data: Option<PaymentMethodDataRequest>,
|
||||
pub customer_acceptance: Option<CustomerAcceptance>,
|
||||
pub payment_type: Option<api_enums::PaymentType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
@ -287,13 +295,17 @@ pub struct PaymentResponseData {
|
||||
pub status: api_enums::IntentStatus,
|
||||
pub amount: MinorUnit,
|
||||
pub currency: api_enums::Currency,
|
||||
pub profile_id: Option<common_utils::id_type::ProfileId>,
|
||||
pub connector: Option<String>,
|
||||
pub payment_method_id: Option<Secret<String>>,
|
||||
pub return_url: Option<common_utils::types::Url>,
|
||||
pub next_action: Option<NextActionData>,
|
||||
pub payment_experience: Option<api_enums::PaymentExperience>,
|
||||
pub error_code: Option<String>,
|
||||
pub error_message: Option<String>,
|
||||
pub payment_method_type: Option<api_enums::PaymentMethodType>,
|
||||
pub client_secret: Option<Secret<String>>,
|
||||
pub payment_type: Option<api_enums::PaymentType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
@ -304,6 +316,7 @@ pub struct CreateMitPaymentRequestData {
|
||||
pub customer_id: Option<common_utils::id_type::CustomerId>,
|
||||
pub recurring_details: Option<RecurringDetails>,
|
||||
pub off_session: Option<bool>,
|
||||
pub profile_id: Option<common_utils::id_type::ProfileId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
@ -452,7 +465,7 @@ pub struct Invoice {
|
||||
pub currency: api_enums::Currency,
|
||||
|
||||
/// Status of the invoice.
|
||||
pub status: String,
|
||||
pub status: common_enums::connector_enums::InvoiceStatus,
|
||||
}
|
||||
|
||||
impl ApiEventMetric for ConfirmSubscriptionResponse {}
|
||||
|
||||
@ -900,7 +900,19 @@ impl TryFrom<Connector> for RoutableConnectors {
|
||||
}
|
||||
|
||||
// Enum representing different status an invoice can have.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, strum::Display, strum::EnumString)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
serde::Deserialize,
|
||||
serde::Serialize,
|
||||
strum::Display,
|
||||
strum::EnumString,
|
||||
ToSchema,
|
||||
)]
|
||||
#[router_derive::diesel_enum(storage_type = "text")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum InvoiceStatus {
|
||||
InvoiceCreated,
|
||||
|
||||
@ -18,12 +18,12 @@ pub struct InvoiceNew {
|
||||
pub customer_id: common_utils::id_type::CustomerId,
|
||||
pub amount: MinorUnit,
|
||||
pub currency: String,
|
||||
pub status: String,
|
||||
pub status: InvoiceStatus,
|
||||
pub provider_name: Connector,
|
||||
pub metadata: Option<SecretSerdeValue>,
|
||||
pub created_at: time::PrimitiveDateTime,
|
||||
pub modified_at: time::PrimitiveDateTime,
|
||||
pub connector_invoice_id: Option<String>,
|
||||
pub connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
@ -45,20 +45,20 @@ pub struct Invoice {
|
||||
pub customer_id: common_utils::id_type::CustomerId,
|
||||
pub amount: MinorUnit,
|
||||
pub currency: String,
|
||||
pub status: String,
|
||||
pub status: InvoiceStatus,
|
||||
pub provider_name: Connector,
|
||||
pub metadata: Option<SecretSerdeValue>,
|
||||
pub created_at: time::PrimitiveDateTime,
|
||||
pub modified_at: time::PrimitiveDateTime,
|
||||
pub connector_invoice_id: Option<String>,
|
||||
pub connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, AsChangeset, Deserialize)]
|
||||
#[diesel(table_name = invoice)]
|
||||
pub struct InvoiceUpdate {
|
||||
pub status: Option<String>,
|
||||
pub status: Option<InvoiceStatus>,
|
||||
pub payment_method_id: Option<String>,
|
||||
pub connector_invoice_id: Option<String>,
|
||||
pub connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
pub modified_at: time::PrimitiveDateTime,
|
||||
pub payment_intent_id: Option<common_utils::id_type::PaymentId>,
|
||||
}
|
||||
@ -78,7 +78,7 @@ impl InvoiceNew {
|
||||
status: InvoiceStatus,
|
||||
provider_name: Connector,
|
||||
metadata: Option<SecretSerdeValue>,
|
||||
connector_invoice_id: Option<String>,
|
||||
connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
) -> Self {
|
||||
let id = common_utils::id_type::InvoiceId::generate();
|
||||
let now = common_utils::date_time::now();
|
||||
@ -93,7 +93,7 @@ impl InvoiceNew {
|
||||
customer_id,
|
||||
amount,
|
||||
currency,
|
||||
status: status.to_string(),
|
||||
status,
|
||||
provider_name,
|
||||
metadata,
|
||||
created_at: now,
|
||||
@ -107,12 +107,12 @@ impl InvoiceUpdate {
|
||||
pub fn new(
|
||||
payment_method_id: Option<String>,
|
||||
status: Option<InvoiceStatus>,
|
||||
connector_invoice_id: Option<String>,
|
||||
connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
payment_intent_id: Option<common_utils::id_type::PaymentId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
payment_method_id,
|
||||
status: status.map(|status| status.to_string()),
|
||||
status,
|
||||
connector_invoice_id,
|
||||
payment_intent_id,
|
||||
modified_at: common_utils::date_time::now(),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use diesel::{associations::HasTable, ExpressionMethods};
|
||||
use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};
|
||||
|
||||
use super::generics;
|
||||
use crate::{
|
||||
@ -66,4 +66,18 @@ impl Invoice {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_invoice_by_subscription_id_connector_invoice_id(
|
||||
conn: &PgPooledConn,
|
||||
subscription_id: String,
|
||||
connector_invoice_id: common_utils::id_type::InvoiceId,
|
||||
) -> StorageResult<Option<Self>> {
|
||||
generics::generic_find_one_optional::<<Self as HasTable>::Table, _, _>(
|
||||
conn,
|
||||
dsl::subscription_id
|
||||
.eq(subscription_id.to_owned())
|
||||
.and(dsl::connector_invoice_id.eq(connector_invoice_id.to_owned())),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ use common_utils::{
|
||||
date_time,
|
||||
encryption::Encryption,
|
||||
errors::{CustomResult, ValidationError},
|
||||
ext_traits::ValueExt,
|
||||
id_type, pii,
|
||||
types::{
|
||||
keymanager::{self, KeyManagerState, ToEncryptable},
|
||||
@ -90,6 +91,23 @@ impl Customer {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Get the connector customer ID for the specified connector label, if present
|
||||
#[cfg(feature = "v1")]
|
||||
pub fn get_connector_customer_map(
|
||||
&self,
|
||||
) -> FxHashMap<id_type::MerchantConnectorAccountId, String> {
|
||||
use masking::PeekInterface;
|
||||
if let Some(connector_customer_value) = &self.connector_customer {
|
||||
connector_customer_value
|
||||
.peek()
|
||||
.clone()
|
||||
.parse_value("ConnectorCustomerMap")
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
FxHashMap::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the connector customer ID for the specified connector label, if present
|
||||
#[cfg(feature = "v1")]
|
||||
pub fn get_connector_customer_id(&self, connector_label: &str) -> Option<&str> {
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use common_utils::{
|
||||
errors::{CustomResult, ValidationError},
|
||||
id_type::GenerateId,
|
||||
@ -9,7 +7,6 @@ use common_utils::{
|
||||
MinorUnit,
|
||||
},
|
||||
};
|
||||
use error_stack::ResultExt;
|
||||
use masking::Secret;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
@ -27,10 +24,10 @@ pub struct Invoice {
|
||||
pub customer_id: common_utils::id_type::CustomerId,
|
||||
pub amount: MinorUnit,
|
||||
pub currency: String,
|
||||
pub status: String,
|
||||
pub status: common_enums::connector_enums::InvoiceStatus,
|
||||
pub provider_name: common_enums::connector_enums::Connector,
|
||||
pub metadata: Option<SecretSerdeValue>,
|
||||
pub connector_invoice_id: Option<String>,
|
||||
pub connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@ -89,11 +86,6 @@ impl super::behaviour::Conversion for Invoice {
|
||||
}
|
||||
|
||||
async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
|
||||
let invoice_status = common_enums::connector_enums::InvoiceStatus::from_str(&self.status)
|
||||
.change_context(ValidationError::InvalidValue {
|
||||
message: "Invalid invoice status".to_string(),
|
||||
})?;
|
||||
|
||||
Ok(diesel_models::invoice::InvoiceNew::new(
|
||||
self.subscription_id,
|
||||
self.merchant_id,
|
||||
@ -104,7 +96,7 @@ impl super::behaviour::Conversion for Invoice {
|
||||
self.customer_id,
|
||||
self.amount,
|
||||
self.currency.to_string(),
|
||||
invoice_status,
|
||||
self.status,
|
||||
self.provider_name,
|
||||
None,
|
||||
self.connector_invoice_id,
|
||||
@ -127,7 +119,7 @@ impl Invoice {
|
||||
status: common_enums::connector_enums::InvoiceStatus,
|
||||
provider_name: common_enums::connector_enums::Connector,
|
||||
metadata: Option<SecretSerdeValue>,
|
||||
connector_invoice_id: Option<String>,
|
||||
connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: common_utils::id_type::InvoiceId::generate(),
|
||||
@ -140,7 +132,7 @@ impl Invoice {
|
||||
customer_id,
|
||||
amount,
|
||||
currency: currency.to_string(),
|
||||
status: status.to_string(),
|
||||
status,
|
||||
provider_name,
|
||||
metadata,
|
||||
connector_invoice_id,
|
||||
@ -179,12 +171,20 @@ pub trait InvoiceInterface {
|
||||
key_store: &MerchantKeyStore,
|
||||
subscription_id: String,
|
||||
) -> CustomResult<Invoice, Self::Error>;
|
||||
|
||||
async fn find_invoice_by_subscription_id_connector_invoice_id(
|
||||
&self,
|
||||
state: &KeyManagerState,
|
||||
key_store: &MerchantKeyStore,
|
||||
subscription_id: String,
|
||||
connector_invoice_id: common_utils::id_type::InvoiceId,
|
||||
) -> CustomResult<Option<Invoice>, Self::Error>;
|
||||
}
|
||||
|
||||
pub struct InvoiceUpdate {
|
||||
pub status: Option<String>,
|
||||
pub status: Option<common_enums::connector_enums::InvoiceStatus>,
|
||||
pub payment_method_id: Option<String>,
|
||||
pub connector_invoice_id: Option<String>,
|
||||
pub connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
pub modified_at: time::PrimitiveDateTime,
|
||||
pub payment_intent_id: Option<common_utils::id_type::PaymentId>,
|
||||
}
|
||||
@ -236,15 +236,15 @@ impl InvoiceUpdate {
|
||||
pub fn new(
|
||||
payment_method_id: Option<String>,
|
||||
status: Option<common_enums::connector_enums::InvoiceStatus>,
|
||||
connector_invoice_id: Option<String>,
|
||||
connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
payment_intent_id: Option<common_utils::id_type::PaymentId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
payment_method_id,
|
||||
status: status.map(|status| status.to_string()),
|
||||
connector_invoice_id,
|
||||
status,
|
||||
modified_at: common_utils::date_time::now(),
|
||||
payment_intent_id,
|
||||
connector_invoice_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ pub async fn create_subscription(
|
||||
SubscriptionHandler::find_business_profile(&state, &merchant_context, &profile_id)
|
||||
.await
|
||||
.attach_printable("subscriptions: failed to find business profile")?;
|
||||
let customer =
|
||||
let _customer =
|
||||
SubscriptionHandler::find_customer(&state, &merchant_context, &request.customer_id)
|
||||
.await
|
||||
.attach_printable("subscriptions: failed to find customer")?;
|
||||
@ -43,7 +43,6 @@ pub async fn create_subscription(
|
||||
&state,
|
||||
merchant_context.get_merchant_account(),
|
||||
merchant_context.get_merchant_key_store(),
|
||||
Some(customer),
|
||||
profile.clone(),
|
||||
)
|
||||
.await?;
|
||||
@ -119,7 +118,6 @@ pub async fn get_subscription_plans(
|
||||
&state,
|
||||
merchant_context.get_merchant_account(),
|
||||
merchant_context.get_merchant_key_store(),
|
||||
None,
|
||||
profile.clone(),
|
||||
)
|
||||
.await?;
|
||||
@ -169,7 +167,6 @@ pub async fn create_and_confirm_subscription(
|
||||
&state,
|
||||
merchant_context.get_merchant_account(),
|
||||
merchant_context.get_merchant_key_store(),
|
||||
Some(customer),
|
||||
profile.clone(),
|
||||
)
|
||||
.await?;
|
||||
@ -187,9 +184,10 @@ pub async fn create_and_confirm_subscription(
|
||||
.attach_printable("subscriptions: failed to create subscription entry")?;
|
||||
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(
|
||||
&state,
|
||||
customer.clone(),
|
||||
request.customer_id.clone(),
|
||||
request.billing.clone(),
|
||||
request
|
||||
@ -199,6 +197,15 @@ pub async fn create_and_confirm_subscription(
|
||||
.and_then(|data| data.payment_method_data),
|
||||
)
|
||||
.await?;
|
||||
let _customer_updated_response = SubscriptionHandler::update_connector_customer_id_in_customer(
|
||||
&state,
|
||||
&merchant_context,
|
||||
&billing_handler.merchant_connector_id,
|
||||
&customer,
|
||||
customer_create_response,
|
||||
)
|
||||
.await
|
||||
.attach_printable("Failed to update customer with connector customer ID")?;
|
||||
|
||||
let subscription_create_response = billing_handler
|
||||
.create_subscription_on_connector(
|
||||
@ -232,9 +239,7 @@ pub async fn create_and_confirm_subscription(
|
||||
.unwrap_or(connector_enums::InvoiceStatus::InvoiceCreated),
|
||||
billing_handler.connector_data.connector_name,
|
||||
None,
|
||||
invoice_details
|
||||
.clone()
|
||||
.map(|invoice| invoice.id.get_string_repr().to_string()),
|
||||
invoice_details.clone().map(|invoice| invoice.id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -242,13 +247,7 @@ pub async fn create_and_confirm_subscription(
|
||||
.create_invoice_sync_job(
|
||||
&state,
|
||||
&invoice_entry,
|
||||
invoice_details
|
||||
.ok_or(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "invoice_details",
|
||||
})?
|
||||
.id
|
||||
.get_string_repr()
|
||||
.to_string(),
|
||||
invoice_details.clone().map(|details| details.id),
|
||||
billing_handler.connector_data.connector_name,
|
||||
)
|
||||
.await?;
|
||||
@ -327,16 +326,16 @@ pub async fn confirm_subscription(
|
||||
&state,
|
||||
merchant_context.get_merchant_account(),
|
||||
merchant_context.get_merchant_key_store(),
|
||||
Some(customer),
|
||||
profile.clone(),
|
||||
)
|
||||
.await?;
|
||||
let invoice_handler = subscription_entry.get_invoice_handler(profile);
|
||||
let subscription = subscription_entry.subscription.clone();
|
||||
|
||||
let _customer_create_response = billing_handler
|
||||
let customer_create_response = billing_handler
|
||||
.create_customer_on_connector(
|
||||
&state,
|
||||
customer.clone(),
|
||||
subscription.customer_id.clone(),
|
||||
request.payment_details.payment_method_data.billing.clone(),
|
||||
request
|
||||
@ -345,6 +344,15 @@ pub async fn confirm_subscription(
|
||||
.payment_method_data,
|
||||
)
|
||||
.await?;
|
||||
let _customer_updated_response = SubscriptionHandler::update_connector_customer_id_in_customer(
|
||||
&state,
|
||||
&merchant_context,
|
||||
&billing_handler.merchant_connector_id,
|
||||
&customer,
|
||||
customer_create_response,
|
||||
)
|
||||
.await
|
||||
.attach_printable("Failed to update customer with connector customer ID")?;
|
||||
|
||||
let subscription_create_response = billing_handler
|
||||
.create_subscription_on_connector(
|
||||
@ -366,9 +374,7 @@ pub async fn confirm_subscription(
|
||||
.clone()
|
||||
.and_then(|invoice| invoice.status)
|
||||
.unwrap_or(connector_enums::InvoiceStatus::InvoiceCreated),
|
||||
invoice_details
|
||||
.clone()
|
||||
.map(|invoice| invoice.id.get_string_repr().to_string()),
|
||||
invoice_details.clone().map(|invoice| invoice.id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -376,14 +382,7 @@ pub async fn confirm_subscription(
|
||||
.create_invoice_sync_job(
|
||||
&state,
|
||||
&invoice_entry,
|
||||
invoice_details
|
||||
.clone()
|
||||
.ok_or(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "invoice_details",
|
||||
})?
|
||||
.id
|
||||
.get_string_repr()
|
||||
.to_string(),
|
||||
invoice_details.map(|invoice| invoice.id),
|
||||
billing_handler.connector_data.connector_name,
|
||||
)
|
||||
.await?;
|
||||
@ -449,7 +448,6 @@ pub async fn get_estimate(
|
||||
&state,
|
||||
merchant_context.get_merchant_account(),
|
||||
merchant_context.get_merchant_key_store(),
|
||||
None,
|
||||
profile,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -31,7 +31,6 @@ pub struct BillingHandler {
|
||||
pub connector_data: api_types::ConnectorData,
|
||||
pub connector_params: hyperswitch_domain_models::connector_endpoints::ConnectorParams,
|
||||
pub connector_metadata: Option<pii::SecretSerdeValue>,
|
||||
pub customer: Option<hyperswitch_domain_models::customer::Customer>,
|
||||
pub merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId,
|
||||
}
|
||||
|
||||
@ -41,7 +40,6 @@ impl BillingHandler {
|
||||
state: &SessionState,
|
||||
merchant_account: &hyperswitch_domain_models::merchant_account::MerchantAccount,
|
||||
key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore,
|
||||
customer: Option<hyperswitch_domain_models::customer::Customer>,
|
||||
profile: hyperswitch_domain_models::business_profile::Profile,
|
||||
) -> errors::RouterResult<Self> {
|
||||
let merchant_connector_id = profile.get_billing_processor_id()?;
|
||||
@ -102,24 +100,22 @@ impl BillingHandler {
|
||||
connector_data,
|
||||
connector_params,
|
||||
connector_metadata: billing_processor_mca.metadata.clone(),
|
||||
customer,
|
||||
merchant_connector_id,
|
||||
})
|
||||
}
|
||||
pub async fn create_customer_on_connector(
|
||||
&self,
|
||||
state: &SessionState,
|
||||
customer: hyperswitch_domain_models::customer::Customer,
|
||||
customer_id: common_utils::id_type::CustomerId,
|
||||
billing_address: Option<api_models::payments::Address>,
|
||||
payment_method_data: Option<api_models::payments::PaymentMethodData>,
|
||||
) -> errors::RouterResult<ConnectorCustomerResponseData> {
|
||||
let customer =
|
||||
self.customer
|
||||
.as_ref()
|
||||
.ok_or(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "customer",
|
||||
})?;
|
||||
|
||||
) -> errors::RouterResult<Option<ConnectorCustomerResponseData>> {
|
||||
let connector_customer_map = customer.get_connector_customer_map();
|
||||
if connector_customer_map.contains_key(&self.merchant_connector_id) {
|
||||
// Customer already exists on the connector, no need to create again
|
||||
return Ok(None);
|
||||
}
|
||||
let customer_req = ConnectorCustomerData {
|
||||
email: customer.email.clone().map(pii::Email::from),
|
||||
payment_method_data: payment_method_data.clone().map(|pmd| pmd.into()),
|
||||
@ -156,7 +152,7 @@ impl BillingHandler {
|
||||
match response {
|
||||
Ok(response_data) => match response_data {
|
||||
PaymentsResponseData::ConnectorCustomerResponse(customer_response) => {
|
||||
Ok(customer_response)
|
||||
Ok(Some(customer_response))
|
||||
}
|
||||
_ => Err(errors::ApiErrorResponse::SubscriptionError {
|
||||
operation: "Subscription Customer Create".to_string(),
|
||||
@ -233,7 +229,7 @@ impl BillingHandler {
|
||||
pub async fn record_back_to_billing_processor(
|
||||
&self,
|
||||
state: &SessionState,
|
||||
invoice_id: String,
|
||||
invoice_id: common_utils::id_type::InvoiceId,
|
||||
payment_id: common_utils::id_type::PaymentId,
|
||||
payment_status: common_enums::AttemptStatus,
|
||||
amount: common_utils::types::MinorUnit,
|
||||
@ -245,7 +241,9 @@ impl BillingHandler {
|
||||
currency,
|
||||
payment_method_type,
|
||||
attempt_status: payment_status,
|
||||
merchant_reference_id: common_utils::id_type::PaymentReferenceId::from_str(&invoice_id)
|
||||
merchant_reference_id: common_utils::id_type::PaymentReferenceId::from_str(
|
||||
invoice_id.get_string_repr(),
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "invoice_id",
|
||||
})?,
|
||||
@ -392,7 +390,6 @@ impl BillingHandler {
|
||||
connector_integration,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match response {
|
||||
Ok(resp) => Ok(resp),
|
||||
Err(err) => Err(errors::ApiErrorResponse::ExternalConnectorError {
|
||||
|
||||
@ -10,9 +10,7 @@ use masking::{PeekInterface, Secret};
|
||||
|
||||
use super::errors;
|
||||
use crate::{
|
||||
core::{errors::utils::StorageErrorExt, subscription::payments_api_client},
|
||||
routes::SessionState,
|
||||
types::storage as storage_types,
|
||||
core::subscription::payments_api_client, routes::SessionState, types::storage as storage_types,
|
||||
workflows::invoice_sync as invoice_sync_workflow,
|
||||
};
|
||||
|
||||
@ -20,6 +18,7 @@ pub struct InvoiceHandler {
|
||||
pub subscription: hyperswitch_domain_models::subscription::Subscription,
|
||||
pub merchant_account: hyperswitch_domain_models::merchant_account::MerchantAccount,
|
||||
pub profile: hyperswitch_domain_models::business_profile::Profile,
|
||||
pub merchant_key_store: hyperswitch_domain_models::merchant_key_store::MerchantKeyStore,
|
||||
}
|
||||
|
||||
#[allow(clippy::todo)]
|
||||
@ -28,11 +27,13 @@ impl InvoiceHandler {
|
||||
subscription: hyperswitch_domain_models::subscription::Subscription,
|
||||
merchant_account: hyperswitch_domain_models::merchant_account::MerchantAccount,
|
||||
profile: hyperswitch_domain_models::business_profile::Profile,
|
||||
merchant_key_store: hyperswitch_domain_models::merchant_key_store::MerchantKeyStore,
|
||||
) -> Self {
|
||||
Self {
|
||||
subscription,
|
||||
merchant_account,
|
||||
profile,
|
||||
merchant_key_store,
|
||||
}
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -46,7 +47,7 @@ impl InvoiceHandler {
|
||||
status: connector_enums::InvoiceStatus,
|
||||
provider_name: connector_enums::Connector,
|
||||
metadata: Option<pii::SecretSerdeValue>,
|
||||
connector_invoice_id: Option<String>,
|
||||
connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
) -> errors::RouterResult<hyperswitch_domain_models::invoice::Invoice> {
|
||||
let invoice_new = hyperswitch_domain_models::invoice::Invoice::to_invoice(
|
||||
self.subscription.id.to_owned(),
|
||||
@ -65,18 +66,9 @@ impl InvoiceHandler {
|
||||
);
|
||||
|
||||
let key_manager_state = &(state).into();
|
||||
let merchant_key_store = state
|
||||
.store
|
||||
.get_merchant_key_store_by_merchant_id(
|
||||
key_manager_state,
|
||||
self.merchant_account.get_id(),
|
||||
&state.store.get_master_key().to_vec().into(),
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
|
||||
let invoice = state
|
||||
.store
|
||||
.insert_invoice_entry(key_manager_state, &merchant_key_store, invoice_new)
|
||||
.insert_invoice_entry(key_manager_state, &self.merchant_key_store, invoice_new)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::SubscriptionError {
|
||||
operation: "Create Invoice".to_string(),
|
||||
@ -93,7 +85,7 @@ impl InvoiceHandler {
|
||||
payment_method_id: Option<Secret<String>>,
|
||||
payment_intent_id: Option<common_utils::id_type::PaymentId>,
|
||||
status: connector_enums::InvoiceStatus,
|
||||
connector_invoice_id: Option<String>,
|
||||
connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
) -> errors::RouterResult<hyperswitch_domain_models::invoice::Invoice> {
|
||||
let update_invoice = hyperswitch_domain_models::invoice::InvoiceUpdate::new(
|
||||
payment_method_id.as_ref().map(|id| id.peek()).cloned(),
|
||||
@ -102,20 +94,11 @@ impl InvoiceHandler {
|
||||
payment_intent_id,
|
||||
);
|
||||
let key_manager_state = &(state).into();
|
||||
let merchant_key_store = state
|
||||
.store
|
||||
.get_merchant_key_store_by_merchant_id(
|
||||
key_manager_state,
|
||||
self.merchant_account.get_id(),
|
||||
&state.store.get_master_key().to_vec().into(),
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
|
||||
state
|
||||
.store
|
||||
.update_invoice_entry(
|
||||
key_manager_state,
|
||||
&merchant_key_store,
|
||||
&self.merchant_key_store,
|
||||
invoice_id.get_string_repr().to_string(),
|
||||
update_invoice,
|
||||
)
|
||||
@ -151,6 +134,7 @@ impl InvoiceHandler {
|
||||
customer_id: Some(self.subscription.customer_id.clone()),
|
||||
billing: request.billing.clone(),
|
||||
shipping: request.shipping.clone(),
|
||||
profile_id: Some(self.profile.get_id().clone()),
|
||||
setup_future_usage: payment_details.setup_future_usage,
|
||||
return_url: Some(payment_details.return_url.clone()),
|
||||
capture_method: payment_details.capture_method,
|
||||
@ -194,6 +178,7 @@ impl InvoiceHandler {
|
||||
customer_id: Some(self.subscription.customer_id.clone()),
|
||||
billing: request.billing.clone(),
|
||||
shipping: request.shipping.clone(),
|
||||
profile_id: Some(self.profile.get_id().clone()),
|
||||
setup_future_usage: payment_details.setup_future_usage,
|
||||
return_url: payment_details.return_url.clone(),
|
||||
capture_method: payment_details.capture_method,
|
||||
@ -202,6 +187,7 @@ impl InvoiceHandler {
|
||||
payment_method_type: payment_details.payment_method_type,
|
||||
payment_method_data: payment_details.payment_method_data.clone(),
|
||||
customer_acceptance: payment_details.customer_acceptance.clone(),
|
||||
payment_type: payment_details.payment_type,
|
||||
};
|
||||
payments_api_client::PaymentsApiClient::create_and_confirm_payment(
|
||||
state,
|
||||
@ -222,10 +208,12 @@ impl InvoiceHandler {
|
||||
let cit_payment_request = subscription_types::ConfirmPaymentsRequestData {
|
||||
billing: request.payment_details.payment_method_data.billing.clone(),
|
||||
shipping: request.payment_details.shipping.clone(),
|
||||
profile_id: Some(self.profile.get_id().clone()),
|
||||
payment_method: payment_details.payment_method,
|
||||
payment_method_type: payment_details.payment_method_type,
|
||||
payment_method_data: payment_details.payment_method_data.clone(),
|
||||
customer_acceptance: payment_details.customer_acceptance.clone(),
|
||||
payment_type: payment_details.payment_type,
|
||||
};
|
||||
payments_api_client::PaymentsApiClient::confirm_payment(
|
||||
state,
|
||||
@ -242,20 +230,11 @@ impl InvoiceHandler {
|
||||
state: &SessionState,
|
||||
) -> errors::RouterResult<hyperswitch_domain_models::invoice::Invoice> {
|
||||
let key_manager_state = &(state).into();
|
||||
let merchant_key_store = state
|
||||
.store
|
||||
.get_merchant_key_store_by_merchant_id(
|
||||
key_manager_state,
|
||||
self.merchant_account.get_id(),
|
||||
&state.store.get_master_key().to_vec().into(),
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
|
||||
state
|
||||
.store
|
||||
.get_latest_invoice_for_subscription(
|
||||
key_manager_state,
|
||||
&merchant_key_store,
|
||||
&self.merchant_key_store,
|
||||
self.subscription.id.get_string_repr().to_string(),
|
||||
)
|
||||
.await
|
||||
@ -271,20 +250,11 @@ impl InvoiceHandler {
|
||||
invoice_id: common_utils::id_type::InvoiceId,
|
||||
) -> errors::RouterResult<hyperswitch_domain_models::invoice::Invoice> {
|
||||
let key_manager_state = &(state).into();
|
||||
let merchant_key_store = state
|
||||
.store
|
||||
.get_merchant_key_store_by_merchant_id(
|
||||
key_manager_state,
|
||||
self.merchant_account.get_id(),
|
||||
&state.store.get_master_key().to_vec().into(),
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
|
||||
state
|
||||
.store
|
||||
.find_invoice_by_invoice_id(
|
||||
key_manager_state,
|
||||
&merchant_key_store,
|
||||
&self.merchant_key_store,
|
||||
invoice_id.get_string_repr().to_string(),
|
||||
)
|
||||
.await
|
||||
@ -294,11 +264,33 @@ impl InvoiceHandler {
|
||||
.attach_printable("invoices: unable to get invoice by id from database")
|
||||
}
|
||||
|
||||
pub async fn find_invoice_by_subscription_id_connector_invoice_id(
|
||||
&self,
|
||||
state: &SessionState,
|
||||
subscription_id: common_utils::id_type::SubscriptionId,
|
||||
connector_invoice_id: common_utils::id_type::InvoiceId,
|
||||
) -> errors::RouterResult<Option<hyperswitch_domain_models::invoice::Invoice>> {
|
||||
let key_manager_state = &(state).into();
|
||||
state
|
||||
.store
|
||||
.find_invoice_by_subscription_id_connector_invoice_id(
|
||||
key_manager_state,
|
||||
&self.merchant_key_store,
|
||||
subscription_id.get_string_repr().to_string(),
|
||||
connector_invoice_id,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::SubscriptionError {
|
||||
operation: "Get Invoice by Subscription ID and Connector Invoice ID".to_string(),
|
||||
})
|
||||
.attach_printable("invoices: unable to get invoice by subscription id and connector invoice id from database")
|
||||
}
|
||||
|
||||
pub async fn create_invoice_sync_job(
|
||||
&self,
|
||||
state: &SessionState,
|
||||
invoice: &hyperswitch_domain_models::invoice::Invoice,
|
||||
connector_invoice_id: String,
|
||||
connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
connector_name: connector_enums::Connector,
|
||||
) -> errors::RouterResult<()> {
|
||||
let request = storage_types::invoice_sync::InvoiceSyncRequest::new(
|
||||
@ -333,6 +325,7 @@ impl InvoiceHandler {
|
||||
payment_method_id.to_owned(),
|
||||
)),
|
||||
off_session: Some(true),
|
||||
profile_id: Some(self.profile.get_id().clone()),
|
||||
};
|
||||
|
||||
payments_api_client::PaymentsApiClient::create_mit_payment(
|
||||
|
||||
@ -36,6 +36,10 @@ impl PaymentsApiClient {
|
||||
.clone(),
|
||||
),
|
||||
),
|
||||
(
|
||||
headers::X_TENANT_ID.to_string(),
|
||||
masking::Maskable::Normal(state.tenant.tenant_id.get_string_repr().to_string()),
|
||||
),
|
||||
(
|
||||
headers::X_MERCHANT_ID.to_string(),
|
||||
masking::Maskable::Normal(merchant_id.to_string()),
|
||||
|
||||
@ -9,14 +9,17 @@ use common_utils::{consts, ext_traits::OptionExt};
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_domain_models::{
|
||||
merchant_context::MerchantContext,
|
||||
router_response_types::subscriptions as subscription_response_types,
|
||||
router_response_types::{self, subscriptions as subscription_response_types},
|
||||
subscription::{Subscription, SubscriptionStatus},
|
||||
};
|
||||
use masking::Secret;
|
||||
|
||||
use super::errors;
|
||||
use crate::{
|
||||
core::{errors::StorageErrorExt, subscription::invoice_handler::InvoiceHandler},
|
||||
core::{
|
||||
errors::StorageErrorExt, payments as payments_core,
|
||||
subscription::invoice_handler::InvoiceHandler,
|
||||
},
|
||||
db::CustomResult,
|
||||
routes::SessionState,
|
||||
types::{domain, transformers::ForeignTryFrom},
|
||||
@ -109,6 +112,64 @@ impl<'a> SubscriptionHandler<'a> {
|
||||
.change_context(errors::ApiErrorResponse::CustomerNotFound)
|
||||
.attach_printable("subscriptions: unable to fetch customer from database")
|
||||
}
|
||||
pub async fn update_connector_customer_id_in_customer(
|
||||
state: &SessionState,
|
||||
merchant_context: &MerchantContext,
|
||||
merchant_connector_id: &common_utils::id_type::MerchantConnectorAccountId,
|
||||
customer: &hyperswitch_domain_models::customer::Customer,
|
||||
customer_create_response: Option<router_response_types::ConnectorCustomerResponseData>,
|
||||
) -> errors::RouterResult<hyperswitch_domain_models::customer::Customer> {
|
||||
match customer_create_response {
|
||||
Some(customer_response) => {
|
||||
match payments_core::customers::update_connector_customer_in_customers(
|
||||
merchant_connector_id.get_string_repr(),
|
||||
Some(customer),
|
||||
Some(customer_response.connector_customer_id),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Some(customer_update) => Self::update_customer(
|
||||
state,
|
||||
merchant_context,
|
||||
customer.clone(),
|
||||
customer_update,
|
||||
)
|
||||
.await
|
||||
.attach_printable("Failed to update customer with connector customer ID"),
|
||||
None => Ok(customer.clone()),
|
||||
}
|
||||
}
|
||||
None => Ok(customer.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_customer(
|
||||
state: &SessionState,
|
||||
merchant_context: &MerchantContext,
|
||||
customer: hyperswitch_domain_models::customer::Customer,
|
||||
customer_update: domain::CustomerUpdate,
|
||||
) -> errors::RouterResult<hyperswitch_domain_models::customer::Customer> {
|
||||
let key_manager_state = &(state).into();
|
||||
let merchant_key_store = merchant_context.get_merchant_key_store();
|
||||
let merchant_id = merchant_context.get_merchant_account().get_id();
|
||||
let db = state.store.as_ref();
|
||||
|
||||
let updated_customer = db
|
||||
.update_customer_by_customer_id_merchant_id(
|
||||
key_manager_state,
|
||||
customer.customer_id.clone(),
|
||||
merchant_id.clone(),
|
||||
customer,
|
||||
customer_update,
|
||||
merchant_key_store,
|
||||
merchant_context.get_merchant_account().storage_scheme,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("subscriptions: unable to update customer entry in database")?;
|
||||
|
||||
Ok(updated_customer)
|
||||
}
|
||||
|
||||
/// Helper function to find business profile.
|
||||
pub async fn find_business_profile(
|
||||
@ -304,6 +365,11 @@ impl SubscriptionWithHandler<'_> {
|
||||
subscription: self.subscription.clone(),
|
||||
merchant_account: self.merchant_account.clone(),
|
||||
profile,
|
||||
merchant_key_store: self
|
||||
.handler
|
||||
.merchant_context
|
||||
.get_merchant_key_store()
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
pub async fn get_mca(
|
||||
|
||||
@ -2620,10 +2620,6 @@ async fn subscription_incoming_webhook_flow(
|
||||
.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 =
|
||||
@ -2638,11 +2634,29 @@ async fn subscription_incoming_webhook_flow(
|
||||
let subscription_id = mit_payment_data.subscription_id.clone();
|
||||
|
||||
let subscription_with_handler = handler
|
||||
.find_subscription(subscription_id)
|
||||
.find_subscription(subscription_id.clone())
|
||||
.await
|
||||
.attach_printable("subscriptions: failed to get subscription entry in get_subscription")?;
|
||||
|
||||
let invoice_handler = subscription_with_handler.get_invoice_handler(profile.clone());
|
||||
let invoice = invoice_handler
|
||||
.find_invoice_by_subscription_id_connector_invoice_id(
|
||||
&state,
|
||||
subscription_id,
|
||||
mit_payment_data.invoice_id.clone(),
|
||||
)
|
||||
.await
|
||||
.attach_printable(
|
||||
"subscriptions: failed to get invoice by subscription id and connector invoice id",
|
||||
)?;
|
||||
if let Some(invoice) = invoice {
|
||||
// During CIT payment we would have already created invoice entry with status as PaymentPending or Paid.
|
||||
// So we skip incoming webhook for the already processed invoice
|
||||
if invoice.status != InvoiceStatus::InvoiceCreated {
|
||||
logger::info!("Invoice is already being processed, skipping MIT payment creation");
|
||||
return Ok(WebhookResponseTracker::NoEffect);
|
||||
}
|
||||
}
|
||||
|
||||
let payment_method_id = subscription_with_handler
|
||||
.subscription
|
||||
@ -2655,17 +2669,36 @@ async fn subscription_incoming_webhook_flow(
|
||||
|
||||
logger::info!("Payment method ID found: {}", payment_method_id);
|
||||
|
||||
let payment_id = generate_id(consts::ID_LENGTH, "pay");
|
||||
let payment_id = common_utils::id_type::PaymentId::wrap(payment_id).change_context(
|
||||
errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "payment_id",
|
||||
},
|
||||
)?;
|
||||
|
||||
// Multiple MIT payments for the same invoice_generated event is avoided by having the unique constraint on (subscription_id, connector_invoice_id) in the invoices table
|
||||
let invoice_entry = invoice_handler
|
||||
.create_invoice_entry(
|
||||
&state,
|
||||
billing_connector_mca_id.clone(),
|
||||
None,
|
||||
Some(payment_id),
|
||||
mit_payment_data.amount_due,
|
||||
mit_payment_data.currency_code,
|
||||
InvoiceStatus::PaymentPending,
|
||||
connector,
|
||||
None,
|
||||
None,
|
||||
Some(mit_payment_data.invoice_id.clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Create a sync job for the invoice with generated payment_id before initiating MIT payment creation.
|
||||
// This ensures that if payment creation call fails, the sync job can still retrieve the payment status
|
||||
invoice_handler
|
||||
.create_invoice_sync_job(
|
||||
&state,
|
||||
&invoice_entry,
|
||||
Some(mit_payment_data.invoice_id.clone()),
|
||||
connector,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -2678,23 +2711,14 @@ async fn subscription_incoming_webhook_flow(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let updated_invoice = invoice_handler
|
||||
let _updated_invoice = invoice_handler
|
||||
.update_invoice(
|
||||
&state,
|
||||
invoice_entry.id.clone(),
|
||||
payment_response.payment_method_id.clone(),
|
||||
Some(payment_response.payment_id.clone()),
|
||||
InvoiceStatus::from(payment_response.status),
|
||||
Some(mit_payment_data.invoice_id.get_string_repr().to_string()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
invoice_handler
|
||||
.create_invoice_sync_job(
|
||||
&state,
|
||||
&updated_invoice,
|
||||
mit_payment_data.invoice_id.get_string_repr().to_string(),
|
||||
connector,
|
||||
Some(mit_payment_data.invoice_id.clone()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@ -4393,6 +4393,24 @@ impl InvoiceInterface for KafkaStore {
|
||||
.get_latest_invoice_for_subscription(state, key_store, subscription_id)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn find_invoice_by_subscription_id_connector_invoice_id(
|
||||
&self,
|
||||
state: &KeyManagerState,
|
||||
key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore,
|
||||
subscription_id: String,
|
||||
connector_invoice_id: id_type::InvoiceId,
|
||||
) -> CustomResult<Option<DomainInvoice>, errors::StorageError> {
|
||||
self.diesel_store
|
||||
.find_invoice_by_subscription_id_connector_invoice_id(
|
||||
state,
|
||||
key_store,
|
||||
subscription_id,
|
||||
connector_invoice_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
||||
@ -7,7 +7,8 @@ pub struct InvoiceSyncTrackingData {
|
||||
pub merchant_id: id_type::MerchantId,
|
||||
pub profile_id: id_type::ProfileId,
|
||||
pub customer_id: id_type::CustomerId,
|
||||
pub connector_invoice_id: String,
|
||||
// connector_invoice_id is optional because in some cases (Trial/Future), the invoice might not have been created in the connector yet.
|
||||
pub connector_invoice_id: Option<id_type::InvoiceId>,
|
||||
pub connector_name: api_enums::Connector, // The connector to which the invoice belongs
|
||||
}
|
||||
|
||||
@ -18,7 +19,7 @@ pub struct InvoiceSyncRequest {
|
||||
pub merchant_id: id_type::MerchantId,
|
||||
pub profile_id: id_type::ProfileId,
|
||||
pub customer_id: id_type::CustomerId,
|
||||
pub connector_invoice_id: String,
|
||||
pub connector_invoice_id: Option<id_type::InvoiceId>,
|
||||
pub connector_name: api_enums::Connector,
|
||||
}
|
||||
|
||||
@ -44,7 +45,7 @@ impl InvoiceSyncRequest {
|
||||
merchant_id: id_type::MerchantId,
|
||||
profile_id: id_type::ProfileId,
|
||||
customer_id: id_type::CustomerId,
|
||||
connector_invoice_id: String,
|
||||
connector_invoice_id: Option<id_type::InvoiceId>,
|
||||
connector_name: api_enums::Connector,
|
||||
) -> Self {
|
||||
Self {
|
||||
@ -67,7 +68,7 @@ impl InvoiceSyncTrackingData {
|
||||
merchant_id: id_type::MerchantId,
|
||||
profile_id: id_type::ProfileId,
|
||||
customer_id: id_type::CustomerId,
|
||||
connector_invoice_id: String,
|
||||
connector_invoice_id: Option<id_type::InvoiceId>,
|
||||
connector_name: api_enums::Connector,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
||||
@ -162,11 +162,31 @@ impl<'a> InvoiceSyncHandler<'a> {
|
||||
Ok(payments_response)
|
||||
}
|
||||
|
||||
pub async fn perform_billing_processor_record_back_if_possible(
|
||||
&self,
|
||||
payment_response: subscription_types::PaymentResponseData,
|
||||
payment_status: common_enums::AttemptStatus,
|
||||
connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
invoice_sync_status: storage::invoice_sync::InvoiceSyncPaymentStatus,
|
||||
) -> CustomResult<(), router_errors::ApiErrorResponse> {
|
||||
if let Some(connector_invoice_id) = connector_invoice_id {
|
||||
Box::pin(self.perform_billing_processor_record_back(
|
||||
payment_response,
|
||||
payment_status,
|
||||
connector_invoice_id,
|
||||
invoice_sync_status,
|
||||
))
|
||||
.await
|
||||
.attach_printable("Failed to record back to billing processor")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn perform_billing_processor_record_back(
|
||||
&self,
|
||||
payment_response: subscription_types::PaymentResponseData,
|
||||
payment_status: common_enums::AttemptStatus,
|
||||
connector_invoice_id: String,
|
||||
connector_invoice_id: common_utils::id_type::InvoiceId,
|
||||
invoice_sync_status: storage::invoice_sync::InvoiceSyncPaymentStatus,
|
||||
) -> CustomResult<(), router_errors::ApiErrorResponse> {
|
||||
logger::info!("perform_billing_processor_record_back");
|
||||
@ -175,7 +195,6 @@ impl<'a> InvoiceSyncHandler<'a> {
|
||||
self.state,
|
||||
&self.merchant_account,
|
||||
&self.key_store,
|
||||
Some(self.customer.clone()),
|
||||
self.profile.clone(),
|
||||
)
|
||||
.await
|
||||
@ -185,6 +204,7 @@ impl<'a> InvoiceSyncHandler<'a> {
|
||||
self.subscription.clone(),
|
||||
self.merchant_account.clone(),
|
||||
self.profile.clone(),
|
||||
self.key_store.clone(),
|
||||
);
|
||||
|
||||
// TODO: Handle retries here on failure
|
||||
@ -220,20 +240,20 @@ impl<'a> InvoiceSyncHandler<'a> {
|
||||
&self,
|
||||
process: storage::ProcessTracker,
|
||||
payment_response: subscription_types::PaymentResponseData,
|
||||
connector_invoice_id: String,
|
||||
connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
|
||||
) -> CustomResult<(), router_errors::ApiErrorResponse> {
|
||||
let invoice_sync_status =
|
||||
storage::invoice_sync::InvoiceSyncPaymentStatus::from(payment_response.status);
|
||||
match invoice_sync_status {
|
||||
storage::invoice_sync::InvoiceSyncPaymentStatus::PaymentSucceeded => {
|
||||
Box::pin(self.perform_billing_processor_record_back(
|
||||
payment_response,
|
||||
Box::pin(self.perform_billing_processor_record_back_if_possible(
|
||||
payment_response.clone(),
|
||||
common_enums::AttemptStatus::Charged,
|
||||
connector_invoice_id,
|
||||
invoice_sync_status,
|
||||
invoice_sync_status.clone(),
|
||||
))
|
||||
.await
|
||||
.attach_printable("Failed to record back to billing processor")?;
|
||||
.attach_printable("Failed to record back success status to billing processor")?;
|
||||
|
||||
self.finish_process_with_business_status(&process, business_status::COMPLETED_BY_PT)
|
||||
.await
|
||||
@ -256,14 +276,14 @@ impl<'a> InvoiceSyncHandler<'a> {
|
||||
.attach_printable("Failed to update process tracker status")
|
||||
}
|
||||
storage::invoice_sync::InvoiceSyncPaymentStatus::PaymentFailed => {
|
||||
Box::pin(self.perform_billing_processor_record_back(
|
||||
payment_response,
|
||||
common_enums::AttemptStatus::Failure,
|
||||
Box::pin(self.perform_billing_processor_record_back_if_possible(
|
||||
payment_response.clone(),
|
||||
common_enums::AttemptStatus::Charged,
|
||||
connector_invoice_id,
|
||||
invoice_sync_status,
|
||||
invoice_sync_status.clone(),
|
||||
))
|
||||
.await
|
||||
.attach_printable("Failed to record back to billing processor")?;
|
||||
.attach_printable("Failed to record back failure status to billing processor")?;
|
||||
|
||||
self.finish_process_with_business_status(&process, business_status::COMPLETED_BY_PT)
|
||||
.await
|
||||
|
||||
@ -100,6 +100,27 @@ impl<T: DatabaseStore> InvoiceInterface for RouterStore<T> {
|
||||
subscription_id
|
||||
))))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn find_invoice_by_subscription_id_connector_invoice_id(
|
||||
&self,
|
||||
state: &KeyManagerState,
|
||||
key_store: &MerchantKeyStore,
|
||||
subscription_id: String,
|
||||
connector_invoice_id: common_utils::id_type::InvoiceId,
|
||||
) -> CustomResult<Option<DomainInvoice>, StorageError> {
|
||||
let conn = connection::pg_connection_read(self).await?;
|
||||
self.find_optional_resource(
|
||||
state,
|
||||
key_store,
|
||||
Invoice::get_invoice_by_subscription_id_connector_invoice_id(
|
||||
&conn,
|
||||
subscription_id,
|
||||
connector_invoice_id,
|
||||
),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@ -154,6 +175,24 @@ impl<T: DatabaseStore> InvoiceInterface for KVRouterStore<T> {
|
||||
.get_latest_invoice_for_subscription(state, key_store, subscription_id)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn find_invoice_by_subscription_id_connector_invoice_id(
|
||||
&self,
|
||||
state: &KeyManagerState,
|
||||
key_store: &MerchantKeyStore,
|
||||
subscription_id: String,
|
||||
connector_invoice_id: common_utils::id_type::InvoiceId,
|
||||
) -> CustomResult<Option<DomainInvoice>, StorageError> {
|
||||
self.router_store
|
||||
.find_invoice_by_subscription_id_connector_invoice_id(
|
||||
state,
|
||||
key_store,
|
||||
subscription_id,
|
||||
connector_invoice_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@ -197,4 +236,14 @@ impl InvoiceInterface for MockDb {
|
||||
) -> CustomResult<DomainInvoice, StorageError> {
|
||||
Err(StorageError::MockDbError)?
|
||||
}
|
||||
|
||||
async fn find_invoice_by_subscription_id_connector_invoice_id(
|
||||
&self,
|
||||
_state: &KeyManagerState,
|
||||
_key_store: &MerchantKeyStore,
|
||||
_subscription_id: String,
|
||||
_connector_invoice_id: common_utils::id_type::InvoiceId,
|
||||
) -> CustomResult<Option<DomainInvoice>, StorageError> {
|
||||
Err(StorageError::MockDbError)?
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
ALTER TABLE invoice DROP CONSTRAINT IF EXISTS invoice_subscription_id_connector_invoice_id_unique_index;
|
||||
DROP INDEX IF EXISTS invoice_subscription_id_connector_invoice_id_unique_index;
|
||||
@ -0,0 +1,2 @@
|
||||
-- Your SQL goes here
|
||||
ALTER TABLE invoice ADD CONSTRAINT invoice_subscription_id_connector_invoice_id_unique_index UNIQUE (subscription_id, connector_invoice_id);
|
||||
Reference in New Issue
Block a user