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
 | 
			
		||||
    * #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
 | 
			
		||||
    * #3799: [dg] Make digital glarus billing work as monthly subscription
 | 
			
		||||
1.2.13: 2017-12-09
 | 
			
		||||
    * [cms] Introduce UngleichHeaderBackgroundImageAndTextSliderPlugin that allows to have scrolling images and texts
 | 
			
		||||
    * [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 time
 | 
			
		||||
 | 
			
		||||
from datetime import datetime, date, timedelta
 | 
			
		||||
from dateutil.relativedelta import relativedelta
 | 
			
		||||
from django.db import models
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +59,17 @@ class MembershipType(models.Model):
 | 
			
		|||
        return "{} - {}".format(datetime.strftime(start_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):
 | 
			
		||||
    type = models.ForeignKey(MembershipType)
 | 
			
		||||
| 
						 | 
				
			
			@ -71,12 +82,12 @@ class Membership(models.Model):
 | 
			
		|||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_current_membership(cls, user):
 | 
			
		||||
 | 
			
		||||
        has_order_current_month = Q(membershiporder__customer__user=user,
 | 
			
		||||
                                    membershiporder__created_at__month=datetime.today().month)
 | 
			
		||||
        has_order_current_month = Q(
 | 
			
		||||
            membershiporder__customer__user=user,
 | 
			
		||||
            membershiporder__created_at__month=datetime.today().month
 | 
			
		||||
        )
 | 
			
		||||
        # import pdb;pdb.set_trace()
 | 
			
		||||
        return cls.objects.\
 | 
			
		||||
            filter(has_order_current_month).last()
 | 
			
		||||
        return cls.objects.filter(has_order_current_month).last()
 | 
			
		||||
 | 
			
		||||
    # def get_current_active_membership(cls, user):
 | 
			
		||||
    #     membership = cls.get_current_membership(user)
 | 
			
		||||
| 
						 | 
				
			
			@ -84,8 +95,7 @@ class Membership(models.Model):
 | 
			
		|||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_by_user(cls, user):
 | 
			
		||||
        return cls.objects.\
 | 
			
		||||
            filter(membershiporder__customer__user=user).last()
 | 
			
		||||
        return cls.objects.filter(membershiporder__customer__user=user).last()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create(cls, data):
 | 
			
		||||
| 
						 | 
				
			
			@ -96,18 +106,23 @@ class Membership(models.Model):
 | 
			
		|||
    def activate_or_crete(cls, data, user):
 | 
			
		||||
        membership = cls.get_by_user(user)
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def is_digitalglarus_active_member(cls, user):
 | 
			
		||||
        # past_month = (datetime.today() - relativedelta(months=1)).month
 | 
			
		||||
        has_order_current_month = Q(membershiporder__customer__user=user,
 | 
			
		||||
                                    membershiporder__created_at__month=datetime.today().month)
 | 
			
		||||
        has_order_current_month = Q(
 | 
			
		||||
            membershiporder__customer__user=user,
 | 
			
		||||
            membershiporder__created_at__month=datetime.today().month
 | 
			
		||||
        )
 | 
			
		||||
        # has_order_past_month = Q(membershiporder__customer__user=user,
 | 
			
		||||
        #  membershiporder__created_at__month=past_month)
 | 
			
		||||
        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).\
 | 
			
		||||
            filter(active_membership).exists()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -129,6 +144,7 @@ class MembershipOrder(Ordereable, models.Model):
 | 
			
		|||
    membership = models.ForeignKey(Membership)
 | 
			
		||||
    start_date = models.DateField()
 | 
			
		||||
    end_date = models.DateField()
 | 
			
		||||
    stripe_subscription_id = models.CharField(max_length=100, null=True)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def current_membership_dates(cls, user):
 | 
			
		||||
| 
						 | 
				
			
			@ -172,10 +188,12 @@ class MembershipOrder(Ordereable, models.Model):
 | 
			
		|||
    @classmethod
 | 
			
		||||
    def create(cls, data):
 | 
			
		||||
        stripe_charge = data.pop('stripe_charge', None)
 | 
			
		||||
        stripe_subscription_id = data.pop('stripe_subscription_id', None)
 | 
			
		||||
        instance = cls.objects.create(**data)
 | 
			
		||||
        instance.stripe_charge_id = stripe_charge.id
 | 
			
		||||
        instance.last4 = stripe_charge.source.last4
 | 
			
		||||
        instance.cc_brand = stripe_charge.source.brand
 | 
			
		||||
        instance.stripe_subscription_id = stripe_subscription_id
 | 
			
		||||
        instance.save()
 | 
			
		||||
        return instance
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,12 +95,9 @@
 | 
			
		|||
                  <a class="btn btn-primary btn-grey btn-deactivate print" href="{% url 'digitalglarus:membership_deactivate' %}">Deactivate</a>
 | 
			
		||||
                </div>
 | 
			
		||||
              {% elif not current_membership.active %}
 | 
			
		||||
                <form method="POST" action="{% url 'digitalglarus:membership_reactivate' %}">
 | 
			
		||||
                  {% csrf_token %} 
 | 
			
		||||
                  <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>
 | 
			
		||||
                </form>
 | 
			
		||||
              {% endif %}
 | 
			
		||||
            {% else %}
 | 
			
		||||
              <div class="edit-button">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
from model_mommy import mommy
 | 
			
		||||
from unittest import mock
 | 
			
		||||
from unittest import mock, skipIf
 | 
			
		||||
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
| 
						 | 
				
			
			@ -126,6 +126,11 @@ class MembershipPaymentViewTest(BaseTestCase):
 | 
			
		|||
        self.assertEqual(response.context['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')
 | 
			
		||||
    def test_post(self, stripe_mocked_call):
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
import logging
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.shortcuts import render
 | 
			
		||||
from django.http import HttpResponseRedirect
 | 
			
		||||
| 
						 | 
				
			
			@ -9,25 +11,34 @@ from django.utils.translation import get_language
 | 
			
		|||
from djangocms_blog.models import Post
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.views.generic import DetailView, ListView
 | 
			
		||||
from .models import Supporter
 | 
			
		||||
from .mixins import ChangeMembershipStatusMixin
 | 
			
		||||
from utils.forms import ContactUsForm
 | 
			
		||||
from utils.mailer import BaseEmail
 | 
			
		||||
from django.views.generic.edit import FormView
 | 
			
		||||
from membership.models import StripeCustomer
 | 
			
		||||
from utils.views import LoginViewMixin, SignupViewMixin, \
 | 
			
		||||
    PasswordResetViewMixin, PasswordResetConfirmViewMixin
 | 
			
		||||
from utils.forms import PasswordResetRequestForm, UserBillingAddressForm, EditCreditCardForm
 | 
			
		||||
from utils.views import (
 | 
			
		||||
    LoginViewMixin, SignupViewMixin, PasswordResetViewMixin,
 | 
			
		||||
    PasswordResetConfirmViewMixin
 | 
			
		||||
)
 | 
			
		||||
from utils.forms import (
 | 
			
		||||
    PasswordResetRequestForm, UserBillingAddressForm, EditCreditCardForm
 | 
			
		||||
)
 | 
			
		||||
from utils.stripe_utils import StripeUtils
 | 
			
		||||
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
 | 
			
		||||
)
 | 
			
		||||
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,\
 | 
			
		||||
    BookingOrder, BookingCancellation
 | 
			
		||||
 | 
			
		||||
from .mixins import MembershipRequiredMixin, IsNotMemberMixin
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IndexView(TemplateView):
 | 
			
		||||
| 
						 | 
				
			
			@ -271,7 +282,6 @@ class BookingPaymentView(LoginRequiredMixin, MembershipRequiredMixin, FormView):
 | 
			
		|||
        booking_data = {
 | 
			
		||||
            'start_date': start_date,
 | 
			
		||||
            'end_date': end_date,
 | 
			
		||||
            'start_date': start_date,
 | 
			
		||||
            'free_days': free_days,
 | 
			
		||||
            'price': normal_price,
 | 
			
		||||
            'final_price': final_price,
 | 
			
		||||
| 
						 | 
				
			
			@ -355,16 +365,21 @@ class MembershipPaymentView(LoginRequiredMixin, IsNotMemberMixin, FormView):
 | 
			
		|||
            membership_type = data.get('membership_type')
 | 
			
		||||
 | 
			
		||||
            # Get or create stripe customer
 | 
			
		||||
            customer = StripeCustomer.get_or_create(email=self.request.user.email,
 | 
			
		||||
                                                    token=token)
 | 
			
		||||
            customer = StripeCustomer.get_or_create(
 | 
			
		||||
                email=self.request.user.email, token=token
 | 
			
		||||
            )
 | 
			
		||||
            if not customer:
 | 
			
		||||
                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
 | 
			
		||||
            stripe_utils = StripeUtils()
 | 
			
		||||
            charge_response = stripe_utils.make_charge(amount=membership_type.first_month_price,
 | 
			
		||||
                                                       customer=customer.stripe_id)
 | 
			
		||||
            charge_response = stripe_utils.make_charge(
 | 
			
		||||
                amount=membership_type.first_month_price,
 | 
			
		||||
                customer=customer.stripe_id
 | 
			
		||||
            )
 | 
			
		||||
            charge = charge_response.get('response_object')
 | 
			
		||||
 | 
			
		||||
            # Check if the payment was approved
 | 
			
		||||
| 
						 | 
				
			
			@ -373,6 +388,58 @@ class MembershipPaymentView(LoginRequiredMixin, IsNotMemberMixin, FormView):
 | 
			
		|||
                    'paymentError': charge_response.get('error'),
 | 
			
		||||
                    '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)
 | 
			
		||||
 | 
			
		||||
            charge = charge_response.get('response_object')
 | 
			
		||||
| 
						 | 
				
			
			@ -412,6 +479,7 @@ class MembershipPaymentView(LoginRequiredMixin, IsNotMemberMixin, FormView):
 | 
			
		|||
                'customer': customer,
 | 
			
		||||
                'billing_address': billing_address,
 | 
			
		||||
                'stripe_charge': charge,
 | 
			
		||||
                'stripe_subscription_id': stripe_subscription_obj.id,
 | 
			
		||||
                'amount': membership_type.first_month_price,
 | 
			
		||||
                'start_date': membership_start_date,
 | 
			
		||||
                'end_date': membership_end_date
 | 
			
		||||
| 
						 | 
				
			
			@ -481,8 +549,43 @@ class MembershipDeactivateView(LoginRequiredMixin, UpdateView):
 | 
			
		|||
    def post(self, *args, **kwargs):
 | 
			
		||||
        membership = self.get_object()
 | 
			
		||||
        membership.deactivate()
 | 
			
		||||
 | 
			
		||||
        messages.add_message(self.request, messages.SUCCESS, self.success_message)
 | 
			
		||||
        messages.add_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)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
{% load i18n %}
 | 
			
		||||
{% if instance.image %}
 | 
			
		||||
  <div class="bg_img" style="background-image:url({{ instance.image.url }})"></div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -210,12 +210,14 @@ class StripeUtils(object):
 | 
			
		|||
        return return_value
 | 
			
		||||
 | 
			
		||||
    @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
 | 
			
		||||
 | 
			
		||||
        :param customer: The stripe customer identifier
 | 
			
		||||
        :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
 | 
			
		||||
              e.g.
 | 
			
		||||
                    plans = [
 | 
			
		||||
| 
						 | 
				
			
			@ -227,8 +229,7 @@ class StripeUtils(object):
 | 
			
		|||
        """
 | 
			
		||||
 | 
			
		||||
        subscription_result = self.stripe.Subscription.create(
 | 
			
		||||
            customer=customer,
 | 
			
		||||
            items=plans,
 | 
			
		||||
            customer=customer, items=plans, trial_end=trial_end
 | 
			
		||||
        )
 | 
			
		||||
        return subscription_result
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue