├── testsite ├── models.py ├── __init__.py ├── views │ ├── __init__.py │ └── organization.py ├── management │ ├── __init__.py │ └── commands │ │ └── __init__.py ├── templatetags │ ├── __init__.py │ └── testsite_tags.py ├── templates │ ├── accounts │ │ ├── base.html │ │ ├── profile.html │ │ └── register.html │ ├── paginator.html │ ├── registration │ │ └── login.html │ ├── organization_list_index.html │ ├── index.html │ └── base.html ├── static │ └── img │ │ └── stripe-connect.png ├── etc │ ├── credentials │ └── gunicorn.conf ├── fixtures │ ├── 50-visit-card2.json │ ├── DESCRIPTION │ ├── initial_data.json │ └── 120-subscriptions.json ├── package.json ├── requirements-legacy.txt ├── requirements.txt ├── context_processors.py ├── signals.py ├── wsgi.py └── jinja2.py ├── saas ├── api │ ├── __init__.py │ └── serializers_overrides.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── delete_organization.py │ │ ├── delete_processor_customers.py │ │ └── compile_stats.py ├── metrics │ ├── __init__.py │ └── subscriptions.py ├── migrations │ ├── __init__.py │ ├── 0010_0_4_0.py │ ├── 0016_v0_15_4.py │ ├── 0002_auto_20161126_2248.py │ ├── 0005_role_extra.py │ ├── 0017_v1_0_0.py │ ├── 0003_balanceline_is_positive.py │ ├── 0014_v0_9_3.py │ ├── 0012_0_8_3.py │ ├── 0004_auto_20161221_1944.py │ ├── 0011_0_7_0.py │ ├── 0009_0_3_5.py │ └── 0013_0_9_0.py ├── templatetags │ └── __init__.py ├── backends │ ├── stripe_processor │ │ ├── urls │ │ │ ├── __init__.py │ │ │ ├── api.py │ │ │ └── views.py │ │ └── __init__.py │ └── urls │ │ ├── api.py │ │ ├── views.py │ │ └── __init__.py ├── templates │ ├── saas │ │ ├── metrics │ │ │ ├── plans.html │ │ │ ├── revenue.html │ │ │ ├── lifetimevalue.html │ │ │ ├── coupons.html │ │ │ └── base.html │ │ ├── legal │ │ │ ├── agreement.html │ │ │ ├── sign.html │ │ │ └── index.html │ │ ├── billing │ │ │ ├── cart-periods.html │ │ │ ├── charges.html │ │ │ ├── cart-seats.html │ │ │ ├── withdraw.html │ │ │ ├── transactions.html │ │ │ ├── card.html │ │ │ ├── balance.html │ │ │ ├── transfers.html │ │ │ ├── import.html │ │ │ ├── index.html │ │ │ ├── bank.html │ │ │ └── cart.html │ │ ├── profile │ │ │ ├── new.html │ │ │ ├── plans │ │ │ │ ├── index.html │ │ │ │ └── plan.html │ │ │ ├── index.html │ │ │ └── roles │ │ │ │ └── index.html │ │ ├── _filter.html │ │ ├── redeem.html │ │ ├── organization_redirects.html │ │ ├── _razorpay_checkout.html │ │ ├── _paginator.html │ │ ├── users │ │ │ └── roles │ │ │ │ └── accept.html │ │ ├── _user_card.html │ │ ├── pricing.html │ │ ├── _charges.html │ │ ├── _transactions.html │ │ ├── _bank_use.html │ │ ├── _sidebar.html │ │ ├── agreements │ │ │ └── security.md │ │ └── base.html │ └── notification │ │ ├── order_executed.eml │ │ ├── renewal_charge_failed.eml │ │ ├── user_relation_added.eml │ │ ├── user_relation_requested.eml │ │ ├── one_time_coupon_generated.eml │ │ ├── role_grant_accepted.eml │ │ └── charge_receipt.eml ├── __init__.py ├── views │ ├── __init__.py │ └── extra.py ├── urls │ ├── __init__.py │ ├── views │ │ ├── subscriber │ │ │ ├── billing │ │ │ │ ├── __init__.py │ │ │ │ ├── info.py │ │ │ │ └── payment.py │ │ │ ├── __init__.py │ │ │ └── profile.py │ │ ├── provider │ │ │ ├── __init__.py │ │ │ └── billing.py │ │ ├── users.py │ │ ├── __init__.py │ │ ├── noauth.py │ │ ├── request.py │ │ ├── tailredirects.py │ │ └── broker.py │ └── api │ │ ├── legal.py │ │ ├── tailbroker.py │ │ ├── subscriber │ │ ├── __init__.py │ │ ├── charges.py │ │ ├── roles.py │ │ ├── billing.py │ │ └── profile.py │ │ ├── __init__.py │ │ ├── provider │ │ ├── __init__.py │ │ ├── plans.py │ │ ├── charges.py │ │ ├── roles.py │ │ └── billing.py │ │ ├── cart.py │ │ ├── search.py │ │ ├── users.py │ │ └── headbroker.py ├── admin.py └── docs.py ├── MANIFEST.in ├── setup.cfg ├── .gitignore ├── docs ├── periodic-tasks.rst ├── backends.rst ├── models.rst ├── index.rst ├── extensions.rst ├── orders.rst └── security.rst ├── manage.py ├── .readthedocs.yaml ├── setup.py ├── LICENSE.txt └── pyproject.toml /testsite/models.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /saas/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testsite/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /saas/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /saas/metrics/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /saas/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testsite/views/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /saas/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testsite/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testsite/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /saas/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testsite/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /saas/backends/stripe_processor/urls/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md requirements.txt 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /testsite/templates/accounts/base.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base.html" %} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | .DS_Store 4 | credentials 5 | db.sqlite 6 | .idea/ -------------------------------------------------------------------------------- /saas/backends/stripe_processor/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import StripeBackend 2 | -------------------------------------------------------------------------------- /saas/templates/saas/metrics/plans.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/metrics/base.html" %} 2 | -------------------------------------------------------------------------------- /docs/periodic-tasks.rst: -------------------------------------------------------------------------------- 1 | Periodic tasks 2 | ============== 3 | 4 | .. automodule:: saas.management.commands.renewals 5 | -------------------------------------------------------------------------------- /testsite/static/img/stripe-connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MuhamedHabib/djaodjin-saas/master/testsite/static/img/stripe-connect.png -------------------------------------------------------------------------------- /testsite/templates/accounts/profile.html: -------------------------------------------------------------------------------- 1 | {% extends "accounts/base.html" %} 2 | 3 | {% block saas_content %} 4 |
5 | Profile page for {{user}}. 6 |
7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /saas/templates/saas/legal/agreement.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | {{page|safe}}{# OK to use a safe here. We load a committed source .md file #} 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /testsite/etc/credentials: -------------------------------------------------------------------------------- 1 | # SECURITY WARNING: keep the secret key used in production secret! 2 | SECRET_KEY = "%(SECRET_KEY)s" 3 | 4 | # Authentication with payment provider 5 | STRIPE_CLIENT_ID = "" 6 | STRIPE_PUB_KEY = "" 7 | STRIPE_PRIV_KEY = "" 8 | STRIPE_ENDPOINT_SECRET = "" 9 | -------------------------------------------------------------------------------- /testsite/fixtures/50-visit-card2.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "fields":{ 3 | "created_at":"2023-01-01T00:00:00+00:00", 4 | "user": 3, 5 | "plan": 1, 6 | "option": 1, 7 | "recorded": false 8 | }, 9 | "model": "saas.CartItem", "pk": 50 10 | }] 11 | -------------------------------------------------------------------------------- /saas/templates/saas/billing/cart-periods.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/billing/cart.html" %} 2 | 3 | {% block order_title %}Place order ...{% endblock %} 4 | 5 | {% block order_card %} 6 |5 | Please sign: 6 |
7 | {{page|safe}}{# OK to use a safe here. We load a committed source .md file #} 8 | 9 | 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /saas/migrations/0016_v0_15_4.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.16 on 2023-01-04 18:19 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('saas', '0015_0_11_1'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='roledescription', 15 | name='slug', 16 | field=models.SlugField(help_text='Unique identifier shown in the URL bar', unique=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /saas/migrations/0002_auto_20161126_2248.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-11-27 04:48 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('saas', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='roledescription', 17 | old_name='name', 18 | new_name='title', 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /saas/templates/notification/user_relation_added.eml: -------------------------------------------------------------------------------- 1 | {% block subject %} 2 | You were added as a {{role}} to {{organization.printable_name}} 3 | {% endblock %} 4 | 5 | {% block html_content %} 6 | 7 | 8 | 9 | 10 |
12 | {{reason}}
13 |
14 | 15 | You were added as a {{role}} to {{organization.printable_name}} 16 | and can access the Dashboard now. 17 |
18 | 19 | 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /saas/migrations/0005_role_extra.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2017-01-21 05:16 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('saas', '0004_auto_20161221_1944'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='role', 17 | name='extra', 18 | field=models.TextField(null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /saas/migrations/0017_v1_0_0.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.18 on 2023-06-03 09:43 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('saas', '0016_v0_15_4'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='plan', 15 | name='is_personal', 16 | field=models.BooleanField(default=False, help_text='True when the plan is meant for personal profiles first and foremost'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /saas/templates/notification/user_relation_requested.eml: -------------------------------------------------------------------------------- 1 | {% block subject %} 2 | {{user.get_full_name}} requested an additional role on {{organization.printable_name}} 3 | {% endblock %} 4 | 5 | {% block html_content %} 6 | {{user.get_full_name}} requested an additional role on {{organization.printable_name}} 7 | {% if reason %} 8 |9 | {{reason|md}} 10 |
11 | {% endif %} 12 | You can grant this request to {{user.get_full_name}} by following this link: 13 | {{site.as_absolute_uri}}{% url 'saas_role_list' organization.slug %} 14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /saas/migrations/0003_balanceline_is_positive.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2016-12-08 16:53 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('saas', '0002_auto_20161126_2248'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='balanceline', 17 | name='is_positive', 18 | field=models.BooleanField(default=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /saas/migrations/0014_v0_9_3.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2021-02-14 12:18 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import django_countries.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('saas', '0013_0_9_0'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='organization', 17 | name='nb_renewal_attempts', 18 | field=models.PositiveIntegerField(default=0, help_text='Number of successive failed charges'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /testsite/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /saas/templates/saas/_razorpay_checkout.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.11" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/conf.py 17 | 18 | # We recommend specifying your dependencies to enable reproducible builds: 19 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 20 | python: 21 | install: 22 | - requirements: testsite/requirements.txt 23 | -------------------------------------------------------------------------------- /saas/templates/saas/profile/plans/index.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base_dashboard.html" %} 2 | 3 | {% block saas_content %} 4 || Name | 11 |
|---|
| {{entry.printable_name}} | 17 |
12 | Hello {{full_name}}, 13 |
14 |15 | I generated a one-time coupon code for you to access {{coupon.plan.title}}. 16 | 17 | Please register to {{site}}. 18 | When ask for payment information, enter the following one-time coupon code: 19 |
20 |21 | {{coupon.code}} 22 |
23 |
24 | Thank you,
25 | {{user.full_name}}
26 |
8 | Based on your e-mail address we have granted you a {{role_descr}} role on {{organization}}. 9 |
10 |11 | {% if contacts %} 12 | If you need extra permissions, please contact one of the profile managers for {{organization}}: {{contacts}}. 13 | {% else %} 14 | If you have any questions, please contact us. 15 | {% endif %} 16 |
17 |22 | We found a matching profile for your e-mail domain! Please verify your e-mail address before going further. To do so click on the link in the e-mail we just sent you. 23 |
24 | {% endif %} 25 |15 | {{reason|md}} 16 |
17 | {% endif %} 18 |19 | You can review who has a {{role}} to 20 | {{organization.printable_name}} account 21 | now by clicking on the previous link or copy/pasting the following link 22 | in your browser: 23 | {{back_url}} 24 |
25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /saas/templates/notification/charge_receipt.eml: -------------------------------------------------------------------------------- 1 | {% block subject %} 2 | Charge Receipt from {{site.name}} 3 | {% endblock %} 4 | 5 | {% block plain_content %} 6 | This is a Charge Receipt for {{charge.processor_key}} 7 | {% endblock %} 8 | 9 | {% block html_content %} 10 | 11 | 12 | 13 | 14 |25 | Your credit card has been charged for an amount 26 | of {{charge.price|humanize_money}}. For your records, 27 | the charge reference is {{charge.processor_key}}. 28 |
29 |30 | Thank you for your continuous business. Any questions 31 | or comments, email us at {{organization.email}}. 32 |
33 || Name | 14 |Contract Value | 15 |Cash Payments | 16 |Deferred Revenue | 17 |Created at | 18 |Ends at | 19 |
|---|---|---|---|---|---|
| 23 | [[item.printable_name]] 24 | | 25 |[[item.contract_value]] | 26 |[[item.cash_payments]] | 27 |[[item.deferred_revenue]] | 28 |[[item.created_at]] | 29 |[[item.ends_at]] | 30 |
| Date | 11 |Amount | 12 |Origin Profile | 13 |Origin Account | 14 |Destination Profile | 15 |Destination Account | 16 |Description | 17 |
|---|---|---|---|---|---|---|
| [[entry.created_at]] | 23 |[[entry.amount]] | 24 |[[entry.orig_profile.printable_name]] | 25 |[[entry.orig_account]] | 26 |[[entry.dest_profile.printable_name]] | 27 |[[entry.dest_account]] | 28 |29 | |
6 | Welcome to the testsite for djaodjin-saas! 7 |
8 |9 | This is the ugliest site you will either see. Only the bare minimal markup 10 | necessary to validate the functionality of the project was used. 11 | This way makes it a lot easier to understand what truly matters. 12 |
13 |14 | Next Sign-in to the admin site with the superuser you created through 15 | the python ./manage.py createsuperuser command. Then start 16 | by creating a few plans and discounts if you haven't prepopulated 17 | the site with dummy data (python ./manage.py load_test_transactions). 18 |
19 |20 | Once at least one plan is active, you can browse to the Pricing page 21 | and follow the logic a user would go through to sign up for the service. 22 |
23 |24 | If you are browsing to the Pricing page and get a 25 | 404 error, it is most likely you haven't loaded initial 26 | data to represent the website broker 27 | (python ./manage.py loaddata testsite/fixtures/initial_data.json). 28 |
29 |You have no balance due.
{% endblock %} 8 | 9 | {% block order_footer %} 10 |18 | On {{charge.created_at}} we attempted to charge your card 19 |
20 |31 | The charge 32 | {% if charge.is_failed %}failed 33 | {% else %} 34 | {% if charge.is_disputed %}was disputed 35 | {% endif %} 36 | {% endif %} 37 | (ref: {{charge.processor_key}}). 38 |
39 |40 | Please enter a valid credit card. A new charge of 41 | {{lines_price|humanize_money}} will be created. Thank you. 42 |
43 | {% endif %} 44 | {% endwith %} 45 | {% endif %} 46 |7 | You will find here all the legal documents that keeps us out of trouble 8 | while providing you the best possible service. 9 |
10 | {% if agreements %} 11 |{{organization.email}}
27 | {% endif %} 28 | 29 | {% if organization.phone %} 30 |{{organization.phone}}
32 | {% endif %} 33 | 34 | {% if organization.street_address %} 35 |
37 | {{organization.street_address}}
38 | {{organization.locality}}
39 | {{organization.region}} {{organization.postal_code}}
40 | {{organization.country.name}}
41 |
| Date | 7 |Amount | 8 |State | 9 |Description | 10 |11 | | 14 |
|---|---|---|---|---|
21 | No charges [[params.q]]22 | |
23 | ||||
| [[entry.created_at]] | 32 |[[entry.readable_amount]] | 33 |[[entry.state]] | 34 |37 | | |
| Date | 13 |Amount | 14 |Description | 15 |16 | |
|---|---|---|---|
23 | No transactions [[params.q]]24 | |
25 | |||
| [[entry.created_at]] | 34 |37 | [[entry.amount]] 38 | | 39 |42 | | |
| Title | 17 |18 | |
|---|---|
| [[entry.title]] | 25 |29 | Globally defined 30 | | 31 |
You already have an account? Sign in 61 |
8 | {{coupon.code}} was used [[totalItems]] time[[totalItems > 0 ? "s" : ""]]. 9 |
10 || User | 15 |Plan | 17 |Used at | 19 |
|---|---|---|
25 | No use of {{coupon.code}} coupon26 | |
27 | ||
| 35 | [[item.user.full_name]] 37 | | 38 |[[item.plan]] | 39 |[[item.created_at]] | 40 |
15 | You will be able to create charges and payout funds with this processor. 16 |
17 |49 | You will be able to create charges on credit cards. 50 |
51 |52 | If you already have a Stripe account, we recommend that you first login into 53 | your Stripe account before clicking the button below. 54 |
55 |56 | Login to Stripe » 57 |
58 | Connect 59 |70 | The configuration variables STRIPE_CLIENT_ID, STRIPE_PUB_KEY 71 | and STRIPE_PRIV_KEY are invalid. You will need to retrieve 72 | the StripeConnect ClientID, and the pair of publishable/private 73 | API keys for your Stripe account on 74 | stripe.com. 75 |
76 |77 | Once you have those keys, write them in the credentials 78 | configuration file and restart the server. 79 |
80 || 46 | | [[ col[0] ]] | 48 ||
| 54 | [[row.is_active ? 'Active': 'Inactive' ]] 55 | [[row.key]] 57 | | 58 |[[row.key]] | 60 |62 | [[col[1] ]] [[table.unit]] ([[table.scale]]) 63 | | 64 |