diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..610c720
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,40 @@
+name: ci
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ include:
+ - python-version: 3.8
+ tox-env: py38
+ - python-version: 3.8
+ tox-env: flake8
+
+ name: "Python ${{ matrix.python-version }} - ${{ matrix.tox-env }}"
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v3
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Cache tox environments
+ uses: actions/cache@v3
+ with:
+ path: .tox
+ # Refresh the cache if the following files change
+ key: "tox-${{ matrix.python-version }}-${{ matrix.tox-env }}-${{ hashFiles('tox.ini', 'setup.py', 'scripts/tox_install_ecommerce_run_pytest.sh', 'requirements/ecommerce-maple.master.txt', 'payfort-test.txt') }}"
+
+ - name: Install Dependencies
+ run: |
+ pip install tox
+ - name: "Python ${{ matrix.python-version }} - ${{ matrix.tox-env }}"
+ run: "tox -e ${{ matrix.tox-env }}"
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644
index 0000000..e69de29
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..31a829f
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,4 @@
+include CHANGELOG.rst
+include LICENSE
+include README.rst
+recursive-include payfort *.py *.html *.txt
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..643a748
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
+.PHONY: compile_messages download_ecommerce_requirements
+
+
+compile_messages:
+ msgfmt--check --strict --output-file payfort/locale/ar/LC_MESSAGES/django.mo payfort/locale/ar/LC_MESSAGES/django.po
+
+OPENEDX_RELEASE ?= maple.master
+download_ecommerce_requirements:
+ curl -L https://github.com/openedx/ecommerce/raw/open-release/$(OPENEDX_RELEASE)/requirements/test.txt -o requirements/ecommerce-$(OPENEDX_RELEASE).txt
+ sed -i '1i# This file has been downloaded by "make download_ecommerce_requirements"' requirements/ecommerce-$(OPENEDX_RELEASE).txt
+ sed -i '2i# Every thing else below this line is a copy from the openedx/ecommerce test.txt requirements file' requirements/ecommerce-$(OPENEDX_RELEASE).txt
+
+
+tests: ## Run unit and integration tests
+ tox -e py38
+
+unit_tests: ## Run unit tests
+ tox -e py38 -- tests/unit
+
+quality: ## Run code quality checks
+ tox -e flake8
diff --git a/README.md b/README.md
deleted file mode 100644
index e2ed87a..0000000
--- a/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# ecommerce-payfort
-Amazon Payment Services (PayFort) Payment Processor backend for Open edX ecommerce
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..febd711
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,92 @@
+Amazon Payment Services (PayFort) Payment Processor backend for Open edX ecommerce
+==================================================================================
+
+This application provides a custom `Open edX ecommerce `
+payment processor backend for the `Amazon Payment Services `_.
+
+Getting Started with PayFort Integration Development
+####################################################
+
+The API documentation for the PayFort payment services is available at:
+
+- https://paymentservices-reference.payfort.com/docs/api/build/index.html
+
+Scroll through the page, it's a very long single-page documentation.
+
+There's no official Python SDK for PayFort. But we can use the
+`PayFort PHP SDK `_ to understand the API calls and redirect mechanism.
+
+There's a 2016 unofficial Python SDK for PayFort: https://github.com/alisterionold/payfort-python
+
+There's a 2018 blog documenting the integration process:
+
+- `Integrating PayFort — we suffered so you don't have to `_
+
+Testing Credit Cards
+####################
+
+When the testing account is used,
+`PayFort test payment card numbers `_ can be used.
+
+
+Development and Testing
+#######################
+
+TBD: Add devstck and Tutor instructions.
+
+
+To run tests locally in your machine, you need to install the following dependencies::
+
+ $ pip install tox
+
+Then run the all tests::
+
+ $ make tests
+
+
+Or run the unit tests only::
+
+ $ make unit_tests
+
+To run quality quality::
+
+ $ make quality
+
+
+``tox`` can be used directly to run a specific, for example::
+
+ $ tox -e py38 -- tests/unit/test_payfort_utils.py
+
+
+Installation and usage
+######################
+
+* Install this repository inside the ecommerce virtualenv environment using `pip`.
+* In `ecommerce.yml`, add the following settings:
+ ::
+
+ ADDL_INSTALLED_APPS:
+ - payfort
+ ADDL_PAYMENT_PROCESSORS:
+ - 'ecommerce_payfort.processors.PayFort'
+ # many other settings
+ PAYMENT_PROCESSOR_CONFIG:
+ :
+ payfort:
+
+
+* Restart the `ecommerce` service in production and the devserver in the devstack.
+* In the `ecommerce` Django admin site, create waffle switches `payment_processor_active_payfort`, ` to enable the backends.
+* Verify and ensure that the `enable_client_side_checkout` waffle flag is disabled for everyone.
+* Once these steps are done, the `PayFort` processor backend provided by this application will be available as payment options
+ during the payment flow for purchasing paid seats in courses.
+
+
+Author
+######
+
+This application was developed by `ZeitLabs `_ at the request of
+`The National Learning Center `_.
+
+This application is released under the terms of the `AGPLv3 license `_
+and is based on the `ecommmerce-hyperpay plugin `_.
diff --git a/ecommerce_payfort/__init__.py b/ecommerce_payfort/__init__.py
new file mode 100644
index 0000000..43aa411
--- /dev/null
+++ b/ecommerce_payfort/__init__.py
@@ -0,0 +1,4 @@
+"""
+The PayFort payment processor pluggable application for Open edX ecommerce.
+"""
+default_app_config = 'ecommerce_payfort.apps.PayFortConfig'
diff --git a/ecommerce_payfort/apps.py b/ecommerce_payfort/apps.py
new file mode 100644
index 0000000..4e0698d
--- /dev/null
+++ b/ecommerce_payfort/apps.py
@@ -0,0 +1,18 @@
+"""
+PayFort payment processor Django application initialization.
+"""
+from django.apps import AppConfig
+
+
+class PayFortConfig(AppConfig):
+ """
+ Configuration for the PayFort payment processor Django application.
+ """
+ name = 'ecommerce_payfort'
+ plugin_app = {
+ 'url_config': {
+ 'ecommerce': {
+ 'namespace': 'ecommerce_payfort',
+ }
+ },
+ }
diff --git a/ecommerce_payfort/locale/ar/LC_MESSAGES/django.po b/ecommerce_payfort/locale/ar/LC_MESSAGES/django.po
new file mode 100644
index 0000000..e341d8a
--- /dev/null
+++ b/ecommerce_payfort/locale/ar/LC_MESSAGES/django.po
@@ -0,0 +1,23 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+# Translators:
+# Johan , 2023
+# NELC Open edX Translation , 2023
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-05-31 14:35+0000\n"
+"PO-Revision-Date: 2021-04-30 13:21+0000\n"
+"Last-Translator: NELC Open edX Translation , 2021\n"
+"Language-Team: Arabic (https://www.transifex.com/national-center-for-e-learning-and-distance-learning/teams/118545/ar/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: ar\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
diff --git a/ecommerce_payfort/locale/config.yaml b/ecommerce_payfort/locale/config.yaml
new file mode 100644
index 0000000..44b935c
--- /dev/null
+++ b/ecommerce_payfort/locale/config.yaml
@@ -0,0 +1,5 @@
+# Configuration for i18n workflow.
+
+locales:
+ - en # English - Source Language
+ - ar # Arabic
diff --git a/ecommerce_payfort/processors.py b/ecommerce_payfort/processors.py
new file mode 100644
index 0000000..5fd8b3e
--- /dev/null
+++ b/ecommerce_payfort/processors.py
@@ -0,0 +1,41 @@
+"""
+PayFort payment processor.
+"""
+
+import logging
+
+from django.utils.translation import ugettext_lazy as _
+from oscar.apps.payment.exceptions import GatewayError
+
+from ecommerce.extensions.payment.processors import BasePaymentProcessor
+
+logger = logging.getLogger(__name__)
+
+
+def format_price(price):
+ """
+ Return the price in the expected format.
+ """
+ return '{:0.2f}'.format(price)
+
+
+class PayFortException(GatewayError):
+ """
+ An umbrella exception to catch all errors from PayFort.
+ """
+ pass # pylint: disable=unnecessary-pass
+
+
+class PayFort(BasePaymentProcessor):
+ """
+ PayFort payment processor.
+
+ For reference, see https://paymentservices-reference.payfort.com/docs/api/build/index.html
+ Scroll through the page, it's a very long single-page documentation.
+ """
+
+ NAME = 'payfort'
+ CHECKOUT_TEXT = _("Checkout with credit card")
+
+ def __init__(self, site):
+ self.site = site
diff --git a/ecommerce_payfort/templates/payment/payfort.html b/ecommerce_payfort/templates/payment/payfort.html
new file mode 100644
index 0000000..edc69ad
--- /dev/null
+++ b/ecommerce_payfort/templates/payment/payfort.html
@@ -0,0 +1,43 @@
+{% load i18n %}
+{% load static %}
+{% load compress %}
+
+
+
+
+
+ {% trans "PayFort" %} - {{ payment_mode }}
+ {% compress css %}
+ {% if main_css %}
+
+ {% else %}
+
+ {% endif %}
+ {% endcompress %}
+
+ {% compress css %}
+ {# This block is separated to better support browser caching. #}
+ {% block stylesheets %}
+ {% endblock %}
+ {% endcompress %}
+
+
+
+
+
+ {# This adds the header for the page. #}
+ {% include 'edx/partials/_student_navbar.html' %}
+
+
+ {% compress js %}
+
+
+
+ Note: django-compressor does not recognize the data-main attribute. Load the main script separately.
+
+ {% endcompress %}
+
+
diff --git a/ecommerce_payfort/tests/__init__.py b/ecommerce_payfort/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ecommerce_payfort/tests/mixins.py b/ecommerce_payfort/tests/mixins.py
new file mode 100644
index 0000000..4d51704
--- /dev/null
+++ b/ecommerce_payfort/tests/mixins.py
@@ -0,0 +1,30 @@
+import responses
+from django.conf import settings
+from oscar.core.loading import get_class, get_model
+from six.moves.urllib.parse import urljoin
+
+CURRENCY = 'SAR'
+Basket = get_model('basket', 'Basket')
+Order = get_model('order', 'Order')
+PaymentEventType = get_model('order', 'PaymentEventType')
+PaymentProcessorResponse = get_model('payment', 'PaymentProcessorResponse')
+SourceType = get_model('payment', 'SourceType')
+
+post_checkout = get_class('checkout.signals', 'post_checkout')
+
+
+class PayFortMixin:
+ """
+ Mixin with helper methods for mocking PayFort API responses.
+ """
+
+ def mock_api_response(self, path, body, method=responses.POST, resp=responses):
+ url = self._create_api_url(path=path)
+ resp.add(method, url, json=body)
+
+ def _create_api_url(self, path):
+ """
+ Returns the API URL
+ """
+ base_url = settings.PAYMENT_PROCESSOR_CONFIG['edx']['payfort']['payfort_base_api_url']
+ return urljoin(base_url, path)
diff --git a/ecommerce_payfort/tests/processors/__init__.py b/ecommerce_payfort/tests/processors/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ecommerce_payfort/tests/processors/test_payfort.py b/ecommerce_payfort/tests/processors/test_payfort.py
new file mode 100644
index 0000000..b0a3390
--- /dev/null
+++ b/ecommerce_payfort/tests/processors/test_payfort.py
@@ -0,0 +1,12 @@
+import ddt
+import pytest
+
+from ecommerce.extensions.payment.tests.processors.mixins import PaymentProcessorTestCaseMixin
+from ecommerce.tests.testcases import TestCase
+from ecommerce_payfort.tests.mixins import PayFortMixin
+
+
+@ddt.ddt
+@pytest.mark.xfail
+class PayFortTests(PayFortMixin, PaymentProcessorTestCaseMixin, TestCase):
+ pass
diff --git a/ecommerce_payfort/tests/unit/__init__.py b/ecommerce_payfort/tests/unit/__init__.py
new file mode 100644
index 0000000..520bb50
--- /dev/null
+++ b/ecommerce_payfort/tests/unit/__init__.py
@@ -0,0 +1,3 @@
+"""
+Unit tests without external dependencies on ecommerce or payfort.
+"""
diff --git a/ecommerce_payfort/tests/unit/test_payfort_utils.py b/ecommerce_payfort/tests/unit/test_payfort_utils.py
new file mode 100644
index 0000000..8978e03
--- /dev/null
+++ b/ecommerce_payfort/tests/unit/test_payfort_utils.py
@@ -0,0 +1,4 @@
+
+
+def test_utils():
+ pass
diff --git a/ecommerce_payfort/urls.py b/ecommerce_payfort/urls.py
new file mode 100644
index 0000000..a4a9078
--- /dev/null
+++ b/ecommerce_payfort/urls.py
@@ -0,0 +1,16 @@
+"""
+Defines the URL routes for the payfort app.
+"""
+from django.conf.urls import url
+
+from .views import PayFortPaymentPageView, PayFortResponseView
+
+urlpatterns = [
+ url(r'^payment/payfort/pay/$', PayFortPaymentPageView.as_view(), name='payment-form'),
+ url(r'^payment/payfort/submit/$', PayFortResponseView.as_view(), name='submit'),
+ url(
+ r'^payment/payfort/status/(?P.+)/$',
+ PayFortResponseView.as_view(),
+ name='status-check'
+ ),
+]
diff --git a/ecommerce_payfort/views.py b/ecommerce_payfort/views.py
new file mode 100644
index 0000000..c340351
--- /dev/null
+++ b/ecommerce_payfort/views.py
@@ -0,0 +1,50 @@
+"""
+Views related to the PayFort payment processor.
+"""
+
+import logging
+
+from django.db import transaction
+from django.shortcuts import render
+from django.utils.decorators import method_decorator
+from django.views.decorators.csrf import csrf_exempt
+from django.views.generic import View
+from oscar.core.loading import get_class, get_model
+
+from ecommerce.extensions.checkout.mixins import EdxOrderPlacementMixin
+
+from .processors import PayFort
+
+logger = logging.getLogger(__name__)
+
+Applicator = get_class('offer.applicator', 'Applicator')
+Basket = get_model('basket', 'Basket')
+OrderNumberGenerator = get_class('order.utils', 'OrderNumberGenerator')
+
+
+class PayFortPaymentPageView(View):
+ """
+ Render the template which loads the PayFort payment form via JavaScript
+ """
+ template_name = 'payment/payfort.html'
+
+ def post(self, request):
+ """
+ Handles the POST request.
+ """
+ return render(request, self.template_name, request.POST.dict())
+
+
+class PayFortResponseView(EdxOrderPlacementMixin, View):
+ """
+ Handle the response from PayFort after processing the payment.
+ """
+
+ @property
+ def payment_processor(self):
+ return PayFort(self.request.site)
+
+ @method_decorator(transaction.non_atomic_requests)
+ @method_decorator(csrf_exempt)
+ def dispatch(self, request, *args, **kwargs):
+ return super(PayFortResponseView, self).dispatch(request, *args, **kwargs)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..acdca76
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+pytest==7.4.3
+pytest-cov==4.1.0
diff --git a/requirements/ecommerce-maple.master.txt b/requirements/ecommerce-maple.master.txt
new file mode 100644
index 0000000..8f8908f
--- /dev/null
+++ b/requirements/ecommerce-maple.master.txt
@@ -0,0 +1,964 @@
+# This file has been downloaded by "make download_ecommerce_requirements"
+# Every thing else below this line is a copy from the openedx/ecommerce test.txt requirements file
+#
+# This file is autogenerated by pip-compile with python 3.8
+# To update, run:
+#
+# make upgrade
+#
+amqp==2.6.1
+ # via
+ # -r requirements/base.txt
+ # kombu
+analytics-python==1.4.0
+ # via -r requirements/base.txt
+asgiref==3.4.1
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # django
+asn1crypto==1.4.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+astroid==2.3.3
+ # via pylint
+attrs==21.2.0
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # jsonschema
+ # pytest
+ # zeep
+babel==2.9.1
+ # via
+ # -r requirements/base.txt
+ # django-oscar
+ # django-phonenumber-field
+backoff==1.10.0
+ # via
+ # -r requirements/base.txt
+ # analytics-python
+bcrypt==3.2.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+ # paramiko
+beautifulsoup4==4.10.0
+ # via webtest
+billiard==3.6.4.0
+ # via
+ # -r requirements/base.txt
+ # celery
+bleach==4.1.0
+ # via -r requirements/base.txt
+bok-choy==1.1.1
+ # via -r requirements/test.in
+boto3==1.19.10
+ # via -r requirements/base.txt
+botocore==1.22.10
+ # via
+ # -r requirements/base.txt
+ # boto3
+ # s3transfer
+cached-property==1.5.2
+ # via
+ # -r requirements/base.txt
+ # zeep
+celery==4.4.7
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/base.txt
+ # edx-ecommerce-worker
+certifi==2021.10.8
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # cybersource-rest-client-python
+ # requests
+cffi==1.14.6
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # bcrypt
+ # cryptography
+ # cybersource-rest-client-python
+ # pynacl
+chardet==4.0.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+ # diff-cover
+charset-normalizer==2.0.6
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # requests
+configparser==5.1.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+coreapi==2.3.3
+ # via
+ # -r requirements/base.txt
+ # django-rest-swagger
+ # openapi-codec
+coreschema==0.0.4
+ # via
+ # -r requirements/base.txt
+ # coreapi
+coverage[toml]==6.1.1
+ # via
+ # -r requirements/base.txt
+ # -r requirements/test.in
+ # cybersource-rest-client-python
+ # pytest-cov
+crypto==1.4.1
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+cryptography==3.4.8
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # cybersource-rest-client-python
+ # paramiko
+ # pyjwt
+ # pyopenssl
+ # social-auth-core
+cssselect==1.1.0
+ # via
+ # -r requirements/base.txt
+ # premailer
+cssutils==2.3.0
+ # via
+ # -r requirements/base.txt
+ # premailer
+cybersource-rest-client-python==0.0.21
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/base.txt
+datetime==4.3
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+ddt==1.4.4
+ # via -r requirements/test.in
+defusedxml==0.7.1
+ # via
+ # -r requirements/base.txt
+ # python3-openid
+ # social-auth-core
+diff-cover==6.4.2
+ # via -r requirements/test.in
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # django-appconf
+ # django-config-models
+ # django-cors-headers
+ # django-crum
+ # django-extensions
+ # django-extra-views
+ # django-filter
+ # django-haystack
+ # django-model-utils
+ # django-oscar
+ # django-phonenumber-field
+ # django-tables2
+ # django-treebeard
+ # djangorestframework
+ # drf-jwt
+ # edx-auth-backends
+ # edx-django-release-util
+ # edx-django-sites-extensions
+ # edx-django-utils
+ # edx-drf-extensions
+ # edx-i18n-tools
+ # edx-rbac
+ # jsonfield2
+ # mock-django
+ # rest-condition
+ # xss-utils
+django-appconf==1.0.5
+ # via
+ # -r requirements/base.txt
+ # django-compressor
+django-compressor==2.4.1
+ # via
+ # -r requirements/base.txt
+ # django-libsass
+django-config-models==2.2.0
+ # via -r requirements/base.txt
+django-cors-headers==3.10.0
+ # via -r requirements/base.txt
+django-crispy-forms==1.13.0
+ # via -r requirements/base.txt
+django-crum==0.7.9
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # edx-django-utils
+ # edx-rbac
+django-extensions==3.1.3
+ # via -r requirements/base.txt
+django-extra-views==0.13.0
+ # via
+ # -r requirements/base.txt
+ # django-oscar
+django-filter==21.1
+ # via -r requirements/base.txt
+django-haystack==3.1.1
+ # via
+ # -r requirements/base.txt
+ # django-oscar
+django-libsass==0.9
+ # via -r requirements/base.txt
+django-model-utils==4.1.1
+ # via
+ # -r requirements/base.txt
+ # edx-rbac
+django-oscar==2.2
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/base.txt
+django-phonenumber-field==3.0.1
+ # via
+ # -r requirements/base.txt
+ # django-oscar
+django-rest-swagger==2.2.0
+ # via -r requirements/base.txt
+django-simple-history==3.0.0
+ # via -r requirements/base.txt
+django-solo==1.2.0
+ # via -r requirements/base.txt
+django-tables2==2.4.1
+ # via
+ # -r requirements/base.txt
+ # django-oscar
+django-threadlocals==0.10
+ # via -r requirements/base.txt
+django-treebeard==4.4
+ # via
+ # -r requirements/base.txt
+ # django-oscar
+django-waffle==2.2.1
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # edx-django-utils
+ # edx-drf-extensions
+django-webtest==1.9.8
+ # via -r requirements/test.in
+django-widget-tweaks==1.4.8
+ # via
+ # -r requirements/base.txt
+ # django-oscar
+djangorestframework==3.12.4
+ # via
+ # -r requirements/base.txt
+ # django-config-models
+ # django-rest-swagger
+ # djangorestframework-csv
+ # djangorestframework-datatables
+ # drf-extensions
+ # drf-jwt
+ # edx-drf-extensions
+ # rest-condition
+djangorestframework-csv==2.1.1
+ # via -r requirements/base.txt
+djangorestframework-datatables==0.6.0
+ # via -r requirements/base.txt
+drf-extensions==0.7.1
+ # via -r requirements/base.txt
+drf-jwt==1.19.1
+ # via
+ # -r requirements/base.txt
+ # edx-drf-extensions
+edx-auth-backends==3.4.0
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/base.txt
+edx-django-release-util==1.1.0
+ # via -r requirements/base.txt
+edx-django-sites-extensions==3.1.0
+ # via -r requirements/base.txt
+edx-django-utils==4.4.0
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # django-config-models
+ # edx-drf-extensions
+ # edx-rest-api-client
+edx-drf-extensions==6.6.0
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/base.txt
+ # edx-rbac
+edx-ecommerce-worker==3.0.0
+ # via -r requirements/base.txt
+edx-i18n-tools==0.8.1
+ # via -r requirements/test.in
+edx-opaque-keys==2.2.2
+ # via
+ # -r requirements/base.txt
+ # edx-drf-extensions
+edx-rbac==1.5.1
+ # via -r requirements/base.txt
+edx-rest-api-client==5.4.0
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # edx-ecommerce-worker
+enum34==1.1.10
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+extras==1.0.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+ # python-subunit
+ # testtools
+factory-boy==2.12.0
+ # via
+ # -r requirements/base.txt
+ # -r requirements/test.in
+ # django-oscar
+faker==9.8.0
+ # via
+ # -r requirements/base.txt
+ # factory-boy
+filelock==3.3.0
+ # via
+ # -r requirements/tox.txt
+ # tox
+fixtures==3.0.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+ # testtools
+freezegun==1.1.0
+ # via -r requirements/test.in
+funcsigs==1.0.2
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+future==0.18.2
+ # via
+ # -r requirements/base.txt
+ # pyjwkest
+httpretty==0.9.7
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/test.in
+idna==2.7
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # cybersource-rest-client-python
+ # requests
+importlib-metadata==4.8.1
+ # via
+ # -r requirements/e2e.txt
+ # pytest-randomly
+inflect==5.3.0
+ # via jinja2-pluralize
+iniconfig==1.1.1
+ # via
+ # -r requirements/e2e.txt
+ # pytest
+ipaddress==1.0.23
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+isodate==0.6.0
+ # via
+ # -r requirements/base.txt
+ # zeep
+isort==4.3.21
+ # via
+ # -r requirements/test.in
+ # pylint
+itypes==1.2.0
+ # via
+ # -r requirements/base.txt
+ # coreapi
+jinja2==3.0.2
+ # via
+ # -r requirements/base.txt
+ # coreschema
+ # diff-cover
+ # jinja2-pluralize
+jinja2-pluralize==0.3.0
+ # via diff-cover
+jmespath==0.10.0
+ # via
+ # -r requirements/base.txt
+ # boto3
+ # botocore
+jsonfield2==3.0.3
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/base.txt
+jsonschema==3.2.0
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+kombu==4.6.11
+ # via
+ # -r requirements/base.txt
+ # celery
+lazy==1.4
+ # via bok-choy
+lazy-object-proxy==1.4.3
+ # via astroid
+libsass==0.9.2
+ # via
+ # -r requirements/base.txt
+ # django-libsass
+linecache2==1.0.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+ # traceback2
+logger==1.4
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+lxml==4.6.4
+ # via
+ # -r requirements/base.txt
+ # -r requirements/test.in
+ # premailer
+ # zeep
+markdown==2.6.9
+ # via -r requirements/base.txt
+markupsafe==2.0.1
+ # via
+ # -r requirements/base.txt
+ # jinja2
+mccabe==0.6.1
+ # via pylint
+mock==4.0.3
+ # via
+ # -r requirements/test.in
+ # mock-django
+mock-django==0.6.10
+ # via -r requirements/test.in
+monotonic==1.6
+ # via
+ # -r requirements/base.txt
+ # analytics-python
+mysqlclient==1.4.6
+ # via -r requirements/base.txt
+naked==0.1.31
+ # via
+ # -r requirements/base.txt
+ # crypto
+ # cybersource-rest-client-python
+ndg-httpsclient==0.5.1
+ # via -r requirements/base.txt
+newrelic==7.0.0.166
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # edx-django-utils
+nose==1.3.7
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+oauthlib==3.1.1
+ # via
+ # -r requirements/base.txt
+ # requests-oauthlib
+ # social-auth-core
+openapi-codec==1.3.2
+ # via
+ # -r requirements/base.txt
+ # django-rest-swagger
+packaging==21.0
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # -r requirements/tox.txt
+ # bleach
+ # pytest
+ # tox
+paramiko==2.8.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+path==16.2.0
+ # via edx-i18n-tools
+path.py==7.2
+ # via -r requirements/base.txt
+paypalrestsdk==1.13.1
+ # via -r requirements/base.txt
+pbr==5.7.0
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # cybersource-rest-client-python
+ # fixtures
+ # stevedore
+ # testtools
+phonenumbers==8.12.34
+ # via
+ # -r requirements/base.txt
+ # django-oscar
+pillow==8.4.0
+ # via
+ # -r requirements/base.txt
+ # django-oscar
+platformdirs==2.4.0
+ # via
+ # -r requirements/base.txt
+ # zeep
+pluggy==0.13.1
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/e2e.txt
+ # -r requirements/tox.txt
+ # diff-cover
+ # pytest
+ # tox
+polib==1.1.1
+ # via edx-i18n-tools
+premailer==2.9.2
+ # via -r requirements/base.txt
+psutil==5.8.0
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # edx-django-utils
+purl==1.6
+ # via
+ # -r requirements/base.txt
+ # django-oscar
+py==1.10.0
+ # via
+ # -r requirements/e2e.txt
+ # -r requirements/tox.txt
+ # pytest
+ # tox
+pyasn1==0.4.8
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+ # ndg-httpsclient
+ # rsa
+ # x509
+pycodestyle==2.8.0
+ # via -r requirements/test.in
+pycountry==17.1.8
+ # via -r requirements/base.txt
+pycparser==2.20
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # cffi
+ # cybersource-rest-client-python
+pycryptodome==3.11.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+pycryptodomex==3.11.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+ # pyjwkest
+pygments==2.10.0
+ # via
+ # -r requirements/base.txt
+ # diff-cover
+pyjwkest==1.4.2
+ # via
+ # -r requirements/base.txt
+ # edx-drf-extensions
+pyjwt[crypto]==1.7.1
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # cybersource-rest-client-python
+ # drf-jwt
+ # edx-auth-backends
+ # edx-rest-api-client
+ # social-auth-core
+pylint==2.4.4
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/test.in
+pymongo==3.12.0
+ # via
+ # -r requirements/base.txt
+ # edx-opaque-keys
+pynacl==1.4.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+ # paramiko
+pyopenssl==21.0.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+ # ndg-httpsclient
+ # paypalrestsdk
+pyparsing==2.4.7
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # -r requirements/tox.txt
+ # packaging
+pypi==2.1
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+pyrsistent==0.18.0
+ # via
+ # -r requirements/base.txt
+ # jsonschema
+pytest==6.2.5
+ # via
+ # -r requirements/e2e.txt
+ # -r requirements/test.in
+ # pytest-base-url
+ # pytest-cov
+ # pytest-django
+ # pytest-html
+ # pytest-metadata
+ # pytest-randomly
+ # pytest-selenium
+ # pytest-timeout
+ # pytest-variables
+pytest-base-url==1.4.2
+ # via
+ # -r requirements/e2e.txt
+ # pytest-selenium
+pytest-cov==3.0.0
+ # via -r requirements/test.in
+pytest-django==4.4.0
+ # via
+ # -r requirements/test.in
+pytest-html==3.1.1
+ # via
+ # -r requirements/e2e.txt
+ # pytest-selenium
+pytest-metadata==1.11.0
+ # via
+ # -r requirements/e2e.txt
+ # pytest-html
+pytest-randomly==3.10.1
+ # via -r requirements/e2e.txt
+pytest-selenium==2.0.1
+ # via -r requirements/e2e.txt
+pytest-timeout==2.0.0
+ # via -r requirements/e2e.txt
+pytest-variables==1.9.0
+ # via
+ # -r requirements/e2e.txt
+ # pytest-selenium
+python-dateutil==2.8.2
+ # via
+ # -r requirements/base.txt
+ # analytics-python
+ # botocore
+ # edx-drf-extensions
+ # faker
+ # freezegun
+python-dotenv==0.19.1
+ # via -r requirements/e2e.txt
+python-memcached==1.59
+ # via -r requirements/test.in
+python-mimeparse==1.6.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+python-subunit==1.4.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+python-toolbox==1.0.11
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+python3-openid==3.2.0
+ # via
+ # -r requirements/base.txt
+ # social-auth-core
+pytz==2016.10
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # babel
+ # celery
+ # cybersource-rest-client-python
+ # datetime
+ # django
+ # zeep
+pyyaml==5.4.1
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+ # edx-django-release-util
+ # edx-i18n-tools
+ # naked
+rcssmin==1.0.6
+ # via
+ # -r requirements/base.txt
+ # django-compressor
+redis==3.5.3
+ # via
+ # -r requirements/base.txt
+ # edx-ecommerce-worker
+requests==2.26.0
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # analytics-python
+ # coreapi
+ # cybersource-rest-client-python
+ # edx-drf-extensions
+ # edx-rest-api-client
+ # naked
+ # paypalrestsdk
+ # pyjwkest
+ # pytest-base-url
+ # pytest-selenium
+ # requests-file
+ # requests-oauthlib
+ # requests-toolbelt
+ # responses
+ # slumber
+ # social-auth-core
+ # stripe
+ # zeep
+requests-file==1.5.1
+ # via
+ # -r requirements/base.txt
+ # zeep
+requests-oauthlib==1.3.0
+ # via
+ # -r requirements/base.txt
+ # social-auth-core
+requests-toolbelt==0.9.1
+ # via
+ # -r requirements/base.txt
+ # zeep
+responses==0.14.0
+ # via -r requirements/test.in
+rest-condition==1.0.3
+ # via
+ # -r requirements/base.txt
+ # edx-drf-extensions
+rjsmin==1.1.0
+ # via
+ # -r requirements/base.txt
+ # django-compressor
+rsa==4.7.2
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+rules==3.0
+ # via -r requirements/base.txt
+s3transfer==0.5.0
+ # via
+ # -r requirements/base.txt
+ # boto3
+selenium==3.141.0
+ # via
+ # -r requirements/e2e.txt
+ # -r requirements/test.in
+ # bok-choy
+ # pytest-selenium
+semantic-version==2.8.5
+ # via
+ # -r requirements/base.txt
+ # edx-drf-extensions
+shellescape==3.8.1
+ # via
+ # -r requirements/base.txt
+ # crypto
+ # cybersource-rest-client-python
+simplejson==3.17.5
+ # via
+ # -r requirements/base.txt
+ # django-rest-swagger
+six==1.16.0
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # -r requirements/tox.txt
+ # analytics-python
+ # astroid
+ # bcrypt
+ # bleach
+ # bok-choy
+ # cybersource-rest-client-python
+ # django-compressor
+ # django-extra-views
+ # djangorestframework-csv
+ # edx-auth-backends
+ # edx-django-release-util
+ # edx-drf-extensions
+ # edx-ecommerce-worker
+ # edx-rbac
+ # fixtures
+ # httpretty
+ # isodate
+ # jsonschema
+ # libsass
+ # paypalrestsdk
+ # purl
+ # pyjwkest
+ # pynacl
+ # pyopenssl
+ # python-dateutil
+ # python-memcached
+ # requests-file
+ # responses
+ # social-auth-app-django
+ # social-auth-core
+ # tenacity
+ # tox
+slumber==0.7.1
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # edx-rest-api-client
+social-auth-app-django==4.0.0
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/base.txt
+ # edx-auth-backends
+social-auth-core==4.0.2
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/base.txt
+ # edx-auth-backends
+ # social-auth-app-django
+sorl-thumbnail==12.7.0
+ # via -r requirements/base.txt
+soupsieve==2.3
+ # via beautifulsoup4
+sqlparse==0.4.2
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # django
+stevedore==3.4.0
+ # via
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # edx-django-utils
+ # edx-opaque-keys
+stripe==1.70.0
+ # via -r requirements/base.txt
+tenacity==6.3.1
+ # via
+ # -r requirements/e2e.txt
+ # pytest-selenium
+testfixtures==6.18.3
+ # via -r requirements/test.in
+testtools==2.5.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+ # fixtures
+ # python-subunit
+text-unidecode==1.3
+ # via
+ # -r requirements/base.txt
+ # faker
+toml==0.10.2
+ # via
+ # -r requirements/e2e.txt
+ # -r requirements/tox.txt
+ # pytest
+ # tox
+tomli==1.2.1
+ # via coverage
+tox==3.14.6
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/tox.txt
+ # tox-battery
+tox-battery==0.6.1
+ # via -r requirements/tox.txt
+traceback2==1.4.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+typing==3.7.4.3
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+unicodecsv==0.14.1
+ # via
+ # -r requirements/base.txt
+ # djangorestframework-csv
+uritemplate==4.0.0
+ # via
+ # -r requirements/base.txt
+ # coreapi
+urllib3==1.26.7
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/base.txt
+ # -r requirements/e2e.txt
+ # botocore
+ # cybersource-rest-client-python
+ # requests
+ # responses
+ # selenium
+vine==1.3.0
+ # via
+ # -r requirements/base.txt
+ # amqp
+ # celery
+virtualenv==16.7.9
+ # via
+ # -c requirements/pins.txt
+ # -r requirements/tox.txt
+ # tox
+waitress==2.0.0
+ # via webtest
+webencodings==0.5.1
+ # via
+ # -r requirements/base.txt
+ # bleach
+webob==1.8.7
+ # via webtest
+webtest==3.0.0
+ # via django-webtest
+wheel==0.37.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+wrapt==1.11.2
+ # via astroid
+x509==0.1
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+xss-utils==0.3.0
+ # via -r requirements/base.txt
+zeep==4.1.0
+ # via -r requirements/base.txt
+zipp==3.6.0
+ # via
+ # -r requirements/e2e.txt
+ # importlib-metadata
+zope.interface==5.4.0
+ # via
+ # -r requirements/base.txt
+ # cybersource-rest-client-python
+ # datetime
+
+# The following packages are considered to be unsafe in a requirements file:
+# pip
+# setuptools
diff --git a/requirements/payfort-test.txt b/requirements/payfort-test.txt
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/tox_install_ecommerce_run_pytest.sh b/scripts/tox_install_ecommerce_run_pytest.sh
new file mode 100644
index 0000000..c5c187f
--- /dev/null
+++ b/scripts/tox_install_ecommerce_run_pytest.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+# Clone the openedx/ecommerce and install the ecommerce-payfort package and run tests
+# This is refactored form the following CircleCI config file:
+# - https://github.com/open-craft/ecommerce-hyperpay/blob/main/.circleci/config.yml
+#
+# Usage:
+#
+# bash ./scripts/tox_install_ecommerce_run_pytest.sh pytest -v # Run all tests
+#
+# bash ./scripts/tox_install_ecommerce_run_pytest.sh pytest -v tests/test_utils.py # Run a specific test
+#
+#
+
+set -e
+
+export PYTHONWARNINGS=ignore # Suppress warnings from `openedx/ecommerce` code
+
+pip install -e . # Install ecommerce_payfort into the virtualenv
+
+if [ ! -d ".tox/ecommerce-maple.master" ]; then
+ git clone --single-branch --branch=open-release/maple.master --depth=1 https://github.com/openedx/ecommerce.git .tox/ecommerce-maple.master
+fi
+
+rm -rf .tox/ecommerce-maple.master/ecommerce_payfort
+cat settings/payfort.py > .tox/ecommerce-maple.master/ecommerce/settings/payfort.py
+
+cd .tox/ecommerce-maple.master/ecommerce
+
+
+"$@" # Arguments passed to this script
diff --git a/settings/ci.py b/settings/ci.py
new file mode 100644
index 0000000..2ce4b37
--- /dev/null
+++ b/settings/ci.py
@@ -0,0 +1,14 @@
+from ecommerce.settings.payfort import *
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.mysql',
+ 'NAME': 'test_ecommerce',
+ 'USER': 'root',
+ 'PASSWORD': 'password',
+ 'HOST': '127.0.0.1',
+ 'PORT': '3306',
+ 'ATOMIC_REQUESTS': True,
+ 'CONN_MAX_AGE': 60,
+ }
+}
diff --git a/settings/payfort.py b/settings/payfort.py
new file mode 100644
index 0000000..4669f30
--- /dev/null
+++ b/settings/payfort.py
@@ -0,0 +1,3 @@
+from ecommerce.settings.test import *
+
+INSTALLED_APPS += ['ecommerce_payfort']
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..ea30069
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,64 @@
+"""
+Setup file for the ecommerce-payfort Open edX ecommerce payment processor backend plugin.
+"""
+import os
+
+from pathlib import Path
+
+from setuptools import setup
+
+README = open(Path(__file__).parent / 'README.rst').read()
+CHANGELOG = open(Path(__file__).parent / 'CHANGELOG.rst').read()
+
+
+def package_data(pkg, roots):
+ """Generic function to find package_data.
+
+ All of the files under each of the `roots` will be declared as package
+ data for package `pkg`.
+
+ """
+ data = []
+ for root in roots:
+ for dirname, _, files in os.walk(os.path.join(pkg, root)):
+ for fname in files:
+ data.append(os.path.relpath(os.path.join(dirname, fname), pkg))
+
+ return {pkg: data}
+
+
+setup(
+ name='ecommerce-payfort',
+ description='PayFort ecommerce payment processor backend plugin',
+ version='0.1.0',
+ author='ZeitLabs',
+ author_email='info@zeitlabs.com',
+ long_description=f'{README}\n\n{CHANGELOG}',
+ long_description_content_type='text/x-rst',
+ url='https://github.com/Zeit-Labs/ecommerce-payfort',
+ include_package_data=True,
+ zip_safe=False,
+ keywords='Django openedx openedx-plugin ecommerce payfort',
+ classifiers=[
+ 'Development Status :: 3 - Alpha',
+ 'Framework :: Django',
+ 'Framework :: Django :: 2.2',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
+ 'Natural Language :: English',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.8',
+ ],
+ install_requires=[
+ 'Django~=3.2',
+ ],
+ package_data=package_data('ecommerce_payfort', ['locale']),
+ packages=[
+ 'ecommerce_payfort',
+ ],
+ entry_points={
+ 'ecommerce': [
+ 'ecommerce_payfort = payfort.apps:PayFortConfig',
+ ],
+ },
+)
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..4e13a65
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,27 @@
+[tox]
+envlist = flake8,py38
+skipsdist = True
+
+[pytest]
+addopts = --cov=payfort --cov-report=term-missing --cov-report=html -rxXs
+
+[flake8]
+max-line-length = 120
+
+[testenv]
+usedevelop=True
+allowlist_externals = bash
+setenv =
+ DJANGO_SETTINGS_MODULE = ecommerce.settings.payfort
+
+deps =
+ -r{toxinidir}/requirements/ecommerce-maple.master.txt
+ -r{toxinidir}/requirements/payfort-test.txt
+
+commands =
+ # Clone the openedx/ecommerce and install the ecommerce-payfort package and run tests
+ bash ./scripts/tox_install_ecommerce_run_pytest.sh pytest {toxinidir}/ecommerce_payfort/{posargs}
+
+[testenv:flake8]
+deps = flake8
+commands = flake8 ecommerce_payfort setup.py