diff --git a/config/development.toml b/config/development.toml index 4db925ae82..24645bf46a 100644 --- a/config/development.toml +++ b/config/development.toml @@ -319,4 +319,4 @@ card.credit = {connector_list = "stripe,adyen,authorizedotnet,globalpay,worldpay card.debit = {connector_list = "stripe,adyen,authorizedotnet,globalpay,worldpay,multisafepay,nmi,nexinets,noon"} [connector_request_reference_id_config] -merchant_ids_send_payment_id_as_connector_request_id = [] +merchant_ids_send_payment_id_as_connector_request_id = [] \ No newline at end of file diff --git a/crates/router/src/compatibility/stripe/customers.rs b/crates/router/src/compatibility/stripe/customers.rs index 1de62b6fe3..0f90b1b001 100644 --- a/crates/router/src/compatibility/stripe/customers.rs +++ b/crates/router/src/compatibility/stripe/customers.rs @@ -170,8 +170,8 @@ pub async fn list_customer_payment_method_api( path: web::Path, json_payload: web::Query, ) -> HttpResponse { - let customer_id = path.into_inner(); let payload = json_payload.into_inner(); + let customer_id = path.into_inner(); let flow = Flow::CustomerPaymentMethodsList; wrap::compatibility_api_wrap::< @@ -189,12 +189,12 @@ pub async fn list_customer_payment_method_api( &req, payload, |state, auth, req| { - cards::list_customer_payment_method( + cards::do_list_customer_pm_fetch_customer_if_not_passed( state, auth.merchant_account, auth.key_store, - req, - &customer_id, + Some(req), + Some(customer_id.as_str()), ) }, &auth::ApiKeyAuth, diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index eb1b97aca5..14d699eb06 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -1499,15 +1499,47 @@ async fn filter_payment_mandate_based( Ok(recurring_filter) } +pub async fn do_list_customer_pm_fetch_customer_if_not_passed( + state: &routes::AppState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + req: Option, + customer_id: Option<&str>, +) -> errors::RouterResponse { + let db = &*state.store; + if let Some(customer_id) = customer_id { + list_customer_payment_method(state, merchant_account, key_store, None, customer_id).await + } else { + let cloned_secret = req.and_then(|r| r.client_secret.as_ref().cloned()); + let payment_intent = helpers::verify_payment_intent_time_and_client_secret( + db, + &merchant_account, + cloned_secret, + ) + .await?; + let customer_id = payment_intent + .as_ref() + .and_then(|intent| intent.customer_id.to_owned()) + .ok_or(errors::ApiErrorResponse::CustomerNotFound)?; + list_customer_payment_method( + state, + merchant_account, + key_store, + payment_intent, + &customer_id, + ) + .await + } +} + pub async fn list_customer_payment_method( state: &routes::AppState, merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, - req: api::PaymentMethodListRequest, + payment_intent: Option, customer_id: &str, ) -> errors::RouterResponse { let db = &*state.store; - db.find_customer_by_customer_id_merchant_id( customer_id, &merchant_account.merchant_id, @@ -1556,15 +1588,10 @@ pub async fn list_customer_payment_method( parent_payment_method_token, pma.payment_method ); - let payment_intent = helpers::verify_payment_intent_time_and_client_secret( - db, - &merchant_account, - req.client_secret.clone(), - ) - .await?; let current_datetime_utc = common_utils::date_time::now(); let time_eslapsed = current_datetime_utc - payment_intent + .as_ref() .map(|intent| intent.created_at) .unwrap_or_else(|| current_datetime_utc); redis_conn diff --git a/crates/router/src/openapi.rs b/crates/router/src/openapi.rs index 5e1e565b50..b1a99fcbe1 100644 --- a/crates/router/src/openapi.rs +++ b/crates/router/src/openapi.rs @@ -90,6 +90,7 @@ Never share your secret api keys. Keep them guarded and secure. crate::routes::payment_methods::create_payment_method_api, crate::routes::payment_methods::list_payment_method_api, crate::routes::payment_methods::list_customer_payment_method_api, + crate::routes::payment_methods::list_customer_payment_method_api_client, crate::routes::payment_methods::payment_method_retrieve_api, crate::routes::payment_methods::payment_method_update_api, crate::routes::payment_methods::payment_method_delete_api, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 34446d4674..63295353fc 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -222,14 +222,18 @@ impl Customers { route = route .service(web::resource("").route(web::post().to(customers_create))) .service( - web::resource("/{customer_id}") - .route(web::get().to(customers_retrieve)) - .route(web::post().to(customers_update)) - .route(web::delete().to(customers_delete)), + web::resource("/payment_methods") + .route(web::get().to(list_customer_payment_method_api_client)), ) .service( web::resource("/{customer_id}/payment_methods") .route(web::get().to(list_customer_payment_method_api)), + ) + .service( + web::resource("/{customer_id}") + .route(web::get().to(customers_retrieve)) + .route(web::post().to(customers_update)) + .route(web::delete().to(customers_delete)), ); } route diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index b1b10eb7bf..0e6f5c0e24 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -98,7 +98,7 @@ pub async fn list_payment_method_api( /// To filter and list the applicable payment methods for a particular Customer ID #[utoipa::path( get, - path = "/customer/{customer_id}/payment_methods", + path = "/customers/{customer_id}/payment_methods", params ( ("customer_id" = String, Path, description = "The unique identifier for the customer account"), ("accepted_country" = Vec, Query, description = "The two-letter ISO currency code"), @@ -115,39 +115,93 @@ pub async fn list_payment_method_api( ), tag = "Payment Methods", operation_id = "List all Payment Methods for a Customer", - security(("api_key" = []), ("ephemeral_key" = [])) + security(("api_key" = [])) )] #[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))] pub async fn list_customer_payment_method_api( state: web::Data, customer_id: web::Path<(String,)>, req: HttpRequest, - json_payload: web::Query, + query_payload: web::Query, ) -> HttpResponse { let flow = Flow::CustomerPaymentMethodsList; - let customer_id = customer_id.into_inner().0; - - let auth_type = match auth::is_ephemeral_auth(req.headers(), &*state.store, &customer_id).await - { - Ok(auth_type) => auth_type, - Err(err) => return api::log_and_return_error_response(err), + let payload = query_payload.into_inner(); + let (auth, _) = match auth::check_client_secret_and_get_auth(req.headers(), &payload) { + Ok((auth, _auth_flow)) => (auth, _auth_flow), + Err(e) => return api::log_and_return_error_response(e), }; - + let customer_id = customer_id.into_inner().0; api::server_wrap( flow, state.get_ref(), &req, - json_payload.into_inner(), + payload, |state, auth, req| { - cards::list_customer_payment_method( + cards::do_list_customer_pm_fetch_customer_if_not_passed( state, auth.merchant_account, auth.key_store, - req, - &customer_id, + Some(req), + Some(&customer_id), ) }, - &*auth_type, + &*auth, + ) + .await +} + +/// List payment methods for a Customer +/// +/// To filter and list the applicable payment methods for a particular Customer ID +#[utoipa::path( + get, + path = "/customers/payment_methods", + params ( + ("client-secret" = String, Path, description = "A secret known only to your application and the authorization server"), + ("customer_id" = String, Path, description = "The unique identifier for the customer account"), + ("accepted_country" = Vec, Query, description = "The two-letter ISO currency code"), + ("accepted_currency" = Vec, Path, description = "The three-letter ISO currency code"), + ("minimum_amount" = i64, Query, description = "The minimum amount accepted for processing by the particular payment method."), + ("maximum_amount" = i64, Query, description = "The maximum amount amount accepted for processing by the particular payment method."), + ("recurring_payment_enabled" = bool, Query, description = "Indicates whether the payment method is eligible for recurring payments"), + ("installment_payment_enabled" = bool, Query, description = "Indicates whether the payment method is eligible for installment payments"), + ), + responses( + (status = 200, description = "Payment Methods retrieved for customer tied to its respective client-secret passed in the param", body = CustomerPaymentMethodsListResponse), + (status = 400, description = "Invalid Data"), + (status = 404, description = "Payment Methods does not exist in records") + ), + tag = "Payment Methods", + operation_id = "List all Payment Methods for a Customer", + security(("publishable_key" = [])) +)] +#[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))] +pub async fn list_customer_payment_method_api_client( + state: web::Data, + req: HttpRequest, + query_payload: web::Query, +) -> HttpResponse { + let flow = Flow::CustomerPaymentMethodsList; + let payload = query_payload.into_inner(); + let (auth, _) = match auth::check_client_secret_and_get_auth(req.headers(), &payload) { + Ok((auth, _auth_flow)) => (auth, _auth_flow), + Err(e) => return api::log_and_return_error_response(e), + }; + api::server_wrap( + flow, + state.get_ref(), + &req, + payload, + |state, auth, req| { + cards::do_list_customer_pm_fetch_customer_if_not_passed( + state, + auth.merchant_account, + auth.key_store, + Some(req), + None, + ) + }, + &*auth, ) .await } diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index c7aa5135af..352bb16cc3 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -422,7 +422,6 @@ where } .into()); } - Ok((Box::new(ApiKeyAuth), api::AuthFlow::Merchant)) } diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 3a5acf82b6..6b172aa7fa 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -129,7 +129,47 @@ ] } }, - "/customer/{customer_id}/payment_methods": { + "/customers": { + "post": { + "tags": [ + "Customers" + ], + "summary": "Create Customer", + "description": "Create Customer\n\nCreate a customer object and store the customer details to be reused for future payments. Incase the customer already exists in the system, this API will respond with the customer details.", + "operationId": "Create a Customer", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Customer Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerResponse" + } + } + } + }, + "400": { + "description": "Invalid data" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/customers/payment_methods": { "get": { "tags": [ "Payment Methods" @@ -138,6 +178,15 @@ "description": "List payment methods for a Customer\n\nTo filter and list the applicable payment methods for a particular Customer ID", "operationId": "List all Payment Methods for a Customer", "parameters": [ + { + "name": "client-secret", + "in": "path", + "description": "A secret known only to your application and the authorization server", + "required": true, + "schema": { + "type": "string" + } + }, { "name": "customer_id", "in": "path", @@ -212,7 +261,7 @@ ], "responses": { "200": { - "description": "Payment Methods retrieved", + "description": "Payment Methods retrieved for customer tied to its respective client-secret passed in the param", "content": { "application/json": { "schema": { @@ -230,50 +279,7 @@ }, "security": [ { - "api_key": [] - }, - { - "ephemeral_key": [] - } - ] - } - }, - "/customers": { - "post": { - "tags": [ - "Customers" - ], - "summary": "Create Customer", - "description": "Create Customer\n\nCreate a customer object and store the customer details to be reused for future payments. Incase the customer already exists in the system, this API will respond with the customer details.", - "operationId": "Create a Customer", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CustomerRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Customer Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CustomerResponse" - } - } - } - }, - "400": { - "description": "Invalid data" - } - }, - "security": [ - { - "api_key": [] + "publishable_key": [] } ] } @@ -410,6 +416,112 @@ ] } }, + "/customers/{customer_id}/payment_methods": { + "get": { + "tags": [ + "Payment Methods" + ], + "summary": "List payment methods for a Customer", + "description": "List payment methods for a Customer\n\nTo filter and list the applicable payment methods for a particular Customer ID", + "operationId": "List all Payment Methods for a Customer", + "parameters": [ + { + "name": "customer_id", + "in": "path", + "description": "The unique identifier for the customer account", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "accepted_country", + "in": "query", + "description": "The two-letter ISO currency code", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "accepted_currency", + "in": "path", + "description": "The three-letter ISO currency code", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Currency" + } + } + }, + { + "name": "minimum_amount", + "in": "query", + "description": "The minimum amount accepted for processing by the particular payment method.", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "maximum_amount", + "in": "query", + "description": "The maximum amount amount accepted for processing by the particular payment method.", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "recurring_payment_enabled", + "in": "query", + "description": "Indicates whether the payment method is eligible for recurring payments", + "required": true, + "schema": { + "type": "boolean" + } + }, + { + "name": "installment_payment_enabled", + "in": "query", + "description": "Indicates whether the payment method is eligible for installment payments", + "required": true, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Payment Methods retrieved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerPaymentMethodsListResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + }, + "404": { + "description": "Payment Methods does not exist in records" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, "/disputes/list": { "get": { "tags": [