diff --git a/.github/workflows/CI-pr.yml b/.github/workflows/CI-pr.yml index 220c4f2577..369da734da 100644 --- a/.github/workflows/CI-pr.yml +++ b/.github/workflows/CI-pr.yml @@ -325,7 +325,7 @@ jobs: - name: Run cargo clippy with v2 features enabled shell: bash - run: just clippy_v2 -- -D warnings -Aunused -Aclippy::todo -Aclippy::diverging_sub_expression + run: just clippy_v2 - name: Run cargo check enabling only the release and v2 features shell: bash diff --git a/api-reference-v2/api-reference/payments/payments--confirm-intent.mdx b/api-reference-v2/api-reference/payments/payments--confirm-intent.mdx new file mode 100644 index 0000000000..58624c2771 --- /dev/null +++ b/api-reference-v2/api-reference/payments/payments--confirm-intent.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/payments/{id}/confirm-intent +--- \ No newline at end of file diff --git a/api-reference-v2/mint.json b/api-reference-v2/mint.json index 5efdb1ed2a..974e530f31 100644 --- a/api-reference-v2/mint.json +++ b/api-reference-v2/mint.json @@ -38,7 +38,8 @@ "group": "Payments", "pages": [ "api-reference/payments/payments--create-intent", - "api-reference/payments/payments--session-token" + "api-reference/payments/payments--session-token", + "api-reference/payments/payments--confirm-intent" ] }, { diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 72afa1dc3d..bd9de3a296 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -663,7 +663,7 @@ ] } }, - "/v2/payments/{payment_id}/create_external_sdk_tokens": { + "/v2/payments/{payment_id}/create-external-sdk-tokens": { "post": { "tags": [ "Payments" @@ -1848,6 +1848,62 @@ ] } }, + "/v2/payments/{id}/confirm-intent": { + "post": { + "tags": [ + "Payments" + ], + "summary": "Payments - Confirm Intent", + "description": "**Confirms a payment intent object with the payment method data**\n\n.", + "operationId": "Confirm Payment Intent", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsConfirmIntentRequest" + }, + "examples": { + "Confirm the payment intent with card details": { + "value": { + "payment_method_data": { + "card": { + "card_cvc": "123", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "joseph Doe", + "card_number": "4242424242424242" + } + }, + "payment_method_type": "card" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Payment created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsConfirmIntentResponse" + } + } + } + }, + "400": { + "description": "Missing Mandatory fields" + } + }, + "security": [ + { + "publisable_key": [] + } + ] + } + }, "/v2/refunds": { "post": { "tags": [ @@ -2472,6 +2528,65 @@ } } }, + "AmountDetailsResponse": { + "type": "object", + "required": [ + "order_amount", + "currency", + "skip_external_tax_calculation", + "skip_surcharge_calculation" + ], + "properties": { + "order_amount": { + "type": "integer", + "format": "int64", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", + "example": 6540, + "minimum": 0 + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "shipping_cost": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "order_tax_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "skip_external_tax_calculation": { + "$ref": "#/components/schemas/TaxCalculationOverride" + }, + "skip_surcharge_calculation": { + "$ref": "#/components/schemas/SurchargeCalculationOverride" + }, + "surcharge_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "tax_on_surcharge": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + } + } + }, "AmountFilter": { "type": "object", "properties": { @@ -5485,6 +5600,88 @@ "greater_than_equal" ] }, + "ConfirmIntentAmountDetailsResponse": { + "type": "object", + "required": [ + "currency", + "skip_external_tax_calculation", + "skip_surcharge_calculation", + "net_amount", + "amount_capturable" + ], + "properties": { + "order_amount": { + "type": "integer", + "format": "int64", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", + "example": 6540, + "minimum": 0 + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "shipping_cost": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "order_tax_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "skip_external_tax_calculation": { + "$ref": "#/components/schemas/TaxCalculationOverride" + }, + "skip_surcharge_calculation": { + "$ref": "#/components/schemas/SurchargeCalculationOverride" + }, + "surcharge_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "tax_on_surcharge": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "net_amount": { + "$ref": "#/components/schemas/MinorUnit" + }, + "amount_to_capture": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "amount_capturable": { + "$ref": "#/components/schemas/MinorUnit" + }, + "amount_captured": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + } + } + }, "Connector": { "type": "string", "description": "A connector is an integration to fulfill payments", @@ -7101,6 +7298,34 @@ } } }, + "ErrorDetails": { + "type": "object", + "description": "Error details for the payment", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "The error code" + }, + "message": { + "type": "string", + "description": "The error message" + }, + "unified_code": { + "type": "string", + "description": "The unified error code across all connectors.\nThis can be relied upon for taking decisions based on the error.", + "nullable": true + }, + "unified_message": { + "type": "string", + "description": "The unified error message across all connectors.\nIf there is a translation available, this will have the translated message", + "nullable": true + } + } + }, "EventClass": { "type": "string", "enum": [ @@ -13009,162 +13234,29 @@ } } }, - "PaymentsConfirmRequest": { + "PaymentsConfirmIntentRequest": { "type": "object", + "description": "Request for Payment Intent Confirm", + "required": [ + "payment_method_data", + "payment_method_type", + "payment_method_subtype" + ], "properties": { - "amount": { - "type": "integer", - "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", - "example": 6540, - "nullable": true, - "minimum": 0 - }, - "currency": { - "allOf": [ - { - "$ref": "#/components/schemas/Currency" - } - ], - "nullable": true - }, - "amount_to_capture": { - "type": "integer", - "format": "int64", - "description": "The Amount to be captured / debited from the users payment method. It shall be in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc., If not provided, the default amount_to_capture will be the payment amount. Also, it must be less than or equal to the original payment account.", - "example": 6540, - "nullable": true - }, - "shipping_cost": { - "type": "integer", - "format": "int64", - "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", - "example": 6540, - "nullable": true - }, - "payment_id": { - "type": "string", - "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", - "example": "pay_mbabizu24mvu3mela5njyhpit4", - "nullable": true, - "maxLength": 30, - "minLength": 30 - }, - "routing": { - "allOf": [ - { - "$ref": "#/components/schemas/StraightThroughAlgorithm" - } - ], - "nullable": true - }, - "connector": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Connector" - }, - "description": "This allows to manually select a connector with which the payment can go through.", - "example": [ - "stripe", - "adyen" - ], - "nullable": true - }, - "capture_method": { - "allOf": [ - { - "$ref": "#/components/schemas/CaptureMethod" - } - ], - "nullable": true - }, - "authentication_type": { - "allOf": [ - { - "$ref": "#/components/schemas/AuthenticationType" - } - ], - "default": "three_ds", - "nullable": true - }, - "billing": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "confirm": { - "type": "boolean", - "description": "Whether to confirm the payment (if applicable). It can be used to completely process a payment by attaching a payment method, setting `confirm=true` and `capture_method = automatic` in the *Payments/Create API* request itself.", - "default": false, - "example": true, - "nullable": true - }, - "customer": { - "allOf": [ - { - "$ref": "#/components/schemas/CustomerDetails" - } - ], - "nullable": true - }, - "customer_id": { - "type": "string", - "description": "The identifier for the customer", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 - }, - "off_session": { - "type": "boolean", - "description": "Set to true to indicate that the customer is not in your checkout flow during this payment, and therefore is unable to authenticate. This parameter is intended for scenarios where you collect card details and charge them later. When making a recurring payment by passing a mandate_id, this parameter is mandatory", - "example": true, - "nullable": true - }, - "description": { - "type": "string", - "description": "A description for the payment", - "example": "It's my first payment request", - "nullable": true - }, "return_url": { "type": "string", - "description": "The URL to which you want the user to be redirected after the completion of the payment operation", + "description": "The URL to which you want the user to be redirected after the completion of the payment operation\nIf this url is not passed, the url configured in the business profile will be used", "example": "https://hyperswitch.io", "nullable": true }, - "setup_future_usage": { - "allOf": [ - { - "$ref": "#/components/schemas/FutureUsage" - } - ], - "nullable": true - }, "payment_method_data": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodDataRequest" - } - ], - "nullable": true + "$ref": "#/components/schemas/PaymentMethodDataRequest" }, - "payment_method": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethod" - } - ], - "nullable": true + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethod" }, - "payment_token": { - "type": "string", - "description": "As Hyperswitch tokenises the sensitive details about the payments method, it provides the payment_token as a reference to a stored payment method, ensuring that the sensitive details are not exposed in any manner.", - "example": "187282ab-40ef-47a9-9206-5099ba31e432", - "nullable": true + "payment_method_subtype": { + "$ref": "#/components/schemas/PaymentMethodType" }, "shipping": { "allOf": [ @@ -13174,43 +13266,6 @@ ], "nullable": true }, - "statement_descriptor_name": { - "type": "string", - "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", - "example": "Hyperswitch Router", - "nullable": true, - "maxLength": 255 - }, - "statement_descriptor_suffix": { - "type": "string", - "description": "Provides information about a card payment that customers see on their statements. Concatenated with the prefix (shortened descriptor) or statement descriptor that’s set on the account to form the complete statement descriptor. Maximum 22 characters for the concatenated descriptor.", - "example": "Payment for shoes purchase", - "nullable": true, - "maxLength": 255 - }, - "order_details": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderDetailsWithAmount" - }, - "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", - "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", - "nullable": true - }, - "client_secret": { - "type": "string", - "description": "It's a token used for client side verification.", - "example": "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo", - "nullable": true - }, - "mandate_data": { - "allOf": [ - { - "$ref": "#/components/schemas/MandateData" - } - ], - "nullable": true - }, "customer_acceptance": { "allOf": [ { @@ -13219,12 +13274,88 @@ ], "nullable": true }, - "mandate_id": { + "browser_info": { + "allOf": [ + { + "$ref": "#/components/schemas/BrowserInformation" + } + ], + "nullable": true + } + } + }, + "PaymentsConfirmIntentResponse": { + "type": "object", + "description": "Response for Payment Intent Confirm", + "required": [ + "id", + "status", + "amount", + "connector", + "client_secret", + "created", + "payment_method_type", + "payment_method_subtype", + "merchant_connector_id" + ], + "properties": { + "id": { "type": "string", - "description": "A unique identifier to link the payment to a mandate. To do Recurring payments after a mandate has been created, pass the mandate_id instead of payment_method_data", - "example": "mandate_iwer89rnjef349dni3", - "nullable": true, - "maxLength": 255 + "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant.", + "example": "12345_pay_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 + }, + "status": { + "$ref": "#/components/schemas/IntentStatus" + }, + "amount": { + "$ref": "#/components/schemas/ConfirmIntentAmountDetailsResponse" + }, + "connector": { + "type": "string", + "description": "The connector used for the payment", + "example": "stripe" + }, + "client_secret": { + "type": "string", + "description": "It's a token used for client side verification." + }, + "created": { + "type": "string", + "format": "date-time", + "description": "Time when the payment was created", + "example": "2022-09-10T10:11:12Z" + }, + "payment_method_data": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodDataResponseWithBilling" + } + ], + "nullable": true + }, + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethod" + }, + "payment_method_subtype": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "connector_transaction_id": { + "type": "string", + "description": "A unique identifier for a payment provided by the connector", + "example": "993672945374576J", + "nullable": true + }, + "connector_reference_id": { + "type": "string", + "description": "reference(Identifier) to the payment at connector side", + "example": "993672945374576J", + "nullable": true + }, + "merchant_connector_id": { + "type": "string", + "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment" }, "browser_info": { "allOf": [ @@ -13234,138 +13365,13 @@ ], "nullable": true }, - "payment_experience": { + "error": { "allOf": [ { - "$ref": "#/components/schemas/PaymentExperience" + "$ref": "#/components/schemas/ErrorDetails" } ], "nullable": true - }, - "payment_method_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodType" - } - ], - "nullable": true - }, - "merchant_connector_details": { - "allOf": [ - { - "$ref": "#/components/schemas/MerchantConnectorDetailsWrap" - } - ], - "nullable": true - }, - "allowed_payment_method_types": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodType" - }, - "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", - "nullable": true - }, - "retry_action": { - "allOf": [ - { - "$ref": "#/components/schemas/RetryAction" - } - ], - "nullable": true - }, - "metadata": { - "type": "object", - "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.", - "nullable": true - }, - "connector_metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/ConnectorMetadata" - } - ], - "nullable": true - }, - "payment_link": { - "type": "boolean", - "description": "Whether to generate the payment link for this payment or not (if applicable)", - "default": false, - "example": true, - "nullable": true - }, - "payment_link_config": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" - } - ], - "nullable": true - }, - "payment_link_config_id": { - "type": "string", - "description": "Custom payment link config id set at business profile, send only if business_specific_configs is configured", - "nullable": true - }, - "payment_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentType" - } - ], - "nullable": true - }, - "request_incremental_authorization": { - "type": "boolean", - "description": "Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it.", - "nullable": true - }, - "session_expiry": { - "type": "integer", - "format": "int32", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds\n(900) for 15 mins", - "example": 900, - "nullable": true, - "minimum": 0 - }, - "frm_metadata": { - "type": "object", - "description": "Additional data related to some frm(Fraud Risk Management) connectors", - "nullable": true - }, - "request_external_three_ds_authentication": { - "type": "boolean", - "description": "Whether to perform external authentication (if applicable)", - "example": true, - "nullable": true - }, - "recurring_details": { - "allOf": [ - { - "$ref": "#/components/schemas/RecurringDetails" - } - ], - "nullable": true - }, - "charges": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentChargeRequest" - } - ], - "nullable": true - }, - "merchant_order_reference_id": { - "type": "string", - "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", - "example": "Custom_Order_id_123", - "nullable": true, - "maxLength": 255 - }, - "skip_external_tax_calculation": { - "type": "boolean", - "description": "Whether to calculate tax for this payment intent", - "nullable": true } } }, @@ -13583,7 +13589,7 @@ "description": "Global Payment Id for the payment" }, "amount_details": { - "$ref": "#/components/schemas/AmountDetails" + "$ref": "#/components/schemas/AmountDetailsResponse" }, "client_secret": { "type": "string", @@ -13734,376 +13740,6 @@ }, "additionalProperties": false }, - "PaymentsCreateRequest": { - "type": "object", - "required": [ - "amount", - "currency" - ], - "properties": { - "amount": { - "type": "integer", - "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", - "minimum": 0 - }, - "currency": { - "$ref": "#/components/schemas/Currency" - }, - "amount_to_capture": { - "type": "integer", - "format": "int64", - "description": "The Amount to be captured / debited from the users payment method. It shall be in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc., If not provided, the default amount_to_capture will be the payment amount. Also, it must be less than or equal to the original payment account.", - "example": 6540, - "nullable": true - }, - "shipping_cost": { - "type": "integer", - "format": "int64", - "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", - "example": 6540, - "nullable": true - }, - "payment_id": { - "type": "string", - "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", - "example": "pay_mbabizu24mvu3mela5njyhpit4", - "nullable": true, - "maxLength": 30, - "minLength": 30 - }, - "routing": { - "allOf": [ - { - "$ref": "#/components/schemas/StraightThroughAlgorithm" - } - ], - "nullable": true - }, - "connector": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Connector" - }, - "description": "This allows to manually select a connector with which the payment can go through.", - "example": [ - "stripe", - "adyen" - ], - "nullable": true - }, - "capture_method": { - "allOf": [ - { - "$ref": "#/components/schemas/CaptureMethod" - } - ], - "nullable": true - }, - "authentication_type": { - "allOf": [ - { - "$ref": "#/components/schemas/AuthenticationType" - } - ], - "default": "three_ds", - "nullable": true - }, - "billing": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "confirm": { - "type": "boolean", - "description": "Whether to confirm the payment (if applicable). It can be used to completely process a payment by attaching a payment method, setting `confirm=true` and `capture_method = automatic` in the *Payments/Create API* request itself.", - "default": false, - "example": true, - "nullable": true - }, - "customer": { - "allOf": [ - { - "$ref": "#/components/schemas/CustomerDetails" - } - ], - "nullable": true - }, - "customer_id": { - "type": "string", - "description": "The identifier for the customer", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 - }, - "off_session": { - "type": "boolean", - "description": "Set to true to indicate that the customer is not in your checkout flow during this payment, and therefore is unable to authenticate. This parameter is intended for scenarios where you collect card details and charge them later. When making a recurring payment by passing a mandate_id, this parameter is mandatory", - "example": true, - "nullable": true - }, - "description": { - "type": "string", - "description": "A description for the payment", - "example": "It's my first payment request", - "nullable": true - }, - "return_url": { - "type": "string", - "description": "The URL to which you want the user to be redirected after the completion of the payment operation", - "example": "https://hyperswitch.io", - "nullable": true - }, - "setup_future_usage": { - "allOf": [ - { - "$ref": "#/components/schemas/FutureUsage" - } - ], - "nullable": true - }, - "payment_method_data": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodDataRequest" - } - ], - "nullable": true - }, - "payment_method": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethod" - } - ], - "nullable": true - }, - "payment_token": { - "type": "string", - "description": "As Hyperswitch tokenises the sensitive details about the payments method, it provides the payment_token as a reference to a stored payment method, ensuring that the sensitive details are not exposed in any manner.", - "example": "187282ab-40ef-47a9-9206-5099ba31e432", - "nullable": true - }, - "shipping": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "statement_descriptor_name": { - "type": "string", - "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", - "example": "Hyperswitch Router", - "nullable": true, - "maxLength": 255 - }, - "statement_descriptor_suffix": { - "type": "string", - "description": "Provides information about a card payment that customers see on their statements. Concatenated with the prefix (shortened descriptor) or statement descriptor that’s set on the account to form the complete statement descriptor. Maximum 22 characters for the concatenated descriptor.", - "example": "Payment for shoes purchase", - "nullable": true, - "maxLength": 255 - }, - "order_details": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderDetailsWithAmount" - }, - "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", - "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", - "nullable": true - }, - "mandate_data": { - "allOf": [ - { - "$ref": "#/components/schemas/MandateData" - } - ], - "nullable": true - }, - "customer_acceptance": { - "allOf": [ - { - "$ref": "#/components/schemas/CustomerAcceptance" - } - ], - "nullable": true - }, - "mandate_id": { - "type": "string", - "description": "A unique identifier to link the payment to a mandate. To do Recurring payments after a mandate has been created, pass the mandate_id instead of payment_method_data", - "example": "mandate_iwer89rnjef349dni3", - "nullable": true, - "maxLength": 255 - }, - "browser_info": { - "allOf": [ - { - "$ref": "#/components/schemas/BrowserInformation" - } - ], - "nullable": true - }, - "payment_experience": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentExperience" - } - ], - "nullable": true - }, - "payment_method_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodType" - } - ], - "nullable": true - }, - "business_country": { - "allOf": [ - { - "$ref": "#/components/schemas/CountryAlpha2" - } - ], - "nullable": true - }, - "business_label": { - "type": "string", - "description": "Business label of the merchant for this payment.\nTo be deprecated soon. Pass the profile_id instead", - "example": "food", - "nullable": true - }, - "merchant_connector_details": { - "allOf": [ - { - "$ref": "#/components/schemas/MerchantConnectorDetailsWrap" - } - ], - "nullable": true - }, - "allowed_payment_method_types": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodType" - }, - "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", - "nullable": true - }, - "metadata": { - "type": "object", - "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.", - "nullable": true - }, - "connector_metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/ConnectorMetadata" - } - ], - "nullable": true - }, - "payment_link": { - "type": "boolean", - "description": "Whether to generate the payment link for this payment or not (if applicable)", - "default": false, - "example": true, - "nullable": true - }, - "payment_link_config": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" - } - ], - "nullable": true - }, - "payment_link_config_id": { - "type": "string", - "description": "Custom payment link config id set at business profile, send only if business_specific_configs is configured", - "nullable": true - }, - "profile_id": { - "type": "string", - "description": "The business profile to be used for this payment, if not passed the default business profile associated with the merchant account will be used. It is mandatory in case multiple business profiles have been set up.", - "nullable": true - }, - "surcharge_details": { - "allOf": [ - { - "$ref": "#/components/schemas/RequestSurchargeDetails" - } - ], - "nullable": true - }, - "payment_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentType" - } - ], - "nullable": true - }, - "request_incremental_authorization": { - "type": "boolean", - "description": "Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it.", - "nullable": true - }, - "session_expiry": { - "type": "integer", - "format": "int32", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds\n(900) for 15 mins", - "example": 900, - "nullable": true, - "minimum": 0 - }, - "frm_metadata": { - "type": "object", - "description": "Additional data related to some frm(Fraud Risk Management) connectors", - "nullable": true - }, - "request_external_three_ds_authentication": { - "type": "boolean", - "description": "Whether to perform external authentication (if applicable)", - "example": true, - "nullable": true - }, - "recurring_details": { - "allOf": [ - { - "$ref": "#/components/schemas/RecurringDetails" - } - ], - "nullable": true - }, - "charges": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentChargeRequest" - } - ], - "nullable": true - }, - "merchant_order_reference_id": { - "type": "string", - "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", - "example": "Custom_Order_id_123", - "nullable": true, - "maxLength": 255 - }, - "skip_external_tax_calculation": { - "type": "boolean", - "description": "Whether to calculate tax for this payment intent", - "nullable": true - } - } - }, "PaymentsCreateResponseOpenApi": { "type": "object", "required": [ @@ -14797,459 +14433,6 @@ } } }, - "PaymentsRequest": { - "type": "object", - "properties": { - "amount": { - "type": "integer", - "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", - "example": 6540, - "nullable": true, - "minimum": 0 - }, - "currency": { - "allOf": [ - { - "$ref": "#/components/schemas/Currency" - } - ], - "nullable": true - }, - "amount_to_capture": { - "type": "integer", - "format": "int64", - "description": "The Amount to be captured / debited from the users payment method. It shall be in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc., If not provided, the default amount_to_capture will be the payment amount. Also, it must be less than or equal to the original payment account.", - "example": 6540, - "nullable": true - }, - "shipping_cost": { - "type": "integer", - "format": "int64", - "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", - "example": 6540, - "nullable": true - }, - "payment_id": { - "type": "string", - "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", - "example": "pay_mbabizu24mvu3mela5njyhpit4", - "nullable": true, - "maxLength": 30, - "minLength": 30 - }, - "merchant_id": { - "type": "string", - "description": "This is an identifier for the merchant account. This is inferred from the API key\nprovided during the request", - "example": "merchant_1668273825", - "nullable": true, - "maxLength": 255 - }, - "routing": { - "allOf": [ - { - "$ref": "#/components/schemas/StraightThroughAlgorithm" - } - ], - "nullable": true - }, - "connector": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Connector" - }, - "description": "This allows to manually select a connector with which the payment can go through.", - "example": [ - "stripe", - "adyen" - ], - "nullable": true - }, - "capture_method": { - "allOf": [ - { - "$ref": "#/components/schemas/CaptureMethod" - } - ], - "nullable": true - }, - "authentication_type": { - "allOf": [ - { - "$ref": "#/components/schemas/AuthenticationType" - } - ], - "default": "three_ds", - "nullable": true - }, - "billing": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "capture_on": { - "type": "string", - "format": "date-time", - "description": "A timestamp (ISO 8601 code) that determines when the payment should be captured.\nProviding this field will automatically set `capture` to true", - "example": "2022-09-10T10:11:12Z", - "nullable": true - }, - "confirm": { - "type": "boolean", - "description": "Whether to confirm the payment (if applicable). It can be used to completely process a payment by attaching a payment method, setting `confirm=true` and `capture_method = automatic` in the *Payments/Create API* request itself.", - "default": false, - "example": true, - "nullable": true - }, - "customer": { - "allOf": [ - { - "$ref": "#/components/schemas/CustomerDetails" - } - ], - "nullable": true - }, - "customer_id": { - "type": "string", - "description": "The identifier for the customer", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 - }, - "email": { - "type": "string", - "description": "The customer's email address.\nThis field will be deprecated soon, use the customer object instead", - "deprecated": true, - "example": "johntest@test.com", - "nullable": true, - "maxLength": 255 - }, - "name": { - "type": "string", - "description": "The customer's name.\nThis field will be deprecated soon, use the customer object instead.", - "deprecated": true, - "example": "John Test", - "nullable": true, - "maxLength": 255 - }, - "phone": { - "type": "string", - "description": "The customer's phone number\nThis field will be deprecated soon, use the customer object instead", - "deprecated": true, - "example": "9123456789", - "nullable": true, - "maxLength": 255 - }, - "phone_country_code": { - "type": "string", - "description": "The country code for the customer phone number\nThis field will be deprecated soon, use the customer object instead", - "deprecated": true, - "example": "+1", - "nullable": true, - "maxLength": 255 - }, - "off_session": { - "type": "boolean", - "description": "Set to true to indicate that the customer is not in your checkout flow during this payment, and therefore is unable to authenticate. This parameter is intended for scenarios where you collect card details and charge them later. When making a recurring payment by passing a mandate_id, this parameter is mandatory", - "example": true, - "nullable": true - }, - "description": { - "type": "string", - "description": "A description for the payment", - "example": "It's my first payment request", - "nullable": true - }, - "return_url": { - "type": "string", - "description": "The URL to which you want the user to be redirected after the completion of the payment operation", - "example": "https://hyperswitch.io", - "nullable": true - }, - "setup_future_usage": { - "allOf": [ - { - "$ref": "#/components/schemas/FutureUsage" - } - ], - "nullable": true - }, - "payment_method_data": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodDataRequest" - } - ], - "nullable": true - }, - "payment_method": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethod" - } - ], - "nullable": true - }, - "payment_token": { - "type": "string", - "description": "As Hyperswitch tokenises the sensitive details about the payments method, it provides the payment_token as a reference to a stored payment method, ensuring that the sensitive details are not exposed in any manner.", - "example": "187282ab-40ef-47a9-9206-5099ba31e432", - "nullable": true - }, - "card_cvc": { - "type": "string", - "description": "This is used along with the payment_token field while collecting during saved card payments. This field will be deprecated soon, use the payment_method_data.card_token object instead", - "deprecated": true, - "nullable": true - }, - "shipping": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "statement_descriptor_name": { - "type": "string", - "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", - "example": "Hyperswitch Router", - "nullable": true, - "maxLength": 255 - }, - "statement_descriptor_suffix": { - "type": "string", - "description": "Provides information about a card payment that customers see on their statements. Concatenated with the prefix (shortened descriptor) or statement descriptor that’s set on the account to form the complete statement descriptor. Maximum 22 characters for the concatenated descriptor.", - "example": "Payment for shoes purchase", - "nullable": true, - "maxLength": 255 - }, - "order_details": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderDetailsWithAmount" - }, - "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", - "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", - "nullable": true - }, - "client_secret": { - "type": "string", - "description": "It's a token used for client side verification.", - "example": "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo", - "nullable": true - }, - "mandate_data": { - "allOf": [ - { - "$ref": "#/components/schemas/MandateData" - } - ], - "nullable": true - }, - "customer_acceptance": { - "allOf": [ - { - "$ref": "#/components/schemas/CustomerAcceptance" - } - ], - "nullable": true - }, - "mandate_id": { - "type": "string", - "description": "A unique identifier to link the payment to a mandate. To do Recurring payments after a mandate has been created, pass the mandate_id instead of payment_method_data", - "example": "mandate_iwer89rnjef349dni3", - "nullable": true, - "maxLength": 255 - }, - "browser_info": { - "allOf": [ - { - "$ref": "#/components/schemas/BrowserInformation" - } - ], - "nullable": true - }, - "payment_experience": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentExperience" - } - ], - "nullable": true - }, - "payment_method_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodType" - } - ], - "nullable": true - }, - "business_country": { - "allOf": [ - { - "$ref": "#/components/schemas/CountryAlpha2" - } - ], - "nullable": true - }, - "business_label": { - "type": "string", - "description": "Business label of the merchant for this payment.\nTo be deprecated soon. Pass the profile_id instead", - "example": "food", - "nullable": true - }, - "merchant_connector_details": { - "allOf": [ - { - "$ref": "#/components/schemas/MerchantConnectorDetailsWrap" - } - ], - "nullable": true - }, - "allowed_payment_method_types": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodType" - }, - "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", - "nullable": true - }, - "business_sub_label": { - "type": "string", - "description": "Business sub label for the payment", - "nullable": true - }, - "retry_action": { - "allOf": [ - { - "$ref": "#/components/schemas/RetryAction" - } - ], - "nullable": true - }, - "metadata": { - "type": "object", - "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.", - "nullable": true - }, - "connector_metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/ConnectorMetadata" - } - ], - "nullable": true - }, - "feature_metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/FeatureMetadata" - } - ], - "nullable": true - }, - "payment_link": { - "type": "boolean", - "description": "Whether to generate the payment link for this payment or not (if applicable)", - "default": false, - "example": true, - "nullable": true - }, - "payment_link_config": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" - } - ], - "nullable": true - }, - "payment_link_config_id": { - "type": "string", - "description": "Custom payment link config id set at business profile, send only if business_specific_configs is configured", - "nullable": true - }, - "profile_id": { - "type": "string", - "description": "The business profile to be used for this payment, if not passed the default business profile associated with the merchant account will be used. It is mandatory in case multiple business profiles have been set up.", - "nullable": true - }, - "surcharge_details": { - "allOf": [ - { - "$ref": "#/components/schemas/RequestSurchargeDetails" - } - ], - "nullable": true - }, - "payment_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentType" - } - ], - "nullable": true - }, - "request_incremental_authorization": { - "type": "boolean", - "description": "Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it.", - "nullable": true - }, - "session_expiry": { - "type": "integer", - "format": "int32", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds\n(900) for 15 mins", - "example": 900, - "nullable": true, - "minimum": 0 - }, - "frm_metadata": { - "type": "object", - "description": "Additional data related to some frm(Fraud Risk Management) connectors", - "nullable": true - }, - "request_external_three_ds_authentication": { - "type": "boolean", - "description": "Whether to perform external authentication (if applicable)", - "example": true, - "nullable": true - }, - "recurring_details": { - "allOf": [ - { - "$ref": "#/components/schemas/RecurringDetails" - } - ], - "nullable": true - }, - "charges": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentChargeRequest" - } - ], - "nullable": true - }, - "merchant_order_reference_id": { - "type": "string", - "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", - "example": "Custom_Order_id_123", - "nullable": true, - "maxLength": 255 - }, - "skip_external_tax_calculation": { - "type": "boolean", - "description": "Whether to calculate tax for this payment intent", - "nullable": true - } - }, - "additionalProperties": false - }, "PaymentsResponse": { "type": "object", "required": [ @@ -15897,361 +15080,6 @@ } } }, - "PaymentsUpdateRequest": { - "type": "object", - "properties": { - "amount": { - "type": "integer", - "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", - "example": 6540, - "nullable": true, - "minimum": 0 - }, - "currency": { - "allOf": [ - { - "$ref": "#/components/schemas/Currency" - } - ], - "nullable": true - }, - "amount_to_capture": { - "type": "integer", - "format": "int64", - "description": "The Amount to be captured / debited from the users payment method. It shall be in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc., If not provided, the default amount_to_capture will be the payment amount. Also, it must be less than or equal to the original payment account.", - "example": 6540, - "nullable": true - }, - "shipping_cost": { - "type": "integer", - "format": "int64", - "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", - "example": 6540, - "nullable": true - }, - "payment_id": { - "type": "string", - "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", - "example": "pay_mbabizu24mvu3mela5njyhpit4", - "nullable": true, - "maxLength": 30, - "minLength": 30 - }, - "routing": { - "allOf": [ - { - "$ref": "#/components/schemas/StraightThroughAlgorithm" - } - ], - "nullable": true - }, - "connector": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Connector" - }, - "description": "This allows to manually select a connector with which the payment can go through.", - "example": [ - "stripe", - "adyen" - ], - "nullable": true - }, - "capture_method": { - "allOf": [ - { - "$ref": "#/components/schemas/CaptureMethod" - } - ], - "nullable": true - }, - "authentication_type": { - "allOf": [ - { - "$ref": "#/components/schemas/AuthenticationType" - } - ], - "default": "three_ds", - "nullable": true - }, - "billing": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "confirm": { - "type": "boolean", - "description": "Whether to confirm the payment (if applicable). It can be used to completely process a payment by attaching a payment method, setting `confirm=true` and `capture_method = automatic` in the *Payments/Create API* request itself.", - "default": false, - "example": true, - "nullable": true - }, - "customer": { - "allOf": [ - { - "$ref": "#/components/schemas/CustomerDetails" - } - ], - "nullable": true - }, - "customer_id": { - "type": "string", - "description": "The identifier for the customer", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 - }, - "off_session": { - "type": "boolean", - "description": "Set to true to indicate that the customer is not in your checkout flow during this payment, and therefore is unable to authenticate. This parameter is intended for scenarios where you collect card details and charge them later. When making a recurring payment by passing a mandate_id, this parameter is mandatory", - "example": true, - "nullable": true - }, - "description": { - "type": "string", - "description": "A description for the payment", - "example": "It's my first payment request", - "nullable": true - }, - "return_url": { - "type": "string", - "description": "The URL to which you want the user to be redirected after the completion of the payment operation", - "example": "https://hyperswitch.io", - "nullable": true - }, - "setup_future_usage": { - "allOf": [ - { - "$ref": "#/components/schemas/FutureUsage" - } - ], - "nullable": true - }, - "payment_method_data": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodDataRequest" - } - ], - "nullable": true - }, - "payment_method": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethod" - } - ], - "nullable": true - }, - "payment_token": { - "type": "string", - "description": "As Hyperswitch tokenises the sensitive details about the payments method, it provides the payment_token as a reference to a stored payment method, ensuring that the sensitive details are not exposed in any manner.", - "example": "187282ab-40ef-47a9-9206-5099ba31e432", - "nullable": true - }, - "shipping": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "statement_descriptor_name": { - "type": "string", - "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", - "example": "Hyperswitch Router", - "nullable": true, - "maxLength": 255 - }, - "statement_descriptor_suffix": { - "type": "string", - "description": "Provides information about a card payment that customers see on their statements. Concatenated with the prefix (shortened descriptor) or statement descriptor that’s set on the account to form the complete statement descriptor. Maximum 22 characters for the concatenated descriptor.", - "example": "Payment for shoes purchase", - "nullable": true, - "maxLength": 255 - }, - "order_details": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderDetailsWithAmount" - }, - "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", - "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", - "nullable": true - }, - "mandate_data": { - "allOf": [ - { - "$ref": "#/components/schemas/MandateData" - } - ], - "nullable": true - }, - "customer_acceptance": { - "allOf": [ - { - "$ref": "#/components/schemas/CustomerAcceptance" - } - ], - "nullable": true - }, - "browser_info": { - "allOf": [ - { - "$ref": "#/components/schemas/BrowserInformation" - } - ], - "nullable": true - }, - "payment_experience": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentExperience" - } - ], - "nullable": true - }, - "payment_method_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodType" - } - ], - "nullable": true - }, - "merchant_connector_details": { - "allOf": [ - { - "$ref": "#/components/schemas/MerchantConnectorDetailsWrap" - } - ], - "nullable": true - }, - "allowed_payment_method_types": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodType" - }, - "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", - "nullable": true - }, - "retry_action": { - "allOf": [ - { - "$ref": "#/components/schemas/RetryAction" - } - ], - "nullable": true - }, - "metadata": { - "type": "object", - "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.", - "nullable": true - }, - "connector_metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/ConnectorMetadata" - } - ], - "nullable": true - }, - "payment_link": { - "type": "boolean", - "description": "Whether to generate the payment link for this payment or not (if applicable)", - "default": false, - "example": true, - "nullable": true - }, - "payment_link_config": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" - } - ], - "nullable": true - }, - "payment_link_config_id": { - "type": "string", - "description": "Custom payment link config id set at business profile, send only if business_specific_configs is configured", - "nullable": true - }, - "surcharge_details": { - "allOf": [ - { - "$ref": "#/components/schemas/RequestSurchargeDetails" - } - ], - "nullable": true - }, - "payment_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentType" - } - ], - "nullable": true - }, - "request_incremental_authorization": { - "type": "boolean", - "description": "Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it.", - "nullable": true - }, - "session_expiry": { - "type": "integer", - "format": "int32", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds\n(900) for 15 mins", - "example": 900, - "nullable": true, - "minimum": 0 - }, - "frm_metadata": { - "type": "object", - "description": "Additional data related to some frm(Fraud Risk Management) connectors", - "nullable": true - }, - "request_external_three_ds_authentication": { - "type": "boolean", - "description": "Whether to perform external authentication (if applicable)", - "example": true, - "nullable": true - }, - "recurring_details": { - "allOf": [ - { - "$ref": "#/components/schemas/RecurringDetails" - } - ], - "nullable": true - }, - "charges": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentChargeRequest" - } - ], - "nullable": true - }, - "merchant_order_reference_id": { - "type": "string", - "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", - "example": "Custom_Order_id_123", - "nullable": true, - "maxLength": 255 - }, - "skip_external_tax_calculation": { - "type": "boolean", - "description": "Whether to calculate tax for this payment intent", - "nullable": true - } - } - }, "PayoutActionRequest": { "type": "object" }, diff --git a/crates/analytics/Cargo.toml b/crates/analytics/Cargo.toml index 687d5a2e20..6cf886896c 100644 --- a/crates/analytics/Cargo.toml +++ b/crates/analytics/Cargo.toml @@ -7,8 +7,8 @@ rust-version.workspace = true license.workspace = true [features] -v1 = ["api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1"] -v2 = ["api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "storage_impl/v2"] +v1 = ["api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "common_utils/v1"] +v2 = ["api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "storage_impl/v2", "common_utils/v2"] [dependencies] # First party crates diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index 7bbff94d45..000343864b 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -1,9 +1,9 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; #[cfg(feature = "v2")] -use super::PaymentsCreateIntentRequest; -#[cfg(feature = "v2")] -use super::PaymentsCreateIntentResponse; +use super::{ + PaymentsConfirmIntentResponse, PaymentsCreateIntentRequest, PaymentsCreateIntentResponse, +}; #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") @@ -20,7 +20,7 @@ use crate::{ PaymentMethodResponse, PaymentMethodUpdate, }, payments::{ - ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, + self, ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, PaymentListFiltersV2, PaymentListResponse, PaymentListResponseV2, PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, @@ -29,8 +29,8 @@ use crate::{ PaymentsExternalAuthenticationResponse, PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest, PaymentsManualUpdateResponse, PaymentsPostSessionTokensRequest, PaymentsPostSessionTokensResponse, PaymentsRejectRequest, - PaymentsRequest, PaymentsResponse, PaymentsRetrieveRequest, PaymentsSessionResponse, - PaymentsStartRequest, RedirectionResponse, + PaymentsResponse, PaymentsRetrieveRequest, PaymentsSessionResponse, PaymentsStartRequest, + RedirectionResponse, }, }; @@ -131,7 +131,7 @@ impl ApiEventMetric for PaymentsRejectRequest { } #[cfg(feature = "v1")] -impl ApiEventMetric for PaymentsRequest { +impl ApiEventMetric for payments::PaymentsRequest { fn get_api_event_type(&self) -> Option { match self.payment_id { Some(PaymentIdType::PaymentIntentId(ref id)) => Some(ApiEventsType::Payment { @@ -158,6 +158,15 @@ impl ApiEventMetric for PaymentsCreateIntentResponse { } } +#[cfg(feature = "v2")] +impl ApiEventMetric for PaymentsConfirmIntentResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.id.clone(), + }) + } +} + #[cfg(feature = "v1")] impl ApiEventMetric for PaymentsResponse { fn get_api_event_type(&self) -> Option { diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index fd1286a555..fd8419e7f9 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -5,8 +5,6 @@ use std::{ }; pub mod additional_info; use cards::CardNumber; -#[cfg(feature = "v2")] -use common_utils::id_type::GlobalPaymentId; use common_utils::{ consts::default_payments_list_limit, crypto, @@ -117,7 +115,8 @@ pub struct CustomerDetailsResponse { pub phone_country_code: Option, } -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +// Serialize is required because the api event requires Serialize to be implemented +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] #[cfg(feature = "v2")] pub struct PaymentsCreateIntentRequest { @@ -288,16 +287,16 @@ impl PaymentsCreateIntentRequest { } } -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[derive(Debug, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] #[cfg(feature = "v2")] pub struct PaymentsCreateIntentResponse { /// Global Payment Id for the payment #[schema(value_type = String)] - pub id: GlobalPaymentId, + pub id: id_type::GlobalPaymentId, /// The amount details for the payment - pub amount_details: AmountDetails, + pub amount_details: AmountDetailsResponse, /// It's a token used for client side verification. #[schema(value_type = String, example = "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo")] @@ -448,6 +447,66 @@ pub struct AmountDetailsSetter { pub tax_on_surcharge: Option, } +#[cfg(feature = "v2")] +#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] +pub struct AmountDetailsResponse { + /// The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies) + #[schema(value_type = u64, example = 6540)] + pub order_amount: MinorUnit, + /// The currency of the order + #[schema(example = "USD", value_type = Currency)] + pub currency: common_enums::Currency, + /// The shipping cost of the order. This has to be collected from the merchant + pub shipping_cost: Option, + /// Tax amount related to the order. This will be calculated by the external tax provider + pub order_tax_amount: Option, + /// The action to whether calculate tax by calling external tax provider or not + #[schema(value_type = TaxCalculationOverride)] + pub skip_external_tax_calculation: common_enums::TaxCalculationOverride, + /// The action to whether calculate surcharge or not + #[schema(value_type = SurchargeCalculationOverride)] + pub skip_surcharge_calculation: common_enums::SurchargeCalculationOverride, + /// The surcharge amount to be added to the order, collected from the merchant + pub surcharge_amount: Option, + /// tax on surcharge amount + pub tax_on_surcharge: Option, +} + +#[cfg(feature = "v2")] +#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] +pub struct ConfirmIntentAmountDetailsResponse { + /// The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies) + #[schema(value_type = u64, example = 6540)] + #[serde(default, deserialize_with = "amount::deserialize")] + pub order_amount: MinorUnit, + /// The currency of the order + #[schema(example = "USD", value_type = Currency)] + pub currency: common_enums::Currency, + /// The shipping cost of the order. This has to be collected from the merchant + pub shipping_cost: Option, + /// Tax amount related to the order. This will be calculated by the external tax provider + pub order_tax_amount: Option, + /// The action to whether calculate tax by calling external tax provider or not + #[schema(value_type = TaxCalculationOverride)] + pub skip_external_tax_calculation: common_enums::TaxCalculationOverride, + /// The action to whether calculate surcharge or not + #[schema(value_type = SurchargeCalculationOverride)] + pub skip_surcharge_calculation: common_enums::SurchargeCalculationOverride, + /// The surcharge amount to be added to the order, collected from the merchant + pub surcharge_amount: Option, + /// tax on surcharge amount + pub tax_on_surcharge: Option, + /// The total amount of the order including tax, surcharge and shipping cost + pub net_amount: MinorUnit, + /// The amount that was requested to be captured for this payment + pub amount_to_capture: Option, + /// The amount that can be captured on the payment. Either in one go or through multiple captures. + /// This is applicable in case the capture method was either `manual` or `manual_multiple` + pub amount_capturable: MinorUnit, + /// The amount that was captured for this payment. This is the sum of all the captures done on this payment + pub amount_captured: Option, +} + #[cfg(feature = "v2")] impl AmountDetails { pub fn new(amount_details_setter: AmountDetailsSetter) -> Self { @@ -488,6 +547,7 @@ impl AmountDetails { } } +#[cfg(feature = "v1")] #[derive( Default, Debug, @@ -787,6 +847,7 @@ pub struct PaymentsRequest { pub skip_external_tax_calculation: Option, } +#[cfg(feature = "v1")] /// Checks if the inner values of two options are equal /// Returns true if values are not equal, returns false in other cases fn are_optional_values_invalid( @@ -799,6 +860,7 @@ fn are_optional_values_invalid( } } +#[cfg(feature = "v1")] impl PaymentsRequest { /// Get the customer id /// @@ -850,8 +912,61 @@ impl PaymentsRequest { None } } + + pub fn get_feature_metadata_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option, + common_utils::errors::ParsingError, + > { + self.feature_metadata + .as_ref() + .map(Encode::encode_to_value) + .transpose() + } + + pub fn get_connector_metadata_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option, + common_utils::errors::ParsingError, + > { + self.connector_metadata + .as_ref() + .map(Encode::encode_to_value) + .transpose() + } + + pub fn get_allowed_payment_method_types_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option, + common_utils::errors::ParsingError, + > { + self.allowed_payment_method_types + .as_ref() + .map(Encode::encode_to_value) + .transpose() + } + + pub fn get_order_details_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option>, + common_utils::errors::ParsingError, + > { + self.order_details + .as_ref() + .map(|od| { + od.iter() + .map(|order| order.encode_to_value().map(Secret::new)) + .collect::, _>>() + }) + .transpose() + } } +#[cfg(feature = "v1")] #[cfg(test)] mod payments_request_test { use common_utils::generate_customer_id_of_default_length; @@ -936,17 +1051,6 @@ pub struct PaymentChargeRequest { pub transfer_account_id: String, } -impl PaymentsRequest { - pub fn get_total_capturable_amount(&self) -> Option { - let surcharge_amount = self - .surcharge_details - .map(|surcharge_details| surcharge_details.get_total_surcharge_amount()) - .unwrap_or_default(); - self.amount - .map(|amount| MinorUnit::from(amount) + surcharge_amount) - } -} - /// Details of surcharge applied on this payment, if applicable #[derive( Default, Debug, Clone, serde::Serialize, serde::Deserialize, Copy, ToSchema, PartialEq, @@ -957,8 +1061,10 @@ pub struct RequestSurchargeDetails { pub tax_amount: Option, } +// for v2 use the type from common_utils::types +#[cfg(feature = "v1")] /// Browser information to be used for 3DS 2.0 -#[derive(ToSchema)] +#[derive(ToSchema, Debug, serde::Deserialize, serde::Serialize)] pub struct BrowserInformation { /// Color depth supported by the browser pub color_depth: Option, @@ -1013,29 +1119,6 @@ impl RequestSurchargeDetails { } } -#[derive(Default, Debug, Clone)] -pub struct HeaderPayload { - pub payment_confirm_source: Option, - pub client_source: Option, - pub client_version: Option, - pub x_hs_latency: Option, - pub browser_name: Option, - pub x_client_platform: Option, - pub x_merchant_domain: Option, - pub locale: Option, - pub x_app_id: Option, - pub x_redirect_uri: Option, -} - -impl HeaderPayload { - pub fn with_source(payment_confirm_source: api_enums::PaymentSource) -> Self { - Self { - payment_confirm_source: Some(payment_confirm_source), - ..Default::default() - } - } -} - #[derive(Debug, serde::Serialize, Clone, PartialEq, ToSchema, router_derive::PolymorphicSchema)] pub struct PaymentAttemptResponse { /// Unique identifier for the attempt @@ -1134,60 +1217,6 @@ pub struct CaptureResponse { pub reference_id: Option, } -impl PaymentsRequest { - pub fn get_feature_metadata_as_value( - &self, - ) -> common_utils::errors::CustomResult< - Option, - common_utils::errors::ParsingError, - > { - self.feature_metadata - .as_ref() - .map(Encode::encode_to_value) - .transpose() - } - - pub fn get_connector_metadata_as_value( - &self, - ) -> common_utils::errors::CustomResult< - Option, - common_utils::errors::ParsingError, - > { - self.connector_metadata - .as_ref() - .map(Encode::encode_to_value) - .transpose() - } - - pub fn get_allowed_payment_method_types_as_value( - &self, - ) -> common_utils::errors::CustomResult< - Option, - common_utils::errors::ParsingError, - > { - self.allowed_payment_method_types - .as_ref() - .map(Encode::encode_to_value) - .transpose() - } - - pub fn get_order_details_as_value( - &self, - ) -> common_utils::errors::CustomResult< - Option>, - common_utils::errors::ParsingError, - > { - self.order_details - .as_ref() - .map(|od| { - od.iter() - .map(|order| order.encode_to_value().map(Secret::new)) - .collect::, _>>() - }) - .transpose() - } -} - #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Eq)] pub enum Amount { Value(NonZeroI64), @@ -1781,6 +1810,7 @@ impl GetAddressFromPaymentMethodData for BankDebitData { } } +#[cfg(feature = "v1")] /// Custom serializer and deserializer for PaymentMethodData mod payment_method_data_serde { @@ -1925,6 +1955,9 @@ mod payment_method_data_serde { /// The payment method information provided for making a payment #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema, Eq, PartialEq)] pub struct PaymentMethodDataRequest { + /// This field is optional because, in case of saved cards we pass the payment_token + /// There might be cases where we don't need to pass the payment_method_data and pass only payment method billing details + /// We have flattened it because to maintain backwards compatibility with the old API contract #[serde(flatten)] pub payment_method_data: Option, /// billing details for the payment method. @@ -4400,6 +4433,122 @@ pub struct PaymentsResponse { pub connector_mandate_id: Option, } +// Serialize is implemented because, this will be serialized in the api events. +// Usually request types should not have serialize implemented. +// +/// Request for Payment Intent Confirm +#[cfg(feature = "v2")] +#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct PaymentsConfirmIntentRequest { + /// The URL to which you want the user to be redirected after the completion of the payment operation + /// If this url is not passed, the url configured in the business profile will be used + #[schema(value_type = Option, example = "https://hyperswitch.io")] + pub return_url: Option, + + /// The payment instrument data to be used for the payment + pub payment_method_data: PaymentMethodDataRequest, + + /// The payment method type to be used for the payment. This should match with the `payment_method_data` provided + #[schema(value_type = PaymentMethod, example = "card")] + pub payment_method_type: api_enums::PaymentMethod, + + /// The payment method subtype to be used for the payment. This should match with the `payment_method_data` provided + #[schema(value_type = PaymentMethodType, example = "apple_pay")] + pub payment_method_subtype: api_enums::PaymentMethodType, + + /// The shipping address for the payment. This will override the shipping address provided in the create-intent request + pub shipping: Option
, + + /// This "CustomerAcceptance" object is passed during Payments-Confirm request, it enlists the type, time, and mode of acceptance properties related to an acceptance done by the customer. The customer_acceptance sub object is usually passed by the SDK or client. + #[schema(value_type = Option)] + pub customer_acceptance: Option, + + /// Additional details required by 3DS 2.0 + #[schema(value_type = Option)] + pub browser_info: Option, +} + +/// Error details for the payment +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, ToSchema)] +pub struct ErrorDetails { + /// The error code + pub code: String, + /// The error message + pub message: String, + /// The unified error code across all connectors. + /// This can be relied upon for taking decisions based on the error. + pub unified_code: Option, + /// The unified error message across all connectors. + /// If there is a translation available, this will have the translated message + pub unified_message: Option, +} + +/// Response for Payment Intent Confirm +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, ToSchema)] +pub struct PaymentsConfirmIntentResponse { + /// Unique identifier for the payment. This ensures idempotency for multiple payments + /// that have been done by a single merchant. + #[schema( + min_length = 32, + max_length = 64, + example = "12345_pay_01926c58bc6e77c09e809964e72af8c8", + value_type = String, + )] + pub id: id_type::GlobalPaymentId, + + #[schema(value_type = IntentStatus, example = "success")] + pub status: api_enums::IntentStatus, + + /// Amount related information for this payment and attempt + pub amount: ConfirmIntentAmountDetailsResponse, + + /// The connector used for the payment + #[schema(example = "stripe")] + pub connector: String, + + /// It's a token used for client side verification. + #[schema(value_type = String)] + pub client_secret: common_utils::types::ClientSecret, + + /// Time when the payment was created + #[schema(example = "2022-09-10T10:11:12Z")] + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created: PrimitiveDateTime, + + /// The payment method information provided for making a payment + #[schema(value_type = Option)] + #[serde(serialize_with = "serialize_payment_method_data_response")] + pub payment_method_data: Option, + + /// The payment method type for this payment attempt + #[schema(value_type = PaymentMethod, example = "wallet")] + pub payment_method_type: api_enums::PaymentMethod, + + #[schema(value_type = PaymentMethodType, example = "apple_pay")] + pub payment_method_subtype: api_enums::PaymentMethodType, + + /// A unique identifier for a payment provided by the connector + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_transaction_id: Option, + + /// reference(Identifier) to the payment at connector side + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_reference_id: Option, + + /// Identifier of the connector ( merchant connector account ) which was chosen to make the payment + #[schema(value_type = String)] + pub merchant_connector_id: id_type::MerchantConnectorAccountId, + + /// The browser information used for this payment + #[schema(value_type = Option)] + pub browser_info: Option, + + /// Error details for the payment if any + pub error: Option, +} + /// Fee information to be charged on the payment being collected #[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)] pub struct PaymentChargeResponse { @@ -4696,6 +4845,7 @@ pub struct MandateValidationFields { pub off_session: Option, } +#[cfg(feature = "v1")] impl From<&PaymentsRequest> for MandateValidationFields { fn from(req: &PaymentsRequest) -> Self { let recurring_details = req @@ -4757,6 +4907,7 @@ impl From for PaymentsSessionResponse { } } +#[cfg(feature = "v1")] impl From for PaymentsRequest { fn from(item: PaymentsStartRequest) -> Self { Self { @@ -6346,6 +6497,7 @@ pub struct ExtendedCardInfoResponse { pub payload: String, } +#[cfg(feature = "v1")] #[cfg(test)] mod payments_request_api_contract { #![allow(clippy::unwrap_used)] diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index e36a6d7499..3ce9d079c6 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -1391,17 +1391,31 @@ pub enum MerchantStorageScheme { #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum IntentStatus { + /// The payment has succeeded. Refunds and disputes can be initiated. + /// Manual retries are not allowed to be performed. Succeeded, + /// The payment has failed. Refunds and disputes cannot be initiated. + /// This payment can be retried manually with a new payment attempt. Failed, + /// This payment has been cancelled. Cancelled, + /// This payment is still being processed by the payment processor. + /// The status update might happen through webhooks or polling with the connector. Processing, + /// The payment is waiting on some action from the customer. RequiresCustomerAction, + /// The payment is waiting on some action from the merchant + /// This would be in case of manual fraud approval RequiresMerchantAction, + /// The payment is waiting to be confirmed with the payment method by the customer. RequiresPaymentMethod, #[default] RequiresConfirmation, + /// The payment has been authorized, and it waiting to be captured. RequiresCapture, + /// The payment has been captured partially. The remaining amount is cannot be captured. PartiallyCaptured, + /// The payment has been captured partially and the remaining amount is capturable PartiallyCapturedAndCapturable, } diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index 656b299370..a83c1d481c 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -105,6 +105,15 @@ impl ApiEventMetric for id_type::PaymentId { } } +#[cfg(feature = "v2")] +impl ApiEventMetric for id_type::GlobalPaymentId { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.clone(), + }) + } +} + impl ApiEventMetric for Result { fn get_api_event_type(&self) -> Option { match self { diff --git a/crates/common_utils/src/id_type.rs b/crates/common_utils/src/id_type.rs index 859732aed0..3d57a72376 100644 --- a/crates/common_utils/src/id_type.rs +++ b/crates/common_utils/src/id_type.rs @@ -27,7 +27,9 @@ use diesel::{ }; #[cfg(feature = "v2")] pub use global_id::{ - payment::GlobalPaymentId, payment_methods::GlobalPaymentMethodId, refunds::GlobalRefundId, + payment::{GlobalAttemptId, GlobalPaymentId}, + payment_methods::GlobalPaymentMethodId, + refunds::GlobalRefundId, CellId, }; pub use merchant::MerchantId; diff --git a/crates/common_utils/src/id_type/global_id.rs b/crates/common_utils/src/id_type/global_id.rs index 0032c531e4..5490dcda7b 100644 --- a/crates/common_utils/src/id_type/global_id.rs +++ b/crates/common_utils/src/id_type/global_id.rs @@ -1,4 +1,3 @@ -#![allow(unused)] pub mod payment; pub mod payment_methods; pub mod refunds; @@ -24,6 +23,7 @@ pub(crate) struct GlobalId(LengthId) pub(crate) enum GlobalEntity { Customer, Payment, + Attempt, PaymentMethod, Refund, } @@ -34,6 +34,7 @@ impl GlobalEntity { Self::Customer => "cus", Self::Payment => "pay", Self::PaymentMethod => "pm", + Self::Attempt => "att", Self::Refund => "ref", } } diff --git a/crates/common_utils/src/id_type/global_id/payment.rs b/crates/common_utils/src/id_type/global_id/payment.rs index a404c7bc9c..6e5848e5a3 100644 --- a/crates/common_utils/src/id_type/global_id/payment.rs +++ b/crates/common_utils/src/id_type/global_id/payment.rs @@ -2,22 +2,16 @@ use error_stack::ResultExt; use crate::{errors, generate_id_with_default_len, generate_time_ordered_id_without_prefix, types}; -/// A global id that can be used to identify a payment -#[derive( - Debug, - Clone, - Hash, - PartialEq, - Eq, - serde::Serialize, - serde::Deserialize, - diesel::expression::AsExpression, -)] -#[diesel(sql_type = diesel::sql_types::Text)] -pub struct GlobalPaymentId(super::GlobalId); +crate::global_id_type!( + GlobalPaymentId, + "A global id that can be used to identify a payment + The format will be `__` + example - cell1_pay_uu1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p" +); // Database related implementations so that this field can be used directly in the database tables crate::impl_queryable_id_type!(GlobalPaymentId); +crate::impl_to_sql_from_sql_global_id_type!(GlobalPaymentId); impl GlobalPaymentId { /// Get string representation of the id @@ -51,26 +45,24 @@ impl TryFrom> for GlobalPaymentId { } } -// TODO: refactor the macro to include this id use case as well -impl diesel::serialize::ToSql for GlobalPaymentId -where - DB: diesel::backend::Backend, - super::GlobalId: diesel::serialize::ToSql, -{ - fn to_sql<'b>( - &'b self, - out: &mut diesel::serialize::Output<'b, '_, DB>, - ) -> diesel::serialize::Result { - self.0.to_sql(out) - } -} +crate::global_id_type!( + GlobalAttemptId, + "A global id that can be used to identify a payment attempt" +); -impl diesel::deserialize::FromSql for GlobalPaymentId -where - DB: diesel::backend::Backend, - super::GlobalId: diesel::deserialize::FromSql, -{ - fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result { - super::GlobalId::from_sql(value).map(Self) +// Database related implementations so that this field can be used directly in the database tables +crate::impl_queryable_id_type!(GlobalAttemptId); +crate::impl_to_sql_from_sql_global_id_type!(GlobalAttemptId); + +impl GlobalAttemptId { + /// Generate a new GlobalAttemptId from a cell id + pub fn generate(cell_id: &super::CellId) -> Self { + let global_id = super::GlobalId::generate(cell_id.clone(), super::GlobalEntity::Attempt); + Self(global_id) + } + + /// Get string representation of the id + pub fn get_string_repr(&self) -> &str { + self.0.get_string_repr() } } diff --git a/crates/common_utils/src/macros.rs b/crates/common_utils/src/macros.rs index 94d8074c30..21cec6f60f 100644 --- a/crates/common_utils/src/macros.rs +++ b/crates/common_utils/src/macros.rs @@ -172,6 +172,27 @@ mod id_type { }; } + /// Defines a Global Id type + #[cfg(feature = "v2")] + #[macro_export] + macro_rules! global_id_type { + ($type:ident, $doc:literal) => { + #[doc = $doc] + #[derive( + Debug, + Clone, + Hash, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, + diesel::expression::AsExpression, + )] + #[diesel(sql_type = diesel::sql_types::Text)] + pub struct $type($crate::id_type::global_id::GlobalId); + }; + } + /// Implements common methods on the specified ID type. #[macro_export] macro_rules! impl_id_type_methods { @@ -292,6 +313,40 @@ mod id_type { }; } + #[cfg(feature = "v2")] + /// Implements the `ToSql` and `FromSql` traits on the specified Global ID type. + #[macro_export] + macro_rules! impl_to_sql_from_sql_global_id_type { + ($type:ty, $diesel_type:ty) => { + impl diesel::serialize::ToSql<$diesel_type, DB> for $type + where + DB: diesel::backend::Backend, + $crate::id_type::global_id::GlobalId: diesel::serialize::ToSql<$diesel_type, DB>, + { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, DB>, + ) -> diesel::serialize::Result { + self.0.to_sql(out) + } + } + + impl diesel::deserialize::FromSql<$diesel_type, DB> for $type + where + DB: diesel::backend::Backend, + $crate::id_type::global_id::GlobalId: + diesel::deserialize::FromSql<$diesel_type, DB>, + { + fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result { + $crate::id_type::global_id::GlobalId::from_sql(value).map(Self) + } + } + }; + ($type:ty) => { + $crate::impl_to_sql_from_sql_global_id_type!($type, diesel::sql_types::Text); + }; + } + /// Implements the `Queryable` trait on the specified ID type. #[macro_export] macro_rules! impl_queryable_id_type { diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index a1c9b4c00c..2e7977613d 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -603,6 +603,13 @@ impl StringMajorUnit { /// This domain type can be used for any url pub struct Url(url::Url); +impl Url { + /// Get string representation of the url + pub fn get_string_repr(&self) -> &str { + self.0.as_str() + } +} + impl ToSql for Url where DB: Backend, @@ -667,6 +674,30 @@ mod client_secret_type { } } + impl FromStr for ClientSecret { + type Err = ParsingError; + + fn from_str(str_value: &str) -> Result { + let (payment_id, secret) = + str_value + .rsplit_once("_secret_") + .ok_or(ParsingError::EncodeError( + "Expected a string in the format '{payment_id}_secret_{secret}'", + ))?; + + let payment_id = id_type::GlobalPaymentId::try_from(Cow::Owned(payment_id.to_owned())) + .map_err(|err| { + logger::error!(global_payment_id_error=?err); + ParsingError::EncodeError("Error while constructing GlobalPaymentId") + })?; + + Ok(Self { + payment_id, + secret: masking::Secret::new(secret.to_owned()), + }) + } + } + impl<'de> Deserialize<'de> for ClientSecret { fn deserialize(deserializer: D) -> Result where @@ -1096,6 +1127,12 @@ impl Description { pub fn from_str_unchecked(input_str: &'static str) -> Self { Self(LengthString::new_unchecked(input_str.to_owned())) } + + // TODO: Remove this function in future once description in router data is updated to domain type + /// Get the string representation of the description + pub fn get_string_repr(&self) -> &str { + &self.0 .0 + } } /// Domain type for Statement Descriptor @@ -1273,6 +1310,58 @@ where } } +#[cfg(feature = "v2")] +/// Browser information to be used for 3DS 2.0 +// If any of the field is PII, then we can make them as secret +#[derive( + ToSchema, + Debug, + Clone, + serde::Deserialize, + serde::Serialize, + Eq, + PartialEq, + diesel::AsExpression, +)] +#[diesel(sql_type = Jsonb)] +pub struct BrowserInformation { + /// Color depth supported by the browser + pub color_depth: Option, + + /// Whether java is enabled in the browser + pub java_enabled: Option, + + /// Whether javascript is enabled in the browser + pub java_script_enabled: Option, + + /// Language supported + pub language: Option, + + /// The screen height in pixels + pub screen_height: Option, + + /// The screen width in pixels + pub screen_width: Option, + + /// Time zone of the client + pub time_zone: Option, + + /// Ip address of the client + #[schema(value_type = Option)] + pub ip_address: Option, + + /// List of headers that are accepted + #[schema( + example = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8" + )] + pub accept_header: Option, + + /// User-agent of the browser + pub user_agent: Option, +} + +#[cfg(feature = "v2")] +crate::impl_to_sql_from_sql_json!(BrowserInformation); /// Domain type for connector_transaction_id /// Maximum length for connector's transaction_id can be 128 characters in HS DB. /// In case connector's use an identifier whose length exceeds 128 characters, diff --git a/crates/connector_configs/Cargo.toml b/crates/connector_configs/Cargo.toml index 0c460a9f2b..f872896809 100644 --- a/crates/connector_configs/Cargo.toml +++ b/crates/connector_configs/Cargo.toml @@ -13,11 +13,11 @@ development = [] sandbox = [] dummy_connector = ["api_models/dummy_connector", "development"] payouts = ["api_models/payouts"] -v1 = ["api_models/v1"] +v1 = ["api_models/v1", "common_utils/v1"] [dependencies] # First party crates -api_models = { version = "0.1.0", path = "../api_models", package = "api_models"} +api_models = { version = "0.1.0", path = "../api_models", package = "api_models" } common_utils = { version = "0.1.0", path = "../common_utils" } # Third party crates diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index ae98b8a048..ae4722a6f3 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -34,9 +34,8 @@ pub struct PaymentAttempt { pub connector: Option, pub error_message: Option, pub surcharge_amount: Option, - pub payment_method_id: Option, - pub confirm: bool, - pub authentication_type: Option, + pub payment_method_id: Option, + pub authentication_type: storage_enums::AuthenticationType, #[serde(with = "common_utils::custom_serde::iso8601")] pub created_at: PrimitiveDateTime, #[serde(with = "common_utils::custom_serde::iso8601")] @@ -45,12 +44,12 @@ pub struct PaymentAttempt { pub last_synced: Option, pub cancellation_reason: Option, pub amount_to_capture: Option, - pub browser_info: Option, + pub browser_info: Option, pub error_code: Option, pub payment_token: Option, - pub connector_metadata: Option, + pub connector_metadata: Option, pub payment_experience: Option, - pub payment_method_data: Option, + pub payment_method_data: Option, pub preprocessing_step_id: Option, pub error_reason: Option, pub multiple_capture_count: Option, @@ -58,16 +57,15 @@ pub struct PaymentAttempt { pub amount_capturable: MinorUnit, pub updated_by: String, pub merchant_connector_id: Option, - pub authentication_data: Option, - pub encoded_data: Option, + pub authentication_data: Option, + pub encoded_data: Option>, pub unified_code: Option, pub unified_message: Option, - pub net_amount: Option, + pub net_amount: MinorUnit, pub external_three_ds_authentication_attempted: Option, pub authentication_connector: Option, pub authentication_id: Option, pub fingerprint_id: Option, - pub payment_method_billing_address_id: Option, pub charge_id: Option, pub client_source: Option, pub client_version: Option, @@ -75,15 +73,16 @@ pub struct PaymentAttempt { pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, pub card_network: Option, - pub payment_method_type_v2: Option, + pub payment_method_type_v2: storage_enums::PaymentMethod, pub connector_payment_id: Option, - pub payment_method_subtype: Option, + pub payment_method_subtype: storage_enums::PaymentMethodType, pub routing_result: Option, pub authentication_applied: Option, pub external_reference_id: Option, pub tax_on_surcharge: Option, + pub payment_method_billing_address: Option, pub connector_payment_data: Option, - pub id: String, + pub id: id_type::GlobalAttemptId, pub shipping_cost: Option, pub order_tax_amount: Option, pub connector_mandate_detail: Option, @@ -225,9 +224,8 @@ pub struct PaymentAttemptNew { pub error_message: Option, pub surcharge_amount: Option, pub tax_on_surcharge: Option, - pub payment_method_id: Option, - pub confirm: bool, - pub authentication_type: Option, + pub payment_method_id: Option, + pub authentication_type: storage_enums::AuthenticationType, #[serde(with = "common_utils::custom_serde::iso8601")] pub created_at: PrimitiveDateTime, #[serde(with = "common_utils::custom_serde::iso8601")] @@ -236,12 +234,12 @@ pub struct PaymentAttemptNew { pub last_synced: Option, pub cancellation_reason: Option, pub amount_to_capture: Option, - pub browser_info: Option, + pub browser_info: Option, pub payment_token: Option, pub error_code: Option, - pub connector_metadata: Option, + pub connector_metadata: Option, pub payment_experience: Option, - pub payment_method_data: Option, + pub payment_method_data: Option, pub preprocessing_step_id: Option, pub error_reason: Option, pub connector_response_reference_id: Option, @@ -249,16 +247,16 @@ pub struct PaymentAttemptNew { pub amount_capturable: MinorUnit, pub updated_by: String, pub merchant_connector_id: Option, - pub authentication_data: Option, - pub encoded_data: Option, + pub authentication_data: Option, + pub encoded_data: Option>, pub unified_code: Option, pub unified_message: Option, - pub net_amount: Option, + pub net_amount: MinorUnit, pub external_three_ds_authentication_attempted: Option, pub authentication_connector: Option, pub authentication_id: Option, pub fingerprint_id: Option, - pub payment_method_billing_address_id: Option, + pub payment_method_billing_address: Option, pub charge_id: Option, pub client_source: Option, pub client_version: Option, @@ -268,6 +266,9 @@ pub struct PaymentAttemptNew { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, + pub payment_method_type_v2: storage_enums::PaymentMethod, + pub payment_method_subtype: storage_enums::PaymentMethodType, + pub id: id_type::GlobalAttemptId, pub connector_mandate_detail: Option, } @@ -731,48 +732,48 @@ pub enum PaymentAttemptUpdate { // }, } +// TODO: uncomment fields as and when required #[cfg(feature = "v2")] #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] #[diesel(table_name = payment_attempt)] pub struct PaymentAttemptUpdateInternal { - net_amount: Option, - status: Option, - authentication_type: Option, - error_message: Option>, - payment_method_id: Option, - cancellation_reason: Option, - modified_at: PrimitiveDateTime, - browser_info: Option, - payment_token: Option, - error_code: Option>, - connector_metadata: Option, - payment_method_data: Option, - payment_experience: Option, - preprocessing_step_id: Option, - error_reason: Option>, - connector_response_reference_id: Option, - multiple_capture_count: Option, - surcharge_amount: Option, - tax_on_surcharge: Option, - amount_capturable: Option, - updated_by: String, - merchant_connector_id: Option>, - authentication_data: Option, - encoded_data: Option, - unified_code: Option>, - unified_message: Option>, - external_three_ds_authentication_attempted: Option, - authentication_connector: Option, - authentication_id: Option, - fingerprint_id: Option, - payment_method_billing_address_id: Option, - charge_id: Option, - client_source: Option, - client_version: Option, - customer_acceptance: Option, - card_network: Option, - connector_payment_data: Option, - connector_mandate_detail: Option, + // net_amount: Option, + pub status: Option, + // authentication_type: Option, + pub error_message: Option, + pub connector_payment_id: Option, + // payment_method_id: Option, + // cancellation_reason: Option, + pub modified_at: PrimitiveDateTime, + pub browser_info: Option, + // payment_token: Option, + pub error_code: Option, + // connector_metadata: Option, + // payment_method_data: Option, + // payment_experience: Option, + // preprocessing_step_id: Option, + pub error_reason: Option, + // connector_response_reference_id: Option, + // multiple_capture_count: Option, + // pub surcharge_amount: Option, + // tax_on_surcharge: Option, + // amount_capturable: Option, + pub updated_by: String, + pub merchant_connector_id: Option, + pub connector: Option, + // authentication_data: Option, + // encoded_data: Option, + pub unified_code: Option>, + pub unified_message: Option>, + // external_three_ds_authentication_attempted: Option, + // authentication_connector: Option, + // authentication_id: Option, + // fingerprint_id: Option, + // charge_id: Option, + // client_source: Option, + // client_version: Option, + // customer_acceptance: Option, + // card_network: Option, } #[cfg(feature = "v1")] @@ -832,13 +833,6 @@ pub struct PaymentAttemptUpdateInternal { pub connector_mandate_detail: Option, } -#[cfg(feature = "v2")] -impl PaymentAttemptUpdateInternal { - pub fn populate_derived_fields(self, source: &PaymentAttempt) -> Self { - todo!(); - } -} - #[cfg(feature = "v1")] impl PaymentAttemptUpdateInternal { pub fn populate_derived_fields(self, source: &PaymentAttempt) -> Self { diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index f53594075a..210b12b7aa 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -192,6 +192,26 @@ pub struct TaxDetails { pub payment_method_type: Option, } +impl TaxDetails { + /// Get the tax amount + /// If default tax is present, return the default tax amount + /// If default tax is not present, return the tax amount based on the payment method if it matches the provided payment method type + pub fn get_tax_amount(&self, payment_method: PaymentMethodType) -> Option { + self.payment_method_type + .as_ref() + .filter(|payment_method_type_tax| payment_method_type_tax.pmt == payment_method) + .map(|payment_method_type_tax| payment_method_type_tax.order_tax_amount) + .or_else(|| self.get_default_tax_amount()) + } + + /// Get the default tax amount + fn get_default_tax_amount(&self) -> Option { + self.default + .as_ref() + .map(|default_tax_details| default_tax_details.order_tax_amount) + } +} + common_utils::impl_to_sql_from_sql_json!(TaxDetails); #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -333,78 +353,14 @@ pub struct PaymentIntentNew { #[cfg(feature = "v2")] #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PaymentIntentUpdate { - ResponseUpdate { - status: storage_enums::IntentStatus, - amount_captured: Option, - // Moved to attempt - // fingerprint_id: Option, - return_url: Option, - updated_by: String, - // Moved to attempt - // incremental_authorization_allowed: Option, - }, - MetadataUpdate { - metadata: pii::SecretSerdeValue, - updated_by: String, - }, - Update(Box), - PaymentCreateUpdate { - return_url: Option, - status: Option, - customer_id: Option, - shipping_address: Option, - billing_address: Option, - customer_details: Option, - updated_by: String, - }, - MerchantStatusUpdate { - status: storage_enums::IntentStatus, - shipping_address: Option, - billing_address: Option, - updated_by: String, - }, - PGStatusUpdate { + /// Update the payment intent details on payment intent confirmation, before calling the connector + ConfirmIntent { status: storage_enums::IntentStatus, updated_by: String, - // Moved to attempt - // incremental_authorization_allowed: Option, }, - PaymentAttemptAndAttemptCountUpdate { - active_attempt_id: String, - attempt_count: i16, - updated_by: String, - }, - StatusAndAttemptUpdate { + /// Update the payment intent details on payment intent confirmation, after calling the connector + ConfirmIntentPostUpdate { status: storage_enums::IntentStatus, - active_attempt_id: String, - attempt_count: i16, - updated_by: String, - }, - ApproveUpdate { - status: storage_enums::IntentStatus, - frm_merchant_decision: Option, - updated_by: String, - }, - RejectUpdate { - status: storage_enums::IntentStatus, - frm_merchant_decision: Option, - updated_by: String, - }, - SurchargeApplicableUpdate { - surcharge_applicable: Option, - updated_by: String, - }, - IncrementalAuthorizationAmountUpdate { - amount: MinorUnit, - }, - AuthorizationCountUpdate { - authorization_count: i32, - }, - CompleteAuthorizeUpdate { - shipping_address: Option, - }, - ManualUpdate { - status: Option, updated_by: String, }, } @@ -548,35 +504,36 @@ pub struct PaymentIntentUpdateFields { pub tax_details: Option, } +// TODO: uncomment fields as necessary #[cfg(feature = "v2")] #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] #[diesel(table_name = payment_intent)] pub struct PaymentIntentUpdateInternal { - pub amount: Option, - pub currency: Option, + // pub amount: Option, + // pub currency: Option, pub status: Option, - pub amount_captured: Option, - pub customer_id: Option, - pub return_url: Option, - pub setup_future_usage: Option, - pub metadata: Option, + // pub amount_captured: Option, + // pub customer_id: Option, + // pub return_url: Option<>, + // pub setup_future_usage: Option, + // pub metadata: Option, pub modified_at: PrimitiveDateTime, - pub active_attempt_id: Option, - pub description: Option, - pub statement_descriptor: Option, - #[diesel(deserialize_as = super::OptionalDieselArray)] - pub order_details: Option>, - pub attempt_count: Option, + // pub active_attempt_id: Option, + // pub description: Option, + // pub statement_descriptor: Option, + // #[diesel(deserialize_as = super::OptionalDieselArray)] + // pub order_details: Option>, + // pub attempt_count: Option, pub updated_by: String, - pub surcharge_applicable: Option, - pub authorization_count: Option, - pub session_expiry: Option, - pub request_external_three_ds_authentication: Option, - pub frm_metadata: Option, - pub customer_details: Option, - pub billing_address: Option, - pub shipping_address: Option, - pub frm_merchant_decision: Option, + // pub surcharge_applicable: Option, + // pub authorization_count: Option, + // pub session_expiry: Option, + // pub request_external_three_ds_authentication: Option, + // pub frm_metadata: Option, + // pub customer_details: Option, + // pub billing_address: Option, + // pub shipping_address: Option, + // pub frm_merchant_decision: Option, } #[cfg(feature = "v1")] @@ -625,71 +582,60 @@ pub struct PaymentIntentUpdateInternal { #[cfg(feature = "v2")] impl PaymentIntentUpdate { pub fn apply_changeset(self, source: PaymentIntent) -> PaymentIntent { - todo!() - // let PaymentIntentUpdateInternal { - // amount, - // currency, - // status, - // amount_captured, - // customer_id, - // return_url, - // setup_future_usage, - // off_session, - // metadata, - // modified_at: _, - // active_attempt_id, - // description, - // statement_descriptor, - // order_details, - // attempt_count, - // frm_merchant_decision, - // payment_confirm_source, - // updated_by, - // surcharge_applicable, - // authorization_count, - // session_expiry, - // request_external_three_ds_authentication, - // frm_metadata, - // customer_details, - // billing_address, - // merchant_order_reference_id, - // shipping_address, - // is_payment_processor_token_flow, - // } = self.into(); - // PaymentIntent { - // amount: amount.unwrap_or(source.amount), - // currency: currency.unwrap_or(source.currency), - // status: status.unwrap_or(source.status), - // amount_captured: amount_captured.or(source.amount_captured), - // customer_id: customer_id.or(source.customer_id), - // return_url: return_url.or(source.return_url), - // setup_future_usage: setup_future_usage.or(source.setup_future_usage), - // off_session: off_session.or(source.off_session), - // metadata: metadata.or(source.metadata), - // modified_at: common_utils::date_time::now(), - // active_attempt_id: active_attempt_id.unwrap_or(source.active_attempt_id), - // description: description.or(source.description), - // statement_descriptor: statement_descriptor.or(source.statement_descriptor), - // order_details: order_details.or(source.order_details), - // attempt_count: attempt_count.unwrap_or(source.attempt_count), - // frm_merchant_decision: frm_merchant_decision.or(source.frm_merchant_decision), - // payment_confirm_source: payment_confirm_source.or(source.payment_confirm_source), - // updated_by, - // surcharge_applicable: surcharge_applicable.or(source.surcharge_applicable), - // authorization_count: authorization_count.or(source.authorization_count), - // session_expiry: session_expiry.or(source.session_expiry), - // request_external_three_ds_authentication: request_external_three_ds_authentication - // .or(source.request_external_three_ds_authentication), - // frm_metadata: frm_metadata.or(source.frm_metadata), - // customer_details: customer_details.or(source.customer_details), - // billing_address: billing_address.or(source.billing_address), - // shipping_address: shipping_address.or(source.shipping_address), - // merchant_order_reference_id: merchant_order_reference_id - // .or(source.merchant_order_reference_id), - // is_payment_processor_token_flow: is_payment_processor_token_flow - // .or(source.is_payment_processor_token_flow), - // ..source - // } + let PaymentIntentUpdateInternal { + // amount, + // currency, + status, + // amount_captured, + // customer_id, + // return_url, + // setup_future_usage, + // metadata, + modified_at: _, + // active_attempt_id, + // description, + // statement_descriptor, + // order_details, + // attempt_count, + // frm_merchant_decision, + updated_by, + // surcharge_applicable, + // authorization_count, + // session_expiry, + // request_external_three_ds_authentication, + // frm_metadata, + // customer_details, + // billing_address, + // shipping_address, + } = self.into(); + PaymentIntent { + // amount: amount.unwrap_or(source.amount), + // currency: currency.unwrap_or(source.currency), + status: status.unwrap_or(source.status), + // amount_captured: amount_captured.or(source.amount_captured), + // customer_id: customer_id.or(source.customer_id), + // return_url: return_url.or(source.return_url), + // setup_future_usage: setup_future_usage.or(source.setup_future_usage), + // metadata: metadata.or(source.metadata), + modified_at: common_utils::date_time::now(), + // active_attempt_id: active_attempt_id.unwrap_or(source.active_attempt_id), + // description: description.or(source.description), + // statement_descriptor: statement_descriptor.or(source.statement_descriptor), + // order_details: order_details.or(source.order_details), + // attempt_count: attempt_count.unwrap_or(source.attempt_count), + // frm_merchant_decision: frm_merchant_decision.or(source.frm_merchant_decision), + updated_by, + // surcharge_applicable: surcharge_applicable.or(source.surcharge_applicable), + // authorization_count: authorization_count.or(source.authorization_count), + // session_expiry: session_expiry.or(source.session_expiry), + // request_external_three_ds_authentication: request_external_three_ds_authentication + // .or(source.request_external_three_ds_authentication), + // frm_metadata: frm_metadata.or(source.frm_metadata), + // customer_details: customer_details.or(source.customer_details), + // billing_address: billing_address.or(source.billing_address), + // shipping_address: shipping_address.or(source.shipping_address), + ..source + } } } @@ -786,509 +732,18 @@ impl PaymentIntentUpdate { #[cfg(feature = "v2")] impl From for PaymentIntentUpdateInternal { fn from(payment_intent_update: PaymentIntentUpdate) -> Self { - todo!() - // match payment_intent_update { - // PaymentIntentUpdate::MetadataUpdate { - // metadata, - // updated_by, - // } => Self { - // metadata: Some(metadata), - // modified_at: common_utils::date_time::now(), - // updated_by, - // amount: None, - // currency: None, - // status: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::Update(value) => Self { - // amount: Some(value.amount), - // currency: Some(value.currency), - // setup_future_usage: value.setup_future_usage, - // status: Some(value.status), - // customer_id: value.customer_id, - // shipping_address: value.shipping_address, - // billing_address: value.billing_address, - // return_url: value.return_url, - // description: value.description, - // statement_descriptor: value.statement_descriptor, - // order_details: value.order_details, - // metadata: value.metadata, - // payment_confirm_source: value.payment_confirm_source, - // updated_by: value.updated_by, - // session_expiry: value.session_expiry, - // request_external_three_ds_authentication: value - // .request_external_three_ds_authentication, - // frm_metadata: value.frm_metadata, - // customer_details: value.customer_details, - // merchant_order_reference_id: value.merchant_order_reference_id, - // amount_captured: None, - // off_session: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // attempt_count: None, - // frm_merchant_decision: None, - // surcharge_applicable: None, - // authorization_count: None, - // is_payment_processor_token_flow: value.is_payment_processor_token_flow, - // }, - // PaymentIntentUpdate::PaymentCreateUpdate { - // return_url, - // status, - // customer_id, - // shipping_address, - // billing_address, - // customer_details, - // updated_by, - // } => Self { - // return_url, - // status, - // customer_id, - // customer_details, - // modified_at: common_utils::date_time::now(), - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // billing_address, - // merchant_order_reference_id: None, - // shipping_address, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::PGStatusUpdate { status, updated_by } => Self { - // status: Some(status), - // modified_at: common_utils::date_time::now(), - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::MerchantStatusUpdate { - // status, - // billing_address, - // shipping_address, - // updated_by, - // } => Self { - // status: Some(status), - // modified_at: common_utils::date_time::now(), - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address, - // merchant_order_reference_id: None, - // shipping_address, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::ResponseUpdate { - // // amount, - // // currency, - // status, - // amount_captured, - // // customer_id, - // return_url, - // updated_by, - // } => Self { - // // amount, - // // currency: Some(currency), - // status: Some(status), - // amount_captured, - // // customer_id, - // return_url, - // modified_at: common_utils::date_time::now(), - // updated_by, - // amount: None, - // currency: None, - // customer_id: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { - // active_attempt_id, - // attempt_count, - // updated_by, - // } => Self { - // active_attempt_id: Some(active_attempt_id), - // attempt_count: Some(attempt_count), - // updated_by, - // amount: None, - // currency: None, - // status: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // description: None, - // statement_descriptor: None, - // order_details: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::StatusAndAttemptUpdate { - // status, - // active_attempt_id, - // attempt_count, - // updated_by, - // } => Self { - // status: Some(status), - // active_attempt_id: Some(active_attempt_id), - // attempt_count: Some(attempt_count), - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // description: None, - // statement_descriptor: None, - // order_details: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::ApproveUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // } => Self { - // status: Some(status), - // frm_merchant_decision, - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::RejectUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // } => Self { - // status: Some(status), - // frm_merchant_decision, - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::SurchargeApplicableUpdate { - // surcharge_applicable, - // updated_by, - // } => Self { - // surcharge_applicable, - // updated_by, - // amount: None, - // currency: None, - // status: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => Self { - // amount: Some(amount), - // currency: None, - // status: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // updated_by: String::default(), - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::AuthorizationCountUpdate { - // authorization_count, - // } => Self { - // authorization_count: Some(authorization_count), - // amount: None, - // currency: None, - // status: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // updated_by: String::default(), - // surcharge_applicable: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::CompleteAuthorizeUpdate { shipping_address } => Self { - // amount: None, - // currency: None, - // status: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // updated_by: String::default(), - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::ManualUpdate { status, updated_by } => Self { - // status, - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // } + match payment_intent_update { + PaymentIntentUpdate::ConfirmIntent { status, updated_by } => Self { + status: Some(status), + modified_at: common_utils::date_time::now(), + updated_by, + }, + PaymentIntentUpdate::ConfirmIntentPostUpdate { status, updated_by } => Self { + status: Some(status), + modified_at: common_utils::date_time::now(), + updated_by, + }, + } } } diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs index 0627fa0048..549af3ace4 100644 --- a/crates/diesel_models/src/query/payment_attempt.rs +++ b/crates/diesel_models/src/query/payment_attempt.rs @@ -68,11 +68,7 @@ impl PaymentAttempt { _, _, _, - >( - conn, - dsl::id.eq(self.id.to_owned()), - payment_attempt.populate_derived_fields(&self), - ) + >(conn, dsl::id.eq(self.id.to_owned()), payment_attempt) .await { Err(error) => match error.current_context() { diff --git a/crates/diesel_models/src/query/user/sample_data.rs b/crates/diesel_models/src/query/user/sample_data.rs index 27e674f7d7..b97ea39b82 100644 --- a/crates/diesel_models/src/query/user/sample_data.rs +++ b/crates/diesel_models/src/query/user/sample_data.rs @@ -14,11 +14,11 @@ use crate::schema_v2::{ use crate::{ errors, schema::{dispute::dsl as dispute_dsl, refund::dsl as refund_dsl}, - user::sample_data::PaymentAttemptBatchNew, - Dispute, DisputeNew, PaymentAttempt, PaymentIntent, PaymentIntentNew, PgPooledConn, Refund, - RefundNew, StorageResult, + user, Dispute, DisputeNew, PaymentAttempt, PaymentIntent, PaymentIntentNew, PgPooledConn, + Refund, RefundNew, StorageResult, }; +#[cfg(feature = "v1")] pub async fn insert_payment_intents( conn: &PgPooledConn, batch: Vec, @@ -33,9 +33,11 @@ pub async fn insert_payment_intents( .change_context(errors::DatabaseError::Others) .attach_printable("Error while inserting payment intents") } + +#[cfg(feature = "v1")] pub async fn insert_payment_attempts( conn: &PgPooledConn, - batch: Vec, + batch: Vec, ) -> StorageResult> { let query = diesel::insert_into(::table()).values(batch); diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 45a4ce03e5..b5e895cde7 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -749,8 +749,7 @@ diesel::table! { surcharge_amount -> Nullable, #[max_length = 64] payment_method_id -> Nullable, - confirm -> Bool, - authentication_type -> Nullable, + authentication_type -> AuthenticationType, created_at -> Timestamp, modified_at -> Timestamp, last_synced -> Nullable, @@ -782,7 +781,7 @@ diesel::table! { unified_code -> Nullable, #[max_length = 1024] unified_message -> Nullable, - net_amount -> Nullable, + net_amount -> Int8, external_three_ds_authentication_attempted -> Nullable, #[max_length = 64] authentication_connector -> Nullable, @@ -791,8 +790,6 @@ diesel::table! { #[max_length = 64] fingerprint_id -> Nullable, #[max_length = 64] - payment_method_billing_address_id -> Nullable, - #[max_length = 64] charge_id -> Nullable, #[max_length = 64] client_source -> Nullable, @@ -805,16 +802,17 @@ diesel::table! { organization_id -> Varchar, #[max_length = 32] card_network -> Nullable, - payment_method_type_v2 -> Nullable, + payment_method_type_v2 -> Varchar, #[max_length = 128] connector_payment_id -> Nullable, #[max_length = 64] - payment_method_subtype -> Nullable, + payment_method_subtype -> Varchar, routing_result -> Nullable, authentication_applied -> Nullable, #[max_length = 128] external_reference_id -> Nullable, tax_on_surcharge -> Nullable, + payment_method_billing_address -> Nullable, #[max_length = 512] connector_payment_data -> Nullable, #[max_length = 64] diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index 88617afb39..cfc9e1c4c8 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -15,122 +15,120 @@ use crate::{ ConnectorMandateReferenceId, PaymentAttemptNew, }; -#[cfg(feature = "v2")] -#[derive( - Clone, Debug, diesel::Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize, -)] -#[diesel(table_name = payment_attempt)] -pub struct PaymentAttemptBatchNew { - pub payment_id: common_utils::id_type::PaymentId, - pub merchant_id: common_utils::id_type::MerchantId, - pub status: AttemptStatus, - pub error_message: Option, - pub surcharge_amount: Option, - pub tax_on_surcharge: Option, - pub payment_method_id: Option, - pub confirm: bool, - pub authentication_type: Option, - #[serde(with = "common_utils::custom_serde::iso8601")] - pub created_at: PrimitiveDateTime, - #[serde(with = "common_utils::custom_serde::iso8601")] - pub modified_at: PrimitiveDateTime, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - pub last_synced: Option, - pub cancellation_reason: Option, - pub browser_info: Option, - pub payment_token: Option, - pub error_code: Option, - pub connector_metadata: Option, - pub payment_experience: Option, - pub payment_method_data: Option, - pub preprocessing_step_id: Option, - pub error_reason: Option, - pub connector_response_reference_id: Option, - pub multiple_capture_count: Option, - pub amount_capturable: i64, - pub updated_by: String, - pub merchant_connector_id: Option, - pub authentication_data: Option, - pub encoded_data: Option, - pub unified_code: Option, - pub unified_message: Option, - pub net_amount: Option, - pub external_three_ds_authentication_attempted: Option, - pub authentication_connector: Option, - pub authentication_id: Option, - pub payment_method_billing_address_id: Option, - pub fingerprint_id: Option, - pub charge_id: Option, - pub client_source: Option, - pub client_version: Option, - pub customer_acceptance: Option, - pub profile_id: common_utils::id_type::ProfileId, - pub organization_id: common_utils::id_type::OrganizationId, -} +// #[cfg(feature = "v2")] +// #[derive( +// Clone, Debug, diesel::Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize, +// )] +// #[diesel(table_name = payment_attempt)] +// pub struct PaymentAttemptBatchNew { +// pub payment_id: common_utils::id_type::PaymentId, +// pub merchant_id: common_utils::id_type::MerchantId, +// pub status: AttemptStatus, +// pub error_message: Option, +// pub surcharge_amount: Option, +// pub tax_on_surcharge: Option, +// pub payment_method_id: Option, +// pub authentication_type: Option, +// #[serde(with = "common_utils::custom_serde::iso8601")] +// pub created_at: PrimitiveDateTime, +// #[serde(with = "common_utils::custom_serde::iso8601")] +// pub modified_at: PrimitiveDateTime, +// #[serde(default, with = "common_utils::custom_serde::iso8601::option")] +// pub last_synced: Option, +// pub cancellation_reason: Option, +// pub browser_info: Option, +// pub payment_token: Option, +// pub error_code: Option, +// pub connector_metadata: Option, +// pub payment_experience: Option, +// pub payment_method_data: Option, +// pub preprocessing_step_id: Option, +// pub error_reason: Option, +// pub connector_response_reference_id: Option, +// pub multiple_capture_count: Option, +// pub amount_capturable: i64, +// pub updated_by: String, +// pub merchant_connector_id: Option, +// pub authentication_data: Option, +// pub encoded_data: Option, +// pub unified_code: Option, +// pub unified_message: Option, +// pub net_amount: Option, +// pub external_three_ds_authentication_attempted: Option, +// pub authentication_connector: Option, +// pub authentication_id: Option, +// pub fingerprint_id: Option, +// pub charge_id: Option, +// pub client_source: Option, +// pub client_version: Option, +// pub customer_acceptance: Option, +// pub profile_id: common_utils::id_type::ProfileId, +// pub organization_id: common_utils::id_type::OrganizationId, +// } -#[cfg(feature = "v2")] -#[allow(dead_code)] -impl PaymentAttemptBatchNew { - // Used to verify compatibility with PaymentAttemptTable - fn convert_into_normal_attempt_insert(self) -> PaymentAttemptNew { - // PaymentAttemptNew { - // payment_id: self.payment_id, - // merchant_id: self.merchant_id, - // status: self.status, - // error_message: self.error_message, - // surcharge_amount: self.surcharge_amount, - // tax_amount: self.tax_amount, - // payment_method_id: self.payment_method_id, - // confirm: self.confirm, - // authentication_type: self.authentication_type, - // created_at: self.created_at, - // modified_at: self.modified_at, - // last_synced: self.last_synced, - // cancellation_reason: self.cancellation_reason, - // browser_info: self.browser_info, - // payment_token: self.payment_token, - // error_code: self.error_code, - // connector_metadata: self.connector_metadata, - // payment_experience: self.payment_experience, - // card_network: self - // .payment_method_data - // .as_ref() - // .and_then(|data| data.as_object()) - // .and_then(|card| card.get("card")) - // .and_then(|v| v.as_object()) - // .and_then(|v| v.get("card_network")) - // .and_then(|network| network.as_str()) - // .map(|network| network.to_string()), - // payment_method_data: self.payment_method_data, - // straight_through_algorithm: self.straight_through_algorithm, - // preprocessing_step_id: self.preprocessing_step_id, - // error_reason: self.error_reason, - // multiple_capture_count: self.multiple_capture_count, - // connector_response_reference_id: self.connector_response_reference_id, - // amount_capturable: self.amount_capturable, - // updated_by: self.updated_by, - // merchant_connector_id: self.merchant_connector_id, - // authentication_data: self.authentication_data, - // encoded_data: self.encoded_data, - // unified_code: self.unified_code, - // unified_message: self.unified_message, - // net_amount: self.net_amount, - // external_three_ds_authentication_attempted: self - // .external_three_ds_authentication_attempted, - // authentication_connector: self.authentication_connector, - // authentication_id: self.authentication_id, - // payment_method_billing_address_id: self.payment_method_billing_address_id, - // fingerprint_id: self.fingerprint_id, - // charge_id: self.charge_id, - // client_source: self.client_source, - // client_version: self.client_version, - // customer_acceptance: self.customer_acceptance, - // profile_id: self.profile_id, - // organization_id: self.organization_id, - // } - todo!() - } -} +// #[cfg(feature = "v2")] +// #[allow(dead_code)] +// impl PaymentAttemptBatchNew { +// // Used to verify compatibility with PaymentAttemptTable +// fn convert_into_normal_attempt_insert(self) -> PaymentAttemptNew { +// // PaymentAttemptNew { +// // payment_id: self.payment_id, +// // merchant_id: self.merchant_id, +// // status: self.status, +// // error_message: self.error_message, +// // surcharge_amount: self.surcharge_amount, +// // tax_amount: self.tax_amount, +// // payment_method_id: self.payment_method_id, +// // confirm: self.confirm, +// // authentication_type: self.authentication_type, +// // created_at: self.created_at, +// // modified_at: self.modified_at, +// // last_synced: self.last_synced, +// // cancellation_reason: self.cancellation_reason, +// // browser_info: self.browser_info, +// // payment_token: self.payment_token, +// // error_code: self.error_code, +// // connector_metadata: self.connector_metadata, +// // payment_experience: self.payment_experience, +// // card_network: self +// // .payment_method_data +// // .as_ref() +// // .and_then(|data| data.as_object()) +// // .and_then(|card| card.get("card")) +// // .and_then(|v| v.as_object()) +// // .and_then(|v| v.get("card_network")) +// // .and_then(|network| network.as_str()) +// // .map(|network| network.to_string()), +// // payment_method_data: self.payment_method_data, +// // straight_through_algorithm: self.straight_through_algorithm, +// // preprocessing_step_id: self.preprocessing_step_id, +// // error_reason: self.error_reason, +// // multiple_capture_count: self.multiple_capture_count, +// // connector_response_reference_id: self.connector_response_reference_id, +// // amount_capturable: self.amount_capturable, +// // updated_by: self.updated_by, +// // merchant_connector_id: self.merchant_connector_id, +// // authentication_data: self.authentication_data, +// // encoded_data: self.encoded_data, +// // unified_code: self.unified_code, +// // unified_message: self.unified_message, +// // net_amount: self.net_amount, +// // external_three_ds_authentication_attempted: self +// // .external_three_ds_authentication_attempted, +// // authentication_connector: self.authentication_connector, +// // authentication_id: self.authentication_id, +// // payment_method_billing_address_id: self.payment_method_billing_address_id, +// // fingerprint_id: self.fingerprint_id, +// // charge_id: self.charge_id, +// // client_source: self.client_source, +// // client_version: self.client_version, +// // customer_acceptance: self.customer_acceptance, +// // profile_id: self.profile_id, +// // organization_id: self.organization_id, +// // } +// todo!() +// } +// } #[cfg(feature = "v1")] #[derive( diff --git a/crates/drainer/Cargo.toml b/crates/drainer/Cargo.toml index 5cb01dc8fb..ce7301a030 100644 --- a/crates/drainer/Cargo.toml +++ b/crates/drainer/Cargo.toml @@ -10,7 +10,7 @@ license.workspace = true [features] release = ["vergen", "external_services/aws_kms"] vergen = ["router_env/vergen"] -v1 = ["diesel_models/v1", "hyperswitch_interfaces/v1"] +v1 = ["diesel_models/v1", "hyperswitch_interfaces/v1", "common_utils/v1"] [dependencies] actix-web = "4.5.1" diff --git a/crates/external_services/Cargo.toml b/crates/external_services/Cargo.toml index fd7391c13b..735ccc5ebf 100644 --- a/crates/external_services/Cargo.toml +++ b/crates/external_services/Cargo.toml @@ -12,8 +12,8 @@ aws_kms = ["dep:aws-config", "dep:aws-sdk-kms"] email = ["dep:aws-config"] aws_s3 = ["dep:aws-config", "dep:aws-sdk-s3"] hashicorp-vault = ["dep:vaultrs"] -v1 = ["hyperswitch_interfaces/v1"] -dynamic_routing = ["dep:prost", "dep:tonic", "dep:tonic-reflection", "dep:tonic-types", "dep:api_models", "tokio/macros", "tokio/rt-multi-thread" , "dep:tonic-build", "dep:router_env"] +v1 = ["hyperswitch_interfaces/v1", "common_utils/v1"] +dynamic_routing = ["dep:prost", "dep:tonic", "dep:tonic-reflection", "dep:tonic-types", "dep:api_models", "tokio/macros", "tokio/rt-multi-thread", "dep:tonic-build", "dep:router_env"] [dependencies] async-trait = "0.1.79" @@ -34,7 +34,7 @@ serde = { version = "1.0.197", features = ["derive"] } thiserror = "1.0.58" vaultrs = { version = "0.7.2", optional = true } prost = { version = "0.13", optional = true } -tokio = "1.37.0" +tokio = "1.37.0" tonic = { version = "0.12.2", optional = true } tonic-reflection = { version = "0.12.2", optional = true } tonic-types = { version = "0.12.2", optional = true } @@ -49,7 +49,7 @@ api_models = { version = "0.1.0", path = "../api_models", optional = true } [build-dependencies] -tonic-build = { version = "0.12" , optional = true } +tonic-build = { version = "0.12", optional = true } router_env = { version = "0.1.0", path = "../router_env", default-features = false, optional = true } [lints] diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index 86d6da446e..1adbe693de 100644 --- a/crates/hyperswitch_connectors/Cargo.toml +++ b/crates/hyperswitch_connectors/Cargo.toml @@ -8,7 +8,7 @@ license.workspace = true [features] frm = ["hyperswitch_domain_models/frm", "hyperswitch_interfaces/frm"] payouts = ["hyperswitch_domain_models/payouts", "api_models/payouts", "hyperswitch_interfaces/payouts"] -v1 = ["api_models/v1", "hyperswitch_domain_models/v1"] +v1 = ["api_models/v1", "hyperswitch_domain_models/v1", "common_utils/v1"] [dependencies] actix-http = "3.6.0" diff --git a/crates/hyperswitch_connectors/src/connectors/helcim.rs b/crates/hyperswitch_connectors/src/connectors/helcim.rs index 8d632d8b7e..fcb9c77e10 100644 --- a/crates/hyperswitch_connectors/src/connectors/helcim.rs +++ b/crates/hyperswitch_connectors/src/connectors/helcim.rs @@ -65,9 +65,9 @@ impl api::PaymentToken for Helcim {} impl Helcim { pub fn connector_transaction_id( &self, - connector_meta: &Option, + connector_meta: Option<&serde_json::Value>, ) -> CustomResult, errors::ConnectorError> { - let meta: helcim::HelcimMetaData = to_connector_meta(connector_meta.clone())?; + let meta: helcim::HelcimMetaData = to_connector_meta(connector_meta.cloned())?; Ok(Some(meta.preauth_transaction_id.to_string())) } } diff --git a/crates/hyperswitch_connectors/src/connectors/nexinets.rs b/crates/hyperswitch_connectors/src/connectors/nexinets.rs index 902afe3413..94b24bbe86 100644 --- a/crates/hyperswitch_connectors/src/connectors/nexinets.rs +++ b/crates/hyperswitch_connectors/src/connectors/nexinets.rs @@ -68,9 +68,9 @@ impl api::RefundSync for Nexinets {} impl Nexinets { pub fn connector_transaction_id( &self, - connector_meta: &Option, + connector_meta: Option<&serde_json::Value>, ) -> CustomResult, errors::ConnectorError> { - let meta: nexinets::NexinetsPaymentsMetadata = to_connector_meta(connector_meta.clone())?; + let meta: nexinets::NexinetsPaymentsMetadata = to_connector_meta(connector_meta.cloned())?; Ok(meta.transaction_id) } } diff --git a/crates/hyperswitch_domain_models/Cargo.toml b/crates/hyperswitch_domain_models/Cargo.toml index 3a0d4b402b..8fa6d2cc4e 100644 --- a/crates/hyperswitch_domain_models/Cargo.toml +++ b/crates/hyperswitch_domain_models/Cargo.toml @@ -13,8 +13,8 @@ encryption_service = [] olap = [] payouts = ["api_models/payouts"] frm = ["api_models/frm"] -v2 = ["api_models/v2", "diesel_models/v2"] -v1 = ["api_models/v1", "diesel_models/v1"] +v2 = ["api_models/v2", "diesel_models/v2", "common_utils/v2"] +v1 = ["api_models/v1", "diesel_models/v1", "common_utils/v1"] customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2"] payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2"] diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 4803514617..af6c8e65c8 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -123,11 +123,11 @@ impl From for payments::AmountDetails { order_amount: amount_details.order_amount().into(), currency: amount_details.currency(), shipping_cost: amount_details.shipping_cost(), - tax_details: Some(diesel_models::TaxDetails { - default: amount_details - .order_tax_amount() - .map(|order_tax_amount| diesel_models::DefaultTax { order_tax_amount }), - payment_method_type: None, + tax_details: amount_details.order_tax_amount().map(|order_tax_amount| { + diesel_models::TaxDetails { + default: Some(diesel_models::DefaultTax { order_tax_amount }), + payment_method_type: None, + } }), skip_external_tax_calculation: payments::TaxCalculationOverride::from( amount_details.skip_external_tax_calculation(), @@ -137,6 +137,8 @@ impl From for payments::AmountDetails { ), surcharge_amount: amount_details.surcharge_amount(), tax_on_surcharge: amount_details.tax_on_surcharge(), + // We will not receive this in the request. This will be populated after calling the connector / processor + amount_captured: None, } } } diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 4c8cb9c38e..933c13416f 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -22,7 +22,7 @@ use crate::RemoteStorageObject; #[cfg(feature = "v2")] use crate::{business_profile, merchant_account}; #[cfg(feature = "v2")] -use crate::{errors, ApiModelToDieselModelConvertor}; +use crate::{errors, payment_method_data, ApiModelToDieselModelConvertor}; #[cfg(feature = "v1")] #[derive(Clone, Debug, PartialEq, serde::Serialize)] @@ -101,7 +101,7 @@ impl PaymentIntent { } #[cfg(feature = "v2")] -#[derive(Clone, Debug, PartialEq, serde::Serialize)] +#[derive(Clone, Debug, PartialEq, Copy, serde::Serialize)] pub enum TaxCalculationOverride { /// Skip calling the external tax provider Skip, @@ -110,7 +110,7 @@ pub enum TaxCalculationOverride { } #[cfg(feature = "v2")] -#[derive(Clone, Debug, PartialEq, serde::Serialize)] +#[derive(Clone, Debug, PartialEq, Copy, serde::Serialize)] pub enum SurchargeCalculationOverride { /// Skip calculating surcharge Skip, @@ -157,6 +157,9 @@ pub struct AmountDetails { pub surcharge_amount: Option, /// tax on surcharge amount pub tax_on_surcharge: Option, + /// The total amount captured for the order. This is the sum of all the captured amounts for the order. + /// For automatic captures, this will be the same as net amount for the order + pub amount_captured: Option, } #[cfg(feature = "v2")] @@ -176,6 +179,49 @@ impl AmountDetails { TaxCalculationOverride::Calculate => true, } } + + /// Calculate the net amount for the order + pub fn calculate_net_amount(&self) -> MinorUnit { + self.order_amount + + self.shipping_cost.unwrap_or(MinorUnit::zero()) + + self.surcharge_amount.unwrap_or(MinorUnit::zero()) + + self.tax_on_surcharge.unwrap_or(MinorUnit::zero()) + } + + pub fn create_attempt_amount_details( + &self, + confirm_intent_request: &api_models::payments::PaymentsConfirmIntentRequest, + ) -> payment_attempt::AttemptAmountDetails { + let net_amount = self.calculate_net_amount(); + + let surcharge_amount = match self.skip_surcharge_calculation { + SurchargeCalculationOverride::Skip => self.surcharge_amount, + SurchargeCalculationOverride::Calculate => None, + }; + + let tax_on_surcharge = match self.skip_surcharge_calculation { + SurchargeCalculationOverride::Skip => self.tax_on_surcharge, + SurchargeCalculationOverride::Calculate => None, + }; + + let order_tax_amount = match self.skip_external_tax_calculation { + TaxCalculationOverride::Skip => self.tax_details.as_ref().and_then(|tax_details| { + tax_details.get_tax_amount(confirm_intent_request.payment_method_subtype) + }), + TaxCalculationOverride::Calculate => None, + }; + + payment_attempt::AttemptAmountDetails { + net_amount, + amount_to_capture: None, + surcharge_amount, + tax_on_surcharge, + // This will be updated when we receive response from the connector + amount_capturable: MinorUnit::zero(), + shipping_cost: self.shipping_cost, + order_tax_amount, + } + } } #[cfg(feature = "v2")] @@ -294,6 +340,23 @@ impl PaymentIntent { }) .unwrap_or(Ok(common_enums::RequestIncrementalAuthorization::default())) } + + /// Check if the client secret is associated with the payment and if it has been expired + pub fn validate_client_secret( + &self, + client_secret: &common_utils::types::ClientSecret, + ) -> Result<(), errors::api_error_response::ApiErrorResponse> { + common_utils::fp_utils::when(self.client_secret != *client_secret, || { + Err(errors::api_error_response::ApiErrorResponse::ClientSecretInvalid) + })?; + + common_utils::fp_utils::when(self.session_expiry < common_utils::date_time::now(), || { + Err(errors::api_error_response::ApiErrorResponse::ClientSecretExpired) + })?; + + Ok(()) + } + pub async fn create_domain_model_from_request( payment_id: &id_type::GlobalPaymentId, merchant_account: &merchant_account::MerchantAccount, @@ -384,6 +447,48 @@ impl PaymentIntent { } } +#[cfg(feature = "v1")] +#[derive(Default, Debug, Clone)] +pub struct HeaderPayload { + pub payment_confirm_source: Option, + pub client_source: Option, + pub client_version: Option, + pub x_hs_latency: Option, + pub browser_name: Option, + pub x_client_platform: Option, + pub x_merchant_domain: Option, + pub locale: Option, + pub x_app_id: Option, + pub x_redirect_uri: Option, +} + +// TODO: uncomment fields as necessary +#[cfg(feature = "v2")] +#[derive(Default, Debug, Clone)] +pub struct HeaderPayload { + /// The source with which the payment is confirmed. + pub payment_confirm_source: Option, + // pub client_source: Option, + // pub client_version: Option, + pub x_hs_latency: Option, + pub browser_name: Option, + pub x_client_platform: Option, + pub x_merchant_domain: Option, + pub locale: Option, + pub x_app_id: Option, + pub x_redirect_uri: Option, + pub client_secret: Option, +} + +impl HeaderPayload { + pub fn with_source(payment_confirm_source: common_enums::PaymentSource) -> Self { + Self { + payment_confirm_source: Some(payment_confirm_source), + ..Default::default() + } + } +} + #[cfg(feature = "v2")] #[derive(Clone)] pub struct PaymentIntentData @@ -393,3 +498,16 @@ where pub flow: PhantomData, pub payment_intent: PaymentIntent, } + +// TODO: Check if this can be merged with existing payment data +#[cfg(feature = "v2")] +#[derive(Clone)] +pub struct PaymentConfirmData +where + F: Clone, +{ + pub flow: PhantomData, + pub payment_intent: PaymentIntent, + pub payment_attempt: PaymentAttempt, + pub payment_method_data: Option, +} diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 8ecac77e54..76749097b3 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -15,6 +15,8 @@ use diesel_models::{ PaymentAttemptUpdate as DieselPaymentAttemptUpdate, }; use error_stack::ResultExt; +#[cfg(feature = "v2")] +use masking::PeekInterface; use masking::Secret; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -56,7 +58,7 @@ pub trait PaymentAttemptInterface { ) -> error_stack::Result; #[cfg(feature = "v2")] - async fn update_payment_attempt_with_attempt_id( + async fn update_payment_attempt( &self, key_manager_state: &KeyManagerState, merchant_key_store: &MerchantKeyStore, @@ -163,66 +165,139 @@ pub trait PaymentAttemptInterface { ) -> error_stack::Result; } -#[cfg(feature = "v2")] -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct PaymentAttempt { - pub payment_id: id_type::GlobalPaymentId, - pub merchant_id: id_type::MerchantId, - pub status: storage_enums::AttemptStatus, +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] +pub struct AttemptAmountDetails { + /// The total amount for this payment attempt. This includes all the surcharge and tax amounts. pub net_amount: MinorUnit, - pub connector: Option, + /// The amount that has to be captured, pub amount_to_capture: Option, - pub error_message: Option, + /// Surcharge amount for the payment attempt. + /// This is either derived by surcharge rules, or sent by the merchant pub surcharge_amount: Option, + /// Tax amount for the payment attempt + /// This is either derived by surcharge rules, or sent by the merchant pub tax_on_surcharge: Option, - pub confirm: bool, - pub authentication_type: Option, - #[serde(with = "common_utils::custom_serde::iso8601")] + /// The total amount that can be captured for this payment attempt. + pub amount_capturable: MinorUnit, + /// Shipping cost for the payment attempt. + pub shipping_cost: Option, + /// Tax amount for the order. + /// This is either derived by calling an external tax processor, or sent by the merchant + pub order_tax_amount: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] +pub struct ErrorDetails { + /// The error code that was returned by the connector. + /// This is a mandatory field. This is used to lookup the global status map record for unified code and retries + pub code: String, + /// The error message that was returned by the connector. + /// This is a mandatory field. This is used to lookup the global status map record for unified message and retries + pub message: String, + /// The detailed error reason that was returned by the connector. + pub reason: Option, + /// The unified code that is generated by the application based on the global status map record. + /// This can be relied upon for common error code across all connectors + pub unified_code: Option, + /// The unified message that is generated by the application based on the global status map record. + /// This can be relied upon for common error code across all connectors + /// If there is translation available, message will be translated to the requested language + pub unified_message: Option, +} + +/// Domain model for the payment attempt. +/// Few fields which are related are grouped together for better readability and understandability. +/// These fields will be flattened and stored in the database in individual columns +#[cfg(feature = "v2")] +#[derive(Clone, Debug, PartialEq, serde::Serialize)] +pub struct PaymentAttempt { + /// Payment id for the payment attempt + pub payment_id: id_type::GlobalPaymentId, + /// Merchant id for the payment attempt + pub merchant_id: id_type::MerchantId, + /// Amount details for the payment attempt + pub amount_details: AttemptAmountDetails, + /// Status of the payment attempt. This is the status that is updated by the connector. + /// The intent status is updated by the AttemptStatus. + pub status: storage_enums::AttemptStatus, + /// Name of the connector that was used for the payment attempt. The connector is either decided by + /// either running the routing algorithm or by straight through processing request. + /// This will be updated before calling the connector + // TODO: use connector enum, this should be done in v1 as well as a part of moving to domain types wherever possible + pub connector: Option, + /// Error details in case the payment attempt failed + pub error: Option, + /// The authentication type that was requested for the payment attempt. + /// This authentication type maybe decided by step up 3ds or by running the decision engine. + pub authentication_type: storage_enums::AuthenticationType, + /// The time at which the payment attempt was created pub created_at: PrimitiveDateTime, - #[serde(with = "common_utils::custom_serde::iso8601")] + /// The time at which the payment attempt was last modified pub modified_at: PrimitiveDateTime, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub last_synced: Option, + /// The reason for the cancellation of the payment attempt. Some connectors will have strict rules regarding the values this can have + /// Cancellation reason will be validated at the connector level when building the request pub cancellation_reason: Option, - pub browser_info: Option, - pub error_code: Option, + /// Browser information required for 3DS authentication + pub browser_info: Option, + /// Payment token is the token used for temporary use in case the payment method is stored in vault pub payment_token: Option, - pub connector_metadata: Option, + /// Metadata that is returned by the connector. + pub connector_metadata: Option, pub payment_experience: Option, - pub payment_method_data: Option, + /// The insensitive data of the payment method data is stored here + // TODO: evaluate what details should be stored here. Use a domain type instead of serde_json::Value + pub payment_method_data: Option, + /// The result of the routing algorithm. + /// This will store the list of connectors and other related information that was used to route the payment. + // TODO: change this to type instead of serde_json::Value pub routing_result: Option, pub preprocessing_step_id: Option, - pub error_reason: Option, + /// Number of captures that have happened for the payment attempt pub multiple_capture_count: Option, - // reference to the payment at connector side + /// A reference to the payment at connector side. This is returned by the connector pub connector_response_reference_id: Option, - pub amount_capturable: MinorUnit, + /// Whether the payment was updated by postgres or redis pub updated_by: String, - pub authentication_data: Option, - pub encoded_data: Option, + /// The authentication data which is used for external authentication + pub authentication_data: Option, + pub encoded_data: Option>, pub merchant_connector_id: Option, - pub unified_code: Option, - pub unified_message: Option, + /// Whether external 3DS authentication was attempted for this payment. + /// This is based on the configuration of the merchant in the business profile pub external_three_ds_authentication_attempted: Option, + /// The connector that was used for external authentication pub authentication_connector: Option, + /// The foreign key reference to the authentication details pub authentication_id: Option, - pub payment_method_billing_address_id: Option, pub fingerprint_id: Option, pub charge_id: Option, pub client_source: Option, pub client_version: Option, + // TODO: use a type here instead of value pub customer_acceptance: Option, + /// The profile id for the payment attempt. This will be derived from payment intent. pub profile_id: id_type::ProfileId, + /// The organization id for the payment attempt. This will be derived from payment intent. pub organization_id: id_type::OrganizationId, - pub payment_method_type: Option, - pub payment_method_id: Option, + /// Payment method type for the payment attempt + pub payment_method_type: storage_enums::PaymentMethod, + /// Foreig key reference of Payment method id in case the payment instrument was stored + pub payment_method_id: Option, + /// The reference to the payment at the connector side pub connector_payment_id: Option, - pub payment_method_subtype: Option, + /// The payment method subtype for the payment attempt. + pub payment_method_subtype: storage_enums::PaymentMethodType, + /// The authentication type that was applied for the payment attempt. pub authentication_applied: Option, + /// A reference to the payment at connector side. This is returned by the connector pub external_reference_id: Option, - pub shipping_cost: Option, - pub order_tax_amount: Option, - pub id: String, + /// The billing address for the payment method + // TODO: use a type here instead of value + pub payment_method_billing_address: common_utils::crypto::OptionalEncryptableValue, + /// The global identifier for the payment attempt + pub id: id_type::GlobalAttemptId, + /// The connector mandate details which are stored temporarily pub connector_mandate_detail: Option, } @@ -234,7 +309,8 @@ impl PaymentAttempt { #[cfg(feature = "v2")] pub fn get_payment_method(&self) -> Option { - self.payment_method_type + // TODO: check if we can fix this + Some(self.payment_method_type) } #[cfg(feature = "v1")] @@ -244,7 +320,8 @@ impl PaymentAttempt { #[cfg(feature = "v2")] pub fn get_payment_method_type(&self) -> Option { - self.payment_method_subtype + // TODO: check if we can fix this + Some(self.payment_method_subtype) } #[cfg(feature = "v1")] @@ -253,7 +330,7 @@ impl PaymentAttempt { } #[cfg(feature = "v2")] - pub fn get_id(&self) -> &str { + pub fn get_id(&self) -> &id_type::GlobalAttemptId { &self.id } @@ -266,6 +343,71 @@ impl PaymentAttempt { pub fn get_connector_payment_id(&self) -> Option<&str> { self.connector_payment_id.as_deref() } + + /// Construct the domain model from the ConfirmIntentRequest and PaymentIntent + #[cfg(feature = "v2")] + pub async fn create_domain_model( + payment_intent: &super::PaymentIntent, + cell_id: id_type::CellId, + storage_scheme: storage_enums::MerchantStorageScheme, + request: &api_models::payments::PaymentsConfirmIntentRequest, + ) -> CustomResult { + let id = id_type::GlobalAttemptId::generate(&cell_id); + let intent_amount_details = payment_intent.amount_details.clone(); + + let attempt_amount_details = intent_amount_details.create_attempt_amount_details(request); + + let now = common_utils::date_time::now(); + + Ok(Self { + payment_id: payment_intent.id.clone(), + merchant_id: payment_intent.merchant_id.clone(), + amount_details: attempt_amount_details, + status: common_enums::AttemptStatus::Started, + // This will be decided by the routing algorithm and updated in update trackers + // right before calling the connector + connector: None, + authentication_type: payment_intent.authentication_type, + created_at: now, + modified_at: now, + last_synced: None, + cancellation_reason: None, + browser_info: request.browser_info.clone(), + payment_token: None, + connector_metadata: None, + payment_experience: None, + payment_method_data: None, + routing_result: None, + preprocessing_step_id: None, + multiple_capture_count: None, + connector_response_reference_id: None, + updated_by: storage_scheme.to_string(), + authentication_data: None, + encoded_data: None, + merchant_connector_id: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + profile_id: payment_intent.profile_id.clone(), + organization_id: payment_intent.organization_id.clone(), + payment_method_type: request.payment_method_type, + payment_method_id: None, + connector_payment_id: None, + payment_method_subtype: request.payment_method_subtype, + authentication_applied: None, + external_reference_id: None, + // TODO: encrypt and store this + payment_method_billing_address: None, + error: None, + connector_mandate_detail: None, + id, + }) + } } #[cfg(feature = "v1")] @@ -336,6 +478,7 @@ pub struct PaymentAttempt { pub connector_mandate_detail: Option, } +#[cfg(feature = "v1")] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)] pub struct NetAmount { /// The payment amount @@ -350,6 +493,7 @@ pub struct NetAmount { tax_on_surcharge: Option, } +#[cfg(feature = "v1")] impl NetAmount { pub fn new( order_amount: MinorUnit, @@ -511,57 +655,6 @@ pub struct PaymentListFilters { pub authentication_type: Vec, } -#[cfg(feature = "v2")] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct PaymentAttemptNew { - pub payment_id: id_type::PaymentId, - pub merchant_id: id_type::MerchantId, - pub status: storage_enums::AttemptStatus, - pub error_message: Option, - pub surcharge_amount: Option, - pub tax_amount: Option, - pub payment_method_id: Option, - pub confirm: bool, - pub authentication_type: Option, - pub created_at: PrimitiveDateTime, - pub modified_at: PrimitiveDateTime, - pub last_synced: Option, - pub cancellation_reason: Option, - pub browser_info: Option, - pub payment_token: Option, - pub error_code: Option, - pub connector_metadata: Option, - pub payment_experience: Option, - pub payment_method_data: Option, - pub straight_through_algorithm: Option, - pub preprocessing_step_id: Option, - pub error_reason: Option, - pub connector_response_reference_id: Option, - pub multiple_capture_count: Option, - pub amount_capturable: MinorUnit, - pub updated_by: String, - pub merchant_connector_id: Option, - pub authentication_data: Option, - pub encoded_data: Option, - pub unified_code: Option, - pub unified_message: Option, - pub net_amount: Option, - pub external_three_ds_authentication_attempted: Option, - pub authentication_connector: Option, - pub authentication_id: Option, - pub fingerprint_id: Option, - pub payment_method_billing_address_id: Option, - pub charge_id: Option, - pub client_source: Option, - pub client_version: Option, - pub customer_acceptance: Option, - pub profile_id: id_type::ProfileId, - pub organization_id: id_type::OrganizationId, - pub card_network: Option, - pub shipping_cost: Option, - pub order_tax_amount: Option, -} - #[cfg(feature = "v1")] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaymentAttemptNew { @@ -1185,8 +1278,28 @@ impl PaymentAttemptUpdate { // TODO: Add fields as necessary #[cfg(feature = "v2")] -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum PaymentAttemptUpdate {} +#[derive(Debug, Clone, Serialize)] +pub enum PaymentAttemptUpdate { + /// Update the payment attempt on confirming the intent, before calling the connector + ConfirmIntent { + status: storage_enums::AttemptStatus, + updated_by: String, + connector: String, + merchant_connector_id: id_type::MerchantConnectorAccountId, + }, + /// Update the payment attempt on confirming the intent, after calling the connector on success response + ConfirmIntentResponse { + status: storage_enums::AttemptStatus, + connector_payment_id: Option, + updated_by: String, + }, + /// Update the payment attempt on confirming the intent, after calling the connector on error response + ConfirmIntentError { + status: storage_enums::AttemptStatus, + error: ErrorDetails, + updated_by: String, + }, +} #[cfg(feature = "v2")] impl ForeignIDRef for PaymentAttempt { @@ -1463,10 +1576,12 @@ impl behaviour::Conversion for PaymentAttempt { type NewDstType = DieselPaymentAttemptNew; async fn convert(self) -> CustomResult { + use common_utils::encryption::Encryption; + let card_network = self .payment_method_data .as_ref() - .and_then(|data| data.as_object()) + .and_then(|data| data.peek().as_object()) .and_then(|card| card.get("card")) .and_then(|data| data.as_object()) .and_then(|card| card.get("card_network")) @@ -1477,38 +1592,29 @@ impl behaviour::Conversion for PaymentAttempt { payment_id, merchant_id, status, - net_amount, - error_message, - surcharge_amount, - tax_on_surcharge, - confirm, + error, + amount_details, authentication_type, created_at, modified_at, last_synced, cancellation_reason, browser_info, - error_code, payment_token, connector_metadata, payment_experience, payment_method_data, routing_result, preprocessing_step_id, - error_reason, multiple_capture_count, connector_response_reference_id, - amount_capturable, updated_by, authentication_data, encoded_data, merchant_connector_id, - unified_code, - unified_message, external_three_ds_authentication_attempted, authentication_connector, authentication_id, - payment_method_billing_address_id, fingerprint_id, charge_id, client_source, @@ -1522,14 +1628,22 @@ impl behaviour::Conversion for PaymentAttempt { authentication_applied, external_reference_id, id, - amount_to_capture, payment_method_id, - shipping_cost, - order_tax_amount, + payment_method_billing_address, connector, connector_mandate_detail, } = self; + let AttemptAmountDetails { + net_amount, + tax_on_surcharge, + surcharge_amount, + order_tax_amount, + shipping_cost, + amount_capturable, + amount_to_capture, + } = amount_details; + let (connector_payment_id, connector_payment_data) = connector_payment_id .map(ConnectorTransactionId::form_id_and_data) .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) @@ -1540,13 +1654,10 @@ impl behaviour::Conversion for PaymentAttempt { merchant_id, id, status, - error_message, - surcharge_amount, - tax_on_surcharge, + error_message: error.as_ref().map(|details| details.message.clone()), payment_method_id, payment_method_type_v2: payment_method_type, connector_payment_id, - confirm, authentication_type, created_at, modified_at, @@ -1554,14 +1665,14 @@ impl behaviour::Conversion for PaymentAttempt { cancellation_reason, amount_to_capture, browser_info, - error_code, + error_code: error.as_ref().map(|details| details.code.clone()), payment_token, connector_metadata, payment_experience, payment_method_subtype, payment_method_data, preprocessing_step_id, - error_reason, + error_reason: error.as_ref().and_then(|details| details.reason.clone()), multiple_capture_count, connector_response_reference_id, amount_capturable, @@ -1569,14 +1680,17 @@ impl behaviour::Conversion for PaymentAttempt { merchant_connector_id, authentication_data, encoded_data, - unified_code, - unified_message, - net_amount: Some(net_amount), + unified_code: error + .as_ref() + .and_then(|details| details.unified_code.clone()), + unified_message: error + .as_ref() + .and_then(|details| details.unified_message.clone()), + net_amount, external_three_ds_authentication_attempted, authentication_connector, authentication_id, fingerprint_id, - payment_method_billing_address_id, charge_id, client_source, client_version, @@ -1590,66 +1704,95 @@ impl behaviour::Conversion for PaymentAttempt { authentication_applied, external_reference_id, connector, + surcharge_amount, + tax_on_surcharge, + payment_method_billing_address: payment_method_billing_address.map(Encryption::from), connector_payment_data, connector_mandate_detail, }) } async fn convert_back( - _state: &KeyManagerState, + state: &KeyManagerState, storage_model: Self::DstType, - _key: &Secret>, - _key_manager_identifier: keymanager::Identifier, + key: &Secret>, + key_manager_identifier: keymanager::Identifier, ) -> CustomResult where Self: Sized, { + use crate::type_encryption; + async { let connector_payment_id = storage_model .get_optional_connector_transaction_id() .cloned(); + + let amount_details = AttemptAmountDetails { + net_amount: storage_model.net_amount, + tax_on_surcharge: storage_model.tax_on_surcharge, + surcharge_amount: storage_model.surcharge_amount, + order_tax_amount: storage_model.order_tax_amount, + shipping_cost: storage_model.shipping_cost, + amount_capturable: storage_model.amount_capturable, + amount_to_capture: storage_model.amount_to_capture, + }; + + let inner_decrypt = |inner| async { + type_encryption::crypto_operation( + state, + common_utils::type_name!(Self::DstType), + type_encryption::CryptoOperation::DecryptOptional(inner), + key_manager_identifier.clone(), + key.peek(), + ) + .await + .and_then(|val| val.try_into_optionaloperation()) + }; + + let error = storage_model + .error_code + .zip(storage_model.error_message) + .map(|(error_code, error_message)| ErrorDetails { + code: error_code, + message: error_message, + reason: storage_model.error_reason, + unified_code: storage_model.unified_code, + unified_message: storage_model.unified_message, + }); + Ok::>(Self { payment_id: storage_model.payment_id, merchant_id: storage_model.merchant_id, id: storage_model.id, status: storage_model.status, - net_amount: storage_model.net_amount.unwrap_or(MinorUnit::new(0)), - tax_on_surcharge: storage_model.tax_on_surcharge, - error_message: storage_model.error_message, - surcharge_amount: storage_model.surcharge_amount, + amount_details, + error, payment_method_id: storage_model.payment_method_id, payment_method_type: storage_model.payment_method_type_v2, connector_payment_id, - confirm: storage_model.confirm, authentication_type: storage_model.authentication_type, created_at: storage_model.created_at, modified_at: storage_model.modified_at, last_synced: storage_model.last_synced, cancellation_reason: storage_model.cancellation_reason, - amount_to_capture: storage_model.amount_to_capture, browser_info: storage_model.browser_info, - error_code: storage_model.error_code, payment_token: storage_model.payment_token, connector_metadata: storage_model.connector_metadata, payment_experience: storage_model.payment_experience, payment_method_data: storage_model.payment_method_data, routing_result: storage_model.routing_result, preprocessing_step_id: storage_model.preprocessing_step_id, - error_reason: storage_model.error_reason, multiple_capture_count: storage_model.multiple_capture_count, connector_response_reference_id: storage_model.connector_response_reference_id, - amount_capturable: storage_model.amount_capturable, updated_by: storage_model.updated_by, authentication_data: storage_model.authentication_data, encoded_data: storage_model.encoded_data, merchant_connector_id: storage_model.merchant_connector_id, - unified_code: storage_model.unified_code, - unified_message: storage_model.unified_message, external_three_ds_authentication_attempted: storage_model .external_three_ds_authentication_attempted, authentication_connector: storage_model.authentication_connector, authentication_id: storage_model.authentication_id, - payment_method_billing_address_id: storage_model.payment_method_billing_address_id, fingerprint_id: storage_model.fingerprint_id, charge_id: storage_model.charge_id, client_source: storage_model.client_source, @@ -1657,12 +1800,14 @@ impl behaviour::Conversion for PaymentAttempt { customer_acceptance: storage_model.customer_acceptance, profile_id: storage_model.profile_id, organization_id: storage_model.organization_id, - order_tax_amount: storage_model.order_tax_amount, - shipping_cost: storage_model.shipping_cost, payment_method_subtype: storage_model.payment_method_subtype, authentication_applied: storage_model.authentication_applied, external_reference_id: storage_model.external_reference_id, connector: storage_model.connector, + payment_method_billing_address: inner_decrypt( + storage_model.payment_method_billing_address, + ) + .await?, connector_mandate_detail: storage_model.connector_mandate_detail, }) } @@ -1673,25 +1818,30 @@ impl behaviour::Conversion for PaymentAttempt { } async fn construct_new(self) -> CustomResult { + use common_utils::encryption::Encryption; + let card_network = self .payment_method_data .as_ref() - .and_then(|data| data.as_object()) + .and_then(|data| data.peek().as_object()) .and_then(|card| card.get("card")) .and_then(|data| data.as_object()) .and_then(|card| card.get("card_network")) .and_then(|network| network.as_str()) .map(|network| network.to_string()); + let error_details = self.error; + Ok(DieselPaymentAttemptNew { payment_id: self.payment_id, merchant_id: self.merchant_id, status: self.status, - error_message: self.error_message, - surcharge_amount: self.surcharge_amount, - tax_on_surcharge: self.tax_on_surcharge, + error_message: error_details + .as_ref() + .map(|details| details.message.clone()), + surcharge_amount: self.amount_details.surcharge_amount, + tax_on_surcharge: self.amount_details.tax_on_surcharge, payment_method_id: self.payment_method_id, - confirm: self.confirm, authentication_type: self.authentication_type, created_at: self.created_at, modified_at: self.modified_at, @@ -1699,28 +1849,33 @@ impl behaviour::Conversion for PaymentAttempt { cancellation_reason: self.cancellation_reason, browser_info: self.browser_info, payment_token: self.payment_token, - error_code: self.error_code, + error_code: error_details.as_ref().map(|details| details.code.clone()), connector_metadata: self.connector_metadata, payment_experience: self.payment_experience, payment_method_data: self.payment_method_data, preprocessing_step_id: self.preprocessing_step_id, - error_reason: self.error_reason, + error_reason: error_details + .as_ref() + .and_then(|details| details.reason.clone()), connector_response_reference_id: self.connector_response_reference_id, multiple_capture_count: self.multiple_capture_count, - amount_capturable: self.amount_capturable, + amount_capturable: self.amount_details.amount_capturable, updated_by: self.updated_by, merchant_connector_id: self.merchant_connector_id, authentication_data: self.authentication_data, encoded_data: self.encoded_data, - unified_code: self.unified_code, - unified_message: self.unified_message, - net_amount: Some(self.net_amount), + unified_code: error_details + .as_ref() + .and_then(|details| details.unified_code.clone()), + unified_message: error_details + .as_ref() + .and_then(|details| details.unified_message.clone()), + net_amount: self.amount_details.net_amount, external_three_ds_authentication_attempted: self .external_three_ds_authentication_attempted, authentication_connector: self.authentication_connector, authentication_id: self.authentication_id, fingerprint_id: self.fingerprint_id, - payment_method_billing_address_id: self.payment_method_billing_address_id, charge_id: self.charge_id, client_source: self.client_source, client_version: self.client_version, @@ -1728,9 +1883,15 @@ impl behaviour::Conversion for PaymentAttempt { profile_id: self.profile_id, organization_id: self.organization_id, card_network, - order_tax_amount: self.order_tax_amount, - shipping_cost: self.shipping_cost, - amount_to_capture: self.amount_to_capture, + order_tax_amount: self.amount_details.order_tax_amount, + shipping_cost: self.amount_details.shipping_cost, + amount_to_capture: self.amount_details.amount_to_capture, + payment_method_billing_address: self + .payment_method_billing_address + .map(Encryption::from), + payment_method_subtype: self.payment_method_subtype, + payment_method_type_v2: self.payment_method_type, + id: self.id, connector_mandate_detail: self.connector_mandate_detail, }) } @@ -1739,6 +1900,62 @@ impl behaviour::Conversion for PaymentAttempt { #[cfg(feature = "v2")] impl From for diesel_models::PaymentAttemptUpdateInternal { fn from(update: PaymentAttemptUpdate) -> Self { - todo!() + match update { + PaymentAttemptUpdate::ConfirmIntent { + status, + updated_by, + connector, + merchant_connector_id, + } => Self { + status: Some(status), + error_message: None, + modified_at: common_utils::date_time::now(), + browser_info: None, + error_code: None, + error_reason: None, + updated_by, + merchant_connector_id: Some(merchant_connector_id), + unified_code: None, + unified_message: None, + connector_payment_id: None, + connector: Some(connector), + }, + PaymentAttemptUpdate::ConfirmIntentError { + status, + error, + updated_by, + } => Self { + status: Some(status), + error_message: Some(error.message), + error_code: Some(error.code), + modified_at: common_utils::date_time::now(), + browser_info: None, + error_reason: error.reason, + updated_by, + merchant_connector_id: None, + unified_code: None, + unified_message: None, + connector_payment_id: None, + connector: None, + }, + PaymentAttemptUpdate::ConfirmIntentResponse { + status, + connector_payment_id, + updated_by, + } => Self { + status: Some(status), + error_message: None, + error_code: None, + modified_at: common_utils::date_time::now(), + browser_info: None, + error_reason: None, + updated_by, + merchant_connector_id: None, + unified_code: None, + unified_message: None, + connector_payment_id, + connector: None, + }, + } } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index ebd630beb6..f1e4de499e 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -266,76 +266,15 @@ pub enum PaymentIntentUpdate { }, } -// TODO: remove all enum variants and create new variants that should be used for v2 #[cfg(feature = "v2")] #[derive(Debug, Clone, Serialize)] pub enum PaymentIntentUpdate { - ResponseUpdate { - status: storage_enums::IntentStatus, - amount_captured: Option, - return_url: Option, - updated_by: String, - }, - MetadataUpdate { - metadata: pii::SecretSerdeValue, - updated_by: String, - }, - Update(Box), - PaymentCreateUpdate { - return_url: Option, - status: Option, - customer_id: Option, - shipping_address: Option>>, - billing_address: Option>>, - customer_details: Option>>, - updated_by: String, - }, - MerchantStatusUpdate { - status: storage_enums::IntentStatus, - shipping_address: Option>>, - billing_address: Option>>, - updated_by: String, - }, - PGStatusUpdate { + ConfirmIntent { status: storage_enums::IntentStatus, updated_by: String, }, - PaymentAttemptAndAttemptCountUpdate { - active_attempt_id: String, - attempt_count: i16, - updated_by: String, - }, - StatusAndAttemptUpdate { + ConfirmIntentPostUpdate { status: storage_enums::IntentStatus, - active_attempt_id: String, - attempt_count: i16, - updated_by: String, - }, - ApproveUpdate { - status: storage_enums::IntentStatus, - frm_merchant_decision: Option, - updated_by: String, - }, - RejectUpdate { - status: storage_enums::IntentStatus, - frm_merchant_decision: Option, - updated_by: String, - }, - SurchargeApplicableUpdate { - surcharge_applicable: bool, - updated_by: String, - }, - IncrementalAuthorizationAmountUpdate { - amount: MinorUnit, - }, - AuthorizationCountUpdate { - authorization_count: i32, - }, - CompleteAuthorizeUpdate { - shipping_address: Option>>, - }, - ManualUpdate { - status: Option, updated_by: String, }, } @@ -353,7 +292,7 @@ pub struct PaymentIntentUpdateInternal { pub off_session: Option, pub metadata: Option, pub modified_at: Option, - pub active_attempt_id: Option, + pub active_attempt_id: Option, pub description: Option, pub statement_descriptor: Option, pub order_details: Option>, @@ -417,181 +356,22 @@ pub struct PaymentIntentUpdateInternal { pub tax_details: Option, } +// TODO: convert directly to diesel_models::PaymentIntentUpdateInternal #[cfg(feature = "v2")] impl From for PaymentIntentUpdateInternal { fn from(payment_intent_update: PaymentIntentUpdate) -> Self { - todo!() - // match payment_intent_update { - // PaymentIntentUpdate::MetadataUpdate { - // metadata, - // updated_by, - // } => Self { - // metadata: Some(metadata), - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // PaymentIntentUpdate::Update(value) => Self { - // amount: Some(value.amount), - // currency: Some(value.currency), - // setup_future_usage: value.setup_future_usage, - // status: Some(value.status), - // customer_id: value.customer_id, - // return_url: value.return_url, - // description: value.description, - // statement_descriptor: value.statement_descriptor, - // order_details: value.order_details, - // metadata: value.metadata, - // payment_confirm_source: value.payment_confirm_source, - // updated_by: value.updated_by, - // session_expiry: value.session_expiry, - // request_external_three_ds_authentication: value - // .request_external_three_ds_authentication, - // frm_metadata: value.frm_metadata, - // customer_details: value.customer_details, - // billing_address: value.billing_address, - // merchant_order_reference_id: value.merchant_order_reference_id, - // shipping_address: value.shipping_address, - // is_payment_processor_token_flow: value.is_payment_processor_token_flow, - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::PaymentCreateUpdate { - // return_url, - // status, - // customer_id, - // shipping_address, - // billing_address, - // customer_details, - // updated_by, - // } => Self { - // return_url, - // status, - // customer_id, - // shipping_address, - // billing_address, - // customer_details, - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // PaymentIntentUpdate::PGStatusUpdate { status, updated_by } => Self { - // status: Some(status), - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // PaymentIntentUpdate::MerchantStatusUpdate { - // status, - // shipping_address, - // billing_address, - // updated_by, - // } => Self { - // status: Some(status), - // shipping_address, - // billing_address, - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // PaymentIntentUpdate::ResponseUpdate { - // // amount, - // // currency, - // status, - // amount_captured, - // // customer_id, - // return_url, - // updated_by, - // } => Self { - // // amount, - // // currency: Some(currency), - // status: Some(status), - // amount_captured, - // // customer_id, - // return_url, - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { - // active_attempt_id, - // attempt_count, - // updated_by, - // } => Self { - // active_attempt_id: Some(active_attempt_id), - // attempt_count: Some(attempt_count), - // updated_by, - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::StatusAndAttemptUpdate { - // status, - // active_attempt_id, - // attempt_count, - // updated_by, - // } => Self { - // status: Some(status), - // active_attempt_id: Some(active_attempt_id), - // attempt_count: Some(attempt_count), - // updated_by, - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::ApproveUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // } => Self { - // status: Some(status), - // frm_merchant_decision, - // updated_by, - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::RejectUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // } => Self { - // status: Some(status), - // frm_merchant_decision, - // updated_by, - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::SurchargeApplicableUpdate { - // surcharge_applicable, - // updated_by, - // } => Self { - // surcharge_applicable: Some(surcharge_applicable), - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => Self { - // amount: Some(amount), - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::AuthorizationCountUpdate { - // authorization_count, - // } => Self { - // authorization_count: Some(authorization_count), - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::CompleteAuthorizeUpdate { shipping_address } => Self { - // shipping_address, - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::ManualUpdate { status, updated_by } => Self { - // status, - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // } + match payment_intent_update { + PaymentIntentUpdate::ConfirmIntent { status, updated_by } => Self { + status: Some(status), + updated_by, + ..Default::default() + }, + PaymentIntentUpdate::ConfirmIntentPostUpdate { status, updated_by } => Self { + status: Some(status), + updated_by, + ..Default::default() + }, + } } } @@ -799,144 +579,14 @@ use diesel_models::{ #[cfg(feature = "v2")] impl From for DieselPaymentIntentUpdate { fn from(value: PaymentIntentUpdate) -> Self { - todo!() - // match value { - // PaymentIntentUpdate::ResponseUpdate { - // status, - // amount_captured, - // return_url, - // updated_by, - // } => Self::ResponseUpdate { - // status, - // amount_captured, - // return_url, - // updated_by, - // }, - // PaymentIntentUpdate::MetadataUpdate { - // metadata, - // updated_by, - // } => Self::MetadataUpdate { - // metadata, - // updated_by, - // }, - // PaymentIntentUpdate::Update(value) => { - // Self::Update(Box::new(DieselPaymentIntentUpdateFields { - // amount: value.amount, - // currency: value.currency, - // setup_future_usage: value.setup_future_usage, - // status: value.status, - // customer_id: value.customer_id, - // return_url: value.return_url, - // description: value.description, - // statement_descriptor: value.statement_descriptor, - // order_details: value.order_details, - // metadata: value.metadata, - // payment_confirm_source: value.payment_confirm_source, - // updated_by: value.updated_by, - // session_expiry: value.session_expiry, - // request_external_three_ds_authentication: value - // .request_external_three_ds_authentication, - // frm_metadata: value.frm_metadata, - // customer_details: value.customer_details.map(Encryption::from), - // billing_address: value.billing_address.map(Encryption::from), - // shipping_address: value.shipping_address.map(Encryption::from), - // merchant_order_reference_id: value.merchant_order_reference_id, - // is_payment_processor_token_flow: value.is_payment_processor_token_flow, - // })) - // } - // PaymentIntentUpdate::PaymentCreateUpdate { - // return_url, - // status, - // customer_id, - // shipping_address, - // billing_address, - // customer_details, - // updated_by, - // } => Self::PaymentCreateUpdate { - // return_url, - // status, - // customer_id, - // shipping_address: shipping_address.map(Encryption::from), - // billing_address: billing_address.map(Encryption::from), - // customer_details: customer_details.map(Encryption::from), - // updated_by, - // }, - // PaymentIntentUpdate::MerchantStatusUpdate { - // status, - // shipping_address, - // billing_address, - // updated_by, - // } => Self::MerchantStatusUpdate { - // status, - // shipping_address: shipping_address.map(Encryption::from), - // billing_address: billing_address.map(Encryption::from), - // updated_by, - // }, - // PaymentIntentUpdate::PGStatusUpdate { status, updated_by } => { - // Self::PGStatusUpdate { status, updated_by } - // } - // PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { - // active_attempt_id, - // attempt_count, - // updated_by, - // } => Self::PaymentAttemptAndAttemptCountUpdate { - // active_attempt_id, - // attempt_count, - // updated_by, - // }, - // PaymentIntentUpdate::StatusAndAttemptUpdate { - // status, - // active_attempt_id, - // attempt_count, - // updated_by, - // } => Self::StatusAndAttemptUpdate { - // status, - // active_attempt_id, - // attempt_count, - // updated_by, - // }, - // PaymentIntentUpdate::ApproveUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // } => Self::ApproveUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // }, - // PaymentIntentUpdate::RejectUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // } => Self::RejectUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // }, - // PaymentIntentUpdate::SurchargeApplicableUpdate { - // surcharge_applicable, - // updated_by, - // } => Self::SurchargeApplicableUpdate { - // surcharge_applicable: Some(surcharge_applicable), - // updated_by, - // }, - // PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => { - // Self::IncrementalAuthorizationAmountUpdate { amount } - // } - // PaymentIntentUpdate::AuthorizationCountUpdate { - // authorization_count, - // } => Self::AuthorizationCountUpdate { - // authorization_count, - // }, - // PaymentIntentUpdate::CompleteAuthorizeUpdate { shipping_address } => { - // Self::CompleteAuthorizeUpdate { - // shipping_address: shipping_address.map(Encryption::from), - // } - // } - // PaymentIntentUpdate::ManualUpdate { status, updated_by } => { - // Self::ManualUpdate { status, updated_by } - // } - // } + match value { + PaymentIntentUpdate::ConfirmIntent { status, updated_by } => { + Self::ConfirmIntent { status, updated_by } + } + PaymentIntentUpdate::ConfirmIntentPostUpdate { status, updated_by } => { + Self::ConfirmIntentPostUpdate { status, updated_by } + } + } } } @@ -1621,7 +1271,9 @@ impl behaviour::Conversion for PaymentIntent { skip_surcharge_calculation: super::SurchargeCalculationOverride::from( storage_model.surcharge_applicable, ), + amount_captured: storage_model.amount_captured, }; + let billing_address = data .billing .map(|billing| { diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index b41b42eaa2..ba76cc533c 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -9,7 +9,7 @@ use common_utils::{ use error_stack::ResultExt; use masking::{ExposeInterface, Secret}; -use crate::{payment_address::PaymentAddress, payment_method_data}; +use crate::{payment_address::PaymentAddress, payment_method_data, payments}; #[derive(Debug, Clone)] pub struct RouterData { @@ -83,7 +83,7 @@ pub struct RouterData { pub additional_merchant_data: Option, - pub header_payload: Option, + pub header_payload: Option, } // Different patterns of authentication. diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index fb4c0fd080..d29672058f 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -362,7 +362,7 @@ pub struct PaymentsPostProcessingData { pub connector_transaction_id: Option, pub country: Option, pub connector_meta_data: Option, - pub header_payload: Option, + pub header_payload: Option, } impl TryFrom> diff --git a/crates/hyperswitch_interfaces/Cargo.toml b/crates/hyperswitch_interfaces/Cargo.toml index 7ab885de3a..99a06ff2e3 100644 --- a/crates/hyperswitch_interfaces/Cargo.toml +++ b/crates/hyperswitch_interfaces/Cargo.toml @@ -9,7 +9,7 @@ license.workspace = true [features] default = ["dummy_connector", "frm", "payouts"] dummy_connector = [] -v1 = ["hyperswitch_domain_models/v1", "api_models/v1"] +v1 = ["hyperswitch_domain_models/v1", "api_models/v1", "common_utils/v1"] payouts = ["hyperswitch_domain_models/payouts"] frm = ["hyperswitch_domain_models/frm"] diff --git a/crates/kgraph_utils/Cargo.toml b/crates/kgraph_utils/Cargo.toml index 0b7b580e64..0883e14869 100644 --- a/crates/kgraph_utils/Cargo.toml +++ b/crates/kgraph_utils/Cargo.toml @@ -8,8 +8,8 @@ license.workspace = true [features] dummy_connector = ["api_models/dummy_connector", "euclid/dummy_connector"] -v1 = ["api_models/v1"] -v2 = ["api_models/v2"] +v1 = ["api_models/v1", "common_utils/v1"] +v2 = ["api_models/v2", "common_utils/v2"] [dependencies] api_models = { version = "0.1.0", path = "../api_models", package = "api_models" } diff --git a/crates/openapi/Cargo.toml b/crates/openapi/Cargo.toml index dac22071c3..26707479b0 100644 --- a/crates/openapi/Cargo.toml +++ b/crates/openapi/Cargo.toml @@ -16,8 +16,8 @@ common_utils = { version = "0.1.0", path = "../common_utils", features = ["logs" router_env = { version = "0.1.0", path = "../router_env" } [features] -v2 = ["api_models/v2", "api_models/customer_v2"] -v1 = ["api_models/v1"] +v2 = ["api_models/v2", "api_models/customer_v2", "common_utils/v2"] +v1 = ["api_models/v1", "common_utils/v1"] [lints] workspace = true diff --git a/crates/openapi/src/main.rs b/crates/openapi/src/main.rs index 9d58c8b7c0..975ff33107 100644 --- a/crates/openapi/src/main.rs +++ b/crates/openapi/src/main.rs @@ -6,6 +6,9 @@ mod routes; #[allow(clippy::print_stdout)] // Using a logger is not necessary here fn main() { + #[cfg(all(feature = "v1", feature = "v2"))] + compile_error!("features v1 and v2 are mutually exclusive, please enable only one of them"); + #[cfg(feature = "v1")] let relative_file_path = "api-reference/openapi_spec.json"; diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 7c961b2b2b..0a19c5a63c 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -123,6 +123,7 @@ Never share your secret api keys. Keep them guarded and secure. //Routes for payments routes::payments::payments_create_intent, + routes::payments::payments_confirm_intent, //Routes for refunds routes::refunds::refunds_create, @@ -130,6 +131,7 @@ Never share your secret api keys. Keep them guarded and secure. components(schemas( common_utils::types::MinorUnit, common_utils::types::TimeRange, + common_utils::types::BrowserInformation, common_utils::link_utils::GenericLinkUiConfig, common_utils::link_utils::EnabledPaymentMethod, common_utils::payout_method_utils::AdditionalPayoutMethodData, @@ -315,10 +317,6 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::CardRedirectData, api_models::payments::CardToken, api_models::payments::CustomerAcceptance, - api_models::payments::PaymentsRequest, - api_models::payments::PaymentsCreateRequest, - api_models::payments::PaymentsUpdateRequest, - api_models::payments::PaymentsConfirmRequest, api_models::payments::PaymentsResponse, api_models::payments::PaymentsCreateResponseOpenApi, api_models::payments::PaymentRetrieveBody, @@ -415,13 +413,15 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ThreeDsCompletionIndicator, api_models::payments::MifinityData, api_models::enums::TransactionStatus, - api_models::payments::BrowserInformation, api_models::payments::PaymentCreatePaymentLinkConfig, api_models::payments::ThreeDsData, api_models::payments::ThreeDsMethodData, api_models::payments::PollConfigResponse, api_models::payments::ExternalAuthenticationDetailsResponse, api_models::payments::ExtendedCardInfo, + api_models::payments::PaymentsConfirmIntentRequest, + api_models::payments::PaymentsConfirmIntentResponse, + api_models::payments::AmountDetailsResponse, api_models::payment_methods::RequiredFieldInfo, api_models::payment_methods::DefaultPaymentMethod, api_models::payment_methods::MaskedBankDetails, @@ -584,6 +584,9 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentsDynamicTaxCalculationRequest, api_models::payments::PaymentsDynamicTaxCalculationResponse, api_models::payments::DisplayAmountOnSdk, + api_models::payments::ErrorDetails, + common_utils::types::BrowserInformation, + api_models::payments::ConfirmIntentAmountDetailsResponse, )), modifiers(&SecurityAddon) )] diff --git a/crates/openapi/src/routes/payments.rs b/crates/openapi/src/routes/payments.rs index b6a81efda0..3429de60ba 100644 --- a/crates/openapi/src/routes/payments.rs +++ b/crates/openapi/src/routes/payments.rs @@ -405,7 +405,7 @@ pub fn payments_connector_session() {} /// Creates a session object or a session token for wallets like Apple Pay, Google Pay, etc. These tokens are used by Hyperswitch's SDK to initiate these wallets' SDK. #[utoipa::path( post, - path = "/v2/payments/{payment_id}/create_external_sdk_tokens", + path = "/v2/payments/{payment_id}/create-external-sdk-tokens", request_body=PaymentsSessionRequest, responses( (status = 200, description = "Payment session object created or session token was retrieved from wallets", body = PaymentsSessionResponse), @@ -629,3 +629,43 @@ pub fn payments_post_session_tokens() {} )] #[cfg(feature = "v2")] pub fn payments_create_intent() {} + +/// Payments - Confirm Intent +/// +/// **Confirms a payment intent object with the payment method data** +/// +/// . +#[utoipa::path( + post, + path = "/v2/payments/{id}/confirm-intent", + request_body( + content = PaymentsConfirmIntentRequest, + examples( + ( + "Confirm the payment intent with card details" = ( + value = json!({ + "payment_method_type": "card", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "joseph Doe", + "card_cvc": "123" + } + }, + }) + ) + ), + ), + ), + responses( + (status = 200, description = "Payment created", body = PaymentsConfirmIntentResponse), + (status = 400, description = "Missing Mandatory fields") + ), + tag = "Payments", + operation_id = "Confirm Payment Intent", + security(("publisable_key" = [])), +)] +#[cfg(feature = "v2")] +pub fn payments_confirm_intent() {} diff --git a/crates/pm_auth/Cargo.toml b/crates/pm_auth/Cargo.toml index df8136ba83..3923d44b7f 100644 --- a/crates/pm_auth/Cargo.toml +++ b/crates/pm_auth/Cargo.toml @@ -8,7 +8,7 @@ readme = "README.md" license.workspace = true [features] -v1 = ["api_models/v1"] +v1 = ["api_models/v1", "common_utils/v1"] [dependencies] # First party crates diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 849a3beaaf..b794443a04 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -34,7 +34,7 @@ payout_retry = ["payouts"] recon = ["email", "api_models/recon"] retry = [] v2 = ["customer_v2", "payment_methods_v2", "common_default", "api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "storage_impl/v2", "kgraph_utils/v2", "common_utils/v2"] -v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "hyperswitch_interfaces/v1", "kgraph_utils/v1"] +v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "hyperswitch_interfaces/v1", "kgraph_utils/v1", "common_utils/v1"] customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2", "hyperswitch_domain_models/customer_v2", "storage_impl/customer_v2"] payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2", "hyperswitch_domain_models/payment_methods_v2", "storage_impl/payment_methods_v2", "common_utils/payment_methods_v2"] dynamic_routing = ["external_services/dynamic_routing", "storage_impl/dynamic_routing"] diff --git a/crates/router/src/compatibility/stripe/payment_intents.rs b/crates/router/src/compatibility/stripe/payment_intents.rs index 901ca3c11c..e447e9b953 100644 --- a/crates/router/src/compatibility/stripe/payment_intents.rs +++ b/crates/router/src/compatibility/stripe/payment_intents.rs @@ -91,7 +91,7 @@ pub async fn payment_intents_create( api::AuthFlow::Merchant, payments::CallConnectorAction::Trigger, eligible_connectors, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -161,7 +161,7 @@ pub async fn payment_intents_retrieve( auth_flow, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), ) }, &*auth_type, @@ -239,7 +239,7 @@ pub async fn payment_intents_retrieve_with_gateway_creds( api::AuthFlow::Merchant, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), ) }, &*auth_type, @@ -315,7 +315,7 @@ pub async fn payment_intents_update( auth_flow, payments::CallConnectorAction::Trigger, eligible_connectors, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), ) }, &*auth_type, @@ -400,7 +400,7 @@ pub async fn payment_intents_confirm( auth_flow, payments::CallConnectorAction::Trigger, eligible_connectors, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), ) }, &*auth_type, @@ -471,7 +471,7 @@ pub async fn payment_intents_capture( api::AuthFlow::Merchant, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -546,7 +546,7 @@ pub async fn payment_intents_cancel( auth_flow, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), ) }, &*auth_type, diff --git a/crates/router/src/compatibility/stripe/setup_intents.rs b/crates/router/src/compatibility/stripe/setup_intents.rs index e5e68fb4a8..66b43e4ebe 100644 --- a/crates/router/src/compatibility/stripe/setup_intents.rs +++ b/crates/router/src/compatibility/stripe/setup_intents.rs @@ -77,7 +77,7 @@ pub async fn setup_intents_create( api::AuthFlow::Merchant, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -147,7 +147,7 @@ pub async fn setup_intents_retrieve( auth_flow, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), ) }, &*auth_type, @@ -223,7 +223,7 @@ pub async fn setup_intents_update( auth_flow, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), ) }, &*auth_type, @@ -300,7 +300,7 @@ pub async fn setup_intents_confirm( auth_flow, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), ) }, &*auth_type, diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index ac99fb4b52..9e9e43734d 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -2435,31 +2435,13 @@ impl FraudCheckRecordReturnRequest for fraud_check::FraudCheckRecordReturnData { } } -pub trait AccessPaymentAttemptInfo { - fn get_browser_info( - &self, - ) -> Result, error_stack::Report>; -} - -impl AccessPaymentAttemptInfo for PaymentAttempt { - fn get_browser_info( - &self, - ) -> Result, error_stack::Report> { - self.browser_info - .clone() - .map(|b| b.parse_value("BrowserInformation")) - .transpose() - .change_context(ApiErrorResponse::InvalidDataValue { - field_name: "browser_info", - }) - } -} - +#[cfg(feature = "v1")] pub trait PaymentsAttemptData { fn get_browser_info(&self) -> Result>; } +#[cfg(feature = "v1")] impl PaymentsAttemptData for PaymentAttempt { fn get_browser_info( &self, diff --git a/crates/router/src/core/fraud_check/flows/checkout_flow.rs b/crates/router/src/core/fraud_check/flows/checkout_flow.rs index 64c8694a5b..87e76b2c5f 100644 --- a/crates/router/src/core/fraud_check/flows/checkout_flow.rs +++ b/crates/router/src/core/fraud_check/flows/checkout_flow.rs @@ -5,7 +5,6 @@ use masking::ExposeInterface; use super::{ConstructFlowSpecificData, FeatureFrm}; use crate::{ - connector::utils::PaymentsAttemptData, core::{ errors::{ConnectorErrorExt, RouterResult}, fraud_check::types::FrmData, @@ -39,7 +38,7 @@ impl ConstructFlowSpecificData, _merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - _header_payload: Option, + _header_payload: Option, ) -> RouterResult> { todo!() @@ -55,9 +54,11 @@ impl ConstructFlowSpecificData, merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult> { + use crate::connector::utils::PaymentsAttemptData; + let status = storage_enums::AttemptStatus::Pending; let auth_type: ConnectorAuthType = merchant_connector_account diff --git a/crates/router/src/core/fraud_check/flows/record_return.rs b/crates/router/src/core/fraud_check/flows/record_return.rs index 1c55bffeb8..f80f604daa 100644 --- a/crates/router/src/core/fraud_check/flows/record_return.rs +++ b/crates/router/src/core/fraud_check/flows/record_return.rs @@ -36,7 +36,7 @@ impl ConstructFlowSpecificData, _merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - _header_payload: Option, + _header_payload: Option, ) -> RouterResult> { todo!() @@ -52,7 +52,7 @@ impl ConstructFlowSpecificData, merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult> { let status = storage_enums::AttemptStatus::Pending; diff --git a/crates/router/src/core/fraud_check/flows/sale_flow.rs b/crates/router/src/core/fraud_check/flows/sale_flow.rs index 9a1899df42..23c0e05fd5 100644 --- a/crates/router/src/core/fraud_check/flows/sale_flow.rs +++ b/crates/router/src/core/fraud_check/flows/sale_flow.rs @@ -34,7 +34,7 @@ impl ConstructFlowSpecificData, _merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - _header_payload: Option, + _header_payload: Option, ) -> RouterResult> { todo!() } @@ -49,7 +49,7 @@ impl ConstructFlowSpecificData, merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult> { let status = storage_enums::AttemptStatus::Pending; diff --git a/crates/router/src/core/fraud_check/flows/transaction_flow.rs b/crates/router/src/core/fraud_check/flows/transaction_flow.rs index 68db6423e0..d1c26a32f9 100644 --- a/crates/router/src/core/fraud_check/flows/transaction_flow.rs +++ b/crates/router/src/core/fraud_check/flows/transaction_flow.rs @@ -39,7 +39,7 @@ impl _customer: &Option, _merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - _header_payload: Option, + _header_payload: Option, ) -> RouterResult< RouterData, > { @@ -56,7 +56,7 @@ impl customer: &Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult< RouterData, > { diff --git a/crates/router/src/core/fraud_check/operation/fraud_check_post.rs b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs index 8a9a91fb1e..deb4a3621f 100644 --- a/crates/router/src/core/fraud_check/operation/fraud_check_post.rs +++ b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs @@ -1,9 +1,8 @@ -use api_models::payments::HeaderPayload; use async_trait::async_trait; use common_enums::{CaptureMethod, FrmSuggestion}; use common_utils::ext_traits::Encode; use hyperswitch_domain_models::payments::{ - payment_attempt::PaymentAttemptUpdate, payment_intent::PaymentIntentUpdate, + payment_attempt::PaymentAttemptUpdate, payment_intent::PaymentIntentUpdate, HeaderPayload, }; use router_env::{instrument, logger, tracing}; diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 8419a600f3..da1c70ebcf 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3774,6 +3774,7 @@ async fn validate_payment_method_and_client_secret( Ok(()) } +#[cfg(feature = "v1")] #[allow(clippy::too_many_arguments)] pub async fn call_surcharge_decision_management( state: routes::SessionState, @@ -3834,6 +3835,7 @@ pub async fn call_surcharge_decision_management( Ok(merchant_sucharge_configs) } +#[cfg(feature = "v1")] pub async fn call_surcharge_decision_management_for_saved_card( state: &routes::SessionState, merchant_account: &domain::MerchantAccount, diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index 7de4f15adb..ca1cdf0676 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -375,7 +375,9 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( payment_intent: &storage::PaymentIntent, customer_payment_method_list: &mut [api_models::payment_methods::CustomerPaymentMethod], ) -> ConditionalConfigResult { - let mut surcharge_metadata = types::SurchargeMetadata::new(payment_attempt.id.clone()); + // let mut surcharge_metadata = types::SurchargeMetadata::new(payment_attempt.id.clone()); + let mut surcharge_metadata = todo!(); + let surcharge_source = match ( payment_attempt.get_surcharge_details(), algorithm_ref.surcharge_config_algo_id, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index fd45c475b6..2218197743 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -22,7 +22,7 @@ use api_models::admin::MerchantConnectorInfo; use api_models::{ self, enums, mandates::RecurringDetails, - payments::{self as payments_api, HeaderPayload}, + payments::{self as payments_api}, }; pub use common_enums::enums::CallConnectorAction; use common_utils::{ @@ -36,10 +36,11 @@ use events::EventInfo; use futures::future::join_all; use helpers::{decrypt_paze_token, ApplePayData}; #[cfg(feature = "v2")] -use hyperswitch_domain_models::payments::PaymentIntentData; +use hyperswitch_domain_models::payments::{PaymentConfirmData, PaymentIntentData}; pub use hyperswitch_domain_models::{ mandates::{CustomerAcceptance, MandateData}, payment_address::PaymentAddress, + payments::HeaderPayload, router_data::{PaymentMethodToken, RouterData}, router_request_types::CustomerDetails, }; @@ -105,6 +106,140 @@ use crate::{ types::{api::authentication, BrowserInformation}, }; +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments, clippy::type_complexity)] +#[instrument(skip_all, fields(payment_id, merchant_id))] +pub async fn payments_operation_core( + state: &SessionState, + req_state: ReqState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + profile: domain::Profile, + operation: Op, + req: Req, + payment_id: id_type::GlobalPaymentId, + call_connector_action: CallConnectorAction, + header_payload: HeaderPayload, +) -> RouterResult<(D, Req, Option, Option, Option)> +where + F: Send + Clone + Sync, + Req: Send + Sync, + Op: Operation + Send + Sync, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + + // To create connector flow specific interface data + D: ConstructFlowSpecificData, + RouterData: Feature, + + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, + + // To perform router related operation for PaymentResponse + PaymentResponse: Operation, + FData: Send + Sync + Clone, +{ + let operation: BoxedOperation<'_, F, Req, D> = Box::new(operation); + + let (operation, validate_result) = operation + .to_validate_request()? + .validate_request(&req, &merchant_account)?; + + let operations::GetTrackerResponse { + operation, + mut payment_data, + } = operation + .to_get_tracker()? + .get_trackers( + state, + &payment_id, + &req, + &merchant_account, + &profile, + &key_store, + &header_payload, + ) + .await?; + + let (_operation, customer) = operation + .to_domain()? + .get_customer_details( + state, + &mut payment_data, + &key_store, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound) + .attach_printable("Failed while fetching/creating customer")?; + + let connector = get_connector_choice( + &operation, + state, + &req, + &merchant_account, + &profile, + &key_store, + &mut payment_data, + None, + None, + ) + .await?; + + // TODO: do not use if let + let payment_data = if let Some(connector_call_type) = connector { + match connector_call_type { + ConnectorCallType::PreDetermined(connector_data) => { + let (router_data, mca) = call_connector_service( + state, + req_state.clone(), + &merchant_account, + &key_store, + connector_data.clone(), + &operation, + &mut payment_data, + &customer, + call_connector_action.clone(), + &validate_result, + None, + header_payload.clone(), + #[cfg(feature = "frm")] + None, + #[cfg(not(feature = "frm"))] + None, + &profile, + false, + ) + .await?; + + let payments_response_operation = Box::new(PaymentResponse); + + payments_response_operation + .to_post_update_tracker()? + .update_tracker( + state, + payment_data, + router_data, + &key_store, + merchant_account.storage_scheme, + &header_payload.locale, + #[cfg(all(feature = "dynamic_routing", feature = "v1"))] + routable_connectors, + #[cfg(all(feature = "dynamic_routing", feature = "v1"))] + &business_profile, + ) + .await? + } + ConnectorCallType::Retryable(vec) => todo!(), + ConnectorCallType::SessionMultiple(vec) => todo!(), + } + } else { + todo!() + }; + + Ok((payment_data, req, customer, None, None)) +} + #[cfg(feature = "v1")] #[allow(clippy::too_many_arguments, clippy::type_complexity)] #[instrument(skip_all, fields(payment_id, merchant_id))] @@ -366,7 +501,6 @@ where .to_post_update_tracker()? .update_tracker( state, - &validate_result.payment_id, payment_data, router_data, &key_store, @@ -504,7 +638,6 @@ where .to_post_update_tracker()? .update_tracker( state, - &validate_result.payment_id, payment_data, router_data, &key_store, @@ -822,7 +955,6 @@ where .to_post_update_tracker()? .update_tracker( state, - &validate_result.payment_id, payment_data, router_data, &key_store, @@ -885,7 +1017,6 @@ pub async fn payments_intent_operation_core( key_store: domain::MerchantKeyStore, operation: Op, req: Req, - auth_flow: services::AuthFlow, header_payload: HeaderPayload, ) -> RouterResult<(D, Req, Option)> where @@ -918,7 +1049,6 @@ where &merchant_account, &profile, &key_store, - auth_flow, &header_payload, ) .await?; @@ -1319,7 +1449,6 @@ pub async fn payments_intent_core( key_store: domain::MerchantKeyStore, operation: Op, req: Req, - auth_flow: services::AuthFlow, header_payload: HeaderPayload, ) -> RouterResponse where @@ -1337,7 +1466,6 @@ where key_store, operation.clone(), req, - auth_flow, header_payload.clone(), ) .await?; @@ -1345,7 +1473,6 @@ where Res::generate_response( payment_data, customer, - auth_flow, &state.base_url, operation, &state.conf.connector_request_reference_id_config, @@ -1355,6 +1482,66 @@ where ) } +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +pub async fn payments_core( + state: SessionState, + req_state: ReqState, + merchant_account: domain::MerchantAccount, + profile: domain::Profile, + key_store: domain::MerchantKeyStore, + operation: Op, + req: Req, + payment_id: id_type::GlobalPaymentId, + call_connector_action: CallConnectorAction, + header_payload: HeaderPayload, +) -> RouterResponse +where + F: Send + Clone + Sync, + Req: Send + Sync, + FData: Send + Sync + Clone, + Op: Operation + Send + Sync + Clone, + Req: Debug, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + Res: transformers::ToResponse, + // To create connector flow specific interface data + D: ConstructFlowSpecificData, + RouterData: Feature, + + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, + + // To perform router related operation for PaymentResponse + PaymentResponse: Operation, +{ + let (payment_data, _req, customer, connector_http_status_code, external_latency) = + payments_operation_core::<_, _, _, _, _>( + &state, + req_state, + merchant_account, + key_store, + profile, + operation.clone(), + req, + payment_id, + call_connector_action, + header_payload.clone(), + ) + .await?; + + Res::generate_response( + payment_data, + customer, + &state.base_url, + operation, + &state.conf.connector_request_reference_id_config, + connector_http_status_code, + external_latency, + header_payload.x_hs_latency, + ) +} + fn is_start_pay(operation: &Op) -> bool { format!("{operation:?}").eq("PaymentStart") } @@ -2679,32 +2866,6 @@ where Ok(payment_data) } -#[cfg(feature = "v2")] -pub async fn call_create_connector_customer_if_required( - _state: &SessionState, - _customer: &Option, - _merchant_account: &domain::MerchantAccount, - _key_store: &domain::MerchantKeyStore, - _merchant_connector_account: &helpers::MerchantConnectorAccountType, - _payment_data: &mut D, -) -> RouterResult> -where - F: Send + Clone + Sync, - Req: Send + Sync, - - // To create connector flow specific interface data - D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, - D: ConstructFlowSpecificData, - RouterData: Feature + Send, - - // To construct connector flow specific api - dyn api::Connector: - services::api::ConnectorIntegration, -{ - todo!() -} - -#[cfg(feature = "v1")] pub async fn call_create_connector_customer_if_required( state: &SessionState, customer: &Option, @@ -3338,7 +3499,7 @@ pub enum TokenizationAction { pub async fn get_connector_tokenization_action_when_confirm_true( _state: &SessionState, _operation: &BoxedOperation<'_, F, Req, D>, - _payment_data: &mut D, + payment_data: &mut D, _validate_result: &operations::ValidateResult, _merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_key_store: &domain::MerchantKeyStore, @@ -3349,7 +3510,9 @@ where F: Send + Clone, D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, { - todo!() + // TODO: Implement this function + let payment_data = payment_data.to_owned(); + Ok((payment_data, TokenizationAction::SkipConnectorTokenization)) } #[cfg(feature = "v1")] @@ -4060,6 +4223,7 @@ pub async fn get_aggregates_for_payments( )) } +#[cfg(feature = "v1")] pub async fn add_process_sync_task( db: &dyn StorageInterface, payment_attempt: &storage::PaymentAttempt, @@ -4094,6 +4258,16 @@ pub async fn add_process_sync_task( Ok(()) } +#[cfg(feature = "v2")] +pub async fn reset_process_sync_task( + db: &dyn StorageInterface, + payment_attempt: &storage::PaymentAttempt, + schedule_time: time::PrimitiveDateTime, +) -> Result<(), errors::ProcessTrackerError> { + todo!() +} + +#[cfg(feature = "v1")] pub async fn reset_process_sync_task( db: &dyn StorageInterface, payment_attempt: &storage::PaymentAttempt, @@ -4136,6 +4310,53 @@ where Ok(()) } +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +pub async fn get_connector_choice( + operation: &BoxedOperation<'_, F, Req, D>, + state: &SessionState, + req: &Req, + merchant_account: &domain::MerchantAccount, + business_profile: &domain::Profile, + key_store: &domain::MerchantKeyStore, + payment_data: &mut D, + eligible_connectors: Option>, + mandate_type: Option, +) -> RouterResult> +where + F: Send + Clone, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, +{ + // Currently rule based routing and other routing features are not implemented for v2 + // So we are using the default fallback connector for now + // Eligibility analysis is not yet implemented + + let fallback_config = super::admin::ProfileWrapper::new(business_profile.clone()) + .get_default_fallback_list_of_connector_under_profile() + .change_context(errors::RoutingError::FallbackConfigFetchFailed) + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + let first_chosen_connector = fallback_config + .first() + .ok_or(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration)?; + + let connector_name = first_chosen_connector.connector.to_string(); + let merchant_connector_id = first_chosen_connector + .merchant_connector_id + .clone() + .get_required_value("merchant_connector_id")?; + + payment_data.set_connector_in_payment_attempt(Some(connector_name.to_string())); + let connector_data = api::ConnectorData::get_connector_by_name( + &state.conf.connectors, + &connector_name, + api::GetToken::Connector, + Some(merchant_connector_id), + )?; + + Ok(Some(ConnectorCallType::PreDetermined(connector_data))) +} + #[cfg(feature = "v1")] #[allow(clippy::too_many_arguments)] pub async fn get_connector_choice( @@ -6096,6 +6317,7 @@ impl OperationSessionGetters for PaymentData { } } +#[cfg(feature = "v1")] impl OperationSessionSetters for PaymentData { // Setters Implementation fn set_payment_intent(&mut self, payment_intent: storage::PaymentIntent) { @@ -6367,7 +6589,7 @@ impl OperationSessionSetters for PaymentIntentData { fn set_merchant_connector_id_in_attempt( &mut self, - _merchant_connector_id: Option, + merchant_connector_id: Option, ) { todo!() } @@ -6410,3 +6632,213 @@ impl OperationSessionSetters for PaymentIntentData { todo!() } } + +#[cfg(feature = "v2")] +impl OperationSessionGetters for PaymentConfirmData { + fn get_payment_attempt(&self) -> &storage::PaymentAttempt { + &self.payment_attempt + } + + fn get_payment_intent(&self) -> &storage::PaymentIntent { + &self.payment_intent + } + + fn get_payment_method_info(&self) -> Option<&domain::PaymentMethod> { + todo!() + } + + fn get_mandate_id(&self) -> Option<&payments_api::MandateIds> { + todo!() + } + + // what is this address find out and not required remove this + fn get_address(&self) -> &PaymentAddress { + todo!() + } + + fn get_creds_identifier(&self) -> Option<&str> { + None + } + + fn get_token(&self) -> Option<&str> { + todo!() + } + + fn get_multiple_capture_data(&self) -> Option<&types::MultipleCaptureData> { + todo!() + } + + fn get_payment_link_data(&self) -> Option { + todo!() + } + + fn get_ephemeral_key(&self) -> Option { + todo!() + } + + fn get_setup_mandate(&self) -> Option<&MandateData> { + todo!() + } + + fn get_poll_config(&self) -> Option { + todo!() + } + + fn get_authentication(&self) -> Option<&storage::Authentication> { + todo!() + } + + fn get_frm_message(&self) -> Option { + todo!() + } + + fn get_refunds(&self) -> Vec { + todo!() + } + + fn get_disputes(&self) -> Vec { + todo!() + } + + fn get_authorizations(&self) -> Vec { + todo!() + } + + fn get_attempts(&self) -> Option> { + todo!() + } + + fn get_recurring_details(&self) -> Option<&RecurringDetails> { + todo!() + } + + fn get_payment_intent_profile_id(&self) -> Option<&id_type::ProfileId> { + Some(&self.payment_intent.profile_id) + } + + fn get_currency(&self) -> storage_enums::Currency { + self.payment_intent.amount_details.currency + } + + fn get_amount(&self) -> api::Amount { + todo!() + } + + fn get_payment_attempt_connector(&self) -> Option<&str> { + todo!() + } + + fn get_billing_address(&self) -> Option { + todo!() + } + + fn get_payment_method_data(&self) -> Option<&domain::PaymentMethodData> { + self.payment_method_data.as_ref() + } + + fn get_sessions_token(&self) -> Vec { + todo!() + } + + fn get_token_data(&self) -> Option<&storage::PaymentTokenData> { + todo!() + } + + fn get_mandate_connector(&self) -> Option<&MandateConnectorDetails> { + todo!() + } + + fn get_force_sync(&self) -> Option { + todo!() + } + + fn get_capture_method(&self) -> Option { + todo!() + } +} + +#[cfg(feature = "v2")] +impl OperationSessionSetters for PaymentConfirmData { + // Setters Implementation + fn set_payment_intent(&mut self, payment_intent: storage::PaymentIntent) { + self.payment_intent = payment_intent; + } + + fn set_payment_attempt(&mut self, payment_attempt: storage::PaymentAttempt) { + self.payment_attempt = payment_attempt; + } + + fn set_payment_method_data(&mut self, _payment_method_data: Option) { + todo!() + } + + fn set_payment_method_id_in_attempt(&mut self, _payment_method_id: Option) { + todo!() + } + + fn set_email_if_not_present(&mut self, _email: pii::Email) { + todo!() + } + + fn set_pm_token(&mut self, _token: String) { + todo!() + } + + fn set_connector_customer_id(&mut self, _customer_id: Option) { + // TODO: handle this case. Should we add connector_customer_id in paymentConfirmData? + } + + fn push_sessions_token(&mut self, _token: api::SessionToken) { + todo!() + } + + fn set_surcharge_details(&mut self, _surcharge_details: Option) { + todo!() + } + + #[track_caller] + fn set_merchant_connector_id_in_attempt( + &mut self, + merchant_connector_id: Option, + ) { + self.payment_attempt.merchant_connector_id = merchant_connector_id; + } + + fn set_frm_message(&mut self, _frm_message: FraudCheck) { + todo!() + } + + fn set_payment_intent_status(&mut self, status: storage_enums::IntentStatus) { + self.payment_intent.status = status; + } + + fn set_authentication_type_in_attempt( + &mut self, + _authentication_type: Option, + ) { + todo!() + } + + fn set_recurring_mandate_payment_data( + &mut self, + _recurring_mandate_payment_data: + hyperswitch_domain_models::router_data::RecurringMandatePaymentData, + ) { + todo!() + } + + fn set_mandate_id(&mut self, _mandate_id: api_models::payments::MandateIds) { + todo!() + } + + fn set_setup_future_usage_in_payment_intent( + &mut self, + setup_future_usage: storage_enums::FutureUsage, + ) { + self.payment_intent.setup_future_usage = setup_future_usage; + } + + fn set_connector_in_payment_attempt(&mut self, connector: Option) { + self.payment_attempt.connector = connector; + } +} diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 3078cf7225..8c84f7ab9a 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -39,7 +39,7 @@ pub trait ConstructFlowSpecificData { customer: &Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult>; #[cfg(all(feature = "v2", feature = "customer_v2"))] @@ -52,7 +52,7 @@ pub trait ConstructFlowSpecificData { _customer: &Option, _merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - _header_payload: Option, + _header_payload: Option, ) -> RouterResult>; async fn get_merchant_recipient_data<'a>( @@ -75,7 +75,7 @@ pub trait Feature { call_connector_action: payments::CallConnectorAction, connector_request: Option, business_profile: &domain::Profile, - header_payload: api_models::payments::HeaderPayload, + header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult where Self: Sized, diff --git a/crates/router/src/core/payments/flows/approve_flow.rs b/crates/router/src/core/payments/flows/approve_flow.rs index c91de9eb3a..98d204d526 100644 --- a/crates/router/src/core/payments/flows/approve_flow.rs +++ b/crates/router/src/core/payments/flows/approve_flow.rs @@ -25,7 +25,7 @@ impl customer: &Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult { Box::pin(transformers::construct_payment_router_data::< api::Approve, @@ -67,7 +67,7 @@ impl Feature _call_connector_action: payments::CallConnectorAction, _connector_request: Option, _business_profile: &domain::Profile, - _header_payload: api_models::payments::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { Err(ApiErrorResponse::NotImplemented { message: NotImplementedMessage::Reason("Flow not supported".to_string()), diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 03b2a3153e..09a6fb5192 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -1,5 +1,7 @@ use async_trait::async_trait; use common_enums as enums; +#[cfg(feature = "v2")] +use hyperswitch_domain_models::payments::PaymentConfirmData; use router_env::metrics::add_attributes; // use router_env::tracing::Instrument; @@ -20,6 +22,77 @@ use crate::{ utils::OptionExt, }; +#[cfg(feature = "v2")] +#[async_trait] +impl + ConstructFlowSpecificData< + api::Authorize, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + > for PaymentConfirmData +{ + async fn construct_router_data<'a>( + &self, + state: &SessionState, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + customer: &Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, + merchant_recipient_data: Option, + header_payload: Option, + ) -> RouterResult< + types::RouterData< + api::Authorize, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + > { + Box::pin(transformers::construct_payment_router_data_for_authorize( + state, + self.clone(), + connector_id, + merchant_account, + key_store, + customer, + merchant_connector_account, + merchant_recipient_data, + header_payload, + )) + .await + } + + async fn get_merchant_recipient_data<'a>( + &self, + state: &SessionState, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + merchant_connector_account: &helpers::MerchantConnectorAccountType, + connector: &api::ConnectorData, + ) -> RouterResult> { + let payment_method = &self + .payment_attempt + .get_payment_method() + .get_required_value("PaymentMethod")?; + + let data = if *payment_method == enums::PaymentMethod::OpenBanking { + payments::get_merchant_bank_data_for_open_banking_connectors( + merchant_connector_account, + key_store, + connector, + state, + merchant_account, + ) + .await? + } else { + None + }; + + Ok(data) + } +} + +#[cfg(feature = "v1")] #[async_trait] impl ConstructFlowSpecificData< @@ -37,7 +110,7 @@ impl customer: &Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult< types::RouterData< api::Authorize, @@ -91,6 +164,7 @@ impl Ok(data) } } + #[async_trait] impl Feature for types::PaymentsAuthorizeRouterData { async fn decide_flows<'a>( @@ -100,7 +174,7 @@ impl Feature for types::PaymentsAu call_connector_action: payments::CallConnectorAction, connector_request: Option, _business_profile: &domain::Profile, - _header_payload: api_models::payments::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< api::Authorize, diff --git a/crates/router/src/core/payments/flows/cancel_flow.rs b/crates/router/src/core/payments/flows/cancel_flow.rs index 1f212cc631..9c5bc84a62 100644 --- a/crates/router/src/core/payments/flows/cancel_flow.rs +++ b/crates/router/src/core/payments/flows/cancel_flow.rs @@ -25,7 +25,7 @@ impl ConstructFlowSpecificData, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult { Box::pin(transformers::construct_payment_router_data::< api::Void, @@ -67,7 +67,7 @@ impl Feature call_connector_action: payments::CallConnectorAction, connector_request: Option, _business_profile: &domain::Profile, - _header_payload: api_models::payments::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { metrics::PAYMENT_CANCEL_COUNT.add( &metrics::CONTEXT, diff --git a/crates/router/src/core/payments/flows/capture_flow.rs b/crates/router/src/core/payments/flows/capture_flow.rs index 8f326aa1bc..aaa245a341 100644 --- a/crates/router/src/core/payments/flows/capture_flow.rs +++ b/crates/router/src/core/payments/flows/capture_flow.rs @@ -25,7 +25,7 @@ impl customer: &Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult { Box::pin(transformers::construct_payment_router_data::< api::Capture, @@ -67,7 +67,7 @@ impl Feature call_connector_action: payments::CallConnectorAction, connector_request: Option, _business_profile: &domain::Profile, - _header_payload: api_models::payments::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< api::Capture, diff --git a/crates/router/src/core/payments/flows/complete_authorize_flow.rs b/crates/router/src/core/payments/flows/complete_authorize_flow.rs index 0e27e4f37a..24f6d8d075 100644 --- a/crates/router/src/core/payments/flows/complete_authorize_flow.rs +++ b/crates/router/src/core/payments/flows/complete_authorize_flow.rs @@ -29,7 +29,7 @@ impl customer: &Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult< types::RouterData< api::CompleteAuthorize, @@ -81,7 +81,7 @@ impl Feature call_connector_action: payments::CallConnectorAction, connector_request: Option, _business_profile: &domain::Profile, - _header_payload: api_models::payments::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< api::CompleteAuthorize, diff --git a/crates/router/src/core/payments/flows/incremental_authorization_flow.rs b/crates/router/src/core/payments/flows/incremental_authorization_flow.rs index 7a3f9f0ac4..a187146903 100644 --- a/crates/router/src/core/payments/flows/incremental_authorization_flow.rs +++ b/crates/router/src/core/payments/flows/incremental_authorization_flow.rs @@ -28,7 +28,7 @@ impl customer: &Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult { Box::pin(transformers::construct_payment_router_data::< api::IncrementalAuthorization, @@ -74,7 +74,7 @@ impl Feature, _business_profile: &domain::Profile, - _header_payload: api_models::payments::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< api::IncrementalAuthorization, diff --git a/crates/router/src/core/payments/flows/post_session_tokens_flow.rs b/crates/router/src/core/payments/flows/post_session_tokens_flow.rs index fe83090d79..13738480c9 100644 --- a/crates/router/src/core/payments/flows/post_session_tokens_flow.rs +++ b/crates/router/src/core/payments/flows/post_session_tokens_flow.rs @@ -28,7 +28,7 @@ impl customer: &Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult { Box::pin(transformers::construct_payment_router_data::< api::PostSessionTokens, @@ -74,7 +74,7 @@ impl Feature call_connector_action: payments::CallConnectorAction, connector_request: Option, _business_profile: &domain::Profile, - _header_payload: api_models::payments::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< api::PostSessionTokens, diff --git a/crates/router/src/core/payments/flows/psync_flow.rs b/crates/router/src/core/payments/flows/psync_flow.rs index 2bb8fc1908..34ebfdf4ba 100644 --- a/crates/router/src/core/payments/flows/psync_flow.rs +++ b/crates/router/src/core/payments/flows/psync_flow.rs @@ -26,7 +26,7 @@ impl ConstructFlowSpecificData, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult< types::RouterData, > { @@ -70,7 +70,7 @@ impl Feature call_connector_action: payments::CallConnectorAction, connector_request: Option, _business_profile: &domain::Profile, - _header_payload: api_models::payments::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< api::PSync, diff --git a/crates/router/src/core/payments/flows/reject_flow.rs b/crates/router/src/core/payments/flows/reject_flow.rs index 3dc4783d37..43163c1374 100644 --- a/crates/router/src/core/payments/flows/reject_flow.rs +++ b/crates/router/src/core/payments/flows/reject_flow.rs @@ -24,7 +24,7 @@ impl ConstructFlowSpecificData, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult { Box::pin(transformers::construct_payment_router_data::< api::Reject, @@ -66,7 +66,7 @@ impl Feature _call_connector_action: payments::CallConnectorAction, _connector_request: Option, _business_profile: &domain::Profile, - _header_payload: api_models::payments::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { Err(ApiErrorResponse::NotImplemented { message: NotImplementedMessage::Reason("Flow not supported".to_string()), diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 31027bc8c5..736ffdd7b8 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -40,7 +40,7 @@ impl customer: &Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult { Box::pin(transformers::construct_payment_router_data::< api::Session, @@ -80,7 +80,7 @@ impl Feature for types::PaymentsSessio call_connector_action: payments::CallConnectorAction, _connector_request: Option, business_profile: &domain::Profile, - header_payload: api_models::payments::HeaderPayload, + header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { metrics::SESSION_TOKEN_CREATED.add( &metrics::CONTEXT, @@ -172,7 +172,7 @@ async fn create_applepay_session_token( router_data: &types::PaymentsSessionRouterData, connector: &api::ConnectorData, business_profile: &domain::Profile, - header_payload: api_models::payments::HeaderPayload, + header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { let delayed_response = is_session_response_delayed(state, connector); if delayed_response { @@ -500,7 +500,7 @@ async fn create_applepay_session_token( fn create_paze_session_token( router_data: &types::PaymentsSessionRouterData, - _header_payload: api_models::payments::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { let paze_wallet_details = router_data .connector_wallets_details @@ -528,7 +528,7 @@ fn create_paze_session_token( fn create_samsung_pay_session_token( router_data: &types::PaymentsSessionRouterData, - header_payload: api_models::payments::HeaderPayload, + header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { let samsung_pay_session_token_data = router_data .connector_wallets_details @@ -682,7 +682,7 @@ fn create_apple_pay_session_response( connector_name: String, delayed_response: bool, next_action: payment_types::NextActionCall, - header_payload: api_models::payments::HeaderPayload, + header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { match session_response { Some(response) => Ok(types::PaymentsSessionRouterData { @@ -931,7 +931,7 @@ where _confirm: Option, call_connector_action: payments::CallConnectorAction, business_profile: &domain::Profile, - header_payload: api_models::payments::HeaderPayload, + header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult; } @@ -980,7 +980,7 @@ impl RouterDataSession for types::PaymentsSessionRouterData { _confirm: Option, call_connector_action: payments::CallConnectorAction, business_profile: &domain::Profile, - header_payload: api_models::payments::HeaderPayload, + header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { match connector.get_token { api::GetToken::GpayMetadata => { diff --git a/crates/router/src/core/payments/flows/session_update_flow.rs b/crates/router/src/core/payments/flows/session_update_flow.rs index 6cad250081..e48f90d1bb 100644 --- a/crates/router/src/core/payments/flows/session_update_flow.rs +++ b/crates/router/src/core/payments/flows/session_update_flow.rs @@ -28,7 +28,7 @@ impl customer: &Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - _header_payload: Option, + _header_payload: Option, ) -> RouterResult { Box::pin( transformers::construct_router_data_to_update_calculated_tax::< @@ -74,7 +74,7 @@ impl Feature call_connector_action: payments::CallConnectorAction, connector_request: Option, _business_profile: &domain::Profile, - _header_payload: api_models::payments::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< api::SdkSessionUpdate, diff --git a/crates/router/src/core/payments/flows/setup_mandate_flow.rs b/crates/router/src/core/payments/flows/setup_mandate_flow.rs index 69720b870a..2cee81115d 100644 --- a/crates/router/src/core/payments/flows/setup_mandate_flow.rs +++ b/crates/router/src/core/payments/flows/setup_mandate_flow.rs @@ -32,7 +32,7 @@ impl customer: &Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult { Box::pin(transformers::construct_payment_router_data::< api::SetupMandate, @@ -72,7 +72,7 @@ impl Feature for types::Setup call_connector_action: payments::CallConnectorAction, connector_request: Option, _business_profile: &domain::Profile, - _header_payload: api_models::payments::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult { let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< api::SetupMandate, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 27d73d0df4..51cb5793ad 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -385,19 +385,6 @@ pub async fn get_address_by_id( } } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -pub async fn get_token_pm_type_mandate_details( - _state: &SessionState, - _request: &api::PaymentsRequest, - _mandate_type: Option, - _merchant_account: &domain::MerchantAccount, - _merchant_key_store: &domain::MerchantKeyStore, - _payment_method_id: Option, - _payment_intent_customer_id: Option<&id_type::CustomerId>, -) -> RouterResult { - todo!() -} - #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -1168,7 +1155,7 @@ pub fn create_startpay_url( pub fn create_redirect_url( router_base_url: &String, payment_attempt: &PaymentAttempt, - connector_name: &String, + connector_name: impl std::fmt::Display, creds_identifier: Option<&str>, ) -> String { let creds_identifier_path = creds_identifier.map_or_else(String::new, |cd| format!("/{}", cd)); @@ -1194,7 +1181,7 @@ pub fn create_authentication_url( pub fn create_authorize_url( router_base_url: &str, payment_attempt: &PaymentAttempt, - connector_name: &String, + connector_name: impl std::fmt::Display, ) -> String { format!( "{}/payments/{}/{}/authorize/{}", @@ -1208,7 +1195,7 @@ pub fn create_authorize_url( pub fn create_webhook_url( router_base_url: &String, merchant_id: &id_type::MerchantId, - connector_name: &String, + connector_name: impl std::fmt::Display, ) -> String { format!( "{}/webhooks/{}/{}", @@ -1220,7 +1207,7 @@ pub fn create_webhook_url( pub fn create_complete_authorize_url( router_base_url: &String, payment_attempt: &PaymentAttempt, - connector_name: &String, + connector_name: impl std::fmt::Display, ) -> String { format!( "{}/payments/{}/{}/redirect/complete/{}", @@ -1349,6 +1336,8 @@ pub fn payment_intent_status_fsm( None => storage_enums::IntentStatus::RequiresPaymentMethod, } } + +#[cfg(feature = "v1")] pub async fn add_domain_task_to_pt( operation: &Op, state: &SessionState, @@ -1419,6 +1408,7 @@ pub fn validate_max_amount( } } +#[cfg(feature = "v1")] /// Check whether the customer information that is sent in the root of payments request /// and in the customer object are same, if the values mismatch return an error pub fn validate_customer_information( @@ -1436,6 +1426,7 @@ pub fn validate_customer_information( } } +#[cfg(feature = "v1")] /// Get the customer details from customer field if present /// or from the individual fields in `PaymentsRequest` #[instrument(skip_all)] @@ -1707,6 +1698,7 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( )) } +#[cfg(feature = "v1")] pub async fn retrieve_payment_method_with_temporary_token( state: &SessionState, token: &str, @@ -2530,9 +2522,10 @@ pub(crate) fn validate_amount_to_capture( ) } +#[cfg(feature = "v1")] #[instrument(skip_all)] pub(crate) fn validate_payment_method_fields_present( - req: &api::PaymentsRequest, + req: &api_models::payments::PaymentsRequest, ) -> RouterResult<()> { let payment_method_data = req.payment_method_data @@ -3765,14 +3758,18 @@ pub async fn get_merchant_connector_account( ) } #[cfg(feature = "v2")] - // get mca using id { - let _id = merchant_connector_id; - let _ = key_store; - let _ = profile_id; - let _ = connector_name; - let _ = key_manager_state; - todo!() + db.find_merchant_connector_account_by_id( + &state.into(), + merchant_connector_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_connector_id.get_string_repr().to_string(), + }, + ) } } else { #[cfg(feature = "v1")] @@ -3864,11 +3861,12 @@ pub fn router_data_type_conversion( } } +#[cfg(feature = "v1")] #[instrument(skip_all)] pub fn get_attempt_type( payment_intent: &PaymentIntent, payment_attempt: &PaymentAttempt, - request: &api::PaymentsRequest, + request: &api_models::payments::PaymentsRequest, action: &str, ) -> RouterResult { match payment_intent.status { @@ -4075,24 +4073,25 @@ impl AttemptType { } } - #[cfg(feature = "v2")] - // The function creates a new payment_attempt from the previous payment attempt but doesn't populate fields like payment_method, error_code etc. - // Logic to override the fields with data provided in the request should be done after this if required. - // In case if fields are not overridden by the request then they contain the same data that was in the previous attempt provided it is populated in this function. - #[inline(always)] - fn make_new_payment_attempt( - _payment_method_data: Option<&api_models::payments::PaymentMethodData>, - _old_payment_attempt: PaymentAttempt, - _new_attempt_count: i16, - _storage_scheme: enums::MerchantStorageScheme, - ) -> PaymentAttempt { - todo!() - } + // #[cfg(feature = "v2")] + // // The function creates a new payment_attempt from the previous payment attempt but doesn't populate fields like payment_method, error_code etc. + // // Logic to override the fields with data provided in the request should be done after this if required. + // // In case if fields are not overridden by the request then they contain the same data that was in the previous attempt provided it is populated in this function. + // #[inline(always)] + // fn make_new_payment_attempt( + // _payment_method_data: Option<&api_models::payments::PaymentMethodData>, + // _old_payment_attempt: PaymentAttempt, + // _new_attempt_count: i16, + // _storage_scheme: enums::MerchantStorageScheme, + // ) -> PaymentAttempt { + // todo!() + // } + #[cfg(feature = "v1")] #[instrument(skip_all)] pub async fn modify_payment_intent_and_payment_attempt( &self, - request: &api::PaymentsRequest, + request: &api_models::payments::PaymentsRequest, fetched_payment_intent: PaymentIntent, fetched_payment_attempt: PaymentAttempt, state: &SessionState, @@ -4161,7 +4160,7 @@ impl AttemptType { .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; logger::info!( - "manual_retry payment for {:?} with attempt_id {}", + "manual_retry payment for {:?} with attempt_id {:?}", updated_payment_intent.get_id(), new_payment_attempt.get_id() ); @@ -4706,6 +4705,7 @@ pub async fn populate_bin_details_for_payment_method_create( todo!() } +#[cfg(feature = "v1")] pub fn validate_customer_access( payment_intent: &PaymentIntent, auth_flow: services::AuthFlow, diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 56b198c538..6546638a62 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -31,6 +31,9 @@ pub mod tax_calculation; #[cfg(feature = "v2")] pub mod payment_create_intent; +#[cfg(feature = "v2")] +pub mod payment_confirm_intent; + use api_models::enums::FrmSuggestion; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] use api_models::routing::RoutableConnectorChoice; @@ -39,7 +42,9 @@ use error_stack::{report, ResultExt}; use router_env::{instrument, tracing}; #[cfg(feature = "v2")] -pub use self::payment_create_intent::PaymentCreateIntent; +pub use self::payment_confirm_intent::PaymentIntentConfirm; +#[cfg(feature = "v2")] +pub use self::payment_create_intent::PaymentIntentCreate; pub use self::payment_response::PaymentResponse; #[cfg(feature = "v1")] pub use self::{ @@ -149,9 +154,11 @@ pub struct GetTrackerResponse<'a, F: Clone, R, D> { pub mandate_type: Option, } -#[cfg(feature = "v1")] +/// This trait is used to fetch / create all the tracker related information for a payment +/// This functions returns the session data that is used by subsequent functions #[async_trait] pub trait GetTracker: Send { + #[cfg(feature = "v1")] #[allow(clippy::too_many_arguments)] async fn get_trackers<'a>( &'a self, @@ -161,13 +168,12 @@ pub trait GetTracker: Send { merchant_account: &domain::MerchantAccount, mechant_key_store: &domain::MerchantKeyStore, auth_flow: services::AuthFlow, - header_payload: &api::HeaderPayload, + header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult>; -} -#[cfg(feature = "v2")] -#[async_trait] -pub trait GetTracker: Send { + // TODO: this need not return the operation, since operation does not change in v2 + // Operation remains the same from start to finish + #[cfg(feature = "v2")] #[allow(clippy::too_many_arguments)] async fn get_trackers<'a>( &'a self, @@ -177,8 +183,7 @@ pub trait GetTracker: Send { merchant_account: &domain::MerchantAccount, profile: &domain::Profile, mechant_key_store: &domain::MerchantKeyStore, - auth_flow: services::AuthFlow, - header_payload: &api::HeaderPayload, + header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult>; } @@ -310,7 +315,7 @@ pub trait UpdateTracker: Send { updated_customer: Option, mechant_key_store: &domain::MerchantKeyStore, frm_suggestion: Option, - header_payload: api::HeaderPayload, + header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<(BoxedOperation<'b, F, Req, D>, D)> where F: 'b + Send; @@ -322,7 +327,6 @@ pub trait PostUpdateTracker: Send { async fn update_tracker<'b>( &'b self, db: &'b SessionState, - payment_id: &api::PaymentIdType, payment_data: D, response: types::RouterData, key_store: &domain::MerchantKeyStore, @@ -750,3 +754,11 @@ where Ok(false) } } + +/// Validate if a particular operation can be performed for the given intent status +pub trait ValidateStatusForOperation { + fn validate_status_for_operation( + &self, + intent_status: common_enums::IntentStatus, + ) -> Result<(), errors::ApiErrorResponse>; +} diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index e8814c7e56..fe5e7a7e72 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -43,7 +43,7 @@ impl GetTracker, api::PaymentsCaptureRequest> merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - _header_payload: &api::HeaderPayload, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult< operations::GetTrackerResponse<'a, F, api::PaymentsCaptureRequest, PaymentData>, > { @@ -220,7 +220,7 @@ impl UpdateTracker, api::PaymentsCaptureRequest> for _updated_customer: Option, key_store: &domain::MerchantKeyStore, frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<(PaymentApproveOperation<'b, F>, PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index c84dcc50e6..8d7361cd25 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -43,7 +43,7 @@ impl GetTracker, api::PaymentsCancelRequest> merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - _header_payload: &api::HeaderPayload, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult< operations::GetTrackerResponse<'a, F, api::PaymentsCancelRequest, PaymentData>, > { @@ -230,7 +230,7 @@ impl UpdateTracker, api::PaymentsCancelRequest> for _updated_customer: Option, key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<(PaymentCancelOperation<'b, F>, PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 93a79e2d2e..2451cbcd8e 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -44,7 +44,7 @@ impl GetTracker, api::PaymentsCaptu merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - _header_payload: &api::HeaderPayload, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult< operations::GetTrackerResponse< 'a, @@ -283,7 +283,7 @@ impl UpdateTracker, api::PaymentsCaptureRe _updated_customer: Option, _mechant_key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<(PaymentCaptureOperation<'b, F>, payments::PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index e705c64d6e..850fa738cd 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -44,7 +44,7 @@ impl GetTracker, api::PaymentsRequest> for Co merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - _header_payload: &api::HeaderPayload, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult>> { let db = &*state.store; @@ -469,7 +469,7 @@ impl UpdateTracker, api::PaymentsRequest> for Comple _updated_customer: Option, key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<(CompleteAuthorizeOperation<'b, F>, PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index ca32be35e5..74339a88fb 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -66,7 +66,7 @@ impl GetTracker, api::PaymentsRequest> for Pa merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, auth_flow: services::AuthFlow, - header_payload: &api::HeaderPayload, + header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult>> { let key_manager_state = &state.into(); @@ -1065,7 +1065,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen _updated_customer: Option, _key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsRequest, PaymentData>, PaymentData, @@ -1091,7 +1091,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen updated_customer: Option, key_store: &domain::MerchantKeyStore, frm_suggestion: Option, - header_payload: api::HeaderPayload, + header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsRequest, PaymentData>, PaymentData, diff --git a/crates/router/src/core/payments/operations/payment_confirm_intent.rs b/crates/router/src/core/payments/operations/payment_confirm_intent.rs new file mode 100644 index 0000000000..083bb5adca --- /dev/null +++ b/crates/router/src/core/payments/operations/payment_confirm_intent.rs @@ -0,0 +1,370 @@ +use api_models::{ + admin::ExtendedCardInfoConfig, + enums::FrmSuggestion, + payments::{ExtendedCardInfo, GetAddressFromPaymentMethodData, PaymentsConfirmIntentRequest}, +}; +use async_trait::async_trait; +use error_stack::ResultExt; +use hyperswitch_domain_models::payments::{ + payment_attempt::PaymentAttempt, PaymentConfirmData, PaymentIntent, +}; +use router_env::{instrument, tracing}; +use tracing_futures::Instrument; + +use super::{Domain, GetTracker, Operation, UpdateTracker, ValidateRequest}; +use crate::{ + core::{ + authentication, + errors::{self, CustomResult, RouterResult, StorageErrorExt}, + payments::{ + self, helpers, + operations::{self, ValidateStatusForOperation}, + populate_surcharge_details, CustomerDetails, PaymentAddress, PaymentData, + }, + utils as core_utils, + }, + routes::{app::ReqState, SessionState}, + services, + types::{ + self, + api::{self, ConnectorCallType, PaymentIdTypeExt}, + domain::{self}, + storage::{self, enums as storage_enums}, + }, + utils::{self, OptionExt}, +}; + +#[derive(Debug, Clone, Copy)] +pub struct PaymentIntentConfirm; + +impl ValidateStatusForOperation for PaymentIntentConfirm { + /// Validate if the current operation can be performed on the current status of the payment intent + fn validate_status_for_operation( + &self, + intent_status: common_enums::IntentStatus, + ) -> Result<(), errors::ApiErrorResponse> { + match intent_status { + common_enums::IntentStatus::RequiresPaymentMethod => Ok(()), + common_enums::IntentStatus::Succeeded + | common_enums::IntentStatus::Failed + | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::Processing + | common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::RequiresCapture + | common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::RequiresConfirmation + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => { + Err(errors::ApiErrorResponse::PaymentUnexpectedState { + current_flow: format!("{self:?}"), + field_name: "status".to_string(), + current_value: intent_status.to_string(), + states: ["requires_payment_method".to_string()].join(", "), + }) + } + } + } +} + +type BoxedConfirmOperation<'b, F> = + super::BoxedOperation<'b, F, PaymentsConfirmIntentRequest, PaymentConfirmData>; + +// TODO: change the macro to include changes for v2 +// TODO: PaymentData in the macro should be an input +impl Operation for &PaymentIntentConfirm { + type Data = PaymentConfirmData; + fn to_validate_request( + &self, + ) -> RouterResult< + &(dyn ValidateRequest + Send + Sync), + > { + Ok(*self) + } + fn to_get_tracker( + &self, + ) -> RouterResult<&(dyn GetTracker + Send + Sync)> + { + Ok(*self) + } + fn to_domain( + &self, + ) -> RouterResult<&(dyn Domain)> { + Ok(*self) + } + fn to_update_tracker( + &self, + ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> + { + Ok(*self) + } +} +#[automatically_derived] +impl Operation for PaymentIntentConfirm { + type Data = PaymentConfirmData; + fn to_validate_request( + &self, + ) -> RouterResult< + &(dyn ValidateRequest + Send + Sync), + > { + Ok(self) + } + fn to_get_tracker( + &self, + ) -> RouterResult<&(dyn GetTracker + Send + Sync)> + { + Ok(self) + } + fn to_domain(&self) -> RouterResult<&dyn Domain> { + Ok(self) + } + fn to_update_tracker( + &self, + ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> + { + Ok(self) + } +} + +impl ValidateRequest> + for PaymentIntentConfirm +{ + #[instrument(skip_all)] + fn validate_request<'a, 'b>( + &'b self, + request: &PaymentsConfirmIntentRequest, + merchant_account: &'a domain::MerchantAccount, + ) -> RouterResult<(BoxedConfirmOperation<'b, F>, operations::ValidateResult)> { + let validate_result = operations::ValidateResult { + merchant_id: merchant_account.get_id().to_owned(), + storage_scheme: merchant_account.storage_scheme, + requeue: false, + }; + + Ok((Box::new(self), validate_result)) + } +} + +#[async_trait] +impl GetTracker, PaymentsConfirmIntentRequest> + for PaymentIntentConfirm +{ + #[instrument(skip_all)] + async fn get_trackers<'a>( + &'a self, + state: &'a SessionState, + payment_id: &common_utils::id_type::GlobalPaymentId, + request: &PaymentsConfirmIntentRequest, + merchant_account: &domain::MerchantAccount, + profile: &domain::Profile, + key_store: &domain::MerchantKeyStore, + header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + ) -> RouterResult< + operations::GetTrackerResponse<'a, F, PaymentsConfirmIntentRequest, PaymentConfirmData>, + > { + let db = &*state.store; + let key_manager_state = &state.into(); + + let storage_scheme = merchant_account.storage_scheme; + + let payment_intent = db + .find_payment_intent_by_id(key_manager_state, payment_id, key_store, storage_scheme) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + self.validate_status_for_operation(payment_intent.status)?; + let client_secret = header_payload + .client_secret + .as_ref() + .get_required_value("client_secret header")?; + payment_intent.validate_client_secret(client_secret)?; + + let cell_id = state.conf.cell_information.id.clone(); + + let payment_attempt_domain_model = + hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt::create_domain_model( + &payment_intent, + cell_id, + storage_scheme, + request + ) + .await?; + + let payment_attempt = db + .insert_payment_attempt( + key_manager_state, + key_store, + payment_attempt_domain_model, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Could not insert payment attempt")?; + + let payment_method_data = request + .payment_method_data + .payment_method_data + .clone() + .map(hyperswitch_domain_models::payment_method_data::PaymentMethodData::from); + + let payment_data = PaymentConfirmData { + flow: std::marker::PhantomData, + payment_intent, + payment_attempt, + payment_method_data, + }; + + let get_trackers_response = operations::GetTrackerResponse { + operation: Box::new(self), + payment_data, + }; + + Ok(get_trackers_response) + } +} + +#[async_trait] +impl Domain> + for PaymentIntentConfirm +{ + async fn get_customer_details<'a>( + &'a self, + state: &SessionState, + payment_data: &mut PaymentConfirmData, + merchant_key_store: &domain::MerchantKeyStore, + storage_scheme: storage_enums::MerchantStorageScheme, + ) -> CustomResult<(BoxedConfirmOperation<'a, F>, Option), errors::StorageError> + { + match payment_data.payment_intent.customer_id.clone() { + Some(id) => { + let customer = state + .store + .find_customer_by_global_id( + &state.into(), + id.get_string_repr(), + &payment_data.payment_intent.merchant_id, + merchant_key_store, + storage_scheme, + ) + .await?; + + Ok((Box::new(self), Some(customer))) + } + None => Ok((Box::new(self), None)), + } + } + + #[instrument(skip_all)] + async fn make_pm_data<'a>( + &'a self, + state: &'a SessionState, + payment_data: &mut PaymentConfirmData, + storage_scheme: storage_enums::MerchantStorageScheme, + key_store: &domain::MerchantKeyStore, + customer: &Option, + business_profile: &domain::Profile, + ) -> RouterResult<( + BoxedConfirmOperation<'a, F>, + Option, + Option, + )> { + Ok((Box::new(self), None, None)) + } + + async fn get_connector<'a>( + &'a self, + _merchant_account: &domain::MerchantAccount, + state: &SessionState, + request: &PaymentsConfirmIntentRequest, + _payment_intent: &storage::PaymentIntent, + _key_store: &domain::MerchantKeyStore, + ) -> CustomResult { + todo!() + } +} + +#[async_trait] +impl UpdateTracker, PaymentsConfirmIntentRequest> + for PaymentIntentConfirm +{ + #[instrument(skip_all)] + async fn update_trackers<'b>( + &'b self, + state: &'b SessionState, + req_state: ReqState, + mut payment_data: PaymentConfirmData, + customer: Option, + storage_scheme: storage_enums::MerchantStorageScheme, + updated_customer: Option, + key_store: &domain::MerchantKeyStore, + frm_suggestion: Option, + header_payload: hyperswitch_domain_models::payments::HeaderPayload, + ) -> RouterResult<(BoxedConfirmOperation<'b, F>, PaymentConfirmData)> + where + F: 'b + Send, + { + let db = &*state.store; + let key_manager_state = &state.into(); + + let intent_status = common_enums::IntentStatus::Processing; + let attempt_status = common_enums::AttemptStatus::Pending; + + let connector = payment_data + .payment_attempt + .connector + .clone() + .get_required_value("connector") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Connector is none when constructing response")?; + + let merchant_connector_id = payment_data + .payment_attempt + .merchant_connector_id + .clone() + .get_required_value("merchant_connector_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Merchant connector id is none when constructing response")?; + + let payment_intent_update = + hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::ConfirmIntent { + status: intent_status, + updated_by: storage_scheme.to_string(), + }; + + let payment_attempt_update = hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntent { + status: attempt_status, + updated_by: storage_scheme.to_string(), + connector, + merchant_connector_id, + }; + + let updated_payment_intent = db + .update_payment_intent( + key_manager_state, + payment_data.payment_intent.clone(), + payment_intent_update, + key_store, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to update payment intent")?; + + payment_data.payment_intent = updated_payment_intent; + + let updated_payment_attempt = db + .update_payment_attempt( + key_manager_state, + key_store, + payment_data.payment_attempt.clone(), + payment_attempt_update, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to update payment attempt")?; + + payment_data.payment_attempt = updated_payment_attempt; + + Ok((Box::new(self), payment_data)) + } +} diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index fc1f71c208..46799c3c19 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -73,7 +73,7 @@ impl GetTracker, api::PaymentsRequest> for Pa merchant_account: &domain::MerchantAccount, merchant_key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - header_payload: &api::HeaderPayload, + header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult>> { let db = &*state.store; @@ -775,7 +775,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen _updated_customer: Option, key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<(PaymentCreateOperation<'b, F>, PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/operations/payment_create_intent.rs b/crates/router/src/core/payments/operations/payment_create_intent.rs index c0b66b4446..bd82a67e62 100644 --- a/crates/router/src/core/payments/operations/payment_create_intent.rs +++ b/crates/router/src/core/payments/operations/payment_create_intent.rs @@ -24,9 +24,9 @@ use crate::{ }; #[derive(Debug, Clone, Copy)] -pub struct PaymentCreateIntent; +pub struct PaymentIntentCreate; -impl Operation for &PaymentCreateIntent { +impl Operation for &PaymentIntentCreate { type Data = payments::PaymentIntentData; fn to_validate_request( &self, @@ -52,7 +52,7 @@ impl Operation for &PaymentCrea } } -impl Operation for PaymentCreateIntent { +impl Operation for PaymentIntentCreate { type Data = payments::PaymentIntentData; fn to_validate_request( &self, @@ -83,7 +83,7 @@ type PaymentsCreateIntentOperation<'b, F> = #[async_trait] impl GetTracker, PaymentsCreateIntentRequest> - for PaymentCreateIntent + for PaymentIntentCreate { #[instrument(skip_all)] async fn get_trackers<'a>( @@ -94,8 +94,7 @@ impl GetTracker, PaymentsCrea merchant_account: &domain::MerchantAccount, profile: &domain::Profile, key_store: &domain::MerchantKeyStore, - _auth_flow: services::AuthFlow, - _header_payload: &api::HeaderPayload, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult< operations::GetTrackerResponse< 'a, @@ -188,7 +187,7 @@ impl GetTracker, PaymentsCrea #[async_trait] impl UpdateTracker, PaymentsCreateIntentRequest> - for PaymentCreateIntent + for PaymentIntentCreate { #[instrument(skip_all)] async fn update_trackers<'b>( @@ -201,7 +200,7 @@ impl UpdateTracker, PaymentsCreateIn _updated_customer: Option, _key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<( PaymentsCreateIntentOperation<'b, F>, payments::PaymentIntentData, @@ -215,7 +214,7 @@ impl UpdateTracker, PaymentsCreateIn impl ValidateRequest> - for PaymentCreateIntent + for PaymentIntentCreate { #[instrument(skip_all)] fn validate_request<'a, 'b>( @@ -239,7 +238,7 @@ impl #[async_trait] impl Domain> - for PaymentCreateIntent + for PaymentIntentCreate { #[instrument(skip_all)] async fn get_customer_details<'a>( diff --git a/crates/router/src/core/payments/operations/payment_post_session_tokens.rs b/crates/router/src/core/payments/operations/payment_post_session_tokens.rs index 81702979ac..846f739e08 100644 --- a/crates/router/src/core/payments/operations/payment_post_session_tokens.rs +++ b/crates/router/src/core/payments/operations/payment_post_session_tokens.rs @@ -44,7 +44,7 @@ impl GetTracker, api::PaymentsPostSessionToke merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - _header_payload: &api::HeaderPayload, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult< operations::GetTrackerResponse< 'a, @@ -255,7 +255,7 @@ impl UpdateTracker, api::PaymentsPostSessionTokensRe _updated_customer: Option, _key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<(PaymentPostSessionTokensOperation<'b, F>, PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index 2257ff0402..55b9350127 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -39,7 +39,7 @@ impl GetTracker, PaymentsCancelRequest> for P merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - _header_payload: &api::HeaderPayload, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult>> { let db = &*state.store; @@ -216,7 +216,7 @@ impl UpdateTracker, PaymentsCancelRequest> for Payme _updated_customer: Option, key_store: &domain::MerchantKeyStore, _should_decline_transaction: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<(PaymentRejectOperation<'b, F>, PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 274b4f43c5..ae64fe41b1 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use api_models::payments::{ConnectorMandateReferenceId, MandateReferenceId}; -#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +#[cfg(feature = "dynamic_routing")] use api_models::routing::RoutableConnectorChoice; use async_trait::async_trait; use common_enums::{AuthorizationStatus, SessionUpdateStatus}; @@ -12,6 +12,8 @@ use common_utils::{ use error_stack::{report, ResultExt}; use futures::FutureExt; use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt; +#[cfg(feature = "v2")] +use hyperswitch_domain_models::payments::PaymentConfirmData; use router_derive; use router_env::{instrument, logger, metrics::add_attributes, tracing}; use storage_impl::DataModelExt; @@ -40,7 +42,7 @@ use crate::{ }, routes::{metrics, SessionState}, types::{ - self, api, domain, + self, domain, storage::{self, enums}, transformers::{ForeignFrom, ForeignTryFrom}, CaptureSyncResponse, ErrorResponse, @@ -68,7 +70,6 @@ impl PostUpdateTracker, types::PaymentsAuthor async fn update_tracker<'b>( &'b self, db: &'b SessionState, - payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData< F, @@ -92,7 +93,6 @@ impl PostUpdateTracker, types::PaymentsAuthor payment_data = Box::pin(payment_response_update_tracker( db, - payment_id, payment_data, router_data, key_store, @@ -365,7 +365,6 @@ impl PostUpdateTracker, types::PaymentsIncrementalAu async fn update_tracker<'b>( &'b self, state: &'b SessionState, - _payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData< F, @@ -532,7 +531,6 @@ impl PostUpdateTracker, types::PaymentsSyncData> for async fn update_tracker<'b>( &'b self, db: &'b SessionState, - payment_id: &api::PaymentIdType, payment_data: PaymentData, router_data: types::RouterData, key_store: &domain::MerchantKeyStore, @@ -548,7 +546,6 @@ impl PostUpdateTracker, types::PaymentsSyncData> for { Box::pin(payment_response_update_tracker( db, - payment_id, payment_data, router_data, key_store, @@ -623,7 +620,6 @@ impl PostUpdateTracker, types::PaymentsSessionData> async fn update_tracker<'b>( &'b self, db: &'b SessionState, - payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData, key_store: &domain::MerchantKeyStore, @@ -639,7 +635,6 @@ impl PostUpdateTracker, types::PaymentsSessionData> { payment_data = Box::pin(payment_response_update_tracker( db, - payment_id, payment_data, router_data, key_store, @@ -664,7 +659,6 @@ impl PostUpdateTracker, types::SdkPaymentsSessionUpd async fn update_tracker<'b>( &'b self, db: &'b SessionState, - _payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData< F, @@ -789,7 +783,6 @@ impl PostUpdateTracker, types::PaymentsPostSessionTo async fn update_tracker<'b>( &'b self, db: &'b SessionState, - _payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData< F, @@ -851,7 +844,6 @@ impl PostUpdateTracker, types::PaymentsCaptureData> async fn update_tracker<'b>( &'b self, db: &'b SessionState, - payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData, key_store: &domain::MerchantKeyStore, @@ -867,7 +859,6 @@ impl PostUpdateTracker, types::PaymentsCaptureData> { payment_data = Box::pin(payment_response_update_tracker( db, - payment_id, payment_data, router_data, key_store, @@ -890,7 +881,6 @@ impl PostUpdateTracker, types::PaymentsCancelData> f async fn update_tracker<'b>( &'b self, db: &'b SessionState, - payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData, key_store: &domain::MerchantKeyStore, @@ -906,7 +896,6 @@ impl PostUpdateTracker, types::PaymentsCancelData> f { payment_data = Box::pin(payment_response_update_tracker( db, - payment_id, payment_data, router_data, key_store, @@ -931,7 +920,6 @@ impl PostUpdateTracker, types::PaymentsApproveData> async fn update_tracker<'b>( &'b self, db: &'b SessionState, - payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData, key_store: &domain::MerchantKeyStore, @@ -947,7 +935,6 @@ impl PostUpdateTracker, types::PaymentsApproveData> { payment_data = Box::pin(payment_response_update_tracker( db, - payment_id, payment_data, router_data, key_store, @@ -970,7 +957,6 @@ impl PostUpdateTracker, types::PaymentsRejectData> f async fn update_tracker<'b>( &'b self, db: &'b SessionState, - payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData, key_store: &domain::MerchantKeyStore, @@ -986,7 +972,6 @@ impl PostUpdateTracker, types::PaymentsRejectData> f { payment_data = Box::pin(payment_response_update_tracker( db, - payment_id, payment_data, router_data, key_store, @@ -1011,7 +996,6 @@ impl PostUpdateTracker, types::SetupMandateRequestDa async fn update_tracker<'b>( &'b self, db: &'b SessionState, - payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData< F, @@ -1036,7 +1020,6 @@ impl PostUpdateTracker, types::SetupMandateRequestDa payment_data = Box::pin(payment_response_update_tracker( db, - payment_id, payment_data, router_data, key_store, @@ -1134,7 +1117,6 @@ impl PostUpdateTracker, types::CompleteAuthorizeData async fn update_tracker<'b>( &'b self, db: &'b SessionState, - payment_id: &api::PaymentIdType, payment_data: PaymentData, response: types::RouterData, key_store: &domain::MerchantKeyStore, @@ -1150,7 +1132,6 @@ impl PostUpdateTracker, types::CompleteAuthorizeData { Box::pin(payment_response_update_tracker( db, - payment_id, payment_data, response, key_store, @@ -1216,26 +1197,11 @@ impl PostUpdateTracker, types::CompleteAuthorizeData } } -#[cfg(feature = "v2")] -#[instrument(skip_all)] -async fn payment_response_update_tracker( - state: &SessionState, - _payment_id: &api::PaymentIdType, - mut payment_data: PaymentData, - router_data: types::RouterData, - key_store: &domain::MerchantKeyStore, - storage_scheme: enums::MerchantStorageScheme, - locale: &Option, -) -> RouterResult> { - todo!() -} - #[cfg(feature = "v1")] #[instrument(skip_all)] #[allow(clippy::too_many_arguments)] async fn payment_response_update_tracker( state: &SessionState, - _payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData, key_store: &domain::MerchantKeyStore, @@ -2133,6 +2099,195 @@ async fn update_payment_method_status_and_ntid( Ok(()) } +#[cfg(feature = "v2")] +impl Operation for &PaymentResponse { + type Data = PaymentConfirmData; + fn to_post_update_tracker( + &self, + ) -> RouterResult< + &(dyn PostUpdateTracker + Send + Sync), + > { + Ok(*self) + } +} + +#[cfg(feature = "v2")] +impl Operation for PaymentResponse { + type Data = PaymentConfirmData; + fn to_post_update_tracker( + &self, + ) -> RouterResult< + &(dyn PostUpdateTracker + Send + Sync), + > { + Ok(self) + } +} + +#[cfg(feature = "v2")] +#[async_trait] +impl PostUpdateTracker, types::PaymentsAuthorizeData> + for PaymentResponse +{ + async fn update_tracker<'b>( + &'b self, + state: &'b SessionState, + mut payment_data: PaymentConfirmData, + response: types::RouterData, + key_store: &domain::MerchantKeyStore, + storage_scheme: enums::MerchantStorageScheme, + locale: &Option, + #[cfg(all(feature = "v1", feature = "dynamic_routing"))] routable_connector: Vec< + RoutableConnectorChoice, + >, + #[cfg(all(feature = "v1", feature = "dynamic_routing"))] business_profile: &domain::Profile, + ) -> RouterResult> + where + F: 'b + Send + Sync, + { + let db = &*state.store; + let key_manager_state = &state.into(); + + let response_router_data = response; + + match response_router_data.response { + Ok(response) => match response { + types::PaymentsResponseData::TransactionResponse { + resource_id, + redirection_data, + mandate_reference, + connector_metadata, + network_txn_id, + connector_response_reference_id, + incremental_authorization_allowed, + charge_id, + } => { + let attempt_status = response_router_data.status; + let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); + let connector_payment_id = match resource_id { + types::ResponseId::NoResponseId => None, + types::ResponseId::ConnectorTransactionId(id) + | types::ResponseId::EncodedData(id) => Some(id), + }; + + let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::ConfirmIntentPostUpdate { status: intent_status, updated_by: storage_scheme.to_string() }; + let updated_payment_intent = db + .update_payment_intent( + key_manager_state, + payment_data.payment_intent, + payment_intent_update, + key_store, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to update payment intent")?; + payment_data.payment_intent = updated_payment_intent; + + let payment_attempt_update = hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntentResponse { status: attempt_status, connector_payment_id, updated_by: storage_scheme.to_string() }; + let updated_payment_attempt = db + .update_payment_attempt( + key_manager_state, + key_store, + payment_data.payment_attempt, + payment_attempt_update, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to update payment attempt")?; + payment_data.payment_attempt = updated_payment_attempt; + } + types::PaymentsResponseData::MultipleCaptureResponse { + capture_sync_response_list, + } => todo!(), + types::PaymentsResponseData::SessionResponse { session_token } => todo!(), + types::PaymentsResponseData::SessionTokenResponse { session_token } => todo!(), + types::PaymentsResponseData::TransactionUnresolvedResponse { + resource_id, + reason, + connector_response_reference_id, + } => todo!(), + types::PaymentsResponseData::TokenizationResponse { token } => todo!(), + types::PaymentsResponseData::ConnectorCustomerResponse { + connector_customer_id, + } => todo!(), + types::PaymentsResponseData::ThreeDSEnrollmentResponse { + enrolled_v2, + related_transaction_id, + } => todo!(), + types::PaymentsResponseData::PreProcessingResponse { + pre_processing_id, + connector_metadata, + session_token, + connector_response_reference_id, + } => todo!(), + types::PaymentsResponseData::IncrementalAuthorizationResponse { + status, + connector_authorization_id, + error_code, + error_message, + } => todo!(), + types::PaymentsResponseData::PostProcessingResponse { session_token } => todo!(), + types::PaymentsResponseData::SessionUpdateResponse { status } => todo!(), + }, + Err(ErrorResponse { + code, + message, + reason, + status_code, + attempt_status, + connector_transaction_id, + }) => { + let attempt_status = common_enums::AttemptStatus::Failure; + let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); + let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::ConfirmIntentPostUpdate { status: intent_status, updated_by: storage_scheme.to_string() }; + + let updated_payment_intent = db + .update_payment_intent( + key_manager_state, + payment_data.payment_intent, + payment_intent_update, + key_store, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to update payment intent")?; + + payment_data.payment_intent = updated_payment_intent; + + // TODO: populate unified code and message and translation by calling gsm and translation table + let error_details = + hyperswitch_domain_models::payments::payment_attempt::ErrorDetails { + code, + message, + reason, + unified_code: None, + unified_message: None, + }; + + let payment_attempt_update = hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntentError { status: attempt_status, error: error_details,updated_by: storage_scheme.to_string() }; + let updated_payment_attempt = db + .update_payment_attempt( + key_manager_state, + key_store, + payment_data.payment_attempt, + payment_attempt_update, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to update payment attempt")?; + + payment_data.payment_attempt = updated_payment_attempt; + } + } + // TODO: Implement this + Ok(payment_data) + } +} + +#[cfg(feature = "v1")] fn update_connector_mandate_details_for_the_flow( connector_mandate_id: Option, mandate_metadata: Option, diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index d2154f16b6..1bc11854ad 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -43,7 +43,7 @@ impl GetTracker, api::PaymentsSessionRequest> merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - _header_payload: &api::HeaderPayload, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult< operations::GetTrackerResponse<'a, F, api::PaymentsSessionRequest, PaymentData>, > { @@ -239,7 +239,7 @@ impl UpdateTracker, api::PaymentsSessionRequest> for _updated_customer: Option, key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<(PaymentSessionOperation<'b, F>, PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 3042b15bfa..cad5dcfbd6 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -40,7 +40,7 @@ impl GetTracker, api::PaymentsStartRequest> f merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - _header_payload: &api::HeaderPayload, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult< operations::GetTrackerResponse<'a, F, api::PaymentsStartRequest, PaymentData>, > { @@ -224,7 +224,7 @@ impl UpdateTracker, api::PaymentsStartRequest> for P _updated_customer: Option, _mechant_key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<(PaymentSessionOperation<'b, F>, PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 47f16786ab..a3a826ccd0 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -148,7 +148,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen _updated_customer: Option, _key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<( PaymentStatusOperation<'b, F, api::PaymentsRequest>, PaymentData, @@ -172,7 +172,7 @@ impl UpdateTracker, api::PaymentsRetrieveRequest> fo _updated_customer: Option, _key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<( PaymentStatusOperation<'b, F, api::PaymentsRetrieveRequest>, PaymentData, @@ -197,7 +197,7 @@ impl GetTracker, api::PaymentsRetrieveRequest merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - _header_payload: &api::HeaderPayload, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult< operations::GetTrackerResponse<'a, F, api::PaymentsRetrieveRequest, PaymentData>, > { diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index c1eae806ce..757370f0c9 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -54,7 +54,7 @@ impl GetTracker, api::PaymentsRequest> for Pa merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, auth_flow: services::AuthFlow, - _header_payload: &api::HeaderPayload, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult>> { let (mut payment_intent, mut payment_attempt, currency): (_, _, storage_enums::Currency); @@ -695,7 +695,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen _updated_customer: Option, _key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<(PaymentUpdateOperation<'b, F>, PaymentData)> where F: 'b + Send, @@ -715,7 +715,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen _updated_customer: Option, key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<(PaymentUpdateOperation<'b, F>, PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs index 9d80206b9f..1ddaf0b6ab 100644 --- a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs +++ b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs @@ -47,7 +47,7 @@ impl merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - _header_payload: &api::HeaderPayload, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult< operations::GetTrackerResponse< 'a, @@ -200,7 +200,7 @@ impl UpdateTracker, PaymentsIncrementalAut _updated_customer: Option, key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<( PaymentIncrementalAuthorizationOperation<'b, F>, payments::PaymentData, diff --git a/crates/router/src/core/payments/operations/tax_calculation.rs b/crates/router/src/core/payments/operations/tax_calculation.rs index c72dcb6f56..2d4054a60b 100644 --- a/crates/router/src/core/payments/operations/tax_calculation.rs +++ b/crates/router/src/core/payments/operations/tax_calculation.rs @@ -48,7 +48,7 @@ impl GetTracker, api::PaymentsDynamicTaxCalcu merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - _header_payload: &api::HeaderPayload, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult< operations::GetTrackerResponse< 'a, @@ -378,7 +378,7 @@ impl UpdateTracker, api::PaymentsDynamicTaxCalculati _updated_customer: Option, key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, - _header_payload: api::HeaderPayload, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<(PaymentSessionUpdateOperation<'b, F>, PaymentData)> where F: 'b + Send, diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index e4b6d0e287..060e93474b 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -70,10 +70,19 @@ where .clone() .map(|gsm| gsm.step_up_possible) .unwrap_or(false); + + #[cfg(feature = "v1")] let is_no_three_ds_payment = matches!( payment_data.get_payment_attempt().authentication_type, Some(storage_enums::AuthenticationType::NoThreeDs) ); + + #[cfg(feature = "v2")] + let is_no_three_ds_payment = matches!( + payment_data.get_payment_attempt().authentication_type, + storage_enums::AuthenticationType::NoThreeDs + ); + let should_step_up = if step_up_possible && is_no_three_ds_payment { is_step_up_enabled_for_merchant_connector( state, @@ -338,7 +347,7 @@ where payments::CallConnectorAction::Trigger, validate_result, schedule_time, - api::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), frm_suggestion, business_profile, true, diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index af540c6dcf..11908ae0d9 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -826,6 +826,16 @@ pub async fn perform_eligibility_analysis_with_fallback( Ok(final_selection) } +#[cfg(feature = "v2")] +pub async fn perform_session_flow_routing( + session_input: SessionFlowRoutingInput<'_>, + transaction_type: &api_enums::TransactionType, +) -> RoutingResult>> +{ + todo!() +} + +#[cfg(feature = "v1")] pub async fn perform_session_flow_routing( session_input: SessionFlowRoutingInput<'_>, transaction_type: &api_enums::TransactionType, @@ -958,6 +968,7 @@ pub async fn perform_session_flow_routing( allowed_connectors, profile_id: &profile_id, }; + let routable_connector_choice_option = perform_session_routing_for_pm_type( &session_pm_input, transaction_type, diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 03ae1c8800..58ea3b4346 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -16,6 +16,8 @@ use diesel_models::{ payment_attempt::ConnectorMandateReferenceId as DieselConnectorMandateReferenceId, }; use error_stack::{report, ResultExt}; +#[cfg(feature = "v2")] +use hyperswitch_domain_models::payments::PaymentConfirmData; use hyperswitch_domain_models::{payments::payment_intent::CustomerData, router_request_types}; use masking::{ExposeInterface, Maskable, PeekInterface, Secret}; use router_env::{instrument, metrics::add_attributes, tracing}; @@ -43,6 +45,27 @@ use crate::{ utils::{OptionExt, ValueExt}, }; +#[cfg(feature = "v2")] +pub async fn construct_router_data_to_update_calculated_tax<'a, F, T>( + state: &'a SessionState, + payment_data: PaymentData, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + customer: &'a Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, +) -> RouterResult> +where + T: TryFrom>, + types::RouterData: Feature, + F: Clone, + error_stack::Report: + From<>>::Error>, +{ + todo!() +} + +#[cfg(feature = "v1")] pub async fn construct_router_data_to_update_calculated_tax<'a, F, T>( state: &'a SessionState, payment_data: PaymentData, @@ -140,19 +163,215 @@ where Ok(router_data) } -#[cfg(all(feature = "v2", feature = "customer_v2"))] +#[cfg(feature = "v2")] +#[instrument(skip_all)] +#[allow(clippy::too_many_arguments)] +pub async fn construct_payment_router_data_for_authorize<'a>( + state: &'a SessionState, + payment_data: PaymentConfirmData, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + customer: &'a Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, + _merchant_recipient_data: Option, + header_payload: Option, +) -> RouterResult { + use masking::ExposeOptionInterface; + + fp_utils::when(merchant_connector_account.is_disabled(), || { + Err(errors::ApiErrorResponse::MerchantConnectorAccountDisabled) + })?; + + let auth_type: types::ConnectorAuthType = merchant_connector_account + .get_connector_account_details() + .parse_value("ConnectorAuthType") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while parsing value for ConnectorAuthType")?; + + // TODO: Take Globalid and convert to connector reference id + let customer_id = customer + .to_owned() + .map(|customer| customer.id.clone()) + .map(std::borrow::Cow::Owned) + .map(common_utils::id_type::CustomerId::try_from) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Invalid global customer generated, not able to convert to reference id", + )?; + + let payment_method = payment_data.payment_attempt.payment_method_type; + + let router_base_url = &state.base_url; + let attempt = &payment_data.payment_attempt; + + let complete_authorize_url = Some(helpers::create_complete_authorize_url( + router_base_url, + attempt, + connector_id, + )); + + let webhook_url = Some(helpers::create_webhook_url( + router_base_url, + &attempt.merchant_id, + connector_id, + )); + + let router_return_url = Some(helpers::create_redirect_url( + router_base_url, + attempt, + connector_id, + None, + )); + + let connector_request_reference_id = payment_data + .payment_intent + .merchant_reference_id + .map(|id| id.get_string_repr().to_owned()) + .unwrap_or(payment_data.payment_attempt.id.get_string_repr().to_owned()); + + // TODO: few fields are repeated in both routerdata and request + let request = types::PaymentsAuthorizeData { + payment_method_data: payment_data + .payment_method_data + .get_required_value("payment_method_data")?, + setup_future_usage: Some(payment_data.payment_intent.setup_future_usage), + mandate_id: None, + off_session: None, + setup_mandate_details: None, + confirm: true, + statement_descriptor_suffix: None, + statement_descriptor: None, + capture_method: Some(payment_data.payment_intent.capture_method), + amount: payment_data + .payment_attempt + .amount_details + .net_amount + .get_amount_as_i64(), + minor_amount: payment_data.payment_attempt.amount_details.net_amount, + currency: payment_data.payment_intent.amount_details.currency, + browser_info: None, + email: None, + customer_name: None, + payment_experience: None, + order_details: None, + order_category: None, + session_token: None, + enrolled_for_3ds: true, + related_transaction_id: None, + payment_method_type: Some(payment_data.payment_attempt.payment_method_subtype), + router_return_url, + webhook_url, + complete_authorize_url, + customer_id: None, + surcharge_details: None, + request_incremental_authorization: matches!( + payment_data + .payment_intent + .request_incremental_authorization, + RequestIncrementalAuthorization::True | RequestIncrementalAuthorization::Default + ), + metadata: payment_data.payment_intent.metadata.expose_option(), + authentication_data: None, + customer_acceptance: None, + charges: None, + merchant_order_reference_id: None, + integrity_object: None, + }; + + // TODO: evaluate the fields in router data, if they are required or not + let router_data = types::RouterData { + flow: PhantomData, + merchant_id: merchant_account.get_id().clone(), + // TODO: evaluate why we need customer id at the connector level. We already have connector customer id. + customer_id, + connector: connector_id.to_owned(), + // TODO: evaluate why we need payment id at the connector level. We already have connector reference id + payment_id: payment_data + .payment_attempt + .payment_id + .get_string_repr() + .to_owned(), + // TODO: evaluate why we need attempt id at the connector level. We already have connector reference id + attempt_id: payment_data + .payment_attempt + .get_id() + .get_string_repr() + .to_owned(), + status: payment_data.payment_attempt.status, + payment_method, + connector_auth_type: auth_type, + description: payment_data + .payment_intent + .description + .as_ref() + .map(|description| description.get_string_repr()) + .map(ToOwned::to_owned), + // TODO: evaluate why we need to send merchant's return url here + // This should be the return url of application, since application takes care of the redirection + return_url: payment_data + .payment_intent + .return_url + .as_ref() + .map(|description| description.get_string_repr()) + .map(ToOwned::to_owned), + // TODO: Create unified address + address: hyperswitch_domain_models::payment_address::PaymentAddress::default(), + auth_type: payment_data.payment_attempt.authentication_type, + connector_meta_data: None, + connector_wallets_details: None, + request, + response: Err(hyperswitch_domain_models::router_data::ErrorResponse::default()), + amount_captured: None, + minor_amount_captured: None, + access_token: None, + session_token: None, + reference_id: None, + payment_method_status: None, + payment_method_token: None, + connector_customer: None, + recurring_mandate_payment_data: None, + // TODO: This has to be generated as the reference id based on the connector configuration + // Some connectros might not accept accept the global id. This has to be done when generating the reference id + connector_request_reference_id, + preprocessing_id: payment_data.payment_attempt.preprocessing_step_id, + #[cfg(feature = "payouts")] + payout_method_data: None, + #[cfg(feature = "payouts")] + quote_id: None, + // TODO: take this based on the env + test_mode: Some(true), + payment_method_balance: None, + connector_api_version: None, + connector_http_status_code: None, + external_latency: None, + apple_pay_flow: None, + frm_metadata: None, + refund_id: None, + dispute_id: None, + connector_response: None, + integrity_check: Ok(()), + additional_merchant_data: None, + header_payload, + }; + + Ok(router_data) +} + +#[cfg(feature = "v2")] #[instrument(skip_all)] #[allow(clippy::too_many_arguments)] pub async fn construct_payment_router_data<'a, F, T>( - _state: &'a SessionState, - _payment_data: PaymentData, - _connector_id: &str, - _merchant_account: &domain::MerchantAccount, + state: &'a SessionState, + payment_data: PaymentData, + connector_id: &str, + merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, - _customer: &'a Option, - _merchant_connector_account: &helpers::MerchantConnectorAccountType, - _merchant_recipient_data: Option, - _header_payload: Option, + customer: &'a Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, + merchant_recipient_data: Option, + header_payload: Option, ) -> RouterResult> where T: TryFrom>, @@ -176,7 +395,7 @@ pub async fn construct_payment_router_data<'a, F, T>( customer: &'a Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult> where T: TryFrom>, @@ -361,6 +580,7 @@ where Op: Debug, D: OperationSessionGetters, { + #[cfg(feature = "v1")] #[allow(clippy::too_many_arguments)] fn generate_response( data: D, @@ -373,8 +593,22 @@ where external_latency: Option, is_latency_header_enabled: Option, ) -> RouterResponse; + + #[cfg(feature = "v2")] + #[allow(clippy::too_many_arguments)] + fn generate_response( + data: D, + customer: Option, + base_url: &str, + operation: Op, + connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig, + connector_http_status_code: Option, + external_latency: Option, + is_latency_header_enabled: Option, + ) -> RouterResponse; } +#[cfg(feature = "v1")] impl ToResponse for api::PaymentsResponse where F: Clone, @@ -530,7 +764,6 @@ where fn generate_response( payment_data: D, _customer: Option, - _auth_flow: services::AuthFlow, _base_url: &str, operation: Op, _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig, @@ -542,7 +775,7 @@ where Ok(services::ApplicationResponse::JsonWithHeaders(( Self { id: payment_intent.id.clone(), - amount_details: api_models::payments::AmountDetails::foreign_from( + amount_details: api_models::payments::AmountDetailsResponse::foreign_from( payment_intent.amount_details.clone(), ), client_secret: payment_intent.client_secret.clone(), @@ -592,6 +825,75 @@ where } } +#[cfg(feature = "v2")] +impl ToResponse for api_models::payments::PaymentsConfirmIntentResponse +where + F: Clone, + Op: Debug, + D: OperationSessionGetters, +{ + #[allow(clippy::too_many_arguments)] + fn generate_response( + payment_data: D, + _customer: Option, + _base_url: &str, + operation: Op, + _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig, + _connector_http_status_code: Option, + _external_latency: Option, + _is_latency_header_enabled: Option, + ) -> RouterResponse { + let payment_intent = payment_data.get_payment_intent(); + let payment_attempt = payment_data.get_payment_attempt(); + + let amount = api_models::payments::ConfirmIntentAmountDetailsResponse::foreign_from(( + &payment_intent.amount_details, + &payment_attempt.amount_details, + )); + + let connector = payment_attempt + .connector + .clone() + .get_required_value("connector") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Connector is none when constructing response")?; + + let merchant_connector_id = payment_attempt + .merchant_connector_id + .clone() + .get_required_value("merchant_connector_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Merchant connector id is none when constructing response")?; + + let error = payment_attempt + .error + .clone() + .map(api_models::payments::ErrorDetails::foreign_from); + + let response = Self { + id: payment_intent.id.clone(), + status: payment_intent.status, + amount, + connector, + client_secret: payment_intent.client_secret.clone(), + created: payment_intent.created_at, + payment_method_data: None, + payment_method_type: payment_attempt.payment_method_type, + payment_method_subtype: payment_attempt.payment_method_subtype, + connector_transaction_id: payment_attempt.connector_payment_id.clone(), + connector_reference_id: None, + merchant_connector_id, + browser_info: None, + error, + }; + + Ok(services::ApplicationResponse::JsonWithHeaders(( + response, + vec![], + ))) + } +} + #[cfg(feature = "v1")] impl ToResponse for api::PaymentsPostSessionTokensResponse where @@ -679,6 +981,7 @@ impl ForeignTryFrom<(MinorUnit, Option, Option, Currency)> } } +#[cfg(feature = "v1")] impl ToResponse for api::VerifyResponse where F: Clone, @@ -2004,13 +2307,35 @@ impl TryFrom> } impl ConnectorTransactionId for Helcim { + #[cfg(feature = "v1")] fn connector_transaction_id( &self, payment_attempt: storage::PaymentAttempt, ) -> Result, errors::ApiErrorResponse> { if payment_attempt.get_connector_payment_id().is_none() { let metadata = - Self::connector_transaction_id(self, &payment_attempt.connector_metadata); + Self::connector_transaction_id(self, payment_attempt.connector_metadata.as_ref()); + metadata.map_err(|_| errors::ApiErrorResponse::ResourceIdNotFound) + } else { + Ok(payment_attempt + .get_connector_payment_id() + .map(ToString::to_string)) + } + } + + #[cfg(feature = "v2")] + fn connector_transaction_id( + &self, + payment_attempt: storage::PaymentAttempt, + ) -> Result, errors::ApiErrorResponse> { + if payment_attempt.get_connector_payment_id().is_none() { + let metadata = Self::connector_transaction_id( + self, + payment_attempt + .connector_metadata + .as_ref() + .map(|connector_metadata| connector_metadata.peek()), + ); metadata.map_err(|_| errors::ApiErrorResponse::ResourceIdNotFound) } else { Ok(payment_attempt @@ -2021,11 +2346,28 @@ impl ConnectorTransactionId for Helcim { } impl ConnectorTransactionId for Nexinets { + #[cfg(feature = "v1")] fn connector_transaction_id( &self, payment_attempt: storage::PaymentAttempt, ) -> Result, errors::ApiErrorResponse> { - let metadata = Self::connector_transaction_id(self, &payment_attempt.connector_metadata); + let metadata = + Self::connector_transaction_id(self, payment_attempt.connector_metadata.as_ref()); + metadata.map_err(|_| errors::ApiErrorResponse::ResourceIdNotFound) + } + + #[cfg(feature = "v2")] + fn connector_transaction_id( + &self, + payment_attempt: storage::PaymentAttempt, + ) -> Result, errors::ApiErrorResponse> { + let metadata = Self::connector_transaction_id( + self, + payment_attempt + .connector_metadata + .as_ref() + .map(|connector_metadata| connector_metadata.peek()), + ); metadata.map_err(|_| errors::ApiErrorResponse::ResourceIdNotFound) } } @@ -2258,7 +2600,51 @@ impl TryFrom> for types::PaymentsSessionD type Error = error_stack::Report; fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { - todo!(); + let payment_data = additional_data.payment_data.clone(); + + let order_details = additional_data + .payment_data + .payment_intent + .order_details + .map(|order_details| { + order_details + .iter() + .map(|data| data.to_owned().expose()) + .collect() + }); + + let surcharge_amount = payment_data + .surcharge_details + .as_ref() + .map(|surcharge_details| surcharge_details.get_total_surcharge_amount()) + .unwrap_or_default(); + + let amount = payment_data.payment_intent.amount_details.order_amount; + + let shipping_cost = payment_data + .payment_intent + .amount_details + .shipping_cost + .unwrap_or_default(); + + // net_amount here would include amount, surcharge_amount and shipping_cost + let net_amount = amount + surcharge_amount + shipping_cost; + + Ok(Self { + amount: amount.get_amount_as_i64(), //need to change once we move to connector module + minor_amount: amount, + currency: payment_data.currency, + country: payment_data.address.get_payment_method_billing().and_then( + |billing_address| { + billing_address + .address + .as_ref() + .and_then(|address| address.country) + }, + ), + order_details, + surcharge_details: payment_data.surcharge_details, + }) } } @@ -2287,26 +2673,20 @@ impl TryFrom> for types::PaymentsSessionD .collect::, _>>() }) .transpose()?; + let surcharge_amount = payment_data .surcharge_details .as_ref() .map(|surcharge_details| surcharge_details.get_total_surcharge_amount()) .unwrap_or_default(); - #[cfg(feature = "v1")] + let amount = payment_data.payment_intent.amount; - #[cfg(feature = "v2")] - let amount = payment_data.payment_intent.amount_details.order_amount; - #[cfg(feature = "v1")] + let shipping_cost = payment_data .payment_intent .shipping_cost .unwrap_or_default(); - #[cfg(feature = "v2")] - let shipping_cost = payment_data - .payment_intent - .amount_details - .shipping_cost - .unwrap_or_default(); + // net_amount here would include amount, surcharge_amount and shipping_cost let net_amount = amount + surcharge_amount + shipping_cost; @@ -2638,42 +3018,73 @@ impl ForeignFrom for router_request_types::CustomerDetails { } } +/// The response amount details in the confirm intent response will have the combined fields from +/// intent amount details and attempt amount details. #[cfg(feature = "v2")] -impl ForeignFrom - for hyperswitch_domain_models::payments::AmountDetails +impl + ForeignFrom<( + &hyperswitch_domain_models::payments::AmountDetails, + &hyperswitch_domain_models::payments::payment_attempt::AttemptAmountDetails, + )> for api_models::payments::ConfirmIntentAmountDetailsResponse { - fn foreign_from(amount_details: api_models::payments::AmountDetails) -> Self { + fn foreign_from( + (intent_amount_details, attempt_amount_details): ( + &hyperswitch_domain_models::payments::AmountDetails, + &hyperswitch_domain_models::payments::payment_attempt::AttemptAmountDetails, + ), + ) -> Self { Self { - order_amount: amount_details.order_amount().into(), - currency: amount_details.currency(), - shipping_cost: amount_details.shipping_cost(), - tax_details: amount_details.order_tax_amount().map(|order_tax_amount| { - diesel_models::TaxDetails { - default: Some(diesel_models::DefaultTax { order_tax_amount }), - payment_method_type: None, - } - }), - skip_external_tax_calculation: - hyperswitch_domain_models::payments::TaxCalculationOverride::foreign_from( - amount_details.skip_external_tax_calculation(), - ), - skip_surcharge_calculation: - hyperswitch_domain_models::payments::SurchargeCalculationOverride::foreign_from( - amount_details.skip_surcharge_calculation(), - ), - surcharge_amount: amount_details.surcharge_amount(), - tax_on_surcharge: amount_details.tax_on_surcharge(), + order_amount: intent_amount_details.order_amount, + currency: intent_amount_details.currency, + shipping_cost: attempt_amount_details.shipping_cost, + order_tax_amount: attempt_amount_details.order_tax_amount, + skip_external_tax_calculation: common_enums::TaxCalculationOverride::foreign_from( + intent_amount_details.skip_external_tax_calculation, + ), + skip_surcharge_calculation: common_enums::SurchargeCalculationOverride::foreign_from( + intent_amount_details.skip_surcharge_calculation, + ), + surcharge_amount: attempt_amount_details.surcharge_amount, + tax_on_surcharge: attempt_amount_details.tax_on_surcharge, + net_amount: attempt_amount_details.net_amount, + amount_to_capture: attempt_amount_details.amount_to_capture, + amount_capturable: attempt_amount_details.amount_capturable, + amount_captured: intent_amount_details.amount_captured, + } + } +} + +#[cfg(feature = "v2")] +impl ForeignFrom + for api_models::payments::ErrorDetails +{ + fn foreign_from( + amount_details: hyperswitch_domain_models::payments::payment_attempt::ErrorDetails, + ) -> Self { + let hyperswitch_domain_models::payments::payment_attempt::ErrorDetails { + code, + message, + reason, + unified_code, + unified_message, + } = amount_details; + + Self { + code, + message: reason.unwrap_or(message), + unified_code, + unified_message, } } } #[cfg(feature = "v2")] impl ForeignFrom - for api_models::payments::AmountDetails + for api_models::payments::AmountDetailsResponse { fn foreign_from(amount_details: hyperswitch_domain_models::payments::AmountDetails) -> Self { - Self::new(api_models::payments::AmountDetailsSetter { - order_amount: amount_details.order_amount.into(), + Self { + order_amount: amount_details.order_amount, currency: amount_details.currency, shipping_cost: amount_details.shipping_cost, order_tax_amount: amount_details.tax_details.and_then(|tax_details| { @@ -2687,7 +3098,7 @@ impl ForeignFrom ), surcharge_amount: amount_details.surcharge_amount, tax_on_surcharge: amount_details.tax_on_surcharge, - }) + } } } diff --git a/crates/router/src/core/pm_auth.rs b/crates/router/src/core/pm_auth.rs index 2c3f76c88a..b8672ee79b 100644 --- a/crates/router/src/core/pm_auth.rs +++ b/crates/router/src/core/pm_auth.rs @@ -51,7 +51,7 @@ pub async fn create_link_token( merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, payload: api_models::pm_auth::LinkTokenCreateRequest, - headers: Option, + headers: Option, ) -> RouterResponse { let db = &*state.store; @@ -216,7 +216,7 @@ pub async fn create_link_token( _merchant_account: domain::MerchantAccount, _key_store: domain::MerchantKeyStore, _payload: api_models::pm_auth::LinkTokenCreateRequest, - _headers: Option, + _headers: Option, ) -> RouterResponse { todo!() } diff --git a/crates/router/src/core/user/sample_data.rs b/crates/router/src/core/user/sample_data.rs index 5058ad600e..453b3edf00 100644 --- a/crates/router/src/core/user/sample_data.rs +++ b/crates/router/src/core/user/sample_data.rs @@ -1,6 +1,6 @@ use api_models::user::sample_data::SampleDataRequest; use common_utils::errors::ReportSwitchExt; -use diesel_models::{user::sample_data::PaymentAttemptBatchNew, RefundNew}; +use diesel_models::RefundNew; use error_stack::ResultExt; use hyperswitch_domain_models::payments::PaymentIntent; @@ -41,7 +41,7 @@ pub async fn generate_sample_data_for_user( let (payment_intents, payment_attempts, refunds): ( Vec, - Vec, + Vec, Vec, ) = sample_data.into_iter().fold( (Vec::new(), Vec::new(), Vec::new()), diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index a6a9c36bfd..def8690791 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -290,7 +290,7 @@ pub async fn construct_refund_router_data<'a, F>( let webhook_url = Some(helpers::create_webhook_url( &state.base_url.clone(), merchant_account.get_id(), - &connector_id.to_string(), + connector_id, )); let test_mode: Option = merchant_connector_account.is_test_mode_on(); diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index 19670fc8cc..66de5106e6 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -3,13 +3,11 @@ use std::{str::FromStr, time::Instant}; use actix_web::FromRequest; #[cfg(feature = "payouts")] use api_models::payouts as payout_models; -use api_models::{ - payments::HeaderPayload, - webhooks::{self, WebhookResponseTracker}, -}; +use api_models::webhooks::{self, WebhookResponseTracker}; use common_utils::{errors::ReportSwitchExt, events::ApiEventsType}; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ + payments::HeaderPayload, router_request_types::VerifyWebhookSourceRequestData, router_response_types::{VerifyWebhookSourceResponseData, VerifyWebhookStatus}, }; diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 3ef9a2d037..7ff580e37e 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1416,7 +1416,7 @@ impl PaymentAttemptInterface for KafkaStore { } #[cfg(feature = "v2")] - async fn update_payment_attempt_with_attempt_id( + async fn update_payment_attempt( &self, key_manager_state: &KeyManagerState, merchant_key_store: &domain::MerchantKeyStore, @@ -1426,7 +1426,7 @@ impl PaymentAttemptInterface for KafkaStore { ) -> CustomResult { let attempt = self .diesel_store - .update_payment_attempt_with_attempt_id( + .update_payment_attempt( key_manager_state, merchant_key_store, this.clone(), diff --git a/crates/router/src/db/user/sample_data.rs b/crates/router/src/db/user/sample_data.rs index 3add7b744d..37007b13f2 100644 --- a/crates/router/src/db/user/sample_data.rs +++ b/crates/router/src/db/user/sample_data.rs @@ -1,10 +1,11 @@ use common_utils::types::keymanager::KeyManagerState; +#[cfg(feature = "v1")] +use diesel_models::user::sample_data::PaymentAttemptBatchNew; use diesel_models::{ dispute::{Dispute, DisputeNew}, errors::DatabaseError, query::user::sample_data as sample_data_queries, refund::{Refund, RefundNew}, - user::sample_data::PaymentAttemptBatchNew, }; use error_stack::{Report, ResultExt}; use futures::{future::try_join_all, FutureExt}; diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index ef30c99c00..0eff78d38b 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -87,6 +87,7 @@ pub mod headers { pub const X_APP_ID: &str = "x-app-id"; pub const X_REDIRECT_URI: &str = "x-redirect-uri"; pub const X_TENANT_ID: &str = "x-tenant-id"; + pub const X_CLIENT_SECRET: &str = "X-Client-Secret"; pub const X_WP_API_VERSION: &str = "WP-Api-Version"; } diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 274ed6a5cc..33699ed266 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -43,7 +43,7 @@ use super::payouts::*; ))] use super::pm_auth; #[cfg(feature = "oltp")] -use super::poll::retrieve_poll_status; +use super::poll; #[cfg(feature = "olap")] use super::routing; #[cfg(feature = "olap")] @@ -57,7 +57,7 @@ use super::{ #[cfg(feature = "v1")] use super::{apple_pay_certificates_migration, blocklist, payment_link, webhook_events}; #[cfg(any(feature = "olap", feature = "oltp"))] -use super::{configs::*, customers::*, payments::*}; +use super::{configs::*, customers::*, payments}; #[cfg(all(any(feature = "olap", feature = "oltp"), feature = "v1"))] use super::{mandates::*, refunds::*}; #[cfg(feature = "olap")] @@ -517,17 +517,21 @@ pub struct Payments; impl Payments { pub fn server(state: AppState) -> Scope { let mut route = web::scope("/v2/payments").app_data(web::Data::new(state)); - route = route - .service(web::resource("/create-intent").route(web::post().to(payments_create_intent))); - route = route - .service( - web::resource("/{payment_id}/saved_payment_methods") - .route(web::get().to(list_customer_payment_method_for_payment)), - ) - .service( - web::resource("/{payment_id}/create_external_sdk_tokens") - .route(web::post().to(payments_connector_session)), - ); + route = route.service( + web::resource("/create-intent").route(web::post().to(payments::payments_create_intent)), + ); + + route = route.service( + web::scope("/{payment_id}") + .service( + web::resource("/confirm-intent") + .route(web::post().to(payments::payment_confirm_intent)), + ) + .service( + web::resource("/create-external-sdk-tokens") + .route(web::post().to(payments::payments_connector_session)), + ), + ); route } @@ -543,106 +547,114 @@ impl Payments { route = route .service( web::resource("/list") - .route(web::get().to(payments_list)) - .route(web::post().to(payments_list_by_filter)), + .route(web::get().to(payments::payments_list)) + .route(web::post().to(payments::payments_list_by_filter)), ) .service( web::resource("/profile/list") - .route(web::get().to(profile_payments_list)) - .route(web::post().to(profile_payments_list_by_filter)), + .route(web::get().to(payments::profile_payments_list)) + .route(web::post().to(payments::profile_payments_list_by_filter)), + ) + .service( + web::resource("/filter") + .route(web::post().to(payments::get_filters_for_payments)), + ) + .service( + web::resource("/v2/filter").route(web::get().to(payments::get_payment_filters)), + ) + .service( + web::resource("/aggregate") + .route(web::get().to(payments::get_payments_aggregates)), ) - .service(web::resource("/filter").route(web::post().to(get_filters_for_payments))) - .service(web::resource("/v2/filter").route(web::get().to(get_payment_filters))) - .service(web::resource("/aggregate").route(web::get().to(get_payments_aggregates))) .service( web::resource("/profile/aggregate") - .route(web::get().to(get_payments_aggregates_profile)), + .route(web::get().to(payments::get_payments_aggregates_profile)), ) .service( web::resource("/v2/profile/filter") - .route(web::get().to(get_payment_filters_profile)), + .route(web::get().to(payments::get_payment_filters_profile)), ) .service( web::resource("/{payment_id}/manual-update") - .route(web::put().to(payments_manual_update)), + .route(web::put().to(payments::payments_manual_update)), ) } #[cfg(feature = "oltp")] { route = route - .service(web::resource("").route(web::post().to(payments_create))) + .service(web::resource("").route(web::post().to(payments::payments_create))) .service( web::resource("/session_tokens") - .route(web::post().to(payments_connector_session)), + .route(web::post().to(payments::payments_connector_session)), ) .service( web::resource("/sync") - .route(web::post().to(payments_retrieve_with_gateway_creds)), + .route(web::post().to(payments::payments_retrieve_with_gateway_creds)), ) .service( web::resource("/{payment_id}") - .route(web::get().to(payments_retrieve)) - .route(web::post().to(payments_update)), + .route(web::get().to(payments::payments_retrieve)) + .route(web::post().to(payments::payments_update)), ) .service( - web::resource("/{payment_id}/post_session_tokens").route(web::post().to(payments_post_session_tokens)), + web::resource("/{payment_id}/post_session_tokens").route(web::post().to(payments::payments_post_session_tokens)), ) .service( - web::resource("/{payment_id}/confirm").route(web::post().to(payments_confirm)), + web::resource("/{payment_id}/confirm").route(web::post().to(payments::payments_confirm)), ) .service( - web::resource("/{payment_id}/cancel").route(web::post().to(payments_cancel)), + web::resource("/{payment_id}/cancel").route(web::post().to(payments::payments_cancel)), ) .service( - web::resource("/{payment_id}/capture").route(web::post().to(payments_capture)), + web::resource("/{payment_id}/capture").route(web::post().to(payments::payments_capture)), ) .service( web::resource("/{payment_id}/approve") - .route(web::post().to(payments_approve)), + .route(web::post().to(payments::payments_approve)), ) .service( web::resource("/{payment_id}/reject") - .route(web::post().to(payments_reject)), + .route(web::post().to(payments::payments_reject)), ) .service( web::resource("/redirect/{payment_id}/{merchant_id}/{attempt_id}") - .route(web::get().to(payments_start)), + .route(web::get().to(payments::payments_start)), ) .service( web::resource( "/{payment_id}/{merchant_id}/redirect/response/{connector}/{creds_identifier}", ) - .route(web::get().to(payments_redirect_response_with_creds_identifier)), + .route(web::get().to(payments::payments_redirect_response_with_creds_identifier)), ) .service( web::resource("/{payment_id}/{merchant_id}/redirect/response/{connector}") - .route(web::get().to(payments_redirect_response)) - .route(web::post().to(payments_redirect_response)) + .route(web::get().to(payments::payments_redirect_response)) + .route(web::post().to(payments::payments_redirect_response)) ) .service( web::resource("/{payment_id}/{merchant_id}/redirect/complete/{connector}") - .route(web::get().to(payments_complete_authorize_redirect)) - .route(web::post().to(payments_complete_authorize_redirect)), + .route(web::get().to(payments::payments_complete_authorize_redirect)) + .route(web::post().to(payments::payments_complete_authorize_redirect)), ) .service( web::resource("/{payment_id}/complete_authorize") - .route(web::post().to(payments_complete_authorize)), + .route(web::post().to(payments::payments_complete_authorize)), ) .service( - web::resource("/{payment_id}/incremental_authorization").route(web::post().to(payments_incremental_authorization)), + web::resource("/{payment_id}/incremental_authorization").route(web::post().to(payments::payments_incremental_authorization)), ) .service( - web::resource("/{payment_id}/{merchant_id}/authorize/{connector}").route(web::post().to(post_3ds_payments_authorize)), + web::resource("/{payment_id}/{merchant_id}/authorize/{connector}").route(web::post().to(payments::post_3ds_payments_authorize)), ) .service( - web::resource("/{payment_id}/3ds/authentication").route(web::post().to(payments_external_authentication)), + web::resource("/{payment_id}/3ds/authentication").route(web::post().to(payments::payments_external_authentication)), ) .service( - web::resource("/{payment_id}/extended_card_info").route(web::get().to(retrieve_extended_card_info)), + web::resource("/{payment_id}/extended_card_info").route(web::get().to(payments::retrieve_extended_card_info)), ) .service( web::resource("{payment_id}/calculate_tax") - .route(web::post().to(payments_dynamic_tax_calculation)), + .route(web::post().to(payments::payments_dynamic_tax_calculation)), ); } route @@ -1455,12 +1467,14 @@ impl ApplePayCertificatesMigration { pub struct Poll; -#[cfg(feature = "oltp")] +#[cfg(all(feature = "oltp", feature = "v1"))] impl Poll { pub fn server(config: AppState) -> Scope { web::scope("/poll") .app_data(web::Data::new(config)) - .service(web::resource("/status/{poll_id}").route(web::get().to(retrieve_poll_status))) + .service( + web::resource("/status/{poll_id}").route(web::get().to(poll::retrieve_poll_status)), + ) } } diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index cb16d6e050..a0c5c0f990 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -137,6 +137,7 @@ impl From for ApiIdentifier { | Flow::PaymentsCompleteAuthorize | Flow::PaymentsManualUpdate | Flow::SessionUpdateTaxCalculation + | Flow::PaymentsConfirmIntent | Flow::PaymentsCreateIntent | Flow::PaymentsPostSessionTokens => Self::Payments, diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index eba1fa84c9..f13a873473 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -5,9 +5,9 @@ use crate::{ pub mod helpers; use actix_web::{web, Responder}; -use api_models::payments::HeaderPayload; use common_enums::EntityType; use error_stack::report; +use hyperswitch_domain_models::payments::HeaderPayload; use masking::PeekInterface; use router_env::{env, instrument, logger, tracing, types, Flow}; @@ -138,9 +138,8 @@ pub async fn payments_create_intent( auth.merchant_account, auth.profile, auth.key_store, - payments::operations::PaymentCreateIntent, + payments::operations::PaymentIntentCreate, req, - api::AuthFlow::Client, header_payload.clone(), ) }, @@ -1978,3 +1977,125 @@ pub async fn get_payments_aggregates_profile( )) .await } + +#[cfg(feature = "v2")] +/// A private module to hold internal types to be used in route handlers. +/// This is because we will need to implement certain traits on these types which will have the resource id +/// But the api payload will not contain the resource id +/// So these types can hold the resource id along with actual api payload, on which api event and locking action traits can be implemented +mod internal_payload_types { + use super::*; + + // Serialize is implemented because of api events + #[derive(Debug, serde::Serialize)] + pub struct PaymentsGenericRequestWithResourceId { + pub global_payment_id: common_utils::id_type::GlobalPaymentId, + #[serde(flatten)] + pub payload: T, + } + + impl GetLockingInput for PaymentsGenericRequestWithResourceId { + fn get_locking_input(&self, flow: F) -> api_locking::LockAction + where + F: types::FlowMetric, + lock_utils::ApiIdentifier: From, + { + api_locking::LockAction::Hold { + input: api_locking::LockingInput { + unique_locking_key: self.global_payment_id.get_string_repr().to_owned(), + api_identifier: lock_utils::ApiIdentifier::from(flow), + override_lock_retries: None, + }, + } + } + } + + impl common_utils::events::ApiEventMetric + for PaymentsGenericRequestWithResourceId + { + fn get_api_event_type(&self) -> Option { + Some(common_utils::events::ApiEventsType::Payment { + payment_id: self.global_payment_id.clone(), + }) + } + } +} + +#[cfg(feature = "v2")] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsConfirmIntent, payment_id))] +pub async fn payment_confirm_intent( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Json, + path: web::Path, +) -> impl Responder { + use hyperswitch_domain_models::payments::PaymentConfirmData; + + let flow = Flow::PaymentsConfirmIntent; + + // TODO: Populate browser information into the payload + // if let Err(err) = helpers::populate_ip_into_browser_info(&req, &mut payload) { + // return api::log_and_return_error_response(err); + // } + + let global_payment_id = path.into_inner(); + tracing::Span::current().record("payment_id", global_payment_id.get_string_repr()); + + let internal_payload = internal_payload_types::PaymentsGenericRequestWithResourceId { + global_payment_id, + payload: json_payload.into_inner(), + }; + + let header_payload = match HeaderPayload::foreign_try_from(req.headers()) { + Ok(headers) => headers, + Err(err) => { + return api::log_and_return_error_response(err); + } + }; + + // TODO: handle client secret auth + // let (auth_type, auth_flow) = + // match auth::check_client_secret_and_get_auth(req.headers(), &payload) { + // Ok(auth) => auth, + // Err(e) => return api::log_and_return_error_response(e), + // }; + + let locking_action = internal_payload.get_locking_input(flow.clone()); + + Box::pin(api::server_wrap( + flow, + state, + &req, + internal_payload, + |state, auth: auth::AuthenticationDataV2, req, req_state| async { + let payment_id = req.global_payment_id; + let request = req.payload; + + let operation = payments::operations::PaymentIntentConfirm; + + Box::pin(payments::payments_core::< + api_types::Authorize, + api_models::payments::PaymentsConfirmIntentResponse, + _, + _, + _, + PaymentConfirmData, + >( + state, + req_state, + auth.merchant_account, + auth.profile, + auth.key_store, + operation, + request, + payment_id, + payments::CallConnectorAction::Trigger, + header_payload.clone(), + )) + .await + }, + &auth::PublishableKeyAuth, + locking_action, + )) + .await +} diff --git a/crates/router/src/routes/payments/helpers.rs b/crates/router/src/routes/payments/helpers.rs index 7ff3f1d11a..5b0ef815b2 100644 --- a/crates/router/src/routes/payments/helpers.rs +++ b/crates/router/src/routes/payments/helpers.rs @@ -7,6 +7,7 @@ use crate::{ utils::{Encode, ValueExt}, }; +#[cfg(feature = "v1")] pub fn populate_ip_into_browser_info( req: &actix_web::HttpRequest, payload: &mut api::PaymentsRequest, diff --git a/crates/router/src/routes/pm_auth.rs b/crates/router/src/routes/pm_auth.rs index cc0ab02b55..31a2a0ba32 100644 --- a/crates/router/src/routes/pm_auth.rs +++ b/crates/router/src/routes/pm_auth.rs @@ -22,12 +22,13 @@ pub async fn link_token_create( Err(e) => return api::log_and_return_error_response(e), }; - let header_payload = match api_types::payments::HeaderPayload::foreign_try_from(req.headers()) { - Ok(headers) => headers, - Err(err) => { - return api::log_and_return_error_response(err); - } - }; + let header_payload = + match hyperswitch_domain_models::payments::HeaderPayload::foreign_try_from(req.headers()) { + Ok(headers) => headers, + Err(err) => { + return api::log_and_return_error_response(err); + } + }; Box::pin(api::server_wrap( flow, diff --git a/crates/router/src/routes/poll.rs b/crates/router/src/routes/poll.rs index bbb2a9a63a..aefda58921 100644 --- a/crates/router/src/routes/poll.rs +++ b/crates/router/src/routes/poll.rs @@ -8,6 +8,7 @@ use crate::{ types::api::PollId, }; +#[cfg(feature = "v1")] /// Poll - Retrieve Poll Status #[utoipa::path( get, diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 0d84ba9c30..6f6eb38166 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -1225,6 +1225,7 @@ pub trait Authenticate { } } +#[cfg(feature = "v1")] impl Authenticate for api_models::payments::PaymentsRequest { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() @@ -1333,9 +1334,9 @@ pub fn build_redirection_form( } (PreEscaped(format!(r#"