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