use serde::{Deserialize, Serialize};
use super::{InternalVerb, VerbCategory};
pub struct PaymentsChargeCreate;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct PaymentsChargeCreateInput {
pub amount_cents: u64,
pub currency: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub customer_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct PaymentsChargeCreateOutput {
pub id: String,
pub status: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub client_secret: Option<String>,
}
impl InternalVerb for PaymentsChargeCreate {
type Input = PaymentsChargeCreateInput;
type Output = PaymentsChargeCreateOutput;
const ID: &'static str = "payments.charge.create";
const CATEGORY: VerbCategory = VerbCategory::Payments;
}
pub struct PaymentsSubscriptionUpsert;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct PaymentsSubscriptionUpsertInput {
pub customer_id: String,
pub price_id: String,
#[serde(default = "quantity_one")]
pub quantity: u32,
}
fn quantity_one() -> u32 {
1
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct PaymentsSubscriptionUpsertOutput {
pub id: String,
pub status: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub current_period_end: Option<String>,
}
impl InternalVerb for PaymentsSubscriptionUpsert {
type Input = PaymentsSubscriptionUpsertInput;
type Output = PaymentsSubscriptionUpsertOutput;
const ID: &'static str = "payments.subscription.upsert";
const CATEGORY: VerbCategory = VerbCategory::Payments;
}
pub struct PaymentsWebhookVerify;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct PaymentsWebhookVerifyInput {
pub payload: String,
pub signature: String,
pub secret: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct PaymentsWebhookVerifyOutput {
pub valid: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub event_type: Option<String>,
}
impl InternalVerb for PaymentsWebhookVerify {
type Input = PaymentsWebhookVerifyInput;
type Output = PaymentsWebhookVerifyOutput;
const ID: &'static str = "payments.webhook.verify";
const CATEGORY: VerbCategory = VerbCategory::Payments;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verb_ids_match_canonical_namespace() {
for id in [
PaymentsChargeCreate::ID,
PaymentsSubscriptionUpsert::ID,
PaymentsWebhookVerify::ID,
] {
assert!(id.starts_with("payments."), "{id}");
}
}
#[test]
fn verbs_are_under_payments_category() {
assert_eq!(PaymentsChargeCreate::CATEGORY, VerbCategory::Payments);
assert_eq!(PaymentsSubscriptionUpsert::CATEGORY, VerbCategory::Payments);
assert_eq!(PaymentsWebhookVerify::CATEGORY, VerbCategory::Payments);
}
#[test]
fn charge_create_optional_fields_omitted() {
let input = PaymentsChargeCreateInput {
amount_cents: 1000,
currency: "usd".into(),
description: None,
customer_id: None,
};
let wire = serde_json::to_value(&input).unwrap();
assert!(!wire.as_object().unwrap().contains_key("description"));
assert!(!wire.as_object().unwrap().contains_key("customer_id"));
}
#[test]
fn subscription_upsert_defaults_quantity_to_one() {
let wire = r#"{"customer_id":"cus_1","price_id":"price_1"}"#;
let parsed: PaymentsSubscriptionUpsertInput = serde_json::from_str(wire).unwrap();
assert_eq!(parsed.quantity, 1);
}
#[test]
fn webhook_verify_output_omits_event_type_on_failure() {
let out = PaymentsWebhookVerifyOutput {
valid: false,
event_type: None,
};
let wire = serde_json::to_value(&out).unwrap();
assert_eq!(wire["valid"], false);
assert!(!wire.as_object().unwrap().contains_key("event_type"));
}
#[test]
fn webhook_verify_output_includes_event_type_on_success() {
let out = PaymentsWebhookVerifyOutput {
valid: true,
event_type: Some("payment_intent.succeeded".into()),
};
let wire = serde_json::to_value(&out).unwrap();
assert_eq!(wire["valid"], true);
assert_eq!(wire["event_type"], "payment_intent.succeeded");
}
#[cfg(feature = "json-schema")]
#[test]
fn verbs_emit_schemas_via_for_verb() {
use super::super::VerbDescriptor;
let charge = VerbDescriptor::for_verb::<PaymentsChargeCreate>();
assert_eq!(charge.id, "payments.charge.create");
assert!(charge.input_schema.to_string().contains("amount_cents"));
let sub = VerbDescriptor::for_verb::<PaymentsSubscriptionUpsert>();
assert_eq!(sub.id, "payments.subscription.upsert");
let verify = VerbDescriptor::for_verb::<PaymentsWebhookVerify>();
assert_eq!(verify.id, "payments.webhook.verify");
assert!(verify.output_schema.to_string().contains("valid"));
}
}