| row => Init.rowError ("listUsers", row)
fun listUsers trn =
- C.map (getDb ()) mkUserRow' ($`SELECT trn, id, name, rname, bal, joined, app
+ C.map (getDb ()) mkUserRow' ($`SELECT trn, id, name, rname, bal, joined, app, shares, paypal, checkout
FROM WebUser LEFT OUTER JOIN Charge ON usr = id AND trn = ^(C.intToSql trn)
ORDER BY name`)
val (umap, amount) = walkNvs (nvs, SM.empty, #amount tran)
- val payers = Group.groupMembers paying
- val even = amount / real (length payers)
+ val db = getDb ()
+
+ val shares =
+ case C.oneRow db ($`SELECT SUM(shares)
+ FROM WebUser JOIN Membership ON usr = WebUser.id AND grp = ^(C.intToSql paying)`) of
+ [n] => C.intFromSql n
+ | row => Init.rowError ("Bad addHostingCharges share count result", row)
- fun doUser (usr : Init.user, umap) =
+ val even = amount / real shares
+
+ fun doUser ([uid, uname, shares], umap) =
let
+ val uid = C.intFromSql uid
+ val uname = C.stringFromSql uname
+ val shares = C.intFromSql shares
+
val (charge, umap) =
- case SM.find (umap, #name usr) of
- NONE => (even, umap)
- | SOME extra => (even - extra, #1 (SM.remove (umap, #name usr)))
+ case SM.find (umap, uname) of
+ NONE => (even * real shares, umap)
+ | SOME extra => (even * real shares - extra, #1 (SM.remove (umap, uname)))
in
- addCharge {trn = trn, usr = #id usr, amount = charge};
+ addCharge {trn = trn, usr = uid, amount = charge};
umap
end
- val _ = if SM.numItems (foldl doUser umap payers) = 0 then
+ val _ = if SM.numItems (C.fold db doUser umap
+ ($`SELECT id, name, shares
+ FROM WebUser JOIN Membership ON usr = WebUser.id AND grp = ^(C.intToSql paying)`))
+ = 0 then
applyCharges trn
else
raise Fail "Usage description contains an unknown username"
[share] => C.realFromSql share
| row => Init.rowError ("Bad costBase result", row)
+val monthlyCost = 900.0
+val graceMonths = 1
+
+val baseDues = 7.0
+
+fun delinquentPledgers () =
+ let
+ fun makeRow [id, name, shares, amount] = {id = C.intFromSql id, name = C.stringFromSql name,
+ shares = C.intFromSql shares, balance = C.realFromSql amount}
+ | makeRow row = Init.rowError ("Bad delinquentPledgers", row)
+ in
+ C.map (getDb ()) makeRow ($`SELECT WebUserPaying.id, WebUserPaying.name, shares, amount
+ FROM WebUserPaying JOIN Balance ON Balance.id = bal
+ WHERE amount < shares * ^(C.realToSql baseDues) * ^(C.intToSql graceMonths)
+ AND shares > 1
+ ORDER BY name`)
+ end
+
+fun resetPledges ids =
+ ignore (C.dml (getDb ()) ($`UPDATE WebUser SET shares = 1 WHERE id IN (^(String.concatWith ", " (List.map C.intToSql ids)))`))
+
+fun freezeworthyPledgers () =
+ let
+ fun makeRow [id, name, amount, j] = {id = C.intFromSql id, name = C.stringFromSql name,
+ balance = C.realFromSql amount, joinedThisMonth = C.boolFromSql j}
+ | makeRow row = Init.rowError ("Bad freezeworthyPledgers", row)
+ in
+ C.map (getDb ()) makeRow ($`SELECT WebUserPaying.id, WebUserPaying.name, amount, CURRENT_TIMESTAMP - joined < INTERVAL '1 month'
+ FROM WebUserPaying JOIN Balance ON Balance.id = bal
+ WHERE amount >= ^(C.realToSql baseDues) * ^(C.intToSql graceMonths)
+ AND amount < ^(C.realToSql baseDues) * ^(C.intToSql (graceMonths + 1))
+ ORDER BY name`)
+ end
+
+fun bootworthyPledgers () =
+ let
+ fun makeRow [id, name, amount, j] = {id = C.intFromSql id, name = C.stringFromSql name,
+ balance = C.realFromSql amount, joinedThisMonth = C.boolFromSql j}
+ | makeRow row = Init.rowError ("Bad bootworthyPledgers", row)
+ in
+ C.map (getDb ()) makeRow ($`SELECT WebUserPaying.id, WebUserPaying.name, amount, CURRENT_TIMESTAMP - joined < INTERVAL '1 month'
+ FROM WebUserPaying JOIN Balance ON Balance.id = bal
+ WHERE amount < ^(C.realToSql baseDues) * ^(C.intToSql graceMonths)
+ ORDER BY name`)
+ end
+
+fun billDues {descr, base, date} =
+ let
+ val db = getDb ()
+ val paying =
+ case Group.groupNameToId "paying" of
+ NONE => raise Fail "No 'paying' group"
+ | SOME id => id
+
+ val shares =
+ case C.oneRow db ($`SELECT SUM(shares)
+ FROM WebUser JOIN Membership ON usr = WebUser.id AND grp = ^(C.intToSql paying)`) of
+ [n] => C.intFromSql n
+ | row => Init.rowError ("Bad addHostingCharges share count result", row)
+
+ val total = real shares * base
+
+ val give = addTransaction (descr, ~total, date)
+
+ fun doUser [uid, shares] =
+ let
+ val uid = C.intFromSql uid
+ val shares = C.intFromSql shares
+ in
+ addCharge {trn = give, usr = uid, amount = ~(base * real shares)}
+ end
+ | doUser r = Init.rowError ("Bad billDues/doUser row", r)
+
+ val receive = addTransaction (descr, total, date)
+
+ val hcoop = valOf (Init.userNameToId "hcoop")
+ in
+ C.app db doUser ($`SELECT id, shares
+ FROM WebUser JOIN Membership ON usr = WebUser.id AND grp = ^(C.intToSql paying)`);
+ applyCharges give;
+
+ addCharge {trn = receive, usr = hcoop, amount = total};
+ applyCharges receive
+ end
+
+(* Stripe *)
+
+type stripePayment = {charge_id : string, webuser_id : int, card_name : string, paid_on : string, gross_cents : int, fee_cents : int, net : real}
+
+fun mkStripeRow [charge_id, webuser_id, name, paid_on, gross, fee] =
+ {charge_id = C.stringFromSql charge_id, webuser_id = C.intFromSql webuser_id,
+ card_name = C.stringFromSql name, paid_on = C.stringFromSql paid_on,
+ gross_cents = C.intFromSql gross, fee_cents = C.intFromSql fee, net = real (C.intFromSql gross - C.intFromSql fee) / 100.0 }
+ | mkStripeRow row = Init.rowError ("stripe_payment", row)
+
+fun listUserPendingStripePayments uid =
+ C.map (getDb ()) mkStripeRow ($`SELECT charge_id, webuser_id, card_name, paid_on, gross, fee FROM stripe_payment
+ WHERE webuser_id = ^(C.intToSql uid)
+ AND charge_id NOT IN (SELECT stripe_charge_id FROM stripe_handled)
+ ORDER BY paid_on DESC`)
+
+fun listAllPendingStripePayments _ =
+ C.map (getDb ()) mkStripeRow ($`SELECT charge_id, webuser_id, card_name, paid_on, gross, fee FROM stripe_payment
+ WHERE charge_id NOT IN (SELECT stripe_charge_id FROM stripe_handled)
+ ORDER BY paid_on DESC`)
+
+fun lookupStripePayment id =
+ let
+ val c = getDb ()
+ in
+ (case C.oneOrNoRows c ($`SELECT charge_id, webuser_id, card_name, paid_on, gross, fee FROM stripe_payment WHERE charge_id = ^(C.stringToSql id)`) of
+ NONE => raise Fail "Stripe Payment Not Found"
+ | SOME r => mkStripeRow r)
+ end
+
+(* Not Used *)
+val stripeNotify : stripePayment -> bool =
+fn pmt =>
+ let
+ val user = Init.lookupUser (#webuser_id pmt)
+ val mail = Mail.mopen ()
+ in
+ Mail.mwrite (mail, "From: Hcoop Support System <support");
+ Mail.mwrite (mail, emailSuffix);
+ Mail.mwrite (mail, ">\nTo: payment");
+ Mail.mwrite (mail, emailSuffix);
+ Mail.mwrite (mail, "\n");
+ Mail.mwrite (mail, "Subject: Stripe Payment Received");
+ Mail.mwrite (mail, "\n\n");
+
+ Mail.mwrite (mail, "A member has paid us via Stripe. Visit the money page to process the payment.");
+ Mail.mwrite (mail, "Member: ");
+ Mail.mwrite (mail, #name user);
+ Mail.mwrite (mail, "\n");
+ Mail.mwrite (mail, "Amount (after fees): ");
+ Mail.mwrite (mail, Real.toString (#net pmt));
+ Mail.mwrite (mail, "\n\n");
+
+ OS.Process.isSuccess (Mail.mclose mail)
+ end
+
+val applyStripePayment : stripePayment -> int =
+ fn pmt =>
+ let
+ val _ = Group.requireGroupName "money";
+ val amount = #net pmt;
+ val txid = addTransaction ("Stripe", amount, #paid_on pmt)
+ in
+ addCharge {trn = txid, usr = #webuser_id pmt, amount = amount};
+ applyCharges txid;
+ C.dml (getDb ()) ($`INSERT INTO stripe_processed (stripe_charge_id, transaction_id)
+ VALUES (^(C.stringToSql (#charge_id pmt)), ^(C.intToSql txid))`);
+ txid
+ end
end
+