├── .env-example ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.md ├── Procfile ├── README.md ├── app.json ├── app.py ├── data └── .gitkeep ├── docker-compose.yml ├── docs └── technical_settings.png ├── requirements.txt ├── runtime.txt └── templates ├── index.html └── instructions.html /.env-example: -------------------------------------------------------------------------------- 1 | DEBUG=True 2 | LISTEN_HOST=localhost 3 | LISTEN_PORT=5000 4 | APP_URL=http://localhost:5000 5 | APP_CLIENT_ID=value_copied_from_developer_portal 6 | APP_CLIENT_SECRET=value_copied_from_developer_portal 7 | SESSION_SECRET=superstrongsecret_required_for_secure_cookies 8 | DATABASE_PATH=data/hello_world.db 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Expected behavior 2 | 3 | 4 | ### Actual behavior 5 | 6 | 7 | ### Steps to reproduce behavior 8 | 9 | 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### What? 2 | 3 | A description about what this pull request implements and its purpose. Try to be detailed and describe any technical details to simplify the job of the reviewer and the individual on production support. 4 | 5 | #### Tickets / Documentation 6 | 7 | Add links to any relevant tickets and documentation. 8 | 9 | - [Link 1](http://example.com) 10 | - ... 11 | 12 | #### Screenshots (if appropriate) 13 | 14 | Attach images or add image links here. 15 | 16 | ![Example Image](http://placehold.it/300x200) 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | 39 | # Python stuff # 40 | ################ 41 | *.pyc 42 | *.idea 43 | 44 | # Environment # 45 | ############### 46 | *.env 47 | venv/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Hello World apps 2 | 3 | Thanks for showing interest in contributing! 4 | 5 | The following is a set of guidelines for contributing to the Hello World app. These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | #### Table of Contents 8 | 9 | [API Documentation](https://developer.bigcommerce.com/api) 10 | 11 | [How Can I Contribute?](#how-can-i-contribute) 12 | * [Your First Code Contribution](#your-first-code-contribution) 13 | * [Pull Requests](#pull-requests) 14 | 15 | [Styleguides](#styleguides) 16 | * [Git Commit Messages](#git-commit-messages) 17 | * [Python Styleguide](#python-styleguide) 18 | 19 | ### Your First Code Contribution 20 | 21 | Unsure where to begin contributing to Hello World app? Check our [forums](https://forum.bigcommerce.com/s/group/0F913000000HLjECAW), our [stackoverflow](https://stackoverflow.com/questions/tagged/bigcommerce) tag, and the reported [issues](https://github.com/bigcommerce/hello-world-app-python-flask/issues). 22 | 23 | ### Pull Requests 24 | 25 | * Fill in [the required template](https://github.com/bigcommerce/hello-world-app-python-flask/pull/new/master) 26 | * Include screenshots and animated GIFs in your pull request whenever possible. 27 | * End files with a newline. 28 | 29 | ## Styleguides 30 | 31 | ### Git Commit Messages 32 | 33 | * Use the present tense ("Add feature" not "Added feature") 34 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 35 | * Limit the first line to 72 characters or less 36 | * Reference pull requests and external links liberally 37 | 38 | ### Python Styleguide 39 | 40 | All Python must adhere to [PEP8 Python Code Styleguide](https://www.python.org/dev/peps/pep-0008/). 41 | 42 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM 347907137948.dkr.ecr.us-east-1.amazonaws.com/python:3.4.3 2 | 3 | RUN apt-get update &&\ 4 | apt-get install -y -q sqlite3 &&\ 5 | rm -rf /var/lib/apt/lists/* 6 | 7 | ENV USE_ENV true 8 | ENV WORKDIR /opt/services/hello-world-python 9 | ENV HOME $WORKDIR 10 | 11 | RUN groupadd app &&\ 12 | useradd -g app -d $WORKDIR -s /sbin/nologin -c 'Docker image user for the app' app &&\ 13 | mkdir -p $WORKDIR 14 | 15 | ADD . /opt/services/hello-world-python 16 | 17 | RUN pip install -r $WORKDIR/requirements.txt 18 | 19 | RUN chown -R app:app $WORKDIR 20 | 21 | USER app 22 | 23 | CMD cd $WORKDIR && python ./app.py 24 | 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-present, BigCommerce Pty. Ltd. All rights reserved 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 6 | persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 9 | Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 12 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn app:app 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) [(Heroku instructions)](#getting-started-heroku-version) 2 | # BigCommerce Sample App: Python 3 | This is a small Flask application that implements the OAuth callback flow for BigCommerce [Single Click Apps][single_click_apps] 4 | and uses the [BigCommerce API][api_client] to pull a list of products on a BigCommerce store. For information on how to develop apps 5 | for BigCommerce stores, see our [Developer Portal][devportal]. 6 | 7 | We hope this sample gives you a good starting point for building your next killer app! What follows are steps specific 8 | to running and installing this sample application. 9 | 10 | ### Registering the app with BigCommerce 11 | 1. Create a trial store on [BigCommerce](https://www.bigcommerce.com/) 12 | 2. Go to the [Developer Portal][devportal] and log in by going to "My Apps" 13 | 3. Click the button "Create an app", enter a name for the new app, and then click "Create" 14 | 4. You don't have to fill out all the details for your app right away, but you do need 15 | to provide some core details in section 4 (Technical). Note that if you are just getting 16 | started, you can use `localhost` for your hostname, but ultimately you'll need to host your 17 | app on the public Internet. 18 | * _Auth Callback URL_: `https:///bigcommerce/callback` 19 | * _Load Callback URL_: `https:///bigcommerce/load` 20 | * _Uninstall Callback URL_: `https:///bigcommerce/uninstall` 21 | * _Remove User Callback URL_: `https:///bigcommerce/remove-user` (if enabling your app for multiple users) 22 | 5. Enable the _Products - Read Only_ scope under _OAuth scopes_, which is what this sample app needs. 23 | 6. Click `Save & Close` on the top right of the dialog. 24 | 7. You'll now see your app in a list in the _My Apps_ section of Developer Portal. Hover over it and click 25 | _View Client ID_. You'll need these values in the next step. 26 | 27 | ### Getting started 28 | 1. Clone this repo: `git clone git@github.com:bigcommerce/hello-world-app-python-flask.git` 29 | 2. Change to the repo directory: `cd hello-world-app-python-flask` 30 | 3. If you want to use virtualenv: `virtualenv ENV && source ENV/bin/activate` 31 | 4. Install dependencies with pip: `pip install -r requirements.txt` 32 | 5. Copy `.env-example` to `.env` 33 | 6. Edit `.env`: 34 | * Set `BC_CLIENT_ID` and `BC_CLIENT_SECRET` to the values obtained from Developer Portal. 35 | * Set `APP_URL` to `https://`. 36 | * Set `SESSION_SECRET` to a long random string, such as that generated by `os.urandom(64)`. 37 | 7. Make sure to populate the database by opening a Python shell from within the app and running 38 | ``` 39 | from app import db 40 | db.create_all() 41 | ``` 42 | 7. Run the app: `python ./app.py` 43 | 8. Then follow the steps under Installing the app in your trial store. 44 | 45 | ### Hosting the app 46 | In order to install this app in a BigCommerce store, it must be hosted on the public Internet. You can get started in development 47 | by simply running `python app.py` to run it locally, and then use `localhost` in your URLs, but ultimately you will need to host 48 | it somewhere to use the app anywhere other than your development system. 49 | 50 | ### Getting started (Heroku version) 51 | 52 | 1. Click this button: [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 53 | 2. Fill in the details from the app portal on the Heroku deployment page 54 | * See [Registering the app with BigCommerce](#registering-the-app-with-bigcommerce) above. Ignore the callback URLs, just save the app to get the Client ID and Client Secret. 55 | 3. Deploy the app, and click "view" when it's done 56 | 4. Take the callback URLs from the instructions page and plug them into the dev portal. 57 | 5. Then follow the steps under Installing the app in your trial store. 58 | 59 | ### Installing the app in your trial store 60 | * Login to your trial store 61 | * Go to the Marketplace and click _My Drafts_. Find the app you just created and click it. 62 | * A details dialog will open. Click _Install_ and the draft app will be installed in your store. 63 | 64 | [single_click_apps]: https://developer.bigcommerce.com/api/#building-oauth-apps 65 | [api_client]: https://pypi.python.org/pypi/bigcommerce 66 | [devportal]: https://developer.bigcommerce.com 67 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BigCommerce Sample App, Python", 3 | "description": "Sample app for the BigCommerce app store. Handles installation flow and runs an API request, uses the response data to render an interface.", 4 | "keywords": [ 5 | "bigcommerce", 6 | "ecommerce", 7 | "python", 8 | "flask" 9 | ], 10 | "repository": "https://github.com/bigcommerce/hello-world-app-python-flask", 11 | "logo": "https://i.imgur.com/PEkdHpr.png", 12 | "success_url": "/instructions", 13 | "scripts": { 14 | "postdeploy": "python -c 'from app import db; db.create_all()'" 15 | }, 16 | "env": { 17 | "SESSION_SECRET": { 18 | "description": "Random string used to secure the flask session cookie.", 19 | "generator": "secret" 20 | }, 21 | "APP_URL": { 22 | "description": "The public URL of your Heroku app.", 23 | "value": "This will be replaced after deployment" 24 | }, 25 | "DEBUG": { 26 | "description": "Boolean used to put Flask, SQLAlchemy, and other addons into a verbose logging and debug mode. Leave it True for now, but be sure to set it to False for a production app release!", 27 | "value": "True" 28 | }, 29 | "APP_CLIENT_ID": { 30 | "description": "Replace this with the BigCommerce Client ID", 31 | "value": "Client ID" 32 | }, 33 | "APP_CLIENT_SECRET": { 34 | "description": "Replace this with the BigCommerce Client Secret", 35 | "value": "Client Secret" 36 | } 37 | }, 38 | "formation": { 39 | "web": { 40 | "quantity": 1, 41 | "size": "free" 42 | } 43 | }, 44 | "image": "heroku/python", 45 | "addons": [ 46 | "heroku-postgresql" 47 | ] 48 | } -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from bigcommerce.api import BigcommerceApi 2 | import dotenv 3 | import flask 4 | from flask_sqlalchemy import SQLAlchemy 5 | from sqlalchemy.orm import relationship 6 | import os 7 | 8 | # do __name__.split('.')[0] if initialising from a file not at project root 9 | app = flask.Flask(__name__) 10 | 11 | # Look for a .env file 12 | if os.path.exists('.env'): 13 | dotenv.load_dotenv('.env') 14 | 15 | # Load configuration from environment, with defaults 16 | app.config['DEBUG'] = True if os.getenv('DEBUG') == 'True' else False 17 | app.config['LISTEN_HOST'] = os.getenv('LISTEN_HOST', '0.0.0.0') 18 | app.config['LISTEN_PORT'] = int(os.getenv('LISTEN_PORT', '5000')) 19 | app.config['APP_URL'] = os.getenv('APP_URL', 'http://localhost:5000') # must be https to avoid browser issues 20 | app.config['APP_CLIENT_ID'] = os.getenv('APP_CLIENT_ID') 21 | app.config['APP_CLIENT_SECRET'] = os.getenv('APP_CLIENT_SECRET') 22 | app.config['SESSION_SECRET'] = os.getenv('SESSION_SECRET', os.urandom(64)) 23 | app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'sqlite:///data/hello_world.sqlite').replace("postgres://", "postgresql://", 1) 24 | app.config['SQLALCHEMY_ECHO'] = app.config['DEBUG'] 25 | app.config['SESSION_COOKIE_SAMESITE'] = "None" 26 | app.config['SESSION_COOKIE_SECURE'] = True 27 | 28 | # Setup secure cookie secret 29 | app.secret_key = app.config['SESSION_SECRET'] 30 | 31 | # Setup db 32 | db = SQLAlchemy(app) 33 | 34 | 35 | class User(db.Model): 36 | id = db.Column(db.Integer, primary_key=True) 37 | bc_id = db.Column(db.Integer, nullable=False) 38 | email = db.Column(db.String(120), nullable=False) 39 | storeusers = relationship("StoreUser", backref="user") 40 | 41 | def __init__(self, bc_id, email): 42 | self.bc_id = bc_id 43 | self.email = email 44 | 45 | def __repr__(self): 46 | return '' % (self.id, self.bc_id, self.email) 47 | 48 | 49 | class StoreUser(db.Model): 50 | id = db.Column(db.Integer, primary_key=True) 51 | store_id = db.Column(db.Integer, db.ForeignKey('store.id'), nullable=False) 52 | user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) 53 | admin = db.Column(db.Boolean, nullable=False, default=False) 54 | 55 | def __init__(self, store, user, admin=False): 56 | self.store_id = store.id 57 | self.user_id = user.id 58 | self.admin = admin 59 | 60 | def __repr__(self): 61 | return '' \ 62 | % (self.id, self.user.email, self.user_id, self.store.store_id, self.admin) 63 | 64 | 65 | class Store(db.Model): 66 | id = db.Column(db.Integer, primary_key=True) 67 | store_hash = db.Column(db.String(16), nullable=False, unique=True) 68 | access_token = db.Column(db.String(128), nullable=False) 69 | scope = db.Column(db.Text(), nullable=False) 70 | admin_storeuser_id = relationship("StoreUser", 71 | primaryjoin="and_(StoreUser.store_id==Store.id, StoreUser.admin==True)") 72 | storeusers = relationship("StoreUser", backref="store") 73 | 74 | def __init__(self, store_hash, access_token, scope): 75 | self.store_hash = store_hash 76 | self.access_token = access_token 77 | self.scope = scope 78 | 79 | def __repr__(self): 80 | return '' \ 81 | % (self.id, self.store_hash, self.access_token, self.scope) 82 | 83 | 84 | # 85 | # Error handling and helpers 86 | # 87 | def error_info(e): 88 | content = "" 89 | try: # it's probably a HttpException, if you're using the bigcommerce client 90 | content += str(e.headers) + "
" + str(e.content) + "
" 91 | req = e.response.request 92 | content += "
Request:
" + req.url + "
" + str(req.headers) + "
" + str(req.body) 93 | except AttributeError as e: # not a HttpException 94 | content += "

(This page threw an exception: {})".format(str(e)) 95 | return content 96 | 97 | 98 | @app.errorhandler(500) 99 | def internal_server_error(e): 100 | content = "Internal Server Error: " + str(e) + "
" 101 | content += error_info(e) 102 | return content, 500 103 | 104 | 105 | @app.errorhandler(400) 106 | def bad_request(e): 107 | content = "Bad Request: " + str(e) + "
" 108 | content += error_info(e) 109 | return content, 400 110 | 111 | 112 | def jwt_error(e): 113 | print(f"JWT verification failed: {e}") 114 | return "Payload verification failed!", 401 115 | 116 | 117 | # Helper for template rendering 118 | def render(template, context): 119 | return flask.render_template(template, **context) 120 | 121 | 122 | def client_id(): 123 | return app.config['APP_CLIENT_ID'] 124 | 125 | 126 | def client_secret(): 127 | return app.config['APP_CLIENT_SECRET'] 128 | 129 | # 130 | # OAuth pages 131 | # 132 | 133 | 134 | # The Auth Callback URL. See https://developer.bigcommerce.com/api/callback 135 | @app.route('/bigcommerce/callback') 136 | def auth_callback(): 137 | # Put together params for token request 138 | code = flask.request.args['code'] 139 | context = flask.request.args['context'] 140 | scope = flask.request.args['scope'] 141 | store_hash = context.split('/')[1] 142 | redirect = app.config['APP_URL'] + flask.url_for('auth_callback') 143 | 144 | # Fetch a permanent oauth token. This will throw an exception on error, 145 | # which will get caught by our error handler above. 146 | client = BigcommerceApi(client_id=client_id(), store_hash=store_hash) 147 | token = client.oauth_fetch_token(client_secret(), code, context, scope, redirect) 148 | bc_user_id = token['user']['id'] 149 | email = token['user']['email'] 150 | access_token = token['access_token'] 151 | 152 | # Create or update store 153 | store = Store.query.filter_by(store_hash=store_hash).first() 154 | if store is None: 155 | store = Store(store_hash, access_token, scope) 156 | db.session.add(store) 157 | db.session.commit() 158 | else: 159 | store.access_token = access_token 160 | store.scope = scope 161 | db.session.add(store) 162 | db.session.commit() 163 | # If the app was installed before, make sure the old admin user is no longer marked as the admin 164 | oldadminuser = StoreUser.query.filter_by(store_id=store.id, admin=True).first() 165 | if oldadminuser: 166 | oldadminuser.admin = False 167 | db.session.add(oldadminuser) 168 | 169 | # Create or update global BC user 170 | user = User.query.filter_by(bc_id=bc_user_id).first() 171 | if user is None: 172 | user = User(bc_user_id, email) 173 | db.session.add(user) 174 | elif user.email != email: 175 | user.email = email 176 | db.session.add(user) 177 | 178 | # Create or update store user 179 | storeuser = StoreUser.query.filter_by(user_id=user.id, store_id=store.id).first() 180 | if not storeuser: 181 | storeuser = StoreUser(store, user, admin=True) 182 | else: 183 | storeuser.admin = True 184 | db.session.add(storeuser) 185 | db.session.commit() 186 | 187 | # Log user in and redirect to app home 188 | flask.session['storeuserid'] = storeuser.id 189 | return flask.redirect(app.config['APP_URL']) 190 | 191 | 192 | # The Load URL. See https://developer.bigcommerce.com/api/load 193 | @app.route('/bigcommerce/load') 194 | def load(): 195 | # Decode and verify payload 196 | payload = flask.request.args['signed_payload_jwt'] 197 | try: 198 | user_data = BigcommerceApi.oauth_verify_payload_jwt(payload, client_secret(), client_id()) 199 | except Exception as e: 200 | return jwt_error(e) 201 | 202 | bc_user_id = user_data['user']['id'] 203 | email = user_data['user']['email'] 204 | store_hash = user_data['sub'].split('stores/')[1] 205 | 206 | # Lookup store 207 | store = Store.query.filter_by(store_hash=store_hash).first() 208 | if store is None: 209 | return "Store not found!", 401 210 | 211 | # Lookup user and create if doesn't exist (this can happen if you enable multi-user 212 | # when registering your app) 213 | user = User.query.filter_by(bc_id=bc_user_id).first() 214 | if user is None: 215 | user = User(bc_user_id, email) 216 | db.session.add(user) 217 | db.session.commit() 218 | storeuser = StoreUser.query.filter_by(user_id=user.id, store_id=store.id).first() 219 | if storeuser is None: 220 | storeuser = StoreUser(store, user) 221 | db.session.add(storeuser) 222 | db.session.commit() 223 | 224 | # Log user in and redirect to app interface 225 | flask.session['storeuserid'] = storeuser.id 226 | return flask.redirect(app.config['APP_URL']) 227 | 228 | 229 | # The Uninstall URL. See https://developer.bigcommerce.com/api/load 230 | @app.route('/bigcommerce/uninstall') 231 | def uninstall(): 232 | # Decode and verify payload 233 | payload = flask.request.args['signed_payload_jwt'] 234 | try: 235 | user_data = BigcommerceApi.oauth_verify_payload_jwt(payload, client_secret(), client_id()) 236 | except Exception as e: 237 | return jwt_error(e) 238 | 239 | # Lookup store 240 | store_hash = user_data['sub'].split('stores/')[1] 241 | store = Store.query.filter_by(store_hash=store_hash).first() 242 | if store is None: 243 | return "Store not found!", 401 244 | 245 | # Clean up: delete store associated users. This logic is up to you. 246 | # You may decide to keep these records around in case the user installs 247 | # your app again. 248 | storeusers = StoreUser.query.filter_by(store_id=store.id) 249 | for storeuser in storeusers: 250 | db.session.delete(storeuser) 251 | db.session.delete(store) 252 | db.session.commit() 253 | 254 | return flask.Response('Deleted', status=204) 255 | 256 | 257 | # The Remove User Callback URL. 258 | @app.route('/bigcommerce/remove-user') 259 | def remove_user(): 260 | payload = flask.request.args['signed_payload_jwt'] 261 | try: 262 | user_data = BigcommerceApi.oauth_verify_payload_jwt(payload, client_secret(), client_id()) 263 | except Exception as e: 264 | return jwt_error(e) 265 | 266 | store_hash = user_data['sub'].split('stores/')[1] 267 | store = Store.query.filter_by(store_hash=store_hash).first() 268 | if store is None: 269 | return "Store not found!", 401 270 | 271 | # Lookup user and delete it 272 | bc_user_id = user_data['user']['id'] 273 | user = User.query.filter_by(bc_id=bc_user_id).first() 274 | if user is not None: 275 | storeuser = StoreUser.query.filter_by(user_id=user.id, store_id=store.id).first() 276 | db.session.delete(storeuser) 277 | db.session.commit() 278 | 279 | return flask.Response('Deleted', status=204) 280 | 281 | 282 | # 283 | # App interface 284 | # 285 | @app.route('/') 286 | def index(): 287 | # Lookup user 288 | storeuser = StoreUser.query.filter_by(id=flask.session['storeuserid']).first() 289 | if storeuser is None: 290 | return "Not logged in!", 401 291 | store = storeuser.store 292 | user = storeuser.user 293 | 294 | # Construct api client 295 | client = BigcommerceApi(client_id=client_id(), 296 | store_hash=store.store_hash, 297 | access_token=store.access_token) 298 | 299 | # Fetch a few products 300 | products = client.Products.all(limit=10) 301 | 302 | # Render page 303 | context = dict() 304 | context['products'] = products 305 | context['user'] = user 306 | context['store'] = store 307 | context['client_id'] = client_id() 308 | context['api_url'] = client.connection.host 309 | return render('index.html', context) 310 | 311 | 312 | @app.route('/instructions') 313 | def instructions(): 314 | if not app.config['DEBUG']: 315 | return "Forbidden - instructions only visible in debug mode" 316 | context = dict() 317 | return render('instructions.html', context) 318 | 319 | 320 | if __name__ == "__main__": 321 | db.create_all() 322 | app.run(app.config['LISTEN_HOST'], app.config['LISTEN_PORT']) 323 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcommerce/hello-world-app-python-flask/8044c391c954b23436e32c65d6224725e0b9d2f4/data/.gitkeep -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | dev: 2 | extends: 3 | service: hello_world_python 4 | file: ../../docker-compose.yml 5 | volumes: 6 | - ./:/opt/services/hello-world-python 7 | 8 | prod: 9 | image: 394389787758.dkr.ecr.us-east-1.amazonaws.com/devbox/hello_world_python:latest 10 | container_name: hello_world_python 11 | hostname: hello_world_python 12 | ports: 13 | - '5000' 14 | env_file: 15 | - .env-example 16 | -------------------------------------------------------------------------------- /docs/technical_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcommerce/hello-world-app-python-flask/8044c391c954b23436e32c65d6224725e0b9d2f4/docs/technical_settings.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==3.0.0 2 | Werkzeug==3.0.0 3 | itsdangerous==2.1.2 4 | requests==2.31.0 5 | python-dotenv==0.17.1 6 | bigcommerce==0.23.3 7 | gunicorn==20.1.0 8 | psycopg2-binary==2.9.9 9 | Flask-SQLAlchemy==3.1.1 10 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.11.6 2 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Welcome {{user.email}}!

5 | 6 |
7 |

Client ID

8 |
{{client_id}}
9 | 10 |
11 |

User

12 |
{{user|e}}
13 | 14 |
15 |

Store

16 |
{{store|e}}
17 | 18 |
19 |

CURL

20 |
21 |         curl -XGET -v -H 'Accept: application/json'
22 |                       -H 'X-Auth-Client: {{client_id}}'
23 |                       -H 'X-Auth-Token: {{store.access_token}}'
24 |                       https://{{api_url}}/stores/{{store.store_hash}}/v3/catalog/products
25 |       
26 | 27 |
28 |

Products

29 |
    30 | {% for p in products %} 31 |
  • {{ p.name }} — {{ p.price }}
  • 32 | {% endfor %} 33 |
34 | 35 |
36 |

Scopes

37 |
    38 | {{ store.scope }} 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /templates/instructions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 |

Heroku deployment successful

10 |

Final steps

11 |

Update app in BC dev portal

12 |

To complete setup, please fill in these values in the dev portal:

13 | 14 | 15 | 16 | 17 | 18 |
Callback URL:
Load URL:
Uninstall URL:
Remove User URL:
19 |

Update Heroku environment variable

20 |

One last step! You'll need to log into your Heroku account and set the APP_URL environment variable to:

21 |

If you have the Heroku CLI, you can just run this command:

22 |

You can view Heroku's documentation on setting environment variables here.

23 |

At this point, you should be able to install the app in your store.

24 | 25 | 26 | 36 | 37 | --------------------------------------------------------------------------------