diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index b884508211..021cc82402 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -1,6 +1,7 @@ use common_utils::custom_serde; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; +use utoipa::ToSchema; use crate::{disputes, enums as api_enums, payments, refunds}; @@ -81,20 +82,33 @@ pub struct IncomingWebhookDetails { pub resource_object: Vec, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, ToSchema)] pub struct OutgoingWebhook { + /// The merchant id of the merchant pub merchant_id: String, + + /// The unique event id for each webhook pub event_id: String, + + /// The type of event this webhook corresponds to. + #[schema(value_type = EventType)] pub event_type: api_enums::EventType, + + /// This is specific to the flow, for ex: it will be `PaymentsResponse` for payments flow pub content: OutgoingWebhookContent, #[serde(default, with = "custom_serde::iso8601")] + + /// The time at which webhook was sent pub timestamp: PrimitiveDateTime, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, ToSchema)] #[serde(tag = "type", content = "object", rename_all = "snake_case")] pub enum OutgoingWebhookContent { + #[schema(value_type = PaymentsResponse)] PaymentDetails(payments::PaymentsResponse), + #[schema(value_type = RefundResponse)] RefundDetails(refunds::RefundResponse), + #[schema(value_type = DisputeResponse)] DisputeDetails(Box), } diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 53e9f66928..b3a76df3d0 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -736,6 +736,7 @@ impl Currency { serde::Serialize, strum::Display, strum::EnumString, + ToSchema, )] #[router_derive::diesel_enum(storage_type = "pg_enum")] #[serde(rename_all = "snake_case")] diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index b0087b0522..1ead4924bf 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -17,7 +17,6 @@ pub mod routes; pub mod scheduler; pub mod middleware; -#[cfg(feature = "openapi")] pub mod openapi; pub mod services; pub mod types; diff --git a/crates/router/src/openapi.rs b/crates/router/src/openapi.rs index 98e4f642fe..b650b1d859 100644 --- a/crates/router/src/openapi.rs +++ b/crates/router/src/openapi.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "openapi")] #[derive(utoipa::OpenApi)] #[openapi( info( @@ -79,13 +80,13 @@ Never share your secret api keys. Keep them guarded and secure. crate::routes::mandates::get_mandate, crate::routes::mandates::revoke_mandate, crate::routes::payments::payments_create, - // crate::routes::payments::payments_start, + // crate::routes::payments::payments_start, crate::routes::payments::payments_retrieve, crate::routes::payments::payments_update, crate::routes::payments::payments_confirm, crate::routes::payments::payments_capture, crate::routes::payments::payments_connector_session, - // crate::routes::payments::payments_redirect_response, + // crate::routes::payments::payments_redirect_response, crate::routes::payments::payments_cancel, crate::routes::payments::payments_list, crate::routes::payment_methods::create_payment_method_api, @@ -318,6 +319,9 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::PayoutStatus, api_models::enums::PayoutType, api_models::payments::FrmMessage, + api_models::webhooks::OutgoingWebhook, + api_models::webhooks::OutgoingWebhookContent, + api_models::enums::EventType, crate::types::api::admin::MerchantAccountResponse, crate::types::api::admin::MerchantConnectorId, crate::types::api::admin::MerchantDetails, @@ -346,7 +350,7 @@ impl utoipa::Modify for SecurityAddon { SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description( "api-key", "API keys are the most common method of authentication and can be obtained \ - from the HyperSwitch dashboard." + from the HyperSwitch dashboard." ))), ), ( @@ -354,7 +358,7 @@ impl utoipa::Modify for SecurityAddon { SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description( "api-key", "Admin API keys allow you to perform some privileged actions such as \ - creating a merchant account and Merchant Connector account." + creating a merchant account and Merchant Connector account." ))), ), ( @@ -362,7 +366,7 @@ impl utoipa::Modify for SecurityAddon { SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description( "api-key", "Publishable keys are a type of keys that can be public and have limited \ - scope of usage." + scope of usage." ))), ), ( @@ -370,10 +374,114 @@ impl utoipa::Modify for SecurityAddon { SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description( "api-key", "Ephemeral keys provide temporary access to singular data, such as access \ - to a single customer object for a short period of time." + to a single customer object for a short period of time." ))), ), ]); } } } + +pub mod examples { + /// Creating the payment with minimal fields + pub const PAYMENTS_CREATE_MINIMUM_FIELDS: &str = r#"{ + "amount": 6540, + "currency": "USD", + }"#; + + /// Creating a manual capture payment + pub const PAYMENTS_CREATE_WITH_MANUAL_CAPTURE: &str = r#"{ + "amount": 6540, + "currency": "USD", + "capture_method":"manual" + }"#; + + /// Creating a payment with billing and shipping address + pub const PAYMENTS_CREATE_WITH_ADDRESS: &str = r#"{ + "amount": 6540, + "currency": "USD", + "customer": { + "id" : "cus_abcdefgh" + }, + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "joseph", + "last_name": "Doe" + }, + "phone": { + "number": "8056594427", + "country_code": "+91" + } + } + }"#; + + /// Creating a payment with customer details + pub const PAYMENTS_CREATE_WITH_CUSTOMER_DATA: &str = r#"{ + "amount": 6540, + "currency": "USD", + "customer": { + "id":"cus_abcdefgh", + "name":"John Dough", + "phone":"9999999999", + "email":"john@example.com" + } + }"#; + + /// 3DS force payment + pub const PAYMENTS_CREATE_WITH_FORCED_3DS: &str = r#"{ + "amount": 6540, + "currency": "USD", + "authentication_type" : "three_ds" + }"#; + + /// A payment with other fields + pub const PAYMENTS_CREATE: &str = r#"{ + "amount": 6540, + "currency": "USD", + "payment_id": "abcdefghijklmnopqrstuvwxyz", + "customer": { + "id":"cus_abcdefgh", + "name":"John Dough", + "phone":"9999999999", + "email":"john@example.com" + }, + "description": "Its my first payment request", + "statement_descriptor_name": "joseph", + "statement_descriptor_suffix": "JS", + "metadata": { + "udf1": "some-value", + "udf2": "some-value" + } + }"#; + + /// Creating the payment with order details + pub const PAYMENTS_CREATE_WITH_ORDER_DETAILS: &str = r#"{ + "amount": 6540, + "currency": "USD", + "order_details": [ + { + "product_name": "Apple iPhone 15", + "quantity": 1, + "amount" : 6540 + } + ] + }"#; + + /// Creating the payment with connector metadata for noon + pub const PAYMENTS_CREATE_WITH_NOON_ORDER_CATETORY: &str = r#"{ + "amount": 6540, + "currency": "USD", + "connector_metadata": { + "noon": { + "order_category":"shoes" + } + } + }"#; +} diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index e47a248fbf..32de056c1f 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -10,6 +10,12 @@ use crate::{ errors::http_not_implemented, payments::{self, PaymentRedirectFlow}, }, + openapi::examples::{ + PAYMENTS_CREATE, PAYMENTS_CREATE_MINIMUM_FIELDS, PAYMENTS_CREATE_WITH_ADDRESS, + PAYMENTS_CREATE_WITH_CUSTOMER_DATA, PAYMENTS_CREATE_WITH_FORCED_3DS, + PAYMENTS_CREATE_WITH_MANUAL_CAPTURE, PAYMENTS_CREATE_WITH_NOON_ORDER_CATETORY, + PAYMENTS_CREATE_WITH_ORDER_DETAILS, + }, services::{api, authentication as auth}, types::{ api::{self as api_types, enums as api_enums, payments as payment_types}, @@ -23,17 +29,59 @@ use crate::{ #[utoipa::path( post, path = "/payments", - request_body=PaymentsCreateRequest, + request_body( + content = PaymentsCreateRequest, + examples( + ( + "Create a payment with minimul fields" = ( + value = json!(PAYMENTS_CREATE_MINIMUM_FIELDS) + ) + ), + ( + "Create a manual capture payment" = ( + value = json!(PAYMENTS_CREATE_WITH_MANUAL_CAPTURE) + ) + ), + ( + "Create a payment with address" = ( + value = json!(PAYMENTS_CREATE_WITH_ADDRESS) + ) + ), + ( + "Create a payment with customer details" = ( + value = json!(PAYMENTS_CREATE_WITH_CUSTOMER_DATA) + ) + ), + ( + "Create a 3DS payment" = ( + value = json!(PAYMENTS_CREATE_WITH_FORCED_3DS) + ) + ), + ( + "Create a payment" = ( + value = json!(PAYMENTS_CREATE) + ) + ), + ( + "Create a payment with order details" = ( + value = json!(PAYMENTS_CREATE_WITH_ORDER_DETAILS) + ) + ), + ( + "Create a payment with order category for noon" = ( + value = json!(PAYMENTS_CREATE_WITH_NOON_ORDER_CATETORY) + ) + ), + )), responses( (status = 200, description = "Payment created", body = PaymentsResponse), (status = 400, description = "Missing Mandatory fields") ), tag = "Payments", operation_id = "Create a Payment", - security(("api_key" = [])) + security(("api_key" = [])), )] #[instrument(skip_all, fields(flow = ?Flow::PaymentsCreate))] -// #[post("")] pub async fn payments_create( state: web::Data, req: actix_web::HttpRequest, diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index ac28e9563d..d637dc6161 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -976,6 +976,32 @@ "application/json": { "schema": { "$ref": "#/components/schemas/PaymentsCreateRequest" + }, + "examples": { + "Create a 3DS payment": { + "value": "{\n \"amount\": 6540,\n \"currency\": \"USD\",\n \"authentication_type\" : \"three_ds\"\n }" + }, + "Create a manual capture payment": { + "value": "{\n \"amount\": 6540,\n \"currency\": \"USD\",\n \"capture_method\":\"manual\"\n }" + }, + "Create a payment": { + "value": "{\n \"amount\": 6540,\n \"currency\": \"USD\",\n \"payment_id\": \"abcdefghijklmnopqrstuvwxyz\",\n \"customer\": {\n \"id\":\"cus_abcdefgh\",\n \"name\":\"John Dough\",\n \"phone\":\"9999999999\",\n \"email\":\"john@example.com\"\n },\n \"description\": \"Its my first payment request\",\n \"statement_descriptor_name\": \"joseph\",\n \"statement_descriptor_suffix\": \"JS\",\n \"metadata\": {\n \"udf1\": \"some-value\",\n \"udf2\": \"some-value\"\n }\n }" + }, + "Create a payment with address": { + "value": "{\n \"amount\": 6540,\n \"currency\": \"USD\",\n \"customer\": {\n \"id\" : \"cus_abcdefgh\"\n },\n \"billing\": {\n \"address\": {\n \"line1\": \"1467\",\n \"line2\": \"Harrison Street\",\n \"line3\": \"Harrison Street\",\n \"city\": \"San Fransico\",\n \"state\": \"California\",\n \"zip\": \"94122\",\n \"country\": \"US\",\n \"first_name\": \"joseph\",\n \"last_name\": \"Doe\"\n },\n \"phone\": {\n \"number\": \"8056594427\",\n \"country_code\": \"+91\"\n }\n }\n }" + }, + "Create a payment with customer details": { + "value": "{\n \"amount\": 6540,\n \"currency\": \"USD\",\n \"customer\": {\n \"id\":\"cus_abcdefgh\",\n \"name\":\"John Dough\",\n \"phone\":\"9999999999\",\n \"email\":\"john@example.com\"\n }\n }" + }, + "Create a payment with minimul fields": { + "value": "{\n \"amount\": 6540,\n \"currency\": \"USD\",\n }" + }, + "Create a payment with order category for noon": { + "value": "{\n \"amount\": 6540,\n \"currency\": \"USD\",\n \"connector_metadata\": {\n \"noon\": {\n \"order_category\":\"shoes\"\n }\n }\n }" + }, + "Create a payment with order details": { + "value": "{\n \"amount\": 6540,\n \"currency\": \"USD\",\n \"order_details\": [\n {\n \"product_name\": \"Apple iPhone 15\",\n \"quantity\": 1,\n \"amount\" : 6540\n }\n ]\n }" + } } } }, @@ -4853,6 +4879,24 @@ } } }, + "EventType": { + "type": "string", + "enum": [ + "payment_succeeded", + "payment_failed", + "payment_processing", + "action_required", + "refund_succeeded", + "refund_failed", + "dispute_opened", + "dispute_expired", + "dispute_accepted", + "dispute_cancelled", + "dispute_challenged", + "dispute_won", + "dispute_lost" + ] + }, "FeatureMetadata": { "type": "object", "properties": { @@ -6986,6 +7030,97 @@ } } }, + "OutgoingWebhook": { + "type": "object", + "required": [ + "merchant_id", + "event_id", + "event_type", + "content" + ], + "properties": { + "merchant_id": { + "type": "string", + "description": "The merchant id of the merchant" + }, + "event_id": { + "type": "string", + "description": "The unique event id for each webhook" + }, + "event_type": { + "$ref": "#/components/schemas/EventType" + }, + "content": { + "$ref": "#/components/schemas/OutgoingWebhookContent" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "The time at which webhook was sent" + } + } + }, + "OutgoingWebhookContent": { + "oneOf": [ + { + "type": "object", + "required": [ + "type", + "object" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "payment_details" + ] + }, + "object": { + "$ref": "#/components/schemas/PaymentsResponse" + } + } + }, + { + "type": "object", + "required": [ + "type", + "object" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "refund_details" + ] + }, + "object": { + "$ref": "#/components/schemas/RefundResponse" + } + } + }, + { + "type": "object", + "required": [ + "type", + "object" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "dispute_details" + ] + }, + "object": { + "$ref": "#/components/schemas/DisputeResponse" + } + } + } + ], + "discriminator": { + "propertyName": "type" + } + }, "PayLaterData": { "oneOf": [ {