Merge pull request #530 from ungleich/task/3747/multiple_cards_support
Task/3747/multiple cards support
This commit is contained in:
		
				commit
				
					
						0caf3da3bd
					
				
			
		
					 23 changed files with 1251 additions and 485 deletions
				
			
		|  | @ -8,7 +8,7 @@ msgid "" | |||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2018-05-12 21:43+0530\n" | ||||
| "POT-Creation-Date: 2018-07-05 23:11+0000\n" | ||||
| "PO-Revision-Date: 2018-03-30 23:22+0000\n" | ||||
| "Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
|  | @ -329,6 +329,17 @@ msgstr "wird an der Kasse angewendet" | |||
| msgid "Credit Card" | ||||
| msgstr "Kreditkarte" | ||||
| 
 | ||||
| msgid "" | ||||
| "Please select one of the cards that you used before or fill in your credit " | ||||
| "card information below. We are using <a href=\"https://stripe.com\" target=" | ||||
| "\"_blank\">Stripe</a> for payment and do not store your information in our " | ||||
| "database." | ||||
| msgstr "" | ||||
| "Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine " | ||||
| "Kreditkartendetails unten an. Die Bezahlung wird über " | ||||
| "<a href=\"https://stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. " | ||||
| "Wir speichern Deine Kreditkartendetails nicht in unserer Datenbank." | ||||
| 
 | ||||
| msgid "" | ||||
| "Please fill in your credit card information below. We are using <a href=" | ||||
| "\"https://stripe.com\" target=\"_blank\">Stripe</a> for payment and do not " | ||||
|  | @ -338,31 +349,23 @@ msgstr "" | |||
| "\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung und " | ||||
| "speichern keine Informationen in unserer Datenbank." | ||||
| 
 | ||||
| msgid "" | ||||
| "You are not making any payment yet. After submitting your card information, " | ||||
| "you will be taken to the Confirm Order Page." | ||||
| msgstr "" | ||||
| "Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst ausgelöst, " | ||||
| "nachdem Du die Bestellung auf der nächsten Seite bestätigt hast." | ||||
| msgid "Last" | ||||
| msgstr "Letzten" | ||||
| 
 | ||||
| msgid "Card Number" | ||||
| msgstr "Kreditkartennummer" | ||||
| msgid "Type" | ||||
| msgstr "Typ" | ||||
| 
 | ||||
| msgid "Expiry Date" | ||||
| msgstr "Ablaufdatum" | ||||
| msgid "SELECT" | ||||
| msgstr "AUSWÄHLEN" | ||||
| 
 | ||||
| msgid "CVC" | ||||
| msgstr "" | ||||
| msgid "Add a new credit card" | ||||
| msgstr "Eine neue Kreditkarte hinzufügen" | ||||
| 
 | ||||
| msgid "Card Type" | ||||
| msgstr "Kartentyp" | ||||
| msgid "NEW CARD" | ||||
| msgstr "NEUE KARTE" | ||||
| 
 | ||||
| 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. Die Bezahlung wird erst ausgelöst, " | ||||
| "nachdem Du die Bestellung auf der nächsten Seite bestätigt hast." | ||||
| msgid "New Credit Card" | ||||
| msgstr "Neue Kreditkarte" | ||||
| 
 | ||||
| msgid "Processing" | ||||
| msgstr "Weiter" | ||||
|  | @ -516,6 +519,13 @@ msgstr "Ungültige Speicher-Grösse" | |||
| msgid "Incorrect pricing name. Please contact support{support_email}" | ||||
| msgstr "" | ||||
| 
 | ||||
| #, python-brace-format | ||||
| msgid "{user} does not have permission to access the card" | ||||
| msgstr "{user} hat keine Erlaubnis auf diese Karte zuzugreifen" | ||||
| 
 | ||||
| msgid "An error occurred. Details: {}" | ||||
| msgstr "Ein Fehler ist aufgetreten. Details: {}" | ||||
| 
 | ||||
| msgid "Confirm Order" | ||||
| msgstr "Bestellung Bestätigen" | ||||
| 
 | ||||
|  | @ -529,6 +539,11 @@ msgstr "" | |||
| "Es ist ein Fehler bei der Zahlung betreten. Du wirst nach dem Schliessen vom " | ||||
| "Popup zur Bezahlseite weitergeleitet." | ||||
| 
 | ||||
| #, python-brace-format | ||||
| msgid "An error occurred while associating the card. Details: {details}" | ||||
| msgstr "Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: " | ||||
| "{details}" | ||||
| 
 | ||||
| msgid "Thank you for the order." | ||||
| msgstr "Danke für Deine Bestellung." | ||||
| 
 | ||||
|  | @ -539,6 +554,28 @@ msgstr "" | |||
| "Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " | ||||
| "auf sie zugreifen kannst." | ||||
| 
 | ||||
| #~ msgid "" | ||||
| #~ "You are not making any payment yet. After submitting your card " | ||||
| #~ "information, you will be taken to the Confirm Order Page." | ||||
| #~ msgstr "" | ||||
| #~ "Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst " | ||||
| #~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt " | ||||
| #~ "hast." | ||||
| 
 | ||||
| #~ msgid "Card Number" | ||||
| #~ msgstr "Kreditkartennummer" | ||||
| 
 | ||||
| #~ msgid "Expiry Date" | ||||
| #~ msgstr "Ablaufdatum" | ||||
| 
 | ||||
| #~ 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. Die Bezahlung wird erst " | ||||
| #~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt " | ||||
| #~ "hast." | ||||
| 
 | ||||
| #~ msgid "Pricing" | ||||
| #~ msgstr "Preise" | ||||
| 
 | ||||
|  |  | |||
|  | @ -158,4 +158,25 @@ footer .dcl-link-separator::before { | |||
| .thin-hr { | ||||
|   margin-top: 10px; | ||||
|   margin-bottom: 10px; | ||||
| } | ||||
| 
 | ||||
| .payment-container .credit-card-info { | ||||
|     padding-bottom: 15px; | ||||
|     border-bottom: 1px solid #eee; | ||||
| } | ||||
| .credit-card-info { | ||||
|     display: flex; | ||||
| } | ||||
| 
 | ||||
| .credit-card-info .align-bottom { | ||||
|     align-self: flex-end; | ||||
|     padding-right: 0 !important; | ||||
| } | ||||
| 
 | ||||
| .new-card-head { | ||||
|     margin-top: 10px; | ||||
| } | ||||
| .new-card-button-margin button{ | ||||
|     margin-top: 5px; | ||||
|     margin-bottom: 5px; | ||||
| } | ||||
|  | @ -101,93 +101,55 @@ | |||
|             </div> | ||||
|             <div class="dcl-payment-box"> | ||||
|                 <div class="dcl-payment-section"> | ||||
|                     {% with card_list_len=cards_list|length %} | ||||
|                     <h3><b>{%trans "Credit Card"%}</b></h3> | ||||
|                     <hr class="top-hr"> | ||||
|                     <p> | ||||
|                         {% blocktrans %}Please fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %} | ||||
|                     </p> | ||||
|                     <div> | ||||
|                         {% if credit_card_data.last4 %} | ||||
|                             <form role="form" id="payment-form-with-creditcard" novalidate> | ||||
|                                 <h5 class="billing-head">Credit Card</h5> | ||||
|                                 <h5 class="membership-lead">Last 4: *****{{credit_card_data.last4}}</h5> | ||||
|                                 <h5 class="membership-lead">Type: {{credit_card_data.cc_brand}}</h5> | ||||
|                                 <input type="hidden" name="credit_card_needed" value="false"/> | ||||
|                             </form> | ||||
|                             {% if not messages and not form.non_field_errors %} | ||||
|                                 <p class="card-warning-content card-warning-addtional-margin"> | ||||
|                                     {% trans "You are not making any payment yet. After submitting your card information, you will be taken to the Confirm Order Page." %} | ||||
|                                 </p> | ||||
|                             {% endif %} | ||||
|                             <div id='payment_error'> | ||||
|                                 {% for message in messages %} | ||||
|                                     {% if 'failed_payment' or 'make_charge_error' in message.tags %} | ||||
|                                      <ul class="list-unstyled"> | ||||
|                                         <li> | ||||
|                                             <p class="card-warning-content card-warning-error">{{ message|safe }}</p> | ||||
|                                         </li> | ||||
|                                     </ul> | ||||
|                                     {% endif %} | ||||
|                                 {% endfor %} | ||||
|                                 {% for error in form.non_field_errors %} | ||||
|                                     <p class="card-warning-content card-warning-error"> | ||||
|                                         {{ error|escape }} | ||||
|                                     </p> | ||||
|                                 {% endfor %} | ||||
|                             </div> | ||||
|                             <div class="text-right"> | ||||
|                                 <button id="payment_button_with_creditcard" class="btn btn-vm-contact" type="submit">{%trans "SUBMIT" %}</button> | ||||
|                             </div> | ||||
|                         {% if card_list_len > 0 %} | ||||
|                         {% blocktrans %}Please select one of the cards that you used before or fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %} | ||||
|                         {% else %} | ||||
|                             <form action="" id="payment-form-new" method="POST"> | ||||
|                                 <input type="hidden" name="token"/> | ||||
|                                 <div class="group"> | ||||
|                                     <div class="credit-card-goup"> | ||||
|                                        <div class="card-element card-number-element"> | ||||
|                                            <label>{%trans "Card Number" %}</label> | ||||
|                                            <div id="card-number-element" class="field my-input"></div> | ||||
|                                        </div> | ||||
|                                        <div class="row"> | ||||
|                                            <div class="col-xs-5 card-element card-expiry-element"> | ||||
|                                                <label>{%trans "Expiry Date" %}</label> | ||||
|                                                <div id="card-expiry-element" class="field my-input"></div> | ||||
|                                            </div> | ||||
|                                            <div class="col-xs-3 col-xs-offset-4 card-element card-cvc-element"> | ||||
|                                                <label>{%trans "CVC" %}</label> | ||||
|                                                <div id="card-cvc-element" class="field my-input"></div> | ||||
|                                            </div> | ||||
|                                        </div> | ||||
|                                        <div class="card-element brand"> | ||||
|                                            <label>{%trans "Card Type" %}</label> | ||||
|                                            <i class="pf pf-credit-card" id="brand-icon"></i> | ||||
|                                        </div> | ||||
|                         {% blocktrans %}Please fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %} | ||||
|                         {% endif %} | ||||
|                     </p> | ||||
|                         <div> | ||||
|                             {% for card in cards_list %} | ||||
|                                 <div class="credit-card-info"> | ||||
|                                     <div class="col-xs-6 no-padding"> | ||||
|                                         <h5 class="billing-head">{% trans "Credit Card" %}</h5> | ||||
|                                         <h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5> | ||||
|                                         <h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5> | ||||
|                                     </div> | ||||
|                                     <div class="col-xs-6 text-right align-bottom"> | ||||
|                                         <a class="btn choice-btn choice-btn-faded" href="#" data-id_card="{{card.id}}">{% trans "SELECT" %}</a> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                                 <div id="card-errors"></div> | ||||
|                                 {% if not messages and not form.non_field_errors %} | ||||
|                                     <p class="card-warning-content"> | ||||
|                                         {% trans "You are not making any payment yet. After placing your order, you will be taken to the Submit Payment Page." %} | ||||
|                                     </p> | ||||
|                                 {% endif %} | ||||
|                                 <div id='payment_error'> | ||||
|                                     {% for message in messages %} | ||||
|                                         {% if 'failed_payment' in message.tags or 'make_charge_error' in message.tags or 'error' in message.tags %} | ||||
|                                             <ul class="list-unstyled"> | ||||
|                                                 <li><p class="card-warning-content card-warning-error">{{ message|safe }}</p></li> | ||||
|                                             </ul> | ||||
|                                         {% endif %} | ||||
|                                     {% endfor %} | ||||
|                             {% endfor %} | ||||
|                             {% if card_list_len > 0 %} | ||||
|                                 <div class="new-card-head"> | ||||
|                                     <div class="row"> | ||||
|                                         <div class="col-xs-6"> | ||||
|                                             <h4>{% trans "Add a new credit card" %}</h4> | ||||
|                                         </div> | ||||
|                                         <div class="col-xs-6 text-right new-card-button-margin"> | ||||
|                                             <button data-toggle="collapse" data-target="#newcard" class="btn choice-btn"> | ||||
|                                                 <span class="fa fa-plus"></span>  {% trans "NEW CARD" %} | ||||
|                                             </button> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                                 <div class="text-right"> | ||||
|                                     <button class="btn btn-vm-contact btn-wide" type="submit">{%trans "SUBMIT" %}</button> | ||||
|                                 <div id="newcard" class="collapse"> | ||||
|                                     <hr class="thick-hr"> | ||||
|                                     <div class="card-details-box"> | ||||
|                                         <h3>{%trans "New Credit Card" %}</h3> | ||||
|                                         <hr> | ||||
|                                         {% include "hosting/includes/_card_input.html" %} | ||||
|                                     </div> | ||||
|                                 </div> | ||||
| 
 | ||||
|                                 <div style="display:none;"> | ||||
|                                     <p class="payment-errors"></p> | ||||
|                                 </div> | ||||
|                             </form> | ||||
|                         {% endif %} | ||||
|                     </div> | ||||
|                             {% else%} | ||||
|                                 {% include "hosting/includes/_card_input.html" %} | ||||
|                             {% endif %} | ||||
|                         </div> | ||||
|                     {% endwith %} | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|  | @ -207,13 +169,4 @@ | |||
|     })(); | ||||
| </script> | ||||
| {%endif%} | ||||
| 
 | ||||
| {% if credit_card_data.last4 and credit_card_data.cc_brand %} | ||||
| <script type="text/javascript"> | ||||
|     (function () { | ||||
|         window.hasCreditcard = true; | ||||
|     })(); | ||||
| </script> | ||||
| {%endif%} | ||||
| 
 | ||||
| {%endblock%} | ||||
|  |  | |||
|  | @ -86,8 +86,7 @@ class CeleryTaskTestCase(TestCase): | |||
|             token=self.token | ||||
|         ) | ||||
|         card_details = self.stripe_utils.get_card_details( | ||||
|             stripe_customer.stripe_id, | ||||
|             self.token | ||||
|             stripe_customer.stripe_id | ||||
|         ) | ||||
|         card_details_dict = card_details.get('error') | ||||
|         self.assertEquals(card_details_dict, None) | ||||
|  |  | |||
|  | @ -87,7 +87,7 @@ def create_vm(billing_address_data, stripe_customer_id, specs, | |||
|     create_vm_task.delay(vm_template_id, user, specs, template, order.id) | ||||
| 
 | ||||
|     for session_var in ['specs', 'template', 'billing_address', | ||||
|                         'billing_address_data', | ||||
|                         'billing_address_data', 'card_id', | ||||
|                         'token', 'customer']: | ||||
|         if session_var in request.session: | ||||
|             del request.session[session_var] | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ from django.views.decorators.cache import cache_control | |||
| from django.views.generic import FormView, CreateView, DetailView | ||||
| 
 | ||||
| from hosting.forms import HostingUserLoginForm | ||||
| from hosting.models import HostingOrder | ||||
| from hosting.models import HostingOrder, UserCardDetail | ||||
| from membership.models import CustomUser, StripeCustomer | ||||
| from opennebula_api.serializers import VMTemplateSerializer | ||||
| from utils.forms import BillingAddressForm, BillingAddressFormSignup | ||||
|  | @ -222,19 +222,15 @@ class PaymentOrderView(FormView): | |||
|                 billing_address_form = BillingAddressForm( | ||||
|                     instance=self.request.user.billing_addresses.first() | ||||
|                 ) | ||||
|             # Get user last order | ||||
|             last_hosting_order = HostingOrder.objects.filter( | ||||
|                 customer__user=self.request.user | ||||
|             ).last() | ||||
| 
 | ||||
|             # If user has already an hosting order, get the credit card | ||||
|             # data from it | ||||
|             if last_hosting_order: | ||||
|                 credit_card_data = last_hosting_order.get_cc_data() | ||||
|                 if credit_card_data: | ||||
|                     context['credit_card_data'] = credit_card_data | ||||
|                 else: | ||||
|                     context['credit_card_data'] = None | ||||
|             user = self.request.user | ||||
|             if hasattr(user, 'stripecustomer'): | ||||
|                 stripe_customer = user.stripecustomer | ||||
|             else: | ||||
|                 stripe_customer = None | ||||
|             cards_list = UserCardDetail.get_all_cards_list( | ||||
|                 stripe_customer=stripe_customer | ||||
|             ) | ||||
|             context.update({'cards_list': cards_list}) | ||||
|         else: | ||||
|             billing_address_form = BillingAddressFormSignup( | ||||
|                 initial=billing_address_data | ||||
|  | @ -286,14 +282,42 @@ class PaymentOrderView(FormView): | |||
|             ) | ||||
|         if address_form.is_valid(): | ||||
|             token = address_form.cleaned_data.get('token') | ||||
|             if token is '': | ||||
|                 card_id = address_form.cleaned_data.get('card') | ||||
|                 try: | ||||
|                     user_card_detail = UserCardDetail.objects.get(id=card_id) | ||||
|                     if not request.user.has_perm( | ||||
|                             'view_usercarddetail', user_card_detail | ||||
|                     ): | ||||
|                         raise UserCardDetail.DoesNotExist( | ||||
|                             _("{user} does not have permission to access the " | ||||
|                               "card").format(user=request.user.email) | ||||
|                         ) | ||||
|                 except UserCardDetail.DoesNotExist as e: | ||||
|                     ex = str(e) | ||||
|                     logger.error("Card Id: {card_id}, Exception: {ex}".format( | ||||
|                             card_id=card_id, ex=ex | ||||
|                         ) | ||||
|                     ) | ||||
|                     msg = _("An error occurred. Details: {}".format(ex)) | ||||
|                     messages.add_message( | ||||
|                         self.request, messages.ERROR, msg, | ||||
|                         extra_tags='make_charge_error' | ||||
|                     ) | ||||
|                     return HttpResponseRedirect( | ||||
|                         reverse('datacenterlight:payment') + '#payment_error' | ||||
|                     ) | ||||
|                 request.session['card_id'] = user_card_detail.id | ||||
|             else: | ||||
|                 request.session['token'] = token | ||||
|             if request.user.is_authenticated(): | ||||
|                 this_user = { | ||||
|                     'email': request.user.email, | ||||
|                     'name': request.user.name | ||||
|                 } | ||||
|                 customer = StripeCustomer.get_or_create( | ||||
|                     email=this_user.get('email'), | ||||
|                     token=token) | ||||
|                     email=this_user.get('email'), token=token | ||||
|                 ) | ||||
|             else: | ||||
|                 user_email = address_form.cleaned_data.get('email') | ||||
|                 user_name = address_form.cleaned_data.get('name') | ||||
|  | @ -341,7 +365,6 @@ class PaymentOrderView(FormView): | |||
|                         billing_address_form=address_form | ||||
|                     ) | ||||
|                 ) | ||||
|             request.session['token'] = token | ||||
|             if type(customer) is StripeCustomer: | ||||
|                 request.session['customer'] = customer.stripe_id | ||||
|             else: | ||||
|  | @ -362,32 +385,34 @@ class OrderConfirmationView(DetailView): | |||
| 
 | ||||
|     @cache_control(no_cache=True, must_revalidate=True, no_store=True) | ||||
|     def get(self, request, *args, **kwargs): | ||||
|         context = {} | ||||
|         if 'specs' not in request.session or 'user' not in request.session: | ||||
|             return HttpResponseRedirect(reverse('datacenterlight:index')) | ||||
|         if 'token' not in request.session: | ||||
|             return HttpResponseRedirect(reverse('datacenterlight:payment')) | ||||
|         stripe_api_cus_id = request.session.get('customer') | ||||
|         stripe_utils = StripeUtils() | ||||
|         card_details = stripe_utils.get_card_details(stripe_api_cus_id, | ||||
|                                                      request.session.get( | ||||
|                                                          'token')) | ||||
|         if not card_details.get('response_object'): | ||||
|             msg = card_details.get('error') | ||||
|             messages.add_message(self.request, messages.ERROR, msg, | ||||
|                                  extra_tags='failed_payment') | ||||
|             return HttpResponseRedirect( | ||||
|                 reverse('datacenterlight:payment') + '#payment_error') | ||||
|         context = { | ||||
|         if 'token' in self.request.session: | ||||
|             token = self.request.session['token'] | ||||
|             stripe_utils = StripeUtils() | ||||
|             card_details = stripe_utils.get_cards_details_from_token( | ||||
|                 token | ||||
|             ) | ||||
|             if not card_details.get('response_object'): | ||||
|                 return HttpResponseRedirect(reverse('hosting:payment')) | ||||
|             card_details_response = card_details['response_object'] | ||||
|             context['cc_last4'] = card_details_response['last4'] | ||||
|             context['cc_brand'] = card_details_response['brand'] | ||||
|         else: | ||||
|             card_id = self.request.session.get('card_id') | ||||
|             card_detail = UserCardDetail.objects.get(id=card_id) | ||||
|             context['cc_last4'] = card_detail.last4 | ||||
|             context['cc_brand'] = card_detail.brand | ||||
|         context.update({ | ||||
|             'site_url': reverse('datacenterlight:index'), | ||||
|             'cc_last4': card_details.get('response_object').get('last4'), | ||||
|             'cc_brand': card_details.get('response_object').get('brand'), | ||||
|             'vm': request.session.get('specs'), | ||||
|             'page_header_text': _('Confirm Order'), | ||||
|             'billing_address_data': ( | ||||
|                 request.session.get('billing_address_data') | ||||
|             ), | ||||
|             'cms_integration': get_cms_integration('default'), | ||||
|         } | ||||
|         }) | ||||
|         return render(request, self.template_name, context) | ||||
| 
 | ||||
|     def post(self, request, *args, **kwargs): | ||||
|  | @ -397,13 +422,75 @@ class OrderConfirmationView(DetailView): | |||
|         stripe_api_cus_id = request.session.get('customer') | ||||
|         vm_template_id = template.get('id', 1) | ||||
|         stripe_utils = StripeUtils() | ||||
|         card_details = stripe_utils.get_card_details(stripe_api_cus_id, | ||||
|                                                      request.session.get( | ||||
|                                                          'token')) | ||||
|         if not card_details.get('response_object'): | ||||
|             msg = card_details.get('error') | ||||
|             messages.add_message(self.request, messages.ERROR, msg, | ||||
|                                  extra_tags='failed_payment') | ||||
| 
 | ||||
|         if 'token' in request.session: | ||||
|             card_details = stripe_utils.get_cards_details_from_token( | ||||
|                 request.session.get('token') | ||||
|             ) | ||||
|             if not card_details.get('response_object'): | ||||
|                 msg = card_details.get('error') | ||||
|                 messages.add_message(self.request, messages.ERROR, msg, | ||||
|                                      extra_tags='failed_payment') | ||||
|                 response = { | ||||
|                     'status': False, | ||||
|                     'redirect': "{url}#{section}".format( | ||||
|                         url=reverse('datacenterlight:payment'), | ||||
|                         section='payment_error'), | ||||
|                     'msg_title': str(_('Error.')), | ||||
|                     'msg_body': str( | ||||
|                         _('There was a payment related error.' | ||||
|                           ' On close of this popup, you will be' | ||||
|                           ' redirected back to the payment page.') | ||||
|                     ) | ||||
|                 } | ||||
|                 return JsonResponse(response) | ||||
|             card_details_response = card_details['response_object'] | ||||
|             card_details_dict = { | ||||
|                 'last4': card_details_response['last4'], | ||||
|                 'brand': card_details_response['brand'], | ||||
|                 'card_id': card_details_response['card_id'] | ||||
|             } | ||||
|             stripe_customer_obj = StripeCustomer.objects.filter(stripe_id=stripe_api_cus_id).first() | ||||
|             if stripe_customer_obj: | ||||
|                 ucd = UserCardDetail.get_user_card_details( | ||||
|                     stripe_customer_obj, card_details_response | ||||
|                 ) | ||||
|                 if not ucd: | ||||
|                     acc_result = stripe_utils.associate_customer_card( | ||||
|                         stripe_api_cus_id, request.session['token'], | ||||
|                         set_as_default=True | ||||
|                     ) | ||||
|                     if acc_result['response_object'] is None: | ||||
|                         msg = _( | ||||
|                             'An error occurred while associating the card.' | ||||
|                             ' Details: {details}'.format( | ||||
|                                 details=acc_result['error'] | ||||
|                             ) | ||||
|                         ) | ||||
|                         messages.add_message(self.request, messages.ERROR, msg, | ||||
|                                              extra_tags='failed_payment') | ||||
|                         response = { | ||||
|                             'status': False, | ||||
|                             'redirect': "{url}#{section}".format( | ||||
|                                 url=reverse('hosting:payment'), | ||||
|                                 section='payment_error'), | ||||
|                             'msg_title': str(_('Error.')), | ||||
|                             'msg_body': str( | ||||
|                                 _('There was a payment related error.' | ||||
|                                   ' On close of this popup, you will be redirected' | ||||
|                                   ' back to the payment page.') | ||||
|                             ) | ||||
|                         } | ||||
|                         return JsonResponse(response) | ||||
|         elif 'card_id' in request.session: | ||||
|             card_id = request.session.get('card_id') | ||||
|             user_card_detail = UserCardDetail.objects.get(id=card_id) | ||||
|             card_details_dict = { | ||||
|                 'last4': user_card_detail.last4, | ||||
|                 'brand': user_card_detail.brand, | ||||
|                 'card_id': user_card_detail.card_id | ||||
|             } | ||||
|         else: | ||||
|             response = { | ||||
|                 'status': False, | ||||
|                 'redirect': "{url}#{section}".format( | ||||
|  | @ -417,7 +504,6 @@ class OrderConfirmationView(DetailView): | |||
|             } | ||||
|             return JsonResponse(response) | ||||
| 
 | ||||
|         card_details_dict = card_details.get('response_object') | ||||
|         cpu = specs.get('cpu') | ||||
|         memory = specs.get('memory') | ||||
|         disk_size = specs.get('disk_size') | ||||
|  | @ -442,6 +528,12 @@ class OrderConfirmationView(DetailView): | |||
|         # Check if the subscription was approved and is active | ||||
|         if (stripe_subscription_obj is None | ||||
|                 or stripe_subscription_obj.status != 'active'): | ||||
|             # At this point, we have created a Stripe API card and | ||||
|             # associated it with the customer; but the transaction failed | ||||
|             # due to some reason. So, we would want to dissociate this card | ||||
|             # here. | ||||
|             # ... | ||||
| 
 | ||||
|             msg = subscription_result.get('error') | ||||
|             messages.add_message(self.request, messages.ERROR, msg, | ||||
|                                  extra_tags='failed_payment') | ||||
|  | @ -496,12 +588,36 @@ class OrderConfirmationView(DetailView): | |||
|             stripe_customer_id = request.user.stripecustomer.id | ||||
|             custom_user = request.user | ||||
| 
 | ||||
|         if 'token' in request.session: | ||||
|             ucd = UserCardDetail.get_or_create_user_card_detail( | ||||
|                 stripe_customer=self.request.user.stripecustomer, | ||||
|                 card_details=card_details_response | ||||
|             ) | ||||
|             UserCardDetail.save_default_card_local( | ||||
|                 self.request.user.stripecustomer.stripe_id, | ||||
|                 ucd.card_id | ||||
|             ) | ||||
|         else: | ||||
|             card_id = request.session.get('card_id') | ||||
|             user_card_detail = UserCardDetail.objects.get(id=card_id) | ||||
|             card_details_dict = { | ||||
|                 'last4': user_card_detail.last4, | ||||
|                 'brand': user_card_detail.brand, | ||||
|                 'card_id': user_card_detail.card_id | ||||
|             } | ||||
|             if not user_card_detail.preferred: | ||||
|                 UserCardDetail.set_default_card( | ||||
|                     stripe_api_cus_id=stripe_api_cus_id, | ||||
|                     stripe_source_id=user_card_detail.card_id | ||||
|                 ) | ||||
| 
 | ||||
|         # Save billing address | ||||
|         billing_address_data = request.session.get('billing_address_data') | ||||
|         logger.debug('billing_address_data is {}'.format(billing_address_data)) | ||||
|         billing_address_data.update({ | ||||
|             'user': custom_user.id | ||||
|         }) | ||||
| 
 | ||||
|         user = { | ||||
|             'name': custom_user.name, | ||||
|             'email': custom_user.email, | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ msgid "" | |||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2018-05-12 03:53+0530\n" | ||||
| "POT-Creation-Date: 2018-07-05 23:15+0000\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
|  | @ -274,6 +274,29 @@ msgstr "" | |||
| msgid "You can always order a new VM by following the link below." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Card Number" | ||||
| msgstr "Kreditkartennummer" | ||||
| 
 | ||||
| msgid "Expiry Date" | ||||
| msgstr "Ablaufdatum" | ||||
| 
 | ||||
| msgid "CVC" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Card Type" | ||||
| msgstr "Kartentyp" | ||||
| 
 | ||||
| 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. Die Bezahlung wird erst " | ||||
| "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt " | ||||
| "hast." | ||||
| 
 | ||||
| msgid "SUBMIT" | ||||
| msgstr "ABSENDEN" | ||||
| 
 | ||||
| msgid "Toggle navigation" | ||||
| msgstr "Umschalten" | ||||
| 
 | ||||
|  | @ -439,6 +462,17 @@ msgstr "wird an der Kasse angewendet" | |||
| msgid "Billing Address" | ||||
| msgstr "Rechnungsadresse" | ||||
| 
 | ||||
| msgid "" | ||||
| "Please select one of the cards that you used before or fill in your credit " | ||||
| "card information below. We are using <a href=\"https://stripe.com\" target=" | ||||
| "\"_blank\">Stripe</a> for payment and do not store your information in our " | ||||
| "database." | ||||
| msgstr "" | ||||
| "Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine " | ||||
| "Kreditkartendetails unten an. Die Bezahlung wird über " | ||||
| "<a href=\"https://stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. " | ||||
| "Wir speichern Deine Kreditkartendetails nicht in unserer Datenbank." | ||||
| 
 | ||||
| msgid "" | ||||
| "Please fill in your credit card information below. We are using <a href=" | ||||
| "\"https://stripe.com\" target=\"_blank\">Stripe</a> for payment and do not " | ||||
|  | @ -448,28 +482,24 @@ msgstr "" | |||
| "\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung und " | ||||
| "speichern keine Informationen in unserer Datenbank." | ||||
| 
 | ||||
| msgid "" | ||||
| "You are not making any payment yet. After submitting your card information, " | ||||
| "you will be taken to the Confirm Order Page." | ||||
| msgstr "" | ||||
| "Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst ausgelöst, " | ||||
| "nachdem Du die Bestellung auf der nächsten Seite bestätigt hast." | ||||
| msgid "Last" | ||||
| msgstr "Letzten" | ||||
| 
 | ||||
| msgid "SUBMIT" | ||||
| msgstr "ABSENDEN" | ||||
| 
 | ||||
| msgid "Card Number" | ||||
| msgstr "Kreditkartennummer" | ||||
| 
 | ||||
| msgid "Expiry Date" | ||||
| msgstr "Ablaufdatum" | ||||
| 
 | ||||
| msgid "CVC" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Card Type" | ||||
| msgid "Type" | ||||
| msgstr "Kartentyp" | ||||
| 
 | ||||
| msgid "SELECT" | ||||
| msgstr "AUSWÄHLEN" | ||||
| 
 | ||||
| msgid "Add a new credit card" | ||||
| msgstr "Eine neue Kreditkarte hinzufügen" | ||||
| 
 | ||||
| msgid "NEW CARD" | ||||
| msgstr "BEARBEITEN" | ||||
| 
 | ||||
| msgid "New Credit Card" | ||||
| msgstr "Neue Kreditkarte" | ||||
| 
 | ||||
| msgid "Processing" | ||||
| msgstr "Weiter" | ||||
| 
 | ||||
|  | @ -483,13 +513,22 @@ msgid "Password reset" | |||
| msgstr "Passwort zurücksetzen" | ||||
| 
 | ||||
| msgid "UPDATE" | ||||
| msgstr "" | ||||
| msgstr "AKTUALISIEREN" | ||||
| 
 | ||||
| msgid "Last" | ||||
| msgstr "" | ||||
| msgid "REMOVE CARD" | ||||
| msgstr "KARTE ENTFERNEN" | ||||
| 
 | ||||
| msgid "Type" | ||||
| msgstr "Kartentyp" | ||||
| msgid "Remove Card" | ||||
| msgstr "Karte entfernen" | ||||
| 
 | ||||
| msgid "Do you want to remove this associated card?" | ||||
| msgstr "Möchtest Du den Schlüssel löschen?" | ||||
| 
 | ||||
| msgid "Delete" | ||||
| msgstr "Löschen" | ||||
| 
 | ||||
| msgid "DEFAULT" | ||||
| msgstr "STANDARD" | ||||
| 
 | ||||
| msgid "No Credit Cards Added" | ||||
| msgstr "Es wurde keine Kreditkarte hinzugefügt" | ||||
|  | @ -534,10 +573,7 @@ msgid "Public Key" | |||
| msgstr "" | ||||
| 
 | ||||
| msgid "Private Key" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Delete" | ||||
| msgstr "Löschen" | ||||
| msgstr "Privater Schlüssel" | ||||
| 
 | ||||
| msgid "Delete SSH Key" | ||||
| msgstr "SSH Key löschen" | ||||
|  | @ -670,6 +706,36 @@ msgstr "Dein Passwort konnte nicht zurückgesetzt werden." | |||
| msgid "The reset password link is no longer valid." | ||||
| msgstr "Der Link zum Zurücksetzen Deines Passwortes ist nicht mehr gültig." | ||||
| 
 | ||||
| msgid "Card deassociation successful" | ||||
| msgstr "Die Verbindung mit der Karte wurde erfolgreich aufgehoben" | ||||
| 
 | ||||
| msgid "You are not permitted to do this operation" | ||||
| msgstr "Du hast keine Erlaubnis um diese Operation durchzuführen" | ||||
| 
 | ||||
| msgid "The selected card does not exist" | ||||
| msgstr "Die ausgewählte Karte existiert nicht" | ||||
| 
 | ||||
| msgid "Billing address updated successfully" | ||||
| msgstr "Die Rechnungsadresse wurde erfolgreich aktualisiert" | ||||
| 
 | ||||
| msgid "You seem to have already added this card" | ||||
| msgstr "Es scheint, als hättest du diese Karte bereits hinzugefügt" | ||||
| 
 | ||||
| #, python-brace-format | ||||
| msgid "An error occurred while associating the card. Details: {details}" | ||||
| msgstr "Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: " | ||||
| "{details}" | ||||
| 
 | ||||
| msgid "Successfully associated the card with your account" | ||||
| msgstr "Die Karte wurde erfolgreich mit deinem Konto verbunden" | ||||
| 
 | ||||
| #, python-brace-format | ||||
| msgid "{user} does not have permission to access the card" | ||||
| msgstr "{user} hat keine Erlaubnis auf diese Karte zuzugreifen" | ||||
| 
 | ||||
| msgid "An error occurred. Details: {}" | ||||
| msgstr "Ein Fehler ist aufgetreten. Details: {}" | ||||
| 
 | ||||
| msgid "Invalid credit card" | ||||
| msgstr "Ungültige Kreditkarte" | ||||
| 
 | ||||
|  | @ -807,15 +873,6 @@ msgstr "" | |||
| #~ msgid "Notifications " | ||||
| #~ msgstr "Benachrichtigungen" | ||||
| 
 | ||||
| #~ msgid "REMOVE CARD" | ||||
| #~ msgstr "KARTE ENTFERNEN" | ||||
| 
 | ||||
| #~ msgid "EDIT CARD" | ||||
| #~ msgstr "BEARBEITEN" | ||||
| 
 | ||||
| #~ msgid "Add a new Card." | ||||
| #~ msgstr "Neue Kreditkarte hinzufügen." | ||||
| 
 | ||||
| #~ msgid "You are not making any payment here." | ||||
| #~ msgstr "Es wird noch keine Bezahlung vorgenommen" | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										45
									
								
								hosting/management/commands/import_usercarddetails.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								hosting/management/commands/import_usercarddetails.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| from django.core.management.base import BaseCommand | ||||
| 
 | ||||
| from hosting.models import UserCardDetail | ||||
| from membership.models import CustomUser | ||||
| from utils.stripe_utils import StripeUtils | ||||
| 
 | ||||
| 
 | ||||
| class Command(BaseCommand): | ||||
|     help = '''Imports the usercard details of all customers. Created just for | ||||
|               multiple card support.''' | ||||
| 
 | ||||
|     def handle(self, *args, **options): | ||||
|         try: | ||||
|             stripe_utils = StripeUtils() | ||||
|             for user in CustomUser.objects.all(): | ||||
|                 if hasattr(user, 'stripecustomer'): | ||||
|                     if user.stripecustomer: | ||||
|                         card_details_resp = stripe_utils.get_card_details( | ||||
|                             user.stripecustomer.stripe_id | ||||
|                         ) | ||||
|                         card_details = card_details_resp['response_object'] | ||||
|                         if card_details: | ||||
|                             ucd = UserCardDetail.get_or_create_user_card_detail( | ||||
|                                 stripe_customer=user.stripecustomer, | ||||
|                                 card_details=card_details | ||||
|                             ) | ||||
|                             UserCardDetail.save_default_card_local( | ||||
|                                 user.stripecustomer.stripe_id, | ||||
|                                 ucd.card_id | ||||
|                             ) | ||||
|                             print("Saved user card details for {}".format( | ||||
|                                 user.email | ||||
|                             )) | ||||
|                         else: | ||||
|                             print(" --- Could not get card details for " | ||||
|                                   "{}".format(user.email)) | ||||
|                             print(" --- Error: {}".format( | ||||
|                                 card_details_resp['error'] | ||||
|                             )) | ||||
|                 else: | ||||
|                     print(" === {} does not have a StripeCustomer object".format( | ||||
|                         user.email | ||||
|                     )) | ||||
|         except Exception as e: | ||||
|             print(" *** Error occurred. Details {}".format(str(e))) | ||||
							
								
								
									
										36
									
								
								hosting/migrations/0046_usercarddetail.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								hosting/migrations/0046_usercarddetail.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| # Generated by Django 1.9.4 on 2018-07-03 20:32 | ||||
| from __future__ import unicode_literals | ||||
| 
 | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
| import utils.mixins | ||||
| 
 | ||||
| 
 | ||||
| class Migration(migrations.Migration): | ||||
| 
 | ||||
|     dependencies = [ | ||||
|         ('membership', '0007_auto_20180213_0128'), | ||||
|         ('hosting', '0045_auto_20180701_2028'), | ||||
|     ] | ||||
| 
 | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='UserCardDetail', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|                 ('last4', models.CharField(max_length=4)), | ||||
|                 ('brand', models.CharField(max_length=10)), | ||||
|                 ('card_id', models.CharField(blank=True, default='', max_length=100)), | ||||
|                 ('fingerprint', models.CharField(max_length=100)), | ||||
|                 ('exp_month', models.IntegerField()), | ||||
|                 ('exp_year', models.IntegerField()), | ||||
|                 ('preferred', models.BooleanField(default=False)), | ||||
|                 ('stripe_customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='membership.StripeCustomer')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'permissions': (('view_usercarddetail', 'View User Card'),), | ||||
|             }, | ||||
|             bases=(utils.mixins.AssignPermissionsMixin, models.Model), | ||||
|         ), | ||||
|     ] | ||||
|  | @ -1,16 +1,17 @@ | |||
| import os | ||||
| import logging | ||||
| from dateutil.relativedelta import relativedelta | ||||
| import os | ||||
| 
 | ||||
| from Crypto.PublicKey import RSA | ||||
| from dateutil.relativedelta import relativedelta | ||||
| from django.db import models | ||||
| from django.utils import timezone | ||||
| from django.utils.functional import cached_property | ||||
| from Crypto.PublicKey import RSA | ||||
| 
 | ||||
| from datacenterlight.models import VMPricing, VMTemplate | ||||
| from membership.models import StripeCustomer, CustomUser | ||||
| from utils.models import BillingAddress | ||||
| from utils.mixins import AssignPermissionsMixin | ||||
| from utils.stripe_utils import StripeUtils | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
|  | @ -205,3 +206,156 @@ class VMDetail(models.Model): | |||
|         months = relativedelta(end_date, self.created_at).months or 1 | ||||
|         end_date = self.created_at + relativedelta(months=months, days=-1) | ||||
|         return end_date | ||||
| 
 | ||||
| 
 | ||||
| class UserCardDetail(AssignPermissionsMixin, models.Model): | ||||
|     permissions = ('view_usercarddetail',) | ||||
|     stripe_customer = models.ForeignKey(StripeCustomer) | ||||
|     last4 = models.CharField(max_length=4) | ||||
|     brand = models.CharField(max_length=10) | ||||
|     card_id = models.CharField(max_length=100, blank=True, default='') | ||||
|     fingerprint = models.CharField(max_length=100) | ||||
|     exp_month = models.IntegerField(null=False) | ||||
|     exp_year = models.IntegerField(null=False) | ||||
|     preferred = models.BooleanField(default=False) | ||||
| 
 | ||||
|     class Meta: | ||||
|         permissions = ( | ||||
|             ('view_usercarddetail', 'View User Card'), | ||||
|         ) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def create(cls, stripe_customer=None, last4=None, brand=None, | ||||
|                fingerprint=None, exp_month=None, exp_year=None, card_id=None, | ||||
|                preferred=False): | ||||
|         instance = cls.objects.create( | ||||
|             stripe_customer=stripe_customer, last4=last4, brand=brand, | ||||
|             fingerprint=fingerprint, exp_month=exp_month, exp_year=exp_year, | ||||
|             card_id=card_id, preferred=preferred | ||||
|         ) | ||||
|         instance.assign_permissions(stripe_customer.user) | ||||
|         return instance | ||||
| 
 | ||||
|     @classmethod | ||||
|     def get_all_cards_list(cls, stripe_customer): | ||||
|         """ | ||||
|         Get all the cards of the given customer as a list | ||||
| 
 | ||||
|         :param stripe_customer: The StripeCustomer object | ||||
|         :return: A list of all cards; an empty list if the customer object is | ||||
|                  None | ||||
|         """ | ||||
|         cards_list = [] | ||||
|         if stripe_customer is None: | ||||
|             return cards_list | ||||
|         user_card_details = UserCardDetail.objects.filter( | ||||
|             stripe_customer_id=stripe_customer.id | ||||
|         ).order_by('-preferred', 'id') | ||||
|         for card in user_card_details: | ||||
|             cards_list.append({ | ||||
|                 'last4': card.last4, 'brand': card.brand, 'id': card.id, | ||||
|                 'preferred': card.preferred | ||||
|             }) | ||||
|         return cards_list | ||||
| 
 | ||||
|     @classmethod | ||||
|     def get_or_create_user_card_detail(cls, stripe_customer, card_details): | ||||
|         """ | ||||
|         A method that checks if a UserCardDetail object exists already | ||||
|         based upon the given card_details and creates it for the given | ||||
|         customer if it does not exist. It returns the UserCardDetail object | ||||
|         matching the given card_details if it exists. | ||||
| 
 | ||||
|         :param stripe_customer: The given StripeCustomer object to whom the | ||||
|                 card object should belong to | ||||
|         :param card_details: A dictionary identifying a given card | ||||
|         :return: UserCardDetail object | ||||
|         """ | ||||
|         try: | ||||
|             if ('fingerprint' in card_details and 'exp_month' in card_details | ||||
|                     and 'exp_year' in card_details): | ||||
|                 card_detail = UserCardDetail.objects.get( | ||||
|                     stripe_customer=stripe_customer, | ||||
|                     fingerprint=card_details['fingerprint'], | ||||
|                     exp_month=card_details['exp_month'], | ||||
|                     exp_year=card_details['exp_year'] | ||||
|                 ) | ||||
|             else: | ||||
|                 raise UserCardDetail.DoesNotExist() | ||||
|         except UserCardDetail.DoesNotExist: | ||||
|             preferred = False | ||||
|             if 'preferred' in card_details: | ||||
|                 preferred = card_details['preferred'] | ||||
|             card_detail = UserCardDetail.create( | ||||
|                 stripe_customer=stripe_customer, | ||||
|                 last4=card_details['last4'], | ||||
|                 brand=card_details['brand'], | ||||
|                 fingerprint=card_details['fingerprint'], | ||||
|                 exp_month=card_details['exp_month'], | ||||
|                 exp_year=card_details['exp_year'], | ||||
|                 card_id=card_details['card_id'], | ||||
|                 preferred=preferred | ||||
|             ) | ||||
|         return card_detail | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def set_default_card(stripe_api_cus_id, stripe_source_id): | ||||
|         """ | ||||
|         Sets the given stripe source as the default source for the given | ||||
|         Stripe customer | ||||
|         :param stripe_api_cus_id: Stripe customer id | ||||
|         :param stripe_source_id: The Stripe source id | ||||
|         :return: | ||||
|         """ | ||||
|         stripe_utils = StripeUtils() | ||||
|         cus_response = stripe_utils.get_customer(stripe_api_cus_id) | ||||
|         cu = cus_response['response_object'] | ||||
|         cu.default_source = stripe_source_id | ||||
|         cu.save() | ||||
|         UserCardDetail.save_default_card_local( | ||||
|             stripe_api_cus_id, stripe_source_id | ||||
|         ) | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def set_default_card_from_stripe(stripe_api_cus_id): | ||||
|         stripe_utils = StripeUtils() | ||||
|         cus_response = stripe_utils.get_customer(stripe_api_cus_id) | ||||
|         cu = cus_response['response_object'] | ||||
|         default_source = cu.default_source | ||||
|         if default_source is not None: | ||||
|             UserCardDetail.save_default_card_local( | ||||
|                 stripe_api_cus_id, default_source | ||||
|             ) | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def save_default_card_local(stripe_api_cus_id, card_id): | ||||
|         stripe_cust = StripeCustomer.objects.get(stripe_id=stripe_api_cus_id) | ||||
|         user_card_detail = UserCardDetail.objects.get( | ||||
|             stripe_customer=stripe_cust, card_id=card_id | ||||
|         ) | ||||
|         for card in stripe_cust.usercarddetail_set.all(): | ||||
|             card.preferred = False | ||||
|             card.save() | ||||
|         user_card_detail.preferred = True | ||||
|         user_card_detail.save() | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def get_user_card_details(stripe_customer, card_details): | ||||
|         """ | ||||
|         A utility function to check whether a StripeCustomer is already | ||||
|         associated with the card having given details | ||||
| 
 | ||||
|         :param stripe_customer: | ||||
|         :param card_details: | ||||
|         :return: The UserCardDetails object if it exists, None otherwise | ||||
|         """ | ||||
|         try: | ||||
|             ucd = UserCardDetail.objects.get( | ||||
|                 stripe_customer=stripe_customer, | ||||
|                 fingerprint=card_details['fingerprint'], | ||||
|                 exp_month=card_details['exp_month'], | ||||
|                 exp_year=card_details['exp_year'] | ||||
|             ) | ||||
|             return ucd | ||||
|         except UserCardDetail.DoesNotExist: | ||||
|             return None | ||||
|  |  | |||
|  | @ -23,6 +23,13 @@ | |||
| 	margin: 0 auto; | ||||
| 	max-width: 1120px; | ||||
| } | ||||
| .container-table{ | ||||
|   margin-top: 35px; | ||||
|   overflow-y: hidden; | ||||
| } | ||||
| .container-table table{ | ||||
|   overflow-y: auto; | ||||
| } | ||||
| .borderless td { | ||||
|     border: none !important; | ||||
| } | ||||
|  | @ -35,6 +42,19 @@ | |||
|     color: transparent; | ||||
| } | ||||
| 
 | ||||
| .inline-headers h3, .inline-headers h4 { | ||||
|   display: inline-block; | ||||
|   vertical-align: baseline; | ||||
| } | ||||
| 
 | ||||
| .space-above { | ||||
|   margin-top: 4%; | ||||
| } | ||||
| 
 | ||||
| .space-above-big { | ||||
|   margin-top: 20%; | ||||
| } | ||||
| 
 | ||||
| .table>tbody>tr>td{ | ||||
|   vertical-align: middle; | ||||
| } | ||||
|  | @ -274,6 +294,26 @@ | |||
|   font-size: 16px; | ||||
| } | ||||
| 
 | ||||
| .payment-container .credit-card-info { | ||||
|   padding-bottom: 15px; | ||||
|   border-bottom: 1px solid #eee; | ||||
| } | ||||
| 
 | ||||
| .credit-card-info { | ||||
|   display: flex; | ||||
| } | ||||
| 
 | ||||
| .credit-card-info .align-bottom{ | ||||
|   align-self: flex-end; | ||||
|   padding-right: 0 !important; | ||||
| } | ||||
| 
 | ||||
| .another-card-text { | ||||
|   padding: 20px 0; | ||||
|   font-size: 18px; | ||||
|   font-weight: 700; | ||||
| } | ||||
| 
 | ||||
| .credit-card-form { | ||||
|   max-width: 360px; | ||||
| } | ||||
|  | @ -293,8 +333,15 @@ | |||
|   text-decoration: none; | ||||
| } | ||||
| 
 | ||||
| .settings-container .credit-card-details-opt { | ||||
|   padding-top: 15px; | ||||
| .settings-container .new-card-head { | ||||
|   margin-top: 40px; | ||||
|   margin-bottom: 30px; | ||||
| } | ||||
| 
 | ||||
| .settings-container .new-card-head h4 { | ||||
|   font-size: 15px; | ||||
|   margin-top: 8px; | ||||
|   font-weight: 600; | ||||
| } | ||||
| 
 | ||||
| .caps-link .svg-img { | ||||
|  | @ -313,7 +360,11 @@ | |||
| .settings-container .btn-vm-contact { | ||||
|   font-weight: 600; | ||||
|   font-size: 13px; | ||||
|   /* padding: 4px 15px; */ | ||||
| } | ||||
| 
 | ||||
| .settings-container .choice-btn { | ||||
|   letter-spacing: 2px; | ||||
|   min-width: 127px; | ||||
| } | ||||
| 
 | ||||
| .btn-wide { | ||||
|  | @ -355,6 +406,15 @@ | |||
|   fill: #999; | ||||
| } | ||||
| 
 | ||||
| .card-details-box { | ||||
|   border: 1px solid #eee; | ||||
|   padding: 5px 25px 25px; | ||||
| } | ||||
| 
 | ||||
| .thick-hr { | ||||
|   border-top: 5px solid #eee; | ||||
| } | ||||
| 
 | ||||
| .locale_date { | ||||
|   opacity: 0; | ||||
| } | ||||
|  | @ -370,4 +430,13 @@ | |||
| .thin-hr { | ||||
|   margin-top: 10px; | ||||
|   margin-bottom: 10px; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .new-card-head { | ||||
|     margin-top: 10px; | ||||
| } | ||||
| .new-card-button-margin button{ | ||||
|     margin-top: 5px; | ||||
|     margin-bottom: 5px; | ||||
| } | ||||
|  | @ -269,7 +269,7 @@ | |||
|   border: 2px solid #A3C0E2; | ||||
|   padding: 5px 25px; | ||||
|   font-size: 12px; | ||||
|   letter-spacing: 1.3px; | ||||
|   letter-spacing: 2px; | ||||
| } | ||||
| .btn-vm-contact:hover, .btn-vm-contact:focus { | ||||
|   background: #fff; | ||||
|  |  | |||
|  | @ -195,5 +195,11 @@ $(document).ready(function () { | |||
|             $(element).closest('.form-group').append(error); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     $('.credit-card-info .btn.choice-btn').click(function(){ | ||||
|             var id = this.dataset['id_card']; | ||||
|             $('#id_card').val(id); | ||||
|             $('#billing-form').submit(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										50
									
								
								hosting/templates/hosting/includes/_card_input.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								hosting/templates/hosting/includes/_card_input.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | |||
| {% load i18n %} | ||||
| 
 | ||||
| <form action="" id="payment-form-new" method="POST"> | ||||
|     <input type="hidden" name="token"/> | ||||
|     <input type="hidden" name="id_card" id="id_card" value=""/> | ||||
|     <div class="group"> | ||||
|         <div class="credit-card-goup"> | ||||
|            <div class="card-element card-number-element"> | ||||
|                <label>{%trans "Card Number" %}</label> | ||||
|                <div id="card-number-element" class="field my-input"></div> | ||||
|            </div> | ||||
|            <div class="row"> | ||||
|                <div class="col-xs-5 card-element card-expiry-element"> | ||||
|                    <label>{%trans "Expiry Date" %}</label> | ||||
|                    <div id="card-expiry-element" class="field my-input"></div> | ||||
|                </div> | ||||
|                <div class="col-xs-3 col-xs-offset-4 card-element card-cvc-element"> | ||||
|                    <label>{%trans "CVC" %}</label> | ||||
|                    <div id="card-cvc-element" class="field my-input"></div> | ||||
|                </div> | ||||
|            </div> | ||||
|            <div class="card-element brand"> | ||||
|                <label>{%trans "Card Type" %}</label> | ||||
|                <i class="pf pf-credit-card" id="brand-icon"></i> | ||||
|            </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div id="card-errors"></div> | ||||
|     {% if not messages and not form.non_field_errors %} | ||||
|         <p class="card-warning-content"> | ||||
|             {% trans "You are not making any payment yet. After placing your order, you will be taken to the Submit Payment Page." %} | ||||
|         </p> | ||||
|     {% endif %} | ||||
|     <div id='payment_error'> | ||||
|         {% for message in messages %} | ||||
|             {% if 'failed_payment' in message.tags or 'make_charge_error' in message.tags or 'error' in message.tags %} | ||||
|                 <ul class="list-unstyled"> | ||||
|                     <li><p class="card-warning-content card-warning-error">{{ message|safe }}</p></li> | ||||
|                 </ul> | ||||
|             {% endif %} | ||||
|         {% endfor %} | ||||
|     </div> | ||||
|     <div class="text-right"> | ||||
|         <button class="btn btn-vm-contact btn-wide" type="submit" name="payment-form">{%trans "SUBMIT" %}</button> | ||||
|     </div> | ||||
| 
 | ||||
|     <div style="display:none;"> | ||||
|         <p class="payment-errors"></p> | ||||
|     </div> | ||||
| </form> | ||||
|  | @ -105,114 +105,65 @@ | |||
|                         <h3><b>{%trans "Billing Address"%}</b></h3> | ||||
|                         <hr> | ||||
|                         <form role="form" id="billing-form" method="post" action="" novalidate> | ||||
|                             {% for field in form %} | ||||
|                             {% csrf_token %} | ||||
|                             {% for field in form %} | ||||
|                             {% bootstrap_field field show_label=False type='fields'%} | ||||
|                             {% endfor %} | ||||
|                         </form> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="col-sm-7 col-md-6"> | ||||
|                     <div class="creditcard-box dcl-creditcard"> | ||||
|                         <h3><b>{%trans "Credit Card"%}</b></h3> | ||||
|                         <hr> | ||||
|                 <div class="col-xs-12 col-sm-7 col-md-6 creditcard-box dcl-creditcard"> | ||||
|                     {% with card_list_len=cards_list|length %} | ||||
|                     <h3><b>{%trans "Credit Card"%}</b></h3> | ||||
|                     <hr> | ||||
|                     <div> | ||||
|                         <p> | ||||
|                             {% if card_list_len > 0 %} | ||||
|                             {% blocktrans %}Please select one of the cards that you used before or fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %} | ||||
|                             {% else %} | ||||
|                             {% blocktrans %}Please fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %} | ||||
|                             {% endif %} | ||||
|                         </p> | ||||
|                         <div> | ||||
|                             <p> | ||||
|                                 {% blocktrans %}Please fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %} | ||||
|                             </p> | ||||
|                             <div> | ||||
|                                 {% if credit_card_data.last4 %} | ||||
|                                     <form role="form" id="payment-form-with-creditcard" novalidate> | ||||
|                                         <h5 class="billing-head">Credit Card</h5> | ||||
|                                         <h5 class="membership-lead">Last 4: *****{{credit_card_data.last4}}</h5> | ||||
|                                         <h5 class="membership-lead">Type: {{credit_card_data.cc_brand}}</h5> | ||||
|                                         <input type="hidden" name="credit_card_needed" value="false"/> | ||||
|                                     </form> | ||||
|                                     {% if not messages and not form.non_field_errors %} | ||||
|                                         <p class="card-warning-content card-warning-addtional-margin"> | ||||
|                                             {% trans "You are not making any payment yet. After submitting your card information, you will be taken to the Confirm Order Page." %} | ||||
|                                         </p> | ||||
|                                     {% endif %} | ||||
|                                     <div id='payment_error'> | ||||
|                                         {% for message in messages %} | ||||
|                                             {% if 'failed_payment' or 'make_charge_error' in message.tags %} | ||||
|                                              <ul class="list-unstyled"> | ||||
|                                                 <li> | ||||
|                                                     <p class="card-warning-content card-warning-error">{{ message|safe }}</p> | ||||
|                                                 </li> | ||||
|                                             </ul> | ||||
|                                             {% endif %} | ||||
|                                         {% endfor %} | ||||
|                                         {% for error in form.non_field_errors %} | ||||
|                                             <p class="card-warning-content card-warning-error"> | ||||
|                                                 {{ error|escape }} | ||||
|                                             </p> | ||||
|                                         {% endfor %} | ||||
|                             {% for card in cards_list %} | ||||
|                                 <div class="credit-card-info"> | ||||
|                                     <div class="col-xs-6 no-padding"> | ||||
|                                         <h5 class="billing-head">{% trans "Credit Card" %}</h5> | ||||
|                                         <h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5> | ||||
|                                         <h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5> | ||||
|                                     </div> | ||||
|                                     <div class="text-right"> | ||||
|                                         <button id="payment_button_with_creditcard" class="btn btn-vm-contact" type="submit">{%trans "SUBMIT" %}</button> | ||||
|                                     <div class="col-xs-6 text-right align-bottom"> | ||||
|                                         <a class="btn choice-btn choice-btn-faded" href="#" data-id_card="{{card.id}}">{% trans "SELECT" %}</a> | ||||
|                                     </div> | ||||
|                                 {% else %} | ||||
|                                     <form action="" id="payment-form-new" method="POST"> | ||||
|                                         <input type="hidden" name="token"/> | ||||
|                                         <div class="group"> | ||||
|                                             <div class="credit-card-goup"> | ||||
|                                                 <div class="card-element card-number-element"> | ||||
|                                                     <label>{%trans "Card Number" %}</label> | ||||
|                                                     <div id="card-number-element" class="field my-input"></div> | ||||
|                                                 </div> | ||||
|                                                 <div class="row"> | ||||
|                                                     <div class="col-xs-5 card-element card-expiry-element"> | ||||
|                                                         <label>{%trans "Expiry Date" %}</label> | ||||
|                                                         <div id="card-expiry-element" class="field my-input"></div> | ||||
|                                                     </div> | ||||
|                                                     <div class="col-xs-3 col-xs-offset-4 card-element card-cvc-element"> | ||||
|                                                         <label>{%trans "CVC" %}</label> | ||||
|                                                         <div id="card-cvc-element" class="field my-input"></div> | ||||
|                                                     </div> | ||||
|                                                 </div> | ||||
|                                                 <div class="card-element brand"> | ||||
|                                                    <label>{%trans "Card Type" %}</label> | ||||
|                                                    <i class="pf pf-credit-card" id="brand-icon"></i> | ||||
|                                                </div> | ||||
|                                             </div> | ||||
|                                 </div> | ||||
|                             {% endfor %} | ||||
|                             {% if card_list_len > 0 %} | ||||
|                                 <div class="new-card-head"> | ||||
|                                     <div class="row"> | ||||
|                                         <div class="col-xs-6"> | ||||
|                                             <h4>{% trans "Add a new credit card" %}</h4> | ||||
|                                         </div> | ||||
|                                         <div id="card-errors"></div> | ||||
|                                             {% if not messages and not form.non_field_errors %} | ||||
|                                                 <p class="card-warning-content"> | ||||
|                                                     {% trans "You are not making any payment yet. After submitting your card information, you will be taken to the Confirm Order Page." %} | ||||
|                                                 </p> | ||||
|                                             {% endif %} | ||||
|                                             <div id='payment_error'> | ||||
|                                                 {% for message in messages %} | ||||
|                                                     {% if 'failed_payment' or 'make_charge_error' in message.tags %} | ||||
|                                                      <ul class="list-unstyled"> | ||||
|                                                         <li> | ||||
|                                                             <p class="card-warning-content card-warning-error">{{ message|safe }}</p> | ||||
|                                                         </li> | ||||
|                                                     </ul> | ||||
|                                                     {% endif %} | ||||
|                                                 {% endfor %} | ||||
| 
 | ||||
|                                                 {% for error in form.non_field_errors %} | ||||
|                                                     <p class="card-warning-content card-warning-error"> | ||||
|                                                         {{ error|escape }} | ||||
|                                                     </p> | ||||
|                                                 {% endfor %} | ||||
|                                             </div> | ||||
|                                             <div class="text-right"> | ||||
|                                                 <button class="btn btn-vm-contact btn-wide" type="submit">{%trans "SUBMIT" %}</button> | ||||
|                                             </div> | ||||
|                                         <div class="col-xs-6 text-right"> | ||||
|                                             <button data-toggle="collapse" data-target="#newcard" class="btn choice-btn"> | ||||
|                                                 <span class="fa fa-plus"></span>  {% trans "NEW CARD" %} | ||||
|                                             </button> | ||||
|                                         </div> | ||||
| 
 | ||||
|                                         <div style="display:none;"> | ||||
|                                             <p class="payment-errors"></p> | ||||
|                                         </div> | ||||
|                                     </form> | ||||
|                                 {% endif %} | ||||
|                             </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                                 <div id="newcard" class="collapse"> | ||||
|                                     <hr class="thick-hr"> | ||||
|                                     <div class="card-details-box"> | ||||
|                                         <h3>{%trans "New Credit Card" %}</h3> | ||||
|                                         <hr> | ||||
|                                         {% include "hosting/includes/_card_input.html" %} | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             {% else%} | ||||
|                                 {% include "hosting/includes/_card_input.html" %} | ||||
|                             {% endif %} | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     {% endwith %} | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|  | @ -232,7 +183,7 @@ | |||
|     })(); | ||||
| </script> | ||||
| {%endif%} | ||||
| 
 | ||||
| {% comment "Looks as if no more used. To test..." %} | ||||
| {% if credit_card_data.last4 and credit_card_data.cc_brand %} | ||||
| <script type="text/javascript"> | ||||
|     (function () { | ||||
|  | @ -240,5 +191,5 @@ | |||
|     })(); | ||||
| </script> | ||||
| {%endif%} | ||||
| 
 | ||||
| {% endcomment %} | ||||
| {%endblock%} | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
| 
 | ||||
| {% block content %} | ||||
|     <div class="dashboard-container wide"> | ||||
|         {% include 'hosting/includes/_messages.html' %} | ||||
|         <div class="dashboard-container-head"> | ||||
|             <h1 class="dashboard-title-thin"><img src="{% static 'hosting/img/dashboard_settings.svg' %}" class="un-icon wide"> {% trans "My Settings" %}</h1> | ||||
|         </div> | ||||
|  | @ -14,7 +15,7 @@ | |||
|         <div class="settings-container"> | ||||
|             <div class="row"> | ||||
|                 <div class="col-sm-5 col-md-6  billing dcl-billing"> | ||||
|                     <h3>{%trans "Billing Address"%}</h3> | ||||
|                     <h3>{%trans "Billing Address" %}</h3> | ||||
|                     <hr> | ||||
|                     <form role="form" id="billing-form" method="post" action="" novalidate> | ||||
|                         {% for field in form %} | ||||
|  | @ -22,108 +23,97 @@ | |||
|                             {% bootstrap_field field show_label=False type='fields' bound_css_class='' %} | ||||
|                         {% endfor %} | ||||
|                         <div class="form-group text-right"> | ||||
|                             <button type="submit" class="btn btn-vm-contact btn-wide">{% trans "UPDATE" %}</button> | ||||
|                             <button type="submit" class="btn btn-vm-contact btn-wide" name="billing-form">{% trans "UPDATE" %}</button> | ||||
|                         </div> | ||||
|                     </form> | ||||
|                 </div> | ||||
|                 <div class="col-sm-7 col-md-6 creditcard-box dcl-creditcard"> | ||||
|                     <h3>{%trans "Credit Card"%}</h3> | ||||
|                     <h3>{%trans "Credit Card" %}</h3> | ||||
|                     <hr> | ||||
|                     <div> | ||||
|                         {% if credit_card_data.last4 %} | ||||
|                         {% with card_list_len=cards_list|length %} | ||||
|                         {% for card in cards_list %} | ||||
|                             <div class="credit-card-details"> | ||||
|                                 <h5 class="billing-head">{% trans "Credit Card" %}</h5> | ||||
|                                 <h5 class="membership-lead">{% trans "Last" %} 4: *****{{credit_card_data.last4}}</h5> | ||||
|                                 <h5 class="membership-lead">{% trans "Type" %}: {{credit_card_data.cc_brand}}</h5> | ||||
|                                 {% comment %} | ||||
|                                 <h5 class="membership-lead">{% trans "Last" %} 4: ***** {{card.last4}}</h5> | ||||
|                                 <h5 class="membership-lead">{% trans "Type" %}: {{card.brand}}</h5> | ||||
|                                 <div class="credit-card-details-opt"> | ||||
|                                     <div class="row"> | ||||
|                                         {% if card_list_len > 1 %} | ||||
|                                         <div class="col-xs-6"> | ||||
|                                             <a class="caps-link" href=""><img src="{% static 'hosting/img/delete.svg' %}" class="svg-img">{% trans "REMOVE CARD" %}</a> | ||||
|                                             <a class="caps-link" href="" data-toggle="modal" data-target="#Modal{{ card.id }}"><img src="{% static 'hosting/img/delete.svg' %}" class="svg-img">{% trans "REMOVE CARD" %}</a> | ||||
|                                             <div class="modal fade" id="Modal{{card.id }}" tabindex="-1" role="dialog"> | ||||
|                                                 <div class="modal-dialog" role="document"> | ||||
|                                                     <div class="modal-content"> | ||||
|                                                         <div class="modal-header"> | ||||
|                                                             <button type="button" class="close" data-dismiss="modal" aria-label="Confirm"><span aria-hidden="true">×</span></button> | ||||
|                                                         </div> | ||||
|                                                         <div class="modal-body"> | ||||
|                                                             <div class="modal-icon"><i class="fa fa-trash" aria-hidden="true"></i></div> | ||||
|                                                             <h4 class="modal-title" id="ModalLabel">{% trans "Remove Card"%}</h4> | ||||
|                                                             <div class="modal-text"> | ||||
|                                                                 <p>{% trans "Do you want to remove this associated card?"%}</p> | ||||
|                                                             </div> | ||||
|                                                             <form method="post" action="{% url 'hosting:delete_card' card.id %}"> | ||||
|                                                                 {% csrf_token %} | ||||
|                                                                 <div class="modal-footer"> | ||||
|                                                                     <button type="submit" class="btn btn-danger btn-wide" name="delete_card">{% trans "Delete"%}</button> | ||||
|                                                                 </div> | ||||
|                                                             </form> | ||||
|                                                         </div> | ||||
|                                                     </div> | ||||
|                                                 </div> | ||||
|                                             </div> | ||||
|                                         </div> | ||||
|                                         {% endif %} | ||||
|                                         <div class="col-xs-6 text-right"> | ||||
|                                             <a class="btn btn-vm-contact" href="">{% trans "EDIT CARD" %}</a> | ||||
|                                             {% if card.preferred %} | ||||
|                                             {% trans "DEFAULT" %} | ||||
|                                             {% else %} | ||||
|                                             <form method="post" action=""> | ||||
|                                                 {% csrf_token %} | ||||
|                                                 <input type="hidden" name="card" value="{{card.id}}"> | ||||
|                                                 <a class="btn choice-btn choice-btn-faded" href="#"  onclick="$(this).closest('form').submit()">{% trans "SELECT" %}</a> | ||||
|                                             </form> | ||||
|                                             {% endif %} | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                                 {% endcomment %} | ||||
|                             </div> | ||||
|                         {% else %} | ||||
|                         {% empty %} | ||||
|                             <div class="no-cards"> | ||||
|                                 <h4>{% trans "No Credit Cards Added" %}</h4> | ||||
|                                 <p>{% blocktrans %}We are using <a href="https://stripe.com">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %}</p> | ||||
|                             </div> | ||||
|                         {% endfor %} | ||||
|                         {% endwith %} | ||||
| 
 | ||||
|                             {% comment %} | ||||
|                                 <h4>{% trans "Add a new Card." %}</h4> | ||||
|                                 <p style="margin-bottom: 15px;"> | ||||
|                                     {% blocktrans %}Please fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %} | ||||
|                                 </p> | ||||
|                                 <form action="" id="payment-form-new" class="credit-card-form" method="POST"> | ||||
|                                     <input type="hidden" name="token"/> | ||||
|                                     <div class="credit-card-goup"> | ||||
|                                        <div class="card-element card-number-element"> | ||||
|                                            <label>{%trans "Card Number" %}</label> | ||||
|                                            <div id="card-number-element" class="field my-input"></div> | ||||
|                                        </div> | ||||
|                                        <div class="row"> | ||||
|                                            <div class="col-xs-6 col-sm-4 card-element card-expiry-element"> | ||||
|                                                <label>{%trans "Expiry Date" %}</label> | ||||
|                                                <div id="card-expiry-element" class="field my-input"></div> | ||||
|                                            </div> | ||||
|                                            <div class="col-xs-6 col-sm-4 col-sm-offset-4 card-element card-cvc-element"> | ||||
|                                                <label>{%trans "CVC" %}</label> | ||||
|                                                <div id="card-cvc-element" class="field my-input"></div> | ||||
|                                            </div> | ||||
|                                        </div> | ||||
|                                        <div class="card-element brand"> | ||||
|                                            <label>{%trans "Card Type" %}</label> | ||||
|                                            <i class="pf pf-credit-card" id="brand-icon"></i> | ||||
|                                        </div> | ||||
|                                     </div> | ||||
|                                     <div id="card-errors" role="alert"></div> | ||||
|                                     <div> | ||||
|                                         {% if not messages and not form.non_field_errors %} | ||||
|                                             <p class="card-warning-content"> | ||||
|                                                 {% blocktrans %}You are not making any payment here.{% endblocktrans %} | ||||
|                                             </p> | ||||
|                                         {% endif %} | ||||
|                                         <div id='payment_error'> | ||||
|                                             {% for message in messages %} | ||||
|                                                 {% if 'failed_payment' or 'make_charge_error' in message.tags %} | ||||
|                                                  <ul class="list-unstyled"><li> | ||||
|                                                      <p class="card-warning-content card-warning-error">{{ message|safe }}</p> | ||||
|                                                 </li></ul> | ||||
|                                                 {% endif %} | ||||
|                                             {% endfor %} | ||||
| 
 | ||||
|                                             {% for error in form.non_field_errors %} | ||||
|                                                 <p class="card-warning-content card-warning-error"> | ||||
|                                                     {{ error|escape }} | ||||
|                                                 </p> | ||||
|                                             {% endfor %} | ||||
|                                         </div> | ||||
|                                         <div class="row"> | ||||
|                                             <div class="col-xs-6 col-xs-offset-6 text-right"> | ||||
|                                                 <button class="btn btn-success stripe-payment-btn" type="submit">{%trans "Submit" %} | ||||
|                                                 </button> | ||||
|                                             </div> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
| 
 | ||||
|                                     <div style="display:none;"> | ||||
|                                         <p class="payment-errors"></p> | ||||
|                                     </div> | ||||
|                                 </form> | ||||
|                             {% endcomment %} | ||||
|                         {% endif %} | ||||
|                         <div class="new-card-head"> | ||||
|                             <div class="row"> | ||||
|                                 <div class="col-xs-6"> | ||||
|                                     <h4>{% trans "Add a new credit card" %}</h4> | ||||
|                                 </div> | ||||
|                                 <div class="col-xs-6 text-right"> | ||||
|                                     <button data-toggle="collapse" data-target="#newcard" class="btn choice-btn"> | ||||
|                                         <span class="fa fa-plus"></span>  {% trans "NEW CARD" %} | ||||
|                                     </button> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div id="newcard" class="collapse"> | ||||
|                             <hr class="thick-hr"> | ||||
|                             <div class="card-details-box"> | ||||
|                                 <h3>{%trans "New Credit Card" %}</h3> | ||||
|                                 <hr> | ||||
|                                 {% include "hosting/includes/_card_input.html" %} | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
|     {% comment %} | ||||
|     <!-- stripe key data --> | ||||
|     {% if stripe_key %} | ||||
|         {% get_current_language as LANGUAGE_CODE %} | ||||
|  | @ -137,13 +127,4 @@ | |||
|             })(); | ||||
|         </script> | ||||
|     {%endif%} | ||||
| 
 | ||||
|     {% if credit_card_data.last4 and credit_card_data.cc_brand %} | ||||
|         <script type="text/javascript"> | ||||
|             (function () { | ||||
|                 window.hasCreditcard = true; | ||||
|             })(); | ||||
|         </script> | ||||
|     {%endif%} | ||||
|     {% endcomment %} | ||||
| {%endblock%} | ||||
|  |  | |||
|  | @ -43,6 +43,8 @@ urlpatterns = [ | |||
|         name='choice_ssh_keys'), | ||||
|     url(r'delete_ssh_key/(?P<pk>\d+)/?$', SSHKeyDeleteView.as_view(), | ||||
|         name='delete_ssh_key'), | ||||
|     url(r'delete_card/(?P<pk>\d+)/?$', SettingsView.as_view(), | ||||
|         name='delete_card'), | ||||
|     url(r'create_ssh_key/?$', SSHKeyCreateView.as_view(), | ||||
|         name='create_ssh_key'), | ||||
|     url(r'^notifications/$', NotificationsView.as_view(), | ||||
|  |  | |||
							
								
								
									
										342
									
								
								hosting/views.py
									
										
									
									
									
								
							
							
						
						
									
										342
									
								
								hosting/views.py
									
										
									
									
									
								
							|  | @ -15,11 +15,12 @@ from django.http import ( | |||
|     Http404, HttpResponseRedirect, HttpResponse, JsonResponse | ||||
| ) | ||||
| from django.shortcuts import redirect, render | ||||
| from django.utils.decorators import method_decorator | ||||
| from django.utils.html import escape | ||||
| from django.utils.http import urlsafe_base64_decode | ||||
| from django.utils.safestring import mark_safe | ||||
| from django.utils.translation import get_language, ugettext_lazy as _ | ||||
| from django.utils.translation import ugettext | ||||
| from django.utils.decorators import method_decorator | ||||
| from django.views.decorators.cache import never_cache | ||||
| from django.views.generic import ( | ||||
|     View, CreateView, FormView, ListView, DetailView, DeleteView, | ||||
|  | @ -33,6 +34,7 @@ from stored_messages.settings import stored_messages_settings | |||
| 
 | ||||
| from datacenterlight.models import VMTemplate, VMPricing | ||||
| from datacenterlight.utils import create_vm, get_cms_integration | ||||
| from hosting.models import UserCardDetail | ||||
| from membership.models import CustomUser, StripeCustomer | ||||
| from opennebula_api.models import OpenNebulaManager | ||||
| from opennebula_api.serializers import ( | ||||
|  | @ -43,7 +45,7 @@ from utils.forms import ( | |||
|     BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm, | ||||
|     ResendActivationEmailForm | ||||
| ) | ||||
| from utils.hosting_utils import get_vm_price_with_vat | ||||
| from utils.hosting_utils import get_vm_price_with_vat, HostingUtils | ||||
| from utils.mailer import BaseEmail | ||||
| from utils.stripe_utils import StripeUtils | ||||
| from utils.tasks import send_plain_email_task | ||||
|  | @ -565,6 +567,7 @@ class SettingsView(LoginRequiredMixin, FormView): | |||
|     template_name = "hosting/settings.html" | ||||
|     login_url = reverse_lazy('hosting:login') | ||||
|     form_class = BillingAddressForm | ||||
|     permission_required = ['view_usercarddetail'] | ||||
| 
 | ||||
|     def get_form(self, form_class): | ||||
|         """ | ||||
|  | @ -579,33 +582,124 @@ class SettingsView(LoginRequiredMixin, FormView): | |||
|         context = super(SettingsView, self).get_context_data(**kwargs) | ||||
|         # Get user | ||||
|         user = self.request.user | ||||
|         # Get user last order | ||||
|         last_hosting_order = HostingOrder.objects.filter( | ||||
|             customer__user=user).last() | ||||
|         # If user has already an hosting order, get the credit card data from | ||||
|         # it | ||||
|         if last_hosting_order: | ||||
|             credit_card_data = last_hosting_order.get_cc_data() | ||||
|             context.update({ | ||||
|                 'credit_card_data': credit_card_data if credit_card_data else None, | ||||
|             }) | ||||
|         stripe_customer = None | ||||
|         if hasattr(user, 'stripecustomer'): | ||||
|             stripe_customer = user.stripecustomer | ||||
|         cards_list = UserCardDetail.get_all_cards_list( | ||||
|             stripe_customer=stripe_customer | ||||
|         ) | ||||
|         context.update({ | ||||
|             'cards_list': cards_list, | ||||
|             'stripe_key': settings.STRIPE_API_PUBLIC_KEY | ||||
|         }) | ||||
| 
 | ||||
|         return context | ||||
| 
 | ||||
|     def post(self, request, *args, **kwargs): | ||||
|         if 'card' in request.POST and request.POST['card'] is not '': | ||||
|             card_id = escape(request.POST['card']) | ||||
|             user_card_detail = UserCardDetail.objects.get(id=card_id) | ||||
|             UserCardDetail.set_default_card( | ||||
|                 stripe_api_cus_id=request.user.stripecustomer.stripe_id, | ||||
|                 stripe_source_id=user_card_detail.card_id | ||||
|             ) | ||||
|             msg = _( | ||||
|                 ("Your {brand} card ending in {last4} set as " | ||||
|                  "default card").format( | ||||
|                     brand=user_card_detail.brand, | ||||
|                     last4=user_card_detail.last4 | ||||
|                 ) | ||||
|             ) | ||||
|             messages.add_message(request, messages.SUCCESS, msg) | ||||
|             return HttpResponseRedirect(reverse_lazy('hosting:settings')) | ||||
|         if 'delete_card' in request.POST: | ||||
|             try: | ||||
|                 card = UserCardDetail.objects.get(pk=self.kwargs.get('pk')) | ||||
|                 if (request.user.has_perm(self.permission_required[0], card) | ||||
|                         and | ||||
|                         request.user | ||||
|                                 .stripecustomer | ||||
|                                 .usercarddetail_set | ||||
|                                 .count() > 1): | ||||
|                     if card.card_id is not None: | ||||
|                         stripe_utils = StripeUtils() | ||||
|                         stripe_utils.dissociate_customer_card( | ||||
|                             request.user.stripecustomer.stripe_id, | ||||
|                             card.card_id | ||||
|                         ) | ||||
|                         if card.preferred: | ||||
|                             UserCardDetail.set_default_card_from_stripe( | ||||
|                                 request.user.stripecustomer.stripe_id | ||||
|                             ) | ||||
|                         card.delete() | ||||
|                         msg = _("Card deassociation successful") | ||||
|                         messages.add_message(request, messages.SUCCESS, msg) | ||||
|                 else: | ||||
|                     msg = _("You are not permitted to do this operation") | ||||
|                     messages.add_message(request, messages.ERROR, msg) | ||||
|             except UserCardDetail.DoesNotExist: | ||||
|                 msg = _("The selected card does not exist") | ||||
|                 messages.add_message(request, messages.ERROR, msg) | ||||
|             return HttpResponseRedirect(reverse_lazy('hosting:settings')) | ||||
|         form = self.get_form() | ||||
|         if form.is_valid(): | ||||
|             billing_address_data = form.cleaned_data | ||||
|             billing_address_data.update({ | ||||
|                 'user': self.request.user.id | ||||
|             }) | ||||
|             billing_address_user_form = UserBillingAddressForm( | ||||
|                 instance=self.request.user.billing_addresses.first(), | ||||
|                 data=billing_address_data) | ||||
|             billing_address_user_form.save() | ||||
|             if 'billing-form' in request.POST: | ||||
|                 billing_address_data = form.cleaned_data | ||||
|                 billing_address_data.update({ | ||||
|                     'user': self.request.user.id | ||||
|                 }) | ||||
|                 billing_address_user_form = UserBillingAddressForm( | ||||
|                     instance=self.request.user.billing_addresses.first(), | ||||
|                     data=billing_address_data) | ||||
|                 billing_address_user_form.save() | ||||
|                 msg = _("Billing address updated successfully") | ||||
|                 messages.add_message(request, messages.SUCCESS, msg) | ||||
|             else: | ||||
|                 token = form.cleaned_data.get('token') | ||||
|                 stripe_utils = StripeUtils() | ||||
|                 card_details = stripe_utils.get_cards_details_from_token( | ||||
|                     token | ||||
|                 ) | ||||
|                 if not card_details.get('response_object'): | ||||
|                     form.add_error("__all__", card_details.get('error')) | ||||
|                     return self.render_to_response(self.get_context_data()) | ||||
|                 stripe_customer = StripeCustomer.get_or_create( | ||||
|                     email=request.user.email, token=token | ||||
|                 ) | ||||
|                 card = card_details['response_object'] | ||||
|                 if UserCardDetail.get_user_card_details(stripe_customer, card): | ||||
|                     msg = _('You seem to have already added this card') | ||||
|                     messages.add_message(request, messages.ERROR, msg) | ||||
|                 else: | ||||
|                     acc_result = stripe_utils.associate_customer_card( | ||||
|                         request.user.stripecustomer.stripe_id, token | ||||
|                     ) | ||||
|                     if acc_result['response_object'] is None: | ||||
|                         msg = _( | ||||
|                             'An error occurred while associating the card.' | ||||
|                             ' Details: {details}'.format( | ||||
|                                 details=acc_result['error'] | ||||
|                             ) | ||||
|                         ) | ||||
|                         messages.add_message(request, messages.ERROR, msg) | ||||
|                         return self.render_to_response(self.get_context_data()) | ||||
|                     preferred = False | ||||
|                     if stripe_customer.usercarddetail_set.count() == 0: | ||||
|                         preferred = True | ||||
|                     UserCardDetail.create( | ||||
|                         stripe_customer=stripe_customer, | ||||
|                         last4=card['last4'], | ||||
|                         brand=card['brand'], | ||||
|                         fingerprint=card['fingerprint'], | ||||
|                         exp_month=card['exp_month'], | ||||
|                         exp_year=card['exp_year'], | ||||
|                         card_id=card['card_id'], | ||||
|                         preferred=preferred | ||||
|                     ) | ||||
|                     msg = _( | ||||
|                         "Successfully associated the card with your account" | ||||
|                     ) | ||||
|                     messages.add_message(request, messages.SUCCESS, msg) | ||||
|             return self.render_to_response(self.get_context_data()) | ||||
|         else: | ||||
|             billing_address_data = form.cleaned_data | ||||
|  | @ -638,24 +732,19 @@ class PaymentVMView(LoginRequiredMixin, FormView): | |||
|         context = super(PaymentVMView, self).get_context_data(**kwargs) | ||||
|         # Get user | ||||
|         user = self.request.user | ||||
| 
 | ||||
|         # Get user last order | ||||
|         last_hosting_order = HostingOrder.objects.filter( | ||||
|             customer__user=user).last() | ||||
| 
 | ||||
|         # If user has already an hosting order, get the credit card data from | ||||
|         # it | ||||
|         if last_hosting_order: | ||||
|             credit_card_data = last_hosting_order.get_cc_data() | ||||
|             context.update({ | ||||
|                 'credit_card_data': credit_card_data if credit_card_data else None, | ||||
|             }) | ||||
| 
 | ||||
|         if hasattr(user, 'stripecustomer'): | ||||
|             stripe_customer = user.stripecustomer | ||||
|         else: | ||||
|             stripe_customer = None | ||||
|         cards_list = UserCardDetail.get_all_cards_list( | ||||
|             stripe_customer=stripe_customer | ||||
|         ) | ||||
|         context.update({ | ||||
|             'stripe_key': settings.STRIPE_API_PUBLIC_KEY, | ||||
|             'vm_pricing': VMPricing.get_vm_pricing_by_name( | ||||
|                 self.request.session.get('specs', {}).get('pricing_name') | ||||
|             ), | ||||
|             'cards_list': cards_list, | ||||
|         }) | ||||
| 
 | ||||
|         return context | ||||
|  | @ -664,6 +753,10 @@ class PaymentVMView(LoginRequiredMixin, FormView): | |||
|     def get(self, request, *args, **kwargs): | ||||
|         if 'next' in request.session: | ||||
|             del request.session['next'] | ||||
|         HostingUtils.clear_items_from_list( | ||||
|             request.session, | ||||
|             ['token', 'card_id', 'customer', 'user'] | ||||
|         ) | ||||
|         return self.render_to_response(self.get_context_data()) | ||||
| 
 | ||||
|     @method_decorator(decorators) | ||||
|  | @ -674,23 +767,51 @@ class PaymentVMView(LoginRequiredMixin, FormView): | |||
|             billing_address_data = form.cleaned_data | ||||
|             token = form.cleaned_data.get('token') | ||||
|             owner = self.request.user | ||||
|             # Get or create stripe customer | ||||
|             customer = StripeCustomer.get_or_create(email=owner.email, | ||||
|                                                     token=token) | ||||
|             if not customer: | ||||
|                 msg = _("Invalid credit card") | ||||
|                 messages.add_message( | ||||
|                     self.request, messages.ERROR, msg, | ||||
|                     extra_tags='make_charge_error') | ||||
|                 return HttpResponseRedirect( | ||||
|                     reverse('hosting:payment') + '#payment_error') | ||||
| 
 | ||||
|             if token is '': | ||||
|                 card_id = form.cleaned_data.get('card') | ||||
|                 customer = owner.stripecustomer | ||||
|                 try: | ||||
|                     user_card_detail = UserCardDetail.objects.get(id=card_id) | ||||
|                     if not request.user.has_perm( | ||||
|                             'view_usercarddetail', user_card_detail | ||||
|                     ): | ||||
|                         raise UserCardDetail.DoesNotExist( | ||||
|                             _("{user} does not have permission to access the " | ||||
|                               "card").format(user=request.user.email) | ||||
|                         ) | ||||
|                 except UserCardDetail.DoesNotExist as e: | ||||
|                     ex = str(e) | ||||
|                     logger.error("Card Id: {card_id}, Exception: {ex}".format( | ||||
|                             card_id=card_id, ex=ex | ||||
|                         ) | ||||
|                     ) | ||||
|                     msg = _("An error occurred. Details: {}".format(ex)) | ||||
|                     messages.add_message( | ||||
|                         self.request, messages.ERROR, msg, | ||||
|                         extra_tags='make_charge_error' | ||||
|                     ) | ||||
|                     return HttpResponseRedirect( | ||||
|                         reverse('hosting:payment') + '#payment_error' | ||||
|                     ) | ||||
|                 request.session['card_id'] = user_card_detail.id | ||||
|             else: | ||||
|                 # Get or create stripe customer | ||||
|                 customer = StripeCustomer.get_or_create( | ||||
|                     email=owner.email, token=token | ||||
|                 ) | ||||
|                 if not customer: | ||||
|                     msg = _("Invalid credit card") | ||||
|                     messages.add_message( | ||||
|                         self.request, messages.ERROR, msg, | ||||
|                         extra_tags='make_charge_error') | ||||
|                     return HttpResponseRedirect( | ||||
|                         reverse('hosting:payment') + '#payment_error') | ||||
|                 request.session['token'] = token | ||||
|             request.session['billing_address_data'] = billing_address_data | ||||
|             request.session['token'] = token | ||||
|             request.session['customer'] = customer.stripe_id | ||||
|             return HttpResponseRedirect("{url}?{query_params}".format( | ||||
|                 url=reverse('hosting:order-confirmation'), | ||||
|                 query_params='page=payment')) | ||||
|                 query_params='page=payment') | ||||
|             ) | ||||
|         else: | ||||
|             return self.form_invalid(form) | ||||
| 
 | ||||
|  | @ -723,12 +844,6 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): | |||
|         ).get_context_data(**kwargs) | ||||
|         obj = self.get_object() | ||||
|         owner = self.request.user | ||||
|         stripe_api_cus_id = self.request.session.get('customer') | ||||
|         stripe_utils = StripeUtils() | ||||
|         card_details = stripe_utils.get_card_details( | ||||
|             stripe_api_cus_id, | ||||
|             self.request.session.get('token') | ||||
|         ) | ||||
| 
 | ||||
|         if self.request.GET.get('page') == 'payment': | ||||
|             context['page_header_text'] = _('Confirm Order') | ||||
|  | @ -784,8 +899,9 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): | |||
|                     context['vm']['price'] = price | ||||
|                     context['vm']['discount'] = discount | ||||
|                     context['vm']['vat_percent'] = vat_percent | ||||
|                     context['vm']['total_price'] = price + \ | ||||
|                         vat - discount['amount'] | ||||
|                     context['vm']['total_price'] = ( | ||||
|                             price + vat - discount['amount'] | ||||
|                     ) | ||||
|                 except WrongIdError: | ||||
|                     messages.error( | ||||
|                         self.request, | ||||
|  | @ -800,17 +916,25 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): | |||
|                         _('In order to create a VM, you need to create/upload ' | ||||
|                           'your SSH KEY first.') | ||||
|                     ) | ||||
|         elif not card_details.get('response_object'): | ||||
|             # new order, failed to get card details | ||||
|             context['failed_payment'] = True | ||||
|             context['card_details'] = card_details | ||||
|         else: | ||||
|             # new order, confirm payment | ||||
|             if 'token' in self.request.session: | ||||
|                 token = self.request.session['token'] | ||||
|                 stripe_utils = StripeUtils() | ||||
|                 card_details = stripe_utils.get_cards_details_from_token( | ||||
|                     token | ||||
|                 ) | ||||
|                 if not card_details.get('response_object'): | ||||
|                     return HttpResponseRedirect(reverse('hosting:payment')) | ||||
|                 card_details_response = card_details['response_object'] | ||||
|                 context['cc_last4'] = card_details_response['last4'] | ||||
|                 context['cc_brand'] = card_details_response['brand'] | ||||
|             else: | ||||
|                 card_id = self.request.session.get('card_id') | ||||
|                 card_detail = UserCardDetail.objects.get(id=card_id) | ||||
|                 context['cc_last4'] = card_detail.last4 | ||||
|                 context['cc_brand'] = card_detail.brand | ||||
|             context['site_url'] = reverse('hosting:create_virtual_machine') | ||||
|             context['cc_last4'] = card_details.get('response_object').get( | ||||
|                 'last4') | ||||
|             context['cc_brand'] = card_details.get('response_object').get( | ||||
|                 'cc_brand') | ||||
|             context['vm'] = self.request.session.get('specs') | ||||
|         return context | ||||
| 
 | ||||
|  | @ -821,7 +945,9 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): | |||
|                 return HttpResponseRedirect( | ||||
|                     reverse('hosting:create_virtual_machine') | ||||
|                 ) | ||||
|             if 'token' not in self.request.session: | ||||
| 
 | ||||
|             if ('token' not in self.request.session and | ||||
|                     'card_id' not in self.request.session): | ||||
|                 return HttpResponseRedirect(reverse('hosting:payment')) | ||||
|         self.object = self.get_object() | ||||
|         context = self.get_context_data(object=self.object) | ||||
|  | @ -840,24 +966,68 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): | |||
|     def post(self, request): | ||||
|         template = request.session.get('template') | ||||
|         specs = request.session.get('specs') | ||||
|         stripe_utils = StripeUtils() | ||||
|         # We assume that if the user is here, his/her StripeCustomer | ||||
|         # object already exists | ||||
|         stripe_customer_id = request.user.stripecustomer.id | ||||
|         billing_address_data = request.session.get('billing_address_data') | ||||
|         vm_template_id = template.get('id', 1) | ||||
|         stripe_api_cus_id = self.request.session.get('customer') | ||||
|         # Make stripe charge to a customer | ||||
|         stripe_utils = StripeUtils() | ||||
|         card_details = stripe_utils.get_card_details(stripe_api_cus_id, | ||||
|                                                      request.session.get( | ||||
|                                                          'token')) | ||||
|         if not card_details.get('response_object'): | ||||
|             msg = card_details.get('error') | ||||
|             messages.add_message(self.request, messages.ERROR, msg, | ||||
|                                  extra_tags='failed_payment') | ||||
|             return HttpResponseRedirect( | ||||
|                 reverse('datacenterlight:payment') + '#payment_error') | ||||
|         card_details_dict = card_details.get('response_object') | ||||
|         stripe_api_cus_id = request.user.stripecustomer.stripe_id | ||||
|         if 'token' in self.request.session: | ||||
|             card_details = stripe_utils.get_cards_details_from_token( | ||||
|                 request.session['token'] | ||||
|             ) | ||||
|             if not card_details.get('response_object'): | ||||
|                 return HttpResponseRedirect(reverse('hosting:payment')) | ||||
|             card_details_response = card_details['response_object'] | ||||
|             card_details_dict = { | ||||
|                 'last4': card_details_response['last4'], | ||||
|                 'brand': card_details_response['brand'], | ||||
|                 'card_id': card_details_response['card_id'] | ||||
|             } | ||||
|             ucd = UserCardDetail.get_user_card_details( | ||||
|                 request.user.stripecustomer, card_details_response | ||||
|             ) | ||||
|             if not ucd: | ||||
|                 acc_result = stripe_utils.associate_customer_card( | ||||
|                     stripe_api_cus_id, request.session['token'], | ||||
|                     set_as_default=True | ||||
|                 ) | ||||
|                 if acc_result['response_object'] is None: | ||||
|                     msg = _( | ||||
|                         'An error occurred while associating the card.' | ||||
|                         ' Details: {details}'.format( | ||||
|                             details=acc_result['error'] | ||||
|                         ) | ||||
|                     ) | ||||
|                     messages.add_message(self.request, messages.ERROR, msg, | ||||
|                                          extra_tags='failed_payment') | ||||
|                     response = { | ||||
|                         'status': False, | ||||
|                         'redirect': "{url}#{section}".format( | ||||
|                             url=reverse('hosting:payment'), | ||||
|                             section='payment_error'), | ||||
|                         'msg_title': str(_('Error.')), | ||||
|                         'msg_body': str( | ||||
|                             _('There was a payment related error.' | ||||
|                               ' On close of this popup, you will be redirected' | ||||
|                               ' back to the payment page.') | ||||
|                         ) | ||||
|                     } | ||||
|                     return JsonResponse(response) | ||||
|         else: | ||||
|             card_id = request.session.get('card_id') | ||||
|             user_card_detail = UserCardDetail.objects.get(id=card_id) | ||||
|             card_details_dict = { | ||||
|                 'last4': user_card_detail.last4, | ||||
|                 'brand': user_card_detail.brand, | ||||
|                 'card_id': user_card_detail.card_id | ||||
|             } | ||||
|             if not user_card_detail.preferred: | ||||
|                 UserCardDetail.set_default_card( | ||||
|                     stripe_api_cus_id=stripe_api_cus_id, | ||||
|                     stripe_source_id=user_card_detail.card_id | ||||
|                 ) | ||||
|         cpu = specs.get('cpu') | ||||
|         memory = specs.get('memory') | ||||
|         disk_size = specs.get('disk_size') | ||||
|  | @ -882,6 +1052,12 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): | |||
|         # Check if the subscription was approved and is active | ||||
|         if (stripe_subscription_obj is None or | ||||
|                 stripe_subscription_obj.status != 'active'): | ||||
|             # At this point, we have created a Stripe API card and | ||||
|             # associated it with the customer; but the transaction failed | ||||
|             # due to some reason. So, we would want to dissociate this card | ||||
|             # here. | ||||
|             # ... | ||||
| 
 | ||||
|             msg = subscription_result.get('error') | ||||
|             messages.add_message(self.request, messages.ERROR, msg, | ||||
|                                  extra_tags='failed_payment') | ||||
|  | @ -894,10 +1070,20 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): | |||
|                 'msg_body': str( | ||||
|                     _('There was a payment related error.' | ||||
|                       ' On close of this popup, you will be redirected back to' | ||||
|                       ' the payment page.')) | ||||
|                       ' the payment page.') | ||||
|                 ) | ||||
|             } | ||||
|             return JsonResponse(response) | ||||
| 
 | ||||
|         if 'token' in request.session: | ||||
|             ucd = UserCardDetail.get_or_create_user_card_detail( | ||||
|                 stripe_customer=self.request.user.stripecustomer, | ||||
|                 card_details=card_details_response | ||||
|             ) | ||||
|             UserCardDetail.save_default_card_local( | ||||
|                 self.request.user.stripecustomer.stripe_id, | ||||
|                 ucd.card_id | ||||
|             ) | ||||
|         user = { | ||||
|             'name': self.request.user.name, | ||||
|             'email': self.request.user.email, | ||||
|  |  | |||
|  | @ -222,21 +222,28 @@ class StripeCustomer(models.Model): | |||
|             # check if user is not in stripe but in database | ||||
|             customer = stripe_utils.check_customer(stripe_customer.stripe_id, | ||||
|                                                    stripe_customer.user, token) | ||||
| 
 | ||||
|             if not customer.sources.data: | ||||
|                 stripe_utils.update_customer_token(customer, token) | ||||
|             if "deleted" in customer and customer["deleted"]: | ||||
|                 raise StripeCustomer.DoesNotExist() | ||||
|             return stripe_customer | ||||
| 
 | ||||
|         except StripeCustomer.DoesNotExist: | ||||
|             user = CustomUser.objects.get(email=email) | ||||
|             stripe_utils = StripeUtils() | ||||
|             stripe_data = stripe_utils.create_customer(token, email, user.name) | ||||
|             if stripe_data.get('response_object'): | ||||
|                 stripe_cus_id = stripe_data.get('response_object').get('id') | ||||
| 
 | ||||
|                 stripe_customer = StripeCustomer.objects. \ | ||||
|                     create(user=user, stripe_id=stripe_cus_id) | ||||
| 
 | ||||
|                 if hasattr(user, 'stripecustomer'): | ||||
|                     # User already had a Stripe account and we are here | ||||
|                     # because the account was deleted in dashboard | ||||
|                     # So, we simply update the stripe_id | ||||
|                     user.stripecustomer.stripe_id = stripe_cus_id | ||||
|                     user.stripecustomer.save() | ||||
|                     stripe_customer = user.stripecustomer | ||||
|                 else: | ||||
|                     # The user never had an associated Stripe account | ||||
|                     # So, create one | ||||
|                     stripe_customer = StripeCustomer.objects.create( | ||||
|                         user=user, stripe_id=stripe_cus_id | ||||
|                     ) | ||||
|                 return stripe_customer | ||||
|             else: | ||||
|                 return None | ||||
|  |  | |||
|  | @ -117,6 +117,7 @@ class EditCreditCardForm(forms.Form): | |||
| 
 | ||||
| class BillingAddressForm(forms.ModelForm): | ||||
|     token = forms.CharField(widget=forms.HiddenInput(), required=False) | ||||
|     card = forms.CharField(widget=forms.HiddenInput(), required=False) | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = BillingAddress | ||||
|  | @ -136,6 +137,31 @@ class BillingAddressFormSignup(BillingAddressForm): | |||
|     email = forms.EmailField(label=_('Email Address')) | ||||
|     field_order = ['name', 'email'] | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = BillingAddress | ||||
|         fields = ['name', 'email', 'cardholder_name', 'street_address', | ||||
|                   'city', 'postal_code', 'country'] | ||||
|         labels = { | ||||
|             'name': 'Name', | ||||
|             'email': _('Email'), | ||||
|             'cardholder_name': _('Cardholder Name'), | ||||
|             'street_address': _('Street Address'), | ||||
|             'city': _('City'), | ||||
|             'postal_code': _('Postal Code'), | ||||
|             'Country': _('Country'), | ||||
|         } | ||||
| 
 | ||||
|     def clean_email(self): | ||||
|         email = self.cleaned_data.get('email') | ||||
|         try: | ||||
|             CustomUser.objects.get(email=email) | ||||
|             raise forms.ValidationError( | ||||
|                 _("The email {} is already registered with us. Please reset " | ||||
|                   "your password and access your account.".format(email)) | ||||
|             ) | ||||
|         except CustomUser.DoesNotExist: | ||||
|             return email | ||||
| 
 | ||||
| 
 | ||||
| class UserBillingAddressForm(forms.ModelForm): | ||||
|     user = forms.ModelChoiceField(queryset=CustomUser.objects.all(), | ||||
|  |  | |||
|  | @ -128,3 +128,23 @@ def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0, | |||
|         'amount': float(pricing.discount_amount), | ||||
|     } | ||||
|     return float(price), float(vat), float(vat_percent), discount | ||||
| 
 | ||||
| 
 | ||||
| class HostingUtils: | ||||
|     @staticmethod | ||||
|     def clear_items_from_list(from_list, items_list): | ||||
|         """ | ||||
|         A utility function to clear items from a given list. | ||||
|         Useful when deleting items in bulk from session. | ||||
|         e.g.: | ||||
|         HostingUtils.clear_items_from_list( | ||||
|             request.session, | ||||
|             ['token', 'billing_address_data', 'card_id',] | ||||
|         ) | ||||
|         :param from_list: | ||||
|         :param items_list: | ||||
|         :return: | ||||
|         """ | ||||
|         for var in items_list: | ||||
|             if var in from_list: | ||||
|                 del from_list[var] | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ msgid "" | |||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2017-10-10 21:35+0530\n" | ||||
| "POT-Creation-Date: 2018-07-05 23:18+0000\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
|  | @ -777,11 +777,18 @@ msgstr "" | |||
| msgid "Email Address" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Street Building" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Email" | ||||
| msgstr "E-Mail" | ||||
| 
 | ||||
| msgid "" | ||||
| "The email {} is already registered with us. Please reset your password and " | ||||
| "access your account." | ||||
| msgstr "" | ||||
| "Diese E-Mail-Adresse existiert bereits. Bitte setze dein Passwort zurück um " | ||||
| "auf dein Konto zuzugreifen." | ||||
| 
 | ||||
| msgid "Street Building" | ||||
| msgstr "Gebäude" | ||||
| 
 | ||||
| msgid "Phone number" | ||||
| msgstr "Telefon" | ||||
|  |  | |||
|  | @ -78,6 +78,22 @@ class StripeUtils(object): | |||
|         customer.source = token | ||||
|         customer.save() | ||||
| 
 | ||||
|     @handleStripeError | ||||
|     def associate_customer_card(self, stripe_customer_id, token, | ||||
|                                 set_as_default=False): | ||||
|         customer = stripe.Customer.retrieve(stripe_customer_id) | ||||
|         card = customer.sources.create(source=token) | ||||
|         if set_as_default: | ||||
|             customer.default_source = card.id | ||||
|             customer.save() | ||||
|         return True | ||||
| 
 | ||||
|     @handleStripeError | ||||
|     def dissociate_customer_card(self, stripe_customer_id, card_id): | ||||
|         customer = stripe.Customer.retrieve(stripe_customer_id) | ||||
|         card = customer.sources.retrieve(card_id) | ||||
|         card.delete() | ||||
| 
 | ||||
|     @handleStripeError | ||||
|     def update_customer_card(self, customer_id, token): | ||||
|         customer = stripe.Customer.retrieve(customer_id) | ||||
|  | @ -93,32 +109,47 @@ class StripeUtils(object): | |||
|         return new_card_data | ||||
| 
 | ||||
|     @handleStripeError | ||||
|     def get_card_details(self, customer_id, token): | ||||
|     def get_card_details(self, customer_id): | ||||
|         customer = stripe.Customer.retrieve(customer_id) | ||||
|         credit_card_raw_data = customer.sources.data.pop() | ||||
|         card_details = { | ||||
|             'last4': credit_card_raw_data.last4, | ||||
|             'brand': credit_card_raw_data.brand | ||||
|             'brand': credit_card_raw_data.brand, | ||||
|             'exp_month': credit_card_raw_data.exp_month, | ||||
|             'exp_year': credit_card_raw_data.exp_year, | ||||
|             'fingerprint': credit_card_raw_data.fingerprint, | ||||
|             'card_id': credit_card_raw_data.id | ||||
|         } | ||||
|         return card_details | ||||
| 
 | ||||
|     def check_customer(self, id, user, token): | ||||
|         customers = self.stripe.Customer.all() | ||||
|         if not customers.get('data'): | ||||
|     @handleStripeError | ||||
|     def get_cards_details_from_token(self, token): | ||||
|         stripe_token = stripe.Token.retrieve(token) | ||||
|         card_details = { | ||||
|             'last4': stripe_token.card.last4, | ||||
|             'brand': stripe_token.card.brand, | ||||
|             'exp_month': stripe_token.card.exp_month, | ||||
|             'exp_year': stripe_token.card.exp_year, | ||||
|             'fingerprint': stripe_token.card.fingerprint, | ||||
|             'card_id': stripe_token.card.id | ||||
|         } | ||||
|         return card_details | ||||
| 
 | ||||
|     def check_customer(self, stripe_cus_api_id, user, token): | ||||
|         try: | ||||
|             customer = stripe.Customer.retrieve(stripe_cus_api_id) | ||||
|         except stripe.InvalidRequestError: | ||||
|             customer = self.create_customer(token, user.email, user.name) | ||||
|         else: | ||||
|             try: | ||||
|                 customer = stripe.Customer.retrieve(id) | ||||
|             except stripe.InvalidRequestError: | ||||
|                 customer = self.create_customer(token, user.email, user.name) | ||||
|                 user.stripecustomer.stripe_id = customer.get( | ||||
|                     'response_object').get('id') | ||||
|                 user.stripecustomer.save() | ||||
|             user.stripecustomer.stripe_id = customer.get( | ||||
|                 'response_object').get('id') | ||||
|             user.stripecustomer.save() | ||||
|         if type(customer) is dict: | ||||
|             customer = customer['response_object'] | ||||
|         return customer | ||||
| 
 | ||||
|     @handleStripeError | ||||
|     def get_customer(self, id): | ||||
|         customer = stripe.Customer.retrieve(id) | ||||
|     def get_customer(self, stripe_api_cus_id): | ||||
|         customer = stripe.Customer.retrieve(stripe_api_cus_id) | ||||
|         # data = customer.get('response_object') | ||||
|         return customer | ||||
| 
 | ||||
|  | @ -296,3 +327,15 @@ class StripeUtils(object): | |||
|             cpu=cpu, | ||||
|             memory=memory, | ||||
|             disk_size=disk_size) | ||||
| 
 | ||||
|     @handleStripeError | ||||
|     def set_subscription_meta_data(self, subscription_id, meta_data): | ||||
|         """ | ||||
|         Adds VM metadata to a subscription | ||||
|         :param subscription_id: Stripe identifier for the subscription | ||||
|         :param meta_data: A dict of meta data to be added | ||||
|         :return: | ||||
|         """ | ||||
|         subscription = stripe.Subscription.retrieve(subscription_id) | ||||
|         subscription.metadata = meta_data | ||||
|         subscription.save() | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue