feat(docs): add multiple examples support and webhook schema (#1864)

This commit is contained in:
Narayan Bhat
2023-08-09 11:25:56 +05:30
committed by GitHub
parent 27692fda85
commit f8ef52c645
6 changed files with 317 additions and 12 deletions

View File

@ -1,6 +1,7 @@
use common_utils::custom_serde; use common_utils::custom_serde;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
use utoipa::ToSchema;
use crate::{disputes, enums as api_enums, payments, refunds}; use crate::{disputes, enums as api_enums, payments, refunds};
@ -81,20 +82,33 @@ pub struct IncomingWebhookDetails {
pub resource_object: Vec<u8>, pub resource_object: Vec<u8>,
} }
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize, ToSchema)]
pub struct OutgoingWebhook { pub struct OutgoingWebhook {
/// The merchant id of the merchant
pub merchant_id: String, pub merchant_id: String,
/// The unique event id for each webhook
pub event_id: String, pub event_id: String,
/// The type of event this webhook corresponds to.
#[schema(value_type = EventType)]
pub event_type: api_enums::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, pub content: OutgoingWebhookContent,
#[serde(default, with = "custom_serde::iso8601")] #[serde(default, with = "custom_serde::iso8601")]
/// The time at which webhook was sent
pub timestamp: PrimitiveDateTime, pub timestamp: PrimitiveDateTime,
} }
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(tag = "type", content = "object", rename_all = "snake_case")] #[serde(tag = "type", content = "object", rename_all = "snake_case")]
pub enum OutgoingWebhookContent { pub enum OutgoingWebhookContent {
#[schema(value_type = PaymentsResponse)]
PaymentDetails(payments::PaymentsResponse), PaymentDetails(payments::PaymentsResponse),
#[schema(value_type = RefundResponse)]
RefundDetails(refunds::RefundResponse), RefundDetails(refunds::RefundResponse),
#[schema(value_type = DisputeResponse)]
DisputeDetails(Box<disputes::DisputeResponse>), DisputeDetails(Box<disputes::DisputeResponse>),
} }

View File

@ -736,6 +736,7 @@ impl Currency {
serde::Serialize, serde::Serialize,
strum::Display, strum::Display,
strum::EnumString, strum::EnumString,
ToSchema,
)] )]
#[router_derive::diesel_enum(storage_type = "pg_enum")] #[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]

View File

@ -17,7 +17,6 @@ pub mod routes;
pub mod scheduler; pub mod scheduler;
pub mod middleware; pub mod middleware;
#[cfg(feature = "openapi")]
pub mod openapi; pub mod openapi;
pub mod services; pub mod services;
pub mod types; pub mod types;

View File

@ -1,3 +1,4 @@
#[cfg(feature = "openapi")]
#[derive(utoipa::OpenApi)] #[derive(utoipa::OpenApi)]
#[openapi( #[openapi(
info( info(
@ -79,13 +80,13 @@ Never share your secret api keys. Keep them guarded and secure.
crate::routes::mandates::get_mandate, crate::routes::mandates::get_mandate,
crate::routes::mandates::revoke_mandate, crate::routes::mandates::revoke_mandate,
crate::routes::payments::payments_create, crate::routes::payments::payments_create,
// crate::routes::payments::payments_start, // crate::routes::payments::payments_start,
crate::routes::payments::payments_retrieve, crate::routes::payments::payments_retrieve,
crate::routes::payments::payments_update, crate::routes::payments::payments_update,
crate::routes::payments::payments_confirm, crate::routes::payments::payments_confirm,
crate::routes::payments::payments_capture, crate::routes::payments::payments_capture,
crate::routes::payments::payments_connector_session, 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_cancel,
crate::routes::payments::payments_list, crate::routes::payments::payments_list,
crate::routes::payment_methods::create_payment_method_api, 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::PayoutStatus,
api_models::enums::PayoutType, api_models::enums::PayoutType,
api_models::payments::FrmMessage, 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::MerchantAccountResponse,
crate::types::api::admin::MerchantConnectorId, crate::types::api::admin::MerchantConnectorId,
crate::types::api::admin::MerchantDetails, crate::types::api::admin::MerchantDetails,
@ -346,7 +350,7 @@ impl utoipa::Modify for SecurityAddon {
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description( SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description(
"api-key", "api-key",
"API keys are the most common method of authentication and can be obtained \ "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( SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description(
"api-key", "api-key",
"Admin API keys allow you to perform some privileged actions such as \ "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( SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description(
"api-key", "api-key",
"Publishable keys are a type of keys that can be public and have limited \ "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( SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description(
"api-key", "api-key",
"Ephemeral keys provide temporary access to singular data, such as access \ "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"
}
}
}"#;
}

View File

@ -10,6 +10,12 @@ use crate::{
errors::http_not_implemented, errors::http_not_implemented,
payments::{self, PaymentRedirectFlow}, 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}, services::{api, authentication as auth},
types::{ types::{
api::{self as api_types, enums as api_enums, payments as payment_types}, api::{self as api_types, enums as api_enums, payments as payment_types},
@ -23,17 +29,59 @@ use crate::{
#[utoipa::path( #[utoipa::path(
post, post,
path = "/payments", 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( responses(
(status = 200, description = "Payment created", body = PaymentsResponse), (status = 200, description = "Payment created", body = PaymentsResponse),
(status = 400, description = "Missing Mandatory fields") (status = 400, description = "Missing Mandatory fields")
), ),
tag = "Payments", tag = "Payments",
operation_id = "Create a Payment", operation_id = "Create a Payment",
security(("api_key" = [])) security(("api_key" = [])),
)] )]
#[instrument(skip_all, fields(flow = ?Flow::PaymentsCreate))] #[instrument(skip_all, fields(flow = ?Flow::PaymentsCreate))]
// #[post("")]
pub async fn payments_create( pub async fn payments_create(
state: web::Data<app::AppState>, state: web::Data<app::AppState>,
req: actix_web::HttpRequest, req: actix_web::HttpRequest,

View File

@ -976,6 +976,32 @@
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/PaymentsCreateRequest" "$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": { "FeatureMetadata": {
"type": "object", "type": "object",
"properties": { "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": { "PayLaterData": {
"oneOf": [ "oneOf": [
{ {