use std::collections::HashMap;
use ferro_stripe::{
StripeChargeDisputeCreated, StripeChargeRefunded, StripeCheckoutCompleted,
StripeCheckoutExpired, StripeConnectAccountUpdated, StripeConnectPaymentSucceeded, StripeEvent,
StripeInvoicePaid, StripePaymentIntentAmountCapturableUpdated, StripePaymentIntentCanceled,
StripePaymentIntentFailed, StripeSubscriptionDeleted, StripeSubscriptionUpdated, WebhookEvent,
};
fn parse_event(raw: &str) -> WebhookEvent {
WebhookEvent::from_json(raw).expect("fixture should parse as WebhookEvent envelope")
}
const CHECKOUT_COMPLETED: &str =
include_str!("fixtures/stripe_events/checkout_session_completed.json");
const CHECKOUT_EXPIRED: &str = include_str!("fixtures/stripe_events/checkout_session_expired.json");
#[test]
fn checkout_session_completed_parses_all_fields() {
let event = parse_event(CHECKOUT_COMPLETED);
let typed = StripeCheckoutCompleted::from_raw(&event)
.expect("from_raw should return Some for checkout.session.completed");
assert_eq!(typed.event_id, "evt_test_checkout_completed_001");
assert_eq!(typed.session_id, "cs_test_001");
assert_eq!(typed.payment_intent_id.as_deref(), Some("pi_test_001"));
assert_eq!(typed.amount_total_cents, 1000);
assert_eq!(typed.currency, "usd");
assert_eq!(typed.customer_email.as_deref(), Some("test@example.com"));
let mut expected = HashMap::new();
expected.insert("order_id".to_string(), "order_42".to_string());
assert_eq!(typed.metadata, expected);
}
#[test]
fn checkout_completed_rejects_expired_event() {
let event = parse_event(CHECKOUT_EXPIRED);
assert!(
StripeCheckoutCompleted::from_raw(&event).is_none(),
"StripeCheckoutCompleted must reject checkout.session.expired (type_ guard)"
);
}
#[test]
fn checkout_session_expired_parses_all_fields() {
let event = parse_event(CHECKOUT_EXPIRED);
let typed = StripeCheckoutExpired::from_raw(&event)
.expect("from_raw should return Some for checkout.session.expired");
assert_eq!(typed.event_id, "evt_test_checkout_expired_001");
assert_eq!(typed.session_id, "cs_test_expired_001");
let mut expected = HashMap::new();
expected.insert("order_id".to_string(), "order_exp".to_string());
assert_eq!(typed.metadata, expected);
}
#[test]
fn checkout_expired_rejects_completed_event() {
let event = parse_event(CHECKOUT_COMPLETED);
assert!(
StripeCheckoutExpired::from_raw(&event).is_none(),
"StripeCheckoutExpired must reject checkout.session.completed (type_ guard)"
);
}
const PI_FAILED: &str = include_str!("fixtures/stripe_events/payment_intent_payment_failed.json");
#[test]
fn payment_intent_failed_parses_all_fields() {
let event = parse_event(PI_FAILED);
let typed = StripePaymentIntentFailed::from_raw(&event)
.expect("from_raw should return Some for payment_intent.payment_failed");
assert_eq!(typed.event_id, "evt_test_pi_failed_001");
assert_eq!(typed.payment_intent_id, "pi_test_failed_001");
assert_eq!(typed.session_id.as_deref(), Some("cs_test_related_001"));
assert_eq!(typed.failure_code.as_deref(), Some("card_declined"));
assert_eq!(
typed.failure_message.as_deref(),
Some("Your card was declined.")
);
assert_eq!(
typed
.metadata
.get("checkout_session_id")
.map(String::as_str),
Some("cs_test_related_001")
);
assert_eq!(
typed.metadata.get("order_id").map(String::as_str),
Some("order_99")
);
}
const CHARGE_REFUNDED: &str = include_str!("fixtures/stripe_events/charge_refunded.json");
#[test]
fn charge_refunded_parses_all_fields() {
let event = parse_event(CHARGE_REFUNDED);
let typed = StripeChargeRefunded::from_raw(&event)
.expect("from_raw should return Some for charge.refunded");
assert_eq!(typed.event_id, "evt_test_charge_refunded_001");
assert_eq!(typed.charge_id, "ch_test_refunded_001");
assert_eq!(typed.payment_intent_id.as_deref(), Some("pi_test_ref_001"));
assert_eq!(typed.amount_refunded_cents, 2000);
assert_eq!(typed.refund_id.as_deref(), Some("re_test_refunded_001"));
assert_eq!(
typed.metadata.get("order_id").map(String::as_str),
Some("order_ref")
);
}
const DISPUTE_CREATED: &str = include_str!("fixtures/stripe_events/charge_dispute_created.json");
#[test]
fn charge_dispute_created_parses_all_fields() {
let event = parse_event(DISPUTE_CREATED);
let typed = StripeChargeDisputeCreated::from_raw(&event)
.expect("from_raw should return Some for charge.dispute.created");
assert_eq!(typed.event_id, "evt_test_dispute_created_001");
assert_eq!(typed.charge_id, "ch_test_disputed_001");
assert_eq!(typed.payment_intent_id.as_deref(), Some("pi_test_disp_001"));
assert_eq!(typed.dispute_reason, "fraudulent");
assert_eq!(typed.amount_cents, 3000);
}
const ACCOUNT_UPDATED: &str = include_str!("fixtures/stripe_events/account_updated.json");
#[test]
fn account_updated_parses_all_fields() {
let event = parse_event(ACCOUNT_UPDATED);
let typed = StripeConnectAccountUpdated::from_raw(&event)
.expect("from_raw should return Some for account.updated");
assert_eq!(typed.event_id, "evt_test_account_updated_001");
assert_eq!(typed.account_id, "acct_test_001");
assert!(typed.charges_enabled);
assert!(typed.payouts_enabled);
assert!(typed.details_submitted);
}
const SUB_UPDATED: &str = include_str!("fixtures/stripe_events/customer_subscription_updated.json");
const SUB_DELETED: &str = include_str!("fixtures/stripe_events/customer_subscription_deleted.json");
#[test]
fn subscription_updated_parses_all_fields() {
let event = parse_event(SUB_UPDATED);
let typed = StripeSubscriptionUpdated::from_raw(&event)
.expect("from_raw should return Some for customer.subscription.updated");
assert_eq!(typed.event_id, "evt_test_sub_updated_001");
assert_eq!(typed.subscription_id, "sub_test_updated_001");
assert_eq!(typed.customer_id, "cus_test_sub_001");
}
#[test]
fn subscription_updated_rejects_deleted_event() {
let event = parse_event(SUB_DELETED);
assert!(StripeSubscriptionUpdated::from_raw(&event).is_none());
}
#[test]
fn subscription_deleted_parses_all_fields() {
let event = parse_event(SUB_DELETED);
let typed = StripeSubscriptionDeleted::from_raw(&event)
.expect("from_raw should return Some for customer.subscription.deleted");
assert_eq!(typed.event_id, "evt_test_sub_deleted_001");
assert_eq!(typed.subscription_id, "sub_test_deleted_001");
assert_eq!(typed.customer_id, "cus_test_sub_del_001");
}
#[test]
fn subscription_deleted_rejects_updated_event() {
let event = parse_event(SUB_UPDATED);
assert!(StripeSubscriptionDeleted::from_raw(&event).is_none());
}
const INVOICE_PAID: &str = include_str!("fixtures/stripe_events/invoice_paid.json");
#[test]
fn invoice_paid_parses_all_fields() {
let event = parse_event(INVOICE_PAID);
let typed =
StripeInvoicePaid::from_raw(&event).expect("from_raw should return Some for invoice.paid");
assert_eq!(typed.event_id, "evt_test_invoice_paid_001");
assert_eq!(typed.invoice_id, "in_test_paid_001");
assert_eq!(typed.customer_id, "cus_test_inv_001");
}
#[test]
fn invoice_paid_rejects_checkout_event() {
let event = parse_event(CHECKOUT_COMPLETED);
assert!(StripeInvoicePaid::from_raw(&event).is_none());
}
const PI_CONNECT: &str =
include_str!("fixtures/stripe_events/payment_intent_succeeded_connect.json");
#[test]
fn connect_payment_succeeded_parses_all_fields() {
let event = parse_event(PI_CONNECT);
let typed = StripeConnectPaymentSucceeded::from_raw(&event)
.expect("from_raw should return Some for payment_intent.succeeded with account");
assert_eq!(typed.event_id, "evt_test_pi_connect_001");
assert_eq!(typed.payment_intent_id, "pi_test_connect_001");
assert_eq!(typed.connect_account_id, "acct_test_connect_001");
}
const PI_AMOUNT_CAPTURABLE: &str =
include_str!("fixtures/stripe_events/payment_intent_amount_capturable_updated.json");
const PI_CANCELED: &str = include_str!("fixtures/stripe_events/payment_intent_canceled.json");
#[test]
fn payment_intent_amount_capturable_updated_parses_all_fields() {
let event = parse_event(PI_AMOUNT_CAPTURABLE);
let typed = StripePaymentIntentAmountCapturableUpdated::from_raw(&event)
.expect("from_raw should return Some for payment_intent.amount_capturable_updated");
assert_eq!(typed.event_id, "evt_test_pi_capturable_001");
assert_eq!(typed.payment_intent_id, "pi_test_capturable_001");
assert_eq!(typed.amount_capturable_cents, 5000);
assert_eq!(typed.currency, "usd");
assert_eq!(
typed.metadata.get("booking_id").map(String::as_str),
Some("bk_42")
);
}
#[test]
fn payment_intent_canceled_parses_all_fields() {
let event = parse_event(PI_CANCELED);
let typed = StripePaymentIntentCanceled::from_raw(&event)
.expect("from_raw should return Some for payment_intent.canceled");
assert_eq!(typed.event_id, "evt_test_pi_canceled_001");
assert_eq!(typed.payment_intent_id, "pi_test_canceled_001");
assert_eq!(
typed.cancellation_reason.as_deref(),
Some("requested_by_customer")
);
assert_eq!(
typed.metadata.get("booking_id").map(String::as_str),
Some("bk_43")
);
}
#[test]
fn payment_intent_amount_capturable_updated_rejects_canceled_event() {
let event = parse_event(PI_CANCELED);
assert!(
StripePaymentIntentAmountCapturableUpdated::from_raw(&event).is_none(),
"must reject payment_intent.canceled (type_ guard)"
);
}
#[test]
fn payment_intent_canceled_rejects_amount_capturable_event() {
let event = parse_event(PI_AMOUNT_CAPTURABLE);
assert!(
StripePaymentIntentCanceled::from_raw(&event).is_none(),
"must reject payment_intent.amount_capturable_updated (type_ guard)"
);
}