-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add dropin v6 * Remove previous implementation * Use server-side redirect * Update navigation to new pages * Add handleShopperRedirect with paymentDetails call * Dependabot ignore _archive/v5 * Run E2E with both v5 and v6 * Keep dropin v5 implementation * Correct E2E test workflow for v6 * Correct docker build argument
- Loading branch information
Showing
46 changed files
with
1,805 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,8 @@ | ||
# To get started with Dependabot version updates, you'll need to specify which | ||
# package ecosystems to update and where the package manifests are located. | ||
# Please see the documentation for all configuration options: | ||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates | ||
|
||
version: 2 | ||
updates: | ||
- package-ecosystem: "pip" | ||
directory: "/" | ||
schedule: | ||
interval: "daily" | ||
ignore-paths: | ||
- "_archive/**" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
FROM python:3.9-slim | ||
|
||
COPY app/ /app | ||
COPY requirements.txt /app/requirements.txt | ||
|
||
EXPOSE 5000 | ||
|
||
WORKDIR /app | ||
RUN pip install -r requirements.txt | ||
|
||
CMD [ "python", "./app.py" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
# Sample app with Adyen Adyen Web 5.68.x. | ||
|
||
This folder contains the previous version of the sample application that uses **Adyen Web 5.68.x**. | ||
|
||
Check the root folder of the repository to use the latest Adyen Web 6.x | ||
|
||
## Details | ||
|
||
This repository showcases a PCI-compliant integration of the [Sessions Flow](https://docs.adyen.com/online-payments/build-your-integration/additional-use-cases/), the default integration that we recommend for merchants. Explore this simplified e-commerce demo to discover the code, libraries and configuration you need to enable various payment options in your checkout experience. | ||
|
||
![Card Checkout Demo](app/static/img/cardcheckout.gif) | ||
|
||
The Demo leverages Adyen's API Library for Python [GitHub](https://github.com/Adyen/adyen-python-api-library) | [Docs](https://github.com/Adyen/adyen-python-api-library). | ||
|
||
## Requirements | ||
|
||
- Python 3.5 or greater | ||
- Python libraries: | ||
- flask | ||
- uuid | ||
- Adyen v12.0.0 or higher | ||
|
||
## Installation | ||
|
||
1. Clone this repo | ||
|
||
``` | ||
git clone https://github.com/adyen-examples/adyen-python-online-payments.git | ||
``` | ||
|
||
2. Run `source ./setup.sh` to: | ||
- Create and activate a virtual environment | ||
- Download the necessary python dependencies | ||
|
||
3. Create a `.env` file with all required configuration | ||
|
||
- PORT (default 8080) | ||
- [API key](https://docs.adyen.com/user-management/how-to-get-the-api-key) | ||
- [Client Key](https://docs.adyen.com/user-management/client-side-authentication) | ||
- [Merchant Account](https://docs.adyen.com/account/account-structure) | ||
- [HMAC Key](https://docs.adyen.com/development-resources/webhooks/verify-hmac-signatures) | ||
|
||
Remember to include `http://localhost:8080` in the list of Allowed Origins | ||
|
||
``` | ||
PORT=8080 | ||
ADYEN_API_KEY="your_API_key_here" | ||
ADYEN_MERCHANT_ACCOUNT="your_merchant_account_here" | ||
ADYEN_CLIENT_KEY="your_client_key_here" | ||
ADYEN_HMAC_KEY="your_hmac_key_here" | ||
``` | ||
|
||
## Usage | ||
1. Run `./start.sh` to: | ||
- Initialize the required environment variables. This step is necessary every time you re-activate your venv | ||
- Start Python app | ||
|
||
2. Visit [http://localhost:8080](http://localhost:8080) and select an integration type. | ||
|
||
To try out integrations with test card numbers and payment method details, see [Test card numbers](https://docs.adyen.com/development-resources/test-cards/test-card-numbers). | ||
|
||
# Webhooks | ||
|
||
Webhooks deliver asynchronous notifications about the payment status and other events that are important to receive and process. | ||
You can find more information about webhooks in [this blog post](https://www.adyen.com/knowledge-hub/consuming-webhooks). | ||
|
||
### Webhook setup | ||
|
||
In the Customer Area under the `Developers → Webhooks` section, [create](https://docs.adyen.com/development-resources/webhooks/#set-up-webhooks-in-your-customer-area) a new `Standard webhook`. | ||
|
||
A good practice is to set up basic authentication, copy the generated HMAC Key and set it as an environment variable. The application will use this to verify the [HMAC signatures](https://docs.adyen.com/development-resources/webhooks/verify-hmac-signatures/). | ||
|
||
Make sure the webhook is **enabled**, so it can receive notifications. | ||
|
||
### Expose an endpoint | ||
|
||
This demo provides a simple webhook implementation exposed at `/api/webhooks/notifications` that shows you how to receive, validate and consume the webhook payload. | ||
|
||
### Test your webhook | ||
|
||
The following webhooks `events` should be enabled: | ||
* **AUTHORISATION** | ||
|
||
|
||
To make sure that the Adyen platform can reach your application, we have written a [Webhooks Testing Guide](https://github.com/adyen-examples/.github/blob/main/pages/webhooks-testing.md) | ||
that explores several options on how you can easily achieve this (e.g. running on localhost or cloud). | ||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import logging | ||
|
||
from Adyen.util import is_valid_hmac_notification | ||
from flask import Flask, render_template, send_from_directory, request, abort | ||
|
||
from main.sessions import adyen_sessions | ||
from main.config import * | ||
|
||
|
||
def create_app(): | ||
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO) | ||
logging.getLogger('werkzeug').setLevel(logging.ERROR) | ||
|
||
app = Flask('app') | ||
|
||
# Register 404 handler | ||
app.register_error_handler(404, page_not_found) | ||
|
||
# Routes: | ||
@app.route('/') | ||
def home(): | ||
return render_template('home.html') | ||
|
||
@app.route('/cart/<integration>') | ||
def cart(integration): | ||
return render_template('cart.html', method=integration) | ||
|
||
@app.route('/checkout/<integration>') | ||
def checkout(integration): | ||
|
||
if integration in get_supported_integration(): | ||
return render_template('component.html', method=integration, client_key=get_adyen_client_key()) | ||
else: | ||
abort(404) | ||
|
||
@app.route('/api/sessions', methods=['POST']) | ||
def sessions(): | ||
host_url = request.host_url | ||
|
||
return adyen_sessions(host_url) | ||
|
||
@app.route('/result/success', methods=['GET']) | ||
def checkout_success(): | ||
return render_template('checkout-success.html') | ||
|
||
@app.route('/result/failed', methods=['GET']) | ||
def checkout_failure(): | ||
return render_template('checkout-failed.html') | ||
|
||
@app.route('/result/pending', methods=['GET']) | ||
def checkout_pending(): | ||
return render_template('checkout-success.html') | ||
|
||
@app.route('/result/error', methods=['GET']) | ||
def checkout_error(): | ||
return render_template('checkout-failed.html') | ||
|
||
# Handle redirect (required for some payment methods) | ||
@app.route('/redirect', methods=['POST', 'GET']) | ||
def redirect(): | ||
|
||
return render_template('component.html', method=None, client_key=get_adyen_client_key()) | ||
|
||
# Process incoming webhook notifications | ||
@app.route('/api/webhooks/notifications', methods=['POST']) | ||
def webhook_notifications(): | ||
""" | ||
Receives outcome of each payment | ||
:return: | ||
""" | ||
notifications = request.json['notificationItems'] | ||
# fetch first( and only) NotificationRequestItem | ||
notification = notifications[0] | ||
|
||
if is_valid_hmac_notification(notification['NotificationRequestItem'], get_adyen_hmac_key()) : | ||
# consume event asynchronously | ||
consume_event(notification) | ||
else: | ||
# invalid hmac: do not send [accepted] response | ||
raise Exception("Invalid HMAC signature") | ||
|
||
return '', 202 | ||
|
||
@app.route('/favicon.ico') | ||
def favicon(): | ||
return send_from_directory(os.path.join(app.root_path, 'static'), | ||
'img/favicon.ico') | ||
|
||
return app | ||
|
||
|
||
# process payload asynchronously | ||
def consume_event(notification): | ||
print(f"consume_event merchantReference: {notification['NotificationRequestItem']['merchantReference']} " | ||
f"result? {notification['NotificationRequestItem']['success']}") | ||
|
||
# add item to DB, queue or run in a different thread | ||
|
||
|
||
def page_not_found(error): | ||
return render_template('error.html'), 404 | ||
|
||
|
||
if __name__ == '__main__': | ||
web_app = create_app() | ||
|
||
logging.info(f"Running on http://localhost:{get_port()}") | ||
web_app.run(debug=True, port=get_port(), host='0.0.0.0') | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import os | ||
|
||
from dotenv import load_dotenv, find_dotenv | ||
|
||
load_dotenv(find_dotenv()) | ||
|
||
|
||
def get_port(): | ||
return os.environ.get("PORT", 8080) | ||
|
||
|
||
def get_adyen_merchant_account(): | ||
adyen_merchant_account = os.environ.get("ADYEN_MERCHANT_ACCOUNT") | ||
|
||
if not adyen_merchant_account: | ||
raise Exception("Missing ADYEN_MERCHANT_ACCOUNT in .env") | ||
|
||
return adyen_merchant_account | ||
|
||
|
||
def get_adyen_api_key(): | ||
adyen_api_key = os.environ.get("ADYEN_API_KEY") | ||
|
||
if not adyen_api_key: | ||
raise Exception("Missing ADYEN_API_KEY in .env") | ||
|
||
return adyen_api_key | ||
|
||
|
||
def get_adyen_client_key(): | ||
adyen_client_key = os.environ.get("ADYEN_CLIENT_KEY") | ||
|
||
if not adyen_client_key: | ||
raise Exception("Missing ADYEN_CLIENT_KEY in .env") | ||
|
||
return adyen_client_key | ||
|
||
|
||
def get_adyen_hmac_key(): | ||
adyen_hmac_key = os.environ.get("ADYEN_HMAC_KEY") | ||
|
||
if not adyen_hmac_key: | ||
raise Exception("Missing ADYEN_HMAC_KEY in .env") | ||
|
||
return adyen_hmac_key | ||
|
||
|
||
def get_supported_integration(): | ||
return ['dropin', 'card', 'ideal', 'klarna', 'directEbanking', 'alipay', 'boletobancario', | ||
'sepadirectdebit', 'dotpay', 'giropay', 'ach', 'paypal', 'applepay', | ||
'klarna_paynow', 'klarna', 'klarna_account'] | ||
|
||
|
||
# Check to make sure variables are set | ||
# if not merchant_account or not checkout_apikey or not client_key or not hmac_key: | ||
# raise Exception("Incomplete configuration in .env") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import Adyen | ||
import json | ||
import uuid | ||
from main.config import get_adyen_api_key, get_adyen_merchant_account | ||
|
||
''' | ||
Create Payment Session by calling /sessions endpoint | ||
Request must provide few mandatory attributes (amount, currency, returnUrl, transaction reference) | ||
Your backend should have a payment state where you can fetch information like amount and shopperReference | ||
Parameters | ||
---------- | ||
host_url : string | ||
URL of the host (i.e. http://localhost:8080): required to define returnUrl parameter | ||
''' | ||
|
||
|
||
def adyen_sessions(host_url): | ||
adyen = Adyen.Adyen() | ||
adyen.payment.client.xapikey = get_adyen_api_key() | ||
adyen.payment.client.platform = "test" # change to live for production | ||
adyen.payment.client.merchant_account = get_adyen_merchant_account() | ||
|
||
request = {} | ||
|
||
request['amount'] = {"value": "10000", "currency": "EUR"} # amount in minor units | ||
request['reference'] = f"Reference {uuid.uuid4()}" # provide your unique payment reference | ||
# set redirect URL required for some payment methods | ||
request['returnUrl'] = f"{host_url}/redirect?shopperOrder=myRef" | ||
request['countryCode'] = "NL" | ||
|
||
# set lineItems: required for some payment methods (ie Klarna) | ||
request['lineItems'] = \ | ||
[{"quantity": 1, "amountIncludingTax": 5000, "description": "Sunglasses"}, # amount in minor units | ||
{"quantity": 1, "amountIncludingTax": 5000, "description": "Headphones"}] # amount in minor units | ||
|
||
request['merchantAccount'] = get_adyen_merchant_account() | ||
|
||
result = adyen.checkout.payments_api.sessions(request) | ||
|
||
formatted_response = json.dumps((json.loads(result.raw_response))) | ||
print("/sessions response:\n" + formatted_response) | ||
|
||
return formatted_response |
Oops, something went wrong.