├── 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 |
7 | 8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testsite.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /saas/templates/saas/billing/charges.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base_dashboard.html" %} 2 | 3 | {% block saas_content %} 4 | 5 |
6 | {% include "saas/_charges.html" %} 7 |
8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /saas/templates/saas/billing/cart-seats.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/billing/cart.html" %} 2 | 3 | {% block order_title %}Place order on behalf of ...{% endblock %} 4 | 5 | {% block order_card %} 6 |
7 | 8 |
9 | {% endblock %} 10 | 11 | -------------------------------------------------------------------------------- /saas/templates/saas/profile/new.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Create a new profile ...

5 |
6 | 7 | {{ form.as_p }} 8 |
9 | 10 |
11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /saas/migrations/0010_0_4_0.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.18 on 2019-03-22 01:11 3 | from __future__ import unicode_literals 4 | 5 | import django.core.validators 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('saas', '0009_0_3_5'), 13 | ] 14 | 15 | operations = [ 16 | ] 17 | -------------------------------------------------------------------------------- /testsite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "djaodjin-saas-ui", 3 | "version": "0.15.6-dev", 4 | "dependencies": { 5 | "jquery": "~3.6.0", 6 | "moment": "~2.29.4", 7 | "moment-timezone": "0.5.33", 8 | "vue": "~2.7.8", 9 | "vue-croppa": "~1.3.8" 10 | }, 11 | "engines": { 12 | "node": ">=v10.16.1", 13 | "npm": ">=6.9.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /saas/templates/saas/_filter.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 9 |
10 | -------------------------------------------------------------------------------- /saas/templates/saas/redeem.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Redeem Code

5 |
6 |
7 | 8 | 9 | 10 |
11 |
12 | {% endblock %} 13 | 14 | -------------------------------------------------------------------------------- /testsite/fixtures/DESCRIPTION: -------------------------------------------------------------------------------- 1 | 100-balance-due.json 2 | A charge has failed after a balance was already due. 3 | 4 | 110-balance-checkout.json 5 | There is a balance due on a subscription and a user is adding the plan 6 | in the cart again. 7 | 8 | 130-subscriptions.json 9 | Defines a provider a 2 plans and 5 subscribers such that each subscription 10 | period intersects [2022-04-01,2022-07-31[ in a different way. 11 | -------------------------------------------------------------------------------- /saas/templates/saas/organization_redirects.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Please choose a profile ...

6 | {% for redirect in redirects %} 7 |
8 | {{redirect.1}} 9 |
10 | {% endfor %} 11 | New ... 12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /saas/templates/notification/order_executed.eml: -------------------------------------------------------------------------------- 1 | {% block subject %} 2 | {{provider.printable_name}} Order Confirmation 3 | {% endblock %} 4 | 5 | {% block plain_content %} 6 | {{provider.printable_name}} Order Confirmation 7 | 8 | {% for line in invoiced_items %} 9 | {{line.dest_price|humanize_money}} 10 | {{line.descr}} 11 | {% endfor %} 12 | 13 | Any questions or comments, email us at {{provider.email}}. 14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /saas/templates/saas/metrics/revenue.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/metrics/base.html" %} 2 | 3 | {% block metrics_extra_info %} 4 | {% if urls.provider and urls.provider.metrics_lifetimevalue %} 5 | Lifetime value 6 | {% endif %} 7 | {% if urls.provider and urls.provider.ledger_balances %} 8 | Balance Sheet 9 | {% endif %} 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /testsite/templates/paginator.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /saas/templates/notification/renewal_charge_failed.eml: -------------------------------------------------------------------------------- 1 | {% block subject %} 2 | Renewal failed for {{site.name}} 3 | {% endblock %} 4 | 5 | {% block plain_content %} 6 | We attempted to renew your following subscriptions to {{site.name}}: 7 | {% for line in invoiced_items %} 8 | {{line.dest_price|humanize_money}} 9 | {{line.descr}} 10 | {% endfor %} 11 | 12 | The charge of {{total_price|humanize_money}} failed. 13 | 14 | Please update your payment method at {{back_url}}. 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /saas/templates/saas/legal/sign.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

5 | Please sign: 6 |

7 | {{page|safe}}{# OK to use a safe here. We load a committed source .md file #} 8 | 9 |
10 | 11 | 12 | 13 |
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 |
11 |
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 |
5 | 6 |
7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 | 15 |
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 | 5 |
6 |

Plans

7 | Add Plan ... 8 | 9 | 10 | 14 | 15 |
11 | 12 | [[item.title]] 13 |
16 |
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /testsite/templates/organization_list_index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Organizations

6 | {% if organizations %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for entry in organizations %} 15 | 16 | 17 | 18 | {% endfor %} 19 | 20 |
Name
{{entry.printable_name}}
21 | {% include "paginator.html" %} 22 | {% else %} 23 | No {{title}} 24 | {% endif %} 25 |
26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /saas/templates/saas/billing/withdraw.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base.html" %} 2 | 3 | {% block saas_content %} 4 |

Withdraw funds to your bank account

5 | 6 |
8 | 9 | 10 |
11 | Amount to transfer 12 |
13 | 14 | {{form.amount}} 15 |
16 |
17 | 18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /saas/migrations/0012_0_8_3.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.27 on 2020-01-20 17:04 3 | from __future__ import unicode_literals 4 | 5 | import django.core.validators 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django_countries.fields 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('saas', '0011_0_7_0'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='roledescription', 20 | name='implicit_create_on_none', 21 | field=models.BooleanField(default=False, help_text='Automatically adds the role when a user and profile share the same e-mail domain.'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /saas/templates/saas/_paginator.html: -------------------------------------------------------------------------------- 1 |
3 | 14 |
15 | 16 | [[count]] 17 |    18 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /testsite/requirements-legacy.txt: -------------------------------------------------------------------------------- 1 | Django==1.11.29 2 | django-countries==5.5 3 | django-localflavor==2.2 4 | django-phonenumber-field==2.4.0 5 | djangorestframework==3.9.4 6 | # We need Python Markdown for django.contrib.markup. markdown2 is not enough. 7 | Markdown==3.1.1 8 | phonenumbers==8.12.6 9 | PyJWT==1.6.1 10 | python-dateutil==2.8.1 11 | razorpay==0.2.0 12 | stripe==2.55.1 13 | 14 | # native packages 15 | cryptography==2.2.2 16 | 17 | # testsite-only 18 | coverage==4.0.3 19 | django-debug-toolbar==1.11.1 # This version works with Py2, Django>=1.11 (incl. 2.2) 20 | django-extensions==2.2.5 21 | djaodjin-rules==0.2.9 22 | gunicorn==19.7.1 23 | whitenoise==4.1.2 24 | 25 | # development 26 | Sphinx==1.8.5 # This version works with Py2 27 | sphinxcontrib-httpdomain==1.7.0 28 | -------------------------------------------------------------------------------- /docs/backends.rst: -------------------------------------------------------------------------------- 1 | Processor Backends 2 | ================== 3 | 4 | There always needs to be a special ``Organization``, the processor in your 5 | database. The processor represents the payment processor backend in charge 6 | and deposit transactions. 7 | 8 | ``Organization`` with pk=1 will be considered to be the default processor. 9 | This can be overridden by defining ``PROCESSOR_ID`` in the settings block. 10 | 11 | .. code-block:: 12 | 13 | $ cat settings.py 14 | 15 | SAAS = { 16 | 'PROCESSOR_ID': 1 17 | } 18 | 19 | 20 | Razorpay configuration 21 | ---------------------- 22 | 23 | .. automodule:: saas.backends.razorpay_processor 24 | 25 | Stripe configuration 26 | -------------------- 27 | 28 | .. automodule:: saas.backends.stripe_processor.base 29 | -------------------------------------------------------------------------------- /testsite/etc/gunicorn.conf: -------------------------------------------------------------------------------- 1 | # Template to configure gunicorn 2 | 3 | proc_name="testsite" 4 | bind="127.0.0.1:8020" 5 | workers=3 6 | pidfile="%(LOCALSTATEDIR)s/run/testsite.pid" 7 | #errorlog="%(LOCALSTATEDIR)s/log/gunicorn/testsite-error.log" 8 | #accesslog="%(LOCALSTATEDIR)s/log/gunicorn/testsite-access.log" 9 | accesslog="-" 10 | loglevel="info" 11 | # There is a typo in the default access_log_format so we set it explicitely 12 | # With gunicorn >= 19.0 we need to use %({X-Forwarded-For}i)s instead 13 | # of %(h)s because gunicorn will set REMOTE_ADDR to "" (see github issue #797) 14 | # Last "-" in nginx.conf:log_format is for ``http_x_forwarded_for`` 15 | access_log_format='%(h)s %({Host}i)s %({User-Session}o)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" "%({X-Forwarded-For}i)s"' 16 | -------------------------------------------------------------------------------- /saas/templates/notification/one_time_coupon_generated.eml: -------------------------------------------------------------------------------- 1 | {% block subject %} 2 | {{provider.printable_name}} Subscription 3 | {% endblock %} 4 | 5 | {% block html_content %} 6 | 7 | 8 | 9 | 10 |
11 |

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 |

27 | Any questions or comments, email us at {{provider.email}}. 28 |

29 |
30 | 31 | 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /docs/models.rst: -------------------------------------------------------------------------------- 1 | Database Models 2 | =============== 3 | 4 | .. automodule:: saas.models 5 | 6 | .. image:: saas-models.* 7 | 8 | ``Organization`` can be semantically separated in four categories, processor, 9 | broker, providers and subscribers. 10 | 11 | * subscribers: organizations that subscribe to one or multiple ``Plan``. 12 | * providers: organizations that provides plans others can subscribe to. 13 | * broker: The provider that controls the website. 14 | * processor: The organization / :doc:`backend ` 15 | actually processing the charges. 16 | 17 | In a pure Software-as-a-Service setup, there is only one provider which is 18 | by definition the broker. 19 | 20 | In a marketplace setup, there might be multiple providers even though there 21 | is only one broker, always. The broker controls the domain name on which 22 | the site is hosted. 23 | -------------------------------------------------------------------------------- /saas/templates/saas/users/roles/accept.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | {% if role %} 7 |

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 |
18 | Continue 19 |
20 | {% else %} 21 |

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 |
26 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /saas/templates/notification/role_grant_accepted.eml: -------------------------------------------------------------------------------- 1 | {% extends "notification/base.eml" %} 2 | 3 | {% block subject %} 4 | {# We are putting |safe here because an e-mail's subject line shouldn't be 5 | HTML encoded. #} 6 | {{user.printable_name|safe}} was added as a {{role}} to {{organization.printable_name|safe}} 7 | {% endblock %} 8 | 9 | {% block title %}{{user.printable_name}} was added as a {{role}} to {{organization.printable_name}}{% endblock %} 10 | 11 | {% block html_content %} 12 |

{{user.printable_name}} was added as a {{role}} to {{organization.printable_name}}

13 | {% if reason %} 14 |

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 |
15 |

Charge Receipt

16 |
17 |
card
18 |
{{last4}}
19 |
20 |
21 |
expires
22 |
{{exp_date}}
23 |
24 |

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 |
34 | 35 | 36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /saas/templates/saas/_user_card.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |
8 |
9 | 10 |
11 |
12 |
13 |
[[item.user.printable_name]]
14 |
[[item.user.email]]
15 |
16 |
17 | requested [[item.created_at]] 18 | 19 |
20 |
21 |
22 | invited [[item.created_at]] 23 | 24 |
25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /saas/migrations/0004_auto_20161221_1944.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2016-12-22 01:44 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('saas', '0003_balanceline_is_positive'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='charge', 20 | name='card_name', 21 | field=models.CharField(max_length=50, null=True), 22 | ), 23 | migrations.AddField( 24 | model_name='charge', 25 | name='created_by', 26 | field=models.ForeignKey(db_column='user_id', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. saas documentation master file, created by 2 | sphinx-quickstart on Sun May 5 11:18:54 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Djaodjin saas's documentation! 7 | ========================================= 8 | 9 | djaodjin-saas is a Django application that implements the logic to support 10 | subscription-based Sofware-as-a-Service businesses. 11 | 12 | Major Features: 13 | 14 | - Separate billing profiles and authenticated users 15 | - Double entry book keeping ledger 16 | - Flexible security framework 17 | 18 | Contents: 19 | 20 | .. toctree:: 21 | :maxdepth: 1 22 | 23 | getting-started 24 | subscriptions 25 | pricing 26 | orders 27 | ledger 28 | security 29 | relations 30 | models 31 | views 32 | periodic-tasks 33 | backends 34 | extensions 35 | 36 | Indices and tables 37 | ================== 38 | 39 | * :ref:`search` 40 | 41 | -------------------------------------------------------------------------------- /saas/templates/saas/metrics/lifetimevalue.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base_dashboard.html" %} 2 | 3 | {% block saas_title %} 4 | Lifetime value 5 | {% endblock %} 6 | 7 | {% block saas_content %} 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
NameContract ValueCash PaymentsDeferred RevenueCreated atEnds at
23 | [[item.printable_name]] 24 | [[item.contract_value]][[item.cash_payments]][[item.deferred_revenue]][[item.created_at]][[item.ends_at]]
32 |
33 |
34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /saas/templates/saas/billing/transactions.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base_dashboard.html" %} 2 | 3 | {% block saas_content %} 4 | 5 |
6 | {% include "saas/_filter.html" %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
DateAmountOrigin ProfileOrigin AccountDestination ProfileDestination AccountDescription
[[entry.created_at]][[entry.amount]][[entry.orig_profile.printable_name]][[entry.orig_account]][[entry.dest_profile.printable_name]][[entry.dest_account]]
32 |
33 |
34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /testsite/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==3.2.20 2 | django-countries==7.2.1 3 | django-localflavor==3.1 4 | django-phonenumber-field==5.2.0 5 | djangorestframework==3.14.0 6 | # We need Python Markdown for django.contrib.markup. markdown2 is not enough. 7 | Markdown==3.2.2 8 | phonenumbers==8.13.7 9 | python-dateutil==2.8.2 10 | PyJWT==2.6.0 11 | razorpay==0.2.0 12 | stripe==5.0.0 13 | 14 | # testsite-only 15 | coverage==7.2.1 16 | django-debug-toolbar==3.5.0 # 3.4.0 requires Django>=3.2 17 | # 3.2.4 fails with SQLPanel is not scriptable 18 | # 2.2.1 is the last version for Django2.2 19 | django-extensions==3.2.1 # 3.2.0 required by Django==4.0 20 | djaodjin-rules==0.2.9 21 | gunicorn==20.1.0 22 | whitenoise==6.4.0 23 | 24 | # development 25 | Sphinx==5.1.1 26 | sphinxcontrib-httpdomain==1.8.1 27 | 28 | # When running with Py37, we transitively use the `importlib-metadata` 29 | # package through `djangorestframework` -> `makrdown`. Latest version (>=5.0) 30 | # lead to an error: `'EntryPoints' object has no attribute 'get'`. 31 | importlib-metadata==4.13.0 32 | -------------------------------------------------------------------------------- /saas/templates/saas/billing/card.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base_dashboard.html" %} 2 | 3 | {% block saas_content %} 4 | 13 |
14 |

Update Card for {{organization}}

15 |
18 | 19 | {% include "saas/_card_use.html" %} 20 |
21 |
22 | 25 |
26 |
27 |
28 |
29 |
30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /testsite/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

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 |
30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /saas/templates/saas/billing/balance.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/billing/cart.html" %} 2 | 3 | {% block order_title %}Balance due{% endblock %} 4 | 5 | {% block order_head %}{% endblock %} 6 | 7 | {% block no_invoicables %}

You have no balance due.

{% endblock %} 8 | 9 | {% block order_footer %} 10 |
11 | {% if not last_charge %} 12 | A charge of {{lines_price|humanize_money}} will be created 13 | on your card. Thank you. 14 | {% else %} 15 | {% with last_charge as charge %} 16 | {% if charge.is_failed or charge.is_disputed %} 17 |

18 | On {{charge.created_at}} we attempted to charge your card 19 |

20 |
21 |
22 |
card
23 |
{{charge.last4}}
24 |
25 |
26 |
expires
27 |
{{charge.exp_date}}
28 |
29 |
30 |

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 |
47 | {% endblock %} 48 | -------------------------------------------------------------------------------- /testsite/fixtures/initial_data.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "fields": { 3 | "slug": "stripe", 4 | "full_name": "Stripe", 5 | "created_at": "2021-12-31T00:00:00-00:00", 6 | "processor": 1, 7 | "is_active": 1 8 | }, 9 | "model": "saas.Organization", "pk": 1 10 | }, 11 | { 12 | "fields": { 13 | "slug": "terms-of-use", 14 | "title": "Terms Of Use", 15 | "modified": "2021-12-31T00:00:00-00:00" 16 | }, 17 | "model": "saas.agreement", "pk": 1 18 | }, 19 | { 20 | "fields": { 21 | "slug": "cowork", 22 | "full_name": "ABC Corp.", 23 | "created_at": "2023-01-01T00:00:00-00:00", 24 | "email": "alice+support@localhost.localdomain", 25 | "phone": "555-555-5555", 26 | "street_address": "1 ABC loop", 27 | "locality": "San Francisco", 28 | "region": "CA", 29 | "postal_code": "94102", 30 | "country": "US", 31 | "processor": 1, 32 | "is_provider": 1, 33 | "is_active": 1 34 | }, 35 | "model": "saas.Organization", "pk": 2 36 | }, 37 | { 38 | "fields": { 39 | "created_at": "2023-01-01T00:00:00-00:00", 40 | "slug": "manager", 41 | "title": "Profile Manager" 42 | }, 43 | "model": "saas.RoleDescription", "pk": 1 44 | }] 45 | -------------------------------------------------------------------------------- /saas/templates/saas/pricing.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 7 | {% if plan_list %} 8 | {% for plan in plan_list %} 9 |
10 | {% if plan.created_at|date_in_future %} 11 |

Avalaible soon!

12 | {% endif %} 13 |

{{plan.title}}

14 | {% if plan.period_price.amount > 0 %} 15 |

16 | {{plan.period_price|humanize_money}} {{plan.period_type|humanize_period}} 17 |

18 | {% endif %} 19 | {% if plan.setup_amount > 0 %} 20 | one-time: {{plan.setup_price|humanize_money}} 21 | {% endif %} 22 |
23 |

24 | {{plan.description}} 25 |

26 |
27 | 28 |
29 | {% endfor %} 30 | {% else %} 31 | No Plans yet 32 | {% endif %} 33 |
34 |
35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /saas/templates/saas/legal/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 20 |
21 |

Contact us

22 | 23 | 24 | {% if organization.email %} 25 |

By e-mail

26 |

{{organization.email}}

27 | {% endif %} 28 | 29 | {% if organization.phone %} 30 |

By phone

31 |

{{organization.phone}}

32 | {% endif %} 33 | 34 | {% if organization.street_address %} 35 |

By physical mail

36 |

37 | {{organization.street_address}}
38 | {{organization.locality}}
39 | {{organization.region}} {{organization.postal_code}}
40 | {{organization.country.name}} 41 |

42 | {% endif %} 43 |
44 | {% endblock %} 45 | 46 | -------------------------------------------------------------------------------- /saas/migrations/0011_0_7_0.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.23 on 2019-08-25 12:57 3 | from __future__ import unicode_literals 4 | 5 | import django.core.validators 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django_countries.fields 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('saas', '0010_0_4_0'), 15 | ] 16 | 17 | operations = [ 18 | migrations.RenameField( 19 | model_name='plan', 20 | old_name='interval', 21 | new_name='period_type', 22 | ), 23 | migrations.RenameField( 24 | model_name='plan', 25 | old_name='transaction_fee', 26 | new_name='broker_fee_percent', 27 | ), 28 | migrations.RenameField( 29 | model_name='chargeitem', 30 | old_name='invoiced_fee', 31 | new_name='invoiced_processor_fee', 32 | ), 33 | migrations.AddField( 34 | model_name='chargeitem', 35 | name='invoiced_broker_fee', 36 | field=models.ForeignKey(help_text='Fee transaction to broker in order to process the transaction invoiced through this charge', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='invoiced_broker_fee_item', to='saas.Transaction'), 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /saas/templates/saas/_charges.html: -------------------------------------------------------------------------------- 1 |
2 |

Charges

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 19 | 20 | 23 | 24 | 25 | 28 | 31 | 32 | 33 | 34 | 37 | 38 | 39 |
DateAmountStateDescription 11 |
21 |

No charges [[params.q]]

22 |
[[entry.created_at]][[entry.readable_amount]][[entry.state]]
40 |
41 | -------------------------------------------------------------------------------- /saas/migrations/0009_0_3_5.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.16 on 2018-12-19 18:12 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', '0008_0_3_4'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='cartitem', 17 | name='first_name', 18 | ), 19 | migrations.RemoveField( 20 | model_name='cartitem', 21 | name='last_name', 22 | ), 23 | migrations.AddField( 24 | model_name='cartitem', 25 | name='email', 26 | field=models.CharField(blank=True, max_length=255, null=True), 27 | ), 28 | migrations.AddField( 29 | model_name='cartitem', 30 | name='full_name', 31 | field=models.CharField(blank=True, help_text='Full name of the person that will benefit from the subscription (GroupBuy)', max_length=150, verbose_name='Full name'), 32 | ), 33 | migrations.AlterField( 34 | model_name='charge', 35 | name='state', 36 | field=models.PositiveSmallIntegerField(choices=[(0, 'created'), (2, 'failed'), (1, 'done'), (3, 'disputed')], default=0, help_text='Current state (i.e. created, done, failed, disputed)'), 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | from setuptools import setup 25 | 26 | setup() 27 | -------------------------------------------------------------------------------- /saas/templates/saas/profile/index.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base_dashboard.html" %} 2 | 3 | {% block saas_content %} 4 | 5 |
6 |
7 | 8 |
9 | 10 | 11 | 12 | 13 |
14 | {{form.full_name}} 15 | {{form.email}} 16 | {{form.phone}} 17 | {{form.street_address}} 18 | {{form.locality}} 19 | {{form.postal_code}} 20 | {{form.country}} 21 | {{form.region}} 22 | {{form.is_bulk_buyer}} 23 | {% if form.extra %} 24 | {{form.extra}} 25 | {% endif %} 26 | 27 |
28 |
29 |
30 |
31 | 32 |
33 |
34 |
35 |
36 |
37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /saas/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | PEP 386-compliant version number for the saas django app. 27 | """ 28 | 29 | __version__ = '0.17.2-dev' 30 | -------------------------------------------------------------------------------- /saas/templates/saas/billing/transfers.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base_dashboard.html" %} 2 | 3 | {% block saas_content %} 4 | 5 |
6 |
8 |
9 |

Deposit Information Update

10 |
11 |
Institution
12 |
[[bank_name]]
13 |
14 |
15 |
Account Number
16 |
[[last4]]
17 |
18 |
19 |
20 |

Funds

21 |
22 |
Available
23 |
[[items.balance_amount]] [[items.balance_unit]] (withdraw)
24 |
25 |
26 |
27 |
28 | {% include "saas/_transactions.html" %} 29 |
30 |
31 | {% if urls.raw_charges %} 32 | Charges 33 | {% endif %} 34 | {% if urls.raw_transactions %} 35 | Transaction Ledger 36 | {% endif %} 37 | Add offline transaction 38 |
39 |
40 |
41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /saas/views/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | Helpers to redirect based on session. 27 | """ 28 | 29 | from .redirects import (RedirectFormMixin, 30 | OrganizationRedirectView, ProviderRedirectView, UserRedirectView) 31 | -------------------------------------------------------------------------------- /saas/templates/saas/_transactions.html: -------------------------------------------------------------------------------- 1 |
2 |

Transactions

3 | {% include "saas/_filter.html" %} 4 |
5 | CSV Download 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 25 | 26 | 27 | 30 | 33 | 34 | 39 | 42 | 43 | 44 |
DateAmountDescription
23 |

No transactions [[params.q]]

24 |
[[entry.created_at]] 37 | [[entry.amount]] 38 |
45 | {% include "saas/_paginator.html" %} 46 |
47 | -------------------------------------------------------------------------------- /saas/backends/urls/api.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # * Redistributions in binary form must reproduce the above copyright notice, 10 | # this list of conditions and the following disclaimer in the documentation 11 | # and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs for backends of saas Django app 27 | """ 28 | 29 | from ...compat import include, re_path 30 | 31 | urlpatterns = [ 32 | re_path(r'^', include('saas.backends.stripe_processor.urls.api')), 33 | ] 34 | -------------------------------------------------------------------------------- /saas/urls/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs for the saas django app 27 | """ 28 | 29 | from ..compat import include, path 30 | 31 | 32 | urlpatterns = [ 33 | path('api/', include('saas.urls.api')), 34 | path('', include('saas.urls.views')), 35 | ] 36 | -------------------------------------------------------------------------------- /saas/backends/urls/views.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # * Redistributions in binary form must reproduce the above copyright notice, 10 | # this list of conditions and the following disclaimer in the documentation 11 | # and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs for backends of saas Django app 27 | """ 28 | 29 | from ...compat import include, re_path 30 | 31 | 32 | urlpatterns = [ 33 | re_path(r'^', include('saas.backends.stripe_processor.urls.views')), 34 | ] 35 | -------------------------------------------------------------------------------- /saas/migrations/0013_0_9_0.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-05-24 22:23 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', '0012_0_8_3'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='coupon', 17 | old_name='percent', 18 | new_name='discount_value', 19 | ), 20 | migrations.AddField( 21 | model_name='coupon', 22 | name='discount_type', 23 | field=models.PositiveSmallIntegerField(choices=[(1, 'Percentage'), (2, 'Currency')], default=1), 24 | ), 25 | migrations.RemoveField( 26 | model_name='plan', 27 | name='advance_discount', 28 | ), 29 | migrations.CreateModel( 30 | name='AdvanceDiscount', 31 | fields=[ 32 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 33 | ('discount_type', models.PositiveSmallIntegerField(choices=[(1, 'Percentage'), (2, 'Currency'), (3, 'Period')], default=1)), 34 | ('discount_value', models.PositiveIntegerField(default=0, help_text='Amount of the discount')), 35 | ('length', models.PositiveSmallIntegerField(default=1, help_text='Contract length associated with the period')), 36 | ('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='advance_discounts', to='saas.Plan')), 37 | ], 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /testsite/context_processors.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | from django.conf import settings 26 | 27 | def js_framework(request):#pylint:disable=unused-argument 28 | return { 29 | 'VUEJS': settings.JS_FRAMEWORK == 'vuejs', 30 | 'DATETIME_FORMAT': "MMM dd, yyyy", 31 | } 32 | -------------------------------------------------------------------------------- /saas/backends/urls/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # * Redistributions in binary form must reproduce the above copyright notice, 10 | # this list of conditions and the following disclaimer in the documentation 11 | # and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs for backends of saas Django app 27 | """ 28 | 29 | from ...compat import include, re_path 30 | 31 | 32 | urlpatterns = [ 33 | re_path(r'^api/', include('saas.backends.urls.api')), 34 | re_path(r'^', include('saas.backends.urls.views')), 35 | ] 36 | -------------------------------------------------------------------------------- /saas/backends/stripe_processor/urls/api.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # * Redistributions in binary form must reproduce the above copyright notice, 10 | # this list of conditions and the following disclaimer in the documentation 11 | # and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | from ..views import StripeWebhook 26 | from ....settings import PROCESSOR_HOOK_URL 27 | from ....compat import re_path 28 | 29 | urlpatterns = [ 30 | re_path(r'^%s' % PROCESSOR_HOOK_URL, 31 | StripeWebhook.as_view(), name='saas_processor_hook') 32 | ] 33 | -------------------------------------------------------------------------------- /saas/urls/views/subscriber/billing/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs related to billing. 27 | """ 28 | 29 | from .....compat import include, path 30 | 31 | 32 | urlpatterns = [ 33 | path('', include('saas.urls.views.subscriber.billing.payment')), 34 | path('', include('saas.urls.views.subscriber.billing.info')), 35 | ] 36 | -------------------------------------------------------------------------------- /saas/templates/saas/_bank_use.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | Bank Information {% if last4 and not force_update %}Update{% endif %} 5 | 6 | {% if last4 %} 7 |
8 |
9 |
Bank
10 |
{{bank_name}}
11 |
12 |
13 |
Account Number
14 |
{{last4}}
15 |
16 |
17 | {% endif %} 18 | {% if not last4 or force_update %} 19 | 22 |
23 | 24 | 26 |
27 |
28 | 29 | 31 |
32 |
33 | 34 | 36 |
37 | {% endif %} 38 | 39 | 40 | -------------------------------------------------------------------------------- /saas/templates/saas/_sidebar.html: -------------------------------------------------------------------------------- 1 | {# Profile menu items #} 2 | {% if urls.user and urls.user.profile %} 3 | Profile 4 | {% elif urls.organization and urls.organization.profile %} 5 | Profile 6 | {% endif %} 7 | {% if urls.organization and urls.organization.roles %} 8 | {% for role_title, role_url in urls.organization.roles|iteritems %} 9 | {{role_title}} 10 | {% endfor %} 11 | {% endif %} 12 | 13 | {# Subscriber menu items #} 14 | {% if urls.organization and urls.organization.billing %} 15 | Billing 16 | {% endif %} 17 | {% if urls.organization and urls.organization.subscriptions %} 18 | Subscriptions 19 | {% endif %} 20 | {% if urls.user and urls.user.accessibles %} 21 | Connected profiles 22 | {% endif %} 23 | {% if urls.user and urls.user.notifications %} 24 | Notifications 25 | {% endif %} 26 | {# Provider menu items #} 27 | {% if urls.provider %} 28 | Dashboard 29 | Revenue 30 | Funds 31 | Coupons 32 | Plans 33 | Subscribers 34 | {% endif %} 35 | 36 | {# Broker menu items #} 37 | -------------------------------------------------------------------------------- /saas/urls/api/legal.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs for API related to signing legal agreements. 27 | """ 28 | 29 | from ...api.users import AgreementSignAPIView 30 | from ...compat import path 31 | 32 | 33 | urlpatterns = [ 34 | path('legal//sign', 35 | AgreementSignAPIView.as_view(), name='saas_api_sign_agreement'), 36 | ] 37 | -------------------------------------------------------------------------------- /docs/extensions.rst: -------------------------------------------------------------------------------- 1 | Integration within a multi-app project 2 | ====================================== 3 | 4 | There are two mechanisms to help integrating djaodjin-saas within a project 5 | composed of multiple Django applications. 6 | 7 | - Overriding models 8 | - Replacing default functions 9 | 10 | For example, `djaoapp`_ is a project which ties djaodjin-saas with other Django 11 | applications into a boilerplate Software-as-a-Service (SaaS) WebApp. 12 | 13 | 14 | Overriding models 15 | ----------------- 16 | 17 | Profiles are defined through the `Organization` model. It is often useful 18 | for composition of Django apps to use a single profile model. 19 | This is possible by defining the settings `SAAS_ORGANIZATION_MODEL`. 20 | 21 | User/Profile relationships are implemented through the `Role` model. 22 | It is often useful for composition of Django apps to use a single role model. 23 | This is possible by defining the settings `SAAS_ROLE_MODEL`. If you do so, you 24 | will most likely also need to implement a serializer and define 25 | `SAAS['ROLE_SERIALIZER']`. 26 | 27 | If the ``AUTH_USER_MODEL`` (as returned by ``get_user_model``) has been 28 | overridden, both ``SAAS['USER_SERIALIZER']`` and 29 | ``SAAS['USER_DETAIL_SERIALIZER']`` should be defined and implement a user 30 | model serialization as used in API calls for the summary and detailed contact 31 | information respectively. 32 | 33 | 34 | Replacing default functions 35 | --------------------------- 36 | 37 | .. autodata:: saas.settings.BROKER_CALLABLE 38 | 39 | .. autodata:: saas.settings.BUILD_ABSOLUTE_URI_CALLABLE 40 | 41 | .. autodata:: saas.settings.PICTURE_STORAGE_CALLABLE 42 | 43 | .. autodata:: saas.settings.PRODUCT_URL_CALLABLE 44 | 45 | 46 | .. _djaoapp: https://github.com/djaodjin/djaoapp 47 | -------------------------------------------------------------------------------- /saas/urls/views/subscriber/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs for the saas django app 27 | """ 28 | 29 | from ....compat import include, path 30 | 31 | 32 | urlpatterns = [ 33 | path('', include('saas.urls.views.subscriber.billing')), 34 | path('', include('saas.urls.views.subscriber.profile')), 35 | path('', include('saas.urls.views.users')), 36 | ] 37 | -------------------------------------------------------------------------------- /saas/urls/views/provider/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs related to provider pages 27 | """ 28 | 29 | from ....compat import include, path 30 | 31 | 32 | urlpatterns = [ 33 | path('', include('saas.urls.views.provider.billing')), 34 | path('', include('saas.urls.views.provider.metrics')), 35 | path('', include('saas.urls.views.provider.profile')), 36 | ] 37 | -------------------------------------------------------------------------------- /testsite/fixtures/120-subscriptions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "fields": { 4 | "date_joined": "2023-01-01T00:00:00+00:00", 5 | "email": "xia+120@localhost.localdomain", 6 | "first_name": "Xia", 7 | "last_name": "Subscription", 8 | "is_active": true, 9 | "is_staff": false, 10 | "is_superuser": false, 11 | "last_login": "2023-01-01T00:00:00+00:00", 12 | "password": "pbkdf2_sha256$10000$z0MBiWn0Rlem$iZdC6uHomlE07qGK/TqfcfxNzKJtFp03c0JILF1frRc=", 13 | "username": "xia120" 14 | }, 15 | "model": "auth.User", "pk": 120 16 | }, 17 | { 18 | "fields": { 19 | "last_signed": "2023-01-01T00:00:00+00:00", 20 | "agreement": 1, 21 | "user": 120 22 | }, 23 | "model": "saas.signature", "pk": 120 24 | }, 25 | { 26 | "fields": { 27 | "slug": "club120", 28 | "email": "club+120@localhost.localdomain", 29 | "full_name": "Club120", 30 | "created_at": "2023-01-01T00:00:00+00:00", 31 | "processor": 1, 32 | "is_provider": 0, 33 | "is_active": 1 34 | }, 35 | "model": "saas.Organization", "pk": 120 36 | }, 37 | { 38 | "fields": { 39 | "created_at": "2023-01-01T00:00:00+00:00", 40 | "role_description": 1, 41 | "organization": 120, 42 | "user": 120 43 | }, 44 | "model": "saas.Role", "pk": 120 45 | }, 46 | { 47 | "fields":{ 48 | "slug":"plan-club120", 49 | "title":"Club120 Plan", 50 | "created_at":"2023-01-01T00:00:00+00:00", 51 | "setup_amount":0, 52 | "period_amount":1000, 53 | "broker_fee_percent": 0, 54 | "period_type": 5, 55 | "description":"", 56 | "organization" : 120, 57 | "is_active" : 1 58 | }, 59 | "model" : "saas.Plan", "pk": 120 60 | } 61 | ] 62 | -------------------------------------------------------------------------------- /saas/backends/stripe_processor/urls/views.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # * Redistributions in binary form must reproduce the above copyright notice, 10 | # this list of conditions and the following disclaimer in the documentation 11 | # and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | from ..views import StripeProcessorRedirectView 26 | from ....compat import re_path 27 | 28 | 29 | urlpatterns = [ 30 | re_path(r'^stripe/billing/connected/', 31 | StripeProcessorRedirectView.as_view( 32 | pattern_name='saas_update_bank'), 33 | name='saas_processor_connected_hook'), 34 | ] 35 | -------------------------------------------------------------------------------- /saas/templates/saas/billing/import.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base_dashboard.html" %} 2 | 3 | {% block saas_title %}Add offline transaction{% endblock %} 4 | 5 | {% block saas_content %} 6 | 7 |
8 |
9 | 10 |
11 |
12 | 15 |
16 | 25 |
26 |
27 |
28 | 31 |
32 | 33 |
34 |
35 |
36 | 39 |
40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 | {% endblock %} 51 | -------------------------------------------------------------------------------- /saas/urls/views/users.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs for pages from a ``User`` perspective. 27 | """ 28 | 29 | from ... import settings 30 | from ...compat import re_path 31 | from ...views.users import ProductListView 32 | 33 | 34 | urlpatterns = [ 35 | re_path(r'users/(?P%s)/roles/' % settings.SLUG_RE, 36 | ProductListView.as_view(), name='saas_user_product_list'), 37 | ] 38 | -------------------------------------------------------------------------------- /saas/templates/saas/profile/plans/plan.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base_dashboard.html" %} 2 | 3 | {% block saas_content %} 4 | 5 |
6 | {% if form.instance and form.instance.slug %} 7 |
8 | 13 |
14 | {% endif %} 15 |
17 | 18 |
{# testing .has-error decoration #} 19 | {{form.title}} 20 |
{% for error in form.title.errors %}{{error}}{% endfor %}
21 |
22 | {{form.description}} 23 | {{form.unit}} 24 | {{form.period_amount}} 25 | {{form.period_type}} 26 | {{form.period_length}} 27 | {{form.renewal_type}} 28 | {{form.advance_discount_type}} 29 | {{form.advance_discount_value}} 30 | {{form.advance_discount_length}} 31 | 32 |
33 | {% if show_delete %} 34 |
35 | 36 |
37 | {% endif %} 38 |
39 |
40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /saas/templates/saas/profile/roles/index.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base_dashboard.html" %} 2 | 3 | {% block saas_title %}Roles{% endblock %} 4 | 5 | {% block saas_content %} 6 | 7 |
9 |

Please wait...

11 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 25 | 31 | 32 | 33 |
Title
[[entry.title]] 29 | Globally defined 30 |
34 |
35 | 36 |
37 |
38 |

Add a new type of role

39 |
40 | 41 | 45 |
46 | 47 |
48 |
49 |
50 | {% endblock %} 51 | -------------------------------------------------------------------------------- /saas/urls/api/tailbroker.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs for API available typically only to the broker platform and 27 | that must be included after the provider and subscriber urls. 28 | """ 29 | 30 | from ...api.organizations import OrganizationListAPIView 31 | from ...compat import path 32 | 33 | 34 | urlpatterns = [ 35 | path('profile', 36 | OrganizationListAPIView.as_view(), 37 | name='saas_api_profile'), 38 | ] 39 | -------------------------------------------------------------------------------- /saas/urls/api/subscriber/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | '''API URLs typically associated with the subscriber.''' 26 | 27 | from ....compat import include, path 28 | 29 | 30 | urlpatterns = [ 31 | path('', include('saas.urls.api.subscriber.charges')), 32 | path('', include('saas.urls.api.subscriber.billing')), 33 | path('', include('saas.urls.api.subscriber.roles')), 34 | path('', include('saas.urls.api.subscriber.profile')), 35 | ] 36 | -------------------------------------------------------------------------------- /saas/views/extra.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | from extended_templates.backends.pdf import PdfTemplateResponse 26 | 27 | from .billing import ChargeReceiptView 28 | 29 | 30 | class PrintableChargeReceiptView(ChargeReceiptView): 31 | """ 32 | ``Charge`` receipt as printable PDF format. 33 | 34 | template: saas/printable_charge_receipt.html 35 | """ 36 | template_name = 'saas/printable_charge_receipt.html' 37 | response_class = PdfTemplateResponse 38 | -------------------------------------------------------------------------------- /testsite/views/organization.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | from django.views.generic import ListView, TemplateView 26 | 27 | from saas.mixins import UserMixin 28 | from saas.utils import get_organization_model 29 | 30 | 31 | class OrganizationListView(ListView): 32 | 33 | model = get_organization_model() 34 | template_name = 'organization_list_index.html' 35 | 36 | 37 | class UserProfileView(UserMixin, TemplateView): 38 | 39 | template_name = 'accounts/profile.html' 40 | -------------------------------------------------------------------------------- /testsite/signals.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | from django.contrib.auth import get_user_model 26 | from django.db.models.signals import post_save 27 | from django.dispatch import receiver 28 | from saas.models import get_broker 29 | 30 | 31 | @receiver(post_save, sender=get_user_model()) 32 | def on_user_post_save(sender, instance, created, raw, **kwargs): 33 | #pylint:disable=unused-argument 34 | if created and instance.is_superuser: 35 | get_broker().add_manager(instance) 36 | -------------------------------------------------------------------------------- /testsite/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for testsite project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os, signal 17 | 18 | from django.core.wsgi import get_wsgi_application 19 | 20 | 21 | def save_coverage(*args, **kwargs): 22 | #pylint:disable=unused-argument 23 | sys.stderr.write("saving coverage\n") 24 | cov.stop() 25 | cov.save() 26 | 27 | if os.getenv('DJANGO_COVERAGE'): 28 | import atexit, sys 29 | import coverage 30 | cov = coverage.coverage(data_file=os.path.join(os.getenv('DJANGO_COVERAGE'), 31 | ".coverage.%d" % os.getpid())) 32 | cov.start() 33 | atexit.register(save_coverage) 34 | try: 35 | signal.signal(signal.SIGTERM, save_coverage) 36 | except ValueError as e: 37 | # trapping signals does not work with manage 38 | # trying to do so fails with 39 | # ValueError: signal only works in main thread 40 | pass 41 | 42 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testsite.settings") 43 | 44 | # This application object is used by any WSGI server configured to use this 45 | # file. This includes Django's development server, if the WSGI_APPLICATION 46 | # setting points here. 47 | #pylint: disable=invalid-name 48 | application = get_wsgi_application() 49 | -------------------------------------------------------------------------------- /saas/urls/api/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | '''API URLs for the saas application''' 26 | 27 | from ...compat import include, path 28 | 29 | 30 | urlpatterns = [ 31 | path('', include('saas.urls.api.cart')), 32 | path('', include('saas.urls.api.legal')), 33 | path('', include('saas.urls.api.users')), 34 | path('', include('saas.urls.api.headbroker')), 35 | path('', include('saas.urls.api.provider')), 36 | path('', include('saas.urls.api.subscriber')), 37 | path('', include('saas.urls.api.tailbroker')), 38 | ] 39 | -------------------------------------------------------------------------------- /saas/urls/api/provider/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | '''API URLs typically associated with the provider.''' 26 | 27 | from ....compat import include, path 28 | 29 | 30 | urlpatterns = [ 31 | path('', include('saas.urls.api.provider.charges')), 32 | path('', include('saas.urls.api.provider.billing')), 33 | path('', include('saas.urls.api.provider.roles')), 34 | path('', include('saas.urls.api.provider.subscribers')), 35 | path('', include('saas.urls.api.provider.plans')), 36 | path('', include('saas.urls.api.provider.metrics')), 37 | ] 38 | -------------------------------------------------------------------------------- /saas/urls/views/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs for the saas django app 27 | """ 28 | 29 | from ...compat import include, path 30 | 31 | 32 | urlpatterns = [ 33 | path('', include('saas.urls.views.request')), 34 | path('', include('saas.urls.views.noauth')), 35 | path('', include('saas.urls.views.headredirects')), 36 | path('', include('saas.urls.views.broker')), 37 | path('', include('saas.urls.views.provider')), 38 | path('', include('saas.urls.views.subscriber')), 39 | path('', include('saas.urls.views.tailredirects')), 40 | ] 41 | -------------------------------------------------------------------------------- /saas/urls/api/provider/plans.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | API URLs for a provider plans 27 | """ 28 | 29 | from .... import settings 30 | from ....api.plans import (PlanListCreateAPIView, PlanDetailAPIView) 31 | from ....compat import path 32 | 33 | 34 | urlpatterns = [ 35 | path('profile//plans/' % 36 | settings.PROFILE_URL_KWARG, 37 | PlanDetailAPIView.as_view(), name='saas_api_plan'), 38 | path('profile//plans' % 39 | settings.PROFILE_URL_KWARG, 40 | PlanListCreateAPIView.as_view(), name='saas_api_plans'), 41 | ] 42 | -------------------------------------------------------------------------------- /saas/urls/api/provider/charges.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs API for charges that can only be accessed by a provider. 27 | """ 28 | 29 | from .... import settings 30 | from ....api.charges import ChargeRefundAPIView 31 | from ....compat import path 32 | 33 | # Actually a slug. We are using here such that 34 | # it plays nice with the rules-based permission checks. 35 | urlpatterns = [ 36 | path('billing//charges//refund' % 37 | settings.PROFILE_URL_KWARG, 38 | ChargeRefundAPIView.as_view(), name='saas_api_charge_refund'), 39 | ] 40 | -------------------------------------------------------------------------------- /saas/management/commands/delete_organization.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | Command to obliterate all traces of an Organization, 27 | including Transaction history. 28 | """ 29 | 30 | from django.core.management.base import BaseCommand 31 | 32 | from saas.utils import get_organization_model 33 | 34 | 35 | class Command(BaseCommand): 36 | 37 | help = "Obliterate all traces of an Organization." 38 | 39 | def handle(self, *args, **options): 40 | #pylint: disable=too-many-locals 41 | organizations = get_organization_model().objects.filter(slug__in=args) 42 | organizations.delete() 43 | -------------------------------------------------------------------------------- /saas/urls/api/subscriber/charges.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs API for resources 27 | """ 28 | 29 | from .... import settings 30 | from ....api.charges import ChargeResourceView, EmailChargeReceiptAPIView 31 | from ....compat import path 32 | 33 | 34 | urlpatterns = [ 35 | path('billing//charges//email' % 36 | settings.PROFILE_URL_KWARG, 37 | EmailChargeReceiptAPIView.as_view(), 38 | name='saas_api_email_charge_receipt'), 39 | path('billing//charges/' % 40 | settings.PROFILE_URL_KWARG, 41 | ChargeResourceView.as_view(), name='saas_api_charge'), 42 | ] 43 | -------------------------------------------------------------------------------- /saas/urls/views/noauth.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | from ...compat import path 26 | from ...views.billing import RedeemCouponView 27 | from ...views.legal import AgreementDetailView, AgreementListView 28 | from ...views.plans import CartPlanListView 29 | 30 | 31 | urlpatterns = [ 32 | path('legal//', 33 | AgreementDetailView.as_view(), name='legal_agreement'), 34 | path('legal/', 35 | AgreementListView.as_view(), name='legal_agreement_list'), 36 | path('pricing/', 37 | CartPlanListView.as_view(), name='saas_cart_plan_list'), 38 | path('redeem/', 39 | RedeemCouponView.as_view(), name='saas_redeem_coupon'), 40 | ] 41 | -------------------------------------------------------------------------------- /saas/templates/saas/agreements/security.md: -------------------------------------------------------------------------------- 1 | {{organization.printable_name}} Security Policy 2 | ========================== 3 | 4 | Maintaining an evolving service secure requires a constant re-evaluation 5 | of risks and actions to mitigate them. 6 | 7 | {{organization.printable_name}} does a reasonable attempt at keeping your information secure 8 | using time tested [guidelines](http://en.wikipedia.org/wiki/Web_application_security). 9 | 10 | If you need to report a security vulnerability or have any questions 11 | regarding our security policy, please [e-mail our security chief](mailto:{{organization.email}}) 12 | directly. 13 | 14 | Physical Security 15 | ----------------- 16 | 17 | All of {{organization.printable_name}} online services are hosted at Amazon. As many well-known 18 | major web sites, we rely on Amazon physical security to its data centers. 19 | 20 | Backups are kept off-line off-site. 21 | 22 | Network Security 23 | ---------------- 24 | 25 | All communications to the authenticated service are done through SSL. 26 | 27 | We are running latest operating system distributions and install security 28 | patches as they become available. Since you cannot hack something that 29 | is not there, we are always on the look out to remove unnecessary packages 30 | in the first place. 31 | 32 | On top of Amazon security policies, each virtual machine is configured 33 | with its own firewall with only the minimum number of ports open. 34 | 35 | Solely the strict minimum number of {{organization.printable_name}} employees have shell 36 | access to the {{organization.printable_name}} infrastructure. 37 | 38 | We monitor all access and attempted access to the virtual machines 39 | that provides {{organization.printable_name}} service. 40 | 41 | Credit card safety 42 | ------------------ 43 | 44 | When you refill an Organization credit, we do not store any of your card 45 | information on our servers. It's handed off to [Stripe](http://stripe.com), 46 | a company dedicated to storing your sensitive data on [PCI-Compliant](http://en.wikipedia.org/wiki/Payment_Card_Industry_Data_Security_Standard) 47 | servers. 48 | -------------------------------------------------------------------------------- /saas/urls/api/cart.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs for the cart API of djaodjin saas. 27 | """ 28 | 29 | from ...api.billing import (CartItemAPIView, CartItemUploadAPIView, 30 | CouponRedeemAPIView) 31 | from ...api.plans import PricingAPIView 32 | from ...compat import path 33 | 34 | urlpatterns = [ 35 | path('pricing', 36 | PricingAPIView.as_view(), name='saas_api_pricing'), 37 | path('cart/redeem', 38 | CouponRedeemAPIView.as_view(), name='saas_api_redeem_coupon'), 39 | path('cart//upload', 40 | CartItemUploadAPIView.as_view(), name='saas_api_cart_upload'), 41 | path('cart', CartItemAPIView.as_view(), name='saas_api_cart') 42 | ] 43 | -------------------------------------------------------------------------------- /saas/urls/api/provider/roles.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | API URLs for a provider custom roles 27 | """ 28 | 29 | from .... import settings 30 | from ....api.roles import (RoleDescriptionListCreateView, 31 | RoleDescriptionDetailView) 32 | from ....compat import path 33 | 34 | 35 | urlpatterns = [ 36 | path(r'profile//roles/describe/' % 37 | settings.PROFILE_URL_KWARG, 38 | RoleDescriptionDetailView.as_view(), 39 | name='saas_api_role_description_detail'), 40 | path(r'profile//roles/describe' % 41 | settings.PROFILE_URL_KWARG, 42 | RoleDescriptionListCreateView.as_view(), 43 | name='saas_api_role_description_list'), 44 | ] 45 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, DjaoDjin inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | The following files are copyright of their respective owners and are included 26 | in the repo purely for convienience: 27 | 28 | - From http://jquery.com/ 29 | - testsite/static/vendor/jquery.js 30 | 31 | - From http://momentjs.com/ 32 | - testsite/static/vendor/moment.js 33 | 34 | - From https://angular-ui.github.io/bootstrap/ 35 | - testsite/static/vendor/ui-bootstrap-tpls.js 36 | 37 | - From https://angularjs.org/ 38 | - testsite/static/vendor/angular-animate.js 39 | - testsite/static/vendor/angular-resource.js 40 | - testsite/static/vendor/angular-route.js 41 | - testsite/static/vendor/angular-touch.js 42 | - testsite/static/vendor/angular.js 43 | 44 | - From https://vuejs.org/ 45 | - testsite/static/vendor/vue.js 46 | - testsite/static/vendor/vue2-filters.js 47 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # This pyproject.toml seems to work to build a new package 2 | # when `setuptools==67.6.1` is installed. 3 | [project] 4 | name = "djaodjin-saas" 5 | dynamic = ["version"] 6 | description = "Django application for software-as-service and subscription businesses" 7 | readme = "README.md" 8 | requires-python = ">=3.7" 9 | license = {text = "BSD-2-Clause"} 10 | keywords = ["django", "stripe", "saas", "subscriptions", "razorpay", "plans"] 11 | authors = [ 12 | {name = "The DjaoDjin Team", email = "help@djaodjin.com"} 13 | ] 14 | maintainers = [ 15 | {name = "The DjaoDjin Team", email = "help@djaodjin.com"} 16 | ] 17 | classifiers = [ 18 | "Framework :: Django", 19 | "Environment :: Web Environment", 20 | "Programming Language :: Python", 21 | "License :: OSI Approved :: BSD License" 22 | ] 23 | dependencies = [ 24 | "Django>=1.11", 25 | "django-countries>=2.1.2", 26 | "django-localflavor>=1.0", 27 | "django-phonenumber-field>=2.4.0", 28 | "djangorestframework>=3.3.1", 29 | # We need Python Markdown for django.contrib.markup. markdown2 is not enough. 30 | "Markdown>=2.4", 31 | "phonenumbers>=8.12.6", 32 | "python-dateutil>=2.2", 33 | "stripe>=2.71.0", 34 | "razorpay>=0.2.0" 35 | ] 36 | 37 | [project.urls] 38 | repository = "https://github.com/djaodjin/djaodjin-saas" 39 | documentation = "https://djaodjin-saas.readthedocs.io/" 40 | changelog = "https://github.com/djaodjin/djaodjin-saas/changelog" 41 | 42 | [build-system] 43 | requires = ["setuptools"] 44 | build-backend = "setuptools.build_meta" 45 | 46 | [tool.setuptools.packages.find] 47 | include = ["saas*"] 48 | 49 | [tool.setuptools.package-data] 50 | saas = [ 51 | 'fixtures/*', 52 | 'static/js/*.js', 53 | 'templates/notification/*.eml', 54 | 'templates/saas/*.html', 55 | 'templates/saas/agreements/*.md', 56 | 'templates/saas/billing/*.html', 57 | 'templates/saas/legal/*.html', 58 | 'templates/saas/metrics/*.html', 59 | 'templates/saas/profile/*.html', 60 | 'templates/saas/profile/plans/*.html', 61 | 'templates/saas/profile/roles/*.html', 62 | 'templates/saas/users/*.html', 63 | 'templates/saas/users/roles/*.html' 64 | ] 65 | 66 | [tool.setuptools.dynamic] 67 | version = {attr = "saas.__version__"} 68 | -------------------------------------------------------------------------------- /saas/urls/views/request.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | '''URL for the request user to sign legal agreements.''' 26 | 27 | from ... import settings 28 | from ...compat import path, re_path 29 | from ...views.legal import AgreementSignView 30 | from ...views.optins import RoleGrantAcceptView 31 | from ...views.roles import RoleImplicitGrantAcceptView 32 | 33 | 34 | urlpatterns = [ 35 | re_path(r'users/roles/accept/(?P%s)/' % ( 36 | settings.VERIFICATION_KEY_RE), 37 | RoleGrantAcceptView.as_view(), name='saas_role_grant_accept'), 38 | path('users/roles/accept/', 39 | RoleImplicitGrantAcceptView.as_view(), 40 | name='saas_role_implicit_grant_accept'), 41 | path('legal//sign/', 42 | AgreementSignView.as_view(), name='legal_sign_agreement'), 43 | ] 44 | -------------------------------------------------------------------------------- /saas/urls/api/search.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs to populate type-ahead candidate lists 27 | """ 28 | 29 | from ... import settings 30 | from ...api.accounts import (AccountsTypeaheadAPIView, ProfileAPIView, 31 | ProfilesTypeaheadAPIView, UsersTypeaheadAPIView) 32 | from ...compat import path 33 | 34 | 35 | urlpatterns = [ 36 | path('accounts/users', 37 | UsersTypeaheadAPIView.as_view(), name='saas_api_search_users'), 38 | path('accounts/profiles/' % 39 | settings.PROFILE_URL_KWARG, 40 | ProfileAPIView.as_view(), name='saas_api_search_profile'), 41 | path('accounts/profiles', 42 | ProfilesTypeaheadAPIView.as_view(), name='saas_api_search_profiles'), 43 | path('accounts', 44 | AccountsTypeaheadAPIView.as_view(), name='saas_api_search_accounts'), 45 | ] 46 | -------------------------------------------------------------------------------- /testsite/templates/accounts/register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Sign up

6 |
7 |
8 | 9 |
10 | 11 | {{form.username}} 12 |
13 |
14 | 15 | {{form.email}} 16 |
17 |
18 | 19 | {{form.email2}} 20 |
21 |
22 | 23 | {{form.new_password}} 24 |
25 |
26 | 27 | {{form.new_password2}} 28 |
29 |
30 | 31 | {{form.first_name}} 32 |
33 |
34 | 35 | {{form.last_name}} 36 |
37 |
38 | 39 | {{form.city}} 40 |
41 |
42 | 43 | {{form.country}} 44 |
45 |
46 | 47 | {{form.region}} 48 |
49 |
50 | 51 | {{form.street_address}} 52 |
53 |
54 | 55 | {{form.zip_code}} 56 |
57 | 58 |
59 |
60 |

You already have an account? Sign in 61 |

62 |
63 | 64 | {% endblock %} 65 | 66 | {% block bodyscripts %} 67 | 68 | 73 | {% endblock %} 74 | -------------------------------------------------------------------------------- /saas/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | from django.contrib import admin 26 | 27 | from .models import (AdvanceDiscount, Agreement, CartItem, Charge, ChargeItem, 28 | Coupon, RoleDescription, Plan, Signature, Subscription, Transaction) 29 | from .utils import get_organization_model, get_role_model 30 | 31 | Organization = get_organization_model() 32 | Role = get_role_model() 33 | 34 | admin.site.register(AdvanceDiscount) 35 | admin.site.register(Agreement) 36 | admin.site.register(CartItem) 37 | admin.site.register(Charge) 38 | admin.site.register(ChargeItem) 39 | admin.site.register(Coupon) 40 | admin.site.register(Organization) 41 | admin.site.register(Plan) 42 | admin.site.register(Role) 43 | admin.site.register(RoleDescription) 44 | admin.site.register(Signature) 45 | admin.site.register(Subscription) 46 | admin.site.register(Transaction) 47 | -------------------------------------------------------------------------------- /saas/urls/api/subscriber/roles.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs API for profile managers and custom roles on an Organization 27 | """ 28 | 29 | from .... import settings 30 | from ....api.roles import (RoleListAPIView, RoleByDescrListAPIView, 31 | RoleDetailAPIView) 32 | from ....compat import path, re_path 33 | 34 | 35 | urlpatterns = [ 36 | re_path(r'profile/(?P<%s>%s)/roles/(?P%s)/(?P%s)' % ( 37 | settings.PROFILE_URL_KWARG, settings.SLUG_RE, 38 | settings.SLUG_RE, settings.MAYBE_EMAIL_REGEX), 39 | RoleDetailAPIView.as_view(), name='saas_api_role_detail'), 40 | path('profile//roles/' % 41 | settings.PROFILE_URL_KWARG, 42 | RoleByDescrListAPIView.as_view(), 43 | name='saas_api_roles_by_descr'), 44 | path('profile//roles' % 45 | settings.PROFILE_URL_KWARG, 46 | RoleListAPIView.as_view(), name='saas_api_roles'), 47 | ] 48 | -------------------------------------------------------------------------------- /saas/management/commands/delete_processor_customers.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | import sys 26 | 27 | from django.core.management.base import BaseCommand 28 | 29 | from ...models import get_broker 30 | 31 | 32 | class Command(BaseCommand): 33 | help = """Delete the (customer) account associated with an organization 34 | from the payment processor service.""" 35 | args = 'regex' 36 | 37 | def add_arguments(self, parser): 38 | parser.add_argument('-n', action='store_true', dest='no_execute', 39 | default=False, help='Print but do not execute') 40 | 41 | def handle(self, *args, **options): 42 | pat = r'.*' 43 | if args: 44 | pat = args[0] 45 | processor_backend = get_broker().processor_backend 46 | for cust in processor_backend.list_customers(pat): 47 | sys.stdout.write('%s %s\n' % (str(cust.id), str(cust.description))) 48 | if not options['no_execute']: 49 | cust.delete() 50 | -------------------------------------------------------------------------------- /saas/urls/api/subscriber/billing.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs billing API for subscribers 27 | """ 28 | 29 | from .... import settings 30 | from ....api.billing import CheckoutAPIView 31 | from ....api.backend import PaymentMethodDetailAPIView 32 | from ....api.transactions import BillingsAPIView, StatementBalanceAPIView 33 | from ....compat import path 34 | 35 | 36 | urlpatterns = [ 37 | path('billing//balance' % 38 | settings.PROFILE_URL_KWARG, 39 | StatementBalanceAPIView.as_view(), name='saas_api_cancel_balance_due'), 40 | path('billing//history' % 41 | settings.PROFILE_URL_KWARG, 42 | BillingsAPIView.as_view(), name='saas_api_billings'), 43 | path('billing//card' % 44 | settings.PROFILE_URL_KWARG, 45 | PaymentMethodDetailAPIView.as_view(), name='saas_api_card'), 46 | path('billing//checkout' % 47 | settings.PROFILE_URL_KWARG, 48 | CheckoutAPIView.as_view(), name='saas_api_checkout'), 49 | ] 50 | -------------------------------------------------------------------------------- /saas/templates/saas/metrics/coupons.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base_dashboard.html" %} 2 | 3 | {% block saas_content %} 4 | 5 |
6 | Download .CSV 7 |

8 | {{coupon.code}} was used [[totalItems]] time[[totalItems > 0 ? "s" : ""]]. 9 |

10 | 11 | 12 | 13 | 15 | 17 | 19 | 20 | 21 | 22 | 24 | 27 | 28 | 34 | 38 | 39 | 40 | 41 |
User Plan Used at
25 |

No use of {{coupon.code}} coupon

26 |
35 | [[item.user.full_name]] 37 | [[item.plan]][[item.created_at]]
42 | {% include "saas/_paginator.html" %} 43 |
44 |
45 | {% endblock %} 46 | 47 | -------------------------------------------------------------------------------- /testsite/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SaaS Testsite 7 | 8 | {% if csrf_token %} 9 | 10 | 25 | {% endif %} 26 | {% block localheader %}{% endblock %} 27 | 28 | 29 | {% block menubar %} 30 |
31 | Home 32 | | Pricing 33 | {% if request|is_authenticated %} 34 | | {{request.user.username}} 35 | | Sign Out 36 | {% else %} 37 | | Sign In 38 | {% endif %} 39 |
40 | {% endblock %} 41 |
42 |
43 | {% for message in request|messages %} 44 |
45 | 46 |
{{message|safe}}
47 |
48 | {% endfor %} 49 | {% if form %} 50 | {% for message in form|messages %} 51 |
52 | 53 |
{{message}}
54 |
55 | {% endfor %} 56 | {% endif %} 57 | 62 |
63 |
64 | {% block content %}{% endblock %} 65 | {% block footer %}{% endblock %} 66 | 67 | {% block bodyscripts %}{% endblock %} 68 | 69 | -------------------------------------------------------------------------------- /saas/management/commands/compile_stats.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """Command for the cron job. Daily statistics""" 26 | 27 | import datetime 28 | 29 | from django.core.management.base import BaseCommand 30 | from django.contrib.auth import get_user_model 31 | 32 | from saas.utils import get_organization_model 33 | 34 | 35 | class Command(BaseCommand): 36 | """Daily usage for the service""" 37 | help = 'Print daily usage' 38 | 39 | def handle(self, *args, **options): 40 | end_period = datetime.datetime.now() 41 | start_period = end_period - datetime.timedelta(days=30) 42 | self.stdout.write('from %s to %s\n' % (start_period, end_period)) 43 | for user in get_user_model().objects.filter( 44 | date_joined__gt=start_period): 45 | self.stdout.write('%s %s %s\n' % (str(user.date_joined), 46 | user.username, user.email)) 47 | 48 | self.stdout.write('\n') 49 | for organization in get_organization_model().objects.filter( 50 | created_at__gt=start_period): 51 | self.stdout.write('%s %s\n' 52 | % (organization.created_at, organization)) 53 | -------------------------------------------------------------------------------- /saas/urls/api/users.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs for API related to users accessible by. 27 | """ 28 | 29 | from ... import settings 30 | from ...api.roles import (AccessibleByListAPIView, AccessibleDetailAPIView, 31 | RoleAcceptAPIView, AccessibleByDescrListAPIView, UserProfileListAPIView) 32 | from ...compat import path, re_path 33 | 34 | urlpatterns = [ 35 | re_path( 36 | r'^users/(?P%s)/accessibles/accept/(?P%s)' % ( 37 | settings.SLUG_RE, settings.VERIFICATION_KEY_RE), 38 | RoleAcceptAPIView.as_view(), name='saas_api_accessibles_accept'), 39 | path('users//accessibles//' % 40 | settings.PROFILE_URL_KWARG, 41 | AccessibleDetailAPIView.as_view(), name='saas_api_accessible_detail'), 42 | path('users//accessibles/', 43 | AccessibleByDescrListAPIView.as_view(), 44 | name='saas_api_accessibles_by_descr'), 45 | path('users//accessibles', 46 | AccessibleByListAPIView.as_view(), name='saas_api_accessibles'), 47 | path('users//profiles', 48 | UserProfileListAPIView.as_view(), name='saas_api_user_profiles'), 49 | ] 50 | -------------------------------------------------------------------------------- /saas/urls/views/subscriber/billing/info.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs responding to GET requests with billing history. 27 | """ 28 | 29 | from ..... import settings 30 | from .....compat import path 31 | from .....views.billing import ChargeReceiptView, BillingStatementView 32 | from .....views.download import BillingStatementDownloadView 33 | 34 | try: 35 | from .....views.extra import PrintableChargeReceiptView 36 | urlpatterns = [ 37 | path('billing//receipt//printable/' % 38 | settings.PROFILE_URL_KWARG, 39 | PrintableChargeReceiptView.as_view(), 40 | name='saas_printable_charge_receipt'), 41 | ] 42 | except ImportError: 43 | urlpatterns = [] 44 | 45 | urlpatterns += [ 46 | path('billing//receipt//' % 47 | settings.PROFILE_URL_KWARG, 48 | ChargeReceiptView.as_view(), name='saas_charge_receipt'), 49 | path('billing//history/download' % 50 | settings.PROFILE_URL_KWARG, 51 | BillingStatementDownloadView.as_view(), name='saas_statement_download'), 52 | path('billing//history/' % 53 | settings.PROFILE_URL_KWARG, 54 | BillingStatementView.as_view(), name='saas_billing_info'), 55 | ] 56 | -------------------------------------------------------------------------------- /saas/urls/api/headbroker.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs API for resources available typically only to the broker platform. 27 | """ 28 | 29 | from ... import settings 30 | from ...api.balances import (BalanceLineListAPIView, BrokerBalancesAPIView, 31 | BalanceLineDetailAPIView) 32 | from ...api.charges import ChargeListAPIView 33 | from ...api.transactions import TransactionListAPIView 34 | from ...api.users import RegisteredAPIView 35 | from ...compat import path, re_path 36 | 37 | 38 | urlpatterns = [ 39 | path('billing/transactions', 40 | TransactionListAPIView.as_view(), name='saas_api_transactions'), 41 | path('billing/charges', ChargeListAPIView.as_view(), 42 | name='saas_api_charges'), 43 | re_path(r'^metrics/balances/(?P%s)/lines/(?P\d+)' % ( 44 | settings.SLUG_RE), BalanceLineDetailAPIView.as_view(), 45 | name='saas_api_balance_line'), 46 | path('metrics/balances//lines', 47 | BalanceLineListAPIView.as_view(), name='saas_api_balance_lines'), 48 | path('metrics/balances/', 49 | BrokerBalancesAPIView.as_view(), name='saas_api_broker_balances'), 50 | path('metrics/registered', 51 | RegisteredAPIView.as_view(), name='saas_api_registered'), 52 | ] 53 | -------------------------------------------------------------------------------- /saas/urls/views/subscriber/profile.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | Urls for profiles 27 | """ 28 | 29 | from .... import settings 30 | from ....compat import path, re_path 31 | from ....views.profile import (RoleDetailView, RoleListView, 32 | OrganizationProfileView, SubscriptionListView) 33 | from ....views.optins import SubscriptionGrantAcceptView 34 | 35 | urlpatterns = [ 36 | path('profile//roles//' % 37 | settings.PROFILE_URL_KWARG, 38 | RoleDetailView.as_view(), name='saas_role_detail'), 39 | path('profile//roles/' % 40 | settings.PROFILE_URL_KWARG, 41 | RoleListView.as_view(), name='saas_role_list'), 42 | re_path(r'profile/(?P<%s>%s)/subscriptions/accept/'\ 43 | '(?P%s)/' % ( 44 | settings.PROFILE_URL_KWARG, settings.SLUG_RE, 45 | settings.VERIFICATION_KEY_RE), 46 | SubscriptionGrantAcceptView.as_view(), 47 | name='subscription_grant_accept'), 48 | path('profile//subscriptions/' % 49 | settings.PROFILE_URL_KWARG, 50 | SubscriptionListView.as_view(), name='saas_subscription_list'), 51 | path('profile//contact/' % 52 | settings.PROFILE_URL_KWARG, 53 | OrganizationProfileView.as_view(), name='saas_organization_profile'), 54 | ] 55 | -------------------------------------------------------------------------------- /testsite/templatetags/testsite_tags.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | import json 26 | 27 | from django import template 28 | from django.contrib.messages.api import get_messages 29 | from django.forms import BaseForm 30 | from django.utils.safestring import mark_safe 31 | from saas.compat import is_authenticated as is_authenticated_base, reverse, six 32 | from saas.templatetags.saas_tags import attached_organization 33 | 34 | 35 | register = template.Library() 36 | 37 | 38 | @register.filter() 39 | def is_authenticated(request): 40 | return is_authenticated_base(request) 41 | 42 | 43 | @register.filter() 44 | def messages(obj): 45 | """ 46 | Messages to be displayed to the current session. 47 | """ 48 | if isinstance(obj, BaseForm): 49 | return obj.non_field_errors() 50 | return get_messages(obj) 51 | 52 | 53 | @register.filter 54 | def to_json(value): 55 | if isinstance(value, six.string_types): 56 | return value 57 | return mark_safe(json.dumps(value)) 58 | 59 | 60 | @register.filter() 61 | def url_profile(request): 62 | if is_authenticated_base(request): 63 | organization = attached_organization(request.user) 64 | if organization: 65 | return reverse('saas_organization_profile', args=(organization,)) 66 | return reverse('users_profile', args=(request.user,)) 67 | return None 68 | -------------------------------------------------------------------------------- /docs/orders.rst: -------------------------------------------------------------------------------- 1 | Placing on Order 2 | ================ 3 | 4 | A ``Subscription`` is created when a ``Plan`` is selected and paid for. 5 | As simple as it sounds, there are many variants to implement the previous 6 | sentence. 7 | 8 | Basic Pipeline 9 | ^^^^^^^^^^^^^^ 10 | In the most basic pipeline, a user becomes a subscriber in 2 steps: 11 | 12 | 1. Click a ``Plan`` on the /pricing/ page 13 | 2. Submit credit card information 14 | 15 | Pipeline with Multiple Periods Paid in Advance 16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 17 | It is always better to receive more cash up-front so an intermediate step 18 | is often introduced to enable to pre-pay multiple periods in advance at 19 | a discount. 20 | 21 | 1. Click a ``Plan`` on the /pricing/ page 22 | 2. Pick the number of periods paid in advance 23 | 3. Submit credit card information 24 | 25 | Pipeline with Multiple Products in Shopping Cart 26 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 27 | A growing business often offers multiple products (i.e. ``Plan``) that are 28 | cross-saled to new and existing customers. In that case, the /pricing/ page 29 | is replaced by a more complex catalog. Cart and checkout concepts appear. 30 | 31 | 1. Add multiple ``Plan`` to a user cart 32 | 2. Click a checkout button 33 | 3. Submit credit card information 34 | 35 | .. _group_buy: 36 | 37 | Pipeline to Bulk Subscribe Third-parties 38 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 39 | Software-as-a-Service that target businesses (B2B) and/or other kind 40 | of structured groups almost always require one entity to pay for subscriptions 41 | on behalf of users that belong to it. This can be implemented through 42 | managers (or custom roles) to the subscribed entity 43 | (see :doc:`Security `) or through the entity buying multiple 44 | subscriptions in bluk, on behalf of its users. The later case requires 45 | an extra step to subscribe those third parties. 46 | 47 | 1. Click a ``Plan`` on the /pricing/ page 48 | 2. Enter email address of users to subscribe 49 | 3. Submit credit card information 50 | 51 | Full Pipeline 52 | ^^^^^^^^^^^^^ 53 | 54 | Of course, all of the above cases can be combined together, which leads 55 | to a full pipeline as such: 56 | 57 | .. image:: order-pipeline.* 58 | 59 | 1. Add multiple ``Plan`` to a user cart 60 | 2. Click a checkout button 61 | 3. Pick the number of periods paid in advance 62 | 4. Enter email address of users to subscribe 63 | 5. Submit credit card information 64 | 65 | 66 | Django Views 67 | ------------ 68 | 69 | .. automodule:: saas.views.billing 70 | 71 | .. image:: order-views.* 72 | 73 | .. autoclass:: saas.views.billing.CartBaseView 74 | 75 | .. autoclass:: saas.views.billing.BalanceView 76 | 77 | .. autoclass:: saas.views.billing.CartView 78 | 79 | 80 | -------------------------------------------------------------------------------- /saas/docs.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, Djaodjin Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | #pylint:disable=unused-argument,unused-import 26 | 27 | try: 28 | from drf_yasg.openapi import Response as OpenAPIResponse, Parameter, IN_PATH 29 | from drf_yasg.utils import no_body, swagger_auto_schema 30 | except ImportError: 31 | from functools import wraps 32 | from .compat import available_attrs 33 | 34 | IN_PATH = 0 35 | 36 | class no_body(object):#pylint:disable=invalid-name 37 | pass 38 | 39 | def swagger_auto_schema(function=None, **kwargs): 40 | """ 41 | Dummy decorator when drf_yasg is not present. 42 | """ 43 | def decorator(view_func): 44 | @wraps(view_func, assigned=available_attrs(view_func)) 45 | def _wrapped_view(request, *args, **kwargs): 46 | return view_func(request, *args, **kwargs) 47 | return _wrapped_view 48 | 49 | if function: 50 | return decorator(function) 51 | return decorator 52 | 53 | class OpenAPIResponse(object): 54 | """ 55 | Dummy response object to document API. 56 | """ 57 | def __init__(self, *args, **kwargs): 58 | pass 59 | 60 | class Parameter(object): 61 | """ 62 | Dummy object to document API. 63 | """ 64 | def __init__(self, *args, **kwargs): 65 | pass 66 | -------------------------------------------------------------------------------- /saas/templates/saas/base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block localheader %} 4 | {% if STRIPE_PUB_KEY %} 5 | {% if STRIPE_INTENT_SECRET %} 6 | 7 | {% else %} 8 | 9 | {% endif %} 10 | {% endif %} 11 | {% endblock %} 12 | 13 | {% block content %} 14 |
15 |
16 | {% block sidebar %} 17 | {% include "saas/_sidebar.html" %} 18 | {% endblock %} 19 |
20 |
21 | {% block saas_content %}{% endblock %} 22 |
23 |
24 | {% endblock %} 25 | 26 | {% block bodyscripts %} 27 | 28 | 29 | 30 | 31 | 32 | 78 | {% block saas_bodyscripts %}{% endblock %} 79 | {% endblock %} 80 | 81 | -------------------------------------------------------------------------------- /saas/templates/saas/billing/index.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base_dashboard.html" %} 2 | 3 | {% block saas_content %} 4 | 5 |
6 |
8 |

Credit Card

9 | Update 10 |
11 |
card
12 |
13 | [[last4]] 14 |
15 |
16 |
17 |
expires
18 |
19 | [[exp_date]] 20 |
21 |
22 |
23 |
24 |

Balance

25 |
Please wait...
26 |
27 | {% if urls.organization.api_cancel_balance_due %} 28 | 30 | {% endif %} 31 | {% if urls.organization.vtcharge %} 32 | one-time charge 33 | {% endif %} 34 |
35 |
[[items.balance_amount]] [[items.balance_unit]]
36 |
37 | Balance due(pay now) 38 |
39 |
Balance Credits
40 |
41 |
42 | {% if urls.organization.api_cancel_balance_due %} 43 | 44 |
45 |
46 |

Cancel balance due

47 |

48 | Are you sure you would like to cancel the whole balance of [[items.balance_amount]] [[items.balance_unit]] ? 49 |

50 |
51 | 52 | 53 |
54 |
55 |
56 | 57 | {% endif %} 58 |
59 | {% include "saas/_transactions.html" %} 60 |
61 |
62 | {% endblock %} 63 | -------------------------------------------------------------------------------- /saas/templates/saas/billing/bank.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base.html" %} 2 | 3 | {% block saas_title %} 4 | {{organization.printable_name}} Payment Processor 5 | {% endblock %} 6 | 7 | {% block saas_content %} 8 |
9 | {% if bank_name and bank_name != 'N/A' %} 10 |
11 |
12 |

Connected Processor

13 |
14 |

15 | You will be able to create charges and payout funds with this processor. 16 |

17 |
18 |
19 |
Institution
20 |
21 | {{bank_name}} 22 |
23 |
24 |
25 |
Account Number
26 |
27 | {{last4}} 28 |
29 |
30 |
31 | {% if urls.provider.deauthorize_processor %} 32 |
33 | 34 | 35 | 36 |
37 | {% endif %} 38 |
39 |
40 |
41 | {% else %} 42 | {% if urls.authorize_processor %} 43 |
44 |
45 |

Stripe Account

46 |
47 |
Connect your Stripe account
48 |

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 |
60 |
61 |
62 | {% endif %} 63 | {% if not urls.authorize_processor %} 64 | {# dealing with a misconfigured platform #} 65 |
66 |
67 |

Configure Settings

68 |
69 |

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 |
81 |
82 |
83 | {% endif %} 84 | {% endif %} 85 |
86 | {% endblock %} 87 | -------------------------------------------------------------------------------- /saas/urls/api/subscriber/profile.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs API for profile resources (managers, custom roles and subscriptions) 27 | """ 28 | 29 | from .... import settings 30 | from ....api.organizations import ( 31 | OrganizationDetailAPIView, OrganizationPictureAPIView) 32 | from ....api.subscriptions import (ExpiredSubscriptionsAPIView, 33 | SubscriptionDetailAPIView, SubscribedSubscriptionListAPIView) 34 | from ....compat import path 35 | 36 | 37 | urlpatterns = [ 38 | path('profile//subscriptions/expired' % 39 | settings.PROFILE_URL_KWARG, 40 | ExpiredSubscriptionsAPIView.as_view(), 41 | name='saas_api_subscriptions_expired'), 42 | path('profile//subscriptions/' % 43 | settings.PROFILE_URL_KWARG, 44 | SubscriptionDetailAPIView.as_view(), 45 | name='saas_api_subscription_detail'), 46 | path('profile//subscriptions' % 47 | settings.PROFILE_URL_KWARG, 48 | SubscribedSubscriptionListAPIView.as_view(), 49 | name='saas_api_subscription_list'), 50 | path('profile//picture' % 51 | settings.PROFILE_URL_KWARG, 52 | OrganizationPictureAPIView.as_view(), 53 | name='saas_api_organization_picture'), 54 | path('profile/' % 55 | settings.PROFILE_URL_KWARG, 56 | OrganizationDetailAPIView.as_view(), name='saas_api_organization'), 57 | ] 58 | -------------------------------------------------------------------------------- /saas/urls/views/tailredirects.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | Redirects that need to appear after `urls.views.provider` and 27 | `urls.views.subscriber` 28 | """ 29 | 30 | from django.views.generic import RedirectView 31 | 32 | from ... import settings 33 | from ...compat import path 34 | from ...views import OrganizationRedirectView, ProviderRedirectView 35 | 36 | urlpatterns = [ 37 | path(r'billing//' % 38 | settings.PROFILE_URL_KWARG, 39 | RedirectView.as_view(permanent=False, pattern_name='saas_billing_info'), 40 | name='saas_billing_redirect'), 41 | path('billing/', 42 | OrganizationRedirectView.as_view(pattern_name='saas_billing_info'), 43 | name='saas_billing_base'), 44 | 45 | path(r'profile//' % 46 | settings.PROFILE_URL_KWARG, 47 | RedirectView.as_view(permanent=False, 48 | pattern_name='saas_organization_profile'), 49 | name='saas_profile_redirect'), 50 | path('profile/', OrganizationRedirectView.as_view( 51 | pattern_name='saas_organization_profile'), 52 | name='saas_profile'), 53 | 54 | path('metrics/', 55 | ProviderRedirectView.as_view(pattern_name='saas_metrics_summary'), 56 | name='saas_provider_metrics_summary'), 57 | 58 | path('provider/', 59 | ProviderRedirectView.as_view(pattern_name='saas_organization_profile'), 60 | name='saas_provider_profile'), 61 | ] 62 | -------------------------------------------------------------------------------- /saas/urls/api/provider/billing.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs API for provider resources related to billing 27 | """ 28 | 29 | from .... import settings 30 | from ....api.backend import RetrieveBankAPIView 31 | from ....api.coupons import CouponListCreateAPIView, CouponDetailAPIView 32 | from ....api.transactions import (ReceivablesListAPIView, 33 | TransferListAPIView, ImportTransactionsAPIView) 34 | from ....compat import path 35 | 36 | urlpatterns = [ 37 | path('billing//bank' % 38 | settings.PROFILE_URL_KWARG, 39 | RetrieveBankAPIView.as_view(), name='saas_api_bank'), 40 | path('billing//coupons/' % 41 | settings.PROFILE_URL_KWARG, 42 | CouponDetailAPIView.as_view(), name='saas_api_coupon_detail'), 43 | path('billing//coupons' % 44 | settings.PROFILE_URL_KWARG, 45 | CouponListCreateAPIView.as_view(), name='saas_api_coupon_list'), 46 | path('billing//receivables' % 47 | settings.PROFILE_URL_KWARG, 48 | ReceivablesListAPIView.as_view(), name='saas_api_receivables'), 49 | path('billing//transfers/import' % 50 | settings.PROFILE_URL_KWARG, 51 | ImportTransactionsAPIView.as_view(), 52 | name='saas_api_import_transactions'), 53 | path('billing//transfers' % 54 | settings.PROFILE_URL_KWARG, 55 | TransferListAPIView.as_view(), name='saas_api_transfer_list'), 56 | ] 57 | -------------------------------------------------------------------------------- /saas/templates/saas/billing/cart.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base.html" %} 2 | 3 | {% block content %} 4 |

{% block order_title %}Place Order{% endblock %}

5 |
6 | {% block order_head %} 7 |
8 |
9 | 10 |
11 | 14 |
15 |
16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 | {% endblock %} 24 |
25 |
26 | 27 | {% if invoicables %} 28 | {% include "saas/_invoiceables.html" %} 29 | {% else %} 30 | {% block no_invoicables %} 31 | Your subscription cart is empty. 32 | {% endblock %} 33 | {% endif %} 34 | {% block order_footer %} 35 | {% endblock order_footer %} 36 | {% block order_card %} 37 | {% if RAZORPAY_PUB_KEY %} 38 | {% include "saas/_razorpay_checkout.html" %} 39 |
40 | 41 |
42 | {% elif STRIPE_PUB_KEY %} 43 | {% include "saas/_card_use.html" %} 44 | {% else %} 45 |

46 | Either variables RAZORPAY_PUB_KEY or STRIPE_PUB_KEY must be defined. 47 |

48 | {% endif %} 49 | {% endblock %} 50 |
51 |
52 |
53 | {% endblock %} 54 | 55 | {% block saas_bodyscripts %} 56 | 57 | 77 | {% endblock %} 78 | -------------------------------------------------------------------------------- /saas/urls/views/provider/billing.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs related to provider bank account information. 27 | """ 28 | 29 | from .... import settings 30 | from ....compat import path 31 | from ....views.download import TransferDownloadView 32 | from ....views.billing import (ProcessorAuthorizeView, ProcessorDeAuthorizeView, 33 | CouponListView, ImportTransactionsView, TransferListView, WithdrawView) 34 | 35 | 36 | urlpatterns = [ 37 | path('billing//bank/deauthorize/' % 38 | settings.PROFILE_URL_KWARG, 39 | ProcessorDeAuthorizeView.as_view(), name='saas_deauthorize_processor'), 40 | path('billing//bank/' % 41 | settings.PROFILE_URL_KWARG, 42 | ProcessorAuthorizeView.as_view(), name='saas_update_bank'), 43 | path('billing//coupons/' % 44 | settings.PROFILE_URL_KWARG, 45 | CouponListView.as_view(), name='saas_coupon_list'), 46 | path('billing//transfers/download' % 47 | settings.PROFILE_URL_KWARG, 48 | TransferDownloadView.as_view(), name='saas_transfers_download'), 49 | path('billing//transfers/import/' % 50 | settings.PROFILE_URL_KWARG, 51 | ImportTransactionsView.as_view(), name='saas_import_transactions'), 52 | path('billing//transfers/withdraw/' % 53 | settings.PROFILE_URL_KWARG, 54 | WithdrawView.as_view(), name='saas_withdraw_funds'), 55 | path('billing//transfers/' % 56 | settings.PROFILE_URL_KWARG, 57 | TransferListView.as_view(), name='saas_transfer_info'), 58 | ] 59 | -------------------------------------------------------------------------------- /saas/urls/views/subscriber/billing/payment.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | URLs updating processing information and inserting transactions 27 | through POST requests. 28 | """ 29 | 30 | from ..... import settings 31 | from .....compat import path 32 | from .....views.billing import (CartPeriodsView, CartSeatsView, 33 | CardUpdateView, CartView, BalanceView, CheckoutView) 34 | 35 | 36 | urlpatterns = [ 37 | path('billing//checkout/' % 38 | settings.PROFILE_URL_KWARG, 39 | CheckoutView.as_view(), name='saas_checkout'), 40 | path('billing//cart-seats/' % 41 | settings.PROFILE_URL_KWARG, 42 | CartSeatsView.as_view(), name='saas_cart_seats'), 43 | path('billing//cart-periods/' % 44 | settings.PROFILE_URL_KWARG, 45 | CartPeriodsView.as_view(), name='saas_cart_periods'), 46 | path('billing//cart/' % 47 | settings.PROFILE_URL_KWARG, 48 | CartView.as_view(), name='saas_organization_cart'), 49 | path('billing//card/' % 50 | settings.PROFILE_URL_KWARG, 51 | CardUpdateView.as_view(), name='saas_update_card'), 52 | # Implementation Note: (not ) such that 53 | # the required_manager decorator does not raise a PermissionDenied 54 | # for a plan is subscribed to. 55 | path('billing//balance//' % 56 | settings.PROFILE_URL_KWARG, 57 | BalanceView.as_view(), name='saas_subscription_balance'), 58 | path('billing//balance/' % 59 | settings.PROFILE_URL_KWARG, 60 | BalanceView.as_view(), name='saas_organization_balance'), 61 | ] 62 | -------------------------------------------------------------------------------- /saas/urls/views/broker.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | """ 26 | Urls specific to the hosting site (i.e. broker). 27 | """ 28 | 29 | from ... import settings 30 | from ...compat import path, re_path 31 | from ...views.metrics import BalancesView 32 | from ...views.billing import AllTransactions, ChargeListView, VTChargeView 33 | from ...views.download import (BalancesDownloadView, ChargesDownloadView, 34 | RegisteredDownloadView, TransactionDownloadView) 35 | 36 | urlpatterns = [ 37 | path('billing/charges/download/', 38 | ChargesDownloadView.as_view(), name='saas_charges_download'), 39 | path('billing/charges/', 40 | ChargeListView.as_view(), name='saas_charges'), 41 | re_path(r'billing/transactions/((?P%s)/)?download', 42 | TransactionDownloadView.as_view(), 43 | name='saas_transactions_download'), 44 | re_path(r'^billing/transactions/((?P%s)/)?' % 45 | settings.SELECTOR_RE, 46 | AllTransactions.as_view(), name='saas_broker_transactions'), 47 | # Organization refers to the subscriber in the following URL pattern. 48 | re_path(r'^billing/(?P%s)/vtcharge/' % settings.SLUG_RE, 49 | VTChargeView.as_view(), name='saas_organization_vtcharge'), 50 | re_path(r'^metrics/balances/(?P%s)/((?P\d\d\d\d)/)?download' 51 | % settings.SLUG_RE, 52 | BalancesDownloadView.as_view(), name='saas_balances_download'), 53 | re_path(r'^metrics/balances/(?P%s)/((?P\d\d\d\d)/)?' 54 | % settings.SLUG_RE, 55 | BalancesView.as_view(), name='saas_balance'), 56 | path('metrics/registered/download', 57 | RegisteredDownloadView.as_view(), 58 | name='saas_subscriber_pipeline_download_registered'), 59 | ] 60 | -------------------------------------------------------------------------------- /docs/security.rst: -------------------------------------------------------------------------------- 1 | Flexible Role-based Access Control 2 | ================================== 3 | 4 | Business logic sometimes dictates that a provider has minimal access the billing 5 | profile of a customer and sometimes a provider must be able to update the credit 6 | card associated to a customer while on a phone call with that customer. 7 | 8 | In order to support multiple usage patterns and security constraints, 9 | authorization is not embeded into the djaodjin-saas logic but rather 10 | implemented as URL decorators. It is the responsability of the developper 11 | to associate decorators to URLs as dictated by the business requirements. 12 | 13 | The security framework defines a generic ``RoleDescription`` whose purpose 14 | is to define a role a ``User`` has on an ``Organization`` (see 15 | :doc:`grant and request roles `). 16 | 17 | A organization-agnostic manager role always exists and helps with bootstrapping 18 | the security policies. In most setups a second role, for example, a contributor 19 | role is implemented. 20 | Typically *manager* have full access to an Organization while *contributor* 21 | are restricted to read-only permissions. 22 | 23 | Examples 24 | -------- 25 | 26 | Let's say you want to give POST access to contributors on the refund API, 27 | you would write the following in your urls.py: 28 | 29 | .. code-block:: python 30 | 31 | from urldecorators import url 32 | from saas.api.charges import ChargeRefundAPIView 33 | 34 | urlpatterns = [ 35 | 36 | url(r'^billing/charges/(?P[a-zA-Z0-9_\-\+\.]+)/refund/', 37 | ChargeRefundAPIView.as_view(), 38 | name='saas_api_charge_refund', 39 | decorators=['saas.decorators.requires_provider_weak']), 40 | ] 41 | 42 | The previous example uses `django-urldecorators`_ and a 43 | ``saas.decorators.requires_provider_weak decorator``. 44 | 45 | The ``saas.urls`` module has been split in "common" set of functionalities 46 | such that in many cases you can decorate each include() with an appropriate 47 | decorator instead of each URL one by one. (ex: `testsite/urls.py`_) 48 | 49 | A blog post on `Django Rest Framework, AngularJS and permissions`_ 50 | might also be a useful read. 51 | 52 | 53 | Decorators Available 54 | -------------------- 55 | 56 | .. automodule:: saas.decorators 57 | :members: requires_agreement, requires_paid_subscription, requires_direct, 58 | requires_provider, requires_self_provider 59 | 60 | 61 | .. rubric:: Design Note 62 | 63 | We used to decorate the saas views with the "appropriate" decorators, 64 | except in many projects appropriate had a different meaning. It turns out 65 | that the access control logic is better left to be configured 66 | in the site URLConf through extensions like `django-urldecorators`_. 67 | This is not only more flexible but also make security audits a lot easier. 68 | 69 | .. _django-urldecorators: https://github.com/mila/django-urldecorators 70 | 71 | .. _testsite/urls.py: https://github.com/djaodjin/djaodjin-saas/blob/master/testsite/urls.py 72 | 73 | .. _Django Rest Framework, AngularJS and permissions: http://djaodjin.com/blog/drf-angularjs-access-control.blog.html 74 | -------------------------------------------------------------------------------- /saas/api/serializers_overrides.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | """ 25 | Default implementation when not overriden 26 | """ 27 | 28 | from __future__ import unicode_literals 29 | 30 | from django.core import validators 31 | from django.contrib.auth import get_user_model 32 | from rest_framework import serializers 33 | 34 | from ..compat import gettext_lazy as _ 35 | 36 | 37 | class UserSerializer(serializers.ModelSerializer): 38 | 39 | # Only way I found out to remove the ``UniqueValidator``. We are not 40 | # interested to create new instances here. 41 | slug = serializers.CharField(source='username', validators=[ 42 | validators.RegexValidator(r'^[\w.@+-]+$', _('Enter a valid username.'), 43 | 'invalid')], 44 | help_text=_("Effectively the username. The variable is named `slug`"\ 45 | " such that front-end code can be re-used between Organization"\ 46 | " and User records.")) 47 | email = serializers.EmailField(read_only=True, 48 | help_text=_("E-mail address for the user")) 49 | created_at = serializers.DateTimeField(source='date_joined', required=False, 50 | help_text=_("Date/time of creation (in ISO format)")) 51 | last_login = serializers.DateTimeField(required=False, 52 | help_text=_("Date/time of last login (in ISO format)")) 53 | full_name = serializers.SerializerMethodField( 54 | help_text=_("Full name for the contact (effectively first name"\ 55 | " followed by last name)")) 56 | 57 | class Meta: 58 | model = get_user_model() 59 | fields = ('slug', 'email', 'full_name', 'created_at', 'last_login') 60 | read_only = ('full_name', 'created_at', 'last_login') 61 | 62 | @staticmethod 63 | def get_full_name(obj): 64 | return obj.get_full_name() 65 | -------------------------------------------------------------------------------- /saas/metrics/subscriptions.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | import logging 26 | 27 | from django.db.models import F, Min, Max 28 | 29 | from ..models import Subscription 30 | from ..utils import convert_dates_to_utc 31 | from .base import month_periods 32 | 33 | 34 | LOGGER = logging.getLogger(__name__) 35 | 36 | 37 | def active_subscribers(plan, from_date=None, tz=None): 38 | """ 39 | List of active subscribers for a *plan*. 40 | """ 41 | #pylint:disable=invalid-name 42 | values = [] 43 | for end_period in convert_dates_to_utc(month_periods( 44 | from_date=from_date, tz=tz)): 45 | values.append([end_period, 46 | Subscription.objects.active_at(end_period, plan=plan).count()]) 47 | return values 48 | 49 | 50 | def churn_subscribers(plan=None, from_date=None, tz=None): 51 | """ 52 | List of churn subscribers from the previous period for a *plan*. 53 | """ 54 | #pylint:disable=invalid-name 55 | values = [] 56 | dates = convert_dates_to_utc(month_periods(13, from_date, tz=tz)) 57 | start_period = dates[0] 58 | kwargs = {} 59 | if plan: 60 | kwargs = {'plan': plan} 61 | for end_period in dates[1:]: 62 | values.append([end_period, Subscription.objects.churn_in_period( 63 | start_period, end_period, **kwargs).count()]) 64 | start_period = end_period 65 | return values 66 | 67 | 68 | def subscribers_age(provider=None): 69 | if provider: 70 | queryset = Subscription.objects.filter(plan__organization=provider) 71 | else: 72 | queryset = Subscription.objects.all() 73 | return queryset.values(slug=F('organization__slug')).annotate( 74 | created_at=Min('created_at'), ends_at=Max('ends_at')).order_by( 75 | 'organization__slug') 76 | -------------------------------------------------------------------------------- /testsite/jinja2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, DjaoDjin inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 17 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 20 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | from __future__ import absolute_import 26 | 27 | from django.conf import settings 28 | import django.template.defaultfilters 29 | from django.utils.translation import gettext, ngettext 30 | from jinja2.sandbox import SandboxedEnvironment as Jinja2Environment 31 | import saas.templatetags.saas_tags 32 | 33 | import testsite.templatetags.testsite_tags 34 | 35 | 36 | def environment(**options): 37 | options['extensions'] = ['jinja2.ext.i18n'] 38 | 39 | env = Jinja2Environment(**options) 40 | 41 | # i18n 42 | env.install_gettext_callables(gettext=gettext, ngettext=ngettext, 43 | newstyle=True) 44 | 45 | # Generic filters to render pages 46 | env.filters['is_authenticated'] = \ 47 | testsite.templatetags.testsite_tags.is_authenticated 48 | env.filters['iteritems'] = saas.templatetags.saas_tags.iteritems 49 | env.filters['isoformat'] = saas.templatetags.saas_tags.isoformat 50 | env.filters['messages'] = testsite.templatetags.testsite_tags.messages 51 | env.filters['pluralize'] = django.template.defaultfilters.pluralize 52 | env.filters['to_json'] = testsite.templatetags.testsite_tags.to_json 53 | env.filters['url_profile'] = testsite.templatetags.testsite_tags.url_profile 54 | 55 | # Specific to SaaS 56 | env.filters['humanize_money'] = saas.templatetags.saas_tags.humanize_money 57 | env.filters['humanize_period'] = saas.templatetags.saas_tags.humanize_period 58 | env.filters['date_in_future'] = saas.templatetags.saas_tags.date_in_future 59 | env.filters['md'] = saas.templatetags.saas_tags.md 60 | env.filters['describe'] = saas.templatetags.saas_tags.describe 61 | 62 | env.globals.update({ 63 | 'VUEJS': (settings.JS_FRAMEWORK == 'vuejs'), 64 | 'DATETIME_FORMAT': "MMM dd, yyyy", 65 | }) 66 | 67 | return env 68 | -------------------------------------------------------------------------------- /saas/templates/saas/metrics/base.html: -------------------------------------------------------------------------------- 1 | {% extends "saas/base_dashboard.html" %} 2 | 3 | {% block saas_title %} 4 | {{title}} for {{organization.printable_name}} 5 | {% endblock %} 6 | 7 | {% block saas_content %} 8 | 9 |
10 |
11 |
12 | To 13 | 18 | 25 |
26 |
27 |
32 |

[[table.key]]

36 |
37 |
38 | 39 |
40 | Loading ... 41 |
42 |
43 | 44 | 45 | 46 | 48 | 49 | 53 | 58 | 60 | 64 | 65 | {% block metrics_extra_data %} 66 | {% endblock %} 67 |
[[ col[0] ]]
54 | [[row.is_active ? 'Active': 'Inactive' ]] 55 | [[row.key]] 57 | [[row.key]] 62 | [[col[1] ]] [[table.unit]] ([[table.scale]]) 63 |
68 |
69 |
70 | {% block metrics_extra_info %} 71 | {% endblock %} 72 |
73 |
74 | {% endblock %} 75 | 76 | {% block dashboard_bodyscripts %} 77 | 86 | {% endblock %} 87 | --------------------------------------------------------------------------------