subs
This commit is contained in:
parent
e6486dc361
commit
4921b0760c
@ -293,7 +293,7 @@ button[disabled] {
|
||||
}
|
||||
|
||||
/*
|
||||
HEADER
|
||||
account
|
||||
*/
|
||||
|
||||
header {
|
||||
@ -303,23 +303,26 @@ header {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
.account {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.account-title {
|
||||
flex: 1;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.header-status {
|
||||
margin: 1em 0;
|
||||
.account-status {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.header-username {
|
||||
.account-header {
|
||||
letter-spacing: 0.05em;
|
||||
flex: 1;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.header-status svg {
|
||||
.account-status svg {
|
||||
margin: 0.5em 0 0 1em;
|
||||
height: 1em;
|
||||
background-color: black;
|
||||
|
||||
@ -32,15 +32,20 @@ function BitsBtn(args) {
|
||||
clientReferenceId: account.id
|
||||
});
|
||||
}
|
||||
|
||||
const subscription = account.subscribed
|
||||
? <h3 class="account-header">Subscribed</h3>
|
||||
: <button
|
||||
onClick={subscribeClick}
|
||||
class="stripe-btn"
|
||||
role="link">
|
||||
Subscribe
|
||||
</button>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div id="error-message"></div>
|
||||
<button
|
||||
onClick={subscribeClick}
|
||||
class="stripe-btn"
|
||||
role="link">
|
||||
Subscribe
|
||||
</button>
|
||||
{subscription}
|
||||
<button
|
||||
onClick={bitsClick}
|
||||
class="stripe-btn"
|
||||
@ -84,12 +89,13 @@ function AccountStatus(args) {
|
||||
if (!account) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div class="header-status">
|
||||
<h2 class="header-username">{account.name}</h2>
|
||||
<div class="account">
|
||||
<div class="account-status">
|
||||
<h2 class="account-header">{account.name}</h2>
|
||||
{saw(pingColour(ping))}
|
||||
<div class="ping-text">{ping}ms</div>
|
||||
</div>
|
||||
<h3 class="account-header">{`¤${account.credits}`}</h3>
|
||||
<Elements>
|
||||
<StripeBitsBtn account={account} />
|
||||
</Elements>
|
||||
|
||||
@ -78,6 +78,7 @@ class Login extends Component {
|
||||
class="login-input"
|
||||
type="email"
|
||||
placeholder="username"
|
||||
tabIndex={1}
|
||||
value={this.state.name}
|
||||
onInput={this.nameInput}
|
||||
/>
|
||||
@ -85,6 +86,7 @@ class Login extends Component {
|
||||
class="login-input"
|
||||
type="password"
|
||||
placeholder="password"
|
||||
tabIndex={2}
|
||||
value={this.state.password}
|
||||
onInput={this.passwordInput}
|
||||
/>
|
||||
@ -92,16 +94,19 @@ class Login extends Component {
|
||||
class="login-input"
|
||||
type="text"
|
||||
placeholder="code"
|
||||
tabIndex={3}
|
||||
value={this.state.code}
|
||||
onInput={this.codeInput}
|
||||
/>
|
||||
<button
|
||||
class="login-btn"
|
||||
tabIndex={4}
|
||||
onClick={this.loginSubmit}>
|
||||
Login
|
||||
</button>
|
||||
<button
|
||||
class="login-btn"
|
||||
tabIndex={5}
|
||||
onClick={this.registerSubmit}>
|
||||
Register
|
||||
</button>
|
||||
|
||||
@ -2,14 +2,29 @@ exports.up = async knex => {
|
||||
return knex.schema.createTable('accounts', table => {
|
||||
table.uuid('id').primary();
|
||||
table.timestamps(true, true);
|
||||
|
||||
table.string('name', 42).notNullable().unique();
|
||||
table.string('password').notNullable();
|
||||
|
||||
table.string('token', 64).notNullable();
|
||||
table.timestamp('token_expiry').notNullable();
|
||||
|
||||
table.bigInteger('credits')
|
||||
.defaultTo(0)
|
||||
.notNullable();
|
||||
|
||||
table.bool('subscribed')
|
||||
.defaultTo(false)
|
||||
.notNullable();
|
||||
|
||||
table.index('name');
|
||||
table.index('id');
|
||||
});
|
||||
|
||||
await knex.schema.raw(`
|
||||
ALTER TABLE accounts
|
||||
ADD CHECK (credits > 0);
|
||||
`);
|
||||
};
|
||||
|
||||
exports.down = async () => {};
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
exports.up = async knex => {
|
||||
await knex.schema.createTable('stripe_customers', table => {
|
||||
table.string('customer', 64)
|
||||
table.string('customer', 128)
|
||||
.primary();
|
||||
|
||||
table.uuid('account')
|
||||
@ -16,7 +16,7 @@ exports.up = async knex => {
|
||||
.inTable('accounts')
|
||||
.onDelete('RESTRICT');
|
||||
|
||||
table.string('checkout', 64)
|
||||
table.string('checkout', 128)
|
||||
.notNullable()
|
||||
.unique();
|
||||
|
||||
@ -24,7 +24,7 @@ exports.up = async knex => {
|
||||
});
|
||||
|
||||
await knex.schema.createTable('stripe_subscriptions', table => {
|
||||
table.string('subscription', 64)
|
||||
table.string('subscription', 128)
|
||||
.primary();
|
||||
|
||||
table.uuid('account')
|
||||
@ -36,14 +36,17 @@ exports.up = async knex => {
|
||||
.inTable('accounts')
|
||||
.onDelete('RESTRICT');
|
||||
|
||||
table.string('customer', 64)
|
||||
table.string('customer', 128)
|
||||
.notNullable();
|
||||
|
||||
table.string('checkout', 128)
|
||||
.notNullable();
|
||||
|
||||
table.timestamps(true, true);
|
||||
});
|
||||
|
||||
await knex.schema.createTable('stripe_purchases', table => {
|
||||
table.string('checkout', 64)
|
||||
table.string('checkout', 128)
|
||||
.primary();
|
||||
|
||||
table.uuid('account')
|
||||
@ -55,7 +58,7 @@ exports.up = async knex => {
|
||||
.inTable('accounts')
|
||||
.onDelete('RESTRICT');
|
||||
|
||||
table.string('customer', 64)
|
||||
table.string('customer', 128)
|
||||
.notNullable();
|
||||
|
||||
table.bigInteger('amount')
|
||||
|
||||
@ -31,4 +31,9 @@ actix-web = "1.0.0"
|
||||
actix-web-actors = "1.0.0"
|
||||
actix-cors = "0.1.0"
|
||||
|
||||
stripe-rust = { version = "0.10", features = ["webhooks"] }
|
||||
stripe-rust = { version = "0.10.4", features = ["webhooks"] }
|
||||
|
||||
[patch.crates-io]
|
||||
# stripe-rust = { git = "https://github.com/margh/stripe-rs.git" }
|
||||
|
||||
stripe-rust = { path = "/home/ntr/code/stripe-rs" }
|
||||
|
||||
@ -21,15 +21,15 @@ pub struct Account {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub credits: u32,
|
||||
pub subscribed: bool,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn select(tx: &mut Transaction, id: Uuid) -> Result<Account, Error> {
|
||||
let query = "
|
||||
SELECT id, name, credits
|
||||
SELECT id, name, credits, subscribed
|
||||
FROM accounts
|
||||
WHERE id = $1
|
||||
FOR UPDATE;
|
||||
WHERE id = $1;
|
||||
";
|
||||
|
||||
let result = tx
|
||||
@ -38,17 +38,18 @@ impl Account {
|
||||
let row = result.iter().next()
|
||||
.ok_or(format_err!("account not found {:?}", id))?;
|
||||
|
||||
let db_credits: i32 = row.get(2);
|
||||
|
||||
let db_credits: i64 = row.get(2);
|
||||
let credits = u32::try_from(db_credits)
|
||||
.or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_credits)))?;
|
||||
|
||||
Ok(Account { id, name: row.get(1), credits })
|
||||
let subscribed: bool = row.get(3);
|
||||
|
||||
Ok(Account { id, name: row.get(1), credits, subscribed })
|
||||
}
|
||||
|
||||
pub fn from_token(tx: &mut Transaction, token: String) -> Result<Account, Error> {
|
||||
let query = "
|
||||
SELECT id, name, credits
|
||||
SELECT id, name, subscribed, credits
|
||||
FROM accounts
|
||||
WHERE token = $1
|
||||
AND token_expiry > now();
|
||||
@ -61,17 +62,19 @@ impl Account {
|
||||
.ok_or(err_msg("invalid token"))?;
|
||||
|
||||
let id: Uuid = row.get(0);
|
||||
let db_credits: i32 = row.get(2);
|
||||
let name: String = row.get(1);
|
||||
let subscribed: bool = row.get(2);
|
||||
let db_credits: i64 = row.get(3);
|
||||
|
||||
let credits = u32::try_from(db_credits)
|
||||
.or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_credits)))?;
|
||||
|
||||
Ok(Account { id, name: row.get(1), credits })
|
||||
Ok(Account { id, name, credits, subscribed })
|
||||
}
|
||||
|
||||
pub fn login(tx: &mut Transaction, name: &String, password: &String) -> Result<Account, Error> {
|
||||
let query = "
|
||||
SELECT id, password, name, credits
|
||||
SELECT id, password, name, credits, subscribed
|
||||
FROM accounts
|
||||
WHERE name = $1
|
||||
";
|
||||
@ -97,7 +100,8 @@ impl Account {
|
||||
let id: Uuid = row.get(0);
|
||||
let hash: String = row.get(1);
|
||||
let name: String = row.get(2);
|
||||
let db_credits: i32 = row.get(3);
|
||||
let db_credits: i64 = row.get(3);
|
||||
let subscribed: bool = row.get(4);
|
||||
|
||||
if !verify(password, &hash)? {
|
||||
return Err(err_msg("password does not match"));
|
||||
@ -106,10 +110,10 @@ impl Account {
|
||||
let credits = u32::try_from(db_credits)
|
||||
.or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_credits)))?;
|
||||
|
||||
Ok(Account { id, name, credits })
|
||||
Ok(Account { id, name, credits, subscribed })
|
||||
}
|
||||
|
||||
pub fn new_token(&self, tx: &mut Transaction) -> Result<String, Error> {
|
||||
pub fn new_token(tx: &mut Transaction, id: Uuid) -> Result<String, Error> {
|
||||
let mut rng = thread_rng();
|
||||
let token: String = iter::repeat(())
|
||||
.map(|()| rng.sample(Alphanumeric))
|
||||
@ -125,26 +129,67 @@ impl Account {
|
||||
";
|
||||
|
||||
let result = tx
|
||||
.query(query, &[&token, &self.id])?;
|
||||
.query(query, &[&token, &id])?;
|
||||
|
||||
let _row = result.iter().next()
|
||||
.ok_or(format_err!("account not updated {:?}", self.id))?;
|
||||
let row = result.iter().next()
|
||||
.ok_or(format_err!("account not updated {:?}", id))?;
|
||||
|
||||
info!("login account={:?}", self.name);
|
||||
let name: String = row.get(1);
|
||||
|
||||
info!("login account={:?}", name);
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
}
|
||||
pub fn add_credits(tx: &mut Transaction, id: Uuid, credits: i64) -> Result<String, Error> {
|
||||
let query = "
|
||||
UPDATE accounts
|
||||
SET credits = credits + $1
|
||||
WHERE id = $2
|
||||
RETURNING credits, name;
|
||||
";
|
||||
|
||||
#[derive(Debug,Clone,Serialize,Deserialize)]
|
||||
struct AccountEntry {
|
||||
id: Uuid,
|
||||
name: String,
|
||||
password: String,
|
||||
token: String,
|
||||
}
|
||||
let result = tx
|
||||
.query(query, &[&credits, &id])?;
|
||||
|
||||
let row = result.iter().next()
|
||||
.ok_or(format_err!("account not updated {:?}", id))?;
|
||||
|
||||
println!("{:?}", row);
|
||||
|
||||
let db_credits: i64 = row.get(0);
|
||||
let total = u32::try_from(db_credits)
|
||||
.or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_credits)))?;
|
||||
|
||||
let name: String = row.get(1);
|
||||
|
||||
info!("account credited name={:?} credited={:?} total={:?}", name, credits, total);
|
||||
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
pub fn set_subscribed(tx: &mut Transaction, id: Uuid, subscribed: bool) -> Result<String, Error> {
|
||||
let query = "
|
||||
UPDATE accounts
|
||||
SET subscribed = $1
|
||||
WHERE id = $2
|
||||
RETURNING name;
|
||||
";
|
||||
|
||||
let result = tx
|
||||
.query(query, &[&subscribed, &id])?;
|
||||
|
||||
let row = result.iter().next()
|
||||
.ok_or(format_err!("account not updated {:?}", id))?;
|
||||
|
||||
let name: String = row.get(0);
|
||||
|
||||
info!("account subscription status updated name={:?} subscribed={:?}", name, subscribed);
|
||||
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn account_create(name: &String, password: &String, code: &String, tx: &mut Transaction) -> Result<String, Error> {
|
||||
if password.len() < PASSWORD_MIN_LEN {
|
||||
@ -251,4 +296,4 @@ pub fn account_instances(tx: &mut Transaction, account: &Account) -> Result<Vec<
|
||||
list.sort_unstable_by_key(|c| c.name.clone());
|
||||
|
||||
return Ok(list);
|
||||
}
|
||||
}
|
||||
|
||||
137
server/src/mtx.rs
Normal file
137
server/src/mtx.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use uuid::Uuid;
|
||||
// use rand::prelude::*;
|
||||
|
||||
use serde_cbor::{from_slice};
|
||||
use postgres::transaction::Transaction;
|
||||
|
||||
use failure::Error;
|
||||
use failure::err_msg;
|
||||
|
||||
#[derive(Debug,Copy,Clone,Serialize,Deserialize)]
|
||||
pub enum MtxVariant {
|
||||
ArchitectureMolecular,
|
||||
ArchitectureInvader,
|
||||
}
|
||||
|
||||
impl MtxVariant {
|
||||
fn new(self, account: Uuid) -> Mtx {
|
||||
match self {
|
||||
MtxVariant::ArchitectureInvader => Mtx { id: Uuid::new_v4(), account, variant: self },
|
||||
MtxVariant::ArchitectureMolecular => Mtx { id: Uuid::new_v4(), account, variant: self },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Copy,Clone,Serialize,Deserialize)]
|
||||
pub struct Mtx {
|
||||
id: Uuid,
|
||||
account: Uuid,
|
||||
variant: MtxVariant,
|
||||
}
|
||||
|
||||
impl Mtx {
|
||||
pub fn account_list(tx: &mut Transaction, account: Uuid) -> Result<Vec<Mtx>, Error> {
|
||||
let query = "
|
||||
SELECT data, id
|
||||
FROM mtx
|
||||
WHERE account = $1
|
||||
FOR UPDATE;
|
||||
";
|
||||
|
||||
let result = tx
|
||||
.query(query, &[&account])?;
|
||||
|
||||
let values = result.into_iter().filter_map(|row| {
|
||||
let bytes: Vec<u8> = row.get(0);
|
||||
// let id: Uuid = row.get(1);
|
||||
|
||||
match from_slice::<Mtx>(&bytes) {
|
||||
Ok(i) => Some(i),
|
||||
Err(e) => {
|
||||
warn!("{:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}).collect::<Vec<Mtx>>();
|
||||
|
||||
return Ok(values);
|
||||
}
|
||||
|
||||
pub fn delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> {
|
||||
let query = "
|
||||
DELETE
|
||||
FROM mtx
|
||||
WHERE id = $1;
|
||||
";
|
||||
|
||||
let result = tx
|
||||
.execute(query, &[&id])?;
|
||||
|
||||
if result != 1 {
|
||||
return Err(format_err!("unable to delete mtx {:?}", id));
|
||||
}
|
||||
|
||||
info!("mtx deleted {:?}", id);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn insert(&self, tx: &mut Transaction) -> Result<&Mtx, Error> {
|
||||
let query = "
|
||||
INSERT INTO mtx (id, account, variant)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, account;
|
||||
";
|
||||
|
||||
let result = tx
|
||||
.query(query, &[&self.id, &self.account, &format!("{:?}", self.variant)])?;
|
||||
|
||||
result.iter().next().ok_or(err_msg("mtx not written"))?;
|
||||
|
||||
info!("wrote mtx {:?}", self);
|
||||
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
// pub fn update(&self, tx: &mut Transaction) -> Result<&Mtx, Error> {
|
||||
// let query = "
|
||||
// UPDATE mtx
|
||||
// SET data = $1, updated_at = now()
|
||||
// WHERE id = $2
|
||||
// RETURNING id, data;
|
||||
// ";
|
||||
|
||||
// let result = tx
|
||||
// .query(query, &[&self.id, &to_vec(self)?])?;
|
||||
|
||||
// if let None = result.iter().next() {
|
||||
// return Err(err_msg("mtx not written"));
|
||||
// }
|
||||
|
||||
// info!("wrote mtx {:?}", self);
|
||||
|
||||
// return Ok(self);
|
||||
// }
|
||||
|
||||
pub fn select(tx: &mut Transaction, id: Uuid, account: Uuid) -> Result<Option<Mtx>, Error> {
|
||||
let query = "
|
||||
SELECT data, id
|
||||
FROM mtx
|
||||
WHERE account = $1
|
||||
AND id = $2
|
||||
FOR UPDATE;
|
||||
";
|
||||
|
||||
let result = tx
|
||||
.query(query, &[&account, &id])?;
|
||||
|
||||
if let Some(row) = result.iter().next() {
|
||||
let bytes: Vec<u8> = row.get(0);
|
||||
Ok(Some(from_slice::<Mtx>(&bytes)?))
|
||||
} else {
|
||||
Err(format_err!("mtx not found {:?}", id))
|
||||
}
|
||||
}
|
||||
|
||||
// actual impl
|
||||
}
|
||||
@ -84,7 +84,7 @@ fn login(state: web::Data<State>, params: web::Json::<AccountLoginParams>) -> Re
|
||||
|
||||
match Account::login(&mut tx, ¶ms.name, ¶ms.password) {
|
||||
Ok(a) => {
|
||||
let token = a.new_token(&mut tx).or(Err(MnmlHttpError::ServerError))?;
|
||||
let token = Account::new_token(&mut tx, a.id).or(Err(MnmlHttpError::ServerError))?;
|
||||
tx.commit().or(Err(MnmlHttpError::ServerError))?;
|
||||
Ok(login_res(token))
|
||||
},
|
||||
@ -102,7 +102,7 @@ fn logout(r: HttpRequest, state: web::Data<State>) -> Result<HttpResponse, MnmlH
|
||||
let mut tx = db.transaction().or(Err(MnmlHttpError::ServerError))?;
|
||||
match Account::from_token(&mut tx, t.value().to_string()) {
|
||||
Ok(a) => {
|
||||
a.new_token(&mut tx).or(Err(MnmlHttpError::Unauthorized))?;
|
||||
Account::new_token(&mut tx, a.id).or(Err(MnmlHttpError::Unauthorized))?;
|
||||
tx.commit().or(Err(MnmlHttpError::ServerError))?;
|
||||
return Ok(logout_res());
|
||||
},
|
||||
|
||||
@ -5,14 +5,32 @@ use postgres::transaction::Transaction;
|
||||
use failure::Error;
|
||||
use failure::err_msg;
|
||||
|
||||
use stripe::{Event, EventObject, CheckoutSession};
|
||||
use stripe::{Event, EventObject, CheckoutSession, SubscriptionStatus};
|
||||
|
||||
use net::{State, PgPool, MnmlHttpError};
|
||||
use account::{Account};
|
||||
|
||||
pub fn subscription_account(tx: &mut Transaction, sub: String) -> Result<Uuid, Error> {
|
||||
let query = "
|
||||
SELECT account
|
||||
FROM stripe_subscriptions
|
||||
WHERE subscription = $1;
|
||||
";
|
||||
|
||||
let result = tx
|
||||
.query(query, &[&sub])?;
|
||||
|
||||
let row = result.iter().next()
|
||||
.ok_or(err_msg("user not subscribed"))?;
|
||||
|
||||
Ok(row.get(0))
|
||||
}
|
||||
|
||||
// we use i64 because it is converted to BIGINT for pg
|
||||
const CREDITS_COST_CENTS: i64 = 20;
|
||||
const CREDITS_SUB_BONUS: i64 = 25;
|
||||
// and we can losslessly pull it into u32 which is big
|
||||
// enough for the ballers
|
||||
const CREDITS_COST_CENTS: i64 = 10;
|
||||
const CREDITS_SUB_BONUS: i64 = 40;
|
||||
|
||||
// Because the client_reference_id (account.id) is only included
|
||||
// in the stripe CheckoutSession object
|
||||
@ -60,14 +78,25 @@ impl StripeData {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_credits(&self, tx: &mut Transaction) -> Result<&StripeData, Error> {
|
||||
fn side_effects(&self, tx: &mut Transaction) -> Result<&StripeData, Error> {
|
||||
match self {
|
||||
// when we get a subscription we just immediately set the user to be subbed
|
||||
// so we don't have to deal with going to fetch all the details from
|
||||
// stripe just to double check
|
||||
// update webhooks will tell us when the subscription changes
|
||||
// see EventObject::Subscription handler below
|
||||
StripeData::Subscription { subscription: _, account, customer: _, checkout: _ } => {
|
||||
let account = Account::select(tx, *account)?;
|
||||
Account::add_credits(tx, *account, CREDITS_SUB_BONUS)?;
|
||||
Account::set_subscribed(tx, *account, true)?;
|
||||
Ok(self)
|
||||
},
|
||||
StripeData::Purchase { account: _, customer: _, amount, checkout: _ } => {
|
||||
amount.checked_div(CREDITS_COST_CENTS).expect("credits cost 0");
|
||||
StripeData::Purchase { account, customer: _, amount, checkout: _ } => {
|
||||
let credits = amount
|
||||
.checked_div(CREDITS_COST_CENTS)
|
||||
.expect("credits cost 0");
|
||||
|
||||
Account::add_credits(tx, *account, credits)?;
|
||||
|
||||
Ok(self)
|
||||
},
|
||||
_ => Ok(self),
|
||||
@ -108,8 +137,11 @@ fn stripe_checkout_data(session: CheckoutSession) -> Result<Vec<StripeData>, Err
|
||||
return Ok(items);
|
||||
}
|
||||
|
||||
fn process_stripe(event: Event, pool: &PgPool) -> Result<Vec<StripeData>, Error> {
|
||||
fn process_stripe_event(event: Event, pool: &PgPool) -> Result<String, Error> {
|
||||
info!("stripe event {:?}", event);
|
||||
let connection = pool.get()?;
|
||||
let mut tx = connection.transaction()?;
|
||||
|
||||
match event.data.object {
|
||||
EventObject::CheckoutSession(s) => {
|
||||
let data = match stripe_checkout_data(s) {
|
||||
@ -120,28 +152,50 @@ fn process_stripe(event: Event, pool: &PgPool) -> Result<Vec<StripeData>, Error>
|
||||
}
|
||||
};
|
||||
|
||||
let connection = pool.get()?;
|
||||
let mut tx = connection.transaction()?;
|
||||
|
||||
for item in data.iter() {
|
||||
item.insert(&mut tx)?;
|
||||
item.add_credits(&mut tx)?;
|
||||
item.side_effects(&mut tx)?;
|
||||
}
|
||||
|
||||
tx.commit()?;
|
||||
Ok(data)
|
||||
},
|
||||
|
||||
// we only receive the cancelled and updated events
|
||||
// because the checkout object is needed to link
|
||||
// a sub to an account initially and
|
||||
// stripe doesn't guarantee the order
|
||||
// so this just checks if the sub is still active
|
||||
EventObject::Subscription(s) => {
|
||||
let account = subscription_account(&mut tx, s.id.to_string())?;
|
||||
|
||||
let subbed = match s.status {
|
||||
SubscriptionStatus::Active => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
Account::set_subscribed(&mut tx, account, subbed)?;
|
||||
}
|
||||
_ => {
|
||||
error!("unhandled stripe event {:?}", event);
|
||||
Err(err_msg("UnhanldedEvent"))
|
||||
return Err(err_msg("UnhanldedEvent"));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
tx.commit()?;
|
||||
|
||||
Ok(event.id.to_string())
|
||||
}
|
||||
|
||||
pub fn post_stripe_event(state: web::Data<State>, body: web::Json::<Event>) -> Result<HttpResponse, MnmlHttpError> {
|
||||
let event: Event = body.into_inner();
|
||||
process_stripe(event, &state.pool).or(Err(MnmlHttpError::ServerError))?;
|
||||
Ok(HttpResponse::Accepted().finish())
|
||||
match process_stripe_event(event, &state.pool) {
|
||||
Ok(id)=> {
|
||||
info!("event processed successfully {:?}", id);
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{:?}", e);
|
||||
Err(MnmlHttpError::ServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user