email and sub status

This commit is contained in:
ntr 2019-08-29 12:43:06 +10:00
parent d7b887e1ad
commit ffc070ed51
10 changed files with 124 additions and 48 deletions

View File

@ -3,7 +3,9 @@
*PRODUCTION* *PRODUCTION*
* ACP * ACP
* essential * essential
* stripe verify * serde serialize privatise
* DO postgres
* mobile styles
* treats * treats
* constructs jiggle when clicked * constructs jiggle when clicked
@ -11,12 +13,8 @@
* bot game grind * bot game grind
* serde serialize privatise
* mobile styles
* change score to enum * change score to enum
* pct based translates for combat animation * pct based translates for combat animation
* acp init
* DO postgres
* make our own toasts / msg pane * make our own toasts / msg pane
* send account_instances on players update * send account_instances on players update

View File

@ -38,9 +38,10 @@
text-transform: uppercase; text-transform: uppercase;
figure { figure {
font-size: 125%; width: 75%;
display: flex; display: flex;
flex-flow: column; flex-flow: column;
margin-bottom: 1em;
button { button {
width: 100%; width: 100%;

View File

@ -31,6 +31,7 @@ export const setShowLog = value => ({ type: 'SET_SHOW_LOG', value });
export const setShowNav = value => ({ type: 'SET_SHOW_NAV', value }); export const setShowNav = value => ({ type: 'SET_SHOW_NAV', value });
export const setSkip = value => ({ type: 'SET_SKIP', value }); export const setSkip = value => ({ type: 'SET_SKIP', value });
export const setShop = value => ({ type: 'SET_SHOP', value }); export const setShop = value => ({ type: 'SET_SHOP', value });
export const setSubscription = value => ({ type: 'SET_SUBSCRIPTION', value });
export const setTeam = value => ({ type: 'SET_TEAM', value: Array.from(value) }); export const setTeam = value => ({ type: 'SET_TEAM', value: Array.from(value) });
export const setTeamPage = value => ({ type: 'SET_TEAM_PAGE', value }); export const setTeamPage = value => ({ type: 'SET_TEAM_PAGE', value });

View File

@ -12,6 +12,7 @@ const addState = connect(
function receiveState(state) { function receiveState(state) {
const { const {
account, account,
subscription,
email, email,
ping, ping,
ws, ws,
@ -55,6 +56,7 @@ const addState = connect(
return { return {
account, account,
subscription,
ping, ping,
email, email,
logout, logout,
@ -81,6 +83,7 @@ class AccountStatus extends Component {
render(args, state) { render(args, state) {
const { const {
account, account,
subscription,
ping, ping,
email, email,
logout, logout,
@ -128,15 +131,31 @@ class AccountStatus extends Component {
return this.setState({ unsubState: false }); return this.setState({ unsubState: false });
} }
const subInfo = () => {
if (!subscription) return false;
return <div>
<h3>Subscription</h3>
<dl>
<dt>Period End</dt>
<dd>{new Date(subscription.current_period_end * 1000).toString()}</dd>
<dt>Status</dt>
<dd>{subscription.cancel_at_period_end ? 'Disabled' : 'Active'}</dd>
</dl>
</div>;
}
return ( return (
<section class='account' onClick={tlClick}> <section class='account' onClick={tlClick}>
<div> <div>
<h1>{account.name}</h1> <h1>{account.name}</h1>
<dl> <div class="list">
<dt>Subscription</dt> <figure>
<dd>{account.subscribed ? 'Active' : 'Unsubscribed'}</dd> <figcaption>spawn new construct</figcaption>
</dl> <button onClick={() => sendConstructSpawn()} type="submit">
{unsubBtn()} ¤50
</button>
</figure>
</div>
<button onClick={() => logout()}>Logout</button> <button onClick={() => logout()}>Logout</button>
<button><a href={`mailto:humans@mnml.gg?subject=Account%20Support:%20${account.name}`}> support</a></button> <button><a href={`mailto:humans@mnml.gg?subject=Account%20Support:%20${account.name}`}> support</a></button>
</div> </div>
@ -190,13 +209,9 @@ class AccountStatus extends Component {
Set Password Set Password
</button> </button>
</div> </div>
<div class="list"> <div>
<figure> {subInfo()}
<figcaption>spawn new construct</figcaption> {unsubBtn()}
<button onClick={() => sendConstructSpawn()} type="submit">
¤50
</button>
</figure>
</div> </div>
</section> </section>
); );

View File

@ -13,11 +13,18 @@ function pingColour(ping) {
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
const { const {
ws,
account, account,
ping, ping,
} = state; } = state;
function sendAccountStates() {
ws.sendEmailState();
ws.sendSubscriptionState();
}
return { return {
sendAccountStates,
account, account,
ping, ping,
}; };
@ -49,10 +56,16 @@ function AccountStatus(args) {
account, account,
ping, ping,
accountPage, accountPage,
sendAccountStates,
} = args; } = args;
if (!account) return null; if (!account) return null;
function accountClick() {
sendAccountStates();
accountPage();
}
return ( return (
<div class="account-status"> <div class="account-status">
<div class="account-info"> <div class="account-info">
@ -61,7 +74,7 @@ function AccountStatus(args) {
<div class="ping-text">{ping}ms</div> <div class="ping-text">{ping}ms</div>
</div> </div>
<h3 class="account-header credits">{`¤${account.balance}`}</h3> <h3 class="account-header credits">{`¤${account.balance}`}</h3>
<button onClick={() => accountPage()}> account</button> <button onClick={accountClick}> account</button>
</div> </div>
); );
} }

View File

@ -19,6 +19,10 @@ function registerEvents(store) {
setNav('play'); setNav('play');
} }
function setSubscription(sub) {
store.dispatch(actions.setSubscription(sub));
}
function setConstructList(constructs) { function setConstructList(constructs) {
store.dispatch(actions.setConstructs(constructs)); store.dispatch(actions.setConstructs(constructs));
} }
@ -227,6 +231,7 @@ function registerEvents(store) {
setPing, setPing,
setShop, setShop,
setTeam, setTeam,
setSubscription,
setWs, setWs,
notify, notify,

View File

@ -44,6 +44,8 @@ module.exports = {
skip: createReducer(false, 'SET_SKIP'), skip: createReducer(false, 'SET_SKIP'),
shop: createReducer(false, 'SET_SHOP'), shop: createReducer(false, 'SET_SHOP'),
subscription: createReducer(null, 'SET_SUBSCRIPTION'),
team: createReducer([], 'SET_TEAM'), team: createReducer([], 'SET_TEAM'),
teamPage: createReducer(0, 'SET_TEAM_PAGE'), teamPage: createReducer(0, 'SET_TEAM_PAGE'),
teamSelect: createReducer([null, null, null], 'SET_TEAM_SELECT'), teamSelect: createReducer([null, null, null], 'SET_TEAM_SELECT'),

View File

@ -143,6 +143,15 @@ function createSocket(events) {
function sendMtxConstructSpawn() { function sendMtxConstructSpawn() {
send(['MtxConstructSpawn', {}]); send(['MtxConstructSpawn', {}]);
} }
function sendEmailState() {
send(['EmailState', {}]);
}
function sendSubscriptionState() {
send(['SubscriptionState', {}]);
}
// ------------- // -------------
// Incoming // Incoming
// ------------- // -------------
@ -170,8 +179,9 @@ function createSocket(events) {
events.setTeam(constructs); events.setTeam(constructs);
} }
function onAccountSubscription() { function onSubscriptionState(sub) {
events.notify('Your account has been set to cancelled. You will no longer be billed. Thanks for playing.'); // events.subscriptionState(`Subscription cancelled. Your subscription will remain active until ${exp}. Thank you for your support.`);
events.setSubscription(sub);
} }
function onConstructSpawn(construct) { function onConstructSpawn(construct) {
@ -208,7 +218,7 @@ function createSocket(events) {
AccountTeam: onAccountTeam, AccountTeam: onAccountTeam,
AccountInstances: onAccountInstances, AccountInstances: onAccountInstances,
AccountShop: onAccountShop, AccountShop: onAccountShop,
AccountSubscription: onAccountSubscription, SubscriptionState: onSubscriptionState,
ConstructSpawn: onConstructSpawn, ConstructSpawn: onConstructSpawn,
GameState: onGameState, GameState: onGameState,
EmailState: onEmailState, EmailState: onEmailState,
@ -326,6 +336,9 @@ function createSocket(events) {
sendItemInfo, sendItemInfo,
sendEmailState,
sendSubscriptionState,
sendMtxApply, sendMtxApply,
sendMtxBuy, sendMtxBuy,
sendMtxConstructSpawn, sendMtxConstructSpawn,

View File

@ -3,7 +3,7 @@ use std::io::Read;
use http::State; use http::State;
use iron::prelude::*; use iron::prelude::*;
use iron::response::HttpResponse;
use iron::status; use iron::status;
use persistent::Read as Readable; use persistent::Read as Readable;
@ -13,10 +13,10 @@ use postgres::transaction::Transaction;
use failure::Error; use failure::Error;
use failure::err_msg; use failure::err_msg;
use stripe::{Client, Event, EventObject, CheckoutSession, SubscriptionStatus}; use stripe::{Client, Event, EventObject, CheckoutSession, SubscriptionStatus, Subscription};
use http::{MnmlHttpError}; use http::{MnmlHttpError};
use pg::{PgPool}; use pg::{Db, PgPool};
use account; use account;
use account::Account; use account::Account;
@ -36,7 +36,7 @@ pub fn subscription_account(tx: &mut Transaction, sub: String) -> Result<Uuid, E
Ok(row.get(0)) Ok(row.get(0))
} }
pub fn subscription_cancel(tx: &mut Transaction, client: &Client, account: &Account) -> Result<Option<String>, Error> { pub fn subscription_cancel(tx: &mut Transaction, client: &Client, account: &Account) -> Result<Option<Subscription>, Error> {
let query = " let query = "
SELECT account, customer, checkout, subscription SELECT account, customer, checkout, subscription
FROM stripe_subscriptions FROM stripe_subscriptions
@ -57,19 +57,47 @@ pub fn subscription_cancel(tx: &mut Transaction, client: &Client, account: &Acco
let mut params = stripe::UpdateSubscription::new(); let mut params = stripe::UpdateSubscription::new();
params.cancel_at_period_end = Some(true); params.cancel_at_period_end = Some(true);
let updated = match stripe::Subscription::update(client, &id, params) { match stripe::Subscription::update(client, &id, params) {
Ok(s) => s, Ok(s) => {
info!("subscription cancelled account={:?} subscription={:?}", account, s);
Ok(Some(s))
},
Err(e) => { Err(e) => {
warn!("{:?}", e); warn!("{:?}", e);
return Err(err_msg("unable to cancel subscription")); Err(err_msg("unable to cancel subscription"))
} }
}; }
info!("subscription cancelled account={:?} subscription={:?}", account, updated);
Ok(Some(updated.status.to_string()))
} }
pub fn account_subscription(db: &Db, client: &Client, account: &Account) -> Result<Option<Subscription>, Error> {
let query = "
SELECT account, customer, checkout, subscription
FROM stripe_subscriptions
WHERE account = $1;
";
let result = db
.query(query, &[&account.id])?;
let row = match result.iter().next() {
Some(r) => r,
None => return Ok(None),
};
let _customer: String = row.get(1);
let _checkout: String = row.get(2);
let subscription: String = row.get(3);
let id = subscription.parse()?;
match stripe::Subscription::retrieve(client, &id, &[]) {
Ok(s) => Ok(Some(s)),
Err(e) => {
warn!("{:?}", e);
Err(err_msg("unable to retrieve subscription"))
}
}
}
// we use i64 because it is converted to BIGINT for pg // we use i64 because it is converted to BIGINT for pg
// and we can losslessly pull it into u32 which is big // and we can losslessly pull it into u32 which is big

View File

@ -11,7 +11,7 @@ use failure::err_msg;
use serde_cbor::{from_slice, to_vec}; use serde_cbor::{from_slice, to_vec};
use cookie::Cookie; use cookie::Cookie;
use stripe::Client as StripeClient; use stripe::{Client as StripeClient, Subscription};
use crossbeam_channel::{unbounded, Sender as CbSender}; use crossbeam_channel::{unbounded, Sender as CbSender};
use ws::{listen, CloseCode, Message, Handler, Request, Response}; use ws::{listen, CloseCode, Message, Handler, Request, Response};
@ -40,14 +40,15 @@ pub enum RpcMessage {
AccountTeam(Vec<Construct>), AccountTeam(Vec<Construct>),
AccountInstances(Vec<Instance>), AccountInstances(Vec<Instance>),
AccountShop(mtx::Shop), AccountShop(mtx::Shop),
AccountSubscription(Option<String>),
ConstructSpawn(Construct), ConstructSpawn(Construct),
EmailState(Email),
GameState(Game), GameState(Game),
ItemInfo(ItemInfoCtr), ItemInfo(ItemInfoCtr),
InstanceState(Instance), InstanceState(Instance),
EmailState(Option<Email>),
SubscriptionState(Option<Subscription>),
Pong(()), Pong(()),
@ -81,6 +82,8 @@ enum RpcRequest {
AccountSetTeam { ids: Vec<Uuid> }, AccountSetTeam { ids: Vec<Uuid> },
SubscriptionCancel {}, SubscriptionCancel {},
SubscriptionState {},
EmailState {},
InstanceQueue {}, InstanceQueue {},
InstancePractice {}, InstancePractice {},
@ -143,13 +146,19 @@ impl Connection {
let response = match v { let response = match v {
RpcRequest::AccountState {} => RpcRequest::AccountState {} =>
return Ok(RpcMessage::AccountState(account.clone())), Ok(RpcMessage::AccountState(account.clone())),
RpcRequest::AccountConstructs {} => RpcRequest::AccountConstructs {} =>
Ok(RpcMessage::AccountConstructs(account::constructs(&mut tx, &account)?)), Ok(RpcMessage::AccountConstructs(account::constructs(&mut tx, &account)?)),
RpcRequest::AccountSetTeam { ids } => RpcRequest::AccountSetTeam { ids } =>
Ok(RpcMessage::AccountTeam(account::set_team(&mut tx, &account, ids)?)), Ok(RpcMessage::AccountTeam(account::set_team(&mut tx, &account, ids)?)),
RpcRequest::EmailState {} =>
Ok(RpcMessage::EmailState(mail::select_account(&db, account.id)?)),
RpcRequest::SubscriptionState {} =>
Ok(RpcMessage::SubscriptionState(payments::account_subscription(&db, &self.stripe, &account)?)),
// RpcRequest::AccountShop {} => // RpcRequest::AccountShop {} =>
// Ok(RpcMessage::AccountShop(mtx::account_shop(&mut tx, &account)?)), // Ok(RpcMessage::AccountShop(mtx::account_shop(&mut tx, &account)?)),
@ -201,7 +210,7 @@ impl Connection {
Ok(RpcMessage::AccountShop(mtx::buy(&mut tx, account, mtx)?)), Ok(RpcMessage::AccountShop(mtx::buy(&mut tx, account, mtx)?)),
RpcRequest::SubscriptionCancel { } => RpcRequest::SubscriptionCancel { } =>
Ok(RpcMessage::AccountSubscription(payments::subscription_cancel(&mut tx, &self.stripe, account)?)), Ok(RpcMessage::SubscriptionState(payments::subscription_cancel(&mut tx, &self.stripe, account)?)),
_ => Err(format_err!("unknown request request={:?}", request)), _ => Err(format_err!("unknown request request={:?}", request)),
}; };
@ -239,15 +248,6 @@ impl Handler for Connection {
let db = self.pool.get().unwrap(); let db = self.pool.get().unwrap();
let mut tx = db.transaction().unwrap(); let mut tx = db.transaction().unwrap();
// email state
match mail::select_account(&db, a.id).unwrap() {
Some(e) => {
self.ws.send(RpcMessage::EmailState(e.clone())).unwrap();
self.events.send(Event::Subscribe(self.id, e.id)).unwrap();
},
None => (),
};
// send account constructs // send account constructs
let account_constructs = account::constructs(&mut tx, a).unwrap(); let account_constructs = account::constructs(&mut tx, a).unwrap();
self.ws.send(RpcMessage::AccountConstructs(account_constructs)).unwrap(); self.ws.send(RpcMessage::AccountConstructs(account_constructs)).unwrap();