fix(router): Convert ephemeral to client secret auth list payment_method_customer (#1602)

Co-authored-by: Sahkal Poddar <sahkal.poddar@juspay.in>
Co-authored-by: Abhishek Marrivagu <68317979+Abhicodes-crypto@users.noreply.github.com>
Co-authored-by: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com>
This commit is contained in:
Sahkal Poddar
2023-07-17 16:41:29 +05:30
committed by GitHub
parent 5213656fac
commit 5fbd1cc3c7
8 changed files with 276 additions and 79 deletions

View File

@ -170,8 +170,8 @@ pub async fn list_customer_payment_method_api(
path: web::Path<String>, path: web::Path<String>,
json_payload: web::Query<payment_methods::PaymentMethodListRequest>, json_payload: web::Query<payment_methods::PaymentMethodListRequest>,
) -> HttpResponse { ) -> HttpResponse {
let customer_id = path.into_inner();
let payload = json_payload.into_inner(); let payload = json_payload.into_inner();
let customer_id = path.into_inner();
let flow = Flow::CustomerPaymentMethodsList; let flow = Flow::CustomerPaymentMethodsList;
wrap::compatibility_api_wrap::< wrap::compatibility_api_wrap::<
@ -189,12 +189,12 @@ pub async fn list_customer_payment_method_api(
&req, &req,
payload, payload,
|state, auth, req| { |state, auth, req| {
cards::list_customer_payment_method( cards::do_list_customer_pm_fetch_customer_if_not_passed(
state, state,
auth.merchant_account, auth.merchant_account,
auth.key_store, auth.key_store,
req, Some(req),
&customer_id, Some(customer_id.as_str()),
) )
}, },
&auth::ApiKeyAuth, &auth::ApiKeyAuth,

View File

@ -1499,15 +1499,47 @@ async fn filter_payment_mandate_based(
Ok(recurring_filter) 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<api::PaymentMethodListRequest>,
customer_id: Option<&str>,
) -> errors::RouterResponse<api::CustomerPaymentMethodsListResponse> {
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( pub async fn list_customer_payment_method(
state: &routes::AppState, state: &routes::AppState,
merchant_account: domain::MerchantAccount, merchant_account: domain::MerchantAccount,
key_store: domain::MerchantKeyStore, key_store: domain::MerchantKeyStore,
req: api::PaymentMethodListRequest, payment_intent: Option<storage::PaymentIntent>,
customer_id: &str, customer_id: &str,
) -> errors::RouterResponse<api::CustomerPaymentMethodsListResponse> { ) -> errors::RouterResponse<api::CustomerPaymentMethodsListResponse> {
let db = &*state.store; let db = &*state.store;
db.find_customer_by_customer_id_merchant_id( db.find_customer_by_customer_id_merchant_id(
customer_id, customer_id,
&merchant_account.merchant_id, &merchant_account.merchant_id,
@ -1556,15 +1588,10 @@ pub async fn list_customer_payment_method(
parent_payment_method_token, pma.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 current_datetime_utc = common_utils::date_time::now();
let time_eslapsed = current_datetime_utc let time_eslapsed = current_datetime_utc
- payment_intent - payment_intent
.as_ref()
.map(|intent| intent.created_at) .map(|intent| intent.created_at)
.unwrap_or_else(|| current_datetime_utc); .unwrap_or_else(|| current_datetime_utc);
redis_conn redis_conn

View File

@ -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::create_payment_method_api,
crate::routes::payment_methods::list_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,
crate::routes::payment_methods::list_customer_payment_method_api_client,
crate::routes::payment_methods::payment_method_retrieve_api, crate::routes::payment_methods::payment_method_retrieve_api,
crate::routes::payment_methods::payment_method_update_api, crate::routes::payment_methods::payment_method_update_api,
crate::routes::payment_methods::payment_method_delete_api, crate::routes::payment_methods::payment_method_delete_api,

View File

@ -222,14 +222,18 @@ impl Customers {
route = route route = route
.service(web::resource("").route(web::post().to(customers_create))) .service(web::resource("").route(web::post().to(customers_create)))
.service( .service(
web::resource("/{customer_id}") web::resource("/payment_methods")
.route(web::get().to(customers_retrieve)) .route(web::get().to(list_customer_payment_method_api_client)),
.route(web::post().to(customers_update))
.route(web::delete().to(customers_delete)),
) )
.service( .service(
web::resource("/{customer_id}/payment_methods") web::resource("/{customer_id}/payment_methods")
.route(web::get().to(list_customer_payment_method_api)), .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 route

View File

@ -98,7 +98,7 @@ pub async fn list_payment_method_api(
/// To filter and list the applicable payment methods for a particular Customer ID /// To filter and list the applicable payment methods for a particular Customer ID
#[utoipa::path( #[utoipa::path(
get, get,
path = "/customer/{customer_id}/payment_methods", path = "/customers/{customer_id}/payment_methods",
params ( params (
("customer_id" = String, Path, description = "The unique identifier for the customer account"), ("customer_id" = String, Path, description = "The unique identifier for the customer account"),
("accepted_country" = Vec<String>, Query, description = "The two-letter ISO currency code"), ("accepted_country" = Vec<String>, Query, description = "The two-letter ISO currency code"),
@ -115,39 +115,93 @@ pub async fn list_payment_method_api(
), ),
tag = "Payment Methods", tag = "Payment Methods",
operation_id = "List all Payment Methods for a Customer", operation_id = "List all Payment Methods for a Customer",
security(("api_key" = []), ("ephemeral_key" = [])) security(("api_key" = []))
)] )]
#[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))] #[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))]
pub async fn list_customer_payment_method_api( pub async fn list_customer_payment_method_api(
state: web::Data<AppState>, state: web::Data<AppState>,
customer_id: web::Path<(String,)>, customer_id: web::Path<(String,)>,
req: HttpRequest, req: HttpRequest,
json_payload: web::Query<payment_methods::PaymentMethodListRequest>, query_payload: web::Query<payment_methods::PaymentMethodListRequest>,
) -> HttpResponse { ) -> HttpResponse {
let flow = Flow::CustomerPaymentMethodsList; let flow = Flow::CustomerPaymentMethodsList;
let customer_id = customer_id.into_inner().0; let payload = query_payload.into_inner();
let (auth, _) = match auth::check_client_secret_and_get_auth(req.headers(), &payload) {
let auth_type = match auth::is_ephemeral_auth(req.headers(), &*state.store, &customer_id).await Ok((auth, _auth_flow)) => (auth, _auth_flow),
{ Err(e) => return api::log_and_return_error_response(e),
Ok(auth_type) => auth_type,
Err(err) => return api::log_and_return_error_response(err),
}; };
let customer_id = customer_id.into_inner().0;
api::server_wrap( api::server_wrap(
flow, flow,
state.get_ref(), state.get_ref(),
&req, &req,
json_payload.into_inner(), payload,
|state, auth, req| { |state, auth, req| {
cards::list_customer_payment_method( cards::do_list_customer_pm_fetch_customer_if_not_passed(
state, state,
auth.merchant_account, auth.merchant_account,
auth.key_store, auth.key_store,
req, Some(req),
&customer_id, 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<String>, Query, description = "The two-letter ISO currency code"),
("accepted_currency" = Vec<Currency>, 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<AppState>,
req: HttpRequest,
query_payload: web::Query<payment_methods::PaymentMethodListRequest>,
) -> 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 .await
} }

View File

@ -422,7 +422,6 @@ where
} }
.into()); .into());
} }
Ok((Box::new(ApiKeyAuth), api::AuthFlow::Merchant)) Ok((Box::new(ApiKeyAuth), api::AuthFlow::Merchant))
} }

View File

@ -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": { "get": {
"tags": [ "tags": [
"Payment Methods" "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", "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", "operationId": "List all Payment Methods for a Customer",
"parameters": [ "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", "name": "customer_id",
"in": "path", "in": "path",
@ -212,7 +261,7 @@
], ],
"responses": { "responses": {
"200": { "200": {
"description": "Payment Methods retrieved", "description": "Payment Methods retrieved for customer tied to its respective client-secret passed in the param",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@ -230,50 +279,7 @@
}, },
"security": [ "security": [
{ {
"api_key": [] "publishable_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": []
} }
] ]
} }
@ -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": { "/disputes/list": {
"get": { "get": {
"tags": [ "tags": [