From 607424992af4196f5a3e01477f64d794b3594a47 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:33:09 +0530 Subject: [PATCH] feat(payment_methods): add support to pass apple pay recurring details to obtain apple pay merchant token (#6770) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 175 ++++++++++++++++++ api-reference/openapi_spec.json | 175 ++++++++++++++++++ crates/api_models/src/payments.rs | 94 ++++++++++ crates/diesel_models/src/types.rs | 49 ++++- .../src/connectors/bluesnap/transformers.rs | 1 + crates/hyperswitch_domain_models/src/lib.rs | 85 ++++++++- .../src/router_request_types.rs | 1 + crates/openapi/src/openapi.rs | 6 + crates/openapi/src/openapi_v2.rs | 6 + .../src/connector/payme/transformers.rs | 1 + .../src/connector/trustpay/transformers.rs | 1 + crates/router/src/core/payments.rs | 2 + .../src/core/payments/flows/session_flow.rs | 1 + .../router/src/core/payments/transformers.rs | 154 ++++++++++++++- 14 files changed, 748 insertions(+), 3 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 7a0f31178e0f..e1a745fd9b17 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -3288,12 +3288,169 @@ } ], "nullable": true + }, + "recurring_payment_request": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayRecurringPaymentRequest" + } + ], + "nullable": true + } + } + }, + "ApplePayPaymentTiming": { + "type": "string", + "enum": [ + "immediate", + "recurring" + ] + }, + "ApplePayRecurringDetails": { + "type": "object", + "required": [ + "payment_description", + "regular_billing", + "management_url" + ], + "properties": { + "payment_description": { + "type": "string", + "description": "A description of the recurring payment that Apple Pay displays to the user in the payment sheet" + }, + "regular_billing": { + "$ref": "#/components/schemas/ApplePayRegularBillingDetails" + }, + "billing_agreement": { + "type": "string", + "description": "A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment", + "nullable": true + }, + "management_url": { + "type": "string", + "description": "A URL to a web page where the user can update or delete the payment method for the recurring payment", + "example": "https://hyperswitch.io" + } + } + }, + "ApplePayRecurringPaymentRequest": { + "type": "object", + "required": [ + "payment_description", + "regular_billing", + "management_url" + ], + "properties": { + "payment_description": { + "type": "string", + "description": "A description of the recurring payment that Apple Pay displays to the user in the payment sheet" + }, + "regular_billing": { + "$ref": "#/components/schemas/ApplePayRegularBillingRequest" + }, + "billing_agreement": { + "type": "string", + "description": "A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment", + "nullable": true + }, + "management_url": { + "type": "string", + "description": "A URL to a web page where the user can update or delete the payment method for the recurring payment", + "example": "https://hyperswitch.io" } } }, "ApplePayRedirectData": { "type": "object" }, + "ApplePayRegularBillingDetails": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string", + "description": "The label that Apple Pay displays to the user in the payment sheet with the recurring details" + }, + "recurring_payment_start_date": { + "type": "string", + "format": "date-time", + "description": "The date of the first payment", + "example": "2023-09-10T23:59:59Z", + "nullable": true + }, + "recurring_payment_end_date": { + "type": "string", + "format": "date-time", + "description": "The date of the final payment", + "example": "2023-09-10T23:59:59Z", + "nullable": true + }, + "recurring_payment_interval_unit": { + "allOf": [ + { + "$ref": "#/components/schemas/RecurringPaymentIntervalUnit" + } + ], + "nullable": true + }, + "recurring_payment_interval_count": { + "type": "integer", + "format": "int32", + "description": "The number of interval units that make up the total payment interval", + "nullable": true + } + } + }, + "ApplePayRegularBillingRequest": { + "type": "object", + "required": [ + "amount", + "label", + "payment_timing" + ], + "properties": { + "amount": { + "type": "string", + "description": "The amount of the recurring payment", + "example": "38.02" + }, + "label": { + "type": "string", + "description": "The label that Apple Pay displays to the user in the payment sheet with the recurring details" + }, + "payment_timing": { + "$ref": "#/components/schemas/ApplePayPaymentTiming" + }, + "recurring_payment_start_date": { + "type": "string", + "format": "date-time", + "description": "The date of the first payment", + "nullable": true + }, + "recurring_payment_end_date": { + "type": "string", + "format": "date-time", + "description": "The date of the final payment", + "nullable": true + }, + "recurring_payment_interval_unit": { + "allOf": [ + { + "$ref": "#/components/schemas/RecurringPaymentIntervalUnit" + } + ], + "nullable": true + }, + "recurring_payment_interval_count": { + "type": "integer", + "format": "int32", + "description": "The number of interval units that make up the total payment interval", + "nullable": true + } + } + }, "ApplePaySessionResponse": { "oneOf": [ { @@ -8500,6 +8657,14 @@ }, "description": "Additional tags to be used for global search", "nullable": true + }, + "apple_pay_recurring_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayRecurringDetails" + } + ], + "nullable": true } } }, @@ -18405,6 +18570,16 @@ "propertyName": "type" } }, + "RecurringPaymentIntervalUnit": { + "type": "string", + "enum": [ + "year", + "month", + "day", + "hour", + "minute" + ] + }, "RedirectResponse": { "type": "object", "properties": { diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 9003272df618..4956df4713cc 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -5883,12 +5883,169 @@ } ], "nullable": true + }, + "recurring_payment_request": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayRecurringPaymentRequest" + } + ], + "nullable": true + } + } + }, + "ApplePayPaymentTiming": { + "type": "string", + "enum": [ + "immediate", + "recurring" + ] + }, + "ApplePayRecurringDetails": { + "type": "object", + "required": [ + "payment_description", + "regular_billing", + "management_url" + ], + "properties": { + "payment_description": { + "type": "string", + "description": "A description of the recurring payment that Apple Pay displays to the user in the payment sheet" + }, + "regular_billing": { + "$ref": "#/components/schemas/ApplePayRegularBillingDetails" + }, + "billing_agreement": { + "type": "string", + "description": "A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment", + "nullable": true + }, + "management_url": { + "type": "string", + "description": "A URL to a web page where the user can update or delete the payment method for the recurring payment", + "example": "https://hyperswitch.io" + } + } + }, + "ApplePayRecurringPaymentRequest": { + "type": "object", + "required": [ + "payment_description", + "regular_billing", + "management_url" + ], + "properties": { + "payment_description": { + "type": "string", + "description": "A description of the recurring payment that Apple Pay displays to the user in the payment sheet" + }, + "regular_billing": { + "$ref": "#/components/schemas/ApplePayRegularBillingRequest" + }, + "billing_agreement": { + "type": "string", + "description": "A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment", + "nullable": true + }, + "management_url": { + "type": "string", + "description": "A URL to a web page where the user can update or delete the payment method for the recurring payment", + "example": "https://hyperswitch.io" } } }, "ApplePayRedirectData": { "type": "object" }, + "ApplePayRegularBillingDetails": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string", + "description": "The label that Apple Pay displays to the user in the payment sheet with the recurring details" + }, + "recurring_payment_start_date": { + "type": "string", + "format": "date-time", + "description": "The date of the first payment", + "example": "2023-09-10T23:59:59Z", + "nullable": true + }, + "recurring_payment_end_date": { + "type": "string", + "format": "date-time", + "description": "The date of the final payment", + "example": "2023-09-10T23:59:59Z", + "nullable": true + }, + "recurring_payment_interval_unit": { + "allOf": [ + { + "$ref": "#/components/schemas/RecurringPaymentIntervalUnit" + } + ], + "nullable": true + }, + "recurring_payment_interval_count": { + "type": "integer", + "format": "int32", + "description": "The number of interval units that make up the total payment interval", + "nullable": true + } + } + }, + "ApplePayRegularBillingRequest": { + "type": "object", + "required": [ + "amount", + "label", + "payment_timing" + ], + "properties": { + "amount": { + "type": "string", + "description": "The amount of the recurring payment", + "example": "38.02" + }, + "label": { + "type": "string", + "description": "The label that Apple Pay displays to the user in the payment sheet with the recurring details" + }, + "payment_timing": { + "$ref": "#/components/schemas/ApplePayPaymentTiming" + }, + "recurring_payment_start_date": { + "type": "string", + "format": "date-time", + "description": "The date of the first payment", + "nullable": true + }, + "recurring_payment_end_date": { + "type": "string", + "format": "date-time", + "description": "The date of the final payment", + "nullable": true + }, + "recurring_payment_interval_unit": { + "allOf": [ + { + "$ref": "#/components/schemas/RecurringPaymentIntervalUnit" + } + ], + "nullable": true + }, + "recurring_payment_interval_count": { + "type": "integer", + "format": "int32", + "description": "The number of interval units that make up the total payment interval", + "nullable": true + } + } + }, "ApplePaySessionResponse": { "oneOf": [ { @@ -10998,6 +11155,14 @@ }, "description": "Additional tags to be used for global search", "nullable": true + }, + "apple_pay_recurring_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayRecurringDetails" + } + ], + "nullable": true } } }, @@ -22592,6 +22757,16 @@ "propertyName": "type" } }, + "RecurringPaymentIntervalUnit": { + "type": "string", + "enum": [ + "year", + "month", + "day", + "hour", + "minute" + ] + }, "RedirectResponse": { "type": "object", "properties": { diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index c3b1dfcabbf7..1c2a5c5a7169 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -6227,6 +6227,57 @@ pub struct ApplePayPaymentRequest { #[serde(skip_serializing_if = "Option::is_none")] /// The required shipping contacht fields for connector pub required_shipping_contact_fields: Option, + /// Recurring payment request for apple pay Merchant Token + #[serde(skip_serializing_if = "Option::is_none")] + pub recurring_payment_request: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct ApplePayRecurringPaymentRequest { + /// A description of the recurring payment that Apple Pay displays to the user in the payment sheet + pub payment_description: String, + /// The regular billing cycle for the recurring payment, including start and end dates, an interval, and an interval count + pub regular_billing: ApplePayRegularBillingRequest, + /// A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment + #[serde(skip_serializing_if = "Option::is_none")] + pub billing_agreement: Option, + /// A URL to a web page where the user can update or delete the payment method for the recurring payment + #[schema(value_type = String, example = "https://hyperswitch.io")] + pub management_url: common_utils::types::Url, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct ApplePayRegularBillingRequest { + /// The amount of the recurring payment + #[schema(value_type = String, example = "38.02")] + pub amount: StringMajorUnit, + /// The label that Apple Pay displays to the user in the payment sheet with the recurring details + pub label: String, + /// The time that the payment occurs as part of a successful transaction + pub payment_timing: ApplePayPaymentTiming, + /// The date of the first payment + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_start_date: Option, + /// The date of the final payment + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_end_date: Option, + /// The amount of time — in calendar units, such as day, month, or year — that represents a fraction of the total payment interval + #[serde(skip_serializing_if = "Option::is_none")] + pub recurring_payment_interval_unit: Option, + /// The number of interval units that make up the total payment interval + #[serde(skip_serializing_if = "Option::is_none")] + pub recurring_payment_interval_count: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum ApplePayPaymentTiming { + /// A value that specifies that the payment occurs when the transaction is complete + Immediate, + /// A value that specifies that the payment occurs on a regular basis + Recurring, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema, serde::Deserialize)] @@ -6504,6 +6555,49 @@ pub struct FeatureMetadata { /// Additional tags to be used for global search #[schema(value_type = Option>)] pub search_tags: Option>>, + /// Recurring payment details required for apple pay Merchant Token + pub apple_pay_recurring_details: Option, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct ApplePayRecurringDetails { + /// A description of the recurring payment that Apple Pay displays to the user in the payment sheet + pub payment_description: String, + /// The regular billing cycle for the recurring payment, including start and end dates, an interval, and an interval count + pub regular_billing: ApplePayRegularBillingDetails, + /// A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment + pub billing_agreement: Option, + /// A URL to a web page where the user can update or delete the payment method for the recurring payment + #[schema(value_type = String, example = "https://hyperswitch.io")] + pub management_url: common_utils::types::Url, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct ApplePayRegularBillingDetails { + /// The label that Apple Pay displays to the user in the payment sheet with the recurring details + pub label: String, + /// The date of the first payment + #[schema(example = "2023-09-10T23:59:59Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_start_date: Option, + /// The date of the final payment + #[schema(example = "2023-09-10T23:59:59Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_end_date: Option, + /// The amount of time — in calendar units, such as day, month, or year — that represents a fraction of the total payment interval + pub recurring_payment_interval_unit: Option, + /// The number of interval units that make up the total payment interval + pub recurring_payment_interval_count: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum RecurringPaymentIntervalUnit { + Year, + Month, + Day, + Hour, + Minute, } ///frm message is an object sent inside the payments response...when frm is invoked, its value is Some(...), else its None diff --git a/crates/diesel_models/src/types.rs b/crates/diesel_models/src/types.rs index 6562abab4d6a..7806a7c6e1b8 100644 --- a/crates/diesel_models/src/types.rs +++ b/crates/diesel_models/src/types.rs @@ -48,8 +48,55 @@ pub struct FeatureMetadata { // TODO: Convert this to hashedstrings to avoid PII sensitive data /// Additional tags to be used for global search pub search_tags: Option>>, + /// Recurring payment details required for apple pay Merchant Token + pub apple_pay_recurring_details: Option, } -impl masking::SerializableSecret for FeatureMetadata {} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +pub struct ApplePayRecurringDetails { + /// A description of the recurring payment that Apple Pay displays to the user in the payment sheet + pub payment_description: String, + /// The regular billing cycle for the recurring payment, including start and end dates, an interval, and an interval count + pub regular_billing: ApplePayRegularBillingDetails, + /// A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment + pub billing_agreement: Option, + /// A URL to a web page where the user can update or delete the payment method for the recurring payment + pub management_url: common_utils::types::Url, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +pub struct ApplePayRegularBillingDetails { + /// The label that Apple Pay displays to the user in the payment sheet with the recurring details + pub label: String, + /// The date of the first payment + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_start_date: Option, + /// The date of the final payment + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_end_date: Option, + /// The amount of time — in calendar units, such as day, month, or year — that represents a fraction of the total payment interval + pub recurring_payment_interval_unit: Option, + /// The number of interval units that make up the total payment interval + pub recurring_payment_interval_count: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +#[serde(rename_all = "snake_case")] +pub enum RecurringPaymentIntervalUnit { + Year, + Month, + Day, + Hour, + Minute, +} + +common_utils::impl_to_sql_from_sql_json!(ApplePayRecurringDetails); +common_utils::impl_to_sql_from_sql_json!(ApplePayRegularBillingDetails); +common_utils::impl_to_sql_from_sql_json!(RecurringPaymentIntervalUnit); + common_utils::impl_to_sql_from_sql_json!(FeatureMetadata); #[derive(Default, Debug, Eq, PartialEq, Deserialize, Serialize, Clone)] diff --git a/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs index 2e4503c34691..7970b1b1d42e 100644 --- a/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs @@ -555,6 +555,7 @@ impl merchant_identifier: Some(session_token_data.merchant_identifier), required_billing_contact_fields: None, required_shipping_contact_fields: None, + recurring_payment_request: None, }), connector: "bluesnap".to_string(), delayed_session_token: false, diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index a4fd154a7f16..fe152e876ccf 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -32,10 +32,16 @@ pub trait PayoutAttemptInterface {} pub trait PayoutsInterface {} use api_models::payments::{ + ApplePayRecurringDetails as ApiApplePayRecurringDetails, + ApplePayRegularBillingDetails as ApiApplePayRegularBillingDetails, FeatureMetadata as ApiFeatureMetadata, OrderDetailsWithAmount as ApiOrderDetailsWithAmount, + RecurringPaymentIntervalUnit as ApiRecurringPaymentIntervalUnit, RedirectResponse as ApiRedirectResponse, }; -use diesel_models::types::{FeatureMetadata, OrderDetailsWithAmount, RedirectResponse}; +use diesel_models::types::{ + ApplePayRecurringDetails, ApplePayRegularBillingDetails, FeatureMetadata, + OrderDetailsWithAmount, RecurringPaymentIntervalUnit, RedirectResponse, +}; #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub enum RemoteStorageObject { @@ -75,10 +81,13 @@ impl ApiModelToDieselModelConvertor for FeatureMetadata { let ApiFeatureMetadata { redirect_response, search_tags, + apple_pay_recurring_details, } = from; Self { redirect_response: redirect_response.map(RedirectResponse::convert_from), search_tags, + apple_pay_recurring_details: apple_pay_recurring_details + .map(ApplePayRecurringDetails::convert_from), } } @@ -86,11 +95,14 @@ impl ApiModelToDieselModelConvertor for FeatureMetadata { let Self { redirect_response, search_tags, + apple_pay_recurring_details, } = self; ApiFeatureMetadata { redirect_response: redirect_response .map(|redirect_response| redirect_response.convert_back()), search_tags, + apple_pay_recurring_details: apple_pay_recurring_details + .map(|value| value.convert_back()), } } } @@ -119,6 +131,77 @@ impl ApiModelToDieselModelConvertor for RedirectResponse { } } +impl ApiModelToDieselModelConvertor + for RecurringPaymentIntervalUnit +{ + fn convert_from(from: ApiRecurringPaymentIntervalUnit) -> Self { + match from { + ApiRecurringPaymentIntervalUnit::Year => Self::Year, + ApiRecurringPaymentIntervalUnit::Month => Self::Month, + ApiRecurringPaymentIntervalUnit::Day => Self::Day, + ApiRecurringPaymentIntervalUnit::Hour => Self::Hour, + ApiRecurringPaymentIntervalUnit::Minute => Self::Minute, + } + } + fn convert_back(self) -> ApiRecurringPaymentIntervalUnit { + match self { + Self::Year => ApiRecurringPaymentIntervalUnit::Year, + Self::Month => ApiRecurringPaymentIntervalUnit::Month, + Self::Day => ApiRecurringPaymentIntervalUnit::Day, + Self::Hour => ApiRecurringPaymentIntervalUnit::Hour, + Self::Minute => ApiRecurringPaymentIntervalUnit::Minute, + } + } +} + +impl ApiModelToDieselModelConvertor + for ApplePayRegularBillingDetails +{ + fn convert_from(from: ApiApplePayRegularBillingDetails) -> Self { + Self { + label: from.label, + recurring_payment_start_date: from.recurring_payment_start_date, + recurring_payment_end_date: from.recurring_payment_end_date, + recurring_payment_interval_unit: from + .recurring_payment_interval_unit + .map(RecurringPaymentIntervalUnit::convert_from), + recurring_payment_interval_count: from.recurring_payment_interval_count, + } + } + + fn convert_back(self) -> ApiApplePayRegularBillingDetails { + ApiApplePayRegularBillingDetails { + label: self.label, + recurring_payment_start_date: self.recurring_payment_start_date, + recurring_payment_end_date: self.recurring_payment_end_date, + recurring_payment_interval_unit: self + .recurring_payment_interval_unit + .map(|value| value.convert_back()), + recurring_payment_interval_count: self.recurring_payment_interval_count, + } + } +} + +impl ApiModelToDieselModelConvertor for ApplePayRecurringDetails { + fn convert_from(from: ApiApplePayRecurringDetails) -> Self { + Self { + payment_description: from.payment_description, + regular_billing: ApplePayRegularBillingDetails::convert_from(from.regular_billing), + billing_agreement: from.billing_agreement, + management_url: from.management_url, + } + } + + fn convert_back(self) -> ApiApplePayRecurringDetails { + ApiApplePayRecurringDetails { + payment_description: self.payment_description, + regular_billing: self.regular_billing.convert_back(), + billing_agreement: self.billing_agreement, + management_url: self.management_url, + } + } +} + impl ApiModelToDieselModelConvertor for OrderDetailsWithAmount { fn convert_from(from: ApiOrderDetailsWithAmount) -> Self { let ApiOrderDetailsWithAmount { diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 0e25e195c734..55624af9f728 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -831,6 +831,7 @@ pub struct PaymentsSessionData { pub email: Option, // Minor Unit amount for amount frame work pub minor_amount: MinorUnit, + pub apple_pay_recurring_details: Option, } #[derive(Debug, Clone, Default)] diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 03c280318691..a55f6ed4214c 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -418,6 +418,12 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ApplePayPaymentRequest, api_models::payments::ApplePayBillingContactFields, api_models::payments::ApplePayShippingContactFields, + api_models::payments::ApplePayRecurringPaymentRequest, + api_models::payments::ApplePayRegularBillingRequest, + api_models::payments::ApplePayPaymentTiming, + api_models::payments::RecurringPaymentIntervalUnit, + api_models::payments::ApplePayRecurringDetails, + api_models::payments::ApplePayRegularBillingDetails, api_models::payments::ApplePayAddressParameters, api_models::payments::AmountInfo, api_models::payments::ClickToPaySessionResponse, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index c9e4595ef492..c7425aed2a51 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -380,6 +380,12 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ApplePayBillingContactFields, api_models::payments::ApplePayShippingContactFields, api_models::payments::ApplePayAddressParameters, + api_models::payments::ApplePayRecurringPaymentRequest, + api_models::payments::ApplePayRegularBillingRequest, + api_models::payments::ApplePayPaymentTiming, + api_models::payments::RecurringPaymentIntervalUnit, + api_models::payments::ApplePayRecurringDetails, + api_models::payments::ApplePayRegularBillingDetails, api_models::payments::AmountInfo, api_models::payments::GooglePayWalletData, api_models::payments::PayPalWalletData, diff --git a/crates/router/src/connector/payme/transformers.rs b/crates/router/src/connector/payme/transformers.rs index 7b6b67c8408e..0e88ae236656 100644 --- a/crates/router/src/connector/payme/transformers.rs +++ b/crates/router/src/connector/payme/transformers.rs @@ -579,6 +579,7 @@ impl merchant_identifier: None, required_billing_contact_fields: None, required_shipping_contact_fields: None, + recurring_payment_request: None, }, ), connector: "payme".to_string(), diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index dafca256529e..c20cc1aeb3aa 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -1231,6 +1231,7 @@ pub fn get_apple_pay_session( merchant_identifier: None, required_billing_contact_fields: None, required_shipping_contact_fields: None, + recurring_payment_request: None, }), connector: "trustpay".to_string(), delayed_session_token: true, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 1902770a2f74..f758dc0b0e55 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -1781,6 +1781,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { json_payload: Some(req.json_payload.unwrap_or(serde_json::json!({})).into()), }), search_tags: None, + apple_pay_recurring_details: None, }), ..Default::default() }; @@ -2242,6 +2243,7 @@ impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { ), }), search_tags: None, + apple_pay_recurring_details: None, }), ..Default::default() }; diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 464781f3d7cc..1e3432220ac1 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -715,6 +715,7 @@ fn get_apple_pay_payment_request( merchant_identifier: Some(merchant_identifier.to_string()), required_billing_contact_fields, required_shipping_contact_fields, + recurring_payment_request: session_data.apple_pay_recurring_details, }; Ok(applepay_payment_request) } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 225b077b585a..15f01074c97e 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -8,7 +8,10 @@ use common_enums::{Currency, RequestIncrementalAuthorization}; use common_utils::{ consts::X_HS_LATENCY, fp_utils, pii, - types::{self as common_utils_type, AmountConvertor, MinorUnit, StringMajorUnitForConnector}, + types::{ + self as common_utils_type, AmountConvertor, MinorUnit, StringMajorUnit, + StringMajorUnitForConnector, + }, }; use diesel_models::{ ephemeral_key, @@ -559,6 +562,25 @@ pub async fn construct_payment_router_data_for_sdk_session<'a>( .map(|order_detail| order_detail.expose()) .collect() }); + let required_amount_type = StringMajorUnitForConnector; + + let apple_pay_amount = required_amount_type + .convert( + payment_data.payment_intent.amount_details.order_amount, + payment_data.payment_intent.amount_details.currency, + ) + .change_context(errors::ApiErrorResponse::PreconditionFailed { + message: "Failed to convert amount to string major unit for applePay".to_string(), + })?; + + let apple_pay_recurring_details = payment_data + .payment_intent + .feature_metadata + .and_then(|feature_metadata| feature_metadata.apple_pay_recurring_details) + .map(|apple_pay_recurring_details| { + ForeignInto::foreign_into((apple_pay_recurring_details, apple_pay_amount)) + }); + // TODO: few fields are repeated in both routerdata and request let request = types::PaymentsSessionData { amount: payment_data @@ -582,6 +604,7 @@ pub async fn construct_payment_router_data_for_sdk_session<'a>( order_details, email, minor_amount: payment_data.payment_intent.amount_details.order_amount, + apple_pay_recurring_details, }; // TODO: evaluate the fields in router data, if they are required or not @@ -3082,6 +3105,22 @@ impl TryFrom> for types::PaymentsSessionD // net_amount here would include amount, surcharge_amount and shipping_cost let net_amount = amount + surcharge_amount + shipping_cost; + let required_amount_type = StringMajorUnitForConnector; + + let apple_pay_amount = required_amount_type + .convert(net_amount, payment_data.currency) + .change_context(errors::ApiErrorResponse::PreconditionFailed { + message: "Failed to convert amount to string major unit for applePay".to_string(), + })?; + + let apple_pay_recurring_details = payment_data + .payment_intent + .feature_metadata + .and_then(|feature_metadata| feature_metadata.apple_pay_recurring_details) + .map(|apple_pay_recurring_details| { + ForeignInto::foreign_into((apple_pay_recurring_details, apple_pay_amount)) + }); + Ok(Self { amount: amount.get_amount_as_i64(), //need to change once we move to connector module minor_amount: amount, @@ -3097,6 +3136,7 @@ impl TryFrom> for types::PaymentsSessionD order_details, surcharge_details: payment_data.surcharge_details, email: payment_data.email, + apple_pay_recurring_details, }) } } @@ -3143,6 +3183,29 @@ impl TryFrom> for types::PaymentsSessionD // net_amount here would include amount, surcharge_amount and shipping_cost let net_amount = amount + surcharge_amount + shipping_cost; + let required_amount_type = StringMajorUnitForConnector; + + let apple_pay_amount = required_amount_type + .convert(net_amount, payment_data.currency) + .change_context(errors::ApiErrorResponse::PreconditionFailed { + message: "Failed to convert amount to string major unit for applePay".to_string(), + })?; + + let apple_pay_recurring_details = payment_data + .payment_intent + .feature_metadata + .map(|feature_metadata| { + feature_metadata + .parse_value::("FeatureMetadata") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed parsing FeatureMetadata") + }) + .transpose()? + .and_then(|feature_metadata| feature_metadata.apple_pay_recurring_details) + .map(|apple_pay_recurring_details| { + ForeignFrom::foreign_from((apple_pay_recurring_details, apple_pay_amount)) + }); + Ok(Self { amount: net_amount.get_amount_as_i64(), //need to change once we move to connector module minor_amount: amount, @@ -3158,10 +3221,99 @@ impl TryFrom> for types::PaymentsSessionD order_details, email: payment_data.email, surcharge_details: payment_data.surcharge_details, + apple_pay_recurring_details, }) } } +impl + ForeignFrom<( + diesel_models::types::ApplePayRecurringDetails, + StringMajorUnit, + )> for api_models::payments::ApplePayRecurringPaymentRequest +{ + fn foreign_from( + (apple_pay_recurring_details, net_amount): ( + diesel_models::types::ApplePayRecurringDetails, + StringMajorUnit, + ), + ) -> Self { + Self { + payment_description: apple_pay_recurring_details.payment_description, + regular_billing: api_models::payments::ApplePayRegularBillingRequest { + amount: net_amount, + label: apple_pay_recurring_details.regular_billing.label, + payment_timing: api_models::payments::ApplePayPaymentTiming::Recurring, + recurring_payment_start_date: apple_pay_recurring_details + .regular_billing + .recurring_payment_start_date, + recurring_payment_end_date: apple_pay_recurring_details + .regular_billing + .recurring_payment_end_date, + recurring_payment_interval_unit: apple_pay_recurring_details + .regular_billing + .recurring_payment_interval_unit + .map(ForeignFrom::foreign_from), + recurring_payment_interval_count: apple_pay_recurring_details + .regular_billing + .recurring_payment_interval_count, + }, + billing_agreement: apple_pay_recurring_details.billing_agreement, + management_url: apple_pay_recurring_details.management_url, + } + } +} + +impl ForeignFrom + for api_models::payments::ApplePayRecurringDetails +{ + fn foreign_from( + apple_pay_recurring_details: diesel_models::types::ApplePayRecurringDetails, + ) -> Self { + Self { + payment_description: apple_pay_recurring_details.payment_description, + regular_billing: ForeignFrom::foreign_from(apple_pay_recurring_details.regular_billing), + billing_agreement: apple_pay_recurring_details.billing_agreement, + management_url: apple_pay_recurring_details.management_url, + } + } +} + +impl ForeignFrom + for api_models::payments::ApplePayRegularBillingDetails +{ + fn foreign_from( + apple_pay_regular_billing: diesel_models::types::ApplePayRegularBillingDetails, + ) -> Self { + Self { + label: apple_pay_regular_billing.label, + recurring_payment_start_date: apple_pay_regular_billing.recurring_payment_start_date, + recurring_payment_end_date: apple_pay_regular_billing.recurring_payment_end_date, + recurring_payment_interval_unit: apple_pay_regular_billing + .recurring_payment_interval_unit + .map(ForeignFrom::foreign_from), + recurring_payment_interval_count: apple_pay_regular_billing + .recurring_payment_interval_count, + } + } +} + +impl ForeignFrom + for api_models::payments::RecurringPaymentIntervalUnit +{ + fn foreign_from( + apple_pay_recurring_payment_interval_unit: diesel_models::types::RecurringPaymentIntervalUnit, + ) -> Self { + match apple_pay_recurring_payment_interval_unit { + diesel_models::types::RecurringPaymentIntervalUnit::Day => Self::Day, + diesel_models::types::RecurringPaymentIntervalUnit::Month => Self::Month, + diesel_models::types::RecurringPaymentIntervalUnit::Year => Self::Year, + diesel_models::types::RecurringPaymentIntervalUnit::Hour => Self::Hour, + diesel_models::types::RecurringPaymentIntervalUnit::Minute => Self::Minute, + } + } +} + #[cfg(feature = "v1")] impl TryFrom> for types::SetupMandateRequestData { type Error = error_stack::Report;