mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-01-26 07:19:25 -05:00
Stripe gold subscriptions.
This commit is contained in:
@@ -68,6 +68,8 @@ PAYPAL_BUTTONID_CREDDITS_BYYEAR =
|
||||
STRIPE_WEBHOOK_SECRET =
|
||||
STRIPE_PUBLIC_KEY =
|
||||
STRIPE_SECRET_KEY =
|
||||
STRIPE_MONTHLY_GOLD_PLAN =
|
||||
STRIPE_YEARLY_GOLD_PLAN =
|
||||
|
||||
COINBASE_WEBHOOK_SECRET =
|
||||
COINBASE_BUTTONID_ONETIME_1MO =
|
||||
|
||||
@@ -246,6 +246,7 @@ def make_map():
|
||||
mc('/gold', controller='forms', action="gold")
|
||||
mc('/gold/creditgild/:passthrough', controller='forms', action='creditgild')
|
||||
mc('/gold/thanks', controller='front', action='goldthanks')
|
||||
mc('/gold/subscription', controller='forms', action='subscription')
|
||||
|
||||
mc('/password', controller='forms', action="password")
|
||||
mc('/:action', controller='front',
|
||||
@@ -311,6 +312,10 @@ def make_map():
|
||||
mc('/api/distinguish/:how', controller='api', action="distinguish")
|
||||
mc('/api/spendcreddits', controller='ipn', action="spendcreddits")
|
||||
mc('/api/stripecharge/gold', controller='stripe', action='goldcharge')
|
||||
mc('/api/modify_subscription', controller='stripe',
|
||||
action='modify_subscription')
|
||||
mc('/api/cancel_subscription', controller='stripe',
|
||||
action='cancel_subscription')
|
||||
mc('/api/stripewebhook/gold/:secret', controller='stripe',
|
||||
action='goldwebhook')
|
||||
mc('/api/coinbasewebhook/gold/:secret', controller='coinbase',
|
||||
|
||||
@@ -1495,3 +1495,10 @@ class FormsController(RedditController):
|
||||
giftmessage, passthrough,
|
||||
comment)
|
||||
).render()
|
||||
|
||||
@validate(VUser())
|
||||
def GET_subscription(self):
|
||||
user = c.user
|
||||
content = GoldSubscription(user)
|
||||
return BoringPage(_("reddit gold subscription"), show_sidebar=False,
|
||||
content=content).render()
|
||||
|
||||
@@ -39,15 +39,19 @@ from r2.lib.validator import (
|
||||
nop,
|
||||
textresponse,
|
||||
validatedForm,
|
||||
VByName,
|
||||
VFloat,
|
||||
VInt,
|
||||
VLength,
|
||||
VModhash,
|
||||
VOneOf,
|
||||
VPrintable,
|
||||
VUser,
|
||||
)
|
||||
from r2.models import (
|
||||
Account,
|
||||
account_by_payingid,
|
||||
account_from_stripe_customer_id,
|
||||
accountid_from_paypalsubscription,
|
||||
admintools,
|
||||
append_random_bottlecap_phrase,
|
||||
@@ -63,6 +67,7 @@ from r2.models import (
|
||||
update_gold_transaction,
|
||||
)
|
||||
|
||||
stripe.api_key = g.STRIPE_SECRET_KEY
|
||||
|
||||
def generate_blob(data):
|
||||
passthrough = randstr(15)
|
||||
@@ -602,7 +607,13 @@ class GoldPaymentController(RedditController):
|
||||
msg = _('Your reddit gold payment has failed, contact '
|
||||
'%(gold_email)s for details') % {'gold_email':
|
||||
g.goldthanks_email}
|
||||
# probably want to update gold_table here
|
||||
elif event_type == 'failed_subscription':
|
||||
subject = _('reddit gold subscription payment failed')
|
||||
msg = _('Your reddit gold subscription payment has failed. '
|
||||
'Please go to http://www.reddit.com/subscription to '
|
||||
'make sure your information is correct, or contact '
|
||||
'%(gold_email)s for details') % {'gold_email':
|
||||
g.goldthanks_email}
|
||||
elif event_type == 'refunded':
|
||||
if not (existing and existing.status == 'processed'):
|
||||
return
|
||||
@@ -622,6 +633,26 @@ class GoldPaymentController(RedditController):
|
||||
return
|
||||
send_system_message(buyer, subject, msg)
|
||||
|
||||
def handle_stripe_error(fn):
|
||||
def wrapper(cls, form, *a, **kw):
|
||||
try:
|
||||
return fn(cls, form, *a, **kw)
|
||||
except stripe.CardError as e:
|
||||
form.set_html('.status',
|
||||
_('error: %(error)s') % {'error': e.message})
|
||||
form.find('.stripe-submit').removeAttr('disabled').end()
|
||||
except stripe.InvalidRequestError as e:
|
||||
form.set_html('.status', _('invalid request'))
|
||||
except stripe.APIConnectionError as e:
|
||||
form.set_html('.status', _('api error'))
|
||||
except stripe.AuthenticationError as e:
|
||||
form.set_html('.status', _('connection error'))
|
||||
except stripe.StripeError as e:
|
||||
form.set_html('.status', _('error'))
|
||||
g.log.error('stripe error: %s' % e)
|
||||
return wrapper
|
||||
|
||||
|
||||
class StripeController(GoldPaymentController):
|
||||
name = 'stripe'
|
||||
webhook_secret = g.STRIPE_WEBHOOK_SECRET
|
||||
@@ -631,9 +662,22 @@ class StripeController(GoldPaymentController):
|
||||
'charge.refunded': 'refunded',
|
||||
'customer.created': 'noop',
|
||||
'customer.card.created': 'noop',
|
||||
'customer.card.deleted': 'noop',
|
||||
'transfer.created': 'noop',
|
||||
'transfer.paid': 'noop',
|
||||
'balance.available': 'noop',
|
||||
'invoice.created': 'noop',
|
||||
'invoice.updated': 'noop',
|
||||
'invoice.payment_succeeded': 'noop',
|
||||
'invoice.payment_failed': 'failed_subscription',
|
||||
'invoiceitem.deleted': 'noop',
|
||||
'customer.subscription.created': 'noop',
|
||||
'customer.deleted': 'noop',
|
||||
'customer.updated': 'noop',
|
||||
'customer.subscription.deleted': 'noop',
|
||||
'customer.subscription.trial_will_end': 'noop',
|
||||
'customer.subscription.updated': 'noop',
|
||||
'dummy': 'noop',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -641,6 +685,25 @@ class StripeController(GoldPaymentController):
|
||||
event_dict = json.loads(request.body)
|
||||
event = stripe.Event.construct_from(event_dict, g.STRIPE_SECRET_KEY)
|
||||
status = event.type
|
||||
|
||||
if status == 'invoice.created':
|
||||
# sent 1 hr before a subscription is charged
|
||||
invoice = event.data.object
|
||||
customer_id = invoice.customer
|
||||
account = account_from_stripe_customer_id(customer_id)
|
||||
if not account or (account and account._banned):
|
||||
# there's no associated account - delete the subscription
|
||||
# to cancel the charge
|
||||
g.log.error('no account for stripe invoice: %s', invoice)
|
||||
customer = stripe.Customer.retrieve(customer_id)
|
||||
customer.delete()
|
||||
elif status == 'invoice.payment_failed':
|
||||
invoice = event.data.object
|
||||
customer_id = invoice.customer
|
||||
buyer = account_from_stripe_customer_id(customer_id)
|
||||
webhook = Webhook(subscr_id=customer_id, buyer=buyer)
|
||||
return status, webhook
|
||||
|
||||
event_type = cls.event_type_mappings.get(status)
|
||||
if not event_type:
|
||||
raise ValueError('Stripe: unrecognized status %s' % status)
|
||||
@@ -649,26 +712,104 @@ class StripeController(GoldPaymentController):
|
||||
|
||||
charge = event.data.object
|
||||
description = charge.description
|
||||
invoice_id = charge.invoice
|
||||
transaction_id = 'S%s' % charge.id
|
||||
pennies = charge.amount
|
||||
months, days = months_and_days_from_pennies(pennies)
|
||||
|
||||
try:
|
||||
passthrough, buyer_name = description.split('-', 1)
|
||||
except ValueError:
|
||||
g.log.error('stripe_error on charge: %s', charge)
|
||||
raise
|
||||
|
||||
webhook = Webhook(passthrough=passthrough,
|
||||
transaction_id=transaction_id, pennies=pennies, months=months)
|
||||
return status, webhook
|
||||
if status == 'charge.failed' and invoice_id:
|
||||
# we'll get an additional failure notification event of
|
||||
# "invoice.payment_failed", don't double notify
|
||||
return 'dummy', None
|
||||
elif invoice_id:
|
||||
# subscription charge - special handling
|
||||
customer_id = charge.customer
|
||||
buyer = account_from_stripe_customer_id(customer_id)
|
||||
if not buyer:
|
||||
raise ValueError('no buyer for stripe charge: %s' % charge.id)
|
||||
webhook = Webhook(transaction_id=transaction_id,
|
||||
subscr_id=customer_id, pennies=pennies,
|
||||
months=months, goldtype='autorenew',
|
||||
buyer=buyer)
|
||||
return status, webhook
|
||||
else:
|
||||
try:
|
||||
passthrough, buyer_name = description.split('-', 1)
|
||||
except ValueError:
|
||||
g.log.error('stripe_error on charge: %s', charge)
|
||||
raise
|
||||
|
||||
webhook = Webhook(passthrough=passthrough,
|
||||
transaction_id=transaction_id, pennies=pennies, months=months)
|
||||
return status, webhook
|
||||
|
||||
@classmethod
|
||||
@handle_stripe_error
|
||||
def create_customer(cls, form, token, plan=None):
|
||||
description = c.user.name
|
||||
customer = stripe.Customer.create(card=token, description=description,
|
||||
plan=plan)
|
||||
|
||||
if (customer['active_card']['address_line1_check'] == 'fail' or
|
||||
customer['active_card']['address_zip_check'] == 'fail'):
|
||||
form.set_html('.status',
|
||||
_('error: address verification failed'))
|
||||
form.find('.stripe-submit').removeAttr('disabled').end()
|
||||
return None
|
||||
elif customer['active_card']['cvc_check'] == 'fail':
|
||||
form.set_html('.status', _('error: cvc check failed'))
|
||||
form.find('.stripe-submit').removeAttr('disabled').end()
|
||||
return None
|
||||
else:
|
||||
return customer
|
||||
|
||||
@classmethod
|
||||
@handle_stripe_error
|
||||
def charge_customer(cls, form, customer, pennies, passthrough):
|
||||
charge = stripe.Charge.create(
|
||||
amount=pennies,
|
||||
currency="usd",
|
||||
customer=customer['id'],
|
||||
description='%s-%s' % (passthrough, c.user.name)
|
||||
)
|
||||
return charge
|
||||
|
||||
@classmethod
|
||||
@handle_stripe_error
|
||||
def set_creditcard(cls, form, user, token):
|
||||
if not getattr(user, 'stripe_customer_id', None):
|
||||
return
|
||||
|
||||
customer = stripe.Customer.retrieve(user.stripe_customer_id)
|
||||
customer.card = token
|
||||
customer.save()
|
||||
return customer
|
||||
|
||||
@classmethod
|
||||
@handle_stripe_error
|
||||
def cancel_subscription(user):
|
||||
if not getattr(user, 'stripe_customer_id', None):
|
||||
return
|
||||
|
||||
customer = stripe.Customer.retrieve(user.stripe_customer_id)
|
||||
customer.delete()
|
||||
|
||||
user.stripe_customer_id = None
|
||||
user._commit()
|
||||
subject = _('your gold subscription has been cancelled')
|
||||
message = _('if you have any questions please email %(email)s')
|
||||
message %= {'email': g.goldthanks_email}
|
||||
send_system_message(user, subject, message)
|
||||
return customer
|
||||
|
||||
@validatedForm(VUser(),
|
||||
token=nop('stripeToken'),
|
||||
passthrough=VPrintable("passthrough", max_length=50),
|
||||
pennies=VInt('pennies'),
|
||||
months=VInt("months"))
|
||||
def POST_goldcharge(self, form, jquery, token, passthrough, pennies, months):
|
||||
months=VInt("months"),
|
||||
period=VOneOf("period", ("monthly", "yearly")))
|
||||
def POST_goldcharge(self, form, jquery, token, passthrough, pennies, months,
|
||||
period):
|
||||
"""
|
||||
Submit charge to stripe.
|
||||
|
||||
@@ -687,58 +828,63 @@ class StripeController(GoldPaymentController):
|
||||
g.log.debug('POST_goldcharge: %s' % e.message)
|
||||
return
|
||||
|
||||
penny_months, days = months_and_days_from_pennies(pennies)
|
||||
if not months or months != penny_months:
|
||||
form.set_html('.status', _('stop trying to trick the form'))
|
||||
if period:
|
||||
plan_id = (g.STRIPE_MONTHLY_GOLD_PLAN if period == 'monthly'
|
||||
else g.STRIPE_YEARLY_GOLD_PLAN)
|
||||
else:
|
||||
plan_id = None
|
||||
penny_months, days = months_and_days_from_pennies(pennies)
|
||||
if not months or months != penny_months:
|
||||
form.set_html('.status', _('stop trying to trick the form'))
|
||||
return
|
||||
|
||||
customer = self.create_customer(form, token, plan=plan_id)
|
||||
if not customer:
|
||||
return
|
||||
|
||||
stripe.api_key = g.STRIPE_SECRET_KEY
|
||||
if period:
|
||||
c.user.stripe_customer_id = customer.id
|
||||
c.user._commit()
|
||||
|
||||
try:
|
||||
customer = stripe.Customer.create(card=token)
|
||||
|
||||
if (customer['active_card']['address_line1_check'] == 'fail' or
|
||||
customer['active_card']['address_zip_check'] == 'fail'):
|
||||
form.set_html('.status',
|
||||
_('error: address verification failed'))
|
||||
form.find('.stripe-submit').removeAttr('disabled').end()
|
||||
return
|
||||
|
||||
if customer['active_card']['cvc_check'] == 'fail':
|
||||
form.set_html('.status', _('error: cvc check failed'))
|
||||
form.find('.stripe-submit').removeAttr('disabled').end()
|
||||
return
|
||||
|
||||
charge = stripe.Charge.create(
|
||||
amount=pennies,
|
||||
currency="usd",
|
||||
customer=customer['id'],
|
||||
description='%s-%s' % (passthrough, c.user.name)
|
||||
)
|
||||
except stripe.CardError as e:
|
||||
form.set_html('.status', 'error: %s' % e.message)
|
||||
form.find('.stripe-submit').removeAttr('disabled').end()
|
||||
except stripe.InvalidRequestError as e:
|
||||
form.set_html('.status', _('invalid request'))
|
||||
except stripe.APIConnectionError as e:
|
||||
form.set_html('.status', _('api error'))
|
||||
except stripe.AuthenticationError as e:
|
||||
form.set_html('.status', _('connection error'))
|
||||
except stripe.StripeError as e:
|
||||
form.set_html('.status', _('error'))
|
||||
g.log.error('stripe error: %s' % e)
|
||||
status = _('subscription created')
|
||||
subject = _('reddit gold subscription')
|
||||
body = _('Your subscription is being processed and reddit gold '
|
||||
'will be delivered shortly.')
|
||||
else:
|
||||
form.set_html('.status', _('payment submitted'))
|
||||
charge = self.charge_customer(form, customer, pennies, passthrough)
|
||||
if not charge:
|
||||
return
|
||||
|
||||
# webhook usually sends near instantly, send a message in case
|
||||
status = _('payment submitted')
|
||||
subject = _('reddit gold payment')
|
||||
msg = _('Your payment is being processed and reddit gold will be '
|
||||
'delivered shortly.')
|
||||
msg = append_random_bottlecap_phrase(msg)
|
||||
body = _('Your payment is being processed and reddit gold '
|
||||
'will be delivered shortly.')
|
||||
|
||||
send_system_message(c.user, subject, msg,
|
||||
distinguished='gold-auto')
|
||||
form.set_html('.status', status)
|
||||
body = append_random_bottlecap_phrase(body)
|
||||
send_system_message(c.user, subject, body, distinguished='gold-auto')
|
||||
|
||||
@validatedForm(VUser(),
|
||||
VModhash(),
|
||||
token=nop('stripeToken'))
|
||||
def POST_modify_subscription(self, form, jquery, token):
|
||||
customer = self.set_creditcard(form, c.user, token)
|
||||
if not customer:
|
||||
return
|
||||
|
||||
form.set_html('.status', _('your payment details have been updated'))
|
||||
|
||||
@validatedForm(VUser(),
|
||||
VModhash(),
|
||||
user=VByName('user'))
|
||||
def POST_cancel_subscription(self, form, jquery, user):
|
||||
if user != c.user and not c.user_is_admin:
|
||||
abort(403, "Forbidden")
|
||||
customer = self.cancel_subscription(user)
|
||||
if not customer:
|
||||
return
|
||||
|
||||
form.set_html(".status", _("your subscription has been cancelled"))
|
||||
|
||||
class CoinbaseController(GoldPaymentController):
|
||||
name = 'coinbase'
|
||||
|
||||
@@ -36,6 +36,7 @@ from r2.models.gold import (
|
||||
days_to_pennies,
|
||||
gold_goal_on,
|
||||
gold_revenue_on,
|
||||
get_subscription_details,
|
||||
TIMEZONE as GOLD_TIMEZONE,
|
||||
)
|
||||
from r2.models.promo import (
|
||||
@@ -88,6 +89,7 @@ from r2.lib.utils import Storage
|
||||
from r2.lib.utils import precise_format_timedelta
|
||||
|
||||
from babel.numbers import format_currency
|
||||
from babel.dates import format_date
|
||||
from collections import defaultdict
|
||||
import csv
|
||||
import cStringIO
|
||||
@@ -1703,6 +1705,8 @@ class ProfileBar(Templated):
|
||||
|
||||
if hasattr(user, "gold_subscr_id"):
|
||||
self.gold_subscr_id = user.gold_subscr_id
|
||||
if hasattr(user, "stripe_customer_id"):
|
||||
self.stripe_customer_id = user.stripe_customer_id
|
||||
|
||||
if ((user._id == c.user._id or c.user_is_admin) and
|
||||
user.gold_creddits > 0):
|
||||
@@ -2171,7 +2175,7 @@ class GoldPayment(Templated):
|
||||
paypal_buttonid = g.PAYPAL_BUTTONID_AUTORENEW_BYYEAR
|
||||
|
||||
quantity = None
|
||||
stripe_key = None
|
||||
stripe_key = g.STRIPE_PUBLIC_KEY
|
||||
coinbase_button_id = None
|
||||
|
||||
elif goldtype == "onetime":
|
||||
@@ -2250,6 +2254,42 @@ class GoldPayment(Templated):
|
||||
coinbase_button_id=coinbase_button_id)
|
||||
|
||||
|
||||
class GoldSubscription(Templated):
|
||||
def __init__(self, user):
|
||||
if user.stripe_customer_id:
|
||||
details = get_subscription_details(user)
|
||||
else:
|
||||
details = None
|
||||
|
||||
if details:
|
||||
self.has_stripe_subscription = True
|
||||
date = details['next_charge_date']
|
||||
next_charge_date = format_date(date, format="short",
|
||||
locale=c.locale)
|
||||
credit_card_last4 = details['credit_card_last4']
|
||||
amount = format_currency(details['pennies']/100, 'USD',
|
||||
locale=c.locale)
|
||||
text = _("you have a credit card gold subscription. your card "
|
||||
"(ending in %(last4)s) will be charged %(amount)s on "
|
||||
"%(date)s.")
|
||||
self.text = text % dict(last4=credit_card_last4,
|
||||
amount=amount,
|
||||
date=next_charge_date)
|
||||
self.user_fullname = user._fullname
|
||||
else:
|
||||
self.has_stripe_subscription = False
|
||||
|
||||
paypal_subscr_id = getattr(user, 'gold_subscr_id', None)
|
||||
if paypal_subscr_id:
|
||||
self.has_paypal_subscription = True
|
||||
self.paypal_subscr_id = paypal_subscr_id
|
||||
self.paypal_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_subscr-find&alias=%s" % g.goldthanks_email
|
||||
else:
|
||||
self.has_paypal_subscription = False
|
||||
|
||||
self.stripe_key = g.STRIPE_PUBLIC_KEY
|
||||
Templated.__init__(self)
|
||||
|
||||
class CreditGild(Templated):
|
||||
"""Page for credit card payments for comment gilding."""
|
||||
pass
|
||||
|
||||
@@ -40,10 +40,13 @@ from random import choice
|
||||
from time import time
|
||||
|
||||
from r2.lib.db.tdb_cassandra import NotFound
|
||||
from r2.models import Account
|
||||
from r2.models.subreddit import Frontpage
|
||||
from r2.models.wiki import WikiPage
|
||||
from r2.lib.memoize import memoize
|
||||
|
||||
import stripe
|
||||
|
||||
gold_bonus_cutoff = datetime(2010,7,27,0,0,0,0,g.tz)
|
||||
gold_static_goal_cutoff = datetime(2013, 11, 7, tzinfo=g.display_tz)
|
||||
|
||||
@@ -307,3 +310,36 @@ def gold_goal_on(date):
|
||||
|
||||
return round(goal, 0)
|
||||
|
||||
|
||||
def account_from_stripe_customer_id(stripe_customer_id):
|
||||
q = Account._query(Account.c.stripe_customer_id == stripe_customer_id,
|
||||
Account.c._spam == (True, False), data=True)
|
||||
return next(iter(q), None)
|
||||
|
||||
|
||||
@memoize("subscription-details", time=60)
|
||||
def _get_subscription_details(stripe_customer_id):
|
||||
stripe.api_key = g.STRIPE_SECRET_KEY
|
||||
customer = stripe.Customer.retrieve(stripe_customer_id)
|
||||
|
||||
if getattr(customer, 'deleted', False):
|
||||
return {}
|
||||
|
||||
subscription = customer.subscription
|
||||
card = customer.active_card
|
||||
end = datetime.fromtimestamp(subscription.current_period_end).date()
|
||||
last4 = card.last4
|
||||
pennies = subscription.plan.amount
|
||||
|
||||
return {
|
||||
'next_charge_date': end,
|
||||
'credit_card_last4': last4,
|
||||
'pennies': pennies,
|
||||
}
|
||||
|
||||
|
||||
def get_subscription_details(user):
|
||||
if not getattr(user, 'stripe_customer_id', None):
|
||||
return
|
||||
|
||||
return _get_subscription_details(user.stripe_customer_id)
|
||||
|
||||
@@ -6324,6 +6324,17 @@ body:not(.gold) .allminus-link {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.gold-form {
|
||||
.credit-card-input {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.stripe-submit {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.gold-payment form {
|
||||
display: inline;
|
||||
}
|
||||
@@ -7548,7 +7559,11 @@ body.gold .buttons li.comment-save-button { display: inline; }
|
||||
white-space:nowrap;
|
||||
font-size:smaller;
|
||||
}
|
||||
#stripe-payment .credit-card-amount { text-align: left; }
|
||||
#stripe-payment {
|
||||
.credit-card-amount, .credit-card-interval {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
#stripe-payment th label { display:inline; }
|
||||
#stripe-payment td input {
|
||||
font-size:small;
|
||||
@@ -7574,6 +7589,34 @@ body.gold .buttons li.comment-save-button { display: inline; }
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.gold-subscription {
|
||||
font-size: small;
|
||||
padding: 2px;
|
||||
|
||||
div.buttons {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.cancel-button, .edit-button {
|
||||
margin: 5px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.status, .error {
|
||||
font-size: small;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.roundfield {
|
||||
background-color: #fffdd7;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#stripe-cancel {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.permissions {
|
||||
display: inline-block;
|
||||
font-size: small;
|
||||
|
||||
@@ -12,7 +12,13 @@ r.gold = {
|
||||
$("#stripe-payment").show()
|
||||
})
|
||||
|
||||
$('.stripe-submit').on('click', this.makeStripeToken)
|
||||
$('#stripe-payment.charge .stripe-submit').on('click', function() {
|
||||
r.gold.tokenThenPost('stripecharge/gold')
|
||||
})
|
||||
|
||||
$('#stripe-payment.modify .stripe-submit').on('click', function() {
|
||||
r.gold.tokenThenPost('modify_subscription')
|
||||
})
|
||||
},
|
||||
|
||||
_toggleCommentGoldForm: function (e) {
|
||||
@@ -114,7 +120,25 @@ r.gold = {
|
||||
comment.children('.entry').find('.give-gold').parent().remove()
|
||||
},
|
||||
|
||||
makeStripeToken: function () {
|
||||
tokenThenPost: function (dest) {
|
||||
var postOnSuccess = function (status_code, response) {
|
||||
var form = $('#stripe-payment'),
|
||||
submit = form.find('.stripe-submit'),
|
||||
status = form.find('.status'),
|
||||
token = form.find('[name="stripeToken"]')
|
||||
|
||||
if (response.error) {
|
||||
submit.removeAttr('disabled')
|
||||
status.html(response.error.message)
|
||||
} else {
|
||||
token.val(response.id)
|
||||
post_form(form, dest)
|
||||
}
|
||||
}
|
||||
r.gold.makeStripeToken(postOnSuccess)
|
||||
},
|
||||
|
||||
makeStripeToken: function (responseHandler) {
|
||||
var form = $('#stripe-payment'),
|
||||
publicKey = form.find('[name="stripePublicKey"]').val(),
|
||||
submit = form.find('.stripe-submit'),
|
||||
@@ -131,17 +155,6 @@ r.gold = {
|
||||
cardState = form.find('.card-address_state').val(),
|
||||
cardCountry = form.find('.card-address_country').val(),
|
||||
cardZip = form.find('.card-address_zip').val()
|
||||
|
||||
var stripeResponseHandler = function(statusCode, response) {
|
||||
if (response.error) {
|
||||
submit.removeAttr('disabled')
|
||||
status.html(response.error.message)
|
||||
} else {
|
||||
token.val(response.id)
|
||||
post_form(form, 'stripecharge/gold')
|
||||
}
|
||||
}
|
||||
|
||||
Stripe.setPublishableKey(publicKey)
|
||||
|
||||
if (!cardName) {
|
||||
@@ -178,7 +191,7 @@ r.gold = {
|
||||
address_state: cardState,
|
||||
address_country: cardCountry,
|
||||
address_zip: cardZip
|
||||
}, stripeResponseHandler
|
||||
}, responseHandler
|
||||
)
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -105,24 +105,17 @@
|
||||
<button class="btn stripe-gold gold-button">${_('Credit Card')}</button>
|
||||
</%def>
|
||||
|
||||
<%def name="stripe_form(display=False)">
|
||||
<%def name="base_stripe_form()">
|
||||
<script type="text/javascript" src="https://js.stripe.com/v1/"></script>
|
||||
|
||||
<form action="/api/stripecharge/gold" method="POST"
|
||||
id="stripe-payment"
|
||||
<div id="base-stripe-form"
|
||||
class="gold-checkout"
|
||||
data-vendor="stripe"
|
||||
${not display and "style='display:none'" or ''}>
|
||||
<hr>
|
||||
data-vendor="stripe">
|
||||
<div class="stripe-note">
|
||||
<a class="icon" href="https://stripe.com/help/security">powered by stripe</a>
|
||||
<div>${_('Stripe is PCI compliant and your credit card information is sent directly to them.')}</div>
|
||||
</div>
|
||||
<table class="credit-card-input">
|
||||
<tr>
|
||||
<th><label>${_('amount')}</label></th>
|
||||
<th class="credit-card-amount">${thing.price}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label>${_('name')}</label></th>
|
||||
<td><input type="text" autocomplete="off" class="card-name"></td>
|
||||
@@ -183,12 +176,37 @@
|
||||
</table>
|
||||
<input type="hidden" name="stripePublicKey" value="${thing.stripe_key}">
|
||||
<input type="hidden" name="stripeToken" value="">
|
||||
<input type="hidden" name="pennies" value="${thing.price.pennies}">
|
||||
<input type="hidden" name="months" value="${thing.months}">
|
||||
<input type="hidden" name="passthrough" value="${thing.passthrough}">
|
||||
<button class="btn gold-button stripe-submit">${_('Submit')}</button>
|
||||
<span class="status"></span>
|
||||
</form>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="stripe_form(display=False)">
|
||||
<div id="stripe-payment"
|
||||
class="charge"
|
||||
${not display and "style='display:none'" or ''}>
|
||||
<hr>
|
||||
<table class="credit-card-input">
|
||||
<tr>
|
||||
<th><label>${_('amount')}</label></th>
|
||||
<th class="credit-card-amount">${thing.price}</th>
|
||||
</tr>
|
||||
%if thing.period:
|
||||
<tr>
|
||||
<th><label>${_('renewal interval')}</label></th>
|
||||
<th class="credit-card-interval">${_(thing.period)}</th>
|
||||
</tr>
|
||||
%endif
|
||||
</table>
|
||||
|
||||
<input type="hidden" name="pennies" value="${thing.price.pennies}">
|
||||
<input type="hidden" name="months" value="${thing.months}">
|
||||
<input type="hidden" name="period" value="${thing.period}">
|
||||
<input type="hidden" name="passthrough" value="${thing.passthrough}">
|
||||
|
||||
<hr>
|
||||
${base_stripe_form()}
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="coinbase_button()">
|
||||
|
||||
73
r2/r2/templates/goldsubscription.html
Normal file
73
r2/r2/templates/goldsubscription.html
Normal file
@@ -0,0 +1,73 @@
|
||||
## The contents of this file are subject to the Common Public Attribution
|
||||
## License Version 1.0. (the "License"); you may not use this file except in
|
||||
## compliance with the License. You may obtain a copy of the License at
|
||||
## http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
|
||||
## License Version 1.1, but Sections 14 and 15 have been added to cover use of
|
||||
## software over a computer network and provide for limited attribution for the
|
||||
## Original Developer. In addition, Exhibit A has been modified to be
|
||||
## consistent with Exhibit B.
|
||||
##
|
||||
## Software distributed under the License is distributed on an "AS IS" basis,
|
||||
## WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
||||
## the specific language governing rights and limitations under the License.
|
||||
##
|
||||
## The Original Code is reddit.
|
||||
##
|
||||
## The Original Developer is the Initial Developer. The Initial Developer of
|
||||
## the Original Code is reddit Inc.
|
||||
##
|
||||
## All portions of the code written by reddit are Copyright (c) 2006-2013
|
||||
## reddit Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
<%namespace file="goldpayment.html" import="base_stripe_form"/>
|
||||
|
||||
%if not (thing.has_stripe_subscription or thing.has_paypal_subscription):
|
||||
<div class="error">
|
||||
${_("your account doesn't have a gold subscription.")}
|
||||
</div>
|
||||
%endif
|
||||
|
||||
%if thing.has_stripe_subscription:
|
||||
<div class="gold-subscription">
|
||||
${thing.text}
|
||||
|
||||
<div class="buttons">
|
||||
<button class="edit-button" onclick="$('#stripe-payment').toggle()">
|
||||
${_("use different card")}
|
||||
</button>
|
||||
|
||||
<button class="cancel-button" onclick="$('#stripe-cancel').toggle()">
|
||||
${_("cancel subscription")}
|
||||
</button>
|
||||
|
||||
<span id="stripe-cancel" style='display:none'>
|
||||
<input type="hidden" name="user" value="${thing.user_fullname}">
|
||||
<span class="option error">
|
||||
${_("are you sure?")}
|
||||
 <a href="javascript:void(0)" class="yes"
|
||||
onclick="post_form('#stripe-cancel', 'cancel_subscription')">
|
||||
${_("yes")}
|
||||
</a> / 
|
||||
<a href="javascript:void(0)" class="no"
|
||||
onclick="$('#stripe-cancel').hide()">${_("no")}</a>
|
||||
</span>
|
||||
<span class="status"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="gold-form">
|
||||
<div class="modify" id="stripe-payment" style='display:none'>
|
||||
<div class="roundfield">
|
||||
${base_stripe_form()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
%endif
|
||||
|
||||
%if thing.has_paypal_subscription:
|
||||
<div class="gold-subscription">
|
||||
${_("you have a paypal gold subscription. go to %(paypal)s to manage it.") % dict(paypal=thing.paypal_url)}
|
||||
</div>
|
||||
%endif
|
||||
@@ -136,6 +136,14 @@
|
||||
${thing.gold_subscr_id}
|
||||
</div>
|
||||
%endif
|
||||
|
||||
%if getattr(thing, "stripe_customer_id", None):
|
||||
<div>
|
||||
<a href="/gold/subscription">
|
||||
${_("manage recurring subscription")}
|
||||
</a>
|
||||
</div>
|
||||
%endif
|
||||
%endif
|
||||
%if thing.gold_creddit_message:
|
||||
<div class="gold-creddits-remaining">
|
||||
|
||||
Reference in New Issue
Block a user