record whether webhook events had actions taken, idempotency flag

This commit is contained in:
Brandon Thomas committed 2022-09-19 03:54:32 -04:00
1 parent 3fb26771de
commit 334fc3022e
17 files changed
+62 -27

No files matched your search

@@ -33,8 +33,12 @@ CREATE TABLE stripe_webhook_event_logs (
-- ========== INTERNAL SYSTEM METADATA ==========
-- Whether we handled the event
event_was_handled BOOLEAN NOT NULL DEFAULT FALSE,
-- Whether we handled the event with any sort of action
action_was_taken BOOLEAN NOT NULL DEFAULT FALSE,
-- Whether we should ignore any future retries by Stripe replaying the event.
-- This helps us treat events with idempotency.
should_ignore_retry BOOLEAN NOT NULL DEFAULT FALSE,
-- Some event types may attach a user token
maybe_user_token VARCHAR(32) DEFAULT NULL,
@@ -98,7 +98,8 @@ pub async fn stripe_webhook_handler(
maybe_user_token: None,
maybe_event_entity_id: None,
maybe_stripe_customer_id: None,
event_was_handled: false
action_was_taken: false,
should_ignore_retry: false,
};
// NB:
@@ -298,7 +299,8 @@ pub async fn stripe_webhook_handler(
stripe_event_created_at,
stripe_is_production,
maybe_user_token: webhook_summary.maybe_user_token,
event_was_handled: webhook_summary.event_was_handled,
action_was_taken: webhook_summary.action_was_taken,
should_ignore_retry: webhook_summary.should_ignore_retry,
};
query.insert(&mysql_pool)
@@ -26,6 +26,7 @@ pub fn charge_succeeded_handler(charge: &Charge) -> Result<StripeWebhookSummary,
maybe_user_token: maybe_user_token,
maybe_event_entity_id: Some(charge_id),
maybe_stripe_customer_id: maybe_stripe_customer_id,
event_was_handled: false,
action_was_taken: false,
should_ignore_retry: false,
})
}
@@ -28,6 +28,7 @@ pub fn checkout_session_completed_handler(checkout_session: CheckoutSession) ->
maybe_user_token: maybe_user_token,
maybe_event_entity_id: Some(stripe_checkout_id),
maybe_stripe_customer_id: maybe_stripe_customer_id,
event_was_handled: false,
action_was_taken: false,
should_ignore_retry: false,
})
}
@@ -20,6 +20,7 @@ pub fn customer_created_handler(customer: &Customer) -> Result<StripeWebhookSumm
maybe_user_token: maybe_user_token,
maybe_event_entity_id: Some(customer_id.clone()),
maybe_stripe_customer_id: Some(customer_id.clone()),
event_was_handled: false,
action_was_taken: false,
should_ignore_retry: false,
})
}
@@ -19,6 +19,7 @@ pub fn customer_deleted_handler(customer: &Customer) -> Result<StripeWebhookSumm
maybe_user_token: maybe_user_token,
maybe_event_entity_id: Some(customer_id.clone()),
maybe_stripe_customer_id: Some(customer_id.clone()),
event_was_handled: false,
action_was_taken: false,
should_ignore_retry: false,
})
}
@@ -19,6 +19,7 @@ pub fn customer_updated_handler(customer: &Customer) -> Result<StripeWebhookSumm
maybe_user_token: maybe_user_token,
maybe_event_entity_id: Some(customer_id.clone()),
maybe_stripe_customer_id: Some(customer_id.clone()),
event_was_handled: false,
action_was_taken: false,
should_ignore_retry: false,
})
}
@@ -27,7 +27,8 @@ pub async fn customer_subscription_created_handler(
maybe_user_token: summary.user_token.clone(),
maybe_event_entity_id: Some(summary.stripe_subscription_id.clone()),
maybe_stripe_customer_id: Some(summary.stripe_customer_id.clone()),
event_was_handled: false,
action_was_taken: false,
should_ignore_retry: false,
};
// NB: It's possible to receive events out of order.
@@ -40,6 +41,7 @@ pub async fn customer_subscription_created_handler(
})?;
if maybe_existing_subscription.is_some() {
result.should_ignore_retry = true;
return Ok(result);
}
@@ -70,6 +72,8 @@ pub async fn customer_subscription_created_handler(
StripeWebhookError::ServerError
})?;
result.event_was_handled = true;
result.action_was_taken = true;
result.should_ignore_retry = true;
Ok(result)
}
@@ -21,6 +21,7 @@ pub async fn customer_subscription_deleted_handler(
maybe_user_token: summary.user_token,
maybe_event_entity_id: Some(summary.stripe_subscription_id),
maybe_stripe_customer_id: Some(summary.stripe_customer_id),
event_was_handled: false,
action_was_taken: false,
should_ignore_retry: false,
})
}
@@ -21,8 +21,10 @@ pub async fn customer_subscription_updated_handler(
StripeWebhookError::ServerError // NB: This was probably *our* fault.
})?;
let mut should_process_update = true;
let mut skip_update = false;
let mut action_was_taken = false;
let mut should_ignore_retry = false;
// NB: It's possible to receive events out of order.
let maybe_existing_subscription = get_subscription_by_stripe_id(&summary.stripe_subscription_id, &mysql_pool)
@@ -36,7 +38,8 @@ pub async fn customer_subscription_updated_handler(
match existing_subscription.maybe_stripe_subscription_status {
Some(StripeSubscriptionStatus::Canceled) => {
// NB: This is a terminal status and the subscription cannot be updated any further.
skip_update = true;
should_process_update = false;
should_ignore_retry = true;
}
_ => {}
}
@@ -44,7 +47,7 @@ pub async fn customer_subscription_updated_handler(
// TODO: record cancel_at (future_cancel_at), canceled_at, ended_at (if subscription ended, when it ended), start_date
if !skip_update {
if should_process_update {
let upsert = UpsertSubscriptionByStripeId {
stripe_subscription_id: &summary.stripe_subscription_id,
maybe_user_token: summary.user_token.as_deref(),
@@ -69,12 +72,16 @@ pub async fn customer_subscription_updated_handler(
error!("Mysql error: {:?}", err);
StripeWebhookError::ServerError
})?;
action_was_taken = true;
should_ignore_retry = true;
}
Ok(StripeWebhookSummary {
maybe_user_token: summary.user_token,
maybe_event_entity_id: Some(summary.stripe_subscription_id),
maybe_stripe_customer_id: Some(summary.stripe_customer_id),
event_was_handled: true,
action_was_taken,
should_ignore_retry,
})
}
@@ -78,7 +78,8 @@ pub fn invoice_paid_handler(invoice: &Invoice) -> Result<StripeWebhookSummary, S
maybe_user_token: None,
maybe_event_entity_id: None,
maybe_stripe_customer_id: None,
event_was_handled: false
action_was_taken: false,
should_ignore_retry: false,
})
}
@@ -26,6 +26,7 @@ pub fn invoice_payment_failed_handler(invoice: &Invoice) -> Result<StripeWebhook
maybe_user_token: maybe_user_token,
maybe_event_entity_id: Some(invoice_id),
maybe_stripe_customer_id: maybe_stripe_customer_id,
event_was_handled: false,
action_was_taken: false,
should_ignore_retry: false,
})
}
@@ -26,6 +26,7 @@ pub fn invoice_payment_succeeded_handler(invoice: &Invoice) -> Result<StripeWebh
maybe_user_token: maybe_user_token,
maybe_event_entity_id: Some(invoice_id),
maybe_stripe_customer_id: maybe_stripe_customer_id,
event_was_handled: false,
action_was_taken: false,
should_ignore_retry: false,
})
}
@@ -31,6 +31,7 @@ pub fn invoice_updated_handler(invoice: &Invoice) -> Result<StripeWebhookSummary
maybe_user_token: maybe_user_token,
maybe_event_entity_id: Some(invoice_id),
maybe_stripe_customer_id: maybe_stripe_customer_id,
event_was_handled: false,
action_was_taken: false,
should_ignore_retry: false,
})
}
@@ -26,6 +26,7 @@ pub fn payment_intent_succeeded_handler(payment_intent: &PaymentIntent) -> Resul
maybe_user_token: maybe_user_token,
maybe_event_entity_id: Some(payment_intent_id),
maybe_stripe_customer_id: maybe_stripe_customer_id,
event_was_handled: false,
action_was_taken: false,
should_ignore_retry: false,
})
}
@@ -11,7 +11,11 @@ pub struct StripeWebhookSummary {
/// This is the stripe customer ID, if it was associated with the event.
pub maybe_stripe_customer_id: Option<String>,
/// Whether we took action in response to the webhook.
/// Not all event types have handlers yet.
pub event_was_handled: bool,
/// Whether we took any sort of action in response to the webhook.
/// Not all event types have handlers yet, and even so, sometimes we may choose to do nothing.
pub action_was_taken: bool,
/// Whether any retries should be dropped.
/// This helps us handle event with idempotency in the event Stripe replays them (and it can!)
pub should_ignore_retry: bool,
}
@@ -13,7 +13,8 @@ pub struct InsertStripeWebhookEventLog {
pub stripe_event_created_at: NaiveDateTime,
pub stripe_is_production: bool,
pub maybe_user_token: Option<String>,
pub event_was_handled: bool,
pub action_was_taken: bool,
pub should_ignore_retry: bool,
}
impl InsertStripeWebhookEventLog {
@@ -31,7 +32,8 @@ SET
stripe_event_created_at = ?,
stripe_is_production = ?,
maybe_user_token = ?,
event_was_handled = ?
action_was_taken = ?,
should_ignore_retry = ?
"#,
&self.stripe_event_id,
&self.stripe_event_type,
@@ -40,7 +42,8 @@ SET
&self.stripe_event_created_at,
self.stripe_is_production,
&self.maybe_user_token,
self.event_was_handled,
self.action_was_taken,
self.should_ignore_retry,
);
let query_result = query.execute(mysql_pool).await;