sessions parsing

This commit is contained in:
ntr 2019-06-19 22:04:25 +10:00
parent 27864aa9fc
commit a43f0d309e
13 changed files with 568 additions and 134 deletions

View File

@ -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)

View File

@ -465,6 +465,7 @@ header {
.stripe-btn {
width: 100%;
padding: 0 0.5em;
margin: 0.25em 0;
background: whitesmoke;
color: black;
border-radius: 2px;

View File

@ -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 (
<div>
<div id="error-message"></div>
<button
onClick={onClick}
onClick={subscribeClick}
class="stripe-btn"
id="checkout-button-sku_FGjPl1YKoT241P"
role="link">
Subscribe
</button>
<button
onClick={bitsClick}
class="stripe-btn"
role="link">
Get Bits
</button>
</div>
);
}

View File

@ -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" }

View File

@ -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()?;

View File

@ -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<State>, body: web::Json::<CheckoutSession>) -> Result<HttpResponse, MnmlError> {
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<Self>;
fn started(&mut self, _ctx: &mut Self::Context) {
info!("listening for PaymentProcessor");
}
}
impl Supervised for PaymentProcessor {
fn restarting(&mut self, _ctx: &mut Context<PaymentProcessor>) {
warn!("PaymentProcessor restarting");
}
}
impl PaymentProcessor {
pub fn new(pool: PgPool) -> PaymentProcessor {
PaymentProcessor { pool }
}
}
impl Handler<StripeEvent> for PaymentProcessor {
type Result = Result<Vec<Mtx>, MnmlError>;
fn handle(&mut self, msg: StripeEvent, _: &mut Context<Self>) -> 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<Vec<Mtx>, 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<Vec<Mtx>, MnmlError>;
}
pub fn post_stripe_event(state: web::Data<State>, body: web::Json::<Event>) -> Result<HttpResponse, MnmlError> {
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);
}
}

View File

@ -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<PostgresConnectionManager>;
pub type PgPool = Pool<PostgresConnectionManager>;
@ -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<PostgresConnectionManager> {
pub struct State {
pub pool: PgPool,
pub payments: Addr<PaymentProcessor>,
pub pubsub: Addr<PubSub>,
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")

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}
}
}

View File

@ -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
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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
}
}