diff --git a/WORKLOG.md b/WORKLOG.md
index 20cb28d1..85cc2537 100644
--- a/WORKLOG.md
+++ b/WORKLOG.md
@@ -29,17 +29,6 @@
*$$$*
-clicks buy
- creates stripe order / fill 0x order
-
-server notified of payment
- txs <- payment
-
-
-buy supporter pack
- account credited with features
- char sets
- emotes
* balances table (ingame currency)
diff --git a/client/assets/styles/styles.css b/client/assets/styles/styles.css
index 63dd89a3..eb18d3ef 100644
--- a/client/assets/styles/styles.css
+++ b/client/assets/styles/styles.css
@@ -465,6 +465,7 @@ header {
.stripe-btn {
width: 100%;
padding: 0 0.5em;
+ margin: 0.25em 0;
background: whitesmoke;
color: black;
border-radius: 2px;
diff --git a/client/src/components/account.status.jsx b/client/src/components/account.status.jsx
index 5fbe9282..2a7d6edf 100644
--- a/client/src/components/account.status.jsx
+++ b/client/src/components/account.status.jsx
@@ -15,7 +15,7 @@ function BitsBtn(args) {
stripe,
account,
} = args;
- function onClick(e) {
+ function subscribeClick(e) {
stripe.redirectToCheckout({
items: [{plan: 'plan_FGmRwawcOJJ7Nv', quantity: 1}],
successUrl: 'http://localhost:40080/payments/success',
@@ -23,16 +23,31 @@ function BitsBtn(args) {
clientReferenceId: account.id
});
}
+
+ function bitsClick(e) {
+ stripe.redirectToCheckout({
+ items: [{sku: 'sku_FHUfNEhWQaVDaT', quantity: 1}],
+ successUrl: 'http://localhost:40080/payments/success',
+ cancelUrl: 'http://localhost:40080/payments/cancel',
+ clientReferenceId: account.id
+ });
+ }
return (
+
+
);
}
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 2f62374a..6610612d 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -9,6 +9,7 @@ uuid = { version = "0.5", features = ["serde", "v4"] }
serde = "1"
serde_derive = "1"
serde_cbor = "0.9"
+serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
@@ -28,7 +29,9 @@ fern = "0.5"
actix = "0.8.2"
actix-web = "1.0.0"
actix-web-actors = "1.0.0"
-futures = "0.1"
-bytes = "0.4"
+actix-cors = "0.1.0"
-stripe-rust = "0.10.2"
+stripe-rust = { version = "0.10", features = ["webhooks"] }
+
+[patch.crates-io]
+stripe-rust = { path = "/home/ntr/code/stripe-rs" }
diff --git a/server/src/main.rs b/server/src/main.rs
index 94f3df8e..42cbe65a 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -10,6 +10,7 @@ extern crate r2d2_postgres;
extern crate fallible_iterator;
extern crate actix;
+extern crate actix_cors;
extern crate actix_web;
extern crate actix_web_actors;
@@ -58,7 +59,7 @@ fn setup_logger() -> Result<(), fern::InitError> {
))
})
.level_for("postgres", log::LevelFilter::Info)
- .level(log::LevelFilter::Info)
+ .level(log::LevelFilter::Debug)
.chain(std::io::stdout())
.chain(fern::log_file("log/mnml.log")?)
.apply()?;
diff --git a/server/src/mtx.rs b/server/src/mtx.rs
index 3d113323..bf84c101 100644
--- a/server/src/mtx.rs
+++ b/server/src/mtx.rs
@@ -1,15 +1,115 @@
+use uuid::Uuid;
use actix_web::{web, HttpResponse};
-use stripe::{CheckoutSession};
+use actix::prelude::*;
-use net::{State, MnmlError};
+use stripe::{Event, EventObject, CheckoutSession};
-pub fn stripe_payment(state: web::Data, body: web::Json::) -> Result {
- let db = state.pool.get().or(Err(MnmlError::ServerError))?;
- let mut tx = db.transaction().or(Err(MnmlError::ServerError))?;
+use net::{State, PgPool, MnmlError};
- let session = body.into_inner();
+pub struct PaymentProcessor {
+ pool: PgPool,
+}
- info!("{:?}", session);
+impl Actor for PaymentProcessor {
+ type Context = Context;
+ fn started(&mut self, _ctx: &mut Self::Context) {
+ info!("listening for PaymentProcessor");
+ }
+}
+
+impl Supervised for PaymentProcessor {
+ fn restarting(&mut self, _ctx: &mut Context) {
+ warn!("PaymentProcessor restarting");
+ }
+}
+
+impl PaymentProcessor {
+ pub fn new(pool: PgPool) -> PaymentProcessor {
+ PaymentProcessor { pool }
+ }
+}
+
+impl Handler for PaymentProcessor {
+ type Result = Result, MnmlError>;
+
+ fn handle(&mut self, msg: StripeEvent, _: &mut Context) -> Self::Result {
+ let event = msg.0;
+ match event.data.object {
+ EventObject::CheckoutSession(s) => process_stripe_checkout(s),
+ _ => Err(MnmlError::ServerError),
+ }
+ }
+}
+
+fn process_stripe_checkout(session: CheckoutSession) -> Result, MnmlError> {
+ let account = match session.client_reference_id {
+ Some(a) => Uuid::parse_str(&a).or(Err(MnmlError::UnknownUser))?,
+ None => {
+ warn!("unknown user checkout {:?}", session);
+ return Err(MnmlError::UnknownUser)
+ },
+ };
+
+ // checkout.session.completed
+ // assign stripe customer_id to account
+
+ // if subscription
+ // go get it
+ // set account sub to active and end date
+
+ // if just bits purchase
+
+
+ Ok(vec![])
+}
+
+
+#[derive(Debug,Clone,Copy,Serialize,Deserialize)]
+enum Mtx {
+ Subscription,
+ Currency,
+}
+
+#[derive(Debug,Clone,Serialize,Deserialize)]
+struct Order {
+ account: Uuid,
+ customer: String,
+}
+
+struct StripeEvent(Event);
+impl Message for StripeEvent {
+ type Result = Result, MnmlError>;
+}
+
+pub fn post_stripe_event(state: web::Data, body: web::Json::) -> Result {
+ let event: Event = body.into_inner();
+ info!("stripe event {:?}", event);
+ state.payments.do_send(StripeEvent(event));
Ok(HttpResponse::Ok().finish())
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::prelude::*;
+ use std::fs::File;
+
+ #[test]
+ fn test_stripe_checkout() {
+ let mut f = File::open("./test/checkout.session.completed.purchase.json").expect("couldn't open file");
+ let mut checkout_str = String::new();
+ f.read_to_string(&mut checkout_str)
+ .expect("unable to read file");
+
+ let event: Event = serde_json::from_str(&checkout_str)
+ .expect("could not deserialize");
+
+ let mtx = match event.data.object {
+ EventObject::CheckoutSession(s) => process_stripe_checkout(s),
+ _ => panic!("unknown event obj"),
+ };
+
+ println!("got some fuckin bling {:?}", mtx);
+ }
+}
\ No newline at end of file
diff --git a/server/src/net.rs b/server/src/net.rs
index eafdd420..deb2775f 100644
--- a/server/src/net.rs
+++ b/server/src/net.rs
@@ -1,10 +1,10 @@
use std::env;
use actix_web::{middleware, web, App, HttpMessage, HttpRequest, HttpResponse, HttpServer};
-use actix_web::middleware::cors::Cors;
use actix_web::error::ResponseError;
use actix_web::http::{Cookie};
use actix_web::cookie::{SameSite};
+use actix_cors::Cors;
use actix::prelude::*;
@@ -17,7 +17,7 @@ use warden::{Warden};
use pubsub::PubSub;
use ws::{connect};
use account::{account_login, account_create, account_from_token, account_set_token};
-use mtx::{stripe_payment};
+use mtx::{PaymentProcessor, post_stripe_event};
pub type Db = PooledConnection;
pub type PgPool = Pool;
@@ -32,6 +32,9 @@ pub enum MnmlError {
Unauthorized,
#[fail(display="bad request")]
BadRequest,
+
+ #[fail(display="unknown user")]
+ UnknownUser,
}
impl ResponseError for MnmlError {
@@ -51,6 +54,9 @@ impl ResponseError for MnmlError {
.max_age(-1) // 1 week aligns with db set
.finish())
.json(RpcErrorResponse { err: "unauthorized ".to_string() }),
+
+ MnmlError::UnknownUser => HttpResponse::BadRequest()
+ .json(RpcErrorResponse { err: "unknown user".to_string() }),
}
}
}
@@ -139,6 +145,8 @@ fn create_pool(url: String) -> Pool {
pub struct State {
pub pool: PgPool,
+ pub payments: Addr,
+ pub pubsub: Addr,
secure: bool,
}
@@ -147,23 +155,28 @@ pub fn start() {
.expect("DATABASE_URL must be set");
let pool = create_pool(database_url);
- let sys = System::new("mnml");
+ let _sys = System::new("mnml");
- Warden::new(pool.clone()).start();
+ let _warden = Warden::new(pool.clone()).start();
+
+ let payments = PaymentProcessor::new(pool.clone()).start();
let pubsub_conn = pool.get().expect("could not get pubsub pg connection");
- let pubsub_addr = Supervisor::start(move |_| PubSub::new(pubsub_conn));
+ let pubsub = Supervisor::start(move |_| PubSub::new(pubsub_conn));
HttpServer::new(move || App::new()
- .data(State { pool: pool.clone(), secure: false })
+ .data(State { pool: pool.clone(), secure: false, payments: payments.clone(), pubsub: pubsub.clone() })
.wrap(middleware::Logger::default())
.wrap(Cors::new().supports_credentials())
.service(web::resource("/api/login").route(web::post().to(login)))
.service(web::resource("/api/logout").route(web::post().to(logout)))
.service(web::resource("/api/register").route(web::post().to(register)))
- .service(web::resource("/api/payments/stripe").route(web::post().to(stripe_payment)))
- .service(web::resource("/api/payments/crypto").route(web::post().to(stripe_payment)))
+ .service(web::resource("/api/payments/stripe")
+ .route(web::post().to(post_stripe_event)))
+
+ // .service(web::resource("/api/payments/crypto")
+ // .route(web::post().to(post_stripe_payment)))
.service(web::resource("/api/ws").route(web::get().to(connect))))
.bind("127.0.0.1:40000").expect("could not bind to port")
diff --git a/server/test/checkout.session.completed.purchase.json b/server/test/checkout.session.completed.purchase.json
new file mode 100644
index 00000000..70d7967c
--- /dev/null
+++ b/server/test/checkout.session.completed.purchase.json
@@ -0,0 +1,64 @@
+{
+ "id": "evt_1Emw4mGf8y65MteUNt6J2zlN",
+ "object": "event",
+ "api_version": "2019-05-16",
+ "created": 1560921076,
+ "data": {
+ "object": {
+ "id": "cs_test_0FldEAQpyiya1xFLtZpPuEaWQ8OGnMMsotlnwrWCk8jXrAeeMtVruHwB",
+ "object": "checkout.session",
+ "billing_address_collection": null,
+ "cancel_url": "http://localhost:40080/payments/cancel",
+ "client_reference_id": "ff3bbecb-e744-4674-b411-a11d6832a5ac",
+ "customer": "cus_FHV47hm01bNBpG",
+ "customer_email": null,
+ "display_items": [
+ {
+ "amount": 500,
+ "currency": "aud",
+ "quantity": 1,
+ "sku": {
+ "id": "sku_FHUfNEhWQaVDaT",
+ "object": "sku",
+ "active": true,
+ "attributes": {
+ "name": "20 bits"
+ },
+ "created": 1560919564,
+ "currency": "aud",
+ "image": null,
+ "inventory": {
+ "quantity": null,
+ "type": "infinite",
+ "value": null
+ },
+ "livemode": false,
+ "metadata": {
+ },
+ "package_dimensions": null,
+ "price": 500,
+ "product": "prod_FHUfY9DFwl0pPl",
+ "updated": 1560919796
+ },
+ "type": "sku"
+ }
+ ],
+ "livemode": false,
+ "locale": null,
+ "payment_intent": "pi_1Emw4WGf8y65MteUBrVOy4ME",
+ "payment_method_types": [
+ "card"
+ ],
+ "submit_type": null,
+ "subscription": null,
+ "success_url": "http://localhost:40080/payments/success"
+ }
+ },
+ "livemode": false,
+ "pending_webhooks": 3,
+ "request": {
+ "id": null,
+ "idempotency_key": null
+ },
+ "type": "checkout.session.completed"
+}
\ No newline at end of file
diff --git a/server/test/checkout.session.completed.subscription.json b/server/test/checkout.session.completed.subscription.json
new file mode 100644
index 00000000..29a7e29e
--- /dev/null
+++ b/server/test/checkout.session.completed.subscription.json
@@ -0,0 +1,63 @@
+{
+ "id": "evt_1EmH4cGf8y65MteUj9qhzJzD",
+ "object": "event",
+ "api_version": "2019-05-16",
+ "created": 1560763462,
+ "data": {
+ "object": {
+ "id": "cs_test_RH4RVfajVXRAcruFssXLthaDnfVBJGiPumUfMzPdj5DixpieRl645hkQ",
+ "object": "checkout.session",
+ "billing_address_collection": null,
+ "cancel_url": "http://localhost:40080/payments/cancel",
+ "client_reference_id": "ff3bbecb-e744-4674-b411-a11d6832a5ac",
+ "customer": "cus_FGoioD9GGtTXlW",
+ "customer_email": null,
+ "display_items": [
+ {
+ "amount": 1000,
+ "currency": "aud",
+ "plan": {
+ "id": "plan_FGmRwawcOJJ7Nv",
+ "object": "plan",
+ "active": true,
+ "aggregate_usage": null,
+ "amount": 1000,
+ "billing_scheme": "per_unit",
+ "created": 1560755040,
+ "currency": "aud",
+ "interval": "month",
+ "interval_count": 1,
+ "livemode": false,
+ "metadata": {
+ },
+ "nickname": "basic",
+ "product": "prod_FGmRFYmB700pM5",
+ "tiers": null,
+ "tiers_mode": null,
+ "transform_usage": null,
+ "trial_period_days": null,
+ "usage_type": "licensed"
+ },
+ "quantity": 1,
+ "type": "plan"
+ }
+ ],
+ "livemode": false,
+ "locale": null,
+ "payment_intent": null,
+ "payment_method_types": [
+ "card"
+ ],
+ "submit_type": null,
+ "subscription": "sub_FGoiRaWHZUF01V",
+ "success_url": "http://localhost:40080/payments/success"
+ }
+ },
+ "livemode": false,
+ "pending_webhooks": 1,
+ "request": {
+ "id": null,
+ "idempotency_key": null
+ },
+ "type": "checkout.session.completed"
+}
\ No newline at end of file
diff --git a/server/test/checkout.session.completed.test.json b/server/test/checkout.session.completed.test.json
new file mode 100644
index 00000000..16817050
--- /dev/null
+++ b/server/test/checkout.session.completed.test.json
@@ -0,0 +1,43 @@
+{
+ "created": 1326853478,
+ "livemode": false,
+ "id": "evt_00000000000000",
+ "type": "checkout.session.completed",
+ "object": "event",
+ "request": null,
+ "pending_webhooks": 1,
+ "api_version": "2019-05-16",
+ "data": {
+ "object": {
+ "id": "cs_00000000000000",
+ "object": "checkout.session",
+ "billing_address_collection": null,
+ "cancel_url": "https://example.com/cancel",
+ "client_reference_id": null,
+ "customer": null,
+ "customer_email": null,
+ "display_items": [
+ {
+ "amount": 1500,
+ "currency": "usd",
+ "custom": {
+ "description": "Comfortable cotton t-shirt",
+ "images": null,
+ "name": "T-shirt"
+ },
+ "quantity": 2,
+ "type": "custom"
+ }
+ ],
+ "livemode": false,
+ "locale": null,
+ "payment_intent": "pi_00000000000000",
+ "payment_method_types": [
+ "card"
+ ],
+ "submit_type": null,
+ "subscription": null,
+ "success_url": "https://example.com/success"
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/test/customer.subscription.created.json b/server/test/customer.subscription.created.json
new file mode 100644
index 00000000..effb3c2a
--- /dev/null
+++ b/server/test/customer.subscription.created.json
@@ -0,0 +1,109 @@
+{
+ "created": 1326853478,
+ "livemode": false,
+ "id": "evt_00000000000000",
+ "type": "customer.subscription.created",
+ "object": "event",
+ "request": null,
+ "pending_webhooks": 1,
+ "api_version": "2019-05-16",
+ "data": {
+ "object": {
+ "id": "sub_00000000000000",
+ "object": "subscription",
+ "application_fee_percent": null,
+ "billing": "charge_automatically",
+ "billing_cycle_anchor": 1560755136,
+ "billing_thresholds": null,
+ "cancel_at": null,
+ "cancel_at_period_end": false,
+ "canceled_at": null,
+ "collection_method": "charge_automatically",
+ "created": 1560755136,
+ "current_period_end": 1563347136,
+ "current_period_start": 1560755136,
+ "customer": "cus_00000000000000",
+ "days_until_due": null,
+ "default_payment_method": "pm_1EmEuIGf8y65MteU7aM67eNH",
+ "default_source": null,
+ "default_tax_rates": [
+ ],
+ "discount": null,
+ "ended_at": null,
+ "items": {
+ "object": "list",
+ "data": [
+ {
+ "id": "si_00000000000000",
+ "object": "subscription_item",
+ "billing_thresholds": null,
+ "created": 1560755136,
+ "metadata": {
+ },
+ "plan": {
+ "id": "plan_00000000000000",
+ "object": "plan",
+ "active": true,
+ "aggregate_usage": null,
+ "amount": 1000,
+ "billing_scheme": "per_unit",
+ "created": 1560755040,
+ "currency": "aud",
+ "interval": "month",
+ "interval_count": 1,
+ "livemode": false,
+ "metadata": {
+ },
+ "nickname": "basic",
+ "product": "prod_00000000000000",
+ "tiers": null,
+ "tiers_mode": null,
+ "transform_usage": null,
+ "trial_period_days": null,
+ "usage_type": "licensed"
+ },
+ "quantity": 1,
+ "subscription": "sub_00000000000000"
+ }
+ ],
+ "has_more": false,
+ "total_count": 1,
+ "url": "/v1/subscription_items?subscription=sub_FGmToAWeV09Z35"
+ },
+ "latest_invoice": "in_1EmEuKGf8y65MteUxapoByyd",
+ "livemode": false,
+ "metadata": {
+ },
+ "plan": {
+ "id": "plan_00000000000000",
+ "object": "plan",
+ "active": true,
+ "aggregate_usage": null,
+ "amount": 1000,
+ "billing_scheme": "per_unit",
+ "created": 1560755040,
+ "currency": "aud",
+ "interval": "month",
+ "interval_count": 1,
+ "livemode": false,
+ "metadata": {
+ },
+ "nickname": "basic",
+ "product": "prod_00000000000000",
+ "tiers": null,
+ "tiers_mode": null,
+ "transform_usage": null,
+ "trial_period_days": null,
+ "usage_type": "licensed"
+ },
+ "quantity": 1,
+ "schedule": null,
+ "start": 1560755136,
+ "start_date": 1560755136,
+ "status": "active",
+ "tax_percent": null,
+ "trial_end": null,
+ "trial_start": null
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/test/customer.subscription.updated.json b/server/test/customer.subscription.updated.json
new file mode 100644
index 00000000..0e3974c2
--- /dev/null
+++ b/server/test/customer.subscription.updated.json
@@ -0,0 +1,134 @@
+{
+ "created": 1326853478,
+ "livemode": false,
+ "id": "evt_00000000000000",
+ "type": "customer.subscription.updated",
+ "object": "event",
+ "request": null,
+ "pending_webhooks": 1,
+ "api_version": "2019-05-16",
+ "data": {
+ "object": {
+ "id": "sub_00000000000000",
+ "object": "subscription",
+ "application_fee_percent": null,
+ "billing": "charge_automatically",
+ "billing_cycle_anchor": 1560755136,
+ "billing_thresholds": null,
+ "cancel_at": null,
+ "cancel_at_period_end": false,
+ "canceled_at": null,
+ "collection_method": "charge_automatically",
+ "created": 1560755136,
+ "current_period_end": 1563347136,
+ "current_period_start": 1560755136,
+ "customer": "cus_00000000000000",
+ "days_until_due": null,
+ "default_payment_method": "pm_1EmEuIGf8y65MteU7aM67eNH",
+ "default_source": null,
+ "default_tax_rates": [
+ ],
+ "discount": null,
+ "ended_at": null,
+ "items": {
+ "object": "list",
+ "data": [
+ {
+ "id": "si_00000000000000",
+ "object": "subscription_item",
+ "billing_thresholds": null,
+ "created": 1560755136,
+ "metadata": {
+ },
+ "plan": {
+ "id": "plan_00000000000000",
+ "object": "plan",
+ "active": true,
+ "aggregate_usage": null,
+ "amount": 1000,
+ "billing_scheme": "per_unit",
+ "created": 1560755040,
+ "currency": "aud",
+ "interval": "month",
+ "interval_count": 1,
+ "livemode": false,
+ "metadata": {
+ },
+ "nickname": "basic",
+ "product": "prod_00000000000000",
+ "tiers": null,
+ "tiers_mode": null,
+ "transform_usage": null,
+ "trial_period_days": null,
+ "usage_type": "licensed"
+ },
+ "quantity": 1,
+ "subscription": "sub_00000000000000"
+ }
+ ],
+ "has_more": false,
+ "total_count": 1,
+ "url": "/v1/subscription_items?subscription=sub_FGmToAWeV09Z35"
+ },
+ "latest_invoice": "in_1EmEuKGf8y65MteUxapoByyd",
+ "livemode": false,
+ "metadata": {
+ },
+ "plan": {
+ "id": "plan_00000000000000",
+ "object": "plan",
+ "active": true,
+ "aggregate_usage": null,
+ "amount": 1000,
+ "billing_scheme": "per_unit",
+ "created": 1560755040,
+ "currency": "aud",
+ "interval": "month",
+ "interval_count": 1,
+ "livemode": false,
+ "metadata": {
+ },
+ "nickname": "basic",
+ "product": "prod_00000000000000",
+ "tiers": null,
+ "tiers_mode": null,
+ "transform_usage": null,
+ "trial_period_days": null,
+ "usage_type": "licensed"
+ },
+ "quantity": 1,
+ "schedule": null,
+ "start": 1560755136,
+ "start_date": 1560755136,
+ "status": "active",
+ "tax_percent": null,
+ "trial_end": null,
+ "trial_start": null
+ },
+ "previous_attributes": {
+ "plan": {
+ "id": "OLD_00000000000000",
+ "object": "plan",
+ "active": true,
+ "aggregate_usage": null,
+ "amount": 1000,
+ "billing_scheme": "per_unit",
+ "created": 1560755040,
+ "currency": "aud",
+ "interval": "month",
+ "interval_count": 1,
+ "livemode": false,
+ "metadata": {
+ },
+ "nickname": "basic",
+ "product": "prod_00000000000000",
+ "tiers": null,
+ "tiers_mode": null,
+ "transform_usage": null,
+ "trial_period_days": null,
+ "usage_type": "licensed",
+ "name": "Old plan"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/test/subscription.json b/server/test/subscription.json
deleted file mode 100644
index 495be9bc..00000000
--- a/server/test/subscription.json
+++ /dev/null
@@ -1,101 +0,0 @@
-{
- "object": {
- "id": "sub_FGmgBxTVX2xM7H",
- "object": "subscription",
- "application_fee_percent": null,
- "billing": "charge_automatically",
- "billing_cycle_anchor": 1560755931,
- "billing_thresholds": null,
- "cancel_at": null,
- "cancel_at_period_end": false,
- "canceled_at": null,
- "collection_method": "charge_automatically",
- "created": 1560755931,
- "current_period_end": 1563347931,
- "current_period_start": 1560755931,
- "customer": "cus_FGmgIHDimD5Tei",
- "days_until_due": null,
- "default_payment_method": "pm_1EmF78Gf8y65MteUQBHQU1po",
- "default_source": null,
- "default_tax_rates": [
- ],
- "discount": null,
- "ended_at": null,
- "items": {
- "object": "list",
- "data": [
- {
- "id": "si_FGmg6F7DZAyU2Y",
- "object": "subscription_item",
- "billing_thresholds": null,
- "created": 1560755932,
- "metadata": {
- },
- "plan": {
- "id": "plan_FGmRwawcOJJ7Nv",
- "object": "plan",
- "active": true,
- "aggregate_usage": null,
- "amount": 1000,
- "billing_scheme": "per_unit",
- "created": 1560755040,
- "currency": "aud",
- "interval": "month",
- "interval_count": 1,
- "livemode": false,
- "metadata": {
- },
- "nickname": "basic",
- "product": "prod_FGmRFYmB700pM5",
- "tiers": null,
- "tiers_mode": null,
- "transform_usage": null,
- "trial_period_days": null,
- "usage_type": "licensed"
- },
- "quantity": 1,
- "subscription": "sub_FGmgBxTVX2xM7H",
- "tax_rates": [
- ]
- }
- ],
- "has_more": false,
- "total_count": 1,
- "url": "/v1/subscription_items?subscription=sub_FGmgBxTVX2xM7H"
- },
- "latest_invoice": "in_1EmF7AGf8y65MteUvH8MCela",
- "livemode": false,
- "metadata": {
- },
- "plan": {
- "id": "plan_FGmRwawcOJJ7Nv",
- "object": "plan",
- "active": true,
- "aggregate_usage": null,
- "amount": 1000,
- "billing_scheme": "per_unit",
- "created": 1560755040,
- "currency": "aud",
- "interval": "month",
- "interval_count": 1,
- "livemode": false,
- "metadata": {
- },
- "nickname": "basic",
- "product": "prod_FGmRFYmB700pM5",
- "tiers": null,
- "tiers_mode": null,
- "transform_usage": null,
- "trial_period_days": null,
- "usage_type": "licensed"
- },
- "quantity": 1,
- "schedule": null,
- "start": 1560755931,
- "start_date": 1560755931,
- "status": "active",
- "tax_percent": null,
- "trial_end": null,
- "trial_start": null
- }
-}
\ No newline at end of file