This commit is contained in:
tmslav 2016-05-18 18:44:46 +00:00
commit 150ab173a7
15 changed files with 528 additions and 39 deletions

View file

@ -1,6 +1,5 @@
from django.contrib import admin
from .models import RailsBetaUser, VirtualMachineType
from .models import VirtualMachineType
admin.site.register(RailsBetaUser)
admin.site.register(VirtualMachineType)

View file

@ -10,8 +10,8 @@ class Command(BaseCommand):
hetzner = {
'base_price': 10,
'core_price': 10,
'memory_price': 10,
'disk_size_price': 10,
'memory_price': 5,
'disk_size_price': 1,
'description': 'VM auf einzelner HW, Raid1, kein HA'
}

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-05-12 04:48
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('hosting', '0014_auto_20160505_0541'),
]
operations = [
migrations.RenameField(
model_name='hostingorder',
old_name='VMPlan',
new_name='vm_plan',
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-05-14 06:50
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('hosting', '0015_auto_20160512_0448'),
]
operations = [
migrations.DeleteModel(
name='RailsBetaUser',
),
]

View file

@ -10,14 +10,6 @@ from utils.models import BillingAddress
from .managers import VMPlansManager
class RailsBetaUser(models.Model):
email = models.EmailField(unique=True)
received_date = models.DateTimeField('date received')
def __str__(self):
return "%s - %s" % (self.email, self.received_date)
class VirtualMachineType(models.Model):
HETZNER_NUG = 'hetzner_nug'
@ -86,6 +78,9 @@ class VirtualMachinePlan(models.Model):
objects = VMPlansManager()
def __str__(self):
return "%s" % (self.id)
@cached_property
def hosting_company_name(self):
return self.vm_type.get_hosting_company_display()
@ -106,7 +101,7 @@ class HostingOrder(models.Model):
ORDER_APPROVED_STATUS = 'Approved'
ORDER_DECLINED_STATUS = 'Declined'
VMPlan = models.ForeignKey(VirtualMachinePlan, related_name='hosting_orders')
vm_plan = models.ForeignKey(VirtualMachinePlan, related_name='hosting_orders')
customer = models.ForeignKey(StripeCustomer)
billing_address = models.ForeignKey(BillingAddress)
created_at = models.DateTimeField(auto_now_add=True)
@ -115,13 +110,16 @@ class HostingOrder(models.Model):
cc_brand = models.CharField(max_length=10)
stripe_charge_id = models.CharField(max_length=100, null=True)
def __str__(self):
return "%s" % (self.id)
@cached_property
def status(self):
return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS
@classmethod
def create(cls, VMPlan=None, customer=None, billing_address=None):
instance = cls.objects.create(VMPlan=VMPlan, customer=customer,
def create(cls, vm_plan=None, customer=None, billing_address=None):
instance = cls.objects.create(vm_plan=vm_plan, customer=customer,
billing_address=billing_address)
return instance

View file

@ -6,7 +6,7 @@
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<div class="invoice-title">
<h2>Invoice</h2><h3 class="pull-right">Order # {{object.id}}</h3>
<h2>Invoice</h2><h3 class="pull-right">Order # {{order.id}}</h3>
</div>
<hr>
<div class="row">
@ -14,18 +14,18 @@
<address>
<h3><b>Billed To:</b></h3>
{{user.name}}<br>
{{object.billing_address.street_address}},{{order.billing_address.postal_code}}<br>
{{object.billing_address.city}}, {{object.billing_address.country}}.
{{order.billing_address.street_address}},{{order.billing_address.postal_code}}<br>
{{order.billing_address.city}}, {{order.billing_address.country}}.
</address>
</div>
<div class="col-xs-6 text-right">
<address>
<strong>Order Date:</strong><br>
{{object.created_at}}<br><br>
{{order.created_at}}<br><br>
<strong>Status:</strong><br>
<strong class="{% if object.status == 'Approved' %}text-success
<strong class="{% if order.status == 'Approved' %}text-success
{%else%} text-danger
{% endif %}">{{object.status}}</strong>
{% endif %}">{{order.status}}</strong>
<br><br>
</address>
@ -35,7 +35,7 @@
<div class="col-xs-6">
<address>
<strong>Payment Method:</strong><br>
{{object.cc_brand}} ending **** {{object.last4}}<br>
{{order.cc_brand}} ending **** {{order.last4}}<br>
{{user.email}}
</address>
</div>
@ -48,15 +48,15 @@
<h3><b>Order summary</b></h3>
<hr>
<div class="content">
<p><b>Type</b> <span class="pull-right">{{object.VMPlan.hosting_company_name}}</span></p>
<p><b>Type</b> <span class="pull-right">{{order.vm_plan.hosting_company_name}}</span></p>
<hr>
<p><b>Cores</b> <span class="pull-right">{{object.VMPlan.cores}}</span></p>
<p><b>Cores</b> <span class="pull-right">{{order.vm_plan.cores}}</span></p>
<hr>
<p><b>Memory</b> <span class="pull-right">{{object.VMPlan.memory}} GiB</span></p>
<p><b>Memory</b> <span class="pull-right">{{order.vm_plan.memory}} GiB</span></p>
<hr>
<p><b>Disk space</b> <span class="pull-right">{{object.VMPlan.disk_size}} GiB</span></p>
<p><b>Disk space</b> <span class="pull-right">{{order.vm_plan.disk_size}} GiB</span></p>
<hr>
<h4>Total<p class="pull-right"><b>{{object.VMPlan.price}} CHF</b></p></h4>
<h4>Total<p class="pull-right"><b>{{order.vm_plan.price}} CHF</b></p></h4>
</div>
</div>
</div>

View file

@ -23,7 +23,7 @@
<tr>
<td scope="row">{{order.id}}</td>
<td>{{order.created_at}}</td>
<td>{{order.VMPlan.price}} CHF</td>
<td>{{order.vm_plan.price}} CHF</td>
<td>{% if order.approved %}
<span class="text-success strong">Approved</span>
{% else%}

View file

@ -104,7 +104,7 @@
<tr>
<td scope="row">{{order.id}}</td>
<td>{{order.created_at}}</td>
<td>{{order.VMPlan.price}} CHF</td>
<td>{{order.vm_plan.price}} CHF</td>
<td>{% if order.approved %}
<span class="text-success strong">Approved</span>
{% else%}

75
hosting/test_models.py Normal file
View file

@ -0,0 +1,75 @@
from django.test import TestCase
from django.core.management import call_command
from .models import VirtualMachineType
class VirtualMachineTypeModelTest(TestCase):
def setUp(self):
self.HETZNER_NUG_NAME = 'hetzner_nug'
self.HETZNER_NAME = 'hetzner'
self.HETZNER_RAID6_NAME = 'hetzner_raid6'
self.HETZNER_GLUSTERFS_NAME = 'hetzner_glusterfs'
self.BERN_NAME = 'bern'
self.HETZNER_NUG_EXPECTED_PRICE = 79
self.HETZNER_EXPECTED_PRICE = 180
self.HETZNER_RAID6_EXPECTED_PRICE = 216
self.HETZNER_GLUSTERFS_EXPECTED_PRICE = 252
self.BERN_EXPECTED_PRICE = 202
call_command('create_vm_types')
def test_calculate_price(self):
# hetzner_nug
specifications = {
'cores': 2,
'memory': 10,
'disk_size': 100
}
vm_type = VirtualMachineType.objects.get(hosting_company=self.HETZNER_NUG_NAME)
calculated_price = vm_type.calculate_price(specifications)
self.assertEqual(calculated_price, self.HETZNER_NUG_EXPECTED_PRICE)
# hetzner
specifications = {
'cores': 2,
'memory': 10,
'disk_size': 100
}
vm_type = VirtualMachineType.objects.get(hosting_company=self.HETZNER_NAME)
calculated_price = vm_type.calculate_price(specifications)
self.assertEqual(calculated_price, self.HETZNER_EXPECTED_PRICE)
# hetzner_raid6
specifications = {
'cores': 2,
'memory': 10,
'disk_size': 100
}
vm_type = VirtualMachineType.objects.get(hosting_company=self.HETZNER_RAID6_NAME)
calculated_price = vm_type.calculate_price(specifications)
self.assertEqual(calculated_price, self.HETZNER_RAID6_EXPECTED_PRICE)
# hetzner_glusterfs
specifications = {
'cores': 2,
'memory': 10,
'disk_size': 100
}
vm_type = VirtualMachineType.objects.get(hosting_company=self.HETZNER_GLUSTERFS_NAME)
calculated_price = vm_type.calculate_price(specifications)
self.assertEqual(calculated_price, self.HETZNER_GLUSTERFS_EXPECTED_PRICE)
# bern
specifications = {
'cores': 2,
'memory': 10,
'disk_size': 100
}
vm_type = VirtualMachineType.objects.get(hosting_company=self.BERN_NAME)
calculated_price = vm_type.calculate_price(specifications)
self.assertEqual(calculated_price, self.BERN_EXPECTED_PRICE)

View file

@ -1,8 +1,18 @@
from django.test import TestCase
from unittest import mock
from django.conf import settings
from django.test import TestCase, RequestFactory
from django.core.urlresolvers import reverse
from django.core.urlresolvers import resolve
from .models import VirtualMachineType
from .views import DjangoHostingView, RailsHostingView, NodeJSHostingView
from model_mommy import mommy
from membership.models import CustomUser, StripeCustomer
from .models import VirtualMachineType, HostingOrder, VirtualMachinePlan
from .views import DjangoHostingView, RailsHostingView, NodeJSHostingView, LoginView, SignupView, \
PaymentVMView, OrdersHostingDetailView, OrdersHostingListView, VirtualMachineDetailView, \
VirtualMachinesPlanListView
from utils.tests import BaseTestCase
class ProcessVMSelectionTestMixin(object):
@ -70,3 +80,280 @@ class NodeJSHostingViewTest(TestCase, ProcessVMSelectionTestMixin):
'email': "info@node-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(),
}
class PaymentVMViewTest(BaseTestCase):
def setUp(self):
super(PaymentVMViewTest, self).setUp()
self.view = PaymentVMView
# VM
self.vm = mommy.make(VirtualMachineType, base_price=10000,
memory_price=100,
core_price=1000,
disk_size_price=1)
# post data
self.billing_address = {
'street_address': 'street name',
'city': 'MyCity',
'postal_code': '32123123123123',
'country': 'VE',
'token': 'a23kfmslwxhkwis'
}
# urls
self.url = reverse('hosting:payment')
# Session data
self.session_data = {
'vm_specs': {
'hosting_company': self.vm.hosting_company,
'cores': 1,
'memory': 10,
'disk_size': 10000,
'price': 22000,
}
}
session = self.customer_client.session
session.update(self.session_data)
session.save()
def test_url_resolve_to_view_correctly(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, self.view.__name__)
@mock.patch('utils.stripe_utils.StripeUtils.create_customer')
def test_post(self, stripe_mocked_call):
# Anonymous user should get redirect to login
response = self.client.post(self.url)
expected_url = "%s?next=%s" % (reverse('hosting:login'), reverse('hosting:payment'))
self.assertRedirects(response, expected_url=expected_url,
status_code=302, target_status_code=200)
# Customer user should be able to pay
stripe_mocked_call.return_value = {
'paid': True,
'response_object': self.stripe_mocked_customer,
'error': None
}
response = self.customer_client.post(self.url, self.billing_address)
self.assertEqual(response.status_code, 200)
self.assertTrue(StripeCustomer.objects.filter(user__email=self.customer.email).exists())
stripe_customer = StripeCustomer.objects.get(user__email=self.customer.email)
self.assertEqual(stripe_customer.user, self.customer)
self.assertTrue(HostingOrder.objects.filter(customer=stripe_customer).exists())
hosting_order = HostingOrder.objects.filter(customer=stripe_customer)[0]
vm_plan = {
'cores': hosting_order.vm_plan.cores,
'memory': hosting_order.vm_plan.memory,
'disk_size': hosting_order.vm_plan.disk_size,
'price': hosting_order.vm_plan.price,
'hosting_company': hosting_order.vm_plan.vm_type.hosting_company
}
self.assertEqual(vm_plan, self.session_data.get('vm_specs'))
def test_get(self):
# Anonymous user should get redirect to login
response = self.client.get(self.url)
expected_url = "%s?next=%s" % (reverse('hosting:login'), reverse('hosting:payment'))
self.assertRedirects(response, expected_url=expected_url,
status_code=302, target_status_code=200)
# Logged user should get the page
response = self.customer_client.get(self.url, follow=True)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['stripe_key'],
settings.STRIPE_API_PUBLIC_KEY)
class VirtualMachineDetailViewTest(BaseTestCase):
def setUp(self):
super(VirtualMachineDetailViewTest, self).setUp()
self.stripe_customer = mommy.make(StripeCustomer, user=self.customer)
self.vm = mommy.make(VirtualMachinePlan)
self.order = mommy.make(HostingOrder, customer=self.stripe_customer, vm_plan=self.vm)
self.url = reverse('hosting:virtual_machines', kwargs={'pk': self.vm.id})
self.view = VirtualMachineDetailView()
self.expected_template = 'hosting/virtual_machine_detail.html'
def url_resolve_to_view_correctly(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, self.view.__name__)
def test_get(self):
# Anonymous user should get redirect to login
response = self.client.get(self.url)
expected_url = "%s?next=%s" % (reverse('hosting:login'),
reverse('hosting:virtual_machines',
kwargs={'pk': self.vm.id}))
self.assertRedirects(response, expected_url=expected_url,
status_code=302, target_status_code=200)
# Customer should be able to get data
response = self.customer_client.get(self.url, follow=True)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['virtual_machine'], self.vm)
self.assertTemplateUsed(response, self.expected_template)
class VirtualMachinesPlanListViewTest(BaseTestCase):
def setUp(self):
super(VirtualMachinesPlanListViewTest, self).setUp()
self.stripe_customer = mommy.make(StripeCustomer, user=self.customer)
mommy.make(HostingOrder, customer=self.stripe_customer, approved=True, _quantity=20)
_vms = VirtualMachinePlan.objects.all()
self.vms = sorted(_vms, key=lambda vm: vm.id, reverse=True)
self.url = reverse('hosting:virtual_machines')
self.view = VirtualMachinesPlanListView()
self.expected_template = 'hosting/virtual_machines.html'
def url_resolve_to_view_correctly(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, self.view.__name__)
def test_get(self):
# Anonymous user should get redirect to login
response = self.client.get(self.url)
expected_url = "%s?next=%s" % (reverse('hosting:login'),
reverse('hosting:virtual_machines'))
self.assertRedirects(response, expected_url=expected_url,
status_code=302, target_status_code=200)
# Customer should be able to get his orders
response = self.customer_client.get(self.url, follow=True)
self.assertEqual(response.status_code, 200)
self.assertEqual(list(response.context['vms']), self.vms[:10])
self.assertTemplateUsed(response, self.expected_template)
class OrderHostingDetailViewTest(BaseTestCase):
def setUp(self):
super(OrderHostingDetailViewTest, self).setUp()
self.stripe_customer = mommy.make(StripeCustomer, user=self.customer)
self.order = mommy.make(HostingOrder, customer=self.stripe_customer)
self.url = reverse('hosting:orders', kwargs={'pk': self.order.id})
self.view = OrdersHostingDetailView()
self.expected_template = 'hosting/order_detail.html'
def url_resolve_to_view_correctly(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, self.view.__name__)
def test_get(self):
# Anonymous user should get redirect to login
response = self.client.get(self.url)
expected_url = "%s?next=%s" % (reverse('hosting:login'),
reverse('hosting:orders', kwargs={'pk': self.order.id}))
self.assertRedirects(response, expected_url=expected_url,
status_code=302, target_status_code=200)
# Customer should be able to get data
response = self.customer_client.get(self.url, follow=True)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['order'], self.order)
self.assertTemplateUsed(response, self.expected_template)
class OrdersHostingListViewTest(BaseTestCase):
def setUp(self):
super(OrdersHostingListViewTest, self).setUp()
self.stripe_customer = mommy.make(StripeCustomer, user=self.customer)
_orders = mommy.make(HostingOrder, customer=self.stripe_customer, _quantity=20)
self.orders = sorted(_orders, key=lambda order: order.id, reverse=True)
self.url = reverse('hosting:orders')
self.view = OrdersHostingListView()
self.expected_template = 'hosting/orders.html'
def url_resolve_to_view_correctly(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, self.view.__name__)
def test_get(self):
# Anonymous user should get redirect to login
response = self.client.get(self.url)
expected_url = "%s?next=%s" % (reverse('hosting:login'), reverse('hosting:orders'))
self.assertRedirects(response, expected_url=expected_url,
status_code=302, target_status_code=200)
# Customer should be able to get his orders
response = self.customer_client.get(self.url, follow=True)
self.assertEqual(response.status_code, 200)
self.assertEqual(list(response.context['orders']), self.orders[:10])
self.assertTemplateUsed(response, self.expected_template)
class LoginViewTest(TestCase):
def setUp(self):
self.url = reverse('hosting:login')
self.view = LoginView
self.expected_template = 'hosting/login.html'
self.user = mommy.make('membership.CustomUser')
self.password = 'fake_password'
self.user.set_password(self.password)
self.user.save()
def test_url_resolve_to_view_correctly(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, self.view.__name__)
def test_get(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, self.expected_template)
def test_anonymous_user_can_login(self):
data = {
'email': self.user.email,
'password': self.password
}
response = self.client.post(self.url, data=data, follow=True)
self.assertEqual(response.context['user'], self.user)
self.assertEqual(response.status_code, 200)
class SignupViewTest(TestCase):
def setUp(self):
self.url = reverse('hosting:signup')
self.expected_template = 'hosting/signup.html'
self.view = SignupView
self.signup_data = {
'name': 'ungleich',
'email': 'test@ungleich.com',
'password': 'fake_password',
'confirm_password': 'fake_password',
}
def test_url_resolve_to_view_correctly(self):
found = resolve(self.url)
self.assertEqual(found.func.__name__, self.view.__name__)
def test_get(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, self.expected_template)
def test_anonymous_user_can_signup(self):
response = self.client.post(self.url, data=self.signup_data, follow=True)
self.user = CustomUser.objects.get(email=self.signup_data.get('email'))
self.assertEqual(response.context['user'], self.user)
self.assertEqual(response.status_code, 200)

View file

@ -3,7 +3,7 @@ from django.conf.urls import url
from .views import DjangoHostingView, RailsHostingView, PaymentVMView, \
NodeJSHostingView, LoginView, SignupView, IndexView, \
OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView,\
VirtualMachineDetailListView
VirtualMachineDetailView
urlpatterns = [
# url(r'pricing/?$', VMPricingView.as_view(), name='pricing'),
@ -15,7 +15,7 @@ urlpatterns = [
url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'),
url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'),
url(r'my-virtual-machines/?$', VirtualMachinesPlanListView.as_view(), name='virtual_machines'),
url(r'my-virtual-machines/(?P<pk>\d+)/?$', VirtualMachineDetailListView.as_view(),
url(r'my-virtual-machines/(?P<pk>\d+)/?$', VirtualMachineDetailView.as_view(),
name='virtual_machines'),
url(r'login/?$', LoginView.as_view(), name='login'),
url(r'signup/?$', SignupView.as_view(), name='signup'),

View file

@ -143,8 +143,9 @@ class SignupView(CreateView):
return HttpResponseRedirect(self.get_success_url())
class PaymentVMView(FormView):
class PaymentVMView(LoginRequiredMixin, FormView):
template_name = 'hosting/payment.html'
login_url = reverse_lazy('hosting:login')
form_class = BillingAddressForm
def get_context_data(self, **kwargs):
@ -183,7 +184,7 @@ class PaymentVMView(FormView):
billing_address = form.save()
# Create a Hosting Order
order = HostingOrder.create(VMPlan=plan, customer=customer,
order = HostingOrder.create(vm_plan=plan, customer=customer,
billing_address=billing_address)
# Make stripe charge to a customer
@ -219,6 +220,7 @@ class PaymentVMView(FormView):
class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
template_name = "hosting/order_detail.html"
context_object_name = "order"
login_url = reverse_lazy('hosting:login')
model = HostingOrder
@ -229,6 +231,7 @@ class OrdersHostingListView(LoginRequiredMixin, ListView):
context_object_name = "orders"
model = HostingOrder
paginate_by = 10
ordering = '-id'
def get_queryset(self):
user = self.request.user
@ -242,6 +245,7 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
context_object_name = "vms"
model = VirtualMachinePlan
paginate_by = 10
ordering = '-id'
def get_queryset(self):
user = self.request.user
@ -249,7 +253,7 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
return super(VirtualMachinesPlanListView, self).get_queryset()
class VirtualMachineDetailListView(LoginRequiredMixin, DetailView):
class VirtualMachineDetailView(LoginRequiredMixin, DetailView):
template_name = "hosting/virtual_machine_detail.html"
login_url = reverse_lazy('hosting:login')
model = VirtualMachinePlan

View file

@ -16,6 +16,7 @@ psycopg2
django-mptt
easy_thumbnails
django-polymorphic
model-mommy
#PLUGINS
djangocms_flash

View file

@ -1,5 +1,5 @@
from django.test import TestCase
from .forms import ContactUsForm
from .forms import ContactUsForm, BillingAddressForm
class ContactUsFormTest(TestCase):
@ -23,3 +23,27 @@ class ContactUsFormTest(TestCase):
def test_invalid_form(self):
form = ContactUsForm(data=self.incompleted_data)
self.assertFalse(form.is_valid())
class BillingAddressFormTest(TestCase):
def setUp(self):
self.completed_data = {
'street_address': 'street name',
'city': 'MyCity',
'postal_code': '32123123123123',
'country': 'VE',
'token': 'a23kfmslwxhkwis'
}
self.incompleted_data = {
'street_address': 'test',
}
def test_valid_form(self):
form = BillingAddressForm(data=self.completed_data)
self.assertTrue(form.is_valid())
def test_invalid_form(self):
form = BillingAddressForm(data=self.incompleted_data)
self.assertFalse(form.is_valid())

View file

@ -1,3 +1,66 @@
from django.test import TestCase
from django.test import Client
from model_mommy import mommy
# Create your tests here.
class BaseTestCase(TestCase):
"""
Base class to initialize the test cases
"""
def setUp(self):
# Password
self.dummy_password = 'test_password'
# Users
self.customer, self.another_customer = mommy.make('membership.CustomUser',
_quantity=2)
self.customer.set_password(self.dummy_password)
self.customer.save()
self.another_customer.set_password(self.dummy_password)
self.another_customer.save()
# Stripe mocked data
self.stripe_mocked_customer = self.customer_stripe_mocked_data()
# Clients
self.customer_client = self.get_client(self.customer)
self.another_customer_client = self.get_client(self.another_customer)
def get_client(self, user):
"""
Authenticate a user and return the client
"""
client = Client()
client.login(email=user.email, password=self.dummy_password)
return client
def customer_stripe_mocked_data(self):
return {
"id": "cus_8R1y9UWaIIjZqr",
"object": "customer",
"currency": "usd",
"default_source": "card_18A9up2eZvKYlo2Cq2RJMGeF",
"email": "vmedixtodd+1@gmail.com",
"livemode": False,
"metadata": {
},
"shipping": None,
"sources": {
"object": "list",
"data": [{
"id": "card_18A9up2eZvKYlo2Cq2RJMGeF",
"object": "card",
"brand": "Visa",
"country": "US",
"customer": "cus_8R1y9UWaIIjZqr",
"cvc_check": "pass",
"dynamic_last4": None,
"exp_month": 12,
"exp_year": 2018,
"funding": "credit",
"last4": "4242",
}]
}
}