|
@@ -0,0 +1,59 @@
|
|
|
|
|
+use actix_web::{HttpResponse, HttpRequest, web, post};
|
|
|
|
|
+use stripe::{Event, EventType, EventObject, Webhook};
|
|
|
|
|
+use sqlx::PgPool;
|
|
|
|
|
+use crate::{
|
|
|
|
|
+ http_error::HttpError,
|
|
|
|
|
+ models::purchase::{Purchase, Status},
|
|
|
|
|
+ STRIPE_WEBHOOK_SECRET
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+#[post("/webhook/stripe")]
|
|
|
|
|
+pub async fn route(
|
|
|
|
|
+ req: HttpRequest,
|
|
|
|
|
+ db: web::Data<PgPool>,
|
|
|
|
|
+ body: web::Bytes
|
|
|
|
|
+) -> Result<HttpResponse, HttpError> {
|
|
|
|
|
+ let payload = std::str::from_utf8(&body).unwrap();
|
|
|
|
|
+ let signature = get_signature(&req)?;
|
|
|
|
|
+ let secret = STRIPE_WEBHOOK_SECRET.get().unwrap();
|
|
|
|
|
+ let event = Webhook::construct_event(payload, &signature, secret)
|
|
|
|
|
+ .map_err(|_| HttpError::InvalidInput("Failed to verify webhook".into()))?;
|
|
|
|
|
+
|
|
|
|
|
+ match event.type_ {
|
|
|
|
|
+ EventType::PaymentIntentSucceeded => {handle_success(event, &db).await?},
|
|
|
|
|
+ EventType::PaymentIntentPaymentFailed => {handle_failure(event, &db).await?},
|
|
|
|
|
+ _ => ()
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ Ok(HttpResponse::Ok().finish())
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+fn get_signature(req: &HttpRequest) -> Result<String, HttpError> {
|
|
|
|
|
+ let signature = req.headers()
|
|
|
|
|
+ .get("stripe-signature")
|
|
|
|
|
+ .and_then(|v| v.to_str().ok())
|
|
|
|
|
+ .ok_or_else(|| HttpError::InvalidInput("No signature".into()))?
|
|
|
|
|
+ .to_string();
|
|
|
|
|
+
|
|
|
|
|
+ Ok(signature)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async fn handle_success(event: Event, db: &PgPool) -> Result<(), HttpError> {
|
|
|
|
|
+ let id = match event.data.object {
|
|
|
|
|
+ EventObject::PaymentIntent(pi) => pi.id.to_string(),
|
|
|
|
|
+ _ => {return Err(HttpError::InvalidInput("No payment intent ID".into()))}
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ Purchase::update_status(db, id, Status::Succeeded).await?;
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async fn handle_failure(event: Event, db: &PgPool) -> Result<(), HttpError> {
|
|
|
|
|
+ let id = match event.data.object {
|
|
|
|
|
+ EventObject::PaymentIntent(pi) => pi.id.to_string(),
|
|
|
|
|
+ _ => {return Err(HttpError::InvalidInput("No payment intent ID".into()))}
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ Purchase::update_status(db, id, Status::Succeeded).await?;
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+}
|