Skip to content

Instantly share code, notes, and snippets.

@tav
Last active December 17, 2015 09:39
Show Gist options
  • Save tav/5589012 to your computer and use it in GitHub Desktop.
Save tav/5589012 to your computer and use it in GitHub Desktop.
import logging
from cgi import escape
from decimal import Decimal
from json import loads as decode_json
from google.appengine.api import memcache
from google.appengine.api.urlfetch import fetch as urlfetch, POST
from google.appengine.ext import db
create_key = db.Key.from_path
CAMPAIGNS = [
('sketchup', 500000, 'SketchUp Extension', 'Blah blah blah'),
('hardware', 25000000, 'Hardware Designs', 'More blah blah'),
('platform', 18000000, 'Platform Development', 'Even more blah'),
]
CAMPAIGN_KEYS = {}
for _campaign in CAMPAIGNS:
CAMPAIGN_KEYS[_campaign[0]] = create_key('C', _campaign[0])
del _campaign
CURRENCIES = frozenset(['GBP', 'EUR', 'USD'])
PAYPAL_ACCOUNT = '[email protected]'
class C(db.Model): # key=campaign_id
v = db.IntegerProperty(default=0)
c = db.IntegerProperty(default=0) # count of funders
t1 = db.IntegerProperty(default=0) # total raised (GBP)
t2 = db.IntegerProperty(default=0) # total raised (EUR)
t3 = db.IntegerProperty(default=0) # total raised (USD)
Campaign = C
class P(db.Model): # key=txn_id
v = db.IntegerProperty(default=0)
c = db.StringProperty(default='', indexed=False) # campaign id
d = db.DateTimeProperty(auto_now_add=True) # date created
e = db.StringProperty(default='', indexed=False) # payer email
f = db.StringProperty(default='', indexed=False) # fee
g = db.StringProperty(default='', indexed=False) # gross
h = db.BooleanProperty(default=False) # handled
i = db.TextProperty() # info payload
m = db.DateTimeProperty(auto_now=True) # modified date
n = db.StringProperty(default='', indexed=False) # net
p = db.StringProperty(default='', indexed=False) # payer name
t = db.StringProperty(default='', indexed=False) # currency type
PayPalTransaction = P
class T(db.Model): # parent=campaign_id, key=txn_id
"""Stub entity to synchronise accounted transactions."""
TransactionReceipt = T
# This handler needs to be served at the path: /ipn
#
# The required parameters are:
#
# post_body — The body of the POST request that PayPal made to /ipn
# This would have been encoded in windows-1252 and we
# shouldn't corrupt it by trying to treat it as utf-8
#
# kwargs — The key/value pairs from the parsed POST body.
#
# The returned string value from this handler needs to be served with a
# 200 OK code.
def handle_ipn(post_body, **kwargs):
resp = do_actual_ipn_handling(post_body, kwargs)
if resp != "OK":
logging.info("IPN RETCODE: %s" % resp)
return resp
def do_actual_ipn_handling(txn_info, kwargs):
if kwargs['payment_status'] != 'Completed':
return '1'
resp = urlfetch(
url="https://www.paypal.com/cgi-bin/webscr",
method=POST,
payload=('cmd=_notify-validate&' + txn_info)
)
if resp.content != 'VERIFIED':
return '2'
campaign = kwargs['custom']
if not campaign:
return '3'
if not campaign.startswith('wikihouse.'):
return '4'
campaign = campaign.split('.', 1)[1].strip()
if campaign not in CAMPAIGN_KEYS:
return '5'
receiver = kwargs['receiver_email']
if receiver != PAYPAL_ACCOUNT:
return '6'
currency = kwargs['mc_currency']
if currency not in CURRENCIES:
return '7'
gross = kwargs['mc_gross']
fee = kwargs['mc_fee']
gross_d = Decimal(gross)
fee_d = Decimal(fee)
net = gross_d - fee_d
if net <= 0:
return '8'
txn_id = kwargs['txn_id']
first_name = kwargs.get('first_name')
if first_name:
first_name += ' '
payer_name = (first_name + kwargs.get('last_name', '')).strip()
payer_email = kwargs['payer_email']
txn = PayPalTransaction.get_or_insert(
key_name=txn_id, c=campaign, e=payer_email, f=fee, g=gross, i=txn_info,
n=str(net), p=payer_name, t=currency
)
db.run_in_transaction(
update_campaign_tallies, campaign, txn_id, currency, net
)
txn.h = True
txn.put()
return 'OK'
def update_campaign_tallies(campaign_id, txn_id, currency, amount):
amount_in_pence = int(amount * 100)
campaign_key = CAMPAIGN_KEYS[campaign_id]
receipt = db.get(create_key('T', txn_id, parent=campaign_key))
if receipt:
return
receipt = TransactionReceipt(key_name=txn_id, parent=campaign_key)
campaign = Campaign.get(campaign_key)
campaign.c += 1
if currency == 'GBP':
campaign.t1 += amount_in_pence
elif currency == 'EUR':
campaign.t2 += amount_in_pence
elif currency == 'USD':
campaign.t3 += amount_in_pence
db.put([campaign, receipt])
return
def render_number_with_commas(n):
result = ''
while n >= 1000:
n, r = divmod(n, 1000)
result = ",%03d%s" % (r, result)
return "%d%s" % (n, result)
def get_exchange_rate_to_gbp(currency, cache={}):
if currency == 'GBP':
return 1
if currency in cache:
return cache[currency]
rate = memcache.get('exchange:%s' % currency)
if rate:
return cache.setdefault(currency, rate)
url = "http://rate-exchange.appspot.com/currency?from=%s&to=GBP" % currency
try:
rate = decode_json(urlfetch(url).content)['rate']
except Exception, err:
logging.error("currency conversion: %s" % err)
return 0
memcache.set('exchange:%s' % currency, rate)
return cache.setdefault(currency, rate)
def gen_fund_form():
html = []; out = html.append
entities = dict(
(ent.key().name(), ent)
for ent in Campaign.get(sorted(CAMPAIGN_KEYS))
)
for campaign in CAMPAIGNS:
campaign_id = campaign[0]
ent = entities[campaign_id]
if ent.c == 1:
backers = '1 Backer'
else:
backers = '%d Backers' % ent.c
target = campaign[1]
total = ent.t1
if ent.t2:
total += (ent.t2 * get_exchange_rate_to_gbp('EUR'))
if ent.t3:
total += (ent.t3 * get_exchange_rate_to_gbp('USD'))
raised = int(total)
if raised >= target:
pct = 100
else:
pct = (raised * 100)/target
kwargs = dict(
backers=backers,
campaign=campaign_id,
email=PAYPAL_ACCOUNT,
info=escape(campaign[3]),
pct=pct * 1, # multiply by 2 if width == 200px, etc.
raised=render_number_with_commas(raised/100),
target=render_number_with_commas(target/100),
title=escape(campaign[2])
)
out("""<div class="campaign">
<div class="campaign-title">%(title)s</div>
<div class="campaign-info">%(info)s</div>
<div class="campaign-backers">%(backers)s</div>
<div class="campaign-raised">Raised: £%(raised)s</div>
<div class="campaign-target">%(pct)s%% of target £%(target)s</div>
<form action="https://www.paypal.com/uk/cgi-bin/webscr" method="post">
<input type="hidden" name="charset" value="utf-8">
<input type="hidden" name="cmd" value="_donations">
<input type="hidden" name="business" value="%(email)s">
<input type="hidden" name="item_name" value="Fund WikiHouse">
<input type="hidden" name="item_number" value="wikihouse.%(campaign)s">
<input type="hidden" name="custom" value="wikihouse.%(campaign)s">
<input type="hidden" name="return" value="http://www.wikihouse.cc/thank-you">
<input type="hidden" name="rm" value="1">
<input type="hidden" name="cancel_return" value="http://www.wikihouse.cc/">
<input type="hidden" name="notify_url" value="https://wikihouse-cc.appspot.com/ipn">
<input type="text" name="amount" value="" placeholder="Amount, e.g. 30.00">
<select name="currency_code">
<option value="USD">US Dollars ($)</option>
<option value="GBP">British Pounds (£)</option>
<option value="EUR">Euros (€)</option>
</select>
<input type="submit" name="submit" value=" Back This! ">
</form></div>""" % kwargs)
return ''.join(html)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment