Merge branch 'master' into task/3994/dg_add_signup_clarification_line
This commit is contained in:
		
				commit
				
					
						a743e4e8a0
					
				
			
		
					 8 changed files with 185 additions and 39 deletions
				
			
		|  | @ -8,6 +8,7 @@ Next: | ||||||
|     * #3601: [dcl, hosting] Change minimum required RAM from 2GB to 1GB |     * #3601: [dcl, hosting] Change minimum required RAM from 2GB to 1GB | ||||||
|     * #3973: [dcl] Update datacenterlight and glasfaser contact address to Linthal and company name to "ungleich glarus ag" |     * #3973: [dcl] Update datacenterlight and glasfaser contact address to Linthal and company name to "ungleich glarus ag" | ||||||
|     * #3993: [dg] Fix new user membership payment by setting cardholder_name field for UserBillingAddressForm |     * #3993: [dg] Fix new user membership payment by setting cardholder_name field for UserBillingAddressForm | ||||||
|  |     * #3799: [dg] Make digital glarus billing work as monthly subscription | ||||||
| 1.2.13: 2017-12-09 | 1.2.13: 2017-12-09 | ||||||
|     * [cms] Introduce UngleichHeaderBackgroundImageAndTextSliderPlugin that allows to have scrolling images and texts |     * [cms] Introduce UngleichHeaderBackgroundImageAndTextSliderPlugin that allows to have scrolling images and texts | ||||||
|     * [cms] Remove <p> tag for ungleich cms customer item template |     * [cms] Remove <p> tag for ungleich cms customer item template | ||||||
|  |  | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | # Generated by Django 1.9.4 on 2017-12-23 22:56 | ||||||
|  | from __future__ import unicode_literals | ||||||
|  | 
 | ||||||
|  | from django.db import migrations, models | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  | 
 | ||||||
|  |     dependencies = [ | ||||||
|  |         ('digitalglarus', '0024_bookingcancellation'), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='membershiporder', | ||||||
|  |             name='stripe_subscription_id', | ||||||
|  |             field=models.CharField(max_length=100, null=True), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| import calendar | import calendar | ||||||
|  | import time | ||||||
|  | 
 | ||||||
| from datetime import datetime, date, timedelta | from datetime import datetime, date, timedelta | ||||||
| from dateutil.relativedelta import relativedelta | from dateutil.relativedelta import relativedelta | ||||||
| from django.db import models | from django.db import models | ||||||
|  | @ -59,6 +59,17 @@ class MembershipType(models.Model): | ||||||
|         return "{} - {}".format(datetime.strftime(start_date, "%b, %d %Y"), |         return "{} - {}".format(datetime.strftime(start_date, "%b, %d %Y"), | ||||||
|                                 datetime.strftime(end_date, "%b, %d %Y")) |                                 datetime.strftime(end_date, "%b, %d %Y")) | ||||||
| 
 | 
 | ||||||
|  |     @cached_property | ||||||
|  |     def next_month_in_sec_since_epoch(self): | ||||||
|  |         """ | ||||||
|  |         First day of the next month expressed in seconds since the epoch time | ||||||
|  |         :return: Time in seconds | ||||||
|  |         """ | ||||||
|  |         start_date, end_date = self.first_month_range | ||||||
|  |         first_day_next_month = end_date + timedelta(days=1) | ||||||
|  |         epoch_time = int(time.mktime(first_day_next_month.timetuple())) | ||||||
|  |         return epoch_time | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class Membership(models.Model): | class Membership(models.Model): | ||||||
|     type = models.ForeignKey(MembershipType) |     type = models.ForeignKey(MembershipType) | ||||||
|  | @ -71,12 +82,12 @@ class Membership(models.Model): | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def get_current_membership(cls, user): |     def get_current_membership(cls, user): | ||||||
| 
 |         has_order_current_month = Q( | ||||||
|         has_order_current_month = Q(membershiporder__customer__user=user, |             membershiporder__customer__user=user, | ||||||
|                                     membershiporder__created_at__month=datetime.today().month) |             membershiporder__created_at__month=datetime.today().month | ||||||
|  |         ) | ||||||
|         # import pdb;pdb.set_trace() |         # import pdb;pdb.set_trace() | ||||||
|         return cls.objects.\ |         return cls.objects.filter(has_order_current_month).last() | ||||||
|             filter(has_order_current_month).last() |  | ||||||
| 
 | 
 | ||||||
|     # def get_current_active_membership(cls, user): |     # def get_current_active_membership(cls, user): | ||||||
|     #     membership = cls.get_current_membership(user) |     #     membership = cls.get_current_membership(user) | ||||||
|  | @ -84,8 +95,7 @@ class Membership(models.Model): | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def get_by_user(cls, user): |     def get_by_user(cls, user): | ||||||
|         return cls.objects.\ |         return cls.objects.filter(membershiporder__customer__user=user).last() | ||||||
|             filter(membershiporder__customer__user=user).last() |  | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def create(cls, data): |     def create(cls, data): | ||||||
|  | @ -96,18 +106,23 @@ class Membership(models.Model): | ||||||
|     def activate_or_crete(cls, data, user): |     def activate_or_crete(cls, data, user): | ||||||
|         membership = cls.get_by_user(user) |         membership = cls.get_by_user(user) | ||||||
|         membership_id = membership.id if membership else None |         membership_id = membership.id if membership else None | ||||||
|         obj, created = cls.objects.update_or_create(id=membership_id, defaults=data) |         obj, created = cls.objects.update_or_create( | ||||||
|  |             id=membership_id, defaults=data | ||||||
|  |         ) | ||||||
|         return obj |         return obj | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def is_digitalglarus_active_member(cls, user): |     def is_digitalglarus_active_member(cls, user): | ||||||
|         # past_month = (datetime.today() - relativedelta(months=1)).month |         # past_month = (datetime.today() - relativedelta(months=1)).month | ||||||
|         has_order_current_month = Q(membershiporder__customer__user=user, |         has_order_current_month = Q( | ||||||
|                                     membershiporder__created_at__month=datetime.today().month) |             membershiporder__customer__user=user, | ||||||
|  |             membershiporder__created_at__month=datetime.today().month | ||||||
|  |         ) | ||||||
|         # has_order_past_month = Q(membershiporder__customer__user=user, |         # has_order_past_month = Q(membershiporder__customer__user=user, | ||||||
|         #  membershiporder__created_at__month=past_month) |         #  membershiporder__created_at__month=past_month) | ||||||
|         active_membership = Q(active=True) |         active_membership = Q(active=True) | ||||||
|         # return cls.objects.filter(has_order_past_month | has_order_current_month).\ |         # return cls.objects.filter( | ||||||
|  |         # has_order_past_month | has_order_current_month).\ | ||||||
|         return cls.objects.filter(has_order_current_month).\ |         return cls.objects.filter(has_order_current_month).\ | ||||||
|             filter(active_membership).exists() |             filter(active_membership).exists() | ||||||
| 
 | 
 | ||||||
|  | @ -129,6 +144,7 @@ class MembershipOrder(Ordereable, models.Model): | ||||||
|     membership = models.ForeignKey(Membership) |     membership = models.ForeignKey(Membership) | ||||||
|     start_date = models.DateField() |     start_date = models.DateField() | ||||||
|     end_date = models.DateField() |     end_date = models.DateField() | ||||||
|  |     stripe_subscription_id = models.CharField(max_length=100, null=True) | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def current_membership_dates(cls, user): |     def current_membership_dates(cls, user): | ||||||
|  | @ -172,10 +188,12 @@ class MembershipOrder(Ordereable, models.Model): | ||||||
|     @classmethod |     @classmethod | ||||||
|     def create(cls, data): |     def create(cls, data): | ||||||
|         stripe_charge = data.pop('stripe_charge', None) |         stripe_charge = data.pop('stripe_charge', None) | ||||||
|  |         stripe_subscription_id = data.pop('stripe_subscription_id', None) | ||||||
|         instance = cls.objects.create(**data) |         instance = cls.objects.create(**data) | ||||||
|         instance.stripe_charge_id = stripe_charge.id |         instance.stripe_charge_id = stripe_charge.id | ||||||
|         instance.last4 = stripe_charge.source.last4 |         instance.last4 = stripe_charge.source.last4 | ||||||
|         instance.cc_brand = stripe_charge.source.brand |         instance.cc_brand = stripe_charge.source.brand | ||||||
|  |         instance.stripe_subscription_id = stripe_subscription_id | ||||||
|         instance.save() |         instance.save() | ||||||
|         return instance |         return instance | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -95,12 +95,9 @@ | ||||||
|                   <a class="btn btn-primary btn-grey btn-deactivate print" href="{% url 'digitalglarus:membership_deactivate' %}">Deactivate</a> |                   <a class="btn btn-primary btn-grey btn-deactivate print" href="{% url 'digitalglarus:membership_deactivate' %}">Deactivate</a> | ||||||
|                 </div> |                 </div> | ||||||
|               {% elif not current_membership.active %} |               {% elif not current_membership.active %} | ||||||
|                 <form method="POST" action="{% url 'digitalglarus:membership_reactivate' %}"> |  | ||||||
|                   {% csrf_token %}  |  | ||||||
|                   <div class="edit-button"> |                   <div class="edit-button"> | ||||||
|                     <button type="submit" class="btn btn-primary btn-grey btn-deactivate print" href="{% url 'digitalglarus:membership_reactivate' %}">Reactivate</button> |                     <a class="btn btn-primary btn-grey btn-deactivate" href="{% url 'digitalglarus:membership_pricing' %}">Reactivate</a> | ||||||
|                   </div> |                   </div> | ||||||
|                 </form> |  | ||||||
|               {% endif %} |               {% endif %} | ||||||
|             {% else %} |             {% else %} | ||||||
|               <div class="edit-button"> |               <div class="edit-button"> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| from model_mommy import mommy | from model_mommy import mommy | ||||||
| from unittest import mock | from unittest import mock, skipIf | ||||||
| 
 | 
 | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
|  | @ -126,6 +126,11 @@ class MembershipPaymentViewTest(BaseTestCase): | ||||||
|         self.assertEqual(response.context['membership_type'], |         self.assertEqual(response.context['membership_type'], | ||||||
|                          self.membership_type) |                          self.membership_type) | ||||||
| 
 | 
 | ||||||
|  |     @skipIf( | ||||||
|  |         settings.STRIPE_API_PRIVATE_KEY_TEST is None or | ||||||
|  |         settings.STRIPE_API_PRIVATE_KEY_TEST is "", | ||||||
|  |         """Stripe details unavailable, so skipping CeleryTaskTestCase""" | ||||||
|  |     ) | ||||||
|     @mock.patch('utils.stripe_utils.StripeUtils.create_customer') |     @mock.patch('utils.stripe_utils.StripeUtils.create_customer') | ||||||
|     def test_post(self, stripe_mocked_call): |     def test_post(self, stripe_mocked_call): | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,3 +1,5 @@ | ||||||
|  | import logging | ||||||
|  | 
 | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.shortcuts import render | from django.shortcuts import render | ||||||
| from django.http import HttpResponseRedirect | from django.http import HttpResponseRedirect | ||||||
|  | @ -9,25 +11,34 @@ from django.utils.translation import get_language | ||||||
| from djangocms_blog.models import Post | from djangocms_blog.models import Post | ||||||
| from django.contrib import messages | from django.contrib import messages | ||||||
| from django.views.generic import DetailView, ListView | from django.views.generic import DetailView, ListView | ||||||
| from .models import Supporter |  | ||||||
| from .mixins import ChangeMembershipStatusMixin |  | ||||||
| from utils.forms import ContactUsForm | from utils.forms import ContactUsForm | ||||||
| from utils.mailer import BaseEmail | from utils.mailer import BaseEmail | ||||||
| from django.views.generic.edit import FormView | from django.views.generic.edit import FormView | ||||||
| from membership.models import StripeCustomer | from membership.models import StripeCustomer | ||||||
| from utils.views import LoginViewMixin, SignupViewMixin, \ | from utils.views import ( | ||||||
|     PasswordResetViewMixin, PasswordResetConfirmViewMixin |     LoginViewMixin, SignupViewMixin, PasswordResetViewMixin, | ||||||
| from utils.forms import PasswordResetRequestForm, UserBillingAddressForm, EditCreditCardForm |     PasswordResetConfirmViewMixin | ||||||
|  | ) | ||||||
|  | from utils.forms import ( | ||||||
|  |     PasswordResetRequestForm, UserBillingAddressForm, EditCreditCardForm | ||||||
|  | ) | ||||||
| from utils.stripe_utils import StripeUtils | from utils.stripe_utils import StripeUtils | ||||||
| from utils.models import UserBillingAddress | from utils.models import UserBillingAddress | ||||||
|  | from utils.tasks import send_plain_email_task | ||||||
| 
 | 
 | ||||||
| from .forms import LoginForm, SignupForm, MembershipBillingForm, BookingDateForm,\ | from .forms import ( | ||||||
|  |     LoginForm, SignupForm, MembershipBillingForm, BookingDateForm, | ||||||
|     BookingBillingForm, CancelBookingForm |     BookingBillingForm, CancelBookingForm | ||||||
|  | ) | ||||||
|  | from .models import ( | ||||||
|  |     MembershipType, Membership, MembershipOrder, Booking, BookingPrice, | ||||||
|  |     BookingOrder, BookingCancellation, Supporter | ||||||
|  | ) | ||||||
|  | from .mixins import ( | ||||||
|  |     MembershipRequiredMixin, IsNotMemberMixin, ChangeMembershipStatusMixin | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| from .models import MembershipType, Membership, MembershipOrder, Booking, BookingPrice,\ | logger = logging.getLogger(__name__) | ||||||
|     BookingOrder, BookingCancellation |  | ||||||
| 
 |  | ||||||
| from .mixins import MembershipRequiredMixin, IsNotMemberMixin |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class IndexView(TemplateView): | class IndexView(TemplateView): | ||||||
|  | @ -271,7 +282,6 @@ class BookingPaymentView(LoginRequiredMixin, MembershipRequiredMixin, FormView): | ||||||
|         booking_data = { |         booking_data = { | ||||||
|             'start_date': start_date, |             'start_date': start_date, | ||||||
|             'end_date': end_date, |             'end_date': end_date, | ||||||
|             'start_date': start_date, |  | ||||||
|             'free_days': free_days, |             'free_days': free_days, | ||||||
|             'price': normal_price, |             'price': normal_price, | ||||||
|             'final_price': final_price, |             'final_price': final_price, | ||||||
|  | @ -355,16 +365,21 @@ class MembershipPaymentView(LoginRequiredMixin, IsNotMemberMixin, FormView): | ||||||
|             membership_type = data.get('membership_type') |             membership_type = data.get('membership_type') | ||||||
| 
 | 
 | ||||||
|             # Get or create stripe customer |             # Get or create stripe customer | ||||||
|             customer = StripeCustomer.get_or_create(email=self.request.user.email, |             customer = StripeCustomer.get_or_create( | ||||||
|                                                     token=token) |                 email=self.request.user.email, token=token | ||||||
|  |             ) | ||||||
|             if not customer: |             if not customer: | ||||||
|                 form.add_error("__all__", "Invalid credit card") |                 form.add_error("__all__", "Invalid credit card") | ||||||
|                 return self.render_to_response(self.get_context_data(form=form)) |                 return self.render_to_response( | ||||||
|  |                     self.get_context_data(form=form) | ||||||
|  |                 ) | ||||||
| 
 | 
 | ||||||
|             # 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=membership_type.first_month_price, |             charge_response = stripe_utils.make_charge( | ||||||
|                                                        customer=customer.stripe_id) |                 amount=membership_type.first_month_price, | ||||||
|  |                 customer=customer.stripe_id | ||||||
|  |             ) | ||||||
|             charge = charge_response.get('response_object') |             charge = charge_response.get('response_object') | ||||||
| 
 | 
 | ||||||
|             # Check if the payment was approved |             # Check if the payment was approved | ||||||
|  | @ -373,6 +388,58 @@ class MembershipPaymentView(LoginRequiredMixin, IsNotMemberMixin, FormView): | ||||||
|                     'paymentError': charge_response.get('error'), |                     'paymentError': charge_response.get('error'), | ||||||
|                     'form': form |                     'form': form | ||||||
|                 }) |                 }) | ||||||
|  |                 email_to_admin_data = { | ||||||
|  |                     'subject': "Could not create charge for Digital Glarus " | ||||||
|  |                                "user: {user}".format( | ||||||
|  |                                     user=self.request.user.email | ||||||
|  |                                 ), | ||||||
|  |                     'from_email': 'info@digitalglarus.ch', | ||||||
|  |                     'to': ['info@ungleich.ch'], | ||||||
|  |                     'body': "\n".join( | ||||||
|  |                         ["%s=%s" % (k, v) for (k, v) in | ||||||
|  |                          charge_response.items()]), | ||||||
|  |                 } | ||||||
|  |                 send_plain_email_task.delay(email_to_admin_data) | ||||||
|  |                 return render(request, self.template_name, context) | ||||||
|  | 
 | ||||||
|  |             # Subscribe the customer to dg plan from the next month onwards | ||||||
|  |             stripe_plan = stripe_utils.get_or_create_stripe_plan( | ||||||
|  |                 amount=membership_type.price, | ||||||
|  |                 name='Digital Glarus {sub_type_name} Subscription'.format( | ||||||
|  |                     sub_type_name=membership_type.name | ||||||
|  |                 ), | ||||||
|  |                 stripe_plan_id='dg-{sub_type_name}'.format( | ||||||
|  |                     sub_type_name=membership_type.name | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             subscription_result = stripe_utils.subscribe_customer_to_plan( | ||||||
|  |                 customer.stripe_id, | ||||||
|  |                 [{"plan": stripe_plan.get('response_object').stripe_plan_id}], | ||||||
|  |                 trial_end=membership_type.next_month_in_sec_since_epoch | ||||||
|  |             ) | ||||||
|  |             stripe_subscription_obj = subscription_result.get( | ||||||
|  |                 'response_object' | ||||||
|  |             ) | ||||||
|  |             # Check if call to create subscription was ok | ||||||
|  |             if (stripe_subscription_obj is None or | ||||||
|  |                 (stripe_subscription_obj.status != 'active' and | ||||||
|  |                  stripe_subscription_obj.status != 'trialing')): | ||||||
|  |                 context.update({ | ||||||
|  |                     'paymentError': subscription_result.get('error'), | ||||||
|  |                     'form': form | ||||||
|  |                 }) | ||||||
|  |                 email_to_admin_data = { | ||||||
|  |                     'subject': "Could not create Stripe subscription for " | ||||||
|  |                                "Digital Glarus user: {user}".format( | ||||||
|  |                                     user=self.request.user.email | ||||||
|  |                                 ), | ||||||
|  |                     'from_email': 'info@digitalglarus.ch', | ||||||
|  |                     'to': ['info@ungleich.ch'], | ||||||
|  |                     'body': "\n".join( | ||||||
|  |                         ["%s=%s" % (k, v) for (k, v) in | ||||||
|  |                          subscription_result.items()]), | ||||||
|  |                 } | ||||||
|  |                 send_plain_email_task.delay(email_to_admin_data) | ||||||
|                 return render(request, self.template_name, context) |                 return render(request, self.template_name, context) | ||||||
| 
 | 
 | ||||||
|             charge = charge_response.get('response_object') |             charge = charge_response.get('response_object') | ||||||
|  | @ -412,6 +479,7 @@ class MembershipPaymentView(LoginRequiredMixin, IsNotMemberMixin, FormView): | ||||||
|                 'customer': customer, |                 'customer': customer, | ||||||
|                 'billing_address': billing_address, |                 'billing_address': billing_address, | ||||||
|                 'stripe_charge': charge, |                 'stripe_charge': charge, | ||||||
|  |                 'stripe_subscription_id': stripe_subscription_obj.id, | ||||||
|                 'amount': membership_type.first_month_price, |                 'amount': membership_type.first_month_price, | ||||||
|                 'start_date': membership_start_date, |                 'start_date': membership_start_date, | ||||||
|                 'end_date': membership_end_date |                 'end_date': membership_end_date | ||||||
|  | @ -481,8 +549,43 @@ class MembershipDeactivateView(LoginRequiredMixin, UpdateView): | ||||||
|     def post(self, *args, **kwargs): |     def post(self, *args, **kwargs): | ||||||
|         membership = self.get_object() |         membership = self.get_object() | ||||||
|         membership.deactivate() |         membership.deactivate() | ||||||
| 
 |         messages.add_message( | ||||||
|         messages.add_message(self.request, messages.SUCCESS, self.success_message) |             self.request, messages.SUCCESS, self.success_message | ||||||
|  |         ) | ||||||
|  |         # cancel Stripe subscription | ||||||
|  |         stripe_utils = StripeUtils() | ||||||
|  |         membership_order = MembershipOrder.objects.filter( | ||||||
|  |             customer__user=self.request.user | ||||||
|  |         ).last() | ||||||
|  |         if membership_order: | ||||||
|  |             if membership_order.stripe_subscription_id: | ||||||
|  |                 result = stripe_utils.unsubscribe_customer( | ||||||
|  |                     subscription_id=membership_order.stripe_subscription_id | ||||||
|  |                 ) | ||||||
|  |                 stripe_subscription_obj = result.get('response_object') | ||||||
|  |                 # Check if the subscription was canceled | ||||||
|  |                 if (stripe_subscription_obj is None or | ||||||
|  |                         stripe_subscription_obj.status != 'canceled'): | ||||||
|  |                     error_msg = result.get('error') | ||||||
|  |                     logger.error( | ||||||
|  |                         "Could not cancel Digital Glarus subscription. " | ||||||
|  |                         "Reason: {reason}".format( | ||||||
|  |                             reason=error_msg | ||||||
|  |                         ) | ||||||
|  |                     ) | ||||||
|  |             else: | ||||||
|  |                 logger.error( | ||||||
|  |                     "User {user} may have Stripe subscriptions created " | ||||||
|  |                     "manually. Please check.".format( | ||||||
|  |                         user=self.request.user.name | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |         else: | ||||||
|  |             logger.error( | ||||||
|  |                 "MembershipOrder for {user} not found".format( | ||||||
|  |                             user=self.request.user.name | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
| 
 | 
 | ||||||
|         return HttpResponseRedirect(self.success_url) |         return HttpResponseRedirect(self.success_url) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | {% load i18n %} | ||||||
| {% if instance.image %} | {% if instance.image %} | ||||||
|   <div class="bg_img" style="background-image:url({{ instance.image.url }})"></div> |   <div class="bg_img" style="background-image:url({{ instance.image.url }})"></div> | ||||||
| {% endif %} | {% endif %} | ||||||
|  |  | ||||||
|  | @ -210,12 +210,14 @@ class StripeUtils(object): | ||||||
|         return return_value |         return return_value | ||||||
| 
 | 
 | ||||||
|     @handleStripeError |     @handleStripeError | ||||||
|     def subscribe_customer_to_plan(self, customer, plans): |     def subscribe_customer_to_plan(self, customer, plans, trial_end=None): | ||||||
|         """ |         """ | ||||||
|         Subscribes the given customer to the list of given plans |         Subscribes the given customer to the list of given plans | ||||||
| 
 | 
 | ||||||
|         :param customer: The stripe customer identifier |         :param customer: The stripe customer identifier | ||||||
|         :param plans: A list of stripe plans. |         :param plans: A list of stripe plans. | ||||||
|  |         :param trial_end: An integer representing when the Stripe subscription | ||||||
|  |                is supposed to end | ||||||
|         Ref: https://stripe.com/docs/api/python#create_subscription-items |         Ref: https://stripe.com/docs/api/python#create_subscription-items | ||||||
|               e.g. |               e.g. | ||||||
|                     plans = [ |                     plans = [ | ||||||
|  | @ -227,8 +229,7 @@ class StripeUtils(object): | ||||||
|         """ |         """ | ||||||
| 
 | 
 | ||||||
|         subscription_result = self.stripe.Subscription.create( |         subscription_result = self.stripe.Subscription.create( | ||||||
|             customer=customer, |             customer=customer, items=plans, trial_end=trial_end | ||||||
|             items=plans, |  | ||||||
|         ) |         ) | ||||||
|         return subscription_result |         return subscription_result | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue