wyyerd / stripe-rs

Rust API bindings for the Stripe HTTP API.
Apache License 2.0
223 stars 88 forks source link

How to use `checkout_session::CheckoutSession` vs `placeholders::CheckoutSession` for Event` #166

Closed seenickcode closed 3 years ago

seenickcode commented 3 years ago

Hi there, I'm a bit new to Rust and wanted to use this lib for receiving a Stripe webhook for event type checkout.session.completed. My goal is to be able to get the SKU associated with a checkout_session's line_items field, yet accessing the Event only provides the placeholders::CheckoutSession type which doesn't expose what I need (it only exposes an ID).

Here's an example actix_web handler I'm using to attempt to get this.

Line 34 is what I'm having trouble with:

use super::AppData;
use crate::DbPool;
use actix_session::Session;
use actix_web::{web, Error, HttpRequest};
use actix_web::{HttpResponse, Result};
use log::info;
use std::borrow::Borrow;
use stripe::{CheckoutSession, Event, EventObject, EventType, Webhook};

pub async fn stripe_webhook(
    req: HttpRequest,
    data: web::Data<AppData>,
    pool: web::Data<DbPool>,
    payload: web::Bytes,
) -> Result<HttpResponse, Error> {
    // deserialize Stripe event
    let payload_str = std::str::from_utf8(payload.borrow())?;

    let sig = get_header_value(&req, "Stripe-Signature").unwrap_or_default();
    let secret = data.stripe_webhook_secret.clone();

    let event = Webhook::construct_event(payload_str, sig, &secret[..]).unwrap();

    info!("got stripe event {:?}", event);

    if let EventType::CheckoutSessionCompleted = event.event_type {
        // TODO move to another method
        // let sku = event.data.object::Sku;
        if let EventObject::CheckoutSession(session) = event.data.object {
            // using the 'session' of the event, get the first 'display item' sku
            // ..and the customer email
            let sku = session.id;
            let items = session.line_items;

            // fetch the user via customer email

            // TODO fetch the course via sku (based on prod/non-prod flag)

            // create a purchase using user ID, course slug, stripe email (using user email),
            // transaction ID using the stripe session ID
        }
    }

    // return a 201
    Ok(HttpResponse::Created().finish())
}

fn get_header_value<'a>(req: &'a HttpRequest, key: &'a str) -> Option<&'a str> {
    req.headers().get(key)?.to_str().ok()
}

Any help would be appreciated. Thanks.

seenickcode commented 3 years ago

Turns out I needed to enable the "checkout" feature flag, i.e. stripe-rust = { version = "0.12.3", default-features = false, features = ["checkout", "default-tls", "webhook-events"] }

mikeumus commented 3 years ago

Hi @seenickcode, I'm noobing out here but could you share your AppData or an version of it? Also was pool ever used from the stripe_webhook params? This is could probably be added to /examples as how to parse webhook events. 🤷🏻‍♂️

Thank you 🙇🏻‍♂️

seenickcode commented 3 years ago
#[derive(Clone)]
pub struct AppData {
    pub stripe_webhook_secret: String,
}

impl AppData {
    pub fn new() -> Self {    
        Self {
            stripe_webhook_secret: get_env(
                "STRIPE_WEBHOOK_SECRET",
                "redacted",
            ),
        }
    }
}

Yes I use pool in the handler because I need to access my database.