I use the attached modules/stripeform.py, which is mostly based upon the
contrib/stripe.py
It imports the python stripe module :
https://github.com/stripe/stripe-python.git
I use it like :
@auth.requires_login(otherwise=lambda: helper.flash(
T("You need to log in to extend your subscription")))def buy():
from stripeform import clean_stripe_session
clean_stripe_session() #To prevent reuse of idempotent uuid for
stripe transactions
form = SQLFORM.factory(
# snipped …
)
form = form.process(keepvalues=True)
if form.accepted:
# error logic
else:
session.buyvars = form.vars
redirect(URL('pay'))
elif form.errors: # pragma: no branch
response.flash = T('form has errors')
return dict(form=form)
@auth.requires_login(otherwise=lambda: helper.flash(
T("You need to log in to extend your subscription")))def pay():
if not session.buyvars:
redirect(URL('buy'))
from stripeform import StripeForm
total = # TODO calculate total from session.buyvars content
cart_form = TABLE(# snipped : table displaying what is buyed (from
session.buyvars
)
payment_id = db.payments.insert(amount=total)
form = StripeForm(
pk=STRIPE_PUBLISHABLE_KEY,
sk=STRIPE_SECRET_KEY,
amount=total, # (amount is in cents)
currency='eur',
currency_symbol='€',
description="…",
more = { 'metadata': {'payment_id':payment_id}}
).process()
if form.accepted:
pi = form.response
from datetime import datetime
import pytz
db.payments[payment_id]= dict(…) # Update db with buyed items
if necessary
session.flash = T("Thank you!")
redirect(URL(c='default', f='index'))
elif form.errors: # pragma: no branch
response.flash = form.response.errors
del db.payments[payment_id]
return dict(cart_form=cart_form, form=form)
And the views for pay looks like:
{{extend 'layout.html'}}
<div class="container">
<div class="row">
<h2>{{=T("Payment")}}</h2>
<div class="col-md-7 col-sm-12 col-md-push-5">
<h3>{{=T("Cart Content")}}</h3>
<div class="table-responsive" id="payment">
{{=cart_form}}
</div>
</div>
<div class="col-md-5 col-md-pull-7 col-sm-12">
{{=form}}
</div>
…
On Thu, Mar 23, 2017 at 8:47 PM Joe Barnhart [email protected]
<http://mailto:[email protected]> wrote:
I use Stripe all the time. My sites (web2py and rails) have charged over
> $10M thru stripe so I'd have to say it works pretty well.
>
> My favorite way to use it is to use their Javascript pop-up box, which
> prevents any CC info from even getting into my server logs. I actually
> started using it before the stripe.py contrib, so I haven't used the
> contrib library very much.
>
> I'll look over the question here and post later when I have a minute.
>
> --- Joe B.
>
>
> On Tuesday, March 21, 2017 at 8:19:21 PM UTC-7, Scott Hunter wrote:
>
> Has anyone been able to use Stripe's Checkout with web2py? If so, how did
> you do it? I'm having trouble getting the token it generates back.
>
> - Scott
>
> --
> Resources:
> - http://web2py.com
> - http://web2py.com/book (Documentation)
> - http://github.com/web2py/web2py (Source code)
> - https://code.google.com/p/web2py/issues/list (Report Issues)
> ---
> You received this message because you are subscribed to the Google Groups
> "web2py-users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> For more options, visit https://groups.google.com/d/optout.
>
--
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
---
You received this message because you are subscribed to the Google Groups
"web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.
import logging
import urllib
from hashlib import sha1
import gluon.contrib.simplejson
from gluon.storage import Storage
from gluon.html import DIV, A, P, XML, INPUT
import stripe
logger = logging.getLogger("web2py.app.foundit.stripeform")
logger.setLevel(logging.DEBUG)
def clean_stripe_session():
from gluon import current
current.session.stripe_uuid = None
class Stripe:
"""
Use in WEB2PY (guaranteed PCI compliant)
def pay():
from gluon.contrib.stripe import StripeForm
form = StripeForm(
pk=STRIPE_PUBLISHABLE_KEY,
sk=STRIPE_SECRET_KEY,
amount=150, # $1.5 (amount is in cents)
description="Nothing").process()
if form.accepted:
payment_id = form.response['id']
redirect(URL('thank_you'))
elif form.errors:
redirect(URL('pay_error'))
return dict(form=form)
Low level API:
key='<api key>'
d = Stripe(key).charge(
amount=100, # 1 dollar!!!!
currency='usd',
description='test charge')
print d
print Stripe(key).check(d['id'])
print Stripe(key).refund(d['id'])
Sample output (python dict):
{u'fee': 0, u'description': u'test charge', u'created': 1321242072, u'refunded': False, u'livemode': False, u'object': u'charge', u'currency': u'usd', u'amount': 100, u'paid': True, u'id': u'ch_sdjasgfga83asf', u'card': {u'exp_month': 5, u'country': u'US', u'object': u'card', u'last4': u'4242', u'exp_year': 2012, u'type': u'Visa'}}
if paid is True than transaction was processed
"""
def __init__(self, key):
self.key = key
stripe.api_key = key
def charge(self, amount, # in cents
currency='usd',
idempotency_key=None,
token=None,
description='test charge',
more=None):
if not token:
return Storage() # We should only get token from script.js
res = Storage()
from gluon import current
T = current.T
res.paid = False
try:
customer = Customer()
if not customer.has_card(token):
token = customer.create_card(token)
d = {
'amount': amount,
'currency': currency,
'customer': customer.id,
'source': token,
'description': description,
'idempotency_key': idempotency_key
}
if more:
d.update(more)
res = stripe.Charge.create(**d)
except stripe.error.CardError, e:
# it's a decline, stripe.error.CardError will be caught
from gluon import current
T = current.T
res.errors = T(e.message)
res.reason = T(e.json_body['error'].get('decline_code', ''))
except stripe.error.StripeError, e:
logger.error("%s", e)
res.errors = str(XML(T("Unexpected error when communicating with Stripe. If the problem persists, "
"let us know at <a href='mailto://webmaster@%s'>webmaster@%s</a>", current.SITE_NAME)))
return res
def check(self, charge_id):
return stripe.Charge.retrieve(charge_id)
def refund(self, charge_id):
return stripe.Refund.create(charge=charge_id)
class Customer(object):
from gluon import current
def __init__(self):
pass
def __getattr__(self, name):
return self._stripe_customer.get(name)
@property
@current.cache('_stripe_customer%s' % current.session.auth.user.id,
time_expire=3600,
cache_model=current.cache.ram)
def _stripe_customer(self):
""" Get stripe customer as stored in the stripe_id extra
field of the auth_user table. Create a customer if non exists """
from gluon import current
user = current.session.auth.user
db = current.globalenv['db']
stripe_id = db.auth_user(user.id).stripe_id
if not stripe_id:
# get one
logger.debug("Create Stripe customer")
customer = stripe.Customer.create(
description="%s %s" % (user.first_name, user.last_name),
email=user.email,
metadata={'user_id': user.id}, )
logger.debug("Done created Stripe customer")
db.auth_user[user.id] = dict(stripe_id=customer.id)
else:
logger.debug("Retrieve Stripe customer")
customer = stripe.Customer.retrieve(stripe_id)
logger.debug("Done retrieving Stripe customer")
return customer
@property
@current.cache('valid_cards%s' % current.session.auth.user.id,
time_expire=3600,
cache_model=current.cache.ram)
def valid_cards(self):
cust_id = self._stripe_customer.id
cards = stripe.Customer.retrieve(cust_id).sources.all()
from datetime import datetime
now = datetime.now()
res = [c for c in cards['data']
if c['exp_year'] > now.year or (c['exp_year'] == now.year
and c['exp_month'] >= now.month)]
return res
def create_card(self, card_data):
""" card_data may be a dict or a token returned by stripe.js """
from gluon import current
card = self._stripe_customer.sources.create(source=card_data)
# clear cache for valid_cards
current.cache.ram.clear('valid_cards%s' % current.session.auth.user.id)
return card
def has_card(self, card_token):
return card_token in [c.id for c in self.valid_cards]
class StripeForm(object):
def __init__(self, pk, sk, amount, # in cents
description,
currency='usd',
currency_symbol='$',
security_notice=True,
disclosure_notice=True,
template=None,
extraenv=None,
more=None):
from gluon import current, redirect, URL
from gluon.utils import web2py_uuid
if not (current.request.is_local or current.request.is_https):
redirect(URL(args=current.request.args, scheme='https'))
self.pk = pk
self.sk = sk
self.stripe = Stripe(sk)
self.amount = amount
self.description = description
self.currency = currency
self.currency_symbol = currency_symbol
self.security_notice = security_notice
self.disclosure_notice = disclosure_notice
self.template = template or TEMPLATE
self.accepted = None
self.more = more
self.errors = None
self.signature = sha1(repr((current.session.auth.user.id,
self.amount,
self.description))).hexdigest()
self.uuid = current.session.stripe_uuid or web2py_uuid()
self.extraenv = extraenv
self.customer = None
if not current.session.stripe_uuid:
current.session.stripe_uuid = self.uuid
def process(self):
from gluon import current
request = current.request
if request.post_vars:
if self.signature == request.post_vars.signature:
self.response = self.stripe.charge(
token=request.post_vars.stripeToken,
amount=self.amount,
idempotency_key=self.uuid,
description=self.description,
more=self.more,
currency=self.currency)
if self.response.get('paid', False):
current.session.stripe_uuid = None
self.accepted = True
return self
self.errors = True
return self
def xml(self):
from gluon.template import render
from gluon import current
T = current.T
if self.accepted:
return str(T("Your payment was processed successfully"))
cards = default_card = None
try:
self.customer = Customer()
cards=self.customer.valid_cards
default_card=self.customer.default_source
except stripe.error.APIConnectionError, e:
return str(DIV(XML(T("We've got some connexion error when communicating with Stripe. If the problem persists, "
"let us know at <a href='mailto://webmaster@%s'>webmaster@%s</a>", current.SITE_NAME, current.SITE_NAME)),
_class="error"))
except stripe.error.StripeError, e:
logger.error("%s", e)
return str(DIV(XML(T("Unexpected error when communicating with Stripe. If the problem persists, "
"let us know at <a href='mailto://webmaster@%s'>webmaster@%s</a>", current.SITE_NAME, current.SITE_NAME)),
_class="error"))
context = dict(amount=self.amount,
signature=self.signature,
pk=self.pk,
currency_symbol=self.currency_symbol,
security_notice=self.security_notice,
disclosure_notice=self.disclosure_notice,
cards=cards,
error=(self.errors and self.response.errors) or '',
default_card=default_card)
context.update({'T':T, 'A':A, 'DIV':DIV, 'XML':XML, 'INPUT':INPUT})
return render(content=self.template, context=context)
# TODO use web2py FOMR in order to protect from double submission
TEMPLATE = """
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script>
jQuery(function(){
// This identifies your website in the createToken call below
Stripe.setPublishableKey('{{=pk}}');
var stripeResponseHandler = function(status, response) {
var jQueryform = jQuery('#payment-form');
if (response.error) {
// Show the errors on the form
jQuery('.payment-errors').text(response.error.message).show();
jQueryform.find('button').prop('disabled', false);
} else {
// token contains id, last4, and card type
var token = response.id;
// Insert the token into the form so it gets submitted to the server
var tokenInput = jQuery('<input type="hidden" name="stripeToken" />');
jQueryform.append(tokenInput.val(token));
// and re-submit
jQueryform.get(0).submit();
}
};
jQuery(function(jQuery) {
jQuery('#payment-form').submit(function(e) {
var jQueryform = jQuery(this);
// Disable the submit button to prevent repeated clicks
jQueryform.find('button').prop('disabled', true);
Stripe.createToken(jQueryform, stripeResponseHandler);
// Prevent the form from submitting with the default action
return false;
});
});
});
</script>
<h3>{{=T("Payment Amount: %(currency)s %(amount)s", dict(currency=currency_symbol,
amount="%.2f" % (0.01*amount)))}}</h3>
{{ if cards:}}
<div class="panel panel-default">
<div class="panel-body">
<h4>{{=T("Pay With an Existing Card")}}</h4>
<form action="" method="POST" id="payment-existing-card" class="form-horizontal">
{{for i,card in enumerate(cards):}}
<div class="form-row form-group">
<div class="controls col-sm-offset-1">
{{=INPUT(_type='radio', _name='stripeToken', _id='stripeToken%d' % i, _value=card.id, value=default_card)}} {{=card.brand}} ************{{=card.last4}} {{=card.exp_month}}/{{=card.exp_year}}
</div></div>
{{pass}}
<div class="form-row form-group">
<div class="controls col-sm-offset-4 col-lg-8">
<button type="submit" value="submitcard" class="btn btn-primary">{{=T("Submit Payment")}}</button>
</div></div>
<input type="hidden" name="signature" value="{{=signature}}" />
</form>
</div>
</div>
{{pass}}
<div class="panel panel-default">
<div class="panel-body">
<h4>{{=T("Pay With a New Card")}}</h4>
<form action="" method="POST" id="payment-form" class="form-horizontal">
<div class="form-row form-group">
<label class="col-sm-4 control-label">{{=T("Card Number")}}</label>
<div class="controls col-sm-8">
<input type="text" size="20" style="width:200px" data-stripe="number"
name="number" placeholder="4242424242424242" class="form-control"/>
</div>
</div>
<div class="form-row form-group">
<label class="col-sm-4 control-label">{{=T("Expiration")}}</label>
<div class="controls col-sm-8">
<input type="text" size="2" style="width:60px; display:inline-block"
name='exp_month' data-stripe="exp-month" placeholder="MM"
class="form-control"/>
/
<input type="text" size="4" style="width:80px; display:inline-block"
name='exp_year' data-stripe="exp-year" placeholder="YYYY"
class="form-control"/>
</div>
</div>
<div class="form-row form-group">
<label class="col-sm-4 control-label">{{=T("CVC")}}</label>
<div class="controls col-sm-8">
<input type="text" size="4" style="width:80px" data-stripe="cvc"
name='cvc' placeholder="XXX" class="form-control"/>
<a href="http://en.wikipedia.org/wiki/Card_Verification_Code" target="_blank">{{=T("What is this?")}}</a>
</div>
</div>
<div class="form-row form-group">
<div class="controls col-sm-offset-4 col-lg-8">
<button type="submit" value="submit" class="btn btn-primary">{{=T("Submit Payment")}}</button>
<div class="payment-errors error">{{=error}}</div>
</div>
</div>
<input type="hidden" name="signature" value="{{=signature}}" />
</form>
</div>
</div>
"""