omise-flask-example
omise-flask-example copied to clipboard
Example Flask app demonstrating the Omise payment gateway
Opn Payments is a payment service provider operating in Thailand, Japan, and Singapore. Opn Payments provides a set of APIs that help merchants of any size accept payments online. Read the documentation and start getting paid!
Omise ♥ Flask
Integrate the Opn payment gateway into your Python Flask application to enable convenient payment options for your users.
Flask is a minimal web application framework written in Python that pairs well with our Python library omise-python.
While this tutorial walks through some aspects of integration with Opn Payments, it is not meant to be a comprehensive Flask or omise-python tutorial.
If you run into any issues regarding this tool and the functionality it provides, consult the frequently asked questions in our support documents. If you can't find an answer there, email our support team.
Requirements
To follow along, you will need:
- An Opn Payments account (and your
$OMISE_PUBLIC_KEYand$OMISE_SECRET_KEY) - A Python 3.7 environment
Installation
The first step is to clone the repo:
git clone https://github.com/omise/omise-flask-example
Change to the omise-flask-example directory.
Installation using Pipenv is supported.
cd omise-flask-example
pipenv install
Configuration
Environment variables are a good way to prevent secret keys from leaking into your code (see The Twelve-Factor App).
Use the env.example template to create your .env.
cp env.example .env
Your .env should now look like this:
export OMISE_SECRET_KEY=skey_test_xxxxxxxxxxxxxxxxxxx
export OMISE_PUBLIC_KEY=pkey_test_xxxxxxxxxxxxxxxxxxx
export OMISE_API_VERSION=2017-11-02
export FLASK_SECRET_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export FLASK_ENV=development
export STORE_LOCALE=th_TH
export STORE_CURRENCY=THB
export PREFERRED_URL_SCHEME=http
export SERVER_NAME=localhost:5000
export AUTO_CAPTURE=False
Replace the values for OMISE_SECRET_KEY and OMISE_PUBLIC_KEY with your keys.
They can be found on your Opn Payments Dashboard.
After logging in, click "Keys" in the lower-left corner.
Copy and paste them into this file.
For FLASK_SECRET_KEY, create a random string of characters.
How to do this is left as an exercise for the reader.
With AUTO_CAPTURE, we have set it to False to delay capture and only pre-authorize card charges.
The currency and locale mentioned in the
.envfile assume a test account registered in Thailand. If you are using a test account registered in Japan, you should also setSTORE_CURRENCY=JPYandSTORE_LOCALE=ja_JP. For Singapore, you should setSTORE_CURRENCY=SGDandSTORE_LOCALE=en_SG.
At this point, you should be able to run the application using the following command:
pipenv run flask run
The Flask application should be running at http://localhost.localdomain:5000. Go look at it, add some items to the cart, and hit checkout (you can use these test credit card numbers to create a test charge).

How was this application made?
The source code should be fairly minimal and (hopefully) clear.
For the rest of this README.md, let's walk through a way to integrate the Opn payment gateway as expressed in this basic Flask application.
We will:
- Create an app factory
- Define a blueprint
- Prepare to charge a card
- Successfully charge that card
- Use the dashboard to see our charge
This README lists the highlights. Please consult the source code for the full implementation.
Build your factory
At the root of your omise-flask-example directory should be app.py.
app.py sets up the configuration for this Flask application.
It defines the application factory create_app that creates the Flask application.
This factory does the following:
- Initializes our Flask
app - Configures the application using the
Configclass (loaded fromconfig.py) - Registers blueprints (using the
register_blueprintmethod) on the Flaskapp - Returns the created
app
from flask import Flask
from config import Config
from store.store import store
from payment.checkout import checkout
from webhook.webhook import webhook
from flask_talisman import Talisman
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
app.register_blueprint(store)
app.register_blueprint(checkout)
app.register_blueprint(webhook)
Talisman(
app,
content_security_policy={"default-src": "'unsafe-inline' 'self' *.omise.co"},
)
return app
Notice that there is not much of payment-related activity happening here.
The actual mechanics of charging a card are stored in the checkout blueprint we registered on the application previously.
Blueprints help manage modular Flask applications.
From the documentation:
Flask uses a concept of blueprints for making application components and supporting common patterns within an application or across applications. Blueprints can greatly simplify how large applications work and provide a central means for Flask extensions to register operations on applications. A Blueprint object works similarly to a Flask application object, but it is not actually an application. Rather, it is a blueprint of how to construct or extend an application.
In theory, you could extract these blueprints and include them in your own Opn-powered Flask application.
Laying out the blueprint
On disk, the checkout blueprint and its template are stored in a folder called payment that resembles:
$ tree payment
payment
├── checkout.py
└── templates
├── barcode.html
├── checkout.html
└── complete.html
To create this blueprint, we make an instance of the Blueprint class and define a template_folder under the current directory.
After that, we add a route to the instance the same way we would with a normal Flask app instance using the route decorator prepending the blueprint name: @checkout.route("/checkout").
checkout = Blueprint("checkout", __name__, template_folder="templates")
@checkout.route("/checkout")
def check_out():
cart = Cart()
return render_template(
"checkout.html",
key=current_app.config.get("OMISE_PUBLIC_KEY"),
cart=cart,
Price=Price,
currency=current_app.config.get("STORE_CURRENCY"),
customer=session.get("customer")
)
Now when the user goes to the /checkout route, this blueprint will handle the rendering using the template checkout.html passing in values for key, cart, Price, currency, and customer.
The template displays a very simple cart and the Opn-specific <form> code that powers our payment button.
Here's a minimal version of how this would look.
<form class="checkout-form" name="checkoutForm" method="POST" action="/charge">
<script type="text/javascript" src="https://cdn.omise.co/omise.js"
data-key="{{ key }}"
data-amount="{{ cart.total() }}"
data-currency="{{ currency }}">
</script>
</form>
The payment form generation is handled by Omise.js, which (at a minimum) accepts a key (your OMISE_PUBLIC_KEY), amount, and currency.

Once the user clicks the button "Pay with Opn Payments", the form sends a POST request to our local /charge endpoint providing one of the parameters omiseToken (for cards) or omiseSource (for everything else).
Getting ready to charge
This part is important: We use the token to issue the charge rather than the actual card details. Never have card details stored on or pass through your server.
Using Opn Payments, we avoid the risk of having to collect card information directly.
We can retrieve a token in Flask by calling request.form.get("omiseToken").
So, for our /charge endpoint, we do to at least the following:
- Get the cart information (to determine the price)
- Get the
omiseTokenoromiseSourcefor which we need to actually issue the charge - Configure the
omiseobject that issues the charge - Create an
order_id
The following example additionally collects
customerto enable customer payment flows. See the full implementation for more details.
We configure the omise object by providing the OMISE_SECRET_KEY and OMISE_API_VERSION that we provided during the initial configuration of the application.
While the API version is not strictly necessary, it is a good practice to set this explicitly so that we can migrate to a new version in a controlled way (see our guidelines for API versioning).
After this, we can then create a UUID order_id.
This is not required by Opn Payments, but will be useful for you as a customer-facing identifier for the order.
@checkout.route("/charge", methods=["POST"])
def charge():
cart = Cart()
email = request.form.get("email")
token = request.form.get("omiseToken")
source = request.form.get("omiseSource")
customer = request.form.get("omiseCustomer")
omise.api_secret = current_app.config.get("OMISE_SECRET_KEY")
omise.api_version = current_app.config.get("OMISE_API_VERSION")
order_id = uuid.uuid4()
Attempting the charge
We are almost ready to issue the charge request, but we should keep in mind that there are two ways for a charge API request to go wrong.
First, the request could return an error object with a HTTP status code (e.g. 401 Authentication Failure).
When using omise-python, this type of failure can be handled with a try/except block.
Another way the request could fail is if the charge API request returns a charge object with a failure_code.
Try/Except
To create a charge using omise-python, we call omise.Charge.create that accepts several parameters, of which token, amount, and currency are required.
The amount and currency are retrieved from the session cart and the configured STORE_CURRENCY, respectively.
Optionally, we are adding several parameters, ip, description, and return_uri, all of which are technically optional, but highly recommended.
ipanddescriptionhold the customer IP and a description of the cart contents to help in Opn Payments' automated fraud analysis.- Since most payment methods require a redirection to an authorization service, provide
return_uri, which is the URI to which the customer must be redirected after authorization. - We are also passing a custom metadata parameter.
This object can be of any form, and we are using it to log the application that made this charge ("Omise Flask"), the current cart contents, and the
order_idgenerated previously.
After creating a charge, the result is passed to a processing function (process) that will decide what to do next.
chrg = omise.Charge.create(
amount=cart.total(),
currency=current_app.config.get("STORE_CURRENCY"),
metadata={
"app": "Omise Flask",
"cart": {"items": cart.items()},
"order_id": str(order_id),
},
return_uri=url_for("checkout.order", order_id=order_id, _external=True),
ip=get_client_ip(),
description=str(cart.items()),
**nonce,
)
return process(chrg)
If the charge returns an error within our try clause, we flash a message and return to the checkout page.
We are differentiating between omise.errors.BaseError and the more generic Python exception to make it easier to troubleshoot in the future.
except omise.errors.BaseError as e:
flash("""An error occurred. Please contact support.""")
current_app.logger.error(f"""OmiseError: {repr(e)}. See https://www.omise.co/api-errors""")
return redirect(url_for("checkout.check_out"))
except Exception as e:
flash("""An error occurred. Please contact support.""")
current_app.logger.error(repr(e))
return redirect(url_for("checkout.check_out"))
Successfully charging
Of course, we are really here to handle payments like a boss.
In the process function, we check whether the charge meets a series of conditions that determine how to handle it.
For instance, if it requires a redirection to an authorization service, we redirect the user.
If no exception was raised, failure_code is null, and charge.status is successful, we have successfully charged the card!
In the case of success, we:
- Empty the cart
- Let the user know the charge is successful and provide their Order ID
- Return to the completed charge page
if chrg.status == "successful":
cart.empty()
flash(f"Order {order_id} successfully completed.")
return render_template("complete.html")
Viewing the dashboard
Check your dashboard to see the charge and all its associated details.

Get paid
Thanks for following along. We hope you found this walkthrough helpful.
Opn Payments is a payment service provider operating in Thailand, Japan, and Singapore. omise-python is just one of the many open-source libraries for integrating Opn Payments into your application. We also offer plugins for popular e-commerce platforms.
Testing
pipenv install --dev
pipenv run python -m pytest --disable-pytest-warnings
Contributing
Use black for formatting:
pipenv install --dev
pipenv run black --exclude venv .