Merge branch 'newpayment' into relaunch
Conflicts: frontend/views.py payment/manager.pypull/1/head
commit
f639aa02dd
|
@ -0,0 +1,7 @@
|
|||
from django import forms
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class StripePledgeForm(forms.Form):
|
||||
stripe_token = forms.CharField(required=False, widget=forms.HiddenInput())
|
|
@ -0,0 +1,268 @@
|
|||
# https://github.com/stripe/stripe-python
|
||||
# https://stripe.com/docs/api?lang=python#top
|
||||
|
||||
from datetime import datetime
|
||||
from pytz import utc
|
||||
|
||||
import stripe
|
||||
|
||||
try:
|
||||
import unittest
|
||||
from unittest import TestCase
|
||||
except:
|
||||
from django.test import TestCase
|
||||
from django.utils import unittest
|
||||
|
||||
# if customer.id doesn't exist, create one and then charge the customer
|
||||
# we probably should ask our users whether they are ok with our creating a customer id account -- or ask for credit
|
||||
# card info each time....
|
||||
|
||||
# should load the keys for Stripe from db -- but for now just hardcode here
|
||||
# moving towards not having the stripe api key for the non profit partner in the unglue.it code -- but in a logically
|
||||
# distinct application
|
||||
|
||||
try:
|
||||
from regluit.core.models import Key
|
||||
STRIPE_PK = Key.objects.get(name="STRIPE_PK").value
|
||||
STRIPE_SK = Key.objects.get(name="STRIPE_SK").value
|
||||
STRIPE_PARTNER_PK = Key.objects.get(name="STRIPE_PARTNER_PK").value
|
||||
STRIPE_PARTNER_SK = Key.objects.get(name="STRIPE_PARTNER_SK").value
|
||||
logger.info('Successful loading of STRIPE_*_KEYs')
|
||||
except Exception, e:
|
||||
# currently test keys for Gluejar and for raymond.yee@gmail.com as standin for non-profit
|
||||
STRIPE_PK = 'pk_0EajXPn195ZdF7Gt7pCxsqRhNN5BF'
|
||||
STRIPE_SK = 'sk_0EajIO4Dnh646KPIgLWGcO10f9qnH'
|
||||
STRIPE_PARTNER_PK ='pk_0AnIkNu4WRiJYzxMKgruiUwxzXP2T'
|
||||
STRIPE_PARTNER_SK = 'sk_0AnIvBrnrJoFpfD3YmQBVZuTUAbjs'
|
||||
|
||||
# set default stripe api_key to that of unglue.it
|
||||
|
||||
stripe.api_key = STRIPE_SK
|
||||
|
||||
# https://stripe.com/docs/testing
|
||||
|
||||
TEST_CARDS = (
|
||||
('4242424242424242', 'Visa'),
|
||||
('4012888888881881', 'Visa'),
|
||||
('5555555555554444', 'MasterCard'),
|
||||
('5105105105105100', 'MasterCard'),
|
||||
('378282246310005', 'American Express'),
|
||||
('371449635398431', 'American Express'),
|
||||
('6011111111111117', 'Discover'),
|
||||
('6011000990139424', 'Discover'),
|
||||
('30569309025904', "Diner's Club"),
|
||||
('38520000023237', "Diner's Club"),
|
||||
('3530111333300000', 'JCB'),
|
||||
('3566002020360505','JCB')
|
||||
)
|
||||
|
||||
ERROR_TESTING = dict((
|
||||
('ADDRESS1_ZIP_FAIL', ('4000000000000010', 'address_line1_check and address_zip_check will both fail')),
|
||||
('ADDRESS1_FAIL', ('4000000000000028', 'address_line1_check will fail.')),
|
||||
('ADDRESS_ZIP_FAIL', ('4000000000000036', 'address_zip_check will fail.')),
|
||||
('CVC_CHECK_FAIL', ('4000000000000101', 'cvc_check will fail.')),
|
||||
('BAD_ATTACHED_CARD', ('4000000000000341', 'Attaching this card to a Customer object will succeed, but attempts to charge the customer will fail.')),
|
||||
('CHARGE_DECLINE', ('4000000000000002', 'Charges with this card will always be declined.'))
|
||||
))
|
||||
|
||||
# types of errors / when they can be handled
|
||||
|
||||
#card_declined: Use this special card number - 4000000000000002.
|
||||
#incorrect_number: Use a number that fails the Luhn check, e.g. 4242424242424241.
|
||||
#invalid_expiry_month: Use an invalid month e.g. 13.
|
||||
#invalid_expiry_year: Use a year in the past e.g. 1970.
|
||||
#invalid_cvc: Use a two digit number e.g. 99.
|
||||
|
||||
|
||||
|
||||
def filter_none(d):
|
||||
return dict([(k,v) for (k,v) in d.items() if v is not None])
|
||||
|
||||
# if you create a Customer object, then you'll be able to charge multiple times. You can create a customer with a token.
|
||||
|
||||
# https://stripe.com/docs/tutorials/charges
|
||||
|
||||
def card (number=TEST_CARDS[0][0], exp_month='01', exp_year='2020', cvc=None, name=None,
|
||||
address_line1=None, address_line2=None, address_zip=None, address_state=None, address_country=None):
|
||||
|
||||
card = {
|
||||
"number": number,
|
||||
"exp_month": str(exp_month),
|
||||
"exp_year": str(exp_year),
|
||||
"cvc": str(cvc) if cvc is not None else None,
|
||||
"name": name,
|
||||
"address_line1": address_line1,
|
||||
"address_line2": address_line2,
|
||||
"address_zip": address_zip,
|
||||
"address_state": address_state,
|
||||
"address_country": address_country
|
||||
}
|
||||
|
||||
return filter_none(card)
|
||||
|
||||
|
||||
class StripeClient(object):
|
||||
def __init__(self, api_key=STRIPE_SK):
|
||||
self.api_key = api_key
|
||||
|
||||
# key entities: Charge, Customer, Token, Event
|
||||
|
||||
@property
|
||||
def charge(self):
|
||||
return stripe.Charge(api_key=self.api_key)
|
||||
|
||||
@property
|
||||
def customer(self):
|
||||
return stripe.Customer(api_key=self.api_key)
|
||||
|
||||
@property
|
||||
def token(self):
|
||||
return stripe.Token(api_key=self.api_key)
|
||||
|
||||
@property
|
||||
def transfer(self):
|
||||
return stripe.Transfer(api_key=self.api_key)
|
||||
|
||||
@property
|
||||
def event(self):
|
||||
return stripe.Event(api_key=self.api_key)
|
||||
|
||||
|
||||
def create_token(self, card):
|
||||
return stripe.Token(api_key=self.api_key).create(card=card)
|
||||
|
||||
def create_customer (self, card=None, description=None, email=None, account_balance=None, plan=None, trial_end=None):
|
||||
"""card is a dictionary or a token"""
|
||||
# https://stripe.com/docs/api?lang=python#create_customer
|
||||
|
||||
customer = stripe.Customer(api_key=self.api_key).create(
|
||||
card=card,
|
||||
description=description,
|
||||
email=email,
|
||||
account_balance=account_balance,
|
||||
plan=plan,
|
||||
trial_end=trial_end
|
||||
)
|
||||
|
||||
# customer.id is useful to save in db
|
||||
return customer
|
||||
|
||||
|
||||
def create_charge(self, amount, currency="usd", customer=None, card=None, description=None ):
|
||||
# https://stripe.com/docs/api?lang=python#create_charge
|
||||
# customer or card required but not both
|
||||
# charge the Customer instead of the card
|
||||
# amount in cents
|
||||
charge = stripe.Charge(api_key=self.api_key).create(
|
||||
amount=int(100*amount), # in cents
|
||||
currency=currency,
|
||||
customer=customer.id,
|
||||
description=description
|
||||
)
|
||||
|
||||
return charge
|
||||
|
||||
def refund_charge(self, charge_id):
|
||||
# https://stripe.com/docs/api?lang=python#refund_charge
|
||||
ch = stripe.Charge(api_key=self.api_key).retrieve(charge_id)
|
||||
ch.refund()
|
||||
return ch
|
||||
|
||||
def list_all_charges(self, count=None, offset=None, customer=None):
|
||||
# https://stripe.com/docs/api?lang=python#list_charges
|
||||
return stripe.Charge(api_key=self.api_key).all(count=count, offset=offset, customer=customer)
|
||||
|
||||
# what to work through?
|
||||
|
||||
# can't test Transfer in test mode: "There are no transfers in test mode."
|
||||
|
||||
#pledge scenario
|
||||
# bad card -- what types of erros to handle?
|
||||
# https://stripe.com/docs/api#errors
|
||||
|
||||
# what errors are handled in the python library and how?
|
||||
#
|
||||
|
||||
# Account?
|
||||
|
||||
# https://stripe.com/docs/api#event_types
|
||||
# events of interest -- especially ones that do not directly arise immediately (synchronously) from something we do -- I think
|
||||
# especially: charge.disputed
|
||||
# I think following (charge.succeeded, charge.failed, charge.refunded) pretty much sychronous to our actions
|
||||
# customer.created, customer.updated, customer.deleted
|
||||
|
||||
# transfer
|
||||
# I expect the ones related to transfers all happen asynchronously: transfer.created, transfer.updated, transfer.failed
|
||||
|
||||
# When will the money I charge with Stripe end up in my bank account?
|
||||
# Every day, we transfer the money that you charged seven days previously?that is, you receive the money for your March 1st charges on March 8th.
|
||||
|
||||
# pending payments?
|
||||
# how to tell whether money transferred to bank account yet
|
||||
# best practices for calling Events -- not too often.
|
||||
|
||||
class PledgeScenarioTest(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
print "in setUp"
|
||||
cls._sc = StripeClient(api_key=STRIPE_SK)
|
||||
|
||||
# valid card
|
||||
card0 = card()
|
||||
cls._good_cust = cls._sc.create_customer(card=card0, description="test good customer", email="raymond.yee@gmail.com")
|
||||
|
||||
# bad card
|
||||
test_card_num_to_get_BAD_ATTACHED_CARD = ERROR_TESTING['BAD_ATTACHED_CARD'][0]
|
||||
card1 = card(number=test_card_num_to_get_BAD_ATTACHED_CARD)
|
||||
cls._cust_bad_card = cls._sc.create_customer(card=card1, description="test bad customer", email="rdhyee@gluejar.com")
|
||||
|
||||
def test_charge_good_cust(self):
|
||||
charge = self._sc.create_charge(10, customer=self._good_cust, description="$10 for good cust")
|
||||
print charge.id
|
||||
|
||||
|
||||
def test_error_creating_customer_with_declined_card(self):
|
||||
# should get a CardError upon attempt to create Customer with this card
|
||||
_card = card(number=card(ERROR_TESTING['CHARGE_DECLINE'][0]))
|
||||
self.assertRaises(stripe.CardError, self._sc.create_customer, card=_card)
|
||||
|
||||
def test_charge_bad_cust(self):
|
||||
# expect the card to be declined -- and for us to get CardError
|
||||
self.assertRaises(stripe.CardError, self._sc.create_charge, 10,
|
||||
customer = self._cust_bad_card, description="$10 for bad cust")
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
# clean up stuff we create in test
|
||||
print "in tearDown"
|
||||
cls._good_cust.delete()
|
||||
print "list of customers"
|
||||
print [(i, c.id, c.description, datetime.fromtimestamp(c.created, tz=utc), c.account_balance) for(i, c) in enumerate(cls._sc.customer.all()["data"])]
|
||||
|
||||
print "list of charges"
|
||||
print [(i, c.id, c.amount, c.currency, c.description, datetime.fromtimestamp(c.created, tz=utc), c.paid, c.fee, c.disputed, c.amount_refunded, c.failure_message, c.card.fingerprint, c.card.last4) for (i, c) in enumerate(cls._sc.charge.all()['data'])]
|
||||
|
||||
# can retrieve events since a certain time
|
||||
print "list of events", cls._sc.event.all()
|
||||
# [(i, e.id, e.type, e.created, e.pending_webhooks, e.data) for (i,e) in enumerate(s.event.all()['data'])]
|
||||
|
||||
def suite():
|
||||
|
||||
testcases = [PledgeScenarioTest]
|
||||
#testcases = []
|
||||
suites = unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(testcase) for testcase in testcases])
|
||||
#suites.addTest(LibraryThingTest('test_cache'))
|
||||
#suites.addTest(SettingsTest('test_dev_me_alignment')) # give option to test this alignment
|
||||
return suites
|
||||
|
||||
|
||||
# IPNs/webhooks: https://stripe.com/docs/webhooks
|
||||
# how to use pending_webhooks ?
|
||||
|
||||
# all events
|
||||
# https://stripe.com/docs/api?lang=python#list_events
|
||||
|
||||
if __name__ == '__main__':
|
||||
#unittest.main()
|
||||
suites = suite()
|
||||
#suites = unittest.defaultTestLoader.loadTestsFromModule(__import__('__main__'))
|
||||
unittest.TextTestRunner().run(suites)
|
|
@ -0,0 +1,119 @@
|
|||
{% extends "basepledge.html" %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}Balanced{% endblock %}
|
||||
|
||||
{% block extra_extra_head %}
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
|
||||
|
||||
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" type="text/css">
|
||||
<style type="text/css">
|
||||
[name="marketplace_eid"] {
|
||||
width: 300px;
|
||||
}
|
||||
[name^="expiration"] {
|
||||
width: 50px;
|
||||
}
|
||||
[name="security_code"] {
|
||||
width: 50px;
|
||||
}
|
||||
code { display: block; }
|
||||
pre { color: green; }
|
||||
</style>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block doccontent %}
|
||||
|
||||
<h1>Balanced Sample - Collect Credit Card Information</h1>
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
<form id="payment" method="POST">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
<label>Card Number</label>
|
||||
<input name="card_number" value="4111111111111111" autocomplete="off">
|
||||
</div>
|
||||
<div>
|
||||
<label>Expiration</label>
|
||||
<input name="expiration_month" value="1"> / <input name="expiration_year" value="2020">
|
||||
</div>
|
||||
<div>
|
||||
<label>Security Code</label>
|
||||
<input name="security_code" value="123" autocomplete="off">
|
||||
</div>
|
||||
<button>Submit Payment Data</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="result"></div>
|
||||
<script type="text/javascript" src="https://js.balancedpayments.com/v1/balanced.js"></script>
|
||||
<script type="text/javascript">
|
||||
var $j = jQuery.noConflict();
|
||||
var marketplaceUri = '{{MARKETPLACE_URI}}';
|
||||
|
||||
var debug = function (tag, content) {
|
||||
$j('<' + tag + '>' + content + '</' + tag + '>').appendTo('#result');
|
||||
};
|
||||
|
||||
try {
|
||||
balanced.init(marketplaceUri);
|
||||
} catch (e) {
|
||||
debug('code', 'You need to set the marketplaceUri variable');
|
||||
}
|
||||
|
||||
function balancedCallback(response) {
|
||||
var tag = (response.status < 300) ? 'pre' : 'code';
|
||||
debug(tag, JSON.stringify(response));
|
||||
switch (response.status) {
|
||||
case 201:
|
||||
// response.data.uri == uri of the card resource, submit to your server
|
||||
var form$ = $j('form#payment');
|
||||
var card_uri = response.data.uri;
|
||||
// insert the token into the form so it gets submitted to the server
|
||||
form$.append("<input type='hidden' name='card_uri' value='" + card_uri + "' />");
|
||||
// and submit
|
||||
form$.get(0).submit();
|
||||
case 400:
|
||||
case 403:
|
||||
// missing/malformed data - check response.error for details
|
||||
break;
|
||||
case 402:
|
||||
// we couldn't authorize the buyer's credit card - check response.error for details
|
||||
break;
|
||||
case 404:
|
||||
// your marketplace URI is incorrect
|
||||
break;
|
||||
default:
|
||||
// we did something unexpected - check response.error for details
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var tokenizeCard = function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $form = $j('form#payment');
|
||||
var cardData = {
|
||||
card_number: $form.find('[name="card_number"]').val(),
|
||||
expiration_month: $form.find('[name="expiration_month"]').val(),
|
||||
expiration_year: $form.find('[name="expiration_year"]').val(),
|
||||
security_code: $form.find('[name="security_code"]').val()
|
||||
};
|
||||
|
||||
balanced.card.create(cardData, balancedCallback);
|
||||
// prevent the form from submitting with the default action
|
||||
return false;
|
||||
};
|
||||
|
||||
$j('#payment').submit(tokenizeCard);
|
||||
|
||||
if (window.location.protocol === 'file:') {
|
||||
alert("balanced.js does not work when included in pages served over file:// URLs. Try serving this page over a webserver. Contact support@balancedpayments.com if you need assistance.");
|
||||
}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{% extends "basepledge.html" %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}Stripe{% endblock %}
|
||||
|
||||
{% block extra_extra_head %}
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
|
||||
|
||||
<link href="/static/stripe/tag.css" rel="stylesheet" type="text/css">
|
||||
<script type="text/javascript" src="/static/stripe/tag.js"></script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block doccontent %}
|
||||
Stripe Test!:
|
||||
|
||||
<span class="payment-errors"></span>
|
||||
<form action="" method="post" id="payment-form">
|
||||
{% csrf_token %}
|
||||
<payment key="{{STRIPE_PK}}"></payment>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{% extends "basepledge.html" %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}WePay{% endblock %}
|
||||
|
||||
{% block extra_extra_head %}
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
|
||||
|
||||
<script type="text/javascript" src="https://www.wepay.com/js/iframe.wepay.js"></script>{% endblock %}
|
||||
|
||||
{% block doccontent %}
|
||||
|
||||
<div id="checkout_div"></div>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
WePay.iframe_checkout("checkout_div", "{{checkout_uri}}");
|
||||
</script>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
from django.conf.urls.defaults import *
|
||||
from django.conf import settings
|
||||
from regluit.payment.views import StripeView
|
||||
|
||||
urlpatterns = patterns(
|
||||
"regluit.payment.views",
|
||||
|
@ -21,6 +22,7 @@ if settings.DEBUG:
|
|||
url(r"^testfinish", "testFinish"),
|
||||
url(r"^testrefund", "testRefund"),
|
||||
url(r"^testmodify", "testModify"),
|
||||
url(r"^stripe/test", StripeView.as_view())
|
||||
)
|
||||
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
from regluit.payment.manager import PaymentManager
|
||||
from regluit.payment.models import Transaction
|
||||
from regluit.core.models import Campaign, Wishlist
|
||||
|
||||
from regluit.payment.stripelib import STRIPE_PK
|
||||
|
||||
from regluit.payment.forms import StripePledgeForm
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import render_to_response
|
||||
|
@ -12,6 +17,9 @@ from django.views.decorators.csrf import csrf_exempt
|
|||
from django.test.utils import setup_test_environment
|
||||
from django.template import RequestContext
|
||||
|
||||
from django.views.generic.edit import FormView
|
||||
from django.views.generic.base import TemplateView
|
||||
|
||||
from unittest import TestResult
|
||||
|
||||
|
||||
|
@ -283,5 +291,25 @@ def checkStatus(request):
|
|||
def _render(request, template, template_vars={}):
|
||||
return render_to_response(template, template_vars, RequestContext(request))
|
||||
|
||||
class StripeView(FormView):
|
||||
template_name="stripe.html"
|
||||
form_class = StripePledgeForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
context = super(StripeView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
context.update({
|
||||
'STRIPE_PK':STRIPE_PK
|
||||
})
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
stripe_token = form.cleaned_data["stripe_token"]
|
||||
# e.g., tok_0C0k4jG5B2Oxox
|
||||
#
|
||||
return HttpResponse("stripe_token: {0}".format(stripe_token))
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -28,3 +28,4 @@ pycrypto
|
|||
django-maintenancemode
|
||||
django-smtp-ssl
|
||||
django-ckeditor
|
||||
stripe
|
||||
|
|
|
@ -53,4 +53,5 @@ requests==0.9.1
|
|||
selenium==2.24.0
|
||||
South==0.7.3
|
||||
ssh==1.7.13
|
||||
stripe==1.7.4
|
||||
wsgiref==0.1.2
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
payment, .payment {
|
||||
position: relative;
|
||||
display: block;
|
||||
|
||||
padding: 15px 20px;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-sizing: border-box;
|
||||
}
|
||||
|
||||
payment label, .payment label {
|
||||
display: block;
|
||||
padding: 5px 0;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
payment input, .payment input {
|
||||
padding: 5px 5px;
|
||||
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-sizing: border-box;
|
||||
}
|
||||
|
||||
payment .number input, .payment .number input {
|
||||
padding: 7px 40px 7px 7px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
payment .expiry input, payment .cvc input {
|
||||
width: 45px;
|
||||
}
|
||||
|
||||
payment .expiry em {
|
||||
display: none;
|
||||
}
|
||||
|
||||
payment .cvc,
|
||||
.payment .cvc {
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
payment .expiry,
|
||||
.payment .expiry {
|
||||
float: left;
|
||||
}
|
||||
|
||||
payment .message,
|
||||
.payment .message {
|
||||
display: block;
|
||||
}
|
|
@ -0,0 +1,405 @@
|
|||
(function() {
|
||||
var $, global, script,
|
||||
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
||||
__slice = [].slice;
|
||||
|
||||
$ = this.jQuery || this.Zepto;
|
||||
|
||||
if (!$) {
|
||||
throw 'jQuery/Zepto required';
|
||||
}
|
||||
|
||||
this.PaymentTag = (function() {
|
||||
|
||||
PaymentTag.replaceTags = function(element) {
|
||||
var _this = this;
|
||||
if (element == null) {
|
||||
element = document.body;
|
||||
}
|
||||
return $('payment, .payment-tag', element).each(function(i, tag) {
|
||||
return new _this({
|
||||
el: tag
|
||||
}).render();
|
||||
});
|
||||
};
|
||||
|
||||
PaymentTag.prototype.defaults = {
|
||||
tokenName: 'stripe_token',
|
||||
token: true,
|
||||
cvc: true
|
||||
};
|
||||
|
||||
function PaymentTag(options) {
|
||||
var _ref, _ref1;
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
this.changeCardType = __bind(this.changeCardType, this);
|
||||
|
||||
this.restrictNumeric = __bind(this.restrictNumeric, this);
|
||||
|
||||
this.formatNumber = __bind(this.formatNumber, this);
|
||||
|
||||
this.handleToken = __bind(this.handleToken, this);
|
||||
|
||||
this.submit = __bind(this.submit, this);
|
||||
|
||||
this.$el = options.el || '<payment />';
|
||||
this.$el = $(this.$el);
|
||||
options.key || (options.key = this.$el.attr('key') || this.$el.attr('data-key'));
|
||||
if ((_ref = options.cvc) == null) {
|
||||
options.cvc = !((this.$el.attr('nocvc') != null) || (this.$el.attr('data-nocvc') != null));
|
||||
}
|
||||
if ((_ref1 = options.token) == null) {
|
||||
options.token = !((this.$el.attr('notoken') != null) || (this.$el.attr('data-notoken') != null));
|
||||
}
|
||||
options.form || (options.form = this.$el.parents('form'));
|
||||
this.options = $.extend({}, this.defaults, options);
|
||||
if (this.options.key) {
|
||||
this.setKey(this.options.key);
|
||||
}
|
||||
this.setForm(this.options.form);
|
||||
this.$el.delegate('.number input', 'keydown', this.formatNumber);
|
||||
this.$el.delegate('.number input', 'keyup', this.changeCardType);
|
||||
this.$el.delegate('input[type=tel]', 'keypress', this.restrictNumeric);
|
||||
}
|
||||
|
||||
PaymentTag.prototype.render = function() {
|
||||
this.$el.html(this.constructor.view(this));
|
||||
this.$number = this.$('.number input');
|
||||
this.$cvc = this.$('.cvc input');
|
||||
this.$expiryMonth = this.$('.expiry input.expiryMonth');
|
||||
this.$expiryYear = this.$('.expiry input.expiryYear');
|
||||
this.$message = this.$('.message');
|
||||
return this;
|
||||
};
|
||||
|
||||
PaymentTag.prototype.renderToken = function(token) {
|
||||
this.$token = $('<input type="hidden">');
|
||||
this.$token.attr('name', this.options.tokenName);
|
||||
this.$token.val(token);
|
||||
return this.$el.html(this.$token);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.setForm = function($form) {
|
||||
this.$form = $($form);
|
||||
return this.$form.bind('submit.payment', this.submit);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.setKey = function(key) {
|
||||
this.key = key;
|
||||
return Stripe.setPublishableKey(this.key);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.validate = function() {
|
||||
var expiry, valid;
|
||||
valid = true;
|
||||
this.$('div').removeClass('invalid');
|
||||
this.$message.empty();
|
||||
if (!Stripe.validateCardNumber(this.$number.val())) {
|
||||
valid = false;
|
||||
this.handleError({
|
||||
code: 'invalid_number'
|
||||
});
|
||||
}
|
||||
expiry = this.expiryVal();
|
||||
if (!Stripe.validateExpiry(expiry.month, expiry.year)) {
|
||||
valid = false;
|
||||
this.handleError({
|
||||
code: 'expired_card'
|
||||
});
|
||||
}
|
||||
if (this.options.cvc && !Stripe.validateCVC(this.$cvc.val())) {
|
||||
valid = false;
|
||||
this.handleError({
|
||||
code: 'invalid_cvc'
|
||||
});
|
||||
}
|
||||
if (!valid) {
|
||||
this.$('.invalid input:first').select();
|
||||
}
|
||||
return valid;
|
||||
};
|
||||
|
||||
PaymentTag.prototype.createToken = function(callback) {
|
||||
var complete, expiry,
|
||||
_this = this;
|
||||
complete = function(status, response) {
|
||||
if (response.error) {
|
||||
return callback(response.error);
|
||||
} else {
|
||||
return callback(null, response);
|
||||
}
|
||||
};
|
||||
expiry = this.expiryVal();
|
||||
return Stripe.createToken({
|
||||
number: this.$number.val(),
|
||||
cvc: this.$cvc.val() || null,
|
||||
exp_month: expiry.month,
|
||||
exp_year: expiry.year
|
||||
}, complete);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.submit = function(e) {
|
||||
if (e != null) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e != null) {
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
if (!this.validate()) {
|
||||
return;
|
||||
}
|
||||
if (this.pending) {
|
||||
return;
|
||||
}
|
||||
this.pending = true;
|
||||
this.disableInputs();
|
||||
this.trigger('pending');
|
||||
this.$el.addClass('pending');
|
||||
return this.createToken(this.handleToken);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.handleToken = function(err, response) {
|
||||
this.enableInputs();
|
||||
this.trigger('complete');
|
||||
this.$el.removeClass('pending');
|
||||
this.pending = false;
|
||||
if (err) {
|
||||
return this.handleError(err);
|
||||
} else {
|
||||
this.trigger('success', response);
|
||||
this.$el.addClass('success');
|
||||
if (this.options.token) {
|
||||
this.renderToken(response.id);
|
||||
}
|
||||
this.$form.unbind('submit.payment', this.submit);
|
||||
return this.$form.submit();
|
||||
}
|
||||
};
|
||||
|
||||
PaymentTag.prototype.formatNumber = function(e) {
|
||||
var digit, lastDigits, value;
|
||||
digit = String.fromCharCode(e.which);
|
||||
if (!/^\d+$/.test(digit)) {
|
||||
return;
|
||||
}
|
||||
value = this.$number.val();
|
||||
if (Stripe.cardType(value) === 'American Express') {
|
||||
lastDigits = value.match(/^(\d{4}|\d{4}\s\d{6})$/);
|
||||
} else {
|
||||
lastDigits = value.match(/(?:^|\s)(\d{4})$/);
|
||||
}
|
||||
if (lastDigits) {
|
||||
return this.$number.val(value + ' ');
|
||||
}
|
||||
};
|
||||
|
||||
PaymentTag.prototype.restrictNumeric = function(e) {
|
||||
var char;
|
||||
if (e.shiftKey || e.metaKey) {
|
||||
return true;
|
||||
}
|
||||
if (e.which === 0) {
|
||||
return true;
|
||||
}
|
||||
char = String.fromCharCode(e.which);
|
||||
return !/[A-Za-z]/.test(char);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.cardTypes = {
|
||||
'Visa': 'visa',
|
||||
'American Express': 'amex',
|
||||
'MasterCard': 'mastercard',
|
||||
'Discover': 'discover',
|
||||
'Unknown': 'unknown'
|
||||
};
|
||||
|
||||
PaymentTag.prototype.changeCardType = function(e) {
|
||||
var map, name, type, _ref;
|
||||
type = Stripe.cardType(this.$number.val());
|
||||
if (!this.$number.hasClass(type)) {
|
||||
_ref = this.cardTypes;
|
||||
for (name in _ref) {
|
||||
map = _ref[name];
|
||||
this.$number.removeClass(map);
|
||||
}
|
||||
return this.$number.addClass(this.cardTypes[type]);
|
||||
}
|
||||
};
|
||||
|
||||
PaymentTag.prototype.handleError = function(err) {
|
||||
if (err.message) {
|
||||
this.$message.text(err.message);
|
||||
}
|
||||
switch (err.code) {
|
||||
case 'card_declined':
|
||||
this.invalidInput(this.$number);
|
||||
break;
|
||||
case 'invalid_number':
|
||||
case 'incorrect_number':
|
||||
this.invalidInput(this.$number);
|
||||
break;
|
||||
case 'invalid_expiry_month':
|
||||
this.invalidInput(this.$expiryMonth);
|
||||
break;
|
||||
case 'invalid_expiry_year':
|
||||
case 'expired_card':
|
||||
this.invalidInput(this.$expiryYear);
|
||||
break;
|
||||
case 'invalid_cvc':
|
||||
this.invalidInput(this.$cvc);
|
||||
}
|
||||
this.$('label.invalid:first input').select();
|
||||
this.trigger('error', err);
|
||||
return typeof console !== "undefined" && console !== null ? console.error('Stripe error:', err) : void 0;
|
||||
};
|
||||
|
||||
PaymentTag.prototype.invalidInput = function(input) {
|
||||
input.parent().addClass('invalid');
|
||||
return this.trigger('invalid', [input.attr('name'), input]);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.expiryVal = function() {
|
||||
var month, prefix, trim, year;
|
||||
trim = function(s) {
|
||||
return s.replace(/^\s+|\s+$/g, '');
|
||||
};
|
||||
month = trim(this.$expiryMonth.val());
|
||||
year = trim(this.$expiryYear.val());
|
||||
if (year.length === 2) {
|
||||
prefix = (new Date).getFullYear();
|
||||
prefix = prefix.toString().slice(0, 2);
|
||||
year = prefix + year;
|
||||
}
|
||||
return {
|
||||
month: month,
|
||||
year: year
|
||||
};
|
||||
};
|
||||
|
||||
PaymentTag.prototype.enableInputs = function() {
|
||||
var $elements;
|
||||
$elements = this.$el.add(this.$form).find(':input');
|
||||
return $elements.each(function() {
|
||||
var $item, _ref;
|
||||
$item = $(this);
|
||||
return $elements.attr('disabled', (_ref = $item.data('olddisabled')) != null ? _ref : false);
|
||||
});
|
||||
};
|
||||
|
||||
PaymentTag.prototype.disableInputs = function() {
|
||||
var $elements;
|
||||
$elements = this.$el.add(this.$form).find(':input');
|
||||
return $elements.each(function() {
|
||||
var $item;
|
||||
$item = $(this);
|
||||
$item.data('olddisabled', $item.attr('disabled'));
|
||||
return $item.attr('disabled', true);
|
||||
});
|
||||
};
|
||||
|
||||
PaymentTag.prototype.trigger = function() {
|
||||
var data, event, _ref;
|
||||
event = arguments[0], data = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
|
||||
return (_ref = this.$el).trigger.apply(_ref, ["" + event + ".payment"].concat(__slice.call(data)));
|
||||
};
|
||||
|
||||
PaymentTag.prototype.$ = function(sel) {
|
||||
return $(sel, this.$el);
|
||||
};
|
||||
|
||||
return PaymentTag;
|
||||
|
||||
})();
|
||||
|
||||
document.createElement('payment');
|
||||
|
||||
if (typeof module !== "undefined" && module !== null) {
|
||||
module.exports = PaymentTag;
|
||||
}
|
||||
|
||||
global = this;
|
||||
|
||||
if (global.Stripe) {
|
||||
$(function() {
|
||||
return typeof PaymentTag.replaceTags === "function" ? PaymentTag.replaceTags() : void 0;
|
||||
});
|
||||
} else {
|
||||
script = document.createElement('script');
|
||||
script.onload = script.onreadystatechange = function() {
|
||||
if (!global.Stripe) {
|
||||
return;
|
||||
}
|
||||
if (script.done) {
|
||||
return;
|
||||
}
|
||||
script.done = true;
|
||||
return typeof PaymentTag.replaceTags === "function" ? PaymentTag.replaceTags() : void 0;
|
||||
};
|
||||
script.src = 'https://js.stripe.com/v1/';
|
||||
$(function() {
|
||||
var sibling;
|
||||
sibling = document.getElementsByTagName('script')[0];
|
||||
return sibling != null ? sibling.parentNode.insertBefore(script, sibling) : void 0;
|
||||
});
|
||||
}
|
||||
|
||||
}).call(this);
|
||||
(function() {
|
||||
this.PaymentTag || (this.PaymentTag = {});
|
||||
this.PaymentTag["view"] = function(__obj) {
|
||||
if (!__obj) __obj = {};
|
||||
var __out = [], __capture = function(callback) {
|
||||
var out = __out, result;
|
||||
__out = [];
|
||||
callback.call(this);
|
||||
result = __out.join('');
|
||||
__out = out;
|
||||
return __safe(result);
|
||||
}, __sanitize = function(value) {
|
||||
if (value && value.ecoSafe) {
|
||||
return value;
|
||||
} else if (typeof value !== 'undefined' && value != null) {
|
||||
return __escape(value);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}, __safe, __objSafe = __obj.safe, __escape = __obj.escape;
|
||||
__safe = __obj.safe = function(value) {
|
||||
if (value && value.ecoSafe) {
|
||||
return value;
|
||||
} else {
|
||||
if (!(typeof value !== 'undefined' && value != null)) value = '';
|
||||
var result = new String(value);
|
||||
result.ecoSafe = true;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
if (!__escape) {
|
||||
__escape = __obj.escape = function(value) {
|
||||
return ('' + value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
};
|
||||
}
|
||||
(function() {
|
||||
(function() {
|
||||
|
||||
__out.push('<span class="message"></span>\n\n<div class="number">\n <label for="paymentNumber">Card number</label>\n\n <input type="tel" id="paymentNumber" placeholder="4242 4242 4242 4242" autofocus required>\n</div>\n\n<div class="expiry">\n <label for="paymentExpiryMonth">Expiry date <em>(mm/yy)</em></label>\n\n <input class="expiryMonth" type="tel" id="paymentExpiryMonth" placeholder="mm" required>\n <input class="expiryYear" type="tel" id="paymentExpiryYear" placeholder="yy" required>\n</div>\n\n');
|
||||
|
||||
if (this.options.cvc) {
|
||||
__out.push('\n <div class="cvc">\n <label for="paymentCVC">Security code</label>\n <input type="tel" id="paymentCVC" placeholder="123" maxlength="4" required>\n </div>\n');
|
||||
}
|
||||
|
||||
__out.push('\n');
|
||||
|
||||
}).call(this);
|
||||
|
||||
}).call(__obj);
|
||||
__obj.safe = __objSafe, __obj.escape = __escape;
|
||||
return __out.join('');
|
||||
};
|
||||
}).call(this);
|
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
|
@ -0,0 +1,152 @@
|
|||
payment, .payment {
|
||||
position: relative;
|
||||
display: block;
|
||||
|
||||
border-radius: 5px;
|
||||
padding: 15px 20px;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-sizing: border-box;
|
||||
|
||||
font-size: 12px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial Geneva, sans-serif;
|
||||
|
||||
background: #FFF;
|
||||
background-image: -o-linear-gradient(#FFF, #F9FAFA);
|
||||
background-image: -ms-linear-gradient(#FFF, #F9FAFA);
|
||||
background-image: -moz-linear-gradient(#FFF, #F9FAFA);
|
||||
background-image: -webkit-linear-gradient(#FEFEFE, #F9FAFA);
|
||||
background-image: linear-gradient(#FFF, #F9FAFA);
|
||||
|
||||
-moz-box-shadow: 0 0 2px rgba(80,84,92,0.3), 0 1px 1px rgba(80,84,92,0.5);
|
||||
-webkit-box-shadow: 0 0 2px rgba(80, 84, 92, 0.3), 0 1px 1px rgba(80, 84, 92, 0.5);
|
||||
-ms-box-shadow: 0 0 2px rgba(80, 84, 92, 0.3), 0 1px 1px rgba(80, 84, 92, 0.5);
|
||||
box-shadow: 0 0 2px rgba(80, 84, 92, 0.3), 0 1px 1px rgba(80, 84, 92, 0.5);
|
||||
}
|
||||
|
||||
payment ::-webkit-input-placeholder,
|
||||
.payment ::-webkit-input-placeholder {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
payment label, .payment label {
|
||||
display: block;
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
padding: 5px 0;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0 1px 0 #FFF;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
payment input, .payment input {
|
||||
font-size: 13px;
|
||||
padding: 5px 5px;
|
||||
border: 1px solid #BBB;
|
||||
border-top-color: #999;
|
||||
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1);
|
||||
border-radius: 3px;
|
||||
|
||||
-webkit-transition: -webkit-box-shadow 0.1s ease-in-out;
|
||||
-moz-transition: -moz-box-shadow 0.1s ease-in-out;
|
||||
transition: -moz-box-shadow 0.1s ease-in-out;
|
||||
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-sizing: border-box;
|
||||
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial Geneva, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
payment input:focus, .payment input:focus {
|
||||
border: 1px solid #5695DB;
|
||||
outline: none;
|
||||
-webkit-box-shadow: inset 0 1px 2px #DDD, 0px 0 5px #5695DB;
|
||||
-moz-box-shadow: 0 0 5px #5695db;
|
||||
box-shadow: inset 0 1px 2px #DDD, 0px 0 5px #5695DB;
|
||||
}
|
||||
|
||||
payment .invalid input, .payment .invalid input {
|
||||
outline: none;
|
||||
border-color: rgba(255, 0, 0, 0.5);
|
||||
-moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.20), 0 1px 5px 0 rgba(255, 0, 0, 0.4);
|
||||
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.20), 0 1px 5px 0 rgba(255, 0, 0, 0.4);
|
||||
-ms-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.20), 0 1px 5px 0 rgba(255, 0, 0, 0.4);
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.20), 0 1px 5px 0 rgba(255, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
payment input:disabled, .payment input:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
payment .number,
|
||||
.payment .number {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
payment .number input, .payment .number input {
|
||||
padding: 7px 40px 7px 7px;
|
||||
background: #FFF url(generic.png) 98.5% 20% no-repeat;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
payment .number input.visa, .payment .number input.visa {
|
||||
background-image: url(visa.png);
|
||||
}
|
||||
|
||||
payment .number input.mastercard, .payment .number input.mastercard {
|
||||
background-image: url(mastercard.png);
|
||||
}
|
||||
|
||||
payment .number input.discover, .payment .number input.discover {
|
||||
background-image: url(discover.png);
|
||||
}
|
||||
|
||||
payment .number input.amex, .payment .number input.amex {
|
||||
background-image: url(amex.png);
|
||||
}
|
||||
|
||||
payment .expiry input, payment .cvc input {
|
||||
width: 45px;
|
||||
}
|
||||
|
||||
payment .expiry em {
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
display: none;
|
||||
}
|
||||
|
||||
payment .cvc,
|
||||
.payment .cvc {
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
payment .expiry,
|
||||
.payment .expiry {
|
||||
float: left;
|
||||
}
|
||||
|
||||
payment .message,
|
||||
.payment .message {
|
||||
display: block;
|
||||
}
|
||||
|
||||
payment.pending,
|
||||
.payment.pending,
|
||||
payment.success,
|
||||
.payment.success {
|
||||
background: #FFF url(spinner.gif) center center no-repeat;
|
||||
min-height: 130px;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
Loading…
Reference in New Issue