Python and Flask Are Powerful
jeffknupp.com
jeffknupp.com
try:
charge = stripe.Charge.create(
amount=int(product.price * 100),
currency='usd',
card=stripe_token,
description=email)
except stripe.CardError, e:
return """..err.."""
print chargeI'm now unsure what something like Gumroad's value proposition is, considering I'm basically at feature parity with them (aside from PDF stamping, which I don't do anyway).
After a few years, Stripe is still only available in a handful of countries. If you live in a country that is not only not US (tier 1 for pretty much everything), but not even the handful of high-priority countries like Canada or UK, your options are greatly reduced. For me, for example, it's PayPal or FastSpring - I would love to use Stripe, but I simply can't.
You gather params, post it somewhere, check out the response, and display it. It gets fancier with age, but it's essentially the same thing.
Also the stripe_token is pretty magic, it lets you make charges to customer credit cards without storing the card details or having to take them to an external payment page.
I use Braintree at my current job.
I used to work for Balanced.
I know payment processors. Its just times have changed and this kind of thing is now quite standard. But 'magical' is of opinion, I was just inquiring what he found magical.
Compared with Stripe's 2.9% + 30¢, it's expensive, but Gumroad appears to do two things that Stripe doesn't:
- Work out whether and how much sales tax to charge (which I guess should be easy if you operate in only one state)
- Absorb chargeback fees
This second one makes the 5% seem more reasonable.
purchase = Purchase(uuid=str(uuid.uuid4()),
Other than making sure you're careful with the customer's credit card data (not so much of an issue using Stripe), this was probably the portion of the code where a mistake could have decreased the security of the system.
purchase.downloads_left -= 1
Is this the preferred method to update a field? I'm asking because i'm wondering if sqlalchemy will translate this to an sql query similar to: UPDATE purchase SET downloads_left = downloads_left - 1;
Because otherwise this might be dangerous. purchase.downloads_left = Purchase.downloads_left - 1
which would emit on the next flush. in this code specifically, using that approach it would have to call session.flush() and then re-query for that value since it wants to check what the database came up with. So in that sense it would be better just to emit an explicit UPDATE..RETURNING, which is easy to do with SQLA; this is an example of how "dropping down" a level of abstraction is a critical feature with SQL abstraction tools.however, this is only one way to do it, which is the so-called pessimistic approach. An optimistic approach would just ensure that the transaction isolation is in repeatable read, so that the flush (occurs within the commit() here) would just fail in the very unlikely case a single user is submitting twice. SQLAlchemy also offers a "version counter" feature that can accomplish the same task if RR isn't an option. Both of these are configuration-level features that would allow the code to remain unchanged.
The "session.add(purchase)" is also unnecessary in that code sample, and the code also has a bug in that it does not commit the transaction when downloads_left reaches zero, so the number can never actually reach zero in the database.
UPDATE purchase SET downloads_left=%(downloads_left)s WHERE purchase.id = %(purchases_id)s;
By the way, the posted code has an off-by-one error, as it should do the checking first before the minus operation. Also, the line `db.session.add(purchase)` is redundant.EDIT: remove bad sample
Unless the data isn't that important, it's better to use an RDBMS with SSI[1] support, and retry or bail out if there's a concurrent write.
http://www.postgresql.org/docs/current/static/mvcc-intro.htm...
from django.conf import settings as django_settings
from myapp import settings
myapp_settings = return dict([(s, getattr(settings, s)) for s in dir(settings) if s==s.upper()])
django_settings.configure(**myapp_settings)
from myapp.models import User
app = Flask(__name__)
api = restful.Api(app)
class UserResource(restful.Resource):
def get(self):
return {"result": User.objects.all()}, 200
api.add_resource(UserResource, '/')
if __name__ == '__main__':
app.run(debug=myapp_settings['DEBUG'])
There's just a little trick to get your API to convert django models to Json: from flask.json import JSONEncoder as FlaskJSONEncoder
from django.db.models import Model
from django.db.models.base import ModelBase
from django.db.models.query import QuerySet, ValuesQuerySet
from django.db.models.fields.related import ManyToManyField
from datetime import datetime
def model_to_dict(instance):
"""Same as django.forms.models.model_to_dict, but returns
everything, including non-editable fields"""
opts, data = instance._meta, {}
for f in opts.concrete_fields + opts.many_to_many:
if isinstance(f, ManyToManyField):
data[f.name] = []
if instance.pk is not None:
data[f.name] = list(f.value_from_object(instance).values_list('pk', flat=True))
else: data[f.name] = f.value_from_object(instance)
return data
class ApiJSONEncoder(FlaskJSONEncoder):
def default(self, obj):
if isinstance(obj, (Model, ModelBase)):
return model_to_dict(obj)
if isinstance(obj, (QuerySet, ValuesQuerySet)):
return [model_to_dict(m) for m in obj]
elif isinstance(obj, datetime):
return obj.isoformat("T")
return FlaskJSONEncoder.default(self, obj)<shameless plug>
I wrote a book about combining Stripe and Rails.
https://www.petekeen.net/mastering-modern-payments
</plug>
I have a question on HN etiquette re: plugging decorum. I seen a few instances of this in passing, I thought it was cool.
If it was in this instance, for example: What they did was drop the bait. "I wrote a book about combining Stripe and Rails". Then a reply comment, 99% of the time, will ask for them to dish out the link.
If zrail simply mentioned his book on rails and stripe, my curiosity would have compelled me reply to ask for information on his book (and presumably a link). Also I would see his profile and notice he's also an engineer at a large rails site - which makes me want to hear more from him.
Has anyone else here ever seen this and can show examples of it on HN comments?
Is there another reason some HN posters don't do links in comments, even if they are their own projects but on topic?
We are all here because we want profit from what we learn here (either improving our minds, or more rarely our bank balances) - and indeed most of us secretly or less secretly want to follow the freelancer / ebook / SaaS product route.
To be reminded of the existence of the route and that it is not an impassable journey is, infrequently, a good thing IMO
For email you should probably not use Gmail. They have weird limits and you can easily run afoul of them. A better route is something like Mailgun or Mandrill. They're both free for a large number of outgoing sends and have nice logging and tracking options.
I totally agree here. You're also forced to store a password (even if it's app-specific) in plain text, and the tracking/delivery features are non-existent.
The Mandrill API is pretty rad (I'm using it in my Go app) and the tracking, ability to re-send an email from the last 24 hours (on the free plan) and the server-side templates are all very useful.
try:
mandrill_client = mandrill.Mandrill('YOUR_API_KEY')
message = {
'subject': "Hello there.",
'to': [{'email': 'recipient.email@example.com',
'name': 'Recipient Name',
'type': 'to'}],
'from_name': "Matt",
'from_email': "matt@example.com",
}
result = mandrill_client.messages.send(message=message, async=True) // It'll fire it off without waiting, and you can either set up a web-hook *or* manually re-send if it fails via the dashboard if you don't do massive volumes
except mandrill.Error, e:
# Mandrill errors are thrown as exceptions
print 'A mandrill error occurred: %s - %s' % (e.__class__, e)
# A mandrill error occurred: <class 'mandrill.UnknownSubaccountError'> - No subaccount exists with the id 'customer-123'
raise
Pretty simple stuff. Add in some MergeVars (|NAME|, |PURCHASE_ID|) and server-side templates and you can pass whatever is needed to the API without having to keep "content" in your code or pull in an external template (i.e. Jinja).<shameless plug - trying to pay my way through uni ;)>
Your variation seems even simpler and more straightforward - congrats on launching and all the best for your project!
render_template('something.html')
and then write the HTML for the template in a separate code section.
render_template('error.html')Anything else is a distraction.
Using render_template would have left us wondering whether there was anything relevant to the task in render_template, or at best interrupting the reader's flow.
I just enjoy Python in general as I can get things done so quickly. Especially with a nice environment setup using phone and pip. On top of that PyCharm just works.
I've tried a number of other languages over the years, but could never get to the level of productivity as I've always gotten with Python. And now I'm going to have to buy the OPs book...
This is part of the reason I'm becoming more and more of a fan of web.py and peewee (https://github.com/coleifer/peewee). They are simple but still powerful enough for the majority of use cases.
I think this kind of use case is exactly what has people so excited about node, since, in a single thread, it can go on processing new requests while it's waiting to hear back from, e.g., stripe (or the database, or the disk, or any other kind of IO).
May your book go on to have so many concurrent sales that this becomes an issue!
Kidding (sort of), twisted is great :)
Would this even be an issue if you're using e.g. Apache (multiple threads) with mod_wsgi?
Wouldn't it be better to use Paypal or some well known third party for something as important as payment?
I found some issues and didn't reported because it is too dificult.