mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-11 04:16:55 +00:00
store webhook logs
This commit is contained in:
1 parent
452a3473b3
commit
aec31779b8
13 files changed
+142
-48
No files matched your search
Generated
+1
@@ -864,6 +864,7 @@ dependencies = [
|
||||
"base64 0.13.0",
|
||||
"chrono",
|
||||
"container_common",
|
||||
"database_queries",
|
||||
"dotenv",
|
||||
"http_server_common",
|
||||
"log",
|
||||
|
||||
@@ -23,15 +23,15 @@ CREATE TABLE stripe_webhook_event_logs (
|
||||
-- We don't always extract this.
|
||||
stripe_maybe_event_entity_id VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- When the event was created on Stripe's end
|
||||
stripe_event_created_at TIMESTAMP NOT NULL,
|
||||
|
||||
-- Whether this is in production or test mode in Stripe.
|
||||
-- This is controlled by which API keys are used.
|
||||
-- "livemode"=true in production.
|
||||
stripe_is_production BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- ========== INTERNAL USER / METADATA ==========
|
||||
-- ========== INTERNAL SYSTEM METADATA ==========
|
||||
|
||||
-- Whether we handled the event
|
||||
event_was_handled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- Some event types may attach a user token
|
||||
maybe_user_token VARCHAR(32) DEFAULT NULL,
|
||||
@@ -41,8 +41,13 @@ CREATE TABLE stripe_webhook_event_logs (
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- When the event was created on Stripe's end
|
||||
stripe_event_created_at TIMESTAMP NOT NULL,
|
||||
|
||||
-- ========== INDICES ==========
|
||||
PRIMARY KEY (id),
|
||||
KEY fk_maybe_user_token (maybe_user_token),
|
||||
UNIQUE KEY (stripe_event_id),
|
||||
KEY index_stripe_event_type (stripe_event_type),
|
||||
KEY fk_maybe_user_token (maybe_user_token)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -16,6 +16,7 @@ path = "src/lib.rs"
|
||||
|
||||
# Internal
|
||||
container_common = { path = "../../lib/container_common" }
|
||||
database_queries = { path = "../../lib/database_queries" }
|
||||
http_server_common = { path = "../../lib/http_server_common" }
|
||||
|
||||
# External
|
||||
|
||||
+34
-5
@@ -28,6 +28,8 @@ use sqlx::MySqlPool;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use stripe::{EventObject, EventType, PaymentIntentStatus, Webhook};
|
||||
use database_queries::queries::billing::stripe::insert_stripe_webhook_event_log::InsertStripeWebhookEventLog;
|
||||
use crate::stripe::webhook_event_handlers::stripe_webhook_summary::StripeWebhookSummary;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct StripeWebhookSuccessResponse {
|
||||
@@ -92,6 +94,12 @@ pub async fn stripe_webhook_handler(
|
||||
|
||||
let mut unhandled_event_type = false;
|
||||
|
||||
let mut webhook_summary = StripeWebhookSummary {
|
||||
maybe_user_token: None,
|
||||
maybe_event_id: None,
|
||||
event_was_handled: false
|
||||
};
|
||||
|
||||
// NB:
|
||||
// - "Webhook endpoints might occasionally receive the same event more than once."
|
||||
// - "Stripe does not guarantee delivery of events in the order in which they are generated."
|
||||
@@ -131,17 +139,17 @@ pub async fn stripe_webhook_handler(
|
||||
|
||||
EventType::CustomerSubscriptionCreated => {
|
||||
if let EventObject::Subscription(subscription) = webhook_payload.data.object {
|
||||
let _r = customer_subscription_created_handler(&subscription)?;
|
||||
webhook_summary = customer_subscription_created_handler(&subscription)?;
|
||||
}
|
||||
}
|
||||
EventType::CustomerSubscriptionUpdated => {
|
||||
if let EventObject::Subscription(subscription) = webhook_payload.data.object {
|
||||
let _r = customer_subscription_updated_handler(&subscription)?;
|
||||
webhook_summary = customer_subscription_updated_handler(&subscription)?;
|
||||
}
|
||||
}
|
||||
EventType::CustomerSubscriptionDeleted => {
|
||||
if let EventObject::Subscription(subscription) = webhook_payload.data.object {
|
||||
let _r = customer_subscription_deleted_handler(&subscription)?;
|
||||
webhook_summary = customer_subscription_deleted_handler(&subscription)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,11 +279,32 @@ pub async fn stripe_webhook_handler(
|
||||
},
|
||||
}
|
||||
|
||||
// To play with the payload contents as JSON:
|
||||
// let json = serde_json::ser::to_string(&event_payload).unwrap();
|
||||
// info!("event payload as json: {}", json);
|
||||
|
||||
if unhandled_event_type {
|
||||
warn!("UNHANDLED STRIPE WEBHOOK EVENT TYPE: {:?}", webhook_payload.event_type);
|
||||
warn!("Unhandled Stripe webhook event type: {} ({:?})",
|
||||
&stripe_event_type,
|
||||
&webhook_payload.event_type);
|
||||
}
|
||||
|
||||
// let stripe_client = stripe::Client::new(STRIPE_SECRET_KEY);
|
||||
let query = InsertStripeWebhookEventLog {
|
||||
stripe_event_id,
|
||||
stripe_event_type,
|
||||
stripe_maybe_event_entity_id: webhook_summary.maybe_event_id,
|
||||
stripe_event_created_at,
|
||||
stripe_is_production,
|
||||
maybe_user_token: webhook_summary.maybe_user_token,
|
||||
event_was_handled: webhook_summary.event_was_handled,
|
||||
};
|
||||
|
||||
query.insert(&mysql_pool)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failure to record event: {:?}", err);
|
||||
StripeWebhookError::ServerError
|
||||
})?;
|
||||
|
||||
let response = StripeWebhookSuccessResponse {
|
||||
success: true,
|
||||
|
||||
+8
-12
@@ -1,27 +1,23 @@
|
||||
use crate::stripe::helpers::common_metadata_keys::METADATA_USER_TOKEN;
|
||||
use crate::stripe::helpers::expand_customer_id::expand_customer_id;
|
||||
use crate::stripe::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
use crate::stripe::webhook_event_handlers::stripe_webhook_error::StripeWebhookError;
|
||||
use crate::stripe::webhook_event_handlers::stripe_webhook_summary::StripeWebhookSummary;
|
||||
use log::{error, warn};
|
||||
use stripe::Subscription;
|
||||
use crate::stripe::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
|
||||
/// Handle event type: 'customer.subscription.created'
|
||||
/// Sent when the subscription is created. The subscription status may be incomplete if customer
|
||||
/// authentication is required to complete the payment or if you set payment_behavior to
|
||||
/// default_incomplete. For more details, read about subscription payment behavior.
|
||||
pub fn customer_subscription_created_handler(subscription: &Subscription) -> Result<(), StripeWebhookError> {
|
||||
|
||||
let json = serde_json::ser::to_string(&subscription).unwrap();
|
||||
error!("\n\n subscription.created ----> {}\n\n", json);
|
||||
|
||||
|
||||
pub fn customer_subscription_created_handler(subscription: &Subscription) -> Result<StripeWebhookSummary, StripeWebhookError> {
|
||||
let summary = subscription_summary_extractor(subscription)
|
||||
.map_err(|err| {
|
||||
error!("Error extracting subscription from 'customer.subscription.created' payload: {:?}", err);
|
||||
StripeWebhookError::ServerError // NB: This was probably *our* fault.
|
||||
})?;
|
||||
|
||||
warn!(">>> subscription.created = {:?}", summary);
|
||||
|
||||
Ok(())
|
||||
Ok(StripeWebhookSummary {
|
||||
maybe_user_token: summary.user_token,
|
||||
maybe_event_id: Some(summary.stripe_subscription_id),
|
||||
event_was_handled: false,
|
||||
})
|
||||
}
|
||||
+8
-11
@@ -1,24 +1,21 @@
|
||||
use crate::stripe::helpers::common_metadata_keys::METADATA_USER_TOKEN;
|
||||
use crate::stripe::helpers::expand_customer_id::expand_customer_id;
|
||||
use crate::stripe::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
use crate::stripe::webhook_event_handlers::stripe_webhook_error::StripeWebhookError;
|
||||
use crate::stripe::webhook_event_handlers::stripe_webhook_summary::StripeWebhookSummary;
|
||||
use log::{error, warn};
|
||||
use stripe::Subscription;
|
||||
use crate::stripe::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
|
||||
/// Handle event type: 'customer.subscription.deleted'
|
||||
/// Sent when a customer’s subscription ends.
|
||||
pub fn customer_subscription_deleted_handler(subscription: &Subscription) -> Result<(), StripeWebhookError> {
|
||||
|
||||
let json = serde_json::ser::to_string(&subscription).unwrap();
|
||||
error!("\n\n subscription.deleted ----> {}\n\n", json);
|
||||
|
||||
pub fn customer_subscription_deleted_handler(subscription: &Subscription) -> Result<StripeWebhookSummary, StripeWebhookError> {
|
||||
let summary = subscription_summary_extractor(subscription)
|
||||
.map_err(|err| {
|
||||
error!("Error extracting subscription from 'customer.subscription.deleted' payload: {:?}", err);
|
||||
StripeWebhookError::ServerError // NB: This was probably *our* fault.
|
||||
})?;
|
||||
|
||||
warn!(">>> subscription.deleted = {:?}", summary);
|
||||
|
||||
Ok(())
|
||||
Ok(StripeWebhookSummary {
|
||||
maybe_user_token: summary.user_token,
|
||||
maybe_event_id: Some(summary.stripe_subscription_id),
|
||||
event_was_handled: false,
|
||||
})
|
||||
}
|
||||
+10
-15
@@ -1,25 +1,20 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use crate::stripe::helpers::common_metadata_keys::METADATA_USER_TOKEN;
|
||||
use crate::stripe::helpers::expand_customer_id::expand_customer_id;
|
||||
use crate::stripe::webhook_event_handlers::stripe_webhook_error::StripeWebhookError;
|
||||
use log::{error, warn};
|
||||
use stripe::{Subscription, SubscriptionInterval};
|
||||
use container_common::anyhow_result::AnyhowResult;
|
||||
use crate::stripe::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
use crate::stripe::webhook_event_handlers::stripe_webhook_error::StripeWebhookError;
|
||||
use crate::stripe::webhook_event_handlers::stripe_webhook_summary::StripeWebhookSummary;
|
||||
use log::{error, warn};
|
||||
use stripe::Subscription;
|
||||
|
||||
/// Handle event type: 'customer.subscription.updated'
|
||||
pub fn customer_subscription_updated_handler(subscription: &Subscription) -> Result<(), StripeWebhookError> {
|
||||
//let json = serde_json::ser::to_string(&subscription).unwrap();
|
||||
//error!("\n\n subscription.updated ----> {}\n\n", json);
|
||||
|
||||
pub fn customer_subscription_updated_handler(subscription: &Subscription) -> Result<StripeWebhookSummary, StripeWebhookError> {
|
||||
let summary = subscription_summary_extractor(subscription)
|
||||
.map_err(|err| {
|
||||
error!("Error extracting subscription from 'customer.subscription.updated' payload: {:?}", err);
|
||||
StripeWebhookError::ServerError // NB: This was probably *our* fault.
|
||||
})?;
|
||||
|
||||
warn!(">>> subscription.updated = {:?}", summary);
|
||||
|
||||
Ok(())
|
||||
Ok(StripeWebhookSummary {
|
||||
maybe_user_token: summary.user_token,
|
||||
maybe_event_id: Some(summary.stripe_subscription_id),
|
||||
event_was_handled: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,3 +6,4 @@ pub (crate) mod customer;
|
||||
pub (crate) mod customer_subscription;
|
||||
pub (crate) mod invoice;
|
||||
pub (crate) mod payment_intent;
|
||||
pub (crate) mod stripe_webhook_summary;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
|
||||
/// Reports common information from the webhook handlers
|
||||
pub struct StripeWebhookSummary {
|
||||
/// If we recorded our own internal user ID as Stripe metadata, this passes it upstream.
|
||||
pub maybe_user_token: Option<String>,
|
||||
|
||||
/// This is the core entity type associated with the webhook event.
|
||||
/// We pass this upstream so we can record it and look it up later for debugging.
|
||||
pub maybe_event_id: Option<String>,
|
||||
|
||||
/// Whether we took action in response to the webhook.
|
||||
/// Not all event types have handlers yet.
|
||||
pub event_was_handled: bool,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod stripe;
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
use anyhow::anyhow;
|
||||
use chrono::NaiveDateTime;
|
||||
use container_common::anyhow_result::AnyhowResult;
|
||||
use sqlx::MySqlPool;
|
||||
|
||||
// TODO: Make a trait with default impls to handle common query concerns.
|
||||
|
||||
pub struct InsertStripeWebhookEventLog {
|
||||
pub stripe_event_id: String,
|
||||
pub stripe_event_type: String,
|
||||
pub stripe_maybe_event_entity_id: Option<String>,
|
||||
pub stripe_event_created_at: NaiveDateTime,
|
||||
pub stripe_is_production: bool,
|
||||
pub maybe_user_token: Option<String>,
|
||||
pub event_was_handled: bool,
|
||||
}
|
||||
|
||||
impl InsertStripeWebhookEventLog {
|
||||
|
||||
pub async fn insert(&self, mysql_pool: &MySqlPool) -> AnyhowResult<()> {
|
||||
|
||||
let query = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO stripe_webhook_event_logs
|
||||
SET
|
||||
stripe_event_id = ?,
|
||||
stripe_event_type = ?,
|
||||
stripe_maybe_event_entity_id = ?,
|
||||
stripe_event_created_at = ?,
|
||||
stripe_is_production = ?,
|
||||
maybe_user_token = ?,
|
||||
event_was_handled = ?
|
||||
"#,
|
||||
&self.stripe_event_id,
|
||||
&self.stripe_event_type,
|
||||
&self.stripe_maybe_event_entity_id,
|
||||
&self.stripe_event_created_at,
|
||||
self.stripe_is_production,
|
||||
&self.maybe_user_token,
|
||||
self.event_was_handled,
|
||||
);
|
||||
|
||||
let query_result = query.execute(mysql_pool).await;
|
||||
|
||||
let _record_id = match query_result {
|
||||
Ok(res) => res.last_insert_id(),
|
||||
Err(err) => return Err(anyhow!("Error creating stripe webhook event log: {:?}", err)),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod insert_stripe_webhook_event_log;
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod api_tokens;
|
||||
pub mod billing;
|
||||
pub mod generic_download;
|
||||
pub mod health_check;
|
||||
pub mod ip_bans;
|
||||
|
||||
Reference in new issue
Block a user