Merge pull request #452 from pcoder/task/3701/enable_monthly_payments
Task/3701/enable monthly payments
This commit is contained in:
commit
6206f25b56
15 changed files with 499 additions and 105 deletions
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2017-08-03 03:10+0530\n"
|
"POT-Creation-Date: 2017-08-24 11:28+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -276,6 +276,19 @@ msgstr "Konfiguration"
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "month"
|
||||||
|
msgid "Month"
|
||||||
|
msgstr "Monat"
|
||||||
|
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"By clicking \"Place order\" this plan will charge your credit card account "
|
||||||
|
"with the fee of %(vm_price)sCHF/month"
|
||||||
|
msgstr ""
|
||||||
|
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(vm_price)sCHF "
|
||||||
|
"pro Monat belastet"
|
||||||
|
|
||||||
msgid "Place order"
|
msgid "Place order"
|
||||||
msgstr "Bestellen"
|
msgstr "Bestellen"
|
||||||
|
|
||||||
|
|
22
datacenterlight/migrations/0007_stripeplan.py
Normal file
22
datacenterlight/migrations/0007_stripeplan.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.4 on 2017-08-16 19:47
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('datacenterlight', '0006_vmtemplate'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='StripePlan',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('stripe_plan_id', models.CharField(max_length=100, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
20
datacenterlight/migrations/0008_auto_20170821_2024.py
Normal file
20
datacenterlight/migrations/0008_auto_20170821_2024.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.4 on 2017-08-21 20:24
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('datacenterlight', '0007_stripeplan'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='stripeplan',
|
||||||
|
name='stripe_plan_id',
|
||||||
|
field=models.CharField(max_length=256, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -59,3 +59,15 @@ class VMTemplate(models.Model):
|
||||||
def create(cls, name, opennebula_vm_template_id):
|
def create(cls, name, opennebula_vm_template_id):
|
||||||
vm_template = cls(name=name, opennebula_vm_template_id=opennebula_vm_template_id)
|
vm_template = cls(name=name, opennebula_vm_template_id=opennebula_vm_template_id)
|
||||||
return vm_template
|
return vm_template
|
||||||
|
|
||||||
|
|
||||||
|
class StripePlan(models.Model):
|
||||||
|
"""
|
||||||
|
A model to store Data Center Light's created Stripe plans
|
||||||
|
"""
|
||||||
|
stripe_plan_id = models.CharField(max_length=256, null=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, stripe_plan_id):
|
||||||
|
stripe_plan = cls(stripe_plan_id=stripe_plan_id)
|
||||||
|
return stripe_plan
|
||||||
|
|
|
@ -44,7 +44,7 @@ def retry_task(task, exception=None):
|
||||||
def create_vm_task(self, vm_template_id, user, specs, template,
|
def create_vm_task(self, vm_template_id, user, specs, template,
|
||||||
stripe_customer_id, billing_address_data,
|
stripe_customer_id, billing_address_data,
|
||||||
billing_address_id,
|
billing_address_id,
|
||||||
charge):
|
charge, cc_details):
|
||||||
vm_id = None
|
vm_id = None
|
||||||
try:
|
try:
|
||||||
final_price = specs.get('price')
|
final_price = specs.get('price')
|
||||||
|
@ -91,9 +91,9 @@ def create_vm_task(self, vm_template_id, user, specs, template,
|
||||||
billing_address_user_form.is_valid()
|
billing_address_user_form.is_valid()
|
||||||
billing_address_user_form.save()
|
billing_address_user_form.save()
|
||||||
|
|
||||||
# Associate an order with a stripe payment
|
# Associate an order with a stripe subscription
|
||||||
charge_object = DictDotLookup(charge)
|
charge_object = DictDotLookup(charge)
|
||||||
order.set_stripe_charge(charge_object)
|
order.set_subscription_id(charge_object, cc_details)
|
||||||
|
|
||||||
# If the Stripe payment succeeds, set order status approved
|
# If the Stripe payment succeeds, set order status approved
|
||||||
order.set_approved()
|
order.set_approved()
|
||||||
|
|
|
@ -67,14 +67,17 @@
|
||||||
<hr>
|
<hr>
|
||||||
<p><b>{% trans "Configuration"%}</b> <span class="pull-right">{{request.session.template.name}}</span></p>
|
<p><b>{% trans "Configuration"%}</b> <span class="pull-right">{{request.session.template.name}}</span></p>
|
||||||
<hr>
|
<hr>
|
||||||
<h4>{% trans "Total"%}<p class="pull-right"><b>{{vm.price}} CHF</b></p></h4>
|
<h4>{% trans "Total"%}<p class="pull-right"><b>{{vm.price}} CHF</b><span class="dcl-price-month"> /{% trans "Month" %}</span></p></h4>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class=" content pull-right">
|
<div class="col-md-8 col-xs-7 pull-left tbl-no-padding">
|
||||||
<a href="{{next_url}}" ><button class="btn btn-info">{% trans "Place order"%}</button></a>
|
<p class="dcl-place-order-text">{% blocktrans with vm_price=request.session.specs.price %}By clicking "Place order" this plan will charge your credit card account with the fee of {{ vm_price }}CHF/month{% endblocktrans %}.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 col-xs-5 content tbl-no-padding">
|
||||||
|
<a href="{{next_url}}" ><button class="btn btn-info pull-right">{% trans "Place order"%}</button></a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -50,7 +50,12 @@ class CeleryTaskTestCase(TestCase):
|
||||||
call_command('fetchvmtemplates')
|
call_command('fetchvmtemplates')
|
||||||
|
|
||||||
def test_create_vm_task(self):
|
def test_create_vm_task(self):
|
||||||
"""Tests the create vm task."""
|
"""Tests the create vm task for monthly subscription
|
||||||
|
|
||||||
|
This test is supposed to validate the proper execution
|
||||||
|
of celery create_vm_task on production, as we have no
|
||||||
|
other way to do this.
|
||||||
|
"""
|
||||||
|
|
||||||
# We create a VM from the first template available to DCL
|
# We create a VM from the first template available to DCL
|
||||||
vm_template = VMTemplate.objects.all().first()
|
vm_template = VMTemplate.objects.all().first()
|
||||||
|
@ -61,12 +66,16 @@ class CeleryTaskTestCase(TestCase):
|
||||||
'cpu': 1,
|
'cpu': 1,
|
||||||
'memory': 2,
|
'memory': 2,
|
||||||
'disk_size': 10,
|
'disk_size': 10,
|
||||||
'price': 15,
|
'price': 15
|
||||||
}
|
}
|
||||||
|
|
||||||
stripe_customer = StripeCustomer.get_or_create(
|
stripe_customer = StripeCustomer.get_or_create(
|
||||||
email=self.customer_email,
|
email=self.customer_email,
|
||||||
token=self.token)
|
token=self.token)
|
||||||
|
card_details = self.stripe_utils.get_card_details(
|
||||||
|
stripe_customer.stripe_id,
|
||||||
|
self.token)
|
||||||
|
card_details_dict = card_details.get('response_object')
|
||||||
billing_address = BillingAddress(
|
billing_address = BillingAddress(
|
||||||
cardholder_name=self.customer_name,
|
cardholder_name=self.customer_name,
|
||||||
postal_code='1232',
|
postal_code='1232',
|
||||||
|
@ -83,28 +92,44 @@ class CeleryTaskTestCase(TestCase):
|
||||||
|
|
||||||
billing_address_id = billing_address.id
|
billing_address_id = billing_address.id
|
||||||
vm_template_id = template_data.get('id', 1)
|
vm_template_id = template_data.get('id', 1)
|
||||||
final_price = specs.get('price')
|
|
||||||
|
|
||||||
# Make stripe charge to a customer
|
cpu = specs.get('cpu')
|
||||||
stripe_utils = StripeUtils()
|
memory = specs.get('memory')
|
||||||
charge_response = stripe_utils.make_charge(
|
disk_size = specs.get('disk_size')
|
||||||
amount=final_price,
|
amount_to_be_charged = (cpu * 5) + (memory * 2) + (disk_size * 0.6)
|
||||||
customer=stripe_customer.stripe_id)
|
plan_name = "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format(
|
||||||
|
cpu=cpu,
|
||||||
|
memory=memory,
|
||||||
|
disk_size=disk_size)
|
||||||
|
|
||||||
# Check if the payment was approved
|
stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
|
||||||
if not charge_response.get(
|
ram=memory,
|
||||||
'response_object'):
|
ssd=disk_size,
|
||||||
msg = charge_response.get('error')
|
version=1,
|
||||||
raise Exception("make_charge failed: {}".format(msg))
|
app='dcl')
|
||||||
|
stripe_plan = self.stripe_utils.get_or_create_stripe_plan(
|
||||||
|
amount=amount_to_be_charged,
|
||||||
|
name=plan_name,
|
||||||
|
stripe_plan_id=stripe_plan_id)
|
||||||
|
subscription_result = self.stripe_utils.subscribe_customer_to_plan(
|
||||||
|
stripe_customer.stripe_id,
|
||||||
|
[{"plan": stripe_plan.get(
|
||||||
|
'response_object').stripe_plan_id}])
|
||||||
|
stripe_subscription_obj = subscription_result.get('response_object')
|
||||||
|
# Check if the subscription was approved and is active
|
||||||
|
if stripe_subscription_obj is None or \
|
||||||
|
stripe_subscription_obj.status != 'active':
|
||||||
|
msg = subscription_result.get('error')
|
||||||
|
raise Exception("Creating subscription failed: {}".format(msg))
|
||||||
|
|
||||||
charge = charge_response.get('response_object')
|
|
||||||
async_task = create_vm_task.delay(vm_template_id, self.user,
|
async_task = create_vm_task.delay(vm_template_id, self.user,
|
||||||
specs,
|
specs,
|
||||||
template_data,
|
template_data,
|
||||||
stripe_customer.id,
|
stripe_customer.id,
|
||||||
billing_address_data,
|
billing_address_data,
|
||||||
billing_address_id,
|
billing_address_id,
|
||||||
charge)
|
stripe_subscription_obj,
|
||||||
|
card_details_dict)
|
||||||
new_vm_id = 0
|
new_vm_id = 0
|
||||||
res = None
|
res = None
|
||||||
for i in range(0, 10):
|
for i in range(0, 10):
|
||||||
|
|
|
@ -473,25 +473,53 @@ class OrderConfirmationView(DetailView):
|
||||||
billing_address_data = request.session.get('billing_address_data')
|
billing_address_data = request.session.get('billing_address_data')
|
||||||
billing_address_id = request.session.get('billing_address')
|
billing_address_id = request.session.get('billing_address')
|
||||||
vm_template_id = template.get('id', 1)
|
vm_template_id = template.get('id', 1)
|
||||||
final_price = specs.get('price')
|
|
||||||
|
|
||||||
# Make stripe charge to a customer
|
# Make stripe charge to a customer
|
||||||
stripe_utils = StripeUtils()
|
stripe_utils = StripeUtils()
|
||||||
charge_response = stripe_utils.make_charge(amount=final_price,
|
card_details = stripe_utils.get_card_details(customer.stripe_id,
|
||||||
customer=customer.stripe_id)
|
request.session.get(
|
||||||
|
'token'))
|
||||||
# Check if the payment was approved
|
if not card_details.get('response_object'):
|
||||||
if not charge_response.get('response_object'):
|
msg = card_details.get('error')
|
||||||
msg = charge_response.get('error')
|
|
||||||
messages.add_message(self.request, messages.ERROR, msg,
|
messages.add_message(self.request, messages.ERROR, msg,
|
||||||
extra_tags='make_charge_error')
|
extra_tags='failed_payment')
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
reverse('datacenterlight:payment') + '#payment_error')
|
reverse('datacenterlight:payment') + '#payment_error')
|
||||||
|
card_details_dict = card_details.get('response_object')
|
||||||
|
cpu = specs.get('cpu')
|
||||||
|
memory = specs.get('memory')
|
||||||
|
disk_size = specs.get('disk_size')
|
||||||
|
amount_to_be_charged = (cpu * 5) + (memory * 2) + (disk_size * 0.6)
|
||||||
|
plan_name = "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format(
|
||||||
|
cpu=cpu,
|
||||||
|
memory=memory,
|
||||||
|
disk_size=disk_size)
|
||||||
|
|
||||||
charge = charge_response.get('response_object')
|
stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
|
||||||
|
ram=memory,
|
||||||
|
ssd=disk_size,
|
||||||
|
version=1,
|
||||||
|
app='dcl')
|
||||||
|
stripe_plan = stripe_utils.get_or_create_stripe_plan(
|
||||||
|
amount=amount_to_be_charged,
|
||||||
|
name=plan_name,
|
||||||
|
stripe_plan_id=stripe_plan_id)
|
||||||
|
subscription_result = stripe_utils.subscribe_customer_to_plan(
|
||||||
|
customer.stripe_id,
|
||||||
|
[{"plan": stripe_plan.get(
|
||||||
|
'response_object').stripe_plan_id}])
|
||||||
|
stripe_subscription_obj = subscription_result.get('response_object')
|
||||||
|
# Check if the subscription was approved and is active
|
||||||
|
if stripe_subscription_obj is None or \
|
||||||
|
stripe_subscription_obj.status != 'active':
|
||||||
|
msg = subscription_result.get('error')
|
||||||
|
messages.add_message(self.request, messages.ERROR, msg,
|
||||||
|
extra_tags='failed_payment')
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
reverse('datacenterlight:payment') + '#payment_error')
|
||||||
create_vm_task.delay(vm_template_id, user, specs, template,
|
create_vm_task.delay(vm_template_id, user, specs, template,
|
||||||
stripe_customer_id, billing_address_data,
|
stripe_customer_id, billing_address_data,
|
||||||
billing_address_id,
|
billing_address_id,
|
||||||
charge)
|
stripe_subscription_obj, card_details_dict)
|
||||||
request.session['order_confirmation'] = True
|
request.session['order_confirmation'] = True
|
||||||
return HttpResponseRedirect(reverse('datacenterlight:order_success'))
|
return HttpResponseRedirect(reverse('datacenterlight:order_success'))
|
||||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2017-08-20 21:37+0530\n"
|
"POT-Creation-Date: 2017-08-24 11:12+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -254,6 +254,9 @@ msgstr "Betrag"
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "See Invoice"
|
||||||
|
msgstr "Rechnung"
|
||||||
|
|
||||||
msgid "View Detail"
|
msgid "View Detail"
|
||||||
msgstr "Details anzeigen"
|
msgstr "Details anzeigen"
|
||||||
|
|
||||||
|
@ -272,6 +275,9 @@ msgstr "Konfiguration"
|
||||||
msgid "including VAT"
|
msgid "including VAT"
|
||||||
msgstr "inkl. Mehrwertsteuer"
|
msgstr "inkl. Mehrwertsteuer"
|
||||||
|
|
||||||
|
msgid "Month"
|
||||||
|
msgstr "Monat"
|
||||||
|
|
||||||
msgid "Billing Address"
|
msgid "Billing Address"
|
||||||
msgstr "Rechnungsadresse"
|
msgstr "Rechnungsadresse"
|
||||||
|
|
||||||
|
@ -292,23 +298,10 @@ msgstr ""
|
||||||
"\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung und "
|
"\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung und "
|
||||||
"speichern keine Informationen in unserer Datenbank."
|
"speichern keine Informationen in unserer Datenbank."
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
#| msgid ""
|
|
||||||
#| "\n"
|
|
||||||
#| " You are not making any "
|
|
||||||
#| "payment yet. After submitting your card\n"
|
|
||||||
#| " information, you will be "
|
|
||||||
#| "taken to the Confirm Order Page.\n"
|
|
||||||
#| " "
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"You are not making any payment yet. After submitting your card information, "
|
||||||
" You are not making any "
|
"you will be taken to the Confirm Order Page."
|
||||||
"payment yet. After submitting your card\n"
|
|
||||||
" information, you will be "
|
|
||||||
"taken to the Confirm Order Page.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"\n"
|
|
||||||
"Es wird noch keine Bezahlung vorgenommen. Nach der Eingabe Deiner "
|
"Es wird noch keine Bezahlung vorgenommen. Nach der Eingabe Deiner "
|
||||||
"Kreditkateninformationen wirst du auf die Bestellbestätigungsseite "
|
"Kreditkateninformationen wirst du auf die Bestellbestätigungsseite "
|
||||||
"weitergeleitet."
|
"weitergeleitet."
|
||||||
|
@ -391,7 +384,7 @@ msgstr "Anzeigen"
|
||||||
|
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Public SSH Key"
|
#| msgid "Public SSH Key"
|
||||||
msgid "Public SSH key"
|
msgid "Public SSH Key"
|
||||||
msgstr "Public SSH Key"
|
msgstr "Public SSH Key"
|
||||||
|
|
||||||
msgid "Download"
|
msgid "Download"
|
||||||
|
@ -415,12 +408,6 @@ msgstr "Abrechnungen"
|
||||||
msgid "Current Pricing"
|
msgid "Current Pricing"
|
||||||
msgstr "Aktueller Preis"
|
msgstr "Aktueller Preis"
|
||||||
|
|
||||||
msgid "Month"
|
|
||||||
msgstr "Monat"
|
|
||||||
|
|
||||||
msgid "See Invoice"
|
|
||||||
msgstr "Rechnung"
|
|
||||||
|
|
||||||
msgid "Your VM is"
|
msgid "Your VM is"
|
||||||
msgstr "Deine VM ist"
|
msgstr "Deine VM ist"
|
||||||
|
|
||||||
|
@ -507,6 +494,19 @@ msgid ""
|
||||||
"contact Data Center Light Support."
|
"contact Data Center Light Support."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "\n"
|
||||||
|
#~ " You are not making any "
|
||||||
|
#~ "payment yet. After submitting your card\n"
|
||||||
|
#~ " information, you will be "
|
||||||
|
#~ "taken to the Confirm Order Page.\n"
|
||||||
|
#~ " "
|
||||||
|
#~ msgstr ""
|
||||||
|
#~ "\n"
|
||||||
|
#~ "Es wird noch keine Bezahlung vorgenommen. Nach der Eingabe Deiner "
|
||||||
|
#~ "Kreditkateninformationen wirst du auf die Bestellbestätigungsseite "
|
||||||
|
#~ "weitergeleitet."
|
||||||
|
|
||||||
#~ msgid "Approved"
|
#~ msgid "Approved"
|
||||||
#~ msgstr "Akzeptiert"
|
#~ msgstr "Akzeptiert"
|
||||||
|
|
||||||
|
@ -670,14 +670,6 @@ msgstr ""
|
||||||
#~ msgid "Place Order"
|
#~ msgid "Place Order"
|
||||||
#~ msgstr "Bestelle"
|
#~ msgstr "Bestelle"
|
||||||
|
|
||||||
#~ msgid ""
|
|
||||||
#~ "You are not making any payment yet. After placing your order, you will be "
|
|
||||||
#~ "taken to the Submit Payment Page."
|
|
||||||
#~ msgstr ""
|
|
||||||
#~ "Es wird noch keine Bezahlung vorgenommen. Nach der Eingabe deiner "
|
|
||||||
#~ "Kreditkateninformationen wirst du auf die Bestellbestätigungsseite "
|
|
||||||
#~ "weitergeleitet."
|
|
||||||
|
|
||||||
#~ msgid "CARD NUMBER"
|
#~ msgid "CARD NUMBER"
|
||||||
#~ msgstr "Kreditkartennummer"
|
#~ msgstr "Kreditkartennummer"
|
||||||
|
|
||||||
|
|
20
hosting/migrations/0042_hostingorder_subscription_id.py
Normal file
20
hosting/migrations/0042_hostingorder_subscription_id.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.4 on 2017-08-17 16:34
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hosting', '0041_userhostingkey_private_key'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='hostingorder',
|
||||||
|
name='subscription_id',
|
||||||
|
field=models.CharField(max_length=100, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -50,6 +50,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
|
||||||
cc_brand = models.CharField(max_length=10)
|
cc_brand = models.CharField(max_length=10)
|
||||||
stripe_charge_id = models.CharField(max_length=100, null=True)
|
stripe_charge_id = models.CharField(max_length=100, null=True)
|
||||||
price = models.FloatField()
|
price = models.FloatField()
|
||||||
|
subscription_id = models.CharField(max_length=100, null=True)
|
||||||
|
|
||||||
permissions = ('view_hostingorder',)
|
permissions = ('view_hostingorder',)
|
||||||
|
|
||||||
|
@ -66,7 +67,8 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
|
||||||
return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS
|
return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, price=None, vm_id=None, customer=None, billing_address=None):
|
def create(cls, price=None, vm_id=None, customer=None,
|
||||||
|
billing_address=None):
|
||||||
instance = cls.objects.create(
|
instance = cls.objects.create(
|
||||||
price=price,
|
price=price,
|
||||||
vm_id=vm_id,
|
vm_id=vm_id,
|
||||||
|
@ -86,6 +88,23 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
|
||||||
self.cc_brand = stripe_charge.source.brand
|
self.cc_brand = stripe_charge.source.brand
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
def set_subscription_id(self, subscription_object, cc_details):
|
||||||
|
"""
|
||||||
|
When creating a Stripe subscription, we have subscription id.
|
||||||
|
We store this in the subscription_id field.
|
||||||
|
This method sets the subscription id from subscription_object
|
||||||
|
and also the last4 and credit card brands used for this order.
|
||||||
|
|
||||||
|
:param subscription_object: Stripe's subscription object
|
||||||
|
:param cc_details: A dict containing card details
|
||||||
|
{last4, brand}
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
self.subscription_id = subscription_object.id
|
||||||
|
self.last4 = cc_details.get('last4')
|
||||||
|
self.cc_brand = cc_details.get('brand')
|
||||||
|
self.save()
|
||||||
|
|
||||||
def get_cc_data(self):
|
def get_cc_data(self):
|
||||||
return {
|
return {
|
||||||
'last4': self.last4,
|
'last4': self.last4,
|
||||||
|
@ -137,5 +156,6 @@ class HostingBill(AssignPermissionsMixin, models.Model):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, customer=None, billing_address=None):
|
def create(cls, customer=None, billing_address=None):
|
||||||
instance = cls.objects.create(customer=customer, billing_address=billing_address)
|
instance = cls.objects.create(customer=customer,
|
||||||
|
billing_address=billing_address)
|
||||||
return instance
|
return instance
|
||||||
|
|
|
@ -533,9 +533,21 @@ a.unlink:hover {
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dcl-place-order-text{
|
||||||
|
font-size: 13px;
|
||||||
|
color: #808080;
|
||||||
|
}
|
||||||
|
|
||||||
.dcl-order-table-total .tbl-total {
|
.dcl-order-table-total .tbl-total {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #000;
|
color: #000;
|
||||||
|
padding-left: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tbl-total .dcl-price-month {
|
||||||
|
font-size: 16px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tbl-no-padding {
|
.tbl-no-padding {
|
||||||
|
|
|
@ -41,9 +41,9 @@
|
||||||
{%trans "Total" %} <span>{%trans "including VAT" %}</span>
|
{%trans "Total" %} <span>{%trans "including VAT" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-6 col-sm-6 col-md-6 col-lg-6 tbl-no-padding">
|
<div class="col-xs-6 col-sm-6 col-md-6 col-lg-6 tbl-no-padding">
|
||||||
<div class="col-xs-12 col-sm-6 col-md-6 col-lg-6"></div>
|
<div class="col-xs-12 col-sm-4 col-md-4 col-lg-4"></div>
|
||||||
<div class="col-xs-12 col-sm-4 col-md-4 col-lg-4 tbl-total">{{request.session.specs.price}}
|
<div class="col-xs-12 col-sm-6 col-md-6 col-lg-6 tbl-total">{{request.session.specs.price}}
|
||||||
CHF
|
CHF<span class="dcl-price-month">/{% trans "Month" %}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -87,10 +87,7 @@
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
{% if not messages and not form.non_field_errors %}
|
{% if not messages and not form.non_field_errors %}
|
||||||
<p class="card-warning-content card-warning-addtional-margin">
|
<p class="card-warning-content card-warning-addtional-margin">
|
||||||
{% blocktrans %}
|
{% blocktrans %}You are not making any payment yet. After submitting your card information, you will be taken to the Confirm Order Page.{% endblocktrans %}
|
||||||
You are not making any payment yet. After submitting your card
|
|
||||||
information, you will be taken to the Confirm Order Page.
|
|
||||||
{% endblocktrans %}
|
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id='payment_error'>
|
<div id='payment_error'>
|
||||||
|
@ -147,10 +144,7 @@
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
{% if not messages and not form.non_field_errors %}
|
{% if not messages and not form.non_field_errors %}
|
||||||
<p class="card-warning-content">
|
<p class="card-warning-content">
|
||||||
{% blocktrans %}
|
{% blocktrans %}You are not making any payment yet. After submitting your card information, you will be taken to the Confirm Order Page.{% endblocktrans %}
|
||||||
You are not making any payment yet. After submitting your card
|
|
||||||
information, you will be taken to the Confirm Order Page.
|
|
||||||
{% endblocktrans %}
|
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id='payment_error'>
|
<div id='payment_error'>
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
import logging
|
||||||
import stripe
|
import stripe
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from datacenterlight.models import StripePlan
|
||||||
|
|
||||||
stripe.api_key = settings.STRIPE_API_PRIVATE_KEY
|
stripe.api_key = settings.STRIPE_API_PRIVATE_KEY
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def handleStripeError(f):
|
def handleStripeError(f):
|
||||||
|
@ -26,7 +30,8 @@ def handleStripeError(f):
|
||||||
response.update({'error': err['message']})
|
response.update({'error': err['message']})
|
||||||
return response
|
return response
|
||||||
except stripe.error.RateLimitError as e:
|
except stripe.error.RateLimitError as e:
|
||||||
response.update({'error': "Too many requests made to the API too quickly"})
|
response.update(
|
||||||
|
{'error': "Too many requests made to the API too quickly"})
|
||||||
return response
|
return response
|
||||||
except stripe.error.InvalidRequestError as e:
|
except stripe.error.InvalidRequestError as e:
|
||||||
response.update({'error': "Invalid parameters"})
|
response.update({'error': "Invalid parameters"})
|
||||||
|
@ -55,6 +60,10 @@ class StripeUtils(object):
|
||||||
CURRENCY = 'chf'
|
CURRENCY = 'chf'
|
||||||
INTERVAL = 'month'
|
INTERVAL = 'month'
|
||||||
SUCCEEDED_STATUS = 'succeeded'
|
SUCCEEDED_STATUS = 'succeeded'
|
||||||
|
STRIPE_PLAN_ALREADY_EXISTS = 'Plan already exists'
|
||||||
|
STRIPE_NO_SUCH_PLAN = 'No such plan'
|
||||||
|
PLAN_EXISTS_ERROR_MSG = 'Plan {} exists already.\nCreating a local StripePlan now.'
|
||||||
|
PLAN_DOES_NOT_EXIST_ERROR_MSG = 'Plan {} does not exist.'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.stripe = stripe
|
self.stripe = stripe
|
||||||
|
@ -96,7 +105,8 @@ class StripeUtils(object):
|
||||||
customer = stripe.Customer.retrieve(id)
|
customer = stripe.Customer.retrieve(id)
|
||||||
except stripe.InvalidRequestError:
|
except stripe.InvalidRequestError:
|
||||||
customer = self.create_customer(token, user.email, user.name)
|
customer = self.create_customer(token, user.email, user.name)
|
||||||
user.stripecustomer.stripe_id = customer.get('response_object').get('id')
|
user.stripecustomer.stripe_id = customer.get(
|
||||||
|
'response_object').get('id')
|
||||||
user.stripecustomer.save()
|
user.stripecustomer.save()
|
||||||
return customer
|
return customer
|
||||||
|
|
||||||
|
@ -129,13 +139,92 @@ class StripeUtils(object):
|
||||||
return charge
|
return charge
|
||||||
|
|
||||||
@handleStripeError
|
@handleStripeError
|
||||||
def create_plan(self, amount, name, id):
|
def get_or_create_stripe_plan(self, amount, name, stripe_plan_id):
|
||||||
|
"""
|
||||||
|
This function checks if a StripePlan with the given
|
||||||
|
stripe_plan_id already exists. If it exists then the function
|
||||||
|
returns this object otherwise it creates a new StripePlan and
|
||||||
|
returns the new object.
|
||||||
|
|
||||||
|
:param amount: The amount in CHF
|
||||||
|
:param name: The name of the Stripe plan to be created.
|
||||||
|
:param stripe_plan_id: The id of the Stripe plan to be
|
||||||
|
created. Use get_stripe_plan_id_string function to
|
||||||
|
obtain the name of the plan to be created
|
||||||
|
:return: The StripePlan object if it exists else creates a
|
||||||
|
Plan object in Stripe and a local StripePlan and
|
||||||
|
returns it. Returns None in case of Stripe error
|
||||||
|
"""
|
||||||
|
_amount = float(amount)
|
||||||
|
amount = int(_amount * 100) # stripe amount unit, in cents
|
||||||
|
stripe_plan_db_obj = None
|
||||||
|
try:
|
||||||
|
stripe_plan_db_obj = StripePlan.objects.get(
|
||||||
|
stripe_plan_id=stripe_plan_id)
|
||||||
|
except StripePlan.DoesNotExist:
|
||||||
|
try:
|
||||||
self.stripe.Plan.create(
|
self.stripe.Plan.create(
|
||||||
amount=amount,
|
amount=amount,
|
||||||
interval=self.INTERVAL,
|
interval=self.INTERVAL,
|
||||||
name=name,
|
name=name,
|
||||||
currency=self.CURRENCY,
|
currency=self.CURRENCY,
|
||||||
id=id)
|
id=stripe_plan_id)
|
||||||
|
stripe_plan_db_obj = StripePlan.objects.create(
|
||||||
|
stripe_plan_id=stripe_plan_id)
|
||||||
|
except stripe.error.InvalidRequestError as e:
|
||||||
|
if self.STRIPE_PLAN_ALREADY_EXISTS in str(e):
|
||||||
|
logger.debug(
|
||||||
|
self.PLAN_EXISTS_ERROR_MSG.format(stripe_plan_id))
|
||||||
|
stripe_plan_db_obj = StripePlan.objects.create(
|
||||||
|
stripe_plan_id=stripe_plan_id)
|
||||||
|
return stripe_plan_db_obj
|
||||||
|
|
||||||
|
@handleStripeError
|
||||||
|
def delete_stripe_plan(self, stripe_plan_id):
|
||||||
|
"""
|
||||||
|
Deletes the Plan in Stripe and also deletes the local db copy
|
||||||
|
of the plan if it exists
|
||||||
|
|
||||||
|
:param stripe_plan_id: The stripe plan id that needs to be
|
||||||
|
deleted
|
||||||
|
:return: True if the plan was deleted successfully from
|
||||||
|
Stripe, False otherwise.
|
||||||
|
"""
|
||||||
|
return_value = False
|
||||||
|
try:
|
||||||
|
plan = self.stripe.Plan.retrieve(stripe_plan_id)
|
||||||
|
plan.delete()
|
||||||
|
return_value = True
|
||||||
|
StripePlan.objects.filter(
|
||||||
|
stripe_plan_id=stripe_plan_id).all().delete()
|
||||||
|
except stripe.error.InvalidRequestError as e:
|
||||||
|
if self.STRIPE_NO_SUCH_PLAN in str(e):
|
||||||
|
logger.debug(
|
||||||
|
self.PLAN_DOES_NOT_EXIST_ERROR_MSG.format(stripe_plan_id))
|
||||||
|
return return_value
|
||||||
|
|
||||||
|
@handleStripeError
|
||||||
|
def subscribe_customer_to_plan(self, customer, plans):
|
||||||
|
"""
|
||||||
|
Subscribes the given customer to the list of given plans
|
||||||
|
|
||||||
|
:param customer: The stripe customer identifier
|
||||||
|
:param plans: A list of stripe plans.
|
||||||
|
Ref: https://stripe.com/docs/api/python#create_subscription-items
|
||||||
|
e.g.
|
||||||
|
plans = [
|
||||||
|
{
|
||||||
|
"plan": "dcl-v1-cpu-2-ram-5gb-ssd-10gb",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
:return: The subscription StripeObject
|
||||||
|
"""
|
||||||
|
|
||||||
|
subscription_result = self.stripe.Subscription.create(
|
||||||
|
customer=customer,
|
||||||
|
items=plans,
|
||||||
|
)
|
||||||
|
return subscription_result
|
||||||
|
|
||||||
@handleStripeError
|
@handleStripeError
|
||||||
def make_payment(self, customer, amount, token):
|
def make_payment(self, customer, amount, token):
|
||||||
|
@ -145,3 +234,29 @@ class StripeUtils(object):
|
||||||
customer=customer
|
customer=customer
|
||||||
)
|
)
|
||||||
return charge
|
return charge
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None):
|
||||||
|
"""
|
||||||
|
Returns the stripe plan id string of the form
|
||||||
|
`dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters
|
||||||
|
|
||||||
|
:param cpu: The number of cores
|
||||||
|
:param ram: The size of the RAM in GB
|
||||||
|
:param ssd: The size of ssd storage in GB
|
||||||
|
:param hdd: The size of hdd storage in GB
|
||||||
|
:param version: The version of the Stripe plans
|
||||||
|
:param app: The application to which the stripe plan belongs
|
||||||
|
to. By default it is 'dcl'
|
||||||
|
:return: A string of the form `dcl-v1-cpu-2-ram-5gb-ssd-10gb`
|
||||||
|
"""
|
||||||
|
dcl_plan_string = 'cpu-{cpu}-ram-{ram}gb-ssd-{ssd}gb'.format(cpu=cpu,
|
||||||
|
ram=ram,
|
||||||
|
ssd=ssd)
|
||||||
|
if hdd is not None:
|
||||||
|
dcl_plan_string = '{dcl_plan_string}-hdd-{hdd}gb'.format(
|
||||||
|
dcl_plan_string=dcl_plan_string, hdd=hdd)
|
||||||
|
stripe_plan_id_string = '{app}-v{version}-{plan}'.format(app=app,
|
||||||
|
version=version,
|
||||||
|
plan=dcl_plan_string)
|
||||||
|
return stripe_plan_id_string
|
||||||
|
|
154
utils/tests.py
154
utils/tests.py
|
@ -1,10 +1,15 @@
|
||||||
from django.test import TestCase
|
import uuid
|
||||||
from django.test import Client
|
from unittest.mock import patch
|
||||||
from django.http.request import HttpRequest
|
|
||||||
|
|
||||||
from model_mommy import mommy
|
|
||||||
from utils.stripe_utils import StripeUtils
|
|
||||||
import stripe
|
import stripe
|
||||||
|
from django.http.request import HttpRequest
|
||||||
|
from django.test import Client
|
||||||
|
from django.test import TestCase
|
||||||
|
from model_mommy import mommy
|
||||||
|
|
||||||
|
from datacenterlight.models import StripePlan
|
||||||
|
from membership.models import StripeCustomer
|
||||||
|
from utils.stripe_utils import StripeUtils
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,7 +23,8 @@ class BaseTestCase(TestCase):
|
||||||
self.dummy_password = 'test_password'
|
self.dummy_password = 'test_password'
|
||||||
|
|
||||||
# Users
|
# Users
|
||||||
self.customer, self.another_customer = mommy.make('membership.CustomUser',
|
self.customer, self.another_customer = mommy.make(
|
||||||
|
'membership.CustomUser',
|
||||||
_quantity=2)
|
_quantity=2)
|
||||||
self.customer.set_password(self.dummy_password)
|
self.customer.set_password(self.dummy_password)
|
||||||
self.customer.save()
|
self.customer.save()
|
||||||
|
@ -94,17 +100,16 @@ class TestStripeCustomerDescription(TestCase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.dummy_password = 'test_password'
|
self.customer_password = 'test_password'
|
||||||
self.dummy_email = 'test@ungleich.ch'
|
self.customer_email = 'test@ungleich.ch'
|
||||||
|
self.customer_name = "Monty Python"
|
||||||
self.customer = mommy.make('membership.CustomUser')
|
self.customer = mommy.make('membership.CustomUser')
|
||||||
self.customer.set_password(self.dummy_password)
|
self.customer.set_password(self.customer_password)
|
||||||
self.customer.email = self.dummy_email
|
self.customer.email = self.customer_email
|
||||||
self.customer.save()
|
self.customer.save()
|
||||||
stripe.api_key = settings.STRIPE_API_PRIVATE_KEY
|
self.stripe_utils = StripeUtils()
|
||||||
|
stripe.api_key = settings.STRIPE_API_PRIVATE_KEY_TEST
|
||||||
def test_creating_stripe_customer(self):
|
self.token = stripe.Token.create(
|
||||||
test_name = "Monty Python"
|
|
||||||
token = stripe.Token.create(
|
|
||||||
card={
|
card={
|
||||||
"number": '4111111111111111',
|
"number": '4111111111111111',
|
||||||
"exp_month": 12,
|
"exp_month": 12,
|
||||||
|
@ -112,8 +117,121 @@ class TestStripeCustomerDescription(TestCase):
|
||||||
"cvc": '123'
|
"cvc": '123'
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
stripe_utils = StripeUtils()
|
self.failed_token = stripe.Token.create(
|
||||||
stripe_data = stripe_utils.create_customer(token.id, self.customer.email, test_name)
|
card={
|
||||||
|
"number": '4000000000000341',
|
||||||
|
"exp_month": 12,
|
||||||
|
"exp_year": 2022,
|
||||||
|
"cvc": '123'
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_creating_stripe_customer(self):
|
||||||
|
stripe_data = self.stripe_utils.create_customer(self.token.id,
|
||||||
|
self.customer.email,
|
||||||
|
self.customer_name)
|
||||||
self.assertEqual(stripe_data.get('error'), None)
|
self.assertEqual(stripe_data.get('error'), None)
|
||||||
customer_data = stripe_data.get('response_object')
|
customer_data = stripe_data.get('response_object')
|
||||||
self.assertEqual(customer_data.description, test_name)
|
self.assertEqual(customer_data.description, self.customer_name)
|
||||||
|
|
||||||
|
|
||||||
|
class StripePlanTestCase(TestStripeCustomerDescription):
|
||||||
|
"""
|
||||||
|
A class to test Stripe plans
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_get_stripe_plan_id_string(self):
|
||||||
|
plan_id_string = StripeUtils.get_stripe_plan_id(cpu=2, ram=20, ssd=100,
|
||||||
|
version=1, app='dcl')
|
||||||
|
self.assertEqual(plan_id_string, 'dcl-v1-cpu-2-ram-20gb-ssd-100gb')
|
||||||
|
plan_id_string = StripeUtils.get_stripe_plan_id(cpu=2, ram=20, ssd=100,
|
||||||
|
version=1, app='dcl',
|
||||||
|
hdd=200)
|
||||||
|
self.assertEqual(plan_id_string,
|
||||||
|
'dcl-v1-cpu-2-ram-20gb-ssd-100gb-hdd-200gb')
|
||||||
|
|
||||||
|
def test_get_or_create_plan(self):
|
||||||
|
stripe_plan = self.stripe_utils.get_or_create_stripe_plan(2000,
|
||||||
|
"test plan 1",
|
||||||
|
stripe_plan_id='test-plan-1')
|
||||||
|
self.assertIsNone(stripe_plan.get('error'))
|
||||||
|
self.assertIsInstance(stripe_plan.get('response_object'), StripePlan)
|
||||||
|
|
||||||
|
@patch('utils.stripe_utils.logger')
|
||||||
|
def test_create_duplicate_plans_error_handling(self, mock_logger):
|
||||||
|
"""
|
||||||
|
Test details:
|
||||||
|
1. Create a test plan in Stripe with a particular id
|
||||||
|
2. Try to recreate the plan with the same id
|
||||||
|
3. This creates a Stripe error, the code should be able to handle the error
|
||||||
|
|
||||||
|
:param mock_logger:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
unique_id = str(uuid.uuid4().hex)
|
||||||
|
new_plan_id_str = 'test-plan-{}'.format(unique_id)
|
||||||
|
stripe_plan = self.stripe_utils.get_or_create_stripe_plan(2000,
|
||||||
|
"test plan {}".format(
|
||||||
|
unique_id),
|
||||||
|
stripe_plan_id=new_plan_id_str)
|
||||||
|
self.assertIsInstance(stripe_plan.get('response_object'), StripePlan)
|
||||||
|
self.assertEqual(stripe_plan.get('response_object').stripe_plan_id,
|
||||||
|
new_plan_id_str)
|
||||||
|
|
||||||
|
# Test creating the same plan again and expect the PLAN_EXISTS_ERROR_MSG
|
||||||
|
# We first delete the local Stripe Plan, so that the code tries to create a new plan in Stripe
|
||||||
|
StripePlan.objects.filter(
|
||||||
|
stripe_plan_id=new_plan_id_str).all().delete()
|
||||||
|
stripe_plan_1 = self.stripe_utils.get_or_create_stripe_plan(2000,
|
||||||
|
"test plan {}".format(
|
||||||
|
unique_id),
|
||||||
|
stripe_plan_id=new_plan_id_str)
|
||||||
|
mock_logger.debug.assert_called_with(
|
||||||
|
self.stripe_utils.PLAN_EXISTS_ERROR_MSG.format(new_plan_id_str))
|
||||||
|
self.assertIsInstance(stripe_plan_1.get('response_object'), StripePlan)
|
||||||
|
self.assertEqual(stripe_plan_1.get('response_object').stripe_plan_id,
|
||||||
|
new_plan_id_str)
|
||||||
|
|
||||||
|
# Delete the test stripe plan that we just created
|
||||||
|
delete_result = self.stripe_utils.delete_stripe_plan(new_plan_id_str)
|
||||||
|
self.assertIsInstance(delete_result, dict)
|
||||||
|
self.assertEqual(delete_result.get('response_object'), True)
|
||||||
|
|
||||||
|
@patch('utils.stripe_utils.logger')
|
||||||
|
def test_delete_unexisting_plan_should_fail(self, mock_logger):
|
||||||
|
plan_id = 'crazy-plan-id-that-does-not-exist'
|
||||||
|
result = self.stripe_utils.delete_stripe_plan(plan_id)
|
||||||
|
self.assertIsInstance(result, dict)
|
||||||
|
self.assertEqual(result.get('response_object'), False)
|
||||||
|
mock_logger.debug.assert_called_with(
|
||||||
|
self.stripe_utils.PLAN_DOES_NOT_EXIST_ERROR_MSG.format(plan_id))
|
||||||
|
|
||||||
|
def test_subscribe_customer_to_plan(self):
|
||||||
|
stripe_plan = self.stripe_utils.get_or_create_stripe_plan(2000,
|
||||||
|
"test plan 1",
|
||||||
|
stripe_plan_id='test-plan-1')
|
||||||
|
stripe_customer = StripeCustomer.get_or_create(
|
||||||
|
email=self.customer_email,
|
||||||
|
token=self.token)
|
||||||
|
result = self.stripe_utils.subscribe_customer_to_plan(
|
||||||
|
stripe_customer.stripe_id,
|
||||||
|
[{"plan": stripe_plan.get(
|
||||||
|
'response_object').stripe_plan_id}])
|
||||||
|
self.assertIsInstance(result.get('response_object'),
|
||||||
|
stripe.Subscription)
|
||||||
|
self.assertIsNone(result.get('error'))
|
||||||
|
self.assertEqual(result.get('response_object').get('status'), 'active')
|
||||||
|
|
||||||
|
def test_subscribe_customer_to_plan_failed_payment(self):
|
||||||
|
stripe_plan = self.stripe_utils.get_or_create_stripe_plan(2000,
|
||||||
|
"test plan 1",
|
||||||
|
stripe_plan_id='test-plan-1')
|
||||||
|
stripe_customer = StripeCustomer.get_or_create(
|
||||||
|
email=self.customer_email,
|
||||||
|
token=self.failed_token)
|
||||||
|
result = self.stripe_utils.subscribe_customer_to_plan(
|
||||||
|
stripe_customer.stripe_id,
|
||||||
|
[{"plan": stripe_plan.get(
|
||||||
|
'response_object').stripe_plan_id}])
|
||||||
|
self.assertIsNone(result.get('response_object'), None)
|
||||||
|
self.assertIsNotNone(result.get('error'))
|
||||||
|
|
Loading…
Reference in a new issue