Improve FAQ & video rendering, tighten homepage and CSS
This commit is contained in:
parent
05a1f316e1
commit
c69bb0d614
48 changed files with 4689 additions and 1077 deletions
|
|
@ -1,10 +1,13 @@
|
|||
use std::sync::Arc;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use axum::body::Bytes;
|
||||
use axum::extract::State;
|
||||
use axum::http::{HeaderMap, StatusCode};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use hmac::{Hmac, Mac};
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashSet;
|
||||
use sha2::Sha256;
|
||||
use tracing::{info, warn};
|
||||
|
||||
|
|
@ -13,6 +16,46 @@ use crate::state::SharedState;
|
|||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
/// Process-local LRU of recently processed Stripe event IDs.
|
||||
/// Stripe retries deliver the same event ID; we drop duplicates so we don't
|
||||
/// re-run side effects (subscription writes, token cache invalidation, logs).
|
||||
/// Capacity is intentionally generous: at typical webhook volumes this covers
|
||||
/// far more than Stripe's retry window.
|
||||
struct EventDedup {
|
||||
seen: FxHashSet<String>,
|
||||
queue: VecDeque<String>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl EventDedup {
|
||||
fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
seen: FxHashSet::default(),
|
||||
queue: VecDeque::with_capacity(capacity),
|
||||
capacity,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this event ID is new (and records it),
|
||||
/// `false` if it was already seen recently.
|
||||
fn check_and_insert(&mut self, id: &str) -> bool {
|
||||
if self.seen.contains(id) {
|
||||
return false;
|
||||
}
|
||||
self.seen.insert(id.to_string());
|
||||
self.queue.push_back(id.to_string());
|
||||
if self.queue.len() > self.capacity {
|
||||
if let Some(old) = self.queue.pop_front() {
|
||||
self.seen.remove(&old);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
static EVENT_DEDUP: LazyLock<Mutex<EventDedup>> =
|
||||
LazyLock::new(|| Mutex::new(EventDedup::new(1024)));
|
||||
|
||||
/// Verify Stripe webhook signature (v1 scheme).
|
||||
fn verify_signature(payload: &[u8], sig_header: &str, secret: &str) -> bool {
|
||||
// Parse timestamp and signature from header: "t=TIMESTAMP,v1=SIGNATURE"
|
||||
|
|
@ -95,7 +138,16 @@ pub async fn post_stripe_webhook(
|
|||
};
|
||||
|
||||
let event_type = event["type"].as_str().unwrap_or("");
|
||||
info!(event_type, "Received Stripe webhook");
|
||||
let event_id = event["id"].as_str().unwrap_or("");
|
||||
|
||||
// Idempotency: drop replays/retries of an already-processed event.
|
||||
// We always answer 200 so Stripe stops retrying.
|
||||
if !event_id.is_empty() && !EVENT_DEDUP.lock().check_and_insert(event_id) {
|
||||
info!(event_id, event_type, "Dropping duplicate Stripe webhook");
|
||||
return StatusCode::OK.into_response();
|
||||
}
|
||||
|
||||
info!(event_id, event_type, "Received Stripe webhook");
|
||||
|
||||
if event_type == "checkout.session.completed" {
|
||||
let user_id = event["data"]["object"]["client_reference_id"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue