payment: note that Stripe has instituted an additional 1% fee for non-US cards
[hcoop/portal.git] / remind / remind.sml
1 structure Remind :> REMIND =
2 struct
3
4 open Config
5
6 structure C = PgClient
7
8 fun printReal n =
9 if n < 0.0 then
10 "-" ^ Real.fmt (StringCvt.FIX (SOME 2)) (~n)
11 else
12 Real.fmt (StringCvt.FIX (SOME 2)) n
13
14 val basePerMonth = 7.0
15 val deposit = basePerMonth * 3.0
16
17 fun main _ =
18 let
19 val db = C.conn dbstring
20
21 val totalShares = case C.oneRow db "SELECT SUM(shares) FROM WebUserPaying" of
22 [total] => C.intFromSql total
23 | _ => raise Fail "Bad SUM(shares) row"
24
25 fun doOne [user, members, shares, amount] =
26 let
27 val user = C.stringFromSql user
28 val members = C.intFromSql members
29 val shares = C.intFromSql shares
30 val amount = C.realFromSql amount
31
32 val perMonth = basePerMonth * real shares
33 val headsUp = deposit + perMonth * 2.0
34 in
35 if amount >= headsUp then
36 ()
37 else
38 let
39 val m = Mail.mopen ()
40 fun write msg = Mail.mwrite (m, msg)
41 in
42 if amount < deposit then
43 write "Subject: Your NEGATIVE HCoop balance\n"
44 else
45 write "Subject: Your low HCoop balance\n";
46 write "From: HCoop Payment Reminder Robot <payment";
47 write emailSuffix;
48 write ">\n";
49 write "To: ";
50 write user;
51 write emailSuffix;
52 write "\nCc: payment@hcoop.net";
53 write "\n\n";
54
55 if amount < deposit then
56 (write "Your HCoop balance is negative. This means that you've paid less than you've\n";
57 write "been charged to date, excluding your required deposit. If your account hasn't\n";
58 write "been frozen yet, expect that to happen very soon. Our bylaws allow our board\n";
59 write "of directors to vote you out of the co-op, without any obligation to contact\n";
60 write "you first, when your balance stays negative for three months. Please make a\n";
61 write "payment as soon as possible, so that we don't need to do this!\n\n")
62 else
63 (write "With our current dues projections, you have less than two months left until\n";
64 write "your HCoop balance becomes negative, based on your sliding scale pledge amount.\n";
65 write "Please make a payment as soon as you can. We will freeze your account if your\n";
66 write "balance does become negative, and the board of directors will probably vote you\n";
67 write "out of the cooperative shortly thereafter if you don't make a payment.\n\n");
68
69 write "Your balance: US$";
70 write (printReal (amount - deposit));
71 write "\nTotal number of members linked to your balance: ";
72 write (Int.toString members);
73 write "\nTotal pledge amount: ";
74 write (Int.toString shares);
75 write "\nDeposit: US$";
76 write (printReal deposit);
77 write "\nMinimum amount to pay to not see this message again for two months: US$";
78 write (printReal (headsUp - amount));
79
80 write "\n\nThe deposit requirement was calculated as three months of dues at $7/mo..\n\n";
81
82 write "To make a payment, visit:\n";
83 write " https://members.hcoop.net/\n";
84 write "and use the PayPal or Stripe link.\n";
85
86 write "\nIf for whatever reason you don't plan to pay the amount suggested in this e-mail,\n";
87 write "_please_ don't stay silent. Reply to this message explaining your circumstances.\n";
88
89 ignore (Mail.mclose m)
90 end
91 end
92 | doOne _ = raise Fail "Bad SQL row"
93 in
94 C.app db doOne ("SELECT Balance.name, COUNT(*), SUM(WebUserPaying.shares) AS shrs, Balance.amount FROM WebUserPaying JOIN Balance ON WebUserPaying.bal = Balance.id GROUP BY Balance.name, Balance.amount HAVING amount < " ^ C.realToSql deposit ^ " + " ^ C.realToSql (basePerMonth * 2.0) ^ " * SUM(WebUserPaying.shares)");
95 C.close db;
96 OS.Process.success
97 end handle C.Sql s => (print ("SQL failure: " ^ s ^ "\n");
98 OS.Process.failure)
99
100 end