--- /dev/null
+BEGIN;
+
+CREATE TABLE stripe_payment
+(
+ charge_id text not null primary key,
+ webuser_id integer not null references WebUser (id),
+ card_name text not null,
+ paid_on date not null,
+ gross integer not null,
+ fee integer not null
+);
+
+CREATE TABLE stripe_join_payment
+(
+ charge_id text not null primary key,
+ app_id integer not null references MemberApp (id) unique,
+ card_name text not null,
+ authorized_on date not null,
+ gross integer not null
+-- no fee data, because an uncaptured payment does not have have a balance_transaction
+);
+
+CREATE TABLE stripe_processed
+(
+ stripe_charge_id text not null primary key,
+ transaction_id integer not null references transaction (id),
+
+ foreign key (stripe_charge_id) references stripe_payment (charge_id)
+);
+
+COMMIT;
%><h3>Payment transaction added.</h3>
+<% elseif $"cmd" = "stripeApply" then
+ val stripePmt = Money.lookupStripePayment ($"stripeId");
+ val txid = Money.applyStripePayment stripePmt;
+%><h3>Stripe Payment Processed (Transaction <% txid %>)</h3>
+
<% elseif $"modPay" <> "" then
Group.requireGroupName "money";
showNormal := false;
<input name="rname"> <input type="submit" value="Look up">
</form>
+<h3>Apply Stripe Payments</h3>
+
+<table>
+<tr><td><strong>Date</strong></td><td><strong>Member</strong></td>
+ <td><strong>Name on Card</strong></td>
+ <td><strong>Amount</strong> (After Fees)</td><td><td></td>
+</tr>
+<% foreach stripePmt in Money.listAllPendingStripePayments () do %>
+
+<tr>
+ <td><% #name (Init.lookupUser (#webuser_id stripePmt)) %></td>
+ <td><% #paid_on stripePmt %></td>
+ <td><% #card_name stripePmt %>
+ <td>$<% #net stripePmt %></td>
+ <td><form method="post">
+ <input type="hidden" name="cmd" value="stripeApply" />
+ <input type="hidden" name="stripeId" value="<% #charge_id stripePmt %>" />
+ <input type="submit" value="Apply to Balance" /> <!-- also, refund? -->
+ </form>
+ </td>
+</tr>
+<% end %>
+</table>
+
<h3>Most recent transactions</h3>
+
+
<table>
<tr> <td><b>Date</b></td> <td><b>Description</b></td> <td><b>Amount</b></td> <td><b>Participants</b></td> <td><b>Replace</b></td> <td><b>Delete</b></td> </tr>
<% foreach trn in Money.listTransactionsLimit 20 do %>
val listUsers : int -> (bool * Init.user) list
(* List users and indicate whether they participated in a transaction *)
+ type stripePayment = {charge_id : string, webuser_id : int, card_name : string, paid_on : string, gross_cents : int, fee_cents : int, net : real}
+
+ val listUserPendingStripePayments : int -> stripePayment list
+ val listAllPendingStripePayments : unit -> stripePayment list
+ val lookupStripePayment : string -> stripePayment
+ val applyStripePayment : stripePayment -> int
+
val lookupHostingUsage : int -> string option
type charge = {trn : int, usr : int, amount : real}
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_processed)
+ 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_processed)
+ 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
+
-<% val you = Init.getUser () %>
-
-<h3><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=payment@hcoop.net&item_name=Member+payment+for+<% Init.getUserName () %>">Add to your balance with PayPal</a></h3>
-
-<% switch #paypal you of
- NONE => %><p>You haven't set a PayPal e-mail address. If you are going to send a payment by PayPal, please <a href="pref">set a PayPal e-mail address on the Preferences page</a> first to ensure that you are credited promptly and accurately.</p><%
-end %>
-
-<p>Remember that we credit member balances for PayPal payments <b>after subtracting PayPal's service fees</b>. This means that, to increase your balance by a particular amount, you must make a <b>larger</b> payment than just that amount. You should consult <a href="https://www.paypal.com/us/cgi-bin/webscr?cmd=_display-fees-outside">the PayPal fees page</a> to figure out how much extra to send. We have a business account, which means, for example, a 2.9% plus 30 cent fee for payments from the USA. This means that you can calculate the amount <i>x</i> to send from the amount <i>y</i> you want us to receive with this formula: <i>x</i> = (<i>y</i> + .3) / (1 - .029). The fees may be different for other countries.</p>
\ No newline at end of file
+<% val you = Init.getUser () %>
+
+<% if $"cmd" = "stripeSuccess" then %>
+<div class="notice">
+
+<h3>Stripe Payment Succeeded</h3>
+
+<p>Your payment via Stripe must still be applied to your balance
+manually at present, and will be applied by the treasurer within a few
+days.</p>
+
+</div>
+<% end %>
+
+<h3>Add To Your Balance</h3>
+
+<script src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
+<script src="https://checkout.stripe.com/checkout.js"></script>
+
+<p>Remember that we credit member balances for payments <b>after subtracting service fees</b>. This means that, to increase your balance by a particular amount, you must make a <b>larger</b> payment than just that amount.</p>
+
+<h4>Add to your balance with <a href="https://paypal.com">PayPal</a></h4>
+
+<p>You should consult <a href="https://www.paypal.com/us/cgi-bin/webscr?cmd=_display-fees-outside">the PayPal fees page</a> to figure out how much extra to send. We have a business account, which means, for example, a 2.9% plus 30 cent fee for payments from the USA. This means that you can calculate the amount <em>x</em> to send from the amount <em>y</em> you want us to receive with this formula: <em>x</em> = (<em>y</em> + .3) / (1 - .029). The fees may be different for other countries.</p>
+
+<form method="GET" action="https://www.paypal.com/cgi-bin/webscr">
+ <input type="hidden" name="cmd" value="_xclick" />
+ <input type="hidden" name="business" value="payment@hcoop.net" />
+ <input type="hidden" name="item_name" value="Member payment for <% Init.getUserName () %>" />
+
+ <select id="paypalDuesEasy">
+ <option value="" selected="selected">---</option>
+ <% foreach (amt) in [10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100] do %>
+ <option value="<% amt %>.00">$<% amt %>.00</option>
+ <% end %>
+ </select>
+
+ <script>
+ $("#paypalDuesEasy").change (function () {
+ $("#paypalDues").val ($(this).val ());
+ });
+ </script>
+
+ <label>$<input id="paypalDues" type="text" name="amount" pattern="^\\d+\\.\\d\\d$" /></label>
+ <input type="submit" value="Add to Your Balance With Paypal" />
+</form>
+
+<% switch #paypal you of
+ NONE => %><p>You haven't set a PayPal e-mail address. If you are going to send a payment by PayPal, please <a href="pref">set a PayPal e-mail address on the Preferences page</a> first to ensure that you are credited promptly and accurately.</p><%
+end %>
+
+<h4>Add to your balance with <a href="https://stripe.com/">Stripe</a></h4>
+
+<p>Stripe fees are $0.30 + 2.9% for each transaction regardless of country.</p>
+
+<form id="stripeForm" action="/stripe/stripe-payment.cgi" method="POST">
+ <select id="stripeDuesEasy">
+ <option value="" selected="selected">---</option>
+ <% foreach (amt) in [10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100] do %>
+ <option value="<% amt %>.00">$<% amt %>.00</option>
+ <% end %>
+ </select>
+
+ <script>
+ $("#stripeDuesEasy").change (function () {
+ $("#stripeDues").val ($(this).val ());
+ });
+ </script>
+ <label>$<input type="text" name="stripeDues" id="stripeDues" pattern="^\\d+\\.\\d\\d$"/></label>
+ <input type="hidden" name="webuser_id" value="<% #id you %>" />
+ <input type="hidden" name="webuser_name" value="<% #name you %>" />
+
+ <button id="stripePay">Add To Your Balance With Stripe</button>
+
+ <script>
+ $("#stripePay").click (function(e) {
+ if ($("#stripeDues")[0].validity.valid) {
+ var token = function(res){
+ var $input = $('<input type=hidden name=stripeToken />').val(res.id);
+ $("#stripeDues").val($("#stripeDues").val() * 100);
+ $('#stripeForm').append($input).submit();
+ };
+
+ // Open Checkout with further options
+ StripeCheckout.open({
+ key: 'pk_test_sJkMs1I4fVK4JQu9QkFDjOMs',
+ image: '/globe.gif',
+ name: 'Hcoop',
+ description: 'Dues',
+ amount: ($("#stripeDues").val() * 100),
+ currency: 'usd',
+ address: true,
+ panelLabel: 'Pay {{amount}}',
+ token: token
+ });
+ }
+ return false;
+ });
+ </script>
+</form>
+
<b>Balance: $<% showBal %></b><br>
<b>Deposit: $<% deposit %></b> (3 months of dues at the minimal <a href="pledge">pledge level</a>)
+<h3>Pending Stripe Payments</h3>
+
+<table>
+<tr><td><strong>Date</strong></td><td><strong>Net Amount</strong> (After Fees)</td></tr>
+<% foreach stripePmt in Money.listUserPendingStripePayments
+ (Init.getUserId () ) do %>
+<tr>
+ <td><% #paid_on stripePmt %></td>
+ <td>$<% #net stripePmt %></td>
+</tr>
+<% end %>
+</table>
+
+
<!--b>Balance: $<% #amount bal %></b-->
<% val polls = Poll.listCurrentPolls ();
--- /dev/null
+#!/usr/bin/env python
+# -*- python -*-
+
+import sys
+sys.path.insert(0, '/afs/hcoop.net/user/h/hc/hcoop/portal3/stripe/stripe-pkg/lib/python2.6/site-packages/')
+
+import stripe, cgi, psycopg2, cgitb, datetime, smtplib
+from email.mime.text import MIMEText
+
+cgitb.enable()
+
+def notify_payment (charge, member):
+ msg_text = """
+A member has paid us via Stripe. Please visit the portal money
+page at your earliest convenience to process the payment.
+
+ Member : {0}
+ Amount (cents) : {1}
+ Stripe Charge ID: {2}
+""".format (member, charge.amount, charge.id)
+
+ msg = MIMEText(msg_text)
+ msg['Subject'] = 'Stripe payment received from {0}'.format(member)
+ msg['From'] = 'payment@hcoop.net'
+ msg['To'] = 'payment@hcoop.net'
+
+ s = smtplib.SMTP ('mail.hcoop.net')
+ s.sendmail ('payment@hcoop.net', ['payment@hcoop.net'], msg.as_string ())
+ s.quit ()
+
+def stripe_key ():
+ keyfile = open ("/afs/hcoop.net/user/h/hc/hcoop/.portal-private/stripe", "r")
+ keystring = keyfile.read ()
+ keyfile.close ()
+ return keystring
+
+# Set your secret key: remember to change this to your live secret key in production
+# See your keys here https://manage.stripe.com/account
+
+stripe.api_key = stripe_key ()
+
+# Get the credit card details submitted by the form
+
+request_params = cgi.FieldStorage()
+
+token = request_params.getvalue ('stripeToken')
+webuser_id = request_params.getvalue('webuser_id')
+member_name = request_params.getvalue('webuser_name')
+amount = request_params.getvalue('stripeDues')
+
+# Create the charge on Stripe's servers - this will charge the user's card
+
+try:
+ charge = stripe.Charge.create( amount=amount,
+ currency="usd",
+ card=token,
+ description='Payment for member {0}'.format (member_name))
+except stripe.error.CardError, e: # The card has been declined
+ print 'Status: 200 OK'
+ print
+ print '<html>'
+ print '<head><title>Transaction Failed</title></head>'
+ print '<body>'
+ print '<h1>Failed</h1><p>Reason: '
+ print e.json_body['error']['message']
+ print '</p>'
+ print '</body>'
+ print '</html>'
+else:
+ try:
+ balance = stripe.BalanceTransaction.retrieve (charge.balance_transaction);
+ conn = psycopg2.connect ('dbname=hcoop_portal3test user=hcoop host=postgres port=5433')
+ cur = conn.cursor ()
+ cur.execute ('insert into stripe_payment (charge_id, card_name, webuser_id, paid_on, gross, fee) values (%s, %s, %s, %s, %s, %s)',
+ (charge.id, charge.card.name, webuser_id, datetime.date.today (), charge.amount, balance.fee))
+ except:
+ print 'Status: 200 OK'
+ print 'Content-Type: text/html'
+ print ''
+ print '<h1>Something went wrong after accepting payment!</h1>'
+ charge.refund ()
+ conn.rollback ()
+ print '<p>The charge should be refunded. Please contact payment@hcoop.net if it was not!</p>'
+ raise
+ else:
+ conn.commit ()
+ cur.close ()
+ conn.close ()
+ notify_payment (charge, member_name)
+ print 'Status: 303 See Other'
+ print 'Location: /portal/portal?cmd=stripeSuccess'
+ print ''
+ print '<a href="/portal/portal?cmd=stripeSuccess">Go back to the portal</a>'