diff --git a/.DS_Store b/.DS_Store index 2eb59f9..7cd35ed 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..bcac6c2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,26 @@ +name: Django CI + +on: + push: + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: [3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 0000000..aadc014 --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,9 @@ +name: E2E (Playwright) + +on: + workflow_dispatch: + +jobs: + DummyJob: + steps: + - name: DummyStep diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7246ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.idea +.DS_Store + +config.ini + +venv/ + +.git +*.pyc +__pycache__/ +*.egg-info +dist +build +.vscode + +.env \ No newline at end of file diff --git a/app/.DS_Store b/app/.DS_Store index ee6fe59..db1898a 100644 Binary files a/app/.DS_Store and b/app/.DS_Store differ diff --git a/app/app.py b/app/app.py index 709da4e..ec2d04e 100644 --- a/app/app.py +++ b/app/app.py @@ -3,7 +3,6 @@ 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 @@ -23,17 +22,10 @@ def create_app(): # Routes: @app.route('/') - def home(): - return render_template('home.html') - - @app.route('/cart/') - 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() @@ -79,15 +71,6 @@ def create_app(): 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(): diff --git a/app/main/sessions.py b/app/main/sessions.py deleted file mode 100644 index b3413ac..0000000 --- a/app/main/sessions.py +++ /dev/null @@ -1,56 +0,0 @@ -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 - diff --git a/app/static/js/sessionsCode.js b/app/static/js/sessionsCode.js deleted file mode 100644 index 741f154..0000000 --- a/app/static/js/sessionsCode.js +++ /dev/null @@ -1,248 +0,0 @@ -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(); -} - diff --git a/app/templates/amazonRedirect.html b/app/templates/amazonRedirect.html deleted file mode 100644 index aeba832..0000000 --- a/app/templates/amazonRedirect.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "layout.html" %} -{% block content %} - - - - - - - - -
-
-
-
-
- - - - - - - - - - -{% endblock %} \ No newline at end of file diff --git a/app/templates/amazonpay.html b/app/templates/amazonpay.html deleted file mode 100644 index 3549715..0000000 --- a/app/templates/amazonpay.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends "layout.html" %} -{% block content %} - - - - - - - - -
-
-
- -
-
-
-
-
-

- Check the Source Code to see the full implementation. -

-

- To make a payment, use our test card numbers. -

-
-
- - - - - - - - - - -{% endblock %} \ No newline at end of file diff --git a/app/templates/cart.html b/app/templates/cart.html deleted file mode 100644 index e72c5ab..0000000 --- a/app/templates/cart.html +++ /dev/null @@ -1,97 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} - -
-
-

Cart

-
-
    -
  • - -

    Sunglasses

    -

    €20.00

    -
  • -
  • - -

    Headphones

    -

    €20.00

    -
  • -
-
- -
-
-
- - - -
-
-
- - - -{% endblock %} \ No newline at end of file diff --git a/app/templates/component.html b/app/templates/component.html index 2ac508b..16b6658 100644 --- a/app/templates/component.html +++ b/app/templates/component.html @@ -1,9 +1,33 @@ -{% extends "layout.html" %} -{% block content %} + + + + + + - - - + + + + + + Checkout Create + + + - - - + integrity="sha384-vMZOl6V83EY2UXaXsPUxH5Pt5VpyLeHpSFnANBVjcH5l7yZmJO0QBl3s6XbKwjiN" + crossorigin="anonymous"> @@ -59,6 +80,6 @@ + + - -{% endblock %} \ No newline at end of file diff --git a/app/templates/home.html b/app/templates/home.html deleted file mode 100644 index c73e3a9..0000000 --- a/app/templates/home.html +++ /dev/null @@ -1,137 +0,0 @@ -{% extends "layout.html" %} -{% block content %} -
-
-

Ana's test demo

-
-
-

Implementation via Sessions

-
- -
-

Advanced Checkout - Non Sessions

-
- -
-{% endblock %} \ No newline at end of file diff --git a/app/templates/layout.html b/app/templates/layout.html deleted file mode 100644 index 7a3c07d..0000000 --- a/app/templates/layout.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - Checkout Demo - - - - -
- {% block content %} {% endblock %} -
- - diff --git a/app/templates/sessions-component.html b/app/templates/sessions-component.html deleted file mode 100644 index c27e675..0000000 --- a/app/templates/sessions-component.html +++ /dev/null @@ -1,42 +0,0 @@ -{% extends "layout.html" %} -{% block content %} - - - - - - - - -
-
-
- -
- -
- -
-
-
- - - - - - - - - - -{% endblock %} \ No newline at end of file