Skip to content

Commit

Permalink
Stripe Bitcoin implementation
Browse files Browse the repository at this point in the history
Changes to permit refunds, and in Terms of Service
  • Loading branch information
turukawa committed Feb 23, 2015
1 parent 0b61fee commit 76d85ca
Show file tree
Hide file tree
Showing 14 changed files with 139 additions and 45 deletions.
1 change: 1 addition & 0 deletions whyqd/novel/models/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def issue(self, **kwargs):
self.is_purchased = kwargs.get("is_purchased", False)
self.charge = kwargs.get("charge", None)
self.price = kwargs.get("price", Decimal("0.00"))
self.currency = kwargs.get("currency", "gbp")
self.stripe_id = kwargs.get("stripe_id", None)
# if purchased, and the creator and recipient are the same, then the person is buying this themselves
# (i.e.) not a gift, the person is logged in and they will automatically take ownership...
Expand Down
8 changes: 5 additions & 3 deletions whyqd/novel/templates/novel/buy_novel.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@
<li id="forex_usd" data-value="{{ fx.usd }}"><a href="#">USD</a></li>
<li id="forex_gbp" data-value="{{ fx.gbp }}"><a href="#">GBP</a></li>
<li id="forex_eur" data-value="{{ fx.eur }}"><a href="#">EUR</a></li>
<li id="forex_btc" data-value="{{ fx.btc }}"><a href="#">BTC</a></li>
</ul>
</div>
<p></p>
<div class="row col-xs-12 text-center"><span class="smallprint">For Kindle & ePub readers. DRM-free. Payments are
securely processed by Stripe. This site never has access to your credit card number.</span></div>
<div class="row col-xs-12 text-center" id="buynowresponse"><span class="smallprint">For Kindle & ePub readers.
DRM-free. Payments securely processed by Stripe. BTC is an estimate until confirmed by Stripe.
This site never has access to your credit card number. Please review our
<a href="{% url 'legal' %}">Terms of Service</a></span></div>
</div>
11 changes: 8 additions & 3 deletions whyqd/novel/templates/novel/issue_tokens.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ <h1>Share and Manage {{ novel_object.title }}</h1>
<li id="forex_eur" data-value={{ fx.eur }}><a href="#">EUR</a></li>
</ul>
</div>
<span class="smallprint">Payments are securely
processed by Stripe. This site never has access to your credit card number.</span>
<span class="smallprint" id="bulkbuyinfo">Payments are securely
processed by Stripe. Select USD to pay in BTC (at rate calculated by Stripe). This site
never has access to your credit card number. Please review our
<a href="{% url 'legal' %}">Terms of Service</a></span>
</div>
<div class="col-sm-3 col-md-4" id="bulkbuyinfo"></div>
</div>
</div>
<div id="tab-lend" class="tab-pane">
Expand Down Expand Up @@ -108,6 +109,10 @@ <h1>Share and Manage {{ novel_object.title }}</h1>
{{ form.id }}
<div class="col-xs-6">
{{ form.recipient|add_class:"form-control" }}
{% if form.instance.is_valid and not form.instance.charge.card %}
<input type="text" class="form-control" placeholder="Enter refund wallet number"
id="refundTokenWallet_{{forloop.counter0}}" />
{% endif %}
</div>
<div class="col-xs-6">
{% if form.instance.is_valid %}
Expand Down
12 changes: 9 additions & 3 deletions whyqd/novel/templates/novel/redeem_token.html
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,17 @@
<a href="{% url 'legal' %}">Terms of Service</a>.</li>
</ul>
</dd></dl>
{% if token_object.is_valid and token_object.is_purchased and not token_object.creator and not request.user.is_authenticated %}
{% if show_refund %}
<form class="form-horizontal" id="refundForm">
<div class="form-group">
<label for="refundToken" class="col-sm-5 control-label">This is also your last chance for an immediate
refund:</label>
<label for="refundToken" class="col-sm-5 control-label">This is also your last chance for
an immediate refund:</label>
{% if show_bitcoin_refund %}
<div class="col-sm-3">
<input type="text" class="form-control" placeholder="Enter refund wallet number"
id="refundTokenWallet" />
</div>
{% endif %}
<div class="col-sm-3">
<button type="submit" class="btn btn-default btn-sm"
id="refundToken"
Expand Down
59 changes: 47 additions & 12 deletions whyqd/novel/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,24 @@ def buy_novel(request, surl=None, template_name="novel/buy_novel.html"):
return HttpResponse(json.dumps(buy_response), content_type="application/json")
# Create the charge on Stripe's servers - this will charge the user's card
try:
charge = stripe.Charge.create(
amount=data["stripePrice"], # amount in pence
currency=data["stripeCurrency"],
card=data["stripeToken"],
description=data["stripeDescription"],
statement_description=novel_object.title,
)
# If Bitcoin, process a BitcoinReceiver first
if data["stripeBitcoin"] == "true":
#receiver = stripe.BitcoinReceiver.retrieve(data["stripeToken"])
#receiver(refund_mispayments=True)
charge = stripe.Charge.create(
amount=data["stripePrice"], # amount in pence
currency=data["stripeCurrency"],
source=data["stripeToken"],
description=data["stripeDescription"],
)
else:
charge = stripe.Charge.create(
amount=data["stripePrice"], # amount in pence
currency=data["stripeCurrency"],
card=data["stripeToken"],
description=data["stripeDescription"],
statement_description=novel_object.title,
)
except stripe.CardError, e:
# The card has been declined
if request.is_ajax():
Expand All @@ -89,6 +100,7 @@ def buy_novel(request, surl=None, template_name="novel/buy_novel.html"):
kwargs["creator_ip"] = wiqid.get_user_ip(request)
kwargs["novel"] = novel_object
kwargs["charge"] = charge
kwargs["currency"] = data["stripeCurrency"].lower()
kwargs["stripe_id"] = charge["id"]
kwargs["is_purchased"] = True
if request.user.is_authenticated():
Expand Down Expand Up @@ -302,6 +314,7 @@ def redeem_token(request, surl, template_name="novel/redeem_token.html"):
token_object = get_object_or_404(Token, surl=surl)
if not token_object.is_purchased:
days = settings.TOKEN_DELTA
show_refund = False
if request.user.is_authenticated():
# check if user already owns this item and ignore redemption if they do
if request.user.can_read(token_object.novel) == "owns":
Expand All @@ -316,6 +329,13 @@ def redeem_token(request, surl, template_name="novel/redeem_token.html"):
return redirect("view_wiqi", wiqi_surl=token_object.novel.sentinal.surl)
else:
expiry = settings.S3_TIMER
if token_object.is_valid and \
token_object.is_purchased and \
not token_object.creator:
show_refund = True
show_bitcoin_refund = False
if show_refund and not token_object.charge["card"]:
show_bitcoin_refund = True
page_title = token_object.novel.title
novel_object = token_object.novel
page_subtitle = "Redemption"
Expand Down Expand Up @@ -387,9 +407,12 @@ def refund_token(request, surl, template_name="novel/refund_token.html"):
page_title = token_object.novel.title
refund_response = {'response': 'failure'}
token_refund = False
data = request.POST
if token_object.is_valid and token_object.is_purchased:
if request.user.is_authenticated() or not token_object.creator:
token_refund = True
if not token_object.charge["card"] and (data["bitcoin_refund"] == "false" or data["bitcoin_refund"] == ""):
token_refund = False # No bitcoin address for refund.
if token_refund:
stripe_key = settings.STRIPE_PUBLISHABLE_KEY
# Set your secret key: remember to change this to your live secret key in production
Expand All @@ -398,10 +421,22 @@ def refund_token(request, surl, template_name="novel/refund_token.html"):
try:
# Get the token key
stripe_charge = stripe.Charge.retrieve(token_object.charge["id"])
stripe_refund = stripe_charge.refunds.create(
amount= int(token_object.price * 100), # amount in pence / cents
reason = "requested_by_customer",
)
if token_object.charge["card"]:
stripe_refund = stripe_charge.refunds.create(
amount = int(token_object.price * 100), # amount in pence / cents
reason = "requested_by_customer",
)
else:
try:
stripe_refund = stripe_charge.refunds.create(
refund_address = data["bitcoin_refund"],
amount = int(token_object.price * 100), # amount in pence / cents
)
except stripe.InvalidRequestError, e:
# Stripe insists on a single repayment wallet address
stripe_refund = stripe_charge.refunds.create(
amount = int(token_object.price * 100), # amount in pence / cents
)
token_object.charge = stripe_refund
token_object.is_valid = False
token_object.is_purchased = False
Expand All @@ -415,7 +450,7 @@ def refund_token(request, surl, template_name="novel/refund_token.html"):
}
}
send_email(**email_kwargs)
except stripe.CardError, e:
except stripe.CardError, e:
# The refund has been declined
refund_response = {'response': 'failure'}
if request.is_ajax():
Expand Down
2 changes: 1 addition & 1 deletion whyqd/snippets/forex.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import json # Allows the data to be decoded
# https://github.com/ashokfernandez/PyExchangeRates/blob/master/PyExchangeRates.py

CURRENCY_CHOICE = ("gbp", "usd", "eur")
CURRENCY_CHOICE = ("gbp", "usd", "eur", "btc")
BASE_CURRENCY = "gbp"

def get_forex():
Expand Down
5 changes: 5 additions & 0 deletions whyqd/static/css/whyqd_theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,11 @@ body {
line-height: 1.42857143;
color: #333333;
background-color: #ffffff;
text-rendering: optimizeLegibility;
font-feature-settings: "kern" 1;
font-kerning: normal;
font-variant-ligatures: normal;

}
input,
button,
Expand Down
39 changes: 27 additions & 12 deletions whyqd/static/js/jquery.whyqd.buy.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ $(document).ready(function() {
image: '/static/images/qwyre_logo_128pxBW.png',
name: ns.novel_title,
email: ns.user_email,
bitcoin: "true",
refund_mispayments: "true",
token: function(token) {
// http://stackoverflow.com/a/22461608
// You can access the token ID with `token.id`
if (!ns.user_email) {
ns.user_email = token.email;
}
var is_bitcoin = false;
if (token.type === "bitcoin_receiver") {
is_bitcoin = true;
}
$.ajax({
url: $('#stripeBuy').val(),
type: 'post',
Expand All @@ -25,6 +31,7 @@ $(document).ready(function() {
stripeCurrency: ns.currency,
stripeEmails: ns.user_email,
selfPurchase: true, // Buying for own use
stripeBitcoin: is_bitcoin,
template: 'purchase'
},
success: function(data) {
Expand All @@ -33,7 +40,7 @@ $(document).ready(function() {
$("#buytrigger").val(true).change();
$('#buynow').empty();
if (data.registered) {
$('#buynow').after(
$('#buynow').before(
'<div class="alert alert-success alert-dismissable">'+
'<button type="button" class="close" ' +
'data-dismiss="alert" aria-hidden="true">' + '&times;' +
Expand All @@ -50,7 +57,7 @@ $(document).ready(function() {
}
}
else {
$('#buynow').after(
$('#buynowresponse').before(
'<div class="alert alert-danger alert-dismissable">'+
'<button type="button" class="close" ' +
'data-dismiss="alert" aria-hidden="true">' + '&times;' +
Expand All @@ -61,7 +68,7 @@ $(document).ready(function() {
}
},
error: function(data) {
$('#buynow').after(
$('#buynowresponse').after(
'<div class="alert alert-danger alert-dismissable">'+
'<button type="button" class="close" ' +
'data-dismiss="alert" aria-hidden="true">' + '&times;' +
Expand Down Expand Up @@ -90,39 +97,47 @@ $(document).ready(function() {
}
$('[id^=forex_]').on('click', function (e) {
var forex = this;
var base_price = parseFloat($('#basePrice').attr('value').trim()).toFixed(2);
var base_price = parseFloat($('#basePrice').attr('value').trim());
var next_price = $('#nextPrice').attr('value').trim();
var select_price = parseFloat($(this).attr('data-value').trim()).toFixed(2);
console.log(base_price);
console.log($('#'+this.id).val());
var select_price = parseFloat($(this).attr('data-value').trim());
var new_price = (select_price * base_price);
if (next_price) {
next_price = (parseFloat(next_price).toFixed(2) * select_price/100).toFixed(2);
next_price = (parseFloat(next_price) * select_price/100);
}
$("#stripePrice").val(new_price.toFixed());
switch (this.id) {
case 'forex_usd':
$('#stripeButton').html("Buy for $ " + (new_price/100).toFixed(2));
$("#stripeCurrency").val('USD');
if (next_price) {
$('#nextPriceText').html("Price increases to $ " + next_price);
$('#nextPriceText').html("Price increases to $ " + next_price.toFixed(2));
}
break;
case 'forex_gbp':
$('#stripeButton').html("Buy for &pound; " + (new_price/100).toFixed(2));
$("#stripeCurrency").val('GBP');
if (next_price) {
$('#nextPriceText').html("Price increases to &pound; " + next_price);
$('#nextPriceText').html("Price increases to &pound; " + next_price.toFixed(2));
}
break;
case 'forex_eur':
$('#stripeButton').html("Buy for &euro; " + (new_price/100).toFixed(2));
$("#stripeCurrency").val('EUR');
if (next_price) {
$('#nextPriceText').html("Price increases to &euro; " + next_price);
$('#nextPriceText').html("Price increases to &euro; " + next_price.toFixed(2));
}
break;
case 'forex_btc':
$('#stripeButton').html("Buy for &#3647; " + (new_price/100).toFixed(4));
if (next_price) {
$('#nextPriceText').html("Price increases to &#3647; " + next_price.toFixed(4));
}
// Stripe needs this to be in USD for 10min period conversion rate
var USD_price = parseFloat($('#forex_usd').attr('data-value').trim());
new_price = (USD_price * base_price);
$("#stripeCurrency").val('USD');
break;
}
$("#stripePrice").val(new_price.toFixed());
e.preventDefault();
});
$("#buytrigger").change(function () {
Expand Down
10 changes: 9 additions & 1 deletion whyqd/static/js/jquery.whyqd.redeem.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,16 @@ $(document).ready(function() {
});
$('#refundToken').on('click', function (e) {
var tid = this;
var bitid = false;
if ($('#refundTokenWallet').length != 0) {
bitid = $('#refundTokenWallet').val();
}
$.ajax({
url: tid.value
url: tid.value,
type: 'post',
data: {
bitcoin_refund: bitid
}
}).done(function(data) {
if (data.response == 'success') {
window.location.href = '/';
Expand Down
Loading

0 comments on commit 76d85ca

Please sign in to comment.