Subscription Website Builder - subscription website in a box
Subscribie helps you automatically collect money from your customers, clients and members, without the hassle of manual payments. Save time by having people sign-up themselves, and easily track payments.
You don’t need to be technical, and can integrate it with your existing business by creating your account.
Subscribie is an open source Subscription Billing management software.
https://user-images.githubusercontent.com/1718624/196006904-3f00f852-3b86-4ecc-b940-85e3357c275b.mp4
https://footballclub.subscriby.shop/
Use Subscribie to collect recurring payments online.
Quickly build a subscription based website, taking weekly/monthly/yearly payments- including one-off charges.
Don’t want/know how to code? Pay for the hosted service.
A lot of the hard work has been done for you. If you’re a developer, you can
impress your clients quickly, if you’re a small business owner, you might want
to try the subscription website hosting service but you can always host it yourself too.
Quickly run Subscribie from a container:
If you use podman
:
podman run -p 8082:80 ghcr.io/subscribie/subscribie/subscribie:latest
Or, if you prefer Docker:
docker run -p 8082:80 ghcr.io/subscribie/subscribie/subscribie:latest
Then visit: http://127.0.0.1:8082/auth/login
Username: admin@example.com
Password: password
See CONTRIBUTING.md and quickstart below.
git clone https://github.com/Subscribie/subscribie.git
cd subscribie
cp settings.yaml.example settings.yaml # Copy default settings to settings.yaml (read it)
# Read the .settings.yam file so you're familiar with the application settings/variables
(Optional) Set database path. Edit settings.yaml
and set DB_FULL_PATH
and SQLALCHEMY_DATABASE_URI
. (optional but recommended- do not store data.db in /tmp).
Notice that
sqlite:///
starts with three forward slashes. So, if you want to store the database in/home/sam/data.db
then,
you should putsqlite:////home/sam/data.db
(note four/
‘s)
# Open the settings.yaml file, and change the database path to store somewhere else (e.g. your `/home/Documents/data.db` folder):
DB_FULL_PATH: /tmp/data.db
SQLALCHEMY_DATABASE_URI: sqlite:////tmp/data.db
Create python environment and run flask:
python3 -m venv venv # Create a python3.x virtual environment
. venv/bin/activate # Activate the virtualenv
# If you are on Windows, use: . venv/Scripts/Activate
pip install -r requirements.lock # Install requirements
export FLASK_APP=subscribie
export FLASK_DEBUG=1
flask db upgrade
flask initdb # (recommended- gives you some example data)
The database file is called data.db
. Note,flask initdb
inserts pretend data into your database for testing.
You need a Stripe api key.
Publishable key
and Secret key
settings.yaml
file:Edit your settings.yaml
file
STRIPE_TEST_PUBLISHABLE_KEY: pk_test_<your-Publishable-key>
STRIPE_TEST_SECRET_KEY: sk_test_<your-Secret-key>
export FLASK_APP=subscribie
export FLASK_DEBUG=1
flask run
Now visit http://127.0.0.1:5000
If you like to use docker-compose workflow for local development:
git clone https://github.com/Subscribie/subscribie.git
cd subscribie
cp settings.yaml.example settings.yaml
export COMPOSE_DOCKER_CLI_BUILD=1
export DOCKER_BUILDKIT=1
# Start the container
docker-compose up
# Wait for it to build...
Then visit http://127.0.0.1:5000
To go inside the container, you can do: docker-compose exec web /bin/bash
from the project root directory.
Quick: edit your settings.yaml
file and set:
PYTHON_LOG_LEVEL: DEBUG
E.g. to reduce the amount of logs, to WARNING
or CRITICAL
.
The default log level is DEBUG
which means show as much logging
information as possible.
The possible values are DEBUG, INFO, WARNING, ERROR, CRITICAL
See https://docs.python.org/3/howto/logging.html
Flask does need to be restarted for the log level to change.
Edit your settings.yaml
file
Change:
THEME_NAME="jesmond"
to THEME_NAME="builder"
TEMPLATE_BASE_DIR
if you want to store themes in a different directory.Stop & start subscribie
Complete. The other theme will now load
If you’re creating a new theme, then change TEMPLATE_BASE_DIR
to a directory outside of subscribie root project.
Locally you’ll need to create public/private keys for secure
jwt authentication.
Generate public/private keys automatically
# Use the commands below to automatically create 'private.pem' file and key
openssl genrsa -out private.pem 2048
# Use this command to automatically generate your public.pem
openssl rsa -in private.pem -pubout > public.pem
Update .env file with PRIVATE_KEY and PUBLIC_KEY
PRIVATE_KEY: /path/to/private.pem
PUBLIC_KEY: /path/to/public.pem
Provide the username & password in a POST request, and a jwt token is returned for use in further requests.
curl -v -d "username=admin@example.com" -d "password=password" http://127.0.0.1:5000/auth/jwt-login
curl -v --user "fred:password" http://127.0.0.1:5000/auth/jwt-login
Then use the bearer token in a request to a protect path.
e.g.
curl -v -H "Authorization: Bearer <token>" http://127.0.0.1:5000/auth/protected
curl -v -H "Content-Type: application/json" -H "Authorization: Bearer <token> " http://127.0.0.1:5000/api/plans
Example POST request:
curl -v -H "Content-Type: application/json"
-H "Authorization: Bearer <token>" -d '
{
"interval_unit": "monthly",
"interval_amount": "599",
"sell_price": 0,
"title": "My title",
"requirements": {
"instant_payment": false,
"subscription": true,
"note_to_seller_required": false
},
"selling_points": [
{"point":"Quality"}
]
}' http://127.0.0.1:5000/api/plan
Example PUT request:
curl -v -H 'Content-Type: application/json' -X PUT
-d '
{
"title":"Coffee",
"interval_unit": "monthly",
"selling_points": [
{"point":"Quality"},
{"point": "Unique blend"}
],
"interval_amount":888,
"requirements": {
"instant_payment": false,
"subscription": true,
"note_to_seller_required": false}
}'
http://127.0.0.1:5000/api/plan/229
Example DELETE request:
curl -v -X DELETE -H "Authorization: Bearer <token>" http://127.0.0.1:5000/api/plan/229
The database store is SQL based.
The general steps are:
model.py
with new fieldflask db migrate -m 'added column title to plan model'
- which generates a new migration file./migrations/versions/<hash>_added_column_title_to.py
down
migration, see other migration files for an example).op.batch_alter_table
however that is no longer needed (thanks sqlalchemy!)flask db upgrade
/start-building
endpointshop
Every shop owner gets a deployed flask application, with its own database.
stripe-connect-account-announcer
If a Subscribie shop
connects to Stripe (it does not have to), then the shop
will announce it’s Stripe connect id to the stripe-connect-account-announcer
.
The stripe-connect-account-announcer
stores the Stripe connect id, so that when Stripe webhook events
arrive, the stripe-connect-webhook-endpoint-router
knows which shop
to send the events to.
stripe-connect-webhook-endpoint-router
A Stripe webhook endpoint.
Receives Stripe webhook events, which,
stripe-connect-account-announcer
)shop
shop
verifies the webhook from Stripe, and processes the event.Note, in previous implementations there was one webhook endpoint per shop- this isn’t compatible with Stripe when using Stripe Connect because there’s a limit on the number of webhooks, and connect events need to be routed based on their Stripe connect id anyway, hence the
stripe-connect-webhook-endpoint-router
performs this role.
If the stripe-connect-account-announcer
suffers an outage, this means new shops can’t announce their Stripe account to stripe-connect-webhook-endpoint-router
meaning, when a new Stripe event arrives from Stripe, then, Subscribie’s stripe-connect-webhook-endpoint-router
would not know which shop to send it to. Stripe automatically retries the delivery of events which allows time for the system to recover in an outage.
uWSGI is used to run the application services.
Subscribie Saas uses the following key component of uwsgi: Emperor mode.
uWSGI Emperor mode starts and manages all running Subscribie shops as uWSGI
vassals.
“If the emperor dies, all the vassals die.”
-Emperor mode
subscribie
.Problem: Every shop uses ~45mb of RAM. With lots of Shops the RAM usage can be high. Since shops are not receiving web traffic all the time we can stop them to reduce RAM usage.
Solution: uWSGI vassals are configured to be OnDemandVassals
see OnDemandVassals
and also socket-activated (note that’s two different things):
Result: A reduction of > 17Gb of ram observed on a busy node.
Socket activation is enabled by using the uWSGI feature emperor-on-demand-extension = .socket
in the emperor.ini
config.
OnDemandVassals is enable by using the following config in the injected vassal config for every shop:
# idle time in seconds
idle = 60
# kill the application after idle time is reached
die-on-idle = true
See: Combining on demand vassals with --idle
and --die-on-idle
Needed components / services. Check the .env.example
for each of them.
POST
requests of new sites to be created. The Builder module submits to this endpoint. The server requires uwsgi to be installed. There is an example config in the README.Sometimes you need to rebuild the container if you’ve made changes to the Dockerfile
.
docker-compose up --build --force-recreate