Commit | Line | Data |
---|---|---|
f8b39e09 CE |
1 | #!/usr/bin/env python |
2 | # -*- python -*- | |
3 | ||
4 | import sys | |
5 | sys.path.insert(0, '/afs/hcoop.net/user/h/hc/hcoop/portal3/stripe/stripe-pkg/lib/python2.6/site-packages/') | |
6 | ||
7 | import stripe, cgi, psycopg2, cgitb, datetime, smtplib | |
8 | from email.mime.text import MIMEText | |
5e6afd1a | 9 | from contextlib import contextmanager |
f8b39e09 CE |
10 | |
11 | cgitb.enable() | |
12 | ||
13 | def notify_payment (charge, member): | |
14 | msg_text = """ | |
15 | A member has paid us via Stripe. Please visit the portal money | |
16 | page at your earliest convenience to process the payment. | |
17 | ||
18 | Member : {0} | |
19 | Amount (cents) : {1} | |
20 | Stripe Charge ID: {2} | |
21 | """.format (member, charge.amount, charge.id) | |
22 | ||
23 | msg = MIMEText(msg_text) | |
24 | msg['Subject'] = 'Stripe payment received from {0}'.format(member) | |
25 | msg['From'] = 'payment@hcoop.net' | |
26 | msg['To'] = 'payment@hcoop.net' | |
27 | ||
28 | s = smtplib.SMTP ('mail.hcoop.net') | |
29 | s.sendmail ('payment@hcoop.net', ['payment@hcoop.net'], msg.as_string ()) | |
30 | s.quit () | |
31 | ||
bd5d2441 CE |
32 | def notify_payment_rejected (charge, reason): |
33 | # TODO: notify member... | |
34 | msg_text = """We have rejected a payment from a member. | |
35 | Amount: {0} | |
36 | Stripe Charge ID: {1} | |
37 | Reason: {2} | |
38 | """.format (charge.amount, charge.id, reason) | |
39 | ||
40 | msg = MIMEText(msg_text) | |
41 | msg['Subject'] = 'Stripe payment rejected' | |
42 | msg['From'] = 'payment@hcoop.net' | |
43 | msg['To'] = 'payment@hcoop.net' | |
44 | ||
45 | s = smtplib.SMTP ('mail.hcoop.net') | |
46 | s.sendmail ('payment@hcoop.net', ['payment@hcoop.net'], msg.as_string ()) | |
47 | s.quit () | |
48 | ||
f8b39e09 CE |
49 | def stripe_key (): |
50 | keyfile = open ("/afs/hcoop.net/user/h/hc/hcoop/.portal-private/stripe", "r") | |
51 | keystring = keyfile.read () | |
52 | keyfile.close () | |
53 | return keystring | |
54 | ||
5e6afd1a CE |
55 | |
56 | @contextmanager | |
57 | def stripe_error_handling (): | |
58 | try: | |
59 | yield | |
60 | except stripe.error.CardError, e: # The card has been declined | |
61 | print 'Status: 200 OK' | |
62 | ||
63 | print '<html>' | |
64 | print '<head><title>Transaction Failed</title></head>' | |
65 | print '<body>' | |
66 | print '<h1>Failed</h1><p>Reason: ' | |
67 | print e.json_body['error']['message'] | |
68 | print '</p>' | |
69 | print '</body>' | |
70 | print '</html>' | |
71 | raise | |
bd5d2441 CE |
72 | except stripe.error.StripeError, e: # General stripe failure |
73 | print 'Status: 200 OK' | |
74 | ||
75 | print '<html>' | |
76 | print '<head><title>Stripe Error</title></head>' | |
77 | print '<body>' | |
78 | print '<h1>Failed</h1><p>Reason: ' | |
79 | print e.json_body['error']['message'] | |
80 | print '</p>' | |
81 | print '</body>' | |
82 | print '</html>' | |
83 | raise | |
5e6afd1a CE |
84 | |
85 | @contextmanager | |
86 | def stripe_refund_on_error (charge): | |
87 | try: | |
88 | yield | |
89 | except: | |
90 | print 'Status: 200 OK' | |
91 | print 'Content-Type: text/html' | |
92 | print '' | |
93 | print '<h1>Something went wrong after accepting payment!</h1>' | |
94 | charge.refund () | |
95 | print '<p>The charge should be refunded. Please contact payment@hcoop.net if it was not!</p>' | |
96 | raise | |
97 | ||
bd5d2441 CE |
98 | def stripe_success (redirect_to): |
99 | print 'Status: 303 See Other' | |
100 | print 'Location: {0}'.format(redirect_to); | |
101 | print '' | |
102 | print '<a href="{0}">Go back to the portal</a>'.format(redirect_to) | |
103 | ||
f8b39e09 CE |
104 | # Set your secret key: remember to change this to your live secret key in production |
105 | # See your keys here https://manage.stripe.com/account | |
106 | ||
107 | stripe.api_key = stripe_key () | |
bd5d2441 | 108 | connstring = 'dbname=hcoop_portal3test user=hcoop host=postgres port=5433' |
f8b39e09 CE |
109 | |
110 | # Get the credit card details submitted by the form | |
111 | ||
112 | request_params = cgi.FieldStorage() | |
bd5d2441 | 113 | request_command = request_params.getvalue ('cmd', 'none'); |
f8b39e09 | 114 | |
bd5d2441 | 115 | assert request_command != 'none', 'No command given.' |
f8b39e09 CE |
116 | |
117 | # Create the charge on Stripe's servers - this will charge the user's card | |
118 | ||
bd5d2441 CE |
119 | if request_command == 'member_payment': |
120 | token = request_params.getvalue ('stripeToken') | |
121 | webuser_id = request_params.getvalue('webuser_id') | |
122 | member_name = request_params.getvalue('webuser_name') | |
123 | amount = request_params.getvalue('stripeDues') | |
124 | ||
125 | with stripe_error_handling (): | |
126 | charge = stripe.Charge.create( amount=amount, | |
127 | currency="usd", | |
128 | card=token, | |
129 | description='Payment for member {0}'.format (member_name)) | |
130 | ||
131 | with stripe_refund_on_error (charge): | |
132 | # assert charge.card.address_line1_check == 'pass', 'Address verification failed or unknown.' | |
133 | assert charge.card.cvc_check == 'pass', 'CVC verification failed or unknown.' | |
134 | # assert charge.card.address_zip_check == 'pass', 'Zipcode verification failed or unknown.' | |
135 | ||
136 | balance = stripe.BalanceTransaction.retrieve (charge.balance_transaction) | |
137 | conn = psycopg2.connect ('dbname=hcoop_portal3test user=hcoop host=postgres port=5433') | |
138 | cur = conn.cursor () | |
139 | cur.execute ('insert into stripe_payment (charge_id, card_name, webuser_id, paid_on, gross, fee) values (%s, %s, %s, %s, %s, %s)', | |
140 | (charge.id, charge.card.name, webuser_id, datetime.date.today (), charge.amount, balance.fee)) | |
141 | conn.commit () | |
142 | ||
143 | notify_payment (charge, member_name) | |
144 | stripe_success ('/portal/portal?cmd=stripeSuccess') | |
145 | elif request_command == 'reject_member_payment': | |
146 | # todo: protect command using group file and separate cgi | |
147 | charge_id = request_params.getvalue ('stripeChargeId'); | |
148 | reason = request_params.getvalue ('reason', 'none given'); | |
149 | with stripe_error_handling (): | |
150 | conn = psycopg2.connect (connstring) | |
151 | cur = conn.cursor () | |
152 | ||
153 | cur.execute ('SELECT charge_id FROM stripe_payment WHERE charge_id = %s', (charge_id,)) | |
154 | assert cur.fetchone() != None, 'Bad charge id' | |
155 | cur.execute ('SELECT stripe_charge_id FROM stripe_handled WHERE stripe_charge_id = %s', (charge_id,)) | |
156 | assert cur.fetchone() == None, 'Cannot rejected a previously handled payment' | |
157 | ||
158 | charge = stripe.Charge.retrieve (charge_id); | |
159 | charge.refund () | |
160 | cur.execute ('insert into stripe_rejected (stripe_charge_id, refunded_on, reason) values (%s, %s, %s)', | |
161 | (charge.id, datetime.date.today (), reason)) | |
162 | conn.commit () | |
163 | ||
164 | notify_payment_rejected (charge, reason) | |
165 | stripe_success ('/portal/money?cmd=stripeRejected') | |
166 | else: | |
167 | assert False, 'Invalid command.' | |
168 | ||
169 | # Use mod_authz_groupfile to store money/root | |
170 | # (All hcoop members should be able to use this!) | |
171 | # [support Satisfy? Satisfy: all is OK for now...] | |
172 | # Whenever groups are updated in the portal, write the file | |
173 | # make sure to store the file outside of the web root (duh) | |
174 | # only users in money/root can do reject/adduser | |
175 | # common code should go into a module (feh!) | |
176 | # application_payment in one cgi (anyone) | |
177 | # member_payment in another (only kerberos users) | |
178 | # reject_payment / capture_application_payment (kerberos + inGroup {money, root}) | |
179 | ||
180 | # If there is a way to allow all and check the group info | |
181 | # here... maybe investigate, but beware security holes | |
182 | # alt: libapache2-mod-authnz-external + db helper script | |
183 | # can use ExternalGroup, check kerberos user is in group specified in | |
184 | # another env var | |
5e6afd1a | 185 |