initial commit

This commit is contained in:
anamotaadyen
2022-10-03 17:19:04 +01:00
commit 482fc51ed6
1814 changed files with 327530 additions and 0 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

80
README.md Normal file
View File

@@ -0,0 +1,80 @@
# [Adyen Checkout](https://docs.adyen.com/checkout) integration demo
This repository includes examples of PCI-compliant UI integrations for online payments with Adyen. Within this demo app, we've created a simplified version of an e-commerce website, complete with commented code to highlight key features and concepts of Adyen's API. Check out the underlying code to see how you can integrate Adyen to give your shoppers the option to pay with their preferred payment methods, all in a seamless checkout experience.
![Card Checkout Demo](app/static/img/cardcheckout.gif)
## Supported Integrations
**Python with Flask** demos of the following client-side integrations are available in this repository:
- [Drop-in](https://docs.adyen.com/checkout/drop-in-web)
- [Component](https://docs.adyen.com/checkout/components-web)
- ACH
- Alipay
- Boleto
- Card
- Dotpay
- Giropay
- iDEAL
- Klarna
- PayPal
- SEPA Direct Debit
- Sofort
Please make sure to [add the above payment methods to your Adyen account](https://docs.adyen.com/payment-methods#add-payment-methods-to-your-account) before testing!
## Requirements
- Python 3.5 or greater
- Python libraries:
- flask
- uuid
- Adyen v6.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!
## Contributing
We commit all our new features directly into our GitHub repository. Feel free to request or suggest new features or code changes yourself as well!!
Find out more in our [Contributing](https://github.com/adyen-examples/.github/blob/main/CONTRIBUTING.md) guidelines.
## License
MIT license. For more information, see the **LICENSE** file in the root directory

BIN
app/.DS_Store vendored Normal file

Binary file not shown.

0
app/__init__.py Normal file
View File

Binary file not shown.

151
app/app.py Normal file
View File

@@ -0,0 +1,151 @@
import logging
from Adyen.util import is_valid_hmac_notification
from flask import Flask, render_template, send_from_directory, request, redirect, url_for, abort
from main.sessions import adyen_sessions
from main.paymentMethods import adyen_payment_methods
from main.payments import adyen_payments
from main.redirect import handle_shopper_redirect
from main.additional_details import get_payment_details
from main.disable import disable_card
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')
def checkout():
return render_template('component.html', client_key=get_adyen_client_key())
@app.route('/api/getPaymentMethods', methods=['GET', 'POST'])
def get_payment_methods():
request_data = request.get_json()
# print (request_data)
locale_data = request_data
return adyen_payment_methods(locale_data)
@app.route('/api/initiatePayment', methods=['POST'])
def initiate_payment():
request_data = request.get_json()
# print (request_data)
locale_data = request_data
return adyen_payments(request, locale_data)
@app.route('/api/submitAdditionalDetails', methods=['POST'])
def payment_details():
return get_payment_details(request)
@app.route('/api/disable', methods=['POST'])
def disable():
storedPaymentMethodId = request.get_json()['storedPaymentMethodId']
return disable_card(storedPaymentMethodId)
@app.route('/api/handleShopperRedirect', methods=['POST', 'GET'])
def handle_redirect():
values = request.values.to_dict() # Get values from query params in request object
details_request = {}
if "payload" in values:
details_request["details"] = {"payload": values["payload"]}
elif "redirectResult" in values:
details_request["details"] = {"redirectResult": values["redirectResult"]}
redirect_response = handle_shopper_redirect(details_request)
# Redirect shopper to landing page depending on payment success/failure
if redirect_response["resultCode"] == 'Authorised':
print ('I reach here')
return redirect(url_for('checkout_success'))
elif redirect_response["resultCode"] == 'Received' or redirect_response["resultCode"] == 'Pending':
return redirect(url_for('checkout_pending'))
else:
return redirect(url_for('checkout_failure'))
@app.route('/api/sessions', methods=['POST'])
def sessions():
host_url = request.host_url
request_data = request.get_json()
print (request_data)
locale_data = request_data
return adyen_sessions(host_url, locale_data)
@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']
for notification in notifications:
if is_valid_hmac_notification(notification['NotificationRequestItem'], get_adyen_hmac_key()) :
print(f"merchantReference: {notification['NotificationRequestItem']['merchantReference']} "
f"result? {notification['NotificationRequestItem']['success']}")
else:
# invalid hmac: do not send [accepted] response
raise Exception("Invalid HMAC signature")
return '[accepted]'
@app.route('/favicon.ico')
def favicon():
return send_from_directory(os.path.join(app.root_path, 'static'),
'img/favicon.ico')
return app
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')

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,31 @@
import Adyen
import json
from main.config import get_adyen_api_key, get_adyen_merchant_account
'''
perform a call to /payments/details
Passing in the component state.data object as frontend_request, This looks like the following:
{
details: {
"threeds2.fingerprint/challengeResult" : "eyJ0aHJlZURTQ29tcEluZCI6IlkifQ==""
}
paymentData : "Ab02b4c0!BQABAgB/3ckQEAf5YOdAT83NDjdf+AR4hmjf1fohm2Q8gSe95qb6hE3+GnxfBaK..."
}
'''
def get_payment_details(frontend_request):
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()
details_request = frontend_request.get_json()
print("/payments/details request:\n" + str(details_request))
details_response = adyen.checkout.payments_details(details_request)
formatted_response = json.loads(details_response.raw_response)
print("payments/details response:\n" + str(formatted_response))
return formatted_response

50
app/main/config.py Normal file
View File

@@ -0,0 +1,50 @@
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
# 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")

28
app/main/disable.py Normal file
View File

@@ -0,0 +1,28 @@
import Adyen
import json
from main.config import get_adyen_api_key, get_adyen_merchant_account
'''
perform a call to /disable to delete stored payment method
'''
def disable_card(storedPaymentMethodId):
adyen = Adyen.Adyen()
adyen.payment.client.xapikey = get_adyen_api_key()
adyen.payment.client.platform = "test" # change to live for production
adyen.payment.client.service = "Recurring"
adyen.payment.client.merchant_account = get_adyen_merchant_account()
disable_request = {}
disable_request['shopperReference'] = "UniqueReference"
disable_request['recurringDetailReference'] = f"{storedPaymentMethodId}"
disable_request['merchantAccount'] = "AnaTestMER"
print("/disable request:\n" + str(disable_request))
disable_response = adyen.recurring.disable(disable_request)
formatted_response = json.loads(disable_response.raw_response)
print("disable response:\n" + str(formatted_response))
return formatted_response

View File

@@ -0,0 +1,35 @@
import Adyen
import json
import uuid
from main.config import get_adyen_api_key, get_adyen_merchant_account
'''
Retrieve available payment methods by calling /paymentMethods
Request only needs to include merchantAccount, but you can include more information to get a more refined response
Should have a payment state on your server from which you can fetch information like amount and shopperReference
'''
def adyen_payment_methods(locale_data):
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()
payment_methods_request = {}
payment_methods_request['reference'] = f"Reference {uuid.uuid4()}" # provide your unique payment reference
payment_methods_request['countryCode'] = locale_data['countryCode']
payment_methods_request['shopperReference'] = "UniqueReference"
payment_methods_request['channel'] = "Web"
payment_methods_request['merchantAccount'] = "CheckoutCreateDemo"
print("/paymentMethods request:\n" + str(payment_methods_request))
payment_methods_response = adyen.checkout.payment_methods(payment_methods_request)
formatted_response = json.dumps((json.loads(payment_methods_response.raw_response)))
print("/paymentMethods response:\n" + formatted_response)
return formatted_response

160
app/main/payments.py Normal file
View File

@@ -0,0 +1,160 @@
import Adyen
import uuid
import json
from main.config import get_adyen_api_key, get_adyen_merchant_account
'''
perform a call to /payments
'''
def adyen_payments(frontend_request, locale_data):
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()
payment_info = frontend_request.get_json()
txvariant = payment_info["paymentMethod"]["type"]
payments_request = {}
payments_request['amount'] = {"value": "4000", "currency": locale_data['currency']} # choose_currency(txvariant)}
payments_request['channel'] = "Web"
payments_request['reference'] = f"Reference {uuid.uuid4()}"
payments_request['shopperReference'] = "UniqueReference"
payments_request['returnUrl'] = "http://localhost:8080/api/handleShopperRedirect"
payments_request['countryCode'] = locale_data['countryCode']
payments_request['merchantAccount'] = "AnaTestMER"
payments_request['recurringExpiry'] = "2022-08-01T23:59:59+02:00"
# payments_request['paymentMethod'] = {"subtype": "redirect"}
payments_request.update(payment_info)
if 'klarna' in txvariant:
payments_request['shopperEmail'] = "myEmail@adyen.com"
payments_request['lineItems'] = [
{
'quantity': "1",
'amountExcludingTax': "1950",
'taxPercentage': "1111",
'description': "Sunglasses",
'id': "Item #1",
'taxAmount': "50",
'amountIncludingTax': "2000",
'taxCategory': "High"
},
{
'quantity': "1",
'amountExcludingTax': "1950",
'taxPercentage': "1111",
'description': "Headphones",
'id': "Item #2",
'taxAmount': "50",
'amountIncludingTax': "2000",
'taxCategory': "High"
}]
elif txvariant == 'clearpay':
payments_request['shopperName'] = {"firstName": "Test", "lastName": "Shopper"}
payments_request['deliveryAddress'] = {"city": "London", "country": "GB", "houseNumberOrName": "56", "postalCode": "EC17 2IH", "street": "Mill Lane"}
payments_request['billingAddress'] = {"city": "London", "country": "GB", "houseNumberOrName": "56", "postalCode": "EC17 2IH", "street": "Mill Lane"}
payments_request['lineItems'] = [
{
'quantity': "1",
'amountExcludingTax': "1950",
'taxPercentage': "1111",
'description': "Sunglasses",
'id': "Item #1",
'taxAmount': "50",
'amountIncludingTax': "2000",
'taxCategory': "High"
},
{
'quantity': "1",
'amountExcludingTax': "1950",
'taxPercentage': "1111",
'description': "Headphones",
'id': "Item #2",
'taxAmount': "50",
'amountIncludingTax': "2000",
'taxCategory': "High"
}]
payments_request['shopperEmail'] = "ana.mota@adyen.com"
elif txvariant == 'directEbanking' or txvariant == 'giropay':
payments_request['countryCode'] = "DE"
elif txvariant == 'dotpay':
payments_request['countryCode'] = "PL"
elif txvariant == 'scheme':
payments_request['additionalData'] = {"allow3DS2": "true"}
payments_request['origin'] = "http://localhost:8080"
elif txvariant == 'ach' or txvariant == 'paypal':
payments_request['countryCode'] = 'US'
#subtype = "ecommerce"
payments_request
sanatizeRequest(payments_request)
print("/payments request:\n" + str(payments_request))
payments_response = adyen.checkout.payments(payments_request)
formatted_response = json.dumps((json.loads(payments_response.raw_response)))
print("/payments response:\n" + formatted_response)
return formatted_response
def choose_currency(payment_method):
if payment_method == "alipay":
return "CNY"
elif payment_method == "dotpay":
return "PLN"
elif payment_method == "boletobancario":
return "BRL"
elif payment_method == "ach" or payment_method == "paypal":
return "USD"
else:
return "EUR"
def currency_locale(country):
if country == "NL":
return "EUR"
elif country == "GB":
return "GBP"
elif country == "US":
return "USD"
def sanatizeRequest(payments_request):
del payments_request['locale']
del payments_request['currency']
# loaded = json.loads(payments_request)
# for item in loaded:
# for key in ["currency", "locale"]:
# item.pop(key)
# payments_request = str(loaded)
# for d in payments_request:
# cleanData = []
# cleanDict = {}
# for key, value in d.items():
# if key != 'locale':
# cleanData[key] = value
# with open('cleanData.json', 'r') as payments_request:
# cleanData = json.load(payments_request)
# for element in payments_request:
# element.pop('currency', None)
# with open('cleanData.json', 'w') as payments_request:

28
app/main/redirect.py Normal file
View File

@@ -0,0 +1,28 @@
import Adyen
import json
from main.config import get_adyen_api_key, get_adyen_merchant_account
'''
For redirect payment methods, handle redirect back to website
For redirect methods, pull payload from form data
For 3DS payments, pull MD and PaRes from form data
Return response as dictionary to make success/failure redirect in init.py easier
'''
def handle_shopper_redirect(values):
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()
details_request = values
print("/payments/details request:\n" + str(details_request))
details_response = adyen.checkout.payments_details(details_request)
formatted_response = json.loads(details_response.raw_response)
print("payments/details response:\n" + str(formatted_response))
return formatted_response

56
app/main/sessions.py Normal file
View File

@@ -0,0 +1,56 @@
import Adyen
import json
import uuid
from main.config import get_adyen_api_key, get_adyen_merchant_account
from flask import Flask, render_template, send_from_directory, request, redirect, url_for, abort
'''
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, locale_data):
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": "4000", "currency": locale_data['currency']}
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'] = locale_data['countryCode']
# for Klarna
request['lineItems'] = [{"quantity": "1", "amountIncludingTax": "2000", "description": "Sunglasses", "id": "Item 1", "taxPercentage": "2100", "productUrl": "https://example.com/item1", "imageUrl": "https://example.com/item1pic"},
{"quantity": "1","amountIncludingTax": "2000","description": "Headphones","id": "Item 2","taxPercentage": "2100","productUrl": "https://example.com/item2","imageUrl": "https://example.com/item2pic"}]
request['shopperEmail'] = "customer@email.uk"
request['shopperReference'] = "UniqueReference"
# for clearPay
request['shopperName'] = {"firstName": "Test", "lastName": "Shopper"}
request['deliveryAddress'] = {"city": "London", "country": "GB", "houseNumberOrName": "56", "postalCode": "EC17 2IH", "street": "Mill Lane"}
request['billingAddress'] = {"city": "London", "country": "GB", "houseNumberOrName": "56", "postalCode": "EC17 2IH", "street": "Mill Lane"}
result = adyen.checkout.sessions(request)
print("/sessions request:\n" + str(request))
formatted_response = json.dumps((json.loads(result.raw_response)))
print("/sessions response:\n" + formatted_response)
return formatted_response

BIN
app/static/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,467 @@
/* General page body */
html,
body {
width: 100%;
margin: 0;
font-family: "Fakt", "Segoe UI", sans-serif, Helvetica, Arial;
/*background-color: #E4D8B4; - All things retro */
}
*,
:after,
:before {
box-sizing: border-box;
}
a,
u {
text-decoration: none;
}
a:hover {
text-decoration: none;
}
.hidden {
display: none;
}
#header {
/*background: #C2B28F; - all things retro */
background: #fff;
border-bottom: 1px solid #e6e9eb;
height: 90px;
/* height: 44px; - original */
left: 0;
margin-bottom: 24px;
padding: 14px 26px;
position: fixed;
text-align: center;
top: 0;
width: 100%;
z-index: 2;
box-sizing: border-box;
}
/* Buttons */
.button {
/*background: #272324; - All things retro*/
background: #00112c;
border: 0;
border-radius: 6px;
color: #fff;
cursor: pointer;
display: inline-block;
font-size: 1em;
font-weight: 500;
margin: 0;
padding: 15px;
text-align: center;
transition: background 0.3s ease-out, box-shadow 0.3s ease-out;
width: 100%;
}
.button:hover {
background: #1c3045;
box-shadow: 0 3px 4px rgba(0, 15, 45, 0.2);
}
.logo-box {
/*float:left;
text-align: center;
width: 50%;
margin-bottom: 20px; - All things retro */
align-items: center;
}
.logo-image {
height: 64px;
margin-top: auto;
margin-bottom: auto;
margin-right: 16px;
/*width: 64px;*/
}
.logo-title {
font-family: 'Oleo Script', cursive;
font-size: 30px;
}
/* end General page body */
/* Index page */
.main-container {
margin: auto;
max-width: 1048px;
padding: 0 16px;
display: flex;
flex-direction: column;
}
.integration-list {
display: flex;
max-width: 1048px;
flex-wrap: wrap;
justify-content: center;
list-style: none;
margin: 0;
padding: 0;
}
@media (min-width: 768px) {
.integration-list {
margin-left: -8px;
margin-bottom: -8px;
margin-right: -8px;
}
}
@media (min-width: 1024px) {
.integration-list {
margin-left: -16px;
margin-bottom: -16px;
margin-right: -16px;
}
}
.integration-list-item {
background: #f7f8f9;
border-radius: 6px;
flex: 1 1 0;
margin: 4px;
min-width: 40%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #f7f8f9;
}
.integration-list-item:hover {
box-shadow: 0 16px 24px 0 #e5eaef;
text-decoration: none;
border: 1px solid #06f;
}
@media (min-width: 768px) {
.integration-list-item {
margin: 16px;
min-width: 25%;
}
}
.integration-list-item-link {
padding: 20px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
@media (min-width: 768px) {
.integration-list-item-link {
padding: 28px;
}
}
.integration-list-item-title {
text-align: center;
color: #00112c;
font-size: 1em;
font-weight: 700;
margin: 10px 0 0;
}
@media (min-width: 768px) {
.integration-list-item-title {
font-size: 24px;
margin-left: 0;
margin-bottom: 6px;
margin-right: 0;
}
}
.integration-list-item-subtitle {
color: #00112c;
font-size: 0.67em;
font-weight: 700;
margin: 10px 0 0;
}
@media (min-width: 768px) {
.integration-list-item-subtitle {
font-size: 16px;
margin-left: 0;
margin-bottom: 6px;
margin-right: 0;
}
}
.title-container {
display: flex;
flex-direction: column;
align-items: center;
}
.h3 {
padding-top: 24px;
}
.info {
margin-top: 120px;
color: #00112c;
text-align: center;
}
/* end Index page */
/* Cart preview page */
.shopping-cart {
float: right;
}
@media (min-width: 768px) {
.shopping-cart {
padding: 3px 0 0;
}
}
.shopping-cart-link {
display: inline-block;
position: relative;
}
.order-summary-list-list-item.first-item {
border-top: 1px solid #e6e9eb;
}
.order-summary-list-list-item {
border-bottom: 1px solid #e6e9eb;
display: flex;
height: 117px;
}
.order-summary-list-list-item-image {
height: 64px;
margin-top: auto;
margin-bottom: auto;
margin-right: 16px;
width: auto;
}
.order-summary-list-list-item-title {
font-weight: 700;
margin: auto auto auto 0;
}
.order-summary-list-list-item-price {
color: #687282;
margin: auto 16px;
text-align: right;
width: 80px;
}
@media (min-width: 768px) {
.order-summary-list-list-item-price {
margin: auto 24px;
}
}
.cart {
text-align: center;
margin-top: 80px;
padding-top: 24px;
}
.cart-footer {
font-weight: 700;
margin-top: 17px;
text-align: right;
margin-bottom: 17px;
}
@media (min-width: 768px) {
.cart-footer {
margin-top: 24px;
}
}
.cart-footer .button {
margin-top: 30px;
width: 100%;
}
@media (min-width: 768px) {
.cart-footer .button {
margin-top: 0;
width: 288px;
}
}
.cart-footer-amount {
margin-left: 16px;
margin-right: 24px;
}
.country-selector{
align-items:center;
background:#e5eaef;
border:1px solid #b9c4c9;
border-radius:6px;
display:flex;
height:40px;
margin:auto 0 auto auto;
outline:none;
padding:0 0 0 12px;
position:relative;
width:100%
}
@media (min-width:768px){
.country-selector{
width:200px
}
}
.country-selector__flag{
display:inline-flex;
margin-right:10px;
width:20px
}
.country-selector__select{
-webkit-appearance:none;
-moz-appearance:none;
appearance:none;
background:transparent;
border:0;
cursor:pointer;
flex:1;
font-size:1em;
height:100%;
margin:0 -12px 0 -42px;
outline:none;
padding-left:42px;
width:180px
}
.country-selector:focus{
box-shadow:none
}
.country-selector:hover{
border-color:#99a3ad
}
.country-selector .dropdown-icon{
margin-right:12px;
margin-top:3px
}
/* end of Cart preview page */
/* Payment page */
#payment-page .container {
margin-top: 100px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: 1110px;
}
#payment-page .checkout {
margin-top: 100px;
}
#payment-page .settings {
margin-top: 100px;
}
@media screen and (max-width: 1076px) {
#payment-page .container {
display: flex;
flex-direction: column;
}
.payment {
align-self: center;
}
}
.checkout-component-container {
border: none;
padding: 0;
margin: 8px 0;
width: 100%;
height: 100%;
}
/* Adyen Components */
.payment {
width: 100%;
padding: 0;
display: flex;
justify-content: center;
}
/* end Payments page */
/* PBL page */
.qrcontainer {
padding: 10px;
}
.pbl-conatiner {
margin-top: 100px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100%;
max-width: 1110px;
}
/* end PBL page */
/* Status page */
.status-container {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.status {
margin: 100px 0 126px;
text-align: center;
}
.status .status-image {
display: block;
height: 100px;
margin: 16px auto 0;
}
.status .status-image-thank-you {
height: 66px;
}
.status .status-message {
margin: 8px 0 24px;
}
.status .button {
max-width: 236px;
}
@media (min-width: 768px) {
.status .button {
max-width: 200px;
}
}
/* end Status page */

BIN
app/static/img/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.19375 10.5986C7.59328 11.1337 8.39498 11.1344 8.79536 10.5999L11.7922 6.59956C12.2861 5.94025 11.8156 5 10.9918 5H5.00809C4.18503 5 3.71438 5.93879 4.20681 6.59829L7.19375 10.5986Z" fill="#687282"/>
</svg>

After

Width:  |  Height:  |  Size: 318 B

4
app/static/img/error.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="88" height="88" viewBox="0 0 88 88" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 44C2 20.9046 20.9046 2 44 2C67.0954 2 86 20.9046 86 44C86 67.0954 67.0954 86 44 86C20.9046 86 2 67.0954 2 44Z" stroke="#D10244" stroke-width="4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.5 41.0339L55.8744 28.7009C56.6554 27.9225 57.9218 27.9225 58.7028 28.7009C59.4839 29.4793 59.4839 30.7414 58.7028 31.5199L46.3284 43.8529L58.7028 56.1858C59.4838 56.9643 59.4838 58.2264 58.7028 59.0048C57.9218 59.7832 56.6554 59.7832 55.8744 59.0048L43.5 46.6718L31.1257 59.0048C30.3446 59.7832 29.0783 59.7832 28.2972 59.0048C27.5162 58.2264 27.5162 56.9643 28.2972 56.1858L40.6716 43.8529L28.2972 31.5199C27.5162 30.7414 27.5162 29.4793 28.2972 28.7009C29.0783 27.9225 30.3446 27.9225 31.1256 28.7009L43.5 41.0339Z" fill="#D10244"/>
</svg>

After

Width:  |  Height:  |  Size: 850 B

View File

@@ -0,0 +1,4 @@
<svg width="88" height="88" viewBox="0 0 88 88" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 44C2 20.9046 20.9046 2 44 2C67.0954 2 86 20.9046 86 44C86 67.0954 67.0954 86 44 86C20.9046 86 2 67.0954 2 44Z" stroke="#D10244" stroke-width="4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.5 41.0339L55.8744 28.7009C56.6554 27.9225 57.9218 27.9225 58.7028 28.7009C59.4839 29.4793 59.4839 30.7414 58.7028 31.5199L46.3284 43.8529L58.7028 56.1858C59.4838 56.9643 59.4838 58.2264 58.7028 59.0048C57.9218 59.7832 56.6554 59.7832 55.8744 59.0048L43.5 46.6718L31.1257 59.0048C30.3446 59.7832 29.0783 59.7832 28.2972 59.0048C27.5162 58.2264 27.5162 56.9643 28.2972 56.1858L40.6716 43.8529L28.2972 31.5199C27.5162 30.7414 27.5162 29.4793 28.2972 28.7009C29.0783 27.9225 30.3446 27.9225 31.1256 28.7009L43.5 41.0339Z" fill="#D10244"/>
</svg>

After

Width:  |  Height:  |  Size: 850 B

BIN
app/static/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

3
app/static/img/jetti.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="154" height="62" viewBox="0 0 154 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M45.6814 0.5L34.979 41.0636C33.3191 47.3519 27.7277 51.4702 21.612 51.4702C20.3015 51.4702 18.9474 51.2931 17.5932 50.8502L13.7491 49.6546C6.62875 47.4404 2.47886 39.9122 4.40092 32.6055L5.2309 29.4171L1.43047 28.4428L0.600494 31.587C-0.578948 36.1039 -0.0110681 40.8865 2.21677 45.0048C4.48828 49.1232 8.15766 52.1345 12.5696 53.5072L16.4138 54.7029C18.1174 55.2343 19.821 55.5 21.5683 55.5C23.8399 55.5 26.0677 55.0572 28.1208 54.2158C29.1692 51.5588 31.7028 49.6989 34.6733 49.6546C36.5516 47.5733 37.9932 45.0048 38.7358 42.1707L48.3461 5.72544L56.8642 8.07246L58 3.86554L45.6814 0.5ZM110.708 55.1483C109.668 55.3681 108.542 55.5 107.242 55.5C105.639 55.5 104.166 55.2802 102.78 54.8405C101.394 54.4009 100.181 53.7414 99.1841 52.7742C98.1444 51.8509 97.3213 50.6639 96.7581 49.213C96.1949 47.8062 95.8917 46.0915 95.8917 44.1571V17.2506H90V13.6015H95.8917V0.5H99.7906V13.6455H114V17.2946H99.7906V43.7174C99.7906 46.6631 100.527 48.7294 102.043 49.9165C103.56 51.1035 105.509 51.6751 107.892 51.6751C108.888 51.6751 109.841 51.5871 110.751 51.3673C111.661 51.1475 112.7 50.7958 113.87 50.3121V54.0052C112.787 54.5328 111.747 54.8845 110.708 55.1483ZM58.4891 47.4786C57.0527 46.0759 55.8339 44.4103 54.8763 42.4378C53.9187 40.4653 53.3528 38.2299 53.1352 35.6876H86.9129C86.9275 35.5707 86.942 35.4684 86.9549 35.3775V35.3774C86.9807 35.1956 87 35.0593 87 34.9424V34.1973V34.0219C87 30.9536 86.5647 28.1045 85.6942 25.5183C84.8236 22.9322 83.5613 20.6529 81.9507 18.7243C80.3402 16.7956 78.3814 15.2615 76.0745 14.1656C73.7675 13.0698 71.1993 12.5 68.3265 12.5C65.5842 12.5 63.016 13.0698 60.6655 14.2095C58.3585 15.3491 56.3127 16.8833 54.5716 18.8119C52.8305 20.7406 51.4811 23.0199 50.48 25.606C49.4788 28.1922 49 30.9974 49 33.9342V34.1096C49 37.3094 49.5223 40.2462 50.6105 42.8761C51.6552 45.5061 53.1352 47.7416 54.9633 49.6264C56.7915 51.5112 58.9244 52.9577 61.362 53.9659C63.7995 54.974 66.3677 55.5 69.0229 55.5C72.8969 55.5 76.1615 54.7987 78.8167 53.3084C81.4719 51.818 83.8224 49.9332 85.9118 47.6978L83.2131 45.287C81.5155 47.1279 79.5567 48.6621 77.3368 49.8894C75.1168 51.1167 72.4181 51.7304 69.197 51.7304C67.1947 51.7304 65.236 51.3359 63.4078 50.6346C61.5796 49.9332 59.9255 48.8812 58.4891 47.4786ZM81.646 26.0882C82.3425 28.0168 82.7343 29.9893 82.9084 32.0933H53.1352C53.3093 29.8578 53.7881 27.7538 54.6586 25.7813C55.5292 23.8089 56.6174 22.0994 57.9233 20.6967C59.2291 19.2941 60.7961 18.1544 62.5372 17.3654C64.3219 16.5326 66.1936 16.1381 68.2394 16.1381C70.5899 16.1381 72.6793 16.5765 74.4204 17.4531C76.1615 18.3298 77.6415 19.5133 78.8603 21.0036C80.0355 22.4939 80.9931 24.2034 81.646 26.0882ZM136.242 55.5C137.542 55.5 138.668 55.3681 139.708 55.1483C140.747 54.8845 141.787 54.5328 142.87 54.0052V50.3121C141.7 50.7958 140.661 51.1475 139.751 51.3673C138.841 51.5871 137.888 51.6751 136.892 51.6751C134.509 51.6751 132.56 51.1035 131.043 49.9165C129.527 48.7294 128.791 46.6631 128.791 43.7174V17.2946H143V13.6455H128.791V0.5H124.892V13.6015H119V17.2506H124.892V44.1571C124.892 46.0915 125.195 47.8062 125.758 49.213C126.321 50.6639 127.144 51.8509 128.184 52.7742C129.181 53.7414 130.394 54.4009 131.78 54.8405C133.166 55.2802 134.639 55.5 136.242 55.5ZM148 55.4562V14.5H152V55.5H148V55.4562ZM154 4.5C154 6.70914 152.209 8.5 150 8.5C147.791 8.5 146 6.70914 146 4.5C146 2.29086 147.791 0.5 150 0.5C152.209 0.5 154 2.29086 154 4.5ZM35 51.5C32.2523 51.5 30 53.7523 30 56.5C30 59.2477 32.2523 61.5 35 61.5C37.7477 61.5 40 59.2477 40 56.5C40 53.7523 37.7477 51.5 35 51.5ZM35 58.6622C33.7838 58.6622 32.7928 57.6712 32.7928 56.455C32.7928 55.2387 33.7838 54.2477 35 54.2477C36.2162 54.2477 37.2072 55.2387 37.2072 56.455C37.2072 57.6712 36.2162 58.6622 35 58.6622Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.3 (57544) - http://www.bohemiancoding.com/sketch -->
<title>Icon</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M67.7975,34.25 C68.3735,34.25 68.948375,34.469375 69.38825,34.90925 C70.266875,35.787875 70.266875,37.21325 69.38825,38.091875 L43.136375,64.342625 C42.917,64.562 42.629,64.67225 42.341,64.67225 C42.053,64.67225 41.765,64.562 41.545625,64.342625 L30.40925,53.20625 C29.5295,52.3265 29.5295,50.903375 30.40925,50.023625 C30.848,49.584875 31.424,49.364375 32,49.364375 C32.574875,49.364375 33.152,49.584875 33.59075,50.023625 L42.341,58.775 L66.20675,34.90925 C66.646625,34.469375 67.2215,34.25 67.7975,34.25" id="path-1"></path>
</defs>
<g id="Layouts" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Chanel---04---Checkout" transform="translate(-669.000000, -275.000000)">
<g id="Group-5" transform="translate(524.000000, 100.000000)">
<g id="Group-4" transform="translate(115.000000, 175.000000)">
<g id="Icon" transform="translate(30.000000, 0.000000)">
<path d="M50,4 C24.709139,4 4,24.709139 4,50 C4,75.290861 24.709139,96 50,96 C75.290861,96 96,75.290861 96,50 C96,24.709139 75.290861,4 50,4 Z M50,0 C77.5,0 100,22.5 100,50 C100,77.5 77.5,100 50,100 C22.5,100 0,77.5 0,50 C0,22.5 22.5,0 50,0 Z" id="Path" fill="#0ABF53" fill-rule="nonzero"></path>
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Mask" fill="#0ABF53" xlink:href="#path-1"></use>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.3 (57544) - http://www.bohemiancoding.com/sketch -->
<title>Icon</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M67.7975,34.25 C68.3735,34.25 68.948375,34.469375 69.38825,34.90925 C70.266875,35.787875 70.266875,37.21325 69.38825,38.091875 L43.136375,64.342625 C42.917,64.562 42.629,64.67225 42.341,64.67225 C42.053,64.67225 41.765,64.562 41.545625,64.342625 L30.40925,53.20625 C29.5295,52.3265 29.5295,50.903375 30.40925,50.023625 C30.848,49.584875 31.424,49.364375 32,49.364375 C32.574875,49.364375 33.152,49.584875 33.59075,50.023625 L42.341,58.775 L66.20675,34.90925 C66.646625,34.469375 67.2215,34.25 67.7975,34.25" id="path-1"></path>
</defs>
<g id="Layouts" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Chanel---04---Checkout" transform="translate(-669.000000, -275.000000)">
<g id="Group-5" transform="translate(524.000000, 100.000000)">
<g id="Group-4" transform="translate(115.000000, 175.000000)">
<g id="Icon" transform="translate(30.000000, 0.000000)">
<path d="M50,4 C24.709139,4 4,24.709139 4,50 C4,75.290861 24.709139,96 50,96 C75.290861,96 96,75.290861 96,50 C96,24.709139 75.290861,4 50,4 Z M50,0 C77.5,0 100,22.5 100,50 C100,77.5 77.5,100 50,100 C22.5,100 0,77.5 0,50 C0,22.5 22.5,0 50,0 Z" id="Path" fill="#0ABF53" fill-rule="nonzero"></path>
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Mask" fill="#0ABF53" xlink:href="#path-1"></use>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -0,0 +1,221 @@
const clientKey = JSON.parse(document.getElementById('client-key').innerHTML);
const storedCountry = document.getElementById('country-code');
// let country = "GB";
let countrySettings;
// Used to retrieve country value from url
const urlCountryParams = new URLSearchParams(window.location.search);
const countryURL = urlCountryParams.get('country');
console.log(countryURL)
const flagUrlMap = {
"NL" : {
"src": "https://ca-test.adyen.com/ca/adl/img/flags/nl.svg",
"total": "€40.00",
"currency": "EUR",
"href": "{{ url_for('checkout', integration=method, country=NL) }}"
},
"GB" : {
"src": "https://ca-test.adyen.com/ca/adl/img/flags/gb.svg",
"total": "£40.00",
"currency": "GBP",
"href": "{{ url_for('checkout', integration=method, country=GB) }}"
},
"US" : {
"src": "https://ca-test.adyen.com/ca/adl/img/flags/us.svg",
"total": "$40.00",
"currency": "USD",
"href": "{{ url_for('checkout', integration=method, country=US) }}"
}
}
function changeSelect(el) {
document.getElementById('flag_img').src = flagUrlMap[el.value].src;
document.getElementById("total_cost").innerHTML = flagUrlMap[el.value].total;
const country = el.value;
// countrySettings = getCountryData(country)
}
const countryVariables = [
{
countryCode: "NL",
currency: "EUR",
locale: "en_NL"
},
{
countryCode: "GB",
currency: "GBP",
locale: "en_GB"
},
{
countryCode: "US",
currency: "USD",
locale: "en_US"
}
]
if (storedCountry) {
const selectedCountry = JSON.parse(storedCountry.innerHTML);
countrySettings = getCountryData(selectedCountry)
}
if (countryURL) {
const selectedCountry = countryURL
countrySettings = getCountryData(selectedCountry)
}
function getCountryData(countrySettings) {
return countryVariables.find((locality) => locality.countryCode === countrySettings)
}
// function getCountryData(countrySettings) {
// return countryVariables.find((locality) => locality.countryCode === countrySettings)
// }
async function initCheckout() {
try {
const paymentMethodsResponse = await callServer("/api/getPaymentMethods", countrySettings);
console.log(countrySettings)
let prettyResponse = JSON.stringify(paymentMethodsResponse, null, 2)
console.log(prettyResponse)
const configuration = {
paymentMethodsResponse: paymentMethodsResponse,
clientKey,
locale: countrySettings.locale || "en_GB",
countryCode: countrySettings.countryCode || "GB",
environment: "test",
showPayButton: true,
paymentMethodsConfiguration: {
ideal: {
showImage: true
},
card: {
hasHolderName: true,
holderNameRequired: true,
brands: ['mc','visa','amex', 'cup', 'cartebancaire', 'diners', 'discover', 'jcb'],
name: "Credit or debit card",
enableStoreDetails: true,
amount: {
value: 4000,
currency: countrySettings.currency || "GBP"
}
},
paypal: {
amount: {
currency: countrySettings.currency || "GBP",
value: 4000
},
//commit: false,
environment: "test", // Change this to "live" when you're ready to accept live PayPal payments
countryCode: countrySettings.countryCode || "GB", // Only needed for test. This will be automatically retrieved when you are in production.
showPayButton: true
//subtype: "redirect"
}
},
onSubmit: (state, dropin) => {
if (state.isValid) {
handleSubmission(state, dropin, "/api/initiatePayment", countrySettings);
}
},
onAdditionalDetails: (state, dropin) => {
handleSubmission(state, dropin, "/api/submitAdditionalDetails");
},
onDisableStoredPaymentMethod: (storedPaymentMethodId, resolve, reject) => {
// handleSubmission(state, dropin, "/api/disable");
}
};
const checkout = await AdyenCheckout(configuration);
checkout.create('dropin', {
showRemovePaymentMethodButton: true,
onDisableStoredPaymentMethod: (storedPaymentMethodId, resolve, reject) => {
callServer("/api/disable", {"storedPaymentMethodId":storedPaymentMethodId});
resolve()
reject()
}
})
.mount("#dropin-container");
} catch (error) {
console.error(error);
alert("Error occurred. Look at console for details");
}
}
/*function filterUnimplemented(pm) {
pm.paymentMethods = pm.paymentMethods.filter((it) =>
[
"scheme",
"ideal",
"dotpay",
"giropay",
"sepadirectdebit",
"directEbanking",
"ach",
"alipay",
"klarna_paynow",
"klarna",
"klarna_account",
"paypal",
"boletobancario_santander"
].includes(it.type)
);
return pm;
}*/
// Event handlers called when the shopper selects the pay button,
// or when additional information is required to complete the payment
async function handleSubmission(state, dropin, url, countrySettings) {
try {
//keeping the country data for the /payments call
const mergedData = {
...state.data,
...countrySettings
}
const res = await callServer(url, mergedData);
let prettyResponse = JSON.stringify(res, null, 2)
console.log(prettyResponse)
handleServerResponse(res, dropin);
} catch (error) {
console.error(error);
alert("Error occurred. Look at console for details");
}
}
// Calls your server endpoints
async function callServer(url, data) {
const res = await fetch(url, {
method: "POST",
body: data ? JSON.stringify(data) : "",
headers: {
"Content-Type": "application/json"
}
});
return await res.json();
}
// Handles responses sent from your server to the client
function handleServerResponse(res, dropin) {
if (res.action) {
dropin.handleAction(res.action);
} else {
switch (res.resultCode) {
case "Authorised":
window.location.href = "/result/success";
break;
case "Pending":
case "Received":
window.location.href = "/result/pending";
break;
case "Refused":
window.location.href = "/result/failed";
break;
default:
window.location.href = "/result/error";
break;
}
}
}
initCheckout();

View File

@@ -0,0 +1,248 @@
const clientKey = JSON.parse(document.getElementById('client-key').innerHTML);
const type = JSON.parse(document.getElementById('integration-type').innerHTML);
const storedCountry = document.getElementById('country-code');
let countrySettings;
// Used to finalize a checkout call in case of redirect
const urlParams = new URLSearchParams(window.location.search);
const sessionId = urlParams.get('sessionId'); // Unique identifier for the payment session
const redirectResult = urlParams.get('redirectResult');
// Used to retrieve country value from url
const urlCountryParams = new URLSearchParams(window.location.search);
const countryURL = urlCountryParams.get('country');
console.log(countryURL)
const countryVariables = [
{
countryCode: "NL",
currency: "EUR",
locale: "en_NL"
},
{
countryCode: "GB",
currency: "GBP",
locale: "en_GB"
},
{
countryCode: "US",
currency: "USD",
locale: "en_US"
}
]
if (storedCountry) {
const selectedCountry = JSON.parse(storedCountry.innerHTML);
countrySettings = getCountryData(selectedCountry)
}
if (countryURL) {
const selectedCountry = countryURL
countrySettings = getCountryData(selectedCountry)
}
function getCountryData(countrySettings) {
return countryVariables.find((locality) => locality.countryCode === countrySettings)
}
// Start the Checkout workflow
async function startCheckout() {
try {
// Init Sessions
const checkoutSessionResponse = await callServer(
"/api/sessions?type=" + type,
countrySettings
);
console.log(countrySettings)
// Create AdyenCheckout using Sessions response
const checkout = await createAdyenCheckout(checkoutSessionResponse)
// Create an instance of Drop-in and mount it to the container you created.
const dropinComponent = checkout.create(type, {
showRemovePaymentMethodButton: true,
onDisableStoredPaymentMethod: (storedPaymentMethodId, resolve, reject) => {
callServer("/api/disable", {"storedPaymentMethodId":storedPaymentMethodId});
resolve()
reject()
},
}).mount("#component"); // pass DIV id where component must be rendered
} catch (error) {
console.error(error);
alert("Error occurred. Look at console for details");
}
}
// Some payment methods use redirects. This is where we finalize the operation
async function finalizeCheckout() {
try {
// Create AdyenCheckout re-using existing Session
const checkout = await createAdyenCheckout({id: sessionId});
// Submit the extracted redirectResult (to trigger onPaymentCompleted(result, component) handler)
checkout.submitDetails({details: {redirectResult}});
} catch (error) {
console.error(error);
alert("Error occurred. Look at console for details");
}
}
async function createAdyenCheckout(session) {
const configuration = {
clientKey,
locale: countrySettings.locale || "en_GB",
countryCode: countrySettings.countryCode || "GB",
environment: "test", // change to live for production
showPayButton: true,
session: session,
paymentMethodsConfiguration: {
ideal: {
showImage: true
},
card: {
hasHolderName: true,
holderNameRequired: true,
name: "Credit or debit card",
enableStoreDetails: true,
brands: ['mc','visa','amex', 'cup', 'cartebancaire', 'diners', 'discover', 'jcb'],
//billingAddressRequired: true,
//onSubmit: () => {},
amount: {
value: 4000,
currency: countrySettings.currency || "GBP"
}
},
threeDS2: { // Web Components 4.0.0 and above: sample configuration for the threeDS2 action type
challengeWindowSize: '02'
// Set to any of the following:
// '02': ['390px', '400px'] - The default window size
// '01': ['250px', '400px']
// '03': ['500px', '600px']
// '04': ['600px', '400px']
// '05': ['100%', '100%']
},
paypal: {
amount: {
currency: countrySettings.currency || "GBP",
value: 4000
},
environment: "test",
commit: false,
returnUrl: "http://localhost:8080/checkout/success",
//countryCode: "GB", // Only needed for test. This will be automatically retrieved when you are in production.
lineItems: [
{"quantity": "1",
"amountIncludingTax": "2000",
"description": "Sunglasses",
"id": "Item 1",
"taxPercentage": "2100",
"productUrl": "https://example.com/item1",
"imageUrl": "https://example.com/item1pic"
},
{"quantity": "1",
"amountIncludingTax": "2000",
"description": "Headphones",
"id": "Item 2",
"taxPercentage": "2100",
"productUrl": "https://example.com/item2",
"imageUrl": "https://example.com/item2pic"
}
]
},
//klarna: {
// lineItems: [
// {"quantity": "1",
// "amountIncludingTax": "500",
// "description": "Sunglasses",
// "id": "Item 1",
// "taxPercentage": "2100",
// "productUrl": "https://example.com/item1",
// "imageUrl": "https://example.com/item1pic"}
// ]
//}frontend_request
},
/*onSubmit: (state, component) => {
// Your function calling your server to make the /payments request.
console.log(state);
if (state.isValid) {
handleSubmission(state, component, "/api/initiatePayment");
}
},
onAdditionalDetails: (state, component) => {
handleSubmission(state, component, "/api/submitAdditionalDetails");
},*/
onPaymentCompleted: (result, component) => {
console.info(result, component);
handleServerResponse(result, component);
},
onError: (error, component) => {
console.error(error.name, error.message, error.stack, component);
},
onDisableStoredPaymentMethod: (storedPaymentMethodId, resolve, reject) => {
}
};
return new AdyenCheckout(configuration);
}
/*
// Event handlers called when the shopper selects the pay button,
// or when additional information is required to complete the payment
async function handleSubmission(state, dropin, url) {
try {
const res = await callServer(url, state.data, countrySettings);
handleServerResponse(res, dropin);
} catch (error) {
console.error(error);
alert("Error occurred. Look at console for details");
}
}
*/
// Calls your server endpoints
async function callServer(url, data) {
const res = await fetch(url, {
method: "POST",
body: data ? JSON.stringify(data) : "",
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
}
});
return await res.json();
}
// Handles responses sent from your server to the client
function handleServerResponse(res, component) {
if (res.action) {
component.handleAction(res.action);
} else {
switch (res.resultCode) {
case "Authorised":
window.location.href = "/result/success";
break;
case "Pending":
case "Received":
window.location.href = "/result/pending";
break;
case "Refused":
window.location.href = "/result/failed";
break;
default:
window.location.href = "/result/error";
break;
}
}
}
if (!sessionId) {
startCheckout();
}
else {
// existing session: complete Checkout
finalizeCheckout();
}

View File

@@ -0,0 +1,31 @@
{% extends "layout.html" %}
{% block content %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!-- Adyen CSS from TEST environment (change to live for production)-->
<link rel="stylesheet"
href="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/4.9.0/adyen.css"
integrity="sha384-0IvbHDeulbhdg1tMDeFeGlmjiYoVT6YsbfAMKFU2lFd6YKUVk0Hgivcmva3j6mkK"
crossorigin="anonymous">
<div id="payment-page">
<div class="container">
<div id="amazonpay_payment-container"></div>
</div>
</div>
<!-- Adyen JS from TEST environment (change to live for production)-->
<script src="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/4.9.0/adyen.js"
integrity="sha384-aEL1fltFqDd33ItS8N+aAdd44ida67AQctv9h57pBGjNJ8E2xxbX/CVALJqO8/aM"
crossorigin="anonymous"></script>
<script id="client-key" type="application/json">{{ client_key|tojson }}</script>
<script id="integration-type" type="application/json">{{ method|tojson }}</script>
<script id="country-code" type="application/json">{{ countryCode|default("GB")|tojson }}</script>
<script src="{{ url_for('static', filename='js/amazonRedirect.js') }}"></script>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{% extends "layout.html" %}
{% block content %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!-- Adyen CSS from TEST environment (change to live for production)-->
<link rel="stylesheet"
href="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/4.9.0/adyen.css"
integrity="sha384-0IvbHDeulbhdg1tMDeFeGlmjiYoVT6YsbfAMKFU2lFd6YKUVk0Hgivcmva3j6mkK"
crossorigin="anonymous">
<div id="payment-page">
<div class="container">
<div id="dropin-container" class="payment">
<!-- Component will be rendered here -->
</div>
<div id="amazonpay_payment-container"></div>
<div id="amazonpay_button-container"></div>
</div>
<div class="info">
<p>
Check the Source Code to see the full implementation.
</p>
<p>
To make a payment, use our <a
href="https://docs.adyen.com/developers/development-resources/test-cards/test-card-numbers"
target="_blank">test card numbers</a>.
</p>
</div>
</div>
<!-- Adyen JS from TEST environment (change to live for production)-->
<script src="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/4.9.0/adyen.js"
integrity="sha384-aEL1fltFqDd33ItS8N+aAdd44ida67AQctv9h57pBGjNJ8E2xxbX/CVALJqO8/aM"
crossorigin="anonymous"></script>
<script id="client-key" type="application/json">{{ client_key|tojson }}</script>
<script id="integration-type" type="application/json">{{ method|tojson }}</script>
<script id="country-code" type="application/json">{{ countryCode|default("GB")|tojson }}</script>
<script src="{{ url_for('static', filename='js/amazonpay.js') }}"></script>
{% endblock %}

97
app/templates/cart.html Normal file
View File

@@ -0,0 +1,97 @@
{% extends "layout.html" %}
{% block content %}
<main>
<section class="cart">
<h2>Cart</h2>
<div>
<ul>
<li class="order-summary-list-list-item first-item">
<img src="{{ url_for('static', filename='img/sunglasses.png') }}"
class="order-summary-list-list-item-image"/>
<p class="order-summary-list-list-item-title">Sunglasses</p>
<p id="item_1_cost" class="order-summary-list-list-item-price">€20.00</p>
</li>
<li class="order-summary-list-list-item">
<img src="{{ url_for('static', filename='img/headphones.png') }}"
class="order-summary-list-list-item-image"/>
<p class="order-summary-list-list-item-title">Headphones</p>
<p id="item_2_cost" class="order-summary-list-list-item-price">€20.00</p>
</li>
</ul>
</div>
<div class="cart-footer"><span class="cart-footer-label">Total:</span><span id="total_cost" class="cart-footer-amount">€40.00</span>
<a id="checkout_button" href="{{ url_for('checkout', integration=method) }}">
<p class="button">Continue to checkout</p>
</a>
</div>
</section>
<footer class="footer">
<div class="country-selector">
<div class="country-selector__flag" aria-hidden="true">
<img id="flag_img" src="https://ca-test.adyen.com/ca/adl/img/flags/nl.svg" alt="Flag icon">
</div>
<select onchange="changeSelect(this)" id="country_select" class="country-selector__select" aria-label="country" autocomplete="off">
<option value="NL">Netherlands</option>
<option value="GB">United Kingdom</option>
<option value="US">United States</option>
</select>
<div class="dropdown-icon" aria-hidden="true">
<img src="{{ url_for('static', filename='img/dropdown.svg') }}" alt="Dropdown caret">
</div>
</div>
</footer>
</main>
<script>
const pay_button = document.getElementById('checkout_button');
const old_href = pay_button.href;
//const country = "NL";
//let new_href = `${old_href}?country=${country}`;
updateUrl()
const flagUrlMap = {
"NL" : {
"src": "https://ca-test.adyen.com/ca/adl/img/flags/nl.svg",
"item1": "€20.00",
"item2": "€20.00",
"total": "€40.00",
"currency": "EUR",
"href": "{{ url_for('checkout', integration=method, country=NL) }}"
},
"GB" : {
"src": "https://ca-test.adyen.com/ca/adl/img/flags/gb.svg",
"item1": "£20.00",
"item2": "£20.00",
"total": "£40.00",
"currency": "GBP",
"href": "{{ url_for('checkout', integration=method, country=GB) }}"
},
"US" : {
"src": "https://ca-test.adyen.com/ca/adl/img/flags/us.svg",
"item1": "$20.00",
"item2": "$20.00",
"total": "$40.00",
"href": "{{ url_for('checkout', integration=method, country=US) }}"
}
};
pay_button.href = new_href;
function changeSelect(el) {
document.getElementById('flag_img').src = flagUrlMap[el.value].src;
document.getElementById("item_1_cost").innerHTML = flagUrlMap[el.value].item1;
document.getElementById("item_2_cost").innerHTML = flagUrlMap[el.value].item2;
document.getElementById("total_cost").innerHTML = flagUrlMap[el.value].total;
const country = el.value;
updateUrl(country)
}
function updateUrl(country = "NL") {
const new_href = `${old_href}?country=${country}`;
pay_button.href = new_href
}
</script>
{% endblock %}

View File

@@ -0,0 +1,13 @@
{% extends "layout.html" %}
{% block content %}
<title>Confirmation</title>
<div class="status-container">
<div class="status">
<img src="{{ url_for('static', filename='img/failed.svg') }}" class="status-image"/>
<p class="status-message">Your order has failed. Please try again.</p>
<a class="button" href="/">Try Again</a>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends "layout.html" %}
{% block content %}
<title>Confirmation</title>
<div class="status-container">
<div class="status">
<img src="{{ url_for('static', filename='img/success.svg') }}" class="status-image"/>
<img src="{{ url_for('static', filename='img/thank-you.svg') }}" class="status-image-thank-you"/>
<p class="status-message">Your order has been successfully placed.</p>
<a class="button" href="/">Return Home</a>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,64 @@
{% extends "layout.html" %}
{% block content %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!-- Adyen CSS from TEST environment (change to live for production)-->
<link rel="stylesheet"
href="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/5.15.0/adyen.css"
integrity="sha384-Dm1w8jaVOWA8rxpzkxA41DIyw5VlBjpoMTPfAijlfepYGgLKF+hke3NKeU/KTX7t"
crossorigin="anonymous">
<!-- Bootstrap-colorpicker CSS -->
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-colorpicker/3.4.0/css/bootstrap-colorpicker.min.css"
integrity="sha512-m/uSzCYYP5f55d4nUi9mnY9m49I8T+GUEe4OQd3fYTpFU9CIaPazUG/f8yUkY0EWlXBJnpsA7IToT2ljMgB87Q==" crossorigin="anonymous"
referrerpolicy="no-referrer" /> -->
<div id="payment-page" class="row">
<div class="checkout col">
<div id="dropin-container" class="payment">
<!-- Component will be rendered here -->
</div>
</div>
<div class="settings col">
<div class="country-selector">
<div class="country-selector__flag" aria-hidden="true">
<img id="flag_img" src="https://ca-test.adyen.com/ca/adl/img/flags/nl.svg" alt="Flag icon">
</div>
<select onchange="changeSelect(this)" id="country_select" class="country-selector__select" aria-label="country" autocomplete="off">
<option value="NL">Netherlands</option>
<option value="GB">United Kingdom</option>
<option value="US">United States</option>
</select>
<div class="dropdown-icon" aria-hidden="true">
<img src="{{ url_for('static', filename='img/dropdown.svg') }}" alt="Dropdown caret">
</div>
</div>
</div>
</div>
<!-- Adyen JS from TEST environment (change to live for production)-->
<script src="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/5.15.0/adyen.js"
integrity="sha384-vMZOl6V83EY2UXaXsPUxH5Pt5VpyLeHpSFnANBVjcH5l7yZmJO0QBl3s6XbKwjiN"
crossorigin="anonymous"></script>
<!-- bootstrap-colorpicker JS -->
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-colorpicker/3.4.0/js/bootstrap-colorpicker.min.js"
integrity="sha512-94dgCw8xWrVcgkmOc2fwKjO4dqy/X3q7IjFru6MHJKeaAzCvhkVtOS6S+co+RbcZvvPBngLzuVMApmxkuWZGwQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script> -->
<!-- data from front-end -->
<script id="client-key" type="application/json">{{ client_key|tojson }}</script>
<script id="country-code" type="application/json">{{ countryCode|default("GB")|tojson }}</script>
<!-- local JS -->
<script src="{{ url_for('static', filename='js/adyen-implementation.js') }}"></script>
{% endblock %}

18
app/templates/error.html Normal file
View File

@@ -0,0 +1,18 @@
{% extends "layout.html" %}
{% block title %}Page Not Found{% endblock %}
{% block content %}
<p/>
<p/>
<title>Error</title>
<div class="status-container">
<div class="status">
<p class="status-message">Error!</p>
<p class="status-message">Review response in console and refer to <a
href="https://docs.adyen.com/development-resources/response-handling">Response handling.</a></p>
<a class="button" href="{{ url_for('home') }}">Return Home</a>
</div>
</div>
{% endblock %}

137
app/templates/home.html Normal file
View File

@@ -0,0 +1,137 @@
{% extends "layout.html" %}
{% block content %}
<div class='main-container'>
<div class="info">
<h1>Ana's test demo</h1>
</div>
<div>
<h3 class="h3 title-container">Implementation via Sessions</h3>
</div>
<ul class="integration-list">
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="dropin") }}">
<div class="title-container">
<p class="integration-list-item-title">Drop-in</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="card") }}">
<div class="title-container">
<p class="integration-list-item-title">Card</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="ideal") }}">
<div class="title-container">
<p class="integration-list-item-title">iDEAL</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="klarna") }}">
<div class="title-container">
<p class="integration-list-item-title">Klarna</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="directEbanking") }}">
<div class="title-container">
<p class="integration-list-item-title">Sofort</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="alipay") }}">
<div class="title-container">
<p class="integration-list-item-title">Alipay</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="googlepay") }}">
<div class="title-container">
<p class="integration-list-item-title">GooglePay</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="sepadirectdebit") }}">
<div class="title-container">
<p class="integration-list-item-title">SEPA Direct Debit</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="clearpay") }}">
<div class="title-container">
<p class="integration-list-item-title">ClearPay</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="giropay") }}">
<div class="title-container">
<p class="integration-list-item-title">Giropay</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="ach") }}">
<div class="title-container">
<p class="integration-list-item-title">ACH</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="paypal") }}">
<div class="title-container">
<p class="integration-list-item-title">PayPal</p>
</div>
</a>
</li>
</ul>
<div>
<h3 class="h3 title-container">Advanced Checkout - Non Sessions</h3>
</div>
<ul class="integration-list">
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="nonsessions") }}">
<div class="title-container">
<p class="integration-list-item-title">Drop-in</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="nonsessionCard") }}">
<div class="title-container">
<p class="integration-list-item-title">Card</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="nonsessionIdeal") }}">
<div class="title-container">
<p class="integration-list-item-title">iDEAL</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="paybylink") }}">
<div class="title-container">
<p class="integration-list-item-title">Pay By Link</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="{{ url_for('cart', integration="amazonpay") }}">
<div class="title-container">
<p class="integration-list-item-title">Amazon Pay</p>
</div>
</a>
</li>
</ul>
</div>
{% endblock %}

36
app/templates/layout.html Normal file
View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/application.css') }}" />
<style>
@import url('https://fonts.googleapis.com/css2?family=Oleo+Script:wght@700&display=swap');
</style>
<title>Checkout Demo</title>
</head>
<body>
<header id="header">
<div class="logo-box">
<a href="{{ url_for('home') }}">
<img
class="logo-image"
src="{{ url_for('static', filename='img/mystore-logo.svg') }}"
alt="BARB logo"
/>
</a>
<!--<a class="logo-title">All Things Retro</a>-->
</div>
</header>
<div class="container">
{% block content %} {% endblock %}
</div>
</body>
</html>

View File

@@ -0,0 +1,42 @@
{% extends "layout.html" %}
{% block content %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!-- Adyen CSS from TEST environment (change to live for production)-->
<link rel="stylesheet" href="https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/5.4.0/adyen.css"
integrity="sha384-47lm6XSs4AdvQN9BdRTZykpp82IALHlxMtM5p378Nsg3O3nGoBB86N0d7GXgjrA3"
crossorigin="anonymous" />
<div id="payment-page">
<div class="container">
<div class="checkout-component-container">
<div id="component" class="payment">
<!-- Component will be rendered here -->
</div>
</div>
</div>
</div>
<!-- Adyen JS from TEST environment (change to live for production)-->
<script src="https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/5.4.0/adyen.js"
integrity="sha384-TrjObe7+xENaI4cONDkap2qTzoJIfTaC79+mJuANZ5Y1IxLMvqpPKcmgl3IrqCk3"
crossorigin="anonymous"></script>
<script id="client-key" type="application/json">{{ client_key|tojson }}</script>
<script id="integration-type" type="application/json">{{ method|tojson }}</script>
<!--script type="application/javascript">
const queryString = window.location.search;
console.log(queryString);
const urlCountry = new URLSearchParams(queryString);
const country = urlCountry.get('country')
console.log(country);
</script-->
<script id="country-code" type="application/json">{{ countryCode|default("GB")|tojson }}</script>
<script src="{{ url_for('static', filename='js/adyen-implementation.js') }}"></script>
{% endblock %}

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
flask
Adyen == 6.0.0
python-dotenv===0.19.2
requests == 2.27.1

4
setup.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env bash
python3 -m venv venv
. venv/bin/activate
pip install -r requirements.txt

5
start.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
export FLASK_ENV=development
python3 -m venv venv
. venv/bin/activate
python3 app/app.py

BIN
venv/.DS_Store vendored Normal file

Binary file not shown.

76
venv/bin/activate Normal file
View File

@@ -0,0 +1,76 @@
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly
deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r
fi
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
if [ ! "$1" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
VIRTUAL_ENV="/Users/anamo/Desktop/Demos/Hackathon/CheckoutCreate/venv"
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/bin:$PATH"
export PATH
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
if [ "x(venv) " != x ] ; then
PS1="(venv) ${PS1:-}"
else
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
else
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
fi
fi
export PS1
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r
fi

37
venv/bin/activate.csh Normal file
View File

@@ -0,0 +1,37 @@
# This file must be used with "source bin/activate.csh" *from csh*.
# You cannot run it directly.
# Created by Davide Di Blasi <davidedb@gmail.com>.
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
# Unset irrelevant variables.
deactivate nondestructive
setenv VIRTUAL_ENV "/Users/anamo/Desktop/Demos/Hackathon/CheckoutCreate/venv"
set _OLD_VIRTUAL_PATH="$PATH"
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
set _OLD_VIRTUAL_PROMPT="$prompt"
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
if ("venv" != "") then
set env_name = "venv"
else
if (`basename "VIRTUAL_ENV"` == "__") then
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
else
set env_name = `basename "$VIRTUAL_ENV"`
endif
endif
set prompt = "[$env_name] $prompt"
unset env_name
endif
alias pydoc python -m pydoc
rehash

75
venv/bin/activate.fish Normal file
View File

@@ -0,0 +1,75 @@
# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org)
# you cannot run it directly
function deactivate -d "Exit virtualenv and return to normal shell environment"
# reset old environment variables
if test -n "$_OLD_VIRTUAL_PATH"
set -gx PATH $_OLD_VIRTUAL_PATH
set -e _OLD_VIRTUAL_PATH
end
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
set -e _OLD_VIRTUAL_PYTHONHOME
end
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
functions -e fish_prompt
set -e _OLD_FISH_PROMPT_OVERRIDE
functions -c _old_fish_prompt fish_prompt
functions -e _old_fish_prompt
end
set -e VIRTUAL_ENV
if test "$argv[1]" != "nondestructive"
# Self destruct!
functions -e deactivate
end
end
# unset irrelevant variables
deactivate nondestructive
set -gx VIRTUAL_ENV "/Users/anamo/Desktop/Demos/Hackathon/CheckoutCreate/venv"
set -gx _OLD_VIRTUAL_PATH $PATH
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
# unset PYTHONHOME if set
if set -q PYTHONHOME
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
set -e PYTHONHOME
end
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
# fish uses a function instead of an env var to generate the prompt.
# save the current fish_prompt function as the function _old_fish_prompt
functions -c fish_prompt _old_fish_prompt
# with the original prompt function renamed, we can override with our own.
function fish_prompt
# Save the return status of the last command
set -l old_status $status
# Prompt override?
if test -n "(venv) "
printf "%s%s" "(venv) " (set_color normal)
else
# ...Otherwise, prepend env
set -l _checkbase (basename "$VIRTUAL_ENV")
if test $_checkbase = "__"
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal)
else
printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)
end
end
# Restore the return status of the previous command.
echo "exit $old_status" | .
_old_fish_prompt
end
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
end

11
venv/bin/dotenv Executable file
View File

@@ -0,0 +1,11 @@
#!/Users/anamo/Desktop/Demos/WebCheckoutComponent/adyen-python-online-payments/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from dotenv.cli import cli
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(cli())

11
venv/bin/easy_install Executable file
View File

@@ -0,0 +1,11 @@
#!/Users/anamo/Desktop/Demos/WebCheckoutComponent/adyen-python-online-payments/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from setuptools.command.easy_install import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())

11
venv/bin/easy_install-3.7 Executable file
View File

@@ -0,0 +1,11 @@
#!/Users/anamo/Desktop/Demos/WebCheckoutComponent/adyen-python-online-payments/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from setuptools.command.easy_install import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())

11
venv/bin/flask Executable file
View File

@@ -0,0 +1,11 @@
#!/Users/anamo/Desktop/Demos/WebCheckoutComponent/adyen-python-online-payments/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from flask.cli import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())

11
venv/bin/normalizer Executable file
View File

@@ -0,0 +1,11 @@
#!/Users/anamo/Desktop/Demos/WebCheckoutComponent/adyen-python-online-payments/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from charset_normalizer.cli.normalizer import cli_detect
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(cli_detect())

11
venv/bin/pip Executable file
View File

@@ -0,0 +1,11 @@
#!/Users/anamo/Desktop/Demos/WebCheckoutComponent/adyen-python-online-payments/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())

11
venv/bin/pip3 Executable file
View File

@@ -0,0 +1,11 @@
#!/Users/anamo/Desktop/Demos/WebCheckoutComponent/adyen-python-online-payments/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())

11
venv/bin/pip3.7 Executable file
View File

@@ -0,0 +1,11 @@
#!/Users/anamo/Desktop/Demos/WebCheckoutComponent/adyen-python-online-payments/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())

1
venv/bin/python Symbolic link
View File

@@ -0,0 +1 @@
python3

1
venv/bin/python3 Symbolic link
View File

@@ -0,0 +1 @@
/usr/local/adyen/python/bin/python3

BIN
venv/lib/.DS_Store vendored Normal file

Binary file not shown.

BIN
venv/lib/python3.7/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,19 @@
Metadata-Version: 1.2
Name: Adyen
Version: 6.0.0
Summary: Adyen Python Api
Home-page: https://github.com/Adyen/adyen-python-api-library
Author: Adyen
Author-email: support@adyen.com
Maintainer: Adyen
Maintainer-email: support@adyen.com
License: UNKNOWN
Description: A Python client library for accessing Adyen APIs
Keywords: payments,adyen,fintech
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.6

View File

@@ -0,0 +1,14 @@
README.md
setup.cfg
setup.py
Adyen/__init__.py
Adyen/client.py
Adyen/exceptions.py
Adyen/httpclient.py
Adyen/services.py
Adyen/settings.py
Adyen/util.py
Adyen.egg-info/PKG-INFO
Adyen.egg-info/SOURCES.txt
Adyen.egg-info/dependency_links.txt
Adyen.egg-info/top_level.txt

View File

@@ -0,0 +1,18 @@
../Adyen/__init__.py
../Adyen/__pycache__/__init__.cpython-37.pyc
../Adyen/__pycache__/client.cpython-37.pyc
../Adyen/__pycache__/exceptions.cpython-37.pyc
../Adyen/__pycache__/httpclient.cpython-37.pyc
../Adyen/__pycache__/services.cpython-37.pyc
../Adyen/__pycache__/settings.cpython-37.pyc
../Adyen/__pycache__/util.cpython-37.pyc
../Adyen/client.py
../Adyen/exceptions.py
../Adyen/httpclient.py
../Adyen/services.py
../Adyen/settings.py
../Adyen/util.py
PKG-INFO
SOURCES.txt
dependency_links.txt
top_level.txt

View File

@@ -0,0 +1,46 @@
#!/bin/python
from __future__ import absolute_import, division, unicode_literals
from . import util
from .util import generate_hpp_sig
from .exceptions import (
AdyenAPICommunicationError,
AdyenAPIAuthenticationError,
AdyenAPIInvalidPermission,
AdyenAPIValidationError,
AdyenInvalidRequestError,
AdyenError
)
from .client import AdyenClient
from .services import (
AdyenBase,
AdyenBinLookup,
AdyenRecurring,
AdyenPayment,
AdyenThirdPartyPayout,
AdyenHPP,
AdyenCheckoutApi
)
from .httpclient import HTTPClient
class Adyen(AdyenBase):
def __init__(self, **kwargs):
self.client = AdyenClient(**kwargs)
self.payment = AdyenPayment(client=self.client)
self.binlookup = AdyenBinLookup(client=self.client)
self.payout = AdyenThirdPartyPayout(client=self.client)
self.hpp = AdyenHPP(client=self.client)
self.recurring = AdyenRecurring(client=self.client)
self.checkout = AdyenCheckoutApi(client=self.client)
_base_adyen_obj = Adyen()
recurring = _base_adyen_obj.recurring
hpp = _base_adyen_obj.hpp
payment = _base_adyen_obj.payment
payout = _base_adyen_obj.payout
checkout = _base_adyen_obj.checkout
binlookup = _base_adyen_obj.binlookup

View File

@@ -0,0 +1,779 @@
#!/bin/python
from __future__ import absolute_import, division, unicode_literals
import json as json_lib
import re
from . import util
from .httpclient import HTTPClient
from .exceptions import (
AdyenAPICommunicationError,
AdyenAPIAuthenticationError,
AdyenAPIInvalidPermission,
AdyenAPIValidationError,
AdyenInvalidRequestError,
AdyenAPIInvalidFormat,
AdyenAPIInvalidAmount,
AdyenEndpointInvalidFormat)
from . import settings
class AdyenResult(object):
"""
Args:
message (dict, optional): Parsed message returned from API client.
status_code (int, optional): Default 200. HTTP response code, ie 200,
404, 500, etc.
psp (str, optional): Psp reference returned by Adyen for a payment.
raw_request (str, optional): Raw request placed to Adyen.
raw_response (str, optional): Raw response returned by Adyen.
"""
def __init__(self, message=None, status_code=200,
psp="", raw_request="", raw_response=""):
self.message = message
self.status_code = status_code
self.psp = psp
self.raw_request = raw_request
self.raw_response = raw_response
self.details = {}
def __str__(self):
return repr(self.message)
class AdyenClient(object):
IDEMPOTENCY_HEADER_NAME = 'Idempotency-Key'
"""A requesting client that interacts with Adyen. This class holds the
adyen logic of Adyen HTTP API communication. This is the object that can
maintain its own username, password, merchant_account, hmac and skin_code.
When these values aren't within this object, the root adyen module
variables will be used.
The public methods, call_api and call_hpp, only return AdyenResult objects.
Otherwise raising various validation and communication errors.
Args:
username (str, optional): Username of webservice user
password (str, optional): Password of webservice user
merchant_account (str, optional): Merchant account for requests to be
placed through
platform (str, optional): Defaults "test". The Adyen platform to make
requests against.
skin_code (str, optional): skin_code to place directory_lookup requests
and generate hpp signatures with.
hmac (str, optional): Hmac key that is used for signature calculation.
http_timeout (int, optional): The timeout in seconds for HTTP calls,
default 30.
"""
def __init__(
self,
username=None,
password=None,
xapikey=None,
review_payout_username=None,
review_payout_password=None,
store_payout_username=None, store_payout_password=None,
platform="test", merchant_account=None,
merchant_specific_url=None, skin_code=None,
hmac=None,
http_force=None,
live_endpoint_prefix=None,
http_timeout=30,
api_bin_lookup_version=None,
api_checkout_utility_version=None,
api_checkout_version=None,
api_payment_version=None,
api_payout_version=None,
api_recurring_version=None,
):
self.username = username
self.password = password
self.xapikey = xapikey
self.review_payout_username = review_payout_username
self.review_payout_password = review_payout_password
self.store_payout_username = store_payout_username
self.store_payout_password = store_payout_password
self.platform = platform
self.merchant_specific_url = merchant_specific_url
self.hmac = hmac
self.merchant_account = merchant_account
self.skin_code = skin_code
self.psp_list = []
self.LIB_VERSION = settings.LIB_VERSION
self.USER_AGENT_SUFFIX = settings.LIB_NAME + "/"
self.http_init = False
self.http_force = http_force
self.live_endpoint_prefix = live_endpoint_prefix
self.http_timeout = http_timeout
self.api_bin_lookup_version = api_bin_lookup_version or settings.API_BIN_LOOKUP_VERSION
self.api_checkout_utility_version = api_checkout_utility_version or settings.API_CHECKOUT_UTILITY_VERSION
self.api_checkout_version = api_checkout_version or settings.API_CHECKOUT_VERSION
self.api_payment_version = api_payment_version or settings.API_PAYMENT_VERSION
self.api_payout_version = api_payout_version or settings.API_PAYOUT_VERSION
self.api_recurring_version = api_recurring_version or settings.API_RECURRING_VERSION
def _determine_api_url(self, platform, service, action):
"""This returns the Adyen API endpoint based on the provided platform,
service and action.
Args:
platform (str): Adyen platform, ie 'live' or 'test'.
service (str): API service to place request through.
action (str): the API action to perform.
"""
if platform == "live" and self.live_endpoint_prefix:
base_uri = settings.PAL_LIVE_ENDPOINT_URL_TEMPLATE.format(
self.live_endpoint_prefix
)
else:
base_uri = settings.BASE_PAL_URL.format(platform)
if service == "Recurring":
api_version = self.api_recurring_version
elif service == "Payout":
api_version = self.api_payout_version
elif service == "BinLookup":
api_version = self.api_bin_lookup_version
else:
api_version = self.api_payment_version
return '/'.join([base_uri, service, api_version, action])
@staticmethod
def _determine_hpp_url(platform, action):
"""This returns the Adyen HPP endpoint based on the provided platform,
and action.
Args:
platform (str): Adyen platform, ie 'live' or 'test'.
action (str): the HPP action to perform.
possible actions: select, pay, skipDetails, directory
"""
base_uri = settings.BASE_HPP_URL.format(platform)
service = action + '.shtml'
result = '/'.join([base_uri, service])
return result
def _determine_checkout_url(self, platform, action):
"""This returns the Adyen API endpoint based on the provided platform,
service and action.
Args:
platform (str): Adyen platform, ie 'live' or 'test'.
action (str): the API action to perform.
"""
api_version = self.api_checkout_version
if platform == "test":
base_uri = settings.ENDPOINT_CHECKOUT_TEST
elif self.live_endpoint_prefix is not None and platform == "live":
base_uri = settings.ENDPOINT_CHECKOUT_LIVE_SUFFIX.format(
self.live_endpoint_prefix)
elif self.live_endpoint_prefix is None and platform == "live":
errorstring = """Please set your live suffix. You can set it
by running 'settings.
ENDPOINT_CHECKOUT_LIVE_SUFFIX = 'Your live suffix'"""
raise AdyenEndpointInvalidFormat(errorstring)
else:
raise AdyenEndpointInvalidFormat("invalid config")
if action == "paymentsDetails":
action = "payments/details"
if action == "paymentsResult":
action = "payments/result"
if action == "originKeys":
api_version = self.api_checkout_utility_version
if action == "paymentMethodsBalance":
action = "paymentMethods/balance"
if action == "ordersCancel":
action = "orders/cancel"
return '/'.join([base_uri, api_version, action])
def _review_payout_username(self, **kwargs):
if 'username' in kwargs:
return kwargs['username']
elif self.review_payout_username:
return self.review_payout_username
errorstring = """Please set your review payout
webservice username. You can do this by running
'Adyen.review_payout_username = 'Your payout username' """
raise AdyenInvalidRequestError(errorstring)
def _review_payout_pass(self, **kwargs):
if 'password' in kwargs:
return kwargs["password"]
elif self.review_payout_password:
return self.review_payout_password
errorstring = """Please set your review payout
webservice password. You can do this by running
'Adyen.review_payout_password = 'Your payout password'"""
raise AdyenInvalidRequestError(errorstring)
def _store_payout_username(self, **kwargs):
if 'username' in kwargs:
return kwargs['username']
elif self.store_payout_username:
return self.store_payout_username
errorstring = """Please set your store payout
webservice username. You can do this by running
'Adyen.store_payout_username = 'Your payout username'"""
raise AdyenInvalidRequestError(errorstring)
def _store_payout_pass(self, **kwargs):
if 'password' in kwargs:
return kwargs["password"]
elif self.store_payout_password:
return self.store_payout_password
errorstring = """Please set your store payout
webservice password. You can do this by running
'Adyen.store_payout_password = 'Your payout password'"""
raise AdyenInvalidRequestError(errorstring)
def call_api(
self,
request_data,
service,
action,
idempotency_key=None,
**kwargs
):
"""This will call the adyen api. username, password, merchant_account,
and platform are pulled from root module level and or self object.
AdyenResult will be returned on 200 response. Otherwise, an exception
is raised.
Args:
idempotency_key: https://docs.adyen.com/development-resources
/api-idempotency
request_data (dict): The dictionary of the request to place. This
should be in the structure of the Adyen API.
https://docs.adyen.com/api-explorer
service (str): This is the API service to be called.
action (str): The specific action of the API service to be called
idempotency (bool, optional): Whether the transaction should be
processed idempotently.
https://docs.adyen.com/development-resources/api-idempotency
Returns:
AdyenResult: The AdyenResult is returned when a request was
successful.
"""
if not self.http_init:
self._init_http_client()
# username at self object has highest priority. fallback to root module
# and ensure that it is set.
xapikey = None
if self.xapikey:
xapikey = self.xapikey
elif 'xapikey' in kwargs:
xapikey = kwargs.pop("xapikey")
username = None
if self.username:
username = self.username
elif 'username' in kwargs:
username = kwargs.pop("username")
if service == "Payout":
if any(substring in action for substring in
["store", "submit"]):
username = self._store_payout_username(**kwargs)
else:
username = self._review_payout_username(**kwargs)
if not username and not xapikey:
errorstring = """Please set your webservice username.
You can do this by running
'Adyen.username = 'Your username'"""
raise AdyenInvalidRequestError(errorstring)
# password at self object has highest priority.
# fallback to root module
# and ensure that it is set.
password = None
if self.password and not xapikey:
password = self.password
elif 'password' in kwargs:
password = kwargs.pop("password")
if service == "Payout":
if any(substring in action for substring in
["store", "submit"]):
password = self._store_payout_pass(**kwargs)
else:
password = self._review_payout_pass(**kwargs)
if not password and not xapikey:
errorstring = """Please set your webservice password.
You can do this by running
'Adyen.password = 'Your password'"""
raise AdyenInvalidRequestError(errorstring)
# xapikey at self object has highest priority.
# fallback to root module
# and ensure that it is set.
# platform at self object has highest priority. fallback to root module
# and ensure that it is set to either 'live' or 'test'.
platform = None
if self.platform:
platform = self.platform
elif 'platform' in kwargs:
platform = kwargs.pop('platform')
if not isinstance(platform, str):
errorstring = "'platform' value must be type of string"
raise TypeError(errorstring)
elif platform.lower() not in ['live', 'test']:
errorstring = "'platform' must be the value of 'live' or 'test'"
raise ValueError(errorstring)
message = request_data
if not message.get('merchantAccount'):
message['merchantAccount'] = self.merchant_account
# Add application info
if 'applicationInfo' in request_data:
request_data['applicationInfo'].update({
"adyenLibrary": {
"name": settings.LIB_NAME,
"version": settings.LIB_VERSION
}
})
else:
request_data['applicationInfo'] = {
"adyenLibrary": {
"name": settings.LIB_NAME,
"version": settings.LIB_VERSION
}
}
# Adyen requires this header to be set and uses the combination of
# merchant account and merchant reference to determine uniqueness.
headers = {}
if idempotency_key:
headers[self.IDEMPOTENCY_HEADER_NAME] = idempotency_key
url = self._determine_api_url(platform, service, action)
if xapikey:
raw_response, raw_request, status_code, headers = \
self.http_client.request(url, json=request_data,
xapikey=xapikey, headers=headers,
**kwargs)
else:
raw_response, raw_request, status_code, headers = \
self.http_client.request(url, json=message, username=username,
password=password,
headers=headers,
**kwargs)
# Creates AdyenResponse if request was successful, raises error if not.
adyen_result = self._handle_response(url, raw_response, raw_request,
status_code, headers, message)
return adyen_result
def _init_http_client(self):
self.http_client = HTTPClient(
user_agent_suffix=self.USER_AGENT_SUFFIX,
lib_version=self.LIB_VERSION,
force_request=self.http_force,
timeout=self.http_timeout,
)
self.http_init = True
def call_hpp(self, message, action, hmac_key="", **kwargs):
"""This will call the adyen hpp. hmac_key and platform are pulled from
root module level and or self object.
AdyenResult will be returned on 200 response.
Otherwise, an exception is raised.
Args:
request_data (dict): The dictionary of the request to place. This
should be in the structure of the Adyen API.
https://docs.adyen.com/online-payments/classic-integrations/hosted-payment-pages/hosted-payment-pages-api
service (str): This is the API service to be called.
action (str): The specific action of the API service to be called
Returns:
AdyenResult: The AdyenResult is returned when a request was
succesful.
:param message:
:param hmac_key:
"""
if not self.http_init:
self._init_http_client()
# hmac provided in function has highest priority. fallback to self then
# root module and ensure that it is set.
hmac = hmac_key
if self.hmac:
hmac = self.hmac
elif not hmac:
errorstring = """Please set an hmac with your Adyen.Adyen
class instance.
'Adyen.hmac = \"!WR#F@...\"' or as an additional
parameter in the function call ie.
'Adyen.hpp.directory_lookup(hmac=\"!WR#F@...\"'. Please reach
out to support@Adyen.com if the issue persists."""
raise AdyenInvalidRequestError(errorstring)
# platform provided in self has highest priority,
# fallback to root module and ensure that it is set.
platform = self.platform
if not isinstance(platform, str):
errorstring = "'platform' must be type string"
raise TypeError(errorstring)
elif platform.lower() not in ['live', 'test']:
errorstring = " 'platform' must be the value of 'live' or 'test' "
raise ValueError(errorstring)
if 'skinCode' not in message:
message['skinCode'] = self.skin_code
if 'merchantAccount' not in message:
message['merchantAccount'] = self.merchant_account
if message['merchantAccount'] == "":
message['merchantAccount'] = self.merchant_account
message["merchantSig"] = util.generate_hpp_sig(message, hmac)
url = self._determine_hpp_url(platform, action)
raw_response, raw_request, status_code, headers = \
self.http_client.request(url, data=message,
username="", password="", **kwargs)
# Creates AdyenResponse if request was successful, raises error if not.
adyen_result = self._handle_response(url, raw_response, raw_request,
status_code, headers, message)
return adyen_result
def call_checkout_api(self, request_data, action, idempotency_key=None,
**kwargs):
"""This will call the checkout adyen api. xapi key merchant_account,
and platform are pulled from root module level and or self object.
AdyenResult will be returned on 200 response. Otherwise, an exception
is raised.
Args:
idempotency_key: https://docs.adyen.com/development-resources
/api-idempotency
request_data (dict): The dictionary of the request to place. This
should be in the structure of the Adyen API.
https://docs.adyen.com/api-explorer/#/CheckoutService
service (str): This is the API service to be called.
action (str): The specific action of the API service to be called
"""
if not self.http_init:
self._init_http_client()
# xapi at self object has highest priority. fallback to root module
# and ensure that it is set.
xapikey = False
if self.xapikey:
xapikey = self.xapikey
elif 'xapikey' in kwargs:
xapikey = kwargs.pop("xapikey")
if not xapikey:
errorstring = """Please set your webservice xapikey.
You can do this by running 'Adyen.xapikey = 'Your xapikey'"""
raise AdyenInvalidRequestError(errorstring)
# platform at self object has highest priority. fallback to root module
# and ensure that it is set to either 'live' or 'test'.
platform = None
if self.platform:
platform = self.platform
elif 'platform' in kwargs:
platform = kwargs.pop('platform')
if not isinstance(platform, str):
errorstring = "'platform' value must be type of string"
raise TypeError(errorstring)
elif platform.lower() not in ['live', 'test']:
errorstring = "'platform' must be the value of 'live' or 'test'"
raise ValueError(errorstring)
if not request_data.get('merchantAccount'):
request_data['merchantAccount'] = self.merchant_account
with_app_info = [
"authorise",
"authorise3d",
"authorise3ds2",
"payments",
"paymentSession",
"paymentLinks",
"paymentMethodsBalance",
"sessions"
]
if action in with_app_info:
if 'applicationInfo' in request_data:
request_data['applicationInfo'].update({
"adyenLibrary": {
"name": settings.LIB_NAME,
"version": settings.LIB_VERSION
}
})
else:
request_data['applicationInfo'] = {
"adyenLibrary": {
"name": settings.LIB_NAME,
"version": settings.LIB_VERSION
}
}
# Adyen requires this header to be set and uses the combination of
# merchant account and merchant reference to determine uniqueness.
headers = {}
if idempotency_key:
headers[self.IDEMPOTENCY_HEADER_NAME] = idempotency_key
url = self._determine_checkout_url(platform, action)
raw_response, raw_request, status_code, headers = \
self.http_client.request(url, json=request_data,
xapikey=xapikey, headers=headers,
**kwargs)
# Creates AdyenResponse if request was successful, raises error if not.
adyen_result = self._handle_response(url, raw_response, raw_request,
status_code, headers,
request_data)
return adyen_result
def hpp_payment(self, request_data, action, hmac_key="", **kwargs):
if not self.http_init:
self._init_http_client()
platform = self.platform
if not isinstance(platform, str):
errorstring = "'platform' must be type string"
raise TypeError(errorstring)
elif platform.lower() not in ['live', 'test']:
errorstring = " 'platform' must be the value of 'live' or 'test' "
raise ValueError(errorstring)
if 'skinCode' not in request_data:
request_data['skinCode'] = self.skin_code
hmac = self.hmac
if 'merchantAccount' not in request_data:
request_data['merchantAccount'] = self.merchant_account
if request_data['merchantAccount'] == "":
request_data['merchantAccount'] = self.merchant_account
request_data["merchantSig"] = util.generate_hpp_sig(request_data, hmac)
url = self._determine_hpp_url(platform, action)
adyen_result = {
'url': url,
'message': request_data
}
return adyen_result
def _handle_response(self, url, raw_response, raw_request,
status_code, headers, request_dict):
"""This parses the content from raw communication, raising an error if
anything other than 200 was returned.
Args:
url (str): URL where request was made
raw_response (str): The raw communication sent to Adyen
raw_request (str): The raw response returned by Adyen
status_code (int): The HTTP status code
headers (dict): Key/Value of the headers.
request_dict (dict): The original request dictionary that was given
to the HTTPClient.
Returns:
AdyenResult: Result object if successful.
"""
if (status_code != 200 and status_code != 201):
response = {}
# If the result can't be parsed into json, most likely is raw html.
# Some response are neither json or raw html, handle them here:
if raw_response:
response = json_lib.loads(raw_response)
# Pass raised error to error handler.
self._handle_http_error(url, response, status_code,
headers.get('pspReference'),
raw_request, raw_response,
headers, request_dict)
try:
if response['errorCode']:
raise AdyenAPICommunicationError(
"Unexpected error while communicating with Adyen."
" Received the response data:'{}', HTTP Code:'{}'. "
"Please reach out to support@adyen.com if the "
"problem persists with the psp:{}".format(
raw_response,
status_code,
headers.get('pspReference')),
status_code=status_code,
raw_request=raw_request,
raw_response=raw_response,
url=url,
psp=headers.get('pspReference'),
headers=headers,
error_code=response['errorCode'])
except KeyError:
erstr = 'KeyError: errorCode'
raise AdyenAPICommunicationError(erstr)
else:
try:
response = json_lib.loads(raw_response)
psp = headers.get('pspReference', response.get('pspReference'))
return AdyenResult(message=response, status_code=status_code,
psp=psp, raw_request=raw_request,
raw_response=raw_response)
except ValueError:
# Couldn't parse json so try to pull error from html.
error = self._error_from_hpp(raw_response)
message = request_dict
reference = message.get("reference",
message.get("merchantReference"))
errorstring = """Unable to retrieve payment "
list. Received the error: {}. Please verify your request "
and try again. If the issue persists, please reach out to "
support@adyen.com including the "
merchantReference: {}""".format(error, reference),
raise AdyenInvalidRequestError(errorstring)
def _handle_http_error(self, url, response_obj, status_code, psp_ref,
raw_request, raw_response, headers, message):
"""This function handles the non 200 responses from Adyen, raising an
error that should provide more information.
Args:
url (str): url of the request
response_obj (dict): Dict containing the parsed JSON response from
Adyen
status_code (int): HTTP status code of the request
psp_ref (str): Psp reference of the request attempt
raw_request (str): The raw request placed to Adyen
raw_response (str): The raw response(body) returned by Adyen
headers(dict): headers of the response
Returns:
None
"""
if status_code == 404:
if url == self.merchant_specific_url:
erstr = "Received a 404 for url:'{}'. Please ensure that" \
" the custom merchant specific url is correct" \
.format(url)
raise AdyenAPICommunicationError(erstr,
error_code=response_obj.get(
"errorCode"))
else:
erstr = "Unexpected error while communicating with Adyen." \
" Please reach out to support@adyen.com" \
" if the problem persists"
raise AdyenAPICommunicationError(erstr,
raw_request=raw_request,
raw_response=raw_response,
url=url,
psp=psp_ref,
headers=headers,
error_code=response_obj.get(
"errorCode"))
elif status_code == 400:
erstr = "Received validation error with errorCode: %s," \
" message: %s, HTTP Code: %s. Please verify" \
" the values provided. Please reach out" \
" to support@adyen.com if the problem persists," \
" providing the PSP reference: %s" % (
response_obj["errorCode"], response_obj["message"],
status_code, psp_ref)
raise AdyenAPIValidationError(erstr, error_code=response_obj.get(
"errorCode"))
elif status_code == 401:
erstr = "Unable to authenticate with Adyen's Servers." \
" Please verify the credentials set with the Adyen base" \
" class. Please reach out to your Adyen Admin" \
" if the problem persists"
raise AdyenAPIAuthenticationError(erstr,
error_code=response_obj.get(
"errorCode"))
elif status_code == 403:
if response_obj.get("message") == "Invalid Merchant Account":
erstr = ("You provided the merchant account:'%s' that"
" doesn't exist or you don't have access to it.\n"
"Please verify the merchant account provided. \n"
"Reach out to support@adyen.com"
" if the issue persists") \
% raw_request['merchantAccount']
raise AdyenAPIInvalidPermission(erstr,
error_code=response_obj.get(
"errorCode"))
erstr = "Unable to perform the requested action. message: %s." \
" If you think your webservice user: %s might not have" \
" the necessary permissions to perform this request." \
" Please reach out to support@adyen.com, providing" \
" the PSP reference: %s" % (
response_obj["message"], self.username, psp_ref)
raise AdyenAPIInvalidPermission(erstr, error_code=response_obj.get(
"errorCode"))
elif status_code == 422:
if response_obj.get("message") == "Invalid amount specified":
raise AdyenAPIInvalidAmount(
"Invalid amount specified"
"Amount may be improperly formatted, too small or too big."
"If the issue persists, contact support@adyen.com",
error_code=response_obj.get("errorCode"))
elif status_code == 500:
if response_obj.get("errorType") == "validation":
err_args = (response_obj.get("errorCode"),
response_obj.get("message"),
status_code)
erstr = "Received validation error with errorCode: %s," \
" message: %s, HTTP Code: %s. Please verify" \
" the values provided." % err_args
raise AdyenAPIValidationError(erstr,
error_code=response_obj.get(
"errorCode"))
if response_obj.get("message") == "Failed to serialize node " \
"Failed to parse [123.34]" \
" as a Long":
raise AdyenAPIInvalidFormat(
"The payment amount must be set in cents,"
" and can not contain commas or points.",
error_code=response_obj.get("errorCode")
)
else:
raise AdyenAPICommunicationError(
"Unexpected error while communicating with Adyen. Received the"
" response data:'{}', HTTP Code:'{}'. Please reach out to "
"support@adyen.com if the problem persists"
" with the psp:{}".format(raw_response, status_code, psp_ref),
status_code=status_code,
raw_request=raw_request,
raw_response=raw_response,
url=url,
psp=psp_ref,
headers=headers, error_code=response_obj.get("errorCode"))
@staticmethod
def _error_from_hpp(html):
# Must be updated when Adyen response is changed:
match_obj = re.search(r'>Error:\s*(.*?)<br', html)
if match_obj:
return match_obj.group(1)

View File

@@ -0,0 +1,71 @@
from __future__ import absolute_import, division, unicode_literals
class AdyenError(Exception):
def __init__(self,
message,
raw_request="",
raw_response="",
url="",
psp="",
headers="",
status_code="",
error_code=""):
self.message = message
self.raw_request = raw_request
self.raw_response = raw_response
self.url = url
self.psp = psp
self.headers = headers
self.status_code = status_code
self.error_code = error_code
def __str__(self):
return repr("{}:{}".format(self.__class__.__name__, self.message))
def debug(self):
return ("class: {}\nmessage: {}\nHTTP status_code:{}\nurl: {}"
"request: {}\nresponse: {}\nheaders: {}"
.format(self.__class__.__name__, self.message,
self.status_code, self.url, self.raw_request,
self.raw_response, self.headers))
class AdyenInvalidRequestError(AdyenError):
pass
class AdyenAPIResponseError(AdyenError):
def __init__(self,
message,
*args,
**kwargs):
super(AdyenAPIResponseError, self).__init__(message, *args, **kwargs)
class AdyenAPIAuthenticationError(AdyenAPIResponseError):
pass
class AdyenAPIInvalidPermission(AdyenAPIResponseError):
pass
class AdyenAPICommunicationError(AdyenAPIResponseError):
pass
class AdyenAPIValidationError(AdyenAPIResponseError):
pass
class AdyenAPIInvalidAmount(AdyenAPIResponseError):
pass
class AdyenAPIInvalidFormat(AdyenAPIResponseError):
pass
class AdyenEndpointInvalidFormat(AdyenError):
pass

View File

@@ -0,0 +1,351 @@
#!/bin/python
from __future__ import absolute_import, division, unicode_literals
import sys
try:
import requests
except ImportError:
requests = None
try:
import pycurl
except ImportError:
pycurl = None
try:
# Python 3
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from urllib.error import HTTPError
except ImportError:
# Python 2
from urllib import urlencode
from urllib2 import Request, urlopen, HTTPError
try:
# Python 2
from StringIO import StringIO
except ImportError:
# Python 3
from io import BytesIO
import json as json_lib
import base64
class HTTPClient(object):
def __init__(
self,
user_agent_suffix,
lib_version,
force_request=None,
timeout=None,
):
# Check if requests already available, default to urllib
self.user_agent = user_agent_suffix + lib_version
if not force_request:
if requests:
self.request = self._requests_post
elif pycurl:
self.request = self._pycurl_post
else:
self.request = self._urllib_post
else:
if force_request == 'requests':
self.request = self._requests_post
elif force_request == 'pycurl':
self.request = self._pycurl_post
else:
self.request = self._urllib_post
self.timeout = timeout
def _pycurl_post(
self,
url,
json=None,
data=None,
username="",
password="",
xapikey="",
headers=None
):
"""This function will POST to the url endpoint using pycurl. returning
an AdyenResult object on 200 HTTP response. Either json or data has to
be provided. If username and password are provided, basic auth will be
used.
Args:
url (str): url to send the POST
json (dict, optional): Dict of the JSON to POST
data (dict, optional): Dict, presumed flat structure
of key/value of request to place
username (str, optional): Username for basic auth. Must be included
as part of password.
password (str, optional): Password for basic auth. Must be included
as part of username.
xapikey (str, optional): Adyen API key. Will be used for auth
if username and password are absent.
headers (dict, optional): Key/Value pairs of headers to include
timeout (int, optional): Default 30. Timeout for the request.
Returns:
str: Raw response received
str: Raw request placed
int: HTTP status code, eg 200,404,401
dict: Key/Value pairs of the headers received.
"""
if headers is None:
headers = {}
response_headers = {}
curl = pycurl.Curl()
curl.setopt(curl.URL, url)
if sys.version_info[0] >= 3:
stringbuffer = BytesIO()
else:
stringbuffer = StringIO()
curl.setopt(curl.WRITEDATA, stringbuffer)
# Add User-Agent header to request so that the
# request can be identified as coming from the Adyen Python library.
headers['User-Agent'] = self.user_agent
if username and password:
curl.setopt(curl.USERPWD, '%s:%s' % (username, password))
elif xapikey:
headers["X-API-KEY"] = xapikey
# Convert the header dict to formatted array as pycurl needs.
if sys.version_info[0] >= 3:
header_list = ["%s:%s" % (k, v) for k, v in headers.items()]
else:
header_list = ["%s:%s" % (k, v) for k, v in headers.iteritems()]
# Ensure proper content-type when adding headers
if json:
header_list.append("Content-Type:application/json")
curl.setopt(pycurl.HTTPHEADER, header_list)
# Return regular dict instead of JSON encoded dict for request:
raw_store = json
# Set the request body.
raw_request = json_lib.dumps(json) if json else urlencode(data)
curl.setopt(curl.POSTFIELDS, raw_request)
curl.setopt(curl.TIMEOUT, self.timeout)
curl.perform()
# Grab the response content
result = stringbuffer.getvalue()
status_code = curl.getinfo(curl.RESPONSE_CODE)
curl.close()
# Return regular dict instead of JSON encoded dict for request:
raw_request = raw_store
return result, raw_request, status_code, response_headers
def _requests_post(
self,
url,
json=None,
data=None,
username="",
password="",
xapikey="",
headers=None
):
"""This function will POST to the url endpoint using requests.
Returning an AdyenResult object on 200 HTTP response.
Either json or data has to be provided.
If username and password are provided, basic auth will be used.
Args:
url (str): url to send the POST
json (dict, optional): Dict of the JSON to POST
data (dict, optional): Dict, presumed flat structure of key/value
of request to place
username (str, optionl): Username for basic auth. Must be included
as part of password.
password (str, optional): Password for basic auth. Must be included
as part of username.
xapikey (str, optional): Adyen API key. Will be used for auth
if username and password are absent.
headers (dict, optional): Key/Value pairs of headers to include
timeout (int, optional): Default 30. Timeout for the request.
Returns:
str: Raw response received
str: Raw request placed
int: HTTP status code, eg 200,404,401
dict: Key/Value pairs of the headers received.
"""
if headers is None:
headers = {}
# Adding basic auth if username and password provided.
auth = None
if username and password:
auth = requests.auth.HTTPBasicAuth(username, password)
elif xapikey:
headers['x-api-key'] = xapikey
# Add User-Agent header to request so that the request
# can be identified as coming from the Adyen Python library.
headers['User-Agent'] = self.user_agent
request = requests.post(
url=url,
auth=auth,
data=data,
json=json,
headers=headers,
timeout=self.timeout
)
# Ensure either json or data is returned for raw request
# Updated: Only return regular dict,
# don't switch out formats if this is not important.
message = json
return request.text, message, request.status_code, request.headers
def _urllib_post(
self,
url,
json=None,
data=None,
username="",
password="",
xapikey="",
headers=None,
):
"""This function will POST to the url endpoint using urllib2. returning
an AdyenResult object on 200 HTTP responce. Either json or data has to
be provided. If username and password are provided, basic auth will be
used.
Args:
url (str): url to send the POST
json (dict, optional): Dict of the JSON to POST
data (dict, optional): Dict, presumed flat structure of
key/value of request to place as
www-form
username (str, optional): Username for basic auth. Must be
uncluded as part of password.
password (str, optional): Password for basic auth. Must be
included as part of username.
xapikey (str, optional): Adyen API key. Will be used for auth
if username and password are absent.
headers (dict, optional): Key/Value pairs of headers to include
Returns:
str: Raw response received
str: Raw request placed
int: HTTP status code, eg 200,404,401
dict: Key/Value pairs of the headers received.
"""
if headers is None:
headers = {}
# Store regular dict to return later:
raw_store = json
raw_request = json_lib.dumps(json) if json else urlencode(data)
url_request = Request(url, data=raw_request.encode('utf8'))
if json:
url_request.add_header('Content-Type', 'application/json')
elif not data:
raise ValueError("Please provide either a json or a data field.")
# Add User-Agent header to request so that the
# request can be identified as coming from the Adyen Python library.
headers['User-Agent'] = self.user_agent
# Set regular dict to return as raw_request:
raw_request = raw_store
# Adding basic auth is username and password provided.
if username and password:
if sys.version_info[0] >= 3:
basic_authstring = base64.encodebytes(('%s:%s' %
(username, password))
.encode()).decode(). \
replace('\n', '')
else:
basic_authstring = base64.encodestring('%s:%s' % (username,
password)). \
replace('\n', '')
url_request.add_header("Authorization",
"Basic %s" % basic_authstring)
elif xapikey:
headers["X-API-KEY"] = xapikey
# Adding the headers to the request.
for key, value in headers.items():
url_request.add_header(key, str(value))
# URLlib raises all non 200 responses as en error.
try:
response = urlopen(url_request, timeout=self.timeout)
except HTTPError as e:
raw_response = e.read()
return raw_response, raw_request, e.getcode(), e.headers
else:
raw_response = response.read()
response.close()
# The dict(response.info()) is the headers of the response
# Raw response, raw request, status code and headers returned
return (raw_response, raw_request,
response.getcode(), dict(response.info()))
def request(
self,
url,
json="",
data="",
username="",
password="",
headers=None,
):
"""This is overridden on module initialization. This function will make
an HTTP POST to a given url. Either json/data will be what is posted to
the end point. he HTTP request needs to be basicAuth when username and
password are provided. a headers dict maybe provided,
whatever the values are should be applied.
Args:
url (str): url to send the POST
json (dict, optional): Dict of the JSON to POST
data (dict, optional): Dict, presumed flat structure of
key/value of request to place as
www-form
username (str, optional): Username for basic auth. Must be
included as part of password.
password (str, optional): Password for basic auth. Must be
included as part of username.
xapikey (str, optional): Adyen API key. Will be used for auth
if username and password are absent.
headers (dict, optional): Key/Value pairs of headers to include
Returns:
str: Raw request placed
str: Raw response received
int: HTTP status code, eg 200,404,401
dict: Key/Value pairs of the headers received.
"""
raise NotImplementedError('request of HTTPClient should have been '
'overridden on initialization. '
'Otherwise, can be overridden to '
'supply your own post method')

View File

@@ -0,0 +1,369 @@
from __future__ import absolute_import, division, unicode_literals
import datetime
from Adyen import AdyenClient
class AdyenBase(object):
def __setattr__(self, attr, value):
client_attr = ["username", "password", "platform"]
if attr in client_attr:
if value:
self.client[attr] = value
else:
super(AdyenBase, self).__setattr__(attr, value)
def __getattr__(self, attr):
client_attr = ["username", "password", "platform"]
if attr in client_attr:
return self.client[attr]
class AdyenServiceBase(AdyenBase):
def __init__(self, client=None):
if client:
self.client = client
else:
self.client = AdyenClient()
class AdyenRecurring(AdyenServiceBase):
"""This represents the Adyen API Recurring Service.
API calls currently implemented: listRecurringDetails and disable. Please
refer to the Recurring Manual for specifics around the API.
https://docs.adyen.com/online-payments/tokenization
Args:
client (AdyenAPIClient, optional): An API client for the service to
use. If not provided, a new API client will be created.
"""
def __init__(self, client=None):
super(AdyenRecurring, self).__init__(client=client)
self.service = "Recurring"
def list_recurring_details(self, request, **kwargs):
action = "listRecurringDetails"
return self.client.call_api(request, self.service,
action, **kwargs)
def disable(self, request, **kwargs):
action = "disable"
if 'recurringDetailReference' not in request:
raise ValueError("Include a 'recurringDetailReference'"
" to disable a specific recurring contract.")
else:
return self.client.call_api(request, self.service,
action, **kwargs)
class AdyenHPP(AdyenServiceBase):
"""This represents the Adyen HPP Service.
This currently only implements the directory_lookup request which will
return the list of payment methods available for given shopper. Please
refer to the HPP manual and the directory lookup section for the specifics.
https://docs.adyen.com/online-payments/classic-integrations/hosted-payment-pages/directory-lookup
Args:
client (AdyenAPIClient, optional): An API client for the service to
use. If not provided, a new API client will be created.
"""
def __init__(self, client=None):
super(AdyenHPP, self).__init__(client=client)
def directory_lookup(self, request, **kwargs):
action = "directory"
try:
datetime.datetime.strptime(request['sessionValidity'],
'%Y-%m-%dT%H:%M:%SZ')
except ValueError:
raise ValueError(
"Incorrect date format, should be Y-m-dH:M:SZ,"
" use datetime.strftime('%Y-%m-%dT%H:%M:%SZ')"
" to format a datetime object.")
return self.client.call_hpp(request, action)
def hpp_payment(self, request, skip_details=None, **kwargs):
if skip_details:
action = "skipDetails"
else:
action = "select"
if action == "skipDetails":
if "issuerId" not in request:
request['issuerId'] = ""
if type(request['sessionValidity']) is not str:
raise TypeError(
'HPP: sessionValidity must be type of str,'
' use datetime.strftime to convert and format.')
if all(k in request for k in ("shopperEmail", "shopperReference",
"recurringContract")):
recc = request['recurringContract']
if recc != 'ONECLICK' and recc != 'RECURRING' \
and recc != 'ONECLICK,RECURRING':
raise ValueError(
"HPP: recurringContract must be on of the following"
" values: 'ONECLICK', 'RECURRING',"
" 'ONECLICK,RECURRING'")
result = self.client.hpp_payment(request, action)
return result
class AdyenPayment(AdyenServiceBase):
"""This represents the Adyen API Payment Service.
API calls currently implemented:
authorise
authorise3d
adjustAuthorisation
cancel
capture
refund
cancelOrRefund
Please refer to our API Explorer for specifics around these APIs.
https://docs.adyen.com/api-explorer/
The AdyenPayment class, is accessible as adyen.payment.method(args)
Args:
client (AdyenAPIClient, optional): An API client for the service to
use. If not provided, a new API client will be created.
"""
def __init__(self, client=None):
super(AdyenPayment, self).__init__(client=client)
self.service = "Payment"
def authorise(self, request, idempotency_key=None, **kwargs):
action = "authorise"
if 'shopperEmail' in request:
if request['shopperEmail'] == '':
raise ValueError(
'shopperEmail must contain the shopper email'
' when authorising recurring contracts.')
if 'shopperReference' in request:
if request['shopperReference'] == '':
raise ValueError(
'shopperReference must contain the shopper'
' name when authorising recurring contracts.')
return self.client.call_api(request, self.service,
action, idempotency_key, **kwargs)
def authorise3d(self, request, idempotency_key=None, **kwargs):
action = "authorise3d"
return self.client.call_api(request, self.service,
action, idempotency_key, **kwargs)
def adjustAuthorisation(self, request, **kwargs):
action = "adjustAuthorisation"
return self.client.call_api(request, self.service,
action, **kwargs)
def cancel(self, request, idempotency_key=None, **kwargs):
action = "cancel"
return self.client.call_api(request, self.service,
action, idempotency_key, **kwargs)
def capture(self, request, idempotency_key=None, **kwargs):
action = "capture"
if request['modificationAmount']["value"] == "" or \
request['modificationAmount']['value'] == "0":
raise ValueError(
"Set the 'modificationAmount' to the original transaction"
" amount, or less for a partial capture. "
"modificationAmount should be an object with the following"
" keys: {'currency':,'value':}")
if request['originalReference'] == "":
raise ValueError("Set the 'originalReference' to the psp "
"reference of the transaction to be modified")
response = self.client.call_api(request, self.service,
action, idempotency_key, **kwargs)
return response
def refund(self, request, idempotency_key=None, **kwargs):
action = "refund"
if request['modificationAmount']['value'] == "" or \
request['modificationAmount']['value'] == "0":
raise ValueError(
"To refund this payment, provide the original value. "
"Set the value to less than the original amount, "
"to partially refund this payment.")
else:
return self.client.call_api(request, self.service,
action, idempotency_key, **kwargs)
def cancel_or_refund(self, request, idempotency_key=None, **kwargs):
action = "cancelOrRefund"
return self.client.call_api(
request, self.service, action, idempotency_key, **kwargs
)
class AdyenThirdPartyPayout(AdyenServiceBase):
"""This represents the Adyen Payouts Service.
https://docs.adyen.com/api-explorer/#/Payout/overview
The AdyenThirdPartyPayout class is accessible as adyen.payout.method(args)
Args:
client (AdyenAPIClient, optional): An API client for the service to
use. If not provided, a new API client will be created.
"""
def __init__(self, client=None):
super(AdyenThirdPartyPayout, self).__init__(client=client)
self.service = "Payout"
def confirm(self, request=None, **kwargs):
action = "confirmThirdParty"
return self.client.call_api(
request, self.service, action, **kwargs
)
def decline(self, request=None, **kwargs):
action = "declineThirdParty"
return self.client.call_api(
request, self.service, action, **kwargs
)
def store_detail(self, request=None, **kwargs):
action = "storeDetail"
return self.client.call_api(
request, self.service, action, **kwargs
)
def submit(self, request=None, **kwargs):
action = "submitThirdParty"
return self.client.call_api(
request, self.service, action, **kwargs
)
def store_detail_and_submit(self, request=None, **kwargs):
action = "storeDetailAndSubmitThirdParty"
return self.client.call_api(
request, self.service, action, **kwargs
)
class AdyenCheckoutApi(AdyenServiceBase):
"""This represents the Adyen Checkout API .
API calls currently implemented:
paymentMethods
payments
payments/details
originKeys
Please refer to the checkout documentation for specifics around the API.
https://docs.adyen.com/online-payments
The AdyenPayment class, is accessible as adyen.payment.method(args)
Args:
client (AdyenAPIClient, optional): An API client for the service to
use. If not provided, a new API client will be created.
"""
def __init__(self, client=None):
super(AdyenCheckoutApi, self).__init__(client=client)
self.service = "Checkout"
def payment_methods(self, request, **kwargs):
action = "paymentMethods"
if 'merchantAccount' in request:
if request['merchantAccount'] == '':
raise ValueError(
'merchantAccount must contain the merchant account'
' when retrieving payment methods.')
return self.client.call_checkout_api(request, action, **kwargs)
def payments(self, request, idempotency_key=None, **kwargs):
action = "payments"
return self.client.call_checkout_api(request, action, idempotency_key,
**kwargs)
def payments_details(self, request=None, idempotency_key=None, **kwargs):
action = "paymentsDetails"
return self.client.call_checkout_api(request, action, idempotency_key,
**kwargs)
def payment_session(self, request=None, **kwargs):
action = "paymentSession"
return self.client.call_checkout_api(request, action, **kwargs)
def payment_result(self, request=None, **kwargs):
action = "paymentsResult"
return self.client.call_checkout_api(request, action, **kwargs)
def origin_keys(self, request=None, **kwargs):
action = "originKeys"
return self.client.call_checkout_api(request, action, **kwargs)
def sessions(self, request=None, **kwargs):
action = "sessions"
return self.client.call_checkout_api(request, action, **kwargs)
# Orders endpoints
# /paymentMethods/balance
def payment_methods_balance(self, request, **kwargs):
action = "paymentMethodsBalance"
return self.client.call_checkout_api(request, action, **kwargs)
# /orders
def orders(self, request, **kwargs):
action = "orders"
return self.client.call_checkout_api(request, action, **kwargs)
# /orders/cancel
def orders_cancel(self, request, **kwargs):
action = "ordersCancel"
return self.client.call_checkout_api(request, action, **kwargs)
class AdyenBinLookup(AdyenServiceBase):
"""This represents the Adyen API Bin Lookup service.
API call currently implemented: getCostEstimate.
Please refer to the Bin Lookup Manual for specifics around the API.
https://docs.adyen.com/api-explorer/#/BinLookup/
Args:
client (AdyenAPIClient, optional): An API client for the service to
use. If not provided, a new API client will be created.
"""
def __init__(self, client=None):
super(AdyenBinLookup, self).__init__(client=client)
self.service = "BinLookup"
def get_cost_estimate(self, request="", **kwargs):
action = "getCostEstimate"
return self.client.call_api(request, self.service, action, **kwargs)

View File

@@ -0,0 +1,16 @@
# Those constants are used from the library only
BASE_PAL_URL = "https://pal-{}.adyen.com/pal/servlet"
PAL_LIVE_ENDPOINT_URL_TEMPLATE = "https://{}-pal-live" \
".adyenpayments.com/pal/servlet"
BASE_HPP_URL = "https://{}.adyen.com/hpp"
ENDPOINT_CHECKOUT_TEST = "https://checkout-test.adyen.com"
ENDPOINT_CHECKOUT_LIVE_SUFFIX = "https://{}-checkout-live" \
".adyenpayments.com/checkout"
API_BIN_LOOKUP_VERSION = "v50"
API_CHECKOUT_VERSION = "v68"
API_CHECKOUT_UTILITY_VERSION = "v1"
API_RECURRING_VERSION = "v49"
API_PAYMENT_VERSION = "v64"
API_PAYOUT_VERSION = "v64"
LIB_VERSION = "6.0.0"
LIB_NAME = "adyen-python-api-library"

View File

@@ -0,0 +1,96 @@
from __future__ import absolute_import, division, unicode_literals
from itertools import chain
from collections import OrderedDict
import base64
import hmac
import hashlib
import binascii
def generate_hpp_sig(dict_object, hmac_key):
if 'issuerId' in dict_object:
if dict_object['issuerId'] == "":
del dict_object['issuerId']
if not isinstance(dict_object, dict):
raise ValueError("Must Provide dictionary object")
def escape_val(val):
if isinstance(val, int):
return val
return val.replace('\\', '\\\\').replace(':', '\\:')
hmac_key = binascii.a2b_hex(hmac_key)
ordered_request = OrderedDict(sorted(dict_object.items(),
key=lambda t: t[0]))
signing_string = ':'.join(
map(escape_val, chain(map(str, ordered_request.keys()),
map(str, ordered_request.values()))))
hm = hmac.new(hmac_key, signing_string.encode('utf-8'), hashlib.sha256)
return base64.b64encode(hm.digest())
def is_valid_hmac(dict_object, hmac_key):
if 'additionalData' in dict_object:
if dict_object['additionalData']['hmacSignature'] == "":
raise ValueError("Must Provide hmacSignature in additionalData")
else:
expected_sign = dict_object['additionalData']['hmacSignature']
del dict_object['additionalData']
merchant_sign = generate_hpp_sig(dict_object, hmac_key)
merchant_sign_str = merchant_sign.decode("utf-8")
return merchant_sign_str == expected_sign
def generate_notification_sig(dict_object, hmac_key):
if 'issuerId' in dict_object:
if dict_object['issuerId'] == "":
del dict_object['issuerId']
if not isinstance(dict_object, dict):
raise ValueError("Must Provide dictionary object")
def escape_val(val):
if isinstance(val, int):
return val
return val.replace('\\', '\\\\')
hmac_key = binascii.a2b_hex(hmac_key)
request_dict = dict(dict_object)
request_dict['value'] = request_dict['amount']['value']
request_dict['currency'] = request_dict['amount']['currency']
element_orders = [
'pspReference',
'originalReference',
'merchantAccountCode',
'merchantReference',
'value',
'currency',
'eventCode',
'success',
]
signing_string = ':'.join(
map(escape_val, map(str, (
request_dict.get(element, '') for element in element_orders))))
hm = hmac.new(hmac_key, signing_string.encode('utf-8'), hashlib.sha256)
return base64.b64encode(hm.digest())
def is_valid_hmac_notification(dict_object, hmac_key):
if 'additionalData' in dict_object:
if dict_object['additionalData']['hmacSignature'] == "":
raise ValueError("Must Provide hmacSignature in additionalData")
else:
expected_sign = dict_object['additionalData']['hmacSignature']
del dict_object['additionalData']
merchant_sign = generate_notification_sig(dict_object, hmac_key)
merchant_sign_str = merchant_sign.decode("utf-8")
return merchant_sign_str == expected_sign

View File

@@ -0,0 +1 @@
pip

View File

@@ -0,0 +1,28 @@
Copyright 2010 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,125 @@
Metadata-Version: 2.1
Name: Flask
Version: 2.0.3
Summary: A simple framework for building complex web applications.
Home-page: https://palletsprojects.com/p/flask
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
Maintainer: Pallets
Maintainer-email: contact@palletsprojects.com
License: BSD-3-Clause
Project-URL: Donate, https://palletsprojects.com/donate
Project-URL: Documentation, https://flask.palletsprojects.com/
Project-URL: Changes, https://flask.palletsprojects.com/changes/
Project-URL: Source Code, https://github.com/pallets/flask/
Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/
Project-URL: Twitter, https://twitter.com/PalletsTeam
Project-URL: Chat, https://discord.gg/pallets
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.6
Description-Content-Type: text/x-rst
License-File: LICENSE.rst
Requires-Dist: Werkzeug (>=2.0)
Requires-Dist: Jinja2 (>=3.0)
Requires-Dist: itsdangerous (>=2.0)
Requires-Dist: click (>=7.1.2)
Provides-Extra: async
Requires-Dist: asgiref (>=3.2) ; extra == 'async'
Provides-Extra: dotenv
Requires-Dist: python-dotenv ; extra == 'dotenv'
Flask
=====
Flask is a lightweight `WSGI`_ web application framework. It is designed
to make getting started quick and easy, with the ability to scale up to
complex applications. It began as a simple wrapper around `Werkzeug`_
and `Jinja`_ and has become one of the most popular Python web
application frameworks.
Flask offers suggestions, but doesn't enforce any dependencies or
project layout. It is up to the developer to choose the tools and
libraries they want to use. There are many extensions provided by the
community that make adding new functionality easy.
.. _WSGI: https://wsgi.readthedocs.io/
.. _Werkzeug: https://werkzeug.palletsprojects.com/
.. _Jinja: https://jinja.palletsprojects.com/
Installing
----------
Install and update using `pip`_:
.. code-block:: text
$ pip install -U Flask
.. _pip: https://pip.pypa.io/en/stable/getting-started/
A Simple Example
----------------
.. code-block:: python
# save this as app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello, World!"
.. code-block:: text
$ flask run
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Contributing
------------
For guidance on setting up a development environment and how to make a
contribution to Flask, see the `contributing guidelines`_.
.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst
Donate
------
The Pallets organization develops and supports Flask and the libraries
it uses. In order to grow the community of contributors and users, and
allow the maintainers to devote more time to the projects, `please
donate today`_.
.. _please donate today: https://palletsprojects.com/donate
Links
-----
- Documentation: https://flask.palletsprojects.com/
- Changes: https://flask.palletsprojects.com/changes/
- PyPI Releases: https://pypi.org/project/Flask/
- Source Code: https://github.com/pallets/flask/
- Issue Tracker: https://github.com/pallets/flask/issues/
- Website: https://palletsprojects.com/p/flask/
- Twitter: https://twitter.com/PalletsTeam
- Chat: https://discord.gg/pallets

View File

@@ -0,0 +1,51 @@
../../../bin/flask,sha256=sR4yhMrSywRbEyuOk0H-s-iJmN0WEkOx-nXnh3QFpg8,289
Flask-2.0.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
Flask-2.0.3.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
Flask-2.0.3.dist-info/METADATA,sha256=jK50YtxZfODLQP_GF1sNH6dOXRCI5bBLrAc7pWQwuXw,3839
Flask-2.0.3.dist-info/RECORD,,
Flask-2.0.3.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
Flask-2.0.3.dist-info/entry_points.txt,sha256=s3MqQpduU25y4dq3ftBYD6bMVdVnbMpZP-sUNw0zw0k,41
Flask-2.0.3.dist-info/top_level.txt,sha256=dvi65F6AeGWVU0TBpYiC04yM60-FX1gJFkK31IKQr5c,6
flask/__init__.py,sha256=ubQS5Xt6LMjPSwGO3Jksi5yx8AyuU0vT_VdHjt0j97A,2251
flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
flask/__pycache__/__init__.cpython-37.pyc,,
flask/__pycache__/__main__.cpython-37.pyc,,
flask/__pycache__/app.cpython-37.pyc,,
flask/__pycache__/blueprints.cpython-37.pyc,,
flask/__pycache__/cli.cpython-37.pyc,,
flask/__pycache__/config.cpython-37.pyc,,
flask/__pycache__/ctx.cpython-37.pyc,,
flask/__pycache__/debughelpers.cpython-37.pyc,,
flask/__pycache__/globals.cpython-37.pyc,,
flask/__pycache__/helpers.cpython-37.pyc,,
flask/__pycache__/logging.cpython-37.pyc,,
flask/__pycache__/scaffold.cpython-37.pyc,,
flask/__pycache__/sessions.cpython-37.pyc,,
flask/__pycache__/signals.cpython-37.pyc,,
flask/__pycache__/templating.cpython-37.pyc,,
flask/__pycache__/testing.cpython-37.pyc,,
flask/__pycache__/typing.cpython-37.pyc,,
flask/__pycache__/views.cpython-37.pyc,,
flask/__pycache__/wrappers.cpython-37.pyc,,
flask/app.py,sha256=ectBbi9hGmVHAse5TNcFQZIDRkDAxYUAnLgfuKD0Xws,81975
flask/blueprints.py,sha256=AkAVXZ_MMkjwjklzCAMdBNowTiM0wVQPynnUnXjTL2M,23781
flask/cli.py,sha256=9v7FDIwWZ3QZsR6ka-qMYzMxSThfmQ4PEA4lkI38R6c,32287
flask/config.py,sha256=70Uyjh1Jzb9MfTCT7NDhuZWAzyIEu-TIyk6-22MP3zQ,11285
flask/ctx.py,sha256=Rmw5VOFQdbomLoCQPbU_0FbQkuB56CtpnQVU4yzXYB8,17589
flask/debughelpers.py,sha256=W82-xrRmodjopBngI9roYH-q08EbQwN2HEGfDAi6SA0,6184
flask/globals.py,sha256=cWd-R2hUH3VqPhnmQNww892tQS6Yjqg_wg8UvW1M7NM,1723
flask/helpers.py,sha256=kstplLDtD0Isobilp87Lfmwq1tk2spnHjUf_O5-EhoE,30618
flask/json/__init__.py,sha256=_YIqOsy8YOSyoLbplFtNcKvF5kwNKenmJ87Ub2Myc0k,12104
flask/json/__pycache__/__init__.cpython-37.pyc,,
flask/json/__pycache__/tag.cpython-37.pyc,,
flask/json/tag.py,sha256=fys3HBLssWHuMAIJuTcf2K0bCtosePBKXIWASZEEjnU,8857
flask/logging.py,sha256=1o_hirVGqdj7SBdETnhX7IAjklG89RXlrwz_2CjzQQE,2273
flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
flask/scaffold.py,sha256=fM9mRy7QBh9fhJ0VTogVx900dDa5oxz8FOw6OK5F-TU,32796
flask/sessions.py,sha256=46jK4JlcdeBiYbDWTZJn_6u8EqDV-ByRdhlKrbgFi5M,15714
flask/signals.py,sha256=H7QwDciK-dtBxinjKpexpglP0E6k0MJILiFWTItfmqU,2136
flask/templating.py,sha256=l96VD39JQ0nue4Bcj7wZ4-FWWs-ppLxvgBCpwDQ4KAk,5626
flask/testing.py,sha256=T3mr2PLQEkfxoftSTxmGfTtb_FSX3PgfGT8DUGNPWuk,10840
flask/typing.py,sha256=L5JMltVjj8fovGS1hrMpb13IPfsFDESCCnpRN5CPT4U,1844
flask/views.py,sha256=nhq31TRB5Z-z2mjFGZACaaB2Et5XPCmWhWxJxOvLWww,5948
flask/wrappers.py,sha256=VndbHPRBSUUOejmd2Y3ydkoCVUtsS2OJIdJEVIkBVD8,5604

View File

@@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.37.1)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@@ -0,0 +1,2 @@
[console_scripts]
flask = flask.cli:main

View File

@@ -0,0 +1 @@
flask

View File

@@ -0,0 +1 @@
pip

View File

@@ -0,0 +1,28 @@
Copyright 2007 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,113 @@
Metadata-Version: 2.1
Name: Jinja2
Version: 3.0.3
Summary: A very fast and expressive template engine.
Home-page: https://palletsprojects.com/p/jinja/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
Maintainer: Pallets
Maintainer-email: contact@palletsprojects.com
License: BSD-3-Clause
Project-URL: Donate, https://palletsprojects.com/donate
Project-URL: Documentation, https://jinja.palletsprojects.com/
Project-URL: Changes, https://jinja.palletsprojects.com/changes/
Project-URL: Source Code, https://github.com/pallets/jinja/
Project-URL: Issue Tracker, https://github.com/pallets/jinja/issues/
Project-URL: Twitter, https://twitter.com/PalletsTeam
Project-URL: Chat, https://discord.gg/pallets
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Text Processing :: Markup :: HTML
Requires-Python: >=3.6
Description-Content-Type: text/x-rst
License-File: LICENSE.rst
Requires-Dist: MarkupSafe (>=2.0)
Provides-Extra: i18n
Requires-Dist: Babel (>=2.7) ; extra == 'i18n'
Jinja
=====
Jinja is a fast, expressive, extensible templating engine. Special
placeholders in the template allow writing code similar to Python
syntax. Then the template is passed data to render the final document.
It includes:
- Template inheritance and inclusion.
- Define and import macros within templates.
- HTML templates can use autoescaping to prevent XSS from untrusted
user input.
- A sandboxed environment can safely render untrusted templates.
- AsyncIO support for generating templates and calling async
functions.
- I18N support with Babel.
- Templates are compiled to optimized Python code just-in-time and
cached, or can be compiled ahead-of-time.
- Exceptions point to the correct line in templates to make debugging
easier.
- Extensible filters, tests, functions, and even syntax.
Jinja's philosophy is that while application logic belongs in Python if
possible, it shouldn't make the template designer's job difficult by
restricting functionality too much.
Installing
----------
Install and update using `pip`_:
.. code-block:: text
$ pip install -U Jinja2
.. _pip: https://pip.pypa.io/en/stable/getting-started/
In A Nutshell
-------------
.. code-block:: jinja
{% extends "base.html" %}
{% block title %}Members{% endblock %}
{% block content %}
<ul>
{% for user in users %}
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}
Donate
------
The Pallets organization develops and supports Jinja and other popular
packages. In order to grow the community of contributors and users, and
allow the maintainers to devote more time to the projects, `please
donate today`_.
.. _please donate today: https://palletsprojects.com/donate
Links
-----
- Documentation: https://jinja.palletsprojects.com/
- Changes: https://jinja.palletsprojects.com/changes/
- PyPI Releases: https://pypi.org/project/Jinja2/
- Source Code: https://github.com/pallets/jinja/
- Issue Tracker: https://github.com/pallets/jinja/issues/
- Website: https://palletsprojects.com/p/jinja/
- Twitter: https://twitter.com/PalletsTeam
- Chat: https://discord.gg/pallets

Some files were not shown because too many files have changed in this diff Show More