Skip to content

Commit

Permalink
Add Drop-in v6 (#62)
Browse files Browse the repository at this point in the history
* 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
gcatanese authored Feb 3, 2025
1 parent 30e93e1 commit d6ed484
Show file tree
Hide file tree
Showing 46 changed files with 1,805 additions and 92 deletions.
7 changes: 2 additions & 5 deletions .github/dependabot.yml
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/**"
24 changes: 18 additions & 6 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,28 @@ on:
- LICENSE

jobs:
checkout:

# e2e testing with Drop-in v5 (see /_archive/v5 folder)
checkout-v5:
runs-on: ubuntu-latest
steps:
- name: Checkout project
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Build image (application)
run: docker build -t test-image:latest .
run: docker build -t test-image-v5:latest -f ./_archive/v5/Dockerfile ./_archive/v5
- name: Start container (application)
run: docker run --rm -d --name test-image -p 8080:8080 -e ADYEN_API_KEY="${{ secrets.ADYEN_API_KEY }}" -e ADYEN_MERCHANT_ACCOUNT=${{ secrets.ADYEN_MERCHANT_ACCOUNT }} -e ADYEN_CLIENT_KEY=${{ secrets.ADYEN_CLIENT_KEY }} -e ADYEN_HMAC_KEY=${{ secrets.ADYEN_HMAC_KEY }} test-image:latest
run: docker run --rm -d --name test-image-v5 -p 8080:8080 -e ADYEN_API_KEY="${{ secrets.ADYEN_API_KEY }}" -e ADYEN_MERCHANT_ACCOUNT=${{ secrets.ADYEN_MERCHANT_ACCOUNT }} -e ADYEN_CLIENT_KEY=${{ secrets.ADYEN_CLIENT_KEY }} -e ADYEN_HMAC_KEY=${{ secrets.ADYEN_HMAC_KEY }} test-image-v5:latest
- name: Run testing suite
run: docker run --rm --name adyen-testing-suite -e ADYEN_HMAC_KEY=${{ secrets.ADYEN_HMAC_KEY }} --network host ghcr.io/adyen-examples/adyen-testing-suite:main
run: docker run --rm --name adyen-testing-suite -e PLAYWRIGHT_FOLDERNAME=checkout/v5 -e ADYEN_HMAC_KEY=${{ secrets.ADYEN_HMAC_KEY }} --network host ghcr.io/adyen-examples/adyen-testing-suite:main

# e2e testing with Drop-in v6
checkout-v6:
runs-on: ubuntu-latest
steps:
- name: Checkout project
uses: actions/checkout@v3
- name: Build image (application)
run: docker build -t test-image-v6:latest .
- name: Start container (application)
run: docker run --rm -d --name test-image-v6 -p 8080:8080 -e ADYEN_API_KEY="${{ secrets.ADYEN_API_KEY }}" -e ADYEN_MERCHANT_ACCOUNT=${{ secrets.ADYEN_MERCHANT_ACCOUNT }} -e ADYEN_CLIENT_KEY=${{ secrets.ADYEN_CLIENT_KEY }} -e ADYEN_HMAC_KEY=${{ secrets.ADYEN_HMAC_KEY }} test-image-v6:latest
- name: Run testing suite
run: docker run --rm --name adyen-testing-suite -e PLAYWRIGHT_FOLDERNAME=checkout/v6 -e ADYEN_HMAC_KEY=${{ secrets.ADYEN_HMAC_KEY }} --network host ghcr.io/adyen-examples/adyen-testing-suite:main
11 changes: 11 additions & 0 deletions _archive/v5/Dockerfile
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" ]
87 changes: 87 additions & 0 deletions _archive/v5/README.md
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 added _archive/v5/app/__init__.py
Empty file.
110 changes: 110 additions & 0 deletions _archive/v5/app/app.py
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')


56 changes: 56 additions & 0 deletions _archive/v5/app/main/config.py
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")
46 changes: 46 additions & 0 deletions _archive/v5/app/main/sessions.py
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
Loading

0 comments on commit d6ed484

Please sign in to comment.