initial commit
21
LICENSE
Normal 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
@@ -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.
|
||||
|
||||

|
||||
|
||||
## 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
0
app/__init__.py
Normal file
BIN
app/__pycache__/app.cpython-37.pyc
Normal file
151
app/app.py
Normal 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')
|
||||
|
||||
|
||||
BIN
app/main/__pycache__/additional_details.cpython-37.pyc
Normal file
BIN
app/main/__pycache__/amazonPaymentMethods.cpython-37.pyc
Normal file
BIN
app/main/__pycache__/amazonpay.cpython-37.pyc
Normal file
BIN
app/main/__pycache__/config.cpython-37.pyc
Normal file
BIN
app/main/__pycache__/disable.cpython-37.pyc
Normal file
BIN
app/main/__pycache__/paybylink.cpython-37.pyc
Normal file
BIN
app/main/__pycache__/paymentMethods.cpython-37.pyc
Normal file
BIN
app/main/__pycache__/payments.cpython-37.pyc
Normal file
BIN
app/main/__pycache__/redirect.cpython-37.pyc
Normal file
BIN
app/main/__pycache__/sessions.cpython-37.pyc
Normal file
31
app/main/additional_details.py
Normal 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
@@ -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
@@ -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
|
||||
35
app/main/paymentMethods.py
Normal 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
@@ -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
@@ -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
@@ -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
467
app/static/css/application.css
Normal 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
BIN
app/static/img/cardcheckout.gif
Normal file
|
After Width: | Height: | Size: 3.7 MiB |
3
app/static/img/dropdown.svg
Normal 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
@@ -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 |
4
app/static/img/failed.svg
Normal 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
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/static/img/headphones.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
3
app/static/img/jetti.svg
Normal 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 |
10
app/static/img/mystore-logo.svg
Normal file
|
After Width: | Height: | Size: 83 KiB |
24
app/static/img/pending.svg
Normal 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 |
BIN
app/static/img/shopping-cartcart.png
Normal file
|
After Width: | Height: | Size: 799 B |
24
app/static/img/success.svg
Normal 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 |
BIN
app/static/img/sunglasses.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
11
app/static/img/thank-you.svg
Normal file
|
After Width: | Height: | Size: 70 KiB |
221
app/static/js/adyen-implementation.js
Normal 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();
|
||||
248
app/static/js/sessionsCode.js
Normal 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();
|
||||
}
|
||||
|
||||
31
app/templates/amazonRedirect.html
Normal 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 %}
|
||||
45
app/templates/amazonpay.html
Normal 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
@@ -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 %}
|
||||
13
app/templates/checkout-failed.html
Normal 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 %}
|
||||
14
app/templates/checkout-success.html
Normal 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 %}
|
||||
64
app/templates/component.html
Normal 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
@@ -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
@@ -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
@@ -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>
|
||||
42
app/templates/sessions-component.html
Normal 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
@@ -0,0 +1,4 @@
|
||||
flask
|
||||
Adyen == 6.0.0
|
||||
python-dotenv===0.19.2
|
||||
requests == 2.27.1
|
||||
4
setup.sh
Executable 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
@@ -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
76
venv/bin/activate
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
|
||||
python3
|
||||
1
venv/bin/python3
Symbolic link
@@ -0,0 +1 @@
|
||||
/usr/local/adyen/python/bin/python3
|
||||
BIN
venv/lib/.DS_Store
vendored
Normal file
BIN
venv/lib/python3.7/.DS_Store
vendored
Normal file
BIN
venv/lib/python3.7/site-packages/.DS_Store
vendored
Normal 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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
Adyen
|
||||
46
venv/lib/python3.7/site-packages/Adyen/__init__.py
Normal 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
|
||||
779
venv/lib/python3.7/site-packages/Adyen/client.py
Normal 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)
|
||||
71
venv/lib/python3.7/site-packages/Adyen/exceptions.py
Normal 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
|
||||
351
venv/lib/python3.7/site-packages/Adyen/httpclient.py
Normal 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')
|
||||
369
venv/lib/python3.7/site-packages/Adyen/services.py
Normal 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)
|
||||
16
venv/lib/python3.7/site-packages/Adyen/settings.py
Normal 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"
|
||||
96
venv/lib/python3.7/site-packages/Adyen/util.py
Normal 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
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -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.
|
||||
125
venv/lib/python3.7/site-packages/Flask-2.0.3.dist-info/METADATA
Normal 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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.37.1)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[console_scripts]
|
||||
flask = flask.cli:main
|
||||
@@ -0,0 +1 @@
|
||||
flask
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -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.
|
||||
113
venv/lib/python3.7/site-packages/Jinja2-3.0.3.dist-info/METADATA
Normal 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
|
||||
|
||||
|
||||