Skip to content
This repository has been archived by the owner on Feb 8, 2018. It is now read-only.

Commit

Permalink
Database and Python updates
Browse files Browse the repository at this point in the history
  • Loading branch information
chadwhitacre committed Sep 10, 2017
1 parent 1f10b7b commit 2a3832c
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 178 deletions.
6 changes: 3 additions & 3 deletions deploy/before.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ BEGIN;
, ctime timestamptz NOT NULL DEFAULT now()

-- card charge
, amount bigint NOT NULL
, transaction_id text UNIQUE DEFAULT NULL
, succeeded bool NOT NULL DEFAULT FALSE
, amount bigint NOT NULL
, braintree_transaction_id text UNIQUE DEFAULT NULL
, braintree_result_message text DEFAULT NULL

-- contact info
, name text NOT NULL
Expand Down
33 changes: 17 additions & 16 deletions gratipay/homepage.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ def _parse(raw):
"""

errors = []
x = lambda f: raw.get(f, '').strip()
x = lambda f: raw[f].strip() # KeyError -> 400

# amount
amount = x('amount') or '0'
amount = x('amount')
if (not amount.isdigit()) or (int(amount) < 10):
errors.append('amount')
amount = ''.join(x for x in amount.split('.')[0] if x.isdigit())
Expand Down Expand Up @@ -84,11 +84,14 @@ def _store(parsed):
return PaymentForOpenSource.insert(**parsed)


def _charge(amount, payment_method_nonce, _sale=braintree.Transaction.sale):
return _sale({ 'amount': amount
, 'payment_method_nonce': payment_method_nonce
, 'options': {'submit_for_settlement': True}
})
def _charge(pfos, payment_method_nonce):
charge = braintree.Transaction.sale
result = charge({ 'amount': pfos.amount
, 'payment_method_nonce': payment_method_nonce
, 'options': {'submit_for_settlement': True}
, 'custom_fields': {'pfos_uuid': pfos.uuid}
})
pfos.process_result(result)


def _send(app, email_address, payment_for_open_source):
Expand All @@ -102,14 +105,12 @@ def _send(app, email_address, payment_for_open_source):

def pay_for_open_source(app, raw):
parsed, errors = _parse(raw)
payment_method_nonce = parsed.pop('payment_method_nonce')
payment_for_open_source = _store(parsed)
if not errors:
result = _charge(parsed['amount'], payment_method_nonce)
payment_for_open_source.process_result(result)
if not payment_for_open_source.succeeded:
payment_method_nonce = parsed.pop('payment_method_nonce')
pfos = _store(parsed)
_charge(pfos, payment_method_nonce)
if not pfos.succeeded:
errors.append('charging')
if not errors and parsed['email_address']:
_send(app, parsed['email_address'], payment_for_open_source)
parsed = {} # no need to populate form anymore
return {'parsed': parsed, 'errors': errors}
if parsed['email_address'] and not errors:
_send(app, parsed['email_address'], pfos)
return {'errors': errors, 'receipt_url': pfos.receipt_url}
37 changes: 28 additions & 9 deletions gratipay/models/payment_for_open_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from __future__ import absolute_import, division, print_function, unicode_literals

import gratipay
import uuid as uuidlib
from uuid import uuid4
from postgres.orm import Model


Expand All @@ -11,12 +11,19 @@ class PaymentForOpenSource(Model):
typname = "payments_for_open_source"

def __repr__(self):
return '<PaymentForOpenSource: %s>'.format(repr(self.amount))
return '<PaymentForOpenSource: {}>'.format(repr(self.amount))


@property
def succeeded(self):
return self.braintree_result_message == ''


@property
def receipt_url(self):
return '{}/browse/payments/{}.html'.format(gratipay.base_url, self.uuid)
if not self.succeeded:
return None
return '{}/browse/payments/{}/receipt.html'.format(gratipay.base_url, self.uuid)


@classmethod
Expand All @@ -36,7 +43,7 @@ def insert(cls, amount, name, follow_up, email_address,
cursor=None):
"""Take baseline info and insert into the database.
"""
uuid = uuidlib.uuid4().hex
uuid = uuid4().hex
return (cursor or cls.db).one("""
INSERT INTO payments_for_open_source
(uuid, amount, name, follow_up, email_address,
Expand All @@ -51,11 +58,23 @@ def insert(cls, amount, name, follow_up, email_address,
def process_result(self, result):
"""Take a Braintree API result and update the database.
"""
transaction_id = result.transaction.id if result.transaction else None
result_message = '' if result.is_success else result.message
transaction_id = None
if result.transaction:
transaction_id = result.transaction.id

# Verify that Braintree is sending us the right payload.
# TODO This is hard to test and it should be a pretty tight guarantee,
# so I am commenting out for now. :(
#pfos_uuid = result.transaction.custom_fields['pfos_uuid']
#assert pfos_uuid == self.uuid, (pfos_uuid, transaction_id)

self.db.run("""
UPDATE payments_for_open_source
SET transaction_id=%s
, succeeded=%s
SET braintree_result_message=%s
, braintree_transaction_id=%s
WHERE uuid=%s
""", (transaction_id, result.is_success, self.uuid))
self.set_attributes(transaction_id=transaction_id, succeeded=result.is_success)
""", (result_message, transaction_id, self.uuid))
self.set_attributes( braintree_result_message=result_message
, braintree_transaction_id=transaction_id
)
56 changes: 29 additions & 27 deletions tests/py/fixtures/BadCharge.yml
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
interactions:
- request:
body: !!python/unicode <transaction><amount>10</amount><type>sale</type><options><submit_for_settlement
body: !!python/unicode <transaction><custom_fields><pfos_uuid>d9e8b52693454ddaac42d8bc7c40c7ee</pfos_uuid></custom_fields><amount
type="integer">1000</amount><type>sale</type><options><submit_for_settlement
type="boolean">true</submit_for_settlement></options><payment_method_nonce>fake-valid-nonce</payment_method_nonce></transaction>
headers: {}
method: POST
uri: https://api.sandbox.braintreegateway.com:443/merchants/j9gwdfjdkxymhdgr/transactions
uri: https://api.sandbox.braintreegateway.com:443/merchants/bk8h97tqzyqjhtfn/transactions
response:
body:
string: !!binary |
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
d9Bokos/AQAA//8DALEpavm+EwAA
headers:
cache-control: ['max-age=0, private, must-revalidate']
content-encoding: [gzip]
content-type: [application/xml; charset=utf-8]
etag: [W/"48d3905da353929ec2b8cd3d54737edd"]
etag: [W/"8bb86a94a655c5cede04df1711180b61"]
strict-transport-security: [max-age=31536000; includeSubDomains]
transfer-encoding: [chunked]
vary: [Accept-Encoding]
Expand Down
18 changes: 10 additions & 8 deletions tests/py/fixtures/GoodCharge.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
interactions:
- request:
body: !!python/unicode <transaction><amount>10</amount><type>sale</type><options><submit_for_settlement
body: !!python/unicode <transaction><custom_fields><pfos_uuid>4f1947abb519403ba6a30589bb093a1f</pfos_uuid></custom_fields><amount
type="integer">1000</amount><type>sale</type><options><submit_for_settlement
type="boolean">true</submit_for_settlement></options><payment_method_nonce>deadbeef</payment_method_nonce></transaction>
headers: {}
method: POST
uri: https://api.sandbox.braintreegateway.com:443/merchants/j9gwdfjdkxymhdgr/transactions
uri: https://api.sandbox.braintreegateway.com:443/merchants/bk8h97tqzyqjhtfn/transactions
response:
body:
string: !!binary |
H4sIAP0Ls1kAA6RSu27DMAzc8xWCdkfJkKIFFGXLFzRzQMdMatR6gKLb+O8rP/OA26UbyaNOdyfp
3dVW4gsplt5t5Xq5kgLdyRelu2zl4X2fvcqdWWgIZYZEnjLCGLyLaBZC6G4U23JqBDcBtxKIoJFq
gJjARThxuqSfzK+P2Ije+jRJotC8rTcvG626+h4EZirzmnHgi43NfSVNgMai46NF/vDF0Xl3Qq2m
7QcOizHCBc3BfTr/7YQngddQEhZijmap1XjiJls96B7aMSD1FMM9rAMQ2PhHXmB97disV0l/X45I
a9lEqJKzrhznPrQE8U5erHNbcnZO7xiRucLWlWGq09F5bHLyRKaHSLI+kqyLxBQIRY541moW/i2G
m/f/PELKZeaX/gAAAP//AwB33+lL4gIAAA==
H4sIALdttFkAA6STy26DMBBF9/0Kiz0BlJAGyXF2/YJmHY1hSFHxQ7Zpw9/XEF6JaDddMTN3PL6c
AXq6iZp8obGVkscg2cQBQZmropLXY3B+fwsPwYm9UNBViMYoExq0WkmL7IUQ2pdsF04Jca3GYwDG
QBtEg+QMSAu585fcK+vtozaqc+4r3hSyLEn3KY36eCmCc6bijcNhnm0FV3XANLQCpbsIdB+quEgl
c6TR1P0wQ6C1cEV2lp9SfUuiDMGbrgwWZG3Mhkbjidl29OB7SEdA0ROGpUw1GBD2D155Y50SYVlh
XdjFlbpUNmyaqmC7Msl2r8B56p/xlsMetnF6yDiPsy0kJY3m3snh6lgKQjXSsSSOY4/rnoxaR5hZ
qD3IPhzrSnd+l9Zsw0XlwtJ/Nhadq7GDyJxp/NF1bbL1NIwOGwjvGwj7DbACoeCI3Yutyb9Rn1H/
Z+eey8pP8QMAAP//AwB9fay/UQMAAA==
headers:
cache-control: [no-cache]
content-encoding: [gzip]
Expand Down
Loading

0 comments on commit 2a3832c

Please sign in to comment.