Stripe checkout #104411
@ -144,7 +144,6 @@ class SubscriptionEmailPreviewAdmin(looper.admin.mixins.NoAddDeleteMixin, EmailA
|
|||||||
'payment_failed',
|
'payment_failed',
|
||||||
'managed_notification',
|
'managed_notification',
|
||||||
'subscription_expired',
|
'subscription_expired',
|
||||||
'paypal_subscription_cancelled',
|
|
||||||
):
|
):
|
||||||
emails.append(self.get_object(request, object_id=mail_name))
|
emails.append(self.get_object(request, object_id=mail_name))
|
||||||
return emails
|
return emails
|
||||||
|
24
poetry.lock
generated
24
poetry.lock
generated
@ -2221,16 +2221,26 @@ diagrams = ["jinja2", "railroad-diagrams"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pypdf2"
|
name = "pypdf2"
|
||||||
version = "1.28.6"
|
version = "3.0.1"
|
||||||
description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files"
|
description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7"
|
python-versions = ">=3.6"
|
||||||
files = [
|
files = [
|
||||||
{file = "PyPDF2-1.28.6-py3-none-any.whl", hash = "sha256:d7118f0187153257b1f906dcfcd8236608f4987b6a9999b7c5ad49114706a1ad"},
|
{file = "PyPDF2-3.0.1.tar.gz", hash = "sha256:a74408f69ba6271f71b9352ef4ed03dc53a31aa404d29b5d31f53bfecfee1440"},
|
||||||
{file = "PyPDF2-1.28.6.tar.gz", hash = "sha256:c0840835d18357b077da05bdad1423f5e29419f318135b6a6542895930dc4905"},
|
{file = "pypdf2-3.0.1-py3-none-any.whl", hash = "sha256:d16e4205cfee272fbdc0568b68d82be796540b1537508cef59388f839c191928"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
typing_extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
crypto = ["PyCryptodome"]
|
||||||
|
dev = ["black", "flit", "pip-tools", "pre-commit (<2.18.0)", "pytest-cov", "wheel"]
|
||||||
|
docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"]
|
||||||
|
full = ["Pillow", "PyCryptodome"]
|
||||||
|
image = ["Pillow"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-bidi"
|
name = "python-bidi"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@ -2344,7 +2354,7 @@ resolved_reference = "419abd659ae5a4a6cb6ea9b54aa4bde17aefeb5b"
|
|||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0"
|
version = "6.0"
|
||||||
description = "YAML parser and emitter for Python"
|
description = "YAML parser and emitter for Python"
|
||||||
category = "main"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
files = [
|
files = [
|
||||||
@ -2518,7 +2528,7 @@ requests = ">=2.0.1,<3.0.0"
|
|||||||
name = "responses"
|
name = "responses"
|
||||||
version = "0.24.1"
|
version = "0.24.1"
|
||||||
description = "A utility library for mocking out the `requests` Python library."
|
description = "A utility library for mocking out the `requests` Python library."
|
||||||
category = "main"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
@ -2947,4 +2957,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.8"
|
python-versions = "^3.8"
|
||||||
content-hash = "04753b00e94c929ee66201ce0b11c3119d6ef150ba8e0ca2a6f6c5cf82f49917"
|
content-hash = "386b423472b0adf961feb4bbb54dcb2d1170ca5600c843f7bb92d519235193c1"
|
||||||
|
@ -22,7 +22,6 @@ markupsafe = "^1.1.1"
|
|||||||
meilisearch = "^0.18.0"
|
meilisearch = "^0.18.0"
|
||||||
django-taggit = "^1.3.0"
|
django-taggit = "^1.3.0"
|
||||||
boto3 = "1.18.56"
|
boto3 = "1.18.56"
|
||||||
responses = "^0.24.0"
|
|
||||||
attrs = "^19.3.0"
|
attrs = "^19.3.0"
|
||||||
Flask = "1.0.3"
|
Flask = "1.0.3"
|
||||||
bleach = "^3.2.1"
|
bleach = "^3.2.1"
|
||||||
@ -64,7 +63,8 @@ freezegun = "^1.0.0"
|
|||||||
django-sslserver = "^0.22"
|
django-sslserver = "^0.22"
|
||||||
djhtml = "1.4.0"
|
djhtml = "1.4.0"
|
||||||
phpserialize = "^1.3"
|
phpserialize = "^1.3"
|
||||||
PyPDF2 = "^1.26.0"
|
PyPDF2 = "^3.0.1"
|
||||||
|
responses = "^0.24.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
tblib = "^3.0.0"
|
tblib = "^3.0.0"
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
{% extends "emails/email_base.html" %}
|
|
||||||
{% load subscriptions %}
|
|
||||||
|
|
||||||
{% block header_logo %}
|
|
||||||
{# have a title instead of the logo with the remote image #}
|
|
||||||
<div style="text-align: center; font-weight: bold;">{{ subject }}</div>
|
|
||||||
{% endblock header_logo %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<p>Dear {% firstof user.customer.billing_address.full_name user.full_name user.email %},</p>
|
|
||||||
<p>
|
|
||||||
As you may have heard, Blender Studio's subscription system recently got a new shiny update,
|
|
||||||
more on that in <a href="https://studio.blender.org/blog/subscription-system-update-2021/">the blog post</a>.
|
|
||||||
</p>
|
|
||||||
<p>Due to this update, the old PayPal Subscriptions payment method is no longer supported.</p>
|
|
||||||
<p>
|
|
||||||
This means that PayPal's billing agreement that was used to pay for subscription #{{ subscription.pk }} has been cancelled,
|
|
||||||
and <b>no more charges will be made until subscription's payment method is updated</b>.
|
|
||||||
</p>
|
|
||||||
<p>For this reason, we ask you to update the payment method using the following link:</p>
|
|
||||||
<p style="text-align: center">
|
|
||||||
<a style="
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: #009eff;
|
|
||||||
border-bottom-color: #009eff;
|
|
||||||
border-bottom-left-radius: 8px;
|
|
||||||
border-bottom-right-radius: 8px;
|
|
||||||
border-bottom-style: solid;
|
|
||||||
border-bottom-width: 0px;
|
|
||||||
border-img-source: none;
|
|
||||||
border-left-color: #009eff;
|
|
||||||
border-left-style: solid;
|
|
||||||
border-left-width: 0px;
|
|
||||||
border-right-color: #009eff;
|
|
||||||
border-right-style: solid;
|
|
||||||
border-right-width: 0px;
|
|
||||||
border-top-color: #009eff;
|
|
||||||
border-top-left-radius: 8px;
|
|
||||||
border-top-right-radius: 8px;
|
|
||||||
border-top-style: solid;
|
|
||||||
border-top-width: 0px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #ffffff;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 16px;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
margin-left: 0px;
|
|
||||||
margin-right: 0px;
|
|
||||||
margin-top: 0px;
|
|
||||||
overflow: visible;
|
|
||||||
overflow-x: visible;
|
|
||||||
overflow-y: visible;
|
|
||||||
padding-bottom: 14px;
|
|
||||||
padding-left: 20px;
|
|
||||||
padding-right: 20px;
|
|
||||||
padding-top: 14px;
|
|
||||||
text-align: center;
|
|
||||||
text-transform: none;
|
|
||||||
vertical-align: middle;
|
|
||||||
width: auto;" href="{{ site_url }}{% url "subscriptions:payment-method-change" subscription_id=subscription.pk %}">Update payment method</a>
|
|
||||||
</p>
|
|
||||||
<p>There you can choose to use the same PayPal account, however going through the process of updating the payment method is still necessary.</p>
|
|
||||||
|
|
||||||
<h3>Subscription information:</h3>
|
|
||||||
<hr/>
|
|
||||||
<table style="
|
|
||||||
color: #C7C7C7;
|
|
||||||
font-family: 'Lucida Grande', 'Helvetica Neue', 'Helvetica', 'Arial', 'Verdana', sans-serif;
|
|
||||||
">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Subscription #:</td>
|
|
||||||
<td style="padding-left: 1em;">{{ subscription.id }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Renewal type:</td>
|
|
||||||
<td style="padding-left: 1em;">{{ subscription.collection_method }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Renewal period:</td>
|
|
||||||
<td style="padding-left: 1em;">{{ subscription|renewal_period }}</td>
|
|
||||||
</tr>
|
|
||||||
{% with taxable=subscription.taxable %}
|
|
||||||
<tr>
|
|
||||||
<td>Recurring total:</td>
|
|
||||||
<td style="padding-left: 1em;">{{ taxable.price.with_currency_symbol }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endwith %}
|
|
||||||
<tr>
|
|
||||||
<td>Payment method:</td>
|
|
||||||
<td style="padding-left: 1em;">PayPal Billing Agreement {{ billing_agreement_id }} <b>(cancelled)</b></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Last payment:</td>
|
|
||||||
<td style="padding-left: 1em;">{{ billing_agreement_last_payment_date|date }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Payment due:</td>
|
|
||||||
<td style="padding-left: 1em;">{{ subscription.next_payment|date }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<p>In case you choose not to update the subscription, it will be cancelled a few weeks after payment due date.</p>
|
|
||||||
|
|
||||||
<p>We hope for your understanding and thank you for your support! 🧡</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
--<br />
|
|
||||||
Best regards,<br />
|
|
||||||
|
|
||||||
Blender Studio Team
|
|
||||||
</p>
|
|
||||||
{% endblock body %}
|
|
@ -1,33 +0,0 @@
|
|||||||
{% load subscriptions %}Dear {% firstof user.customer.billing_address.full_name user.full_name user.email %},
|
|
||||||
|
|
||||||
As you may have heard, Blender Studio's subscription system recently got a new shiny update,
|
|
||||||
more on that in the blog post https://studio.blender.org/blog/subscription-system-update-2021/ .
|
|
||||||
|
|
||||||
Due to this update, the old PayPal Subscriptions payment method is no longer supported.
|
|
||||||
This means that PayPal's billing agreement that was used to pay for subscription #{{ subscription.pk }} has been cancelled,
|
|
||||||
and no more charges will be made until subscription's payment method is updated.
|
|
||||||
|
|
||||||
For this reason, we ask you to update the payment method using the following link:
|
|
||||||
|
|
||||||
{{ site_url }}{% url "subscriptions:payment-method-change" subscription_id=subscription.pk %}
|
|
||||||
|
|
||||||
There you can choose to use the same PayPal account, however going through the process of updating the payment method is still necessary.
|
|
||||||
|
|
||||||
Subscription information:
|
|
||||||
-------------------------
|
|
||||||
Subscription #: {{ subscription.id }}
|
|
||||||
Renewal type: {{ subscription.collection_method }}
|
|
||||||
Renewal period: {{ subscription|renewal_period }}{% with taxable=subscription.taxable %}
|
|
||||||
Recurring total: {{ taxable.price.with_currency_symbol }}{% endwith %}
|
|
||||||
Payment method: PayPal Billing Agreement {{ billing_agreement_id }} (cancelled)
|
|
||||||
Last payment: {{ billing_agreement_last_payment_date|date }}
|
|
||||||
Payment due: {{ subscription.next_payment|date }}
|
|
||||||
|
|
||||||
In case you choose not to update the subscription, it will be cancelled a few weeks after payment due date.
|
|
||||||
|
|
||||||
We hope for your understanding and thank you for your continued support! 🧡
|
|
||||||
|
|
||||||
--
|
|
||||||
Best regards,
|
|
||||||
|
|
||||||
Blender Studio Team
|
|
@ -1 +0,0 @@
|
|||||||
Blender Studio Subscription: Action Required
|
|
@ -2,7 +2,7 @@ from decimal import Decimal
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from unittest.mock import patch, Mock
|
from unittest.mock import patch, Mock
|
||||||
|
|
||||||
from PyPDF2 import PdfFileReader
|
from PyPDF2 import PdfReader
|
||||||
from django.test.testcases import TestCase
|
from django.test.testcases import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
@ -23,9 +23,9 @@ Billing Address
|
|||||||
E-mail: {order.email}{expected_vatin}
|
E-mail: {order.email}{expected_vatin}
|
||||||
{expected_external_reference}Invoice Number:
|
{expected_external_reference}Invoice Number:
|
||||||
{order.number}
|
{order.number}
|
||||||
Invoice Date:
|
Invoice Date:
|
||||||
{expected_date}
|
{expected_date}
|
||||||
Payment method:
|
Payment method:
|
||||||
Product
|
Product
|
||||||
Quantity
|
Quantity
|
||||||
Price
|
Price
|
||||||
@ -33,9 +33,13 @@ Blender Studio Subscription
|
|||||||
{expected_team_prefix}Subscription #: {order.subscription_id}
|
{expected_team_prefix}Subscription #: {order.subscription_id}
|
||||||
Renewal type: Automatic
|
Renewal type: Automatic
|
||||||
Renewal period: Monthly{expected_team_seats}
|
Renewal period: Monthly{expected_team_seats}
|
||||||
1 {expected_currency_symbol} {expected_price}{expected_additional_note}
|
1
|
||||||
Subtotal {expected_currency_symbol} {expected_subtotal}{expected_vat}
|
{expected_currency_symbol} {expected_price}{expected_additional_note}
|
||||||
Total {expected_currency_symbol} {expected_total}'''
|
Subtotal
|
||||||
|
{expected_currency_symbol} {expected_subtotal}{expected_vat}
|
||||||
|
Total
|
||||||
|
{expected_currency_symbol} {expected_total}
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
def _fake_ap_date_format(date) -> str:
|
def _fake_ap_date_format(date) -> str:
|
||||||
@ -71,19 +75,10 @@ class TestReceiptPDFView(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _extract_text_from_pdf(self, response):
|
def _extract_text_from_pdf(self, response):
|
||||||
pdf = PdfFileReader(BytesIO(response.content))
|
pdf = PdfReader(BytesIO(response.content))
|
||||||
self.assertEqual(1, pdf.getNumPages())
|
self.assertEqual(1, len(pdf.pages))
|
||||||
pdf_page = pdf.getPage(0)
|
pdf_page = pdf.pages[0]
|
||||||
return (
|
return pdf_page.extract_text()
|
||||||
# FIXME: PyPDF2 extracts text with a lot of additional newlines
|
|
||||||
pdf_page.extract_text()
|
|
||||||
.replace('\n\n', '\n')
|
|
||||||
.replace('\n ', ' ')
|
|
||||||
.replace('\n:', ':')
|
|
||||||
.replace('\n• \n', ' • ')
|
|
||||||
.replace('\n$ \n', ' $ ')
|
|
||||||
.strip()
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_pdf_unpaid_order_not_found(self):
|
def test_get_pdf_unpaid_order_not_found(self):
|
||||||
unpaid_order = OrderFactory(
|
unpaid_order = OrderFactory(
|
||||||
@ -166,13 +161,13 @@ class TestReceiptPDFView(TestCase):
|
|||||||
expected_vatin='',
|
expected_vatin='',
|
||||||
expected_external_reference='',
|
expected_external_reference='',
|
||||||
expected_date=_fake_ap_date_format(order.paid_at),
|
expected_date=_fake_ap_date_format(order.paid_at),
|
||||||
expected_currency_symbol='•',
|
expected_currency_symbol='€',
|
||||||
expected_team_prefix='',
|
expected_team_prefix='',
|
||||||
expected_team_seats='',
|
expected_team_seats='',
|
||||||
expected_price='12.52',
|
expected_price='12.52',
|
||||||
expected_additional_note='',
|
expected_additional_note='',
|
||||||
expected_subtotal='12.52 (ex. VAT)',
|
expected_subtotal='12.52 (ex. VAT)',
|
||||||
expected_vat='\nVAT (19%) • 2.38',
|
expected_vat='\nVAT (19%)\n€ 2.38',
|
||||||
expected_total='14.90',
|
expected_total='14.90',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -212,8 +207,7 @@ class TestReceiptPDFView(TestCase):
|
|||||||
expected_vatin='\nVATIN: DE123456789',
|
expected_vatin='\nVATIN: DE123456789',
|
||||||
expected_external_reference='',
|
expected_external_reference='',
|
||||||
expected_date=_fake_ap_date_format(order.paid_at),
|
expected_date=_fake_ap_date_format(order.paid_at),
|
||||||
# FIXME(anna): PyPDF2's extract_text() doesn't extract EUR sign for some reason
|
expected_currency_symbol='€',
|
||||||
expected_currency_symbol='•',
|
|
||||||
expected_team_prefix='',
|
expected_team_prefix='',
|
||||||
expected_team_seats='',
|
expected_team_seats='',
|
||||||
expected_price='12.52',
|
expected_price='12.52',
|
||||||
@ -263,14 +257,13 @@ class TestReceiptPDFView(TestCase):
|
|||||||
expected_vatin='\nVATIN: NL123456789',
|
expected_vatin='\nVATIN: NL123456789',
|
||||||
expected_external_reference='',
|
expected_external_reference='',
|
||||||
expected_date=_fake_ap_date_format(order.paid_at),
|
expected_date=_fake_ap_date_format(order.paid_at),
|
||||||
# FIXME(anna): PyPDF2's extract_text() doesn't extract EUR sign for some reason
|
expected_currency_symbol='€',
|
||||||
expected_currency_symbol='•',
|
|
||||||
expected_team_prefix='',
|
expected_team_prefix='',
|
||||||
expected_team_seats='',
|
expected_team_seats='',
|
||||||
expected_price='12.31',
|
expected_price='12.31',
|
||||||
expected_subtotal='12.31 (ex. VAT)',
|
expected_subtotal='12.31 (ex. VAT)',
|
||||||
expected_additional_note='',
|
expected_additional_note='',
|
||||||
expected_vat='\nVAT (21%) • 2.59',
|
expected_vat='\nVAT (21%)\n€ 2.59',
|
||||||
expected_total='14.90',
|
expected_total='14.90',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user