stripe.rs 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. use actix_web::{HttpResponse, HttpRequest, web, post};
  2. use stripe::{Event, EventType, EventObject, Webhook};
  3. use sqlx::PgPool;
  4. use crate::{
  5. http_error::HttpError,
  6. models::{
  7. purchase::{Purchase, Status},
  8. user::User
  9. },
  10. STRIPE_WEBHOOK_SECRET
  11. };
  12. #[post("/webhook/stripe")]
  13. pub async fn route(
  14. req: HttpRequest,
  15. db: web::Data<PgPool>,
  16. body: web::Bytes
  17. ) -> Result<HttpResponse, HttpError> {
  18. let payload = std::str::from_utf8(&body).unwrap();
  19. let signature = get_signature(&req)?;
  20. let secret = STRIPE_WEBHOOK_SECRET.get().unwrap();
  21. let event = Webhook::construct_event(payload, &signature, secret)
  22. .map_err(|_| HttpError::InvalidInput("Failed to verify webhook".into()))?;
  23. match event.type_ {
  24. EventType::PaymentIntentSucceeded => {handle_success(event, &db).await?},
  25. EventType::PaymentIntentPaymentFailed => {handle_failure(event, &db).await?},
  26. _ => ()
  27. };
  28. Ok(HttpResponse::Ok().finish())
  29. }
  30. fn get_signature(req: &HttpRequest) -> Result<String, HttpError> {
  31. let signature = req.headers()
  32. .get("stripe-signature")
  33. .and_then(|v| v.to_str().ok())
  34. .ok_or_else(|| HttpError::InvalidInput("No signature".into()))?
  35. .to_string();
  36. Ok(signature)
  37. }
  38. async fn handle_success(event: Event, db: &PgPool) -> Result<(), HttpError> {
  39. let id = match event.data.object {
  40. EventObject::PaymentIntent(pi) => pi.id.to_string(),
  41. _ => {return Err(HttpError::InvalidInput("No payment intent ID".into()))}
  42. };
  43. let p = Purchase::update_status(db, id, Status::Succeeded).await?;
  44. User::add_tokens(db, p.user_id, p.pack_count * p.tokens_per_pack).await?;
  45. Ok(())
  46. }
  47. async fn handle_failure(event: Event, db: &PgPool) -> Result<(), HttpError> {
  48. let id = match event.data.object {
  49. EventObject::PaymentIntent(pi) => pi.id.to_string(),
  50. _ => {return Err(HttpError::InvalidInput("No payment intent ID".into()))}
  51. };
  52. Purchase::update_status(db, id, Status::Succeeded).await?;
  53. Ok(())
  54. }