mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
subscription extractor
This commit is contained in:
1 parent
3b82e7a389
commit
2eaf7c499e
4 files changed
+125
-90
No files matched your search
+8
-1
@@ -1,12 +1,18 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use crate::stripe::helpers::common_metadata_keys::METADATA_USER_TOKEN;
|
||||
use crate::stripe::helpers::expand_customer_id::expand_customer_id;
|
||||
use crate::stripe::webhook_event_handlers::stripe_webhook_error::StripeWebhookError;
|
||||
use log::{error, warn};
|
||||
use stripe::Subscription;
|
||||
use stripe::{Subscription, SubscriptionInterval};
|
||||
use container_common::anyhow_result::AnyhowResult;
|
||||
|
||||
/// Handle event type: 'customer.subscription.updated'
|
||||
pub fn customer_subscription_updated_handler(subscription: &Subscription) -> Result<(), StripeWebhookError> {
|
||||
|
||||
let json = serde_json::ser::to_string(&subscription).unwrap();
|
||||
|
||||
error!("\n\n subscription.updated ----> {}\n\n", json);
|
||||
|
||||
let stripe_subscription_id = subscription.id.to_string();
|
||||
let stripe_subscription_status = subscription.status.to_string();
|
||||
|
||||
@@ -23,3 +29,4 @@ pub fn customer_subscription_updated_handler(subscription: &Subscription) -> Res
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
pub mod customer_subscription_created_handler;
|
||||
pub mod customer_subscription_deleted_handler;
|
||||
pub mod customer_subscription_updated_handler;
|
||||
pub mod customer_subscription_updated_handler;
|
||||
pub mod subscription_event_extractor;
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
use anyhow::anyhow;
|
||||
use chrono::{DateTime, Utc};
|
||||
use stripe::{Price, Subscription, SubscriptionInterval, SubscriptionStatus};
|
||||
use container_common::anyhow_result::AnyhowResult;
|
||||
use crate::stripe::helpers::common_metadata_keys::METADATA_USER_TOKEN;
|
||||
use crate::stripe::helpers::expand_customer_id::expand_customer_id;
|
||||
use crate::stripe::helpers::expand_product_id::expand_product_id;
|
||||
|
||||
pub struct SubscriptionSummary {
|
||||
pub user_token: Option<String>,
|
||||
|
||||
pub stripe_is_production: bool,
|
||||
|
||||
pub stripe_subscription_id: String,
|
||||
pub stripe_subscription_status: SubscriptionStatus,
|
||||
|
||||
pub stripe_customer_id: String,
|
||||
pub stripe_product_id: String,
|
||||
pub stripe_price_id: String,
|
||||
|
||||
pub subscription_is_active: bool,
|
||||
pub billed_at: DateTime<Utc>,
|
||||
|
||||
/// Tell the update handler how many days in the future to set the plan.
|
||||
pub subscription_interval: SubscriptionInterval,
|
||||
/// Calculated from interval
|
||||
pub subscription_expires_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Extract only the subscription details we care about
|
||||
/// This should be unit testable against raw webhook JSON.
|
||||
pub fn subscription_summary_extractor(subscription: &Subscription) -> AnyhowResult<SubscriptionSummary> {
|
||||
let subscription_id = subscription.id.to_string();
|
||||
// NB: Our internal user token.
|
||||
let maybe_user_token = subscription.metadata.get(METADATA_USER_TOKEN)
|
||||
.map(|t| t.to_string());
|
||||
|
||||
if subscription.items.data.len() != 1 {
|
||||
return Err(anyhow!("Too many items in subscription {} : {}",
|
||||
subscription_id, subscription.items.data.len()));
|
||||
}
|
||||
|
||||
let item = match subscription.items.data.first() {
|
||||
None => return Err(anyhow!("Could not get first item in subscription {}", subscription_id)),
|
||||
Some(line_item) => line_item,
|
||||
};
|
||||
|
||||
let price = match &item.price {
|
||||
None => return Err(anyhow!("Could not get item price in subscription {}", subscription_id)),
|
||||
Some(price) => price,
|
||||
};
|
||||
|
||||
let product = match &price.product {
|
||||
None => return Err(anyhow!("Could not get product in subscription {}", subscription_id)),
|
||||
Some(product) => product,
|
||||
};
|
||||
|
||||
Ok(SubscriptionSummary {
|
||||
user_token: maybe_user_token,
|
||||
stripe_subscription_id: subscription_id,
|
||||
stripe_is_production: subscription.livemode,
|
||||
stripe_customer_id: expand_customer_id(&subscription.customer),
|
||||
stripe_subscription_status: subscription.status,
|
||||
stripe_product_id: expand_product_id(product),
|
||||
stripe_price_id: price.id.to_string(),
|
||||
subscription_is_active: subscription.status == SubscriptionStatus::Active,
|
||||
billed_at: Utc::now(),
|
||||
subscription_interval: Default::default(),
|
||||
subscription_expires_at: Utc::now(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use stripe::{Subscription, SubscriptionStatus};
|
||||
use crate::stripe::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
|
||||
#[test]
|
||||
fn test_subscription_summary_extractor() {
|
||||
// NB: Actual raw test data from Stripe to our webhook
|
||||
let json = r#"
|
||||
{"id":"sub_1Lh1wvEU5se17Mekx72OzAzs","automatic_tax":{"enabled":false},
|
||||
"billing_cycle_anchor":1662948037,
|
||||
"cancel_at_period_end":false,"collection_method":"charge_automatically","created":1662948037,
|
||||
"current_period_end":1665540037,"current_period_start":1662948037,
|
||||
"customer":"cus_MPrgIen5Wh6QKG",
|
||||
"default_payment_method":"pm_1Lh1wtEU5se17MekpirKtMJm","default_tax_rates":[],
|
||||
"items":{"data":[{"id":"si_MPrgTAV333Nq7c","created":1662948037,"deleted":false,"metadata":{},
|
||||
"price":{"id":"price_1LeDnKEU5se17MekVr1iYYNf","active":true,"billing_scheme":"per_unit",
|
||||
"created":1662278586,"currency":"usd","deleted":false,"livemode":false,"metadata":{},
|
||||
"product":"prod_MMxi2J5y69VPbO","recurring":{"interval":"month","interval_count":1,
|
||||
"usage_type":"licensed"},"tax_behavior":"exclusive","type":"recurring","unit_amount":700,
|
||||
"unit_amount_decimal":"700"},"quantity":1,"subscription":"sub_1Lh1wvEU5se17Mekx72OzAzs",
|
||||
"tax_rates":[]}],"has_more":false,"total_count":1,
|
||||
"url":"/v1/subscription_items?subscription=sub_1Lh1wvEU5se17Mekx72OzAzs"},
|
||||
"latest_invoice":"in_1Lh1wvEU5se17MekU99NMi1W","livemode":false,
|
||||
"metadata":{"username":"echelon","user_token":"U:token","email":"email@address.com"},
|
||||
"payment_settings":{"save_default_payment_method":"off"},
|
||||
"start_date":1662948037,"status":"active"}
|
||||
"#;
|
||||
|
||||
let subscription = serde_json::from_str::<Subscription>(json).unwrap();
|
||||
|
||||
let summary= subscription_summary_extractor(&subscription).unwrap();
|
||||
|
||||
assert_eq!(summary.user_token, Some("U:token".to_string()));
|
||||
assert_eq!(summary.stripe_subscription_id, "sub_1Lh1wvEU5se17Mekx72OzAzs".to_string());
|
||||
assert_eq!(summary.stripe_subscription_status, SubscriptionStatus::Active);
|
||||
assert_eq!(summary.subscription_is_active, true);
|
||||
assert_eq!(summary.stripe_customer_id, "cus_MPrgIen5Wh6QKG".to_string());
|
||||
assert_eq!(summary.stripe_product_id, "prod_MMxi2J5y69VPbO".to_string());
|
||||
assert_eq!(summary.stripe_price_id, "price_1LeDnKEU5se17MekVr1iYYNf".to_string());
|
||||
assert_eq!(summary.stripe_is_production, false);
|
||||
}
|
||||
}
|
||||
-88
@@ -66,94 +66,6 @@ pub enum InvoicePaidDetails {
|
||||
// appropriate date in the future (plus a day or two for leeway).
|
||||
//
|
||||
pub fn invoice_paid_handler(invoice: &Invoice) -> Result<(), StripeWebhookError> {
|
||||
|
||||
let invoice_id = invoice.id.to_string();
|
||||
|
||||
let is_production= invoice.livemode.unwrap_or(false);
|
||||
|
||||
let paid_status = invoice.status;
|
||||
|
||||
match invoice.status {
|
||||
Some(InvoiceStatus::Paid) => {
|
||||
// Stripe Docs: Your webhook endpoint updates the customer’s access expiration date in your
|
||||
// database to the appropriate date in the future (plus a day or two for leeway).
|
||||
|
||||
error!(" ======================= INVOICE IS PAID ===========================");
|
||||
}
|
||||
_ => {},
|
||||
//None => {}
|
||||
//Some(status) => match status {
|
||||
// InvoiceStatus::Deleted => {}
|
||||
// InvoiceStatus::Draft => {}
|
||||
// InvoiceStatus::Open => {}
|
||||
// InvoiceStatus::Paid => {}
|
||||
// InvoiceStatus::Uncollectible => {}
|
||||
// InvoiceStatus::Void => {}
|
||||
//}
|
||||
}
|
||||
|
||||
error!(" -> invoice data lines = {}", invoice.lines.data.len());
|
||||
|
||||
/*
|
||||
======================= INVOICE IS PAID ===========================
|
||||
-> invoice data lines = 1
|
||||
-> subscription = "sub_1Lgk5BEU5se17MekYFmwTX6v"
|
||||
-> subscription_item = "si_MPZDG0ikIjHnLJ"
|
||||
-> invoice_item = ""
|
||||
-> price.id = "price_1LeDnKEU5se17MekVr1iYYNf"
|
||||
-> USER_TOKEN = "U:SECOND_ID"
|
||||
-> type_ = Subscription
|
||||
--> SUBSCRIPTION <--
|
||||
*/
|
||||
|
||||
let json = serde_json::ser::to_string(invoice).unwrap();
|
||||
|
||||
|
||||
|
||||
error!("\n\n ---> {}\n\n", json);
|
||||
|
||||
invoice.lines.data.iter().for_each(|line| {
|
||||
error!(" -> subscription = {:?}", line.subscription.as_ref().map(|s| s.clone()).unwrap_or("".to_string()));
|
||||
error!(" -> subscription_item = {:?}", line.subscription_item.as_ref().map(|s| s.clone()).unwrap_or("".to_string()));
|
||||
error!(" -> invoice_item = {:?}", line.invoice_item.as_ref().map(|s| s.to_string()).unwrap_or("".to_string()));
|
||||
error!(" -> price.id = {:?}", line.price.as_ref().map(|price| price.id.to_string()).unwrap_or("".to_string()));
|
||||
error!(" -> product.id = {:?}", line.price.as_ref().map(|price| price.product.clone()));
|
||||
error!(" -> USER_TOKEN = {:?}", line.metadata.get(METADATA_USER_TOKEN).map(|d| d.to_string()).unwrap_or("".to_string()));
|
||||
error!(" -> type_ = {:?}", line.type_);
|
||||
match line.type_ {
|
||||
InvoiceLineItemType::InvoiceItem => {
|
||||
error!(" --> INVOICE ITEM <-- ");
|
||||
}
|
||||
InvoiceLineItemType::Subscription => {
|
||||
error!(" --> SUBSCRIPTION <-- ");
|
||||
}
|
||||
}
|
||||
|
||||
error!("\n ------> {:?}\n", line);
|
||||
});
|
||||
|
||||
let maybe_stripe_subscription_id = invoice.subscription
|
||||
.as_ref()
|
||||
.map(|s| expand_subscription_id(s));
|
||||
|
||||
// NB: We'll need this to send them to the "customer portal", which is how they can modify
|
||||
// or cancel their subscriptions.
|
||||
let maybe_stripe_customer_id = invoice.customer
|
||||
.as_ref()
|
||||
.map(|c| expand_customer_id(c));
|
||||
|
||||
// NB: Our internal user token.
|
||||
let maybe_user_token = invoice.metadata.get(METADATA_USER_TOKEN)
|
||||
.map(|t| t.to_string());
|
||||
|
||||
error!(">>> invoice.paid: {:?}, {:?}, {:?}, {:?}, {:?}",
|
||||
invoice_id,
|
||||
paid_status,
|
||||
maybe_stripe_customer_id,
|
||||
maybe_stripe_subscription_id,
|
||||
maybe_user_token
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user