From 8303900c5f1c7d5960bdd7a1a5be3f2e1f770aad Mon Sep 17 00:00:00 2001
From: NyanKiyoshi <6186720+NyanKiyoshi@users.noreply.github.com>
Date: Fri, 10 Aug 2018 10:39:21 +0200
Subject: [PATCH 1/7] Moved base implementation to its own package
---
django_payments_razorpay/__init__.py | 59 +++++++++++++++++++++++++++
django_payments_razorpay/forms.py | 50 +++++++++++++++++++++++
django_payments_razorpay/widgets.py | 44 ++++++++++++++++++++
setup.py | 60 ++++++++++++++++++++++++++++
4 files changed, 213 insertions(+)
create mode 100644 django_payments_razorpay/__init__.py
create mode 100644 django_payments_razorpay/forms.py
create mode 100644 django_payments_razorpay/widgets.py
create mode 100755 setup.py
diff --git a/django_payments_razorpay/__init__.py b/django_payments_razorpay/__init__.py
new file mode 100644
index 0000000..1d4138e
--- /dev/null
+++ b/django_payments_razorpay/__init__.py
@@ -0,0 +1,59 @@
+import json
+from decimal import Decimal
+
+from payments import PaymentStatus, RedirectNeeded
+from payments.core import BasicProvider
+from .forms import ModalPaymentForm
+import razorpay
+import razorpay.errors
+
+
+class RazorPayProvider(BasicProvider):
+
+ form_class = ModalPaymentForm
+ ACCEPTED_CURRENCIES = 'INR',
+
+ def __init__(
+ self,
+ public_key, secret_key,
+ image='', name='', prefill=False, **kwargs):
+
+ # TODO: warn on docs: paisa is the only support currency as of now
+ self.secret_key = secret_key
+ self.public_key = public_key
+ self.image = image
+ self.name = name
+ self.prefill = prefill
+ self.razorpay_client = razorpay.Client(auth=(public_key, secret_key))
+
+ super(RazorPayProvider, self).__init__(**kwargs)
+
+ def get_form(self, payment, data=None):
+ # TODO: raise error if payment.currency is not in ACCEPTED_CURRENCIES
+
+ if payment.status == PaymentStatus.WAITING:
+ payment.change_status(PaymentStatus.INPUT)
+
+ form = self.form_class(
+ data=data, payment=payment, provider=self)
+
+ if form.is_valid():
+ form.save()
+ raise RedirectNeeded(payment.get_success_url())
+ return form
+
+ def charge(self, transaction_id, payment):
+ amount = int(payment.total * 100)
+ charge = self.razorpay_client.payment.capture(transaction_id, amount)
+ return charge
+
+ def refund(self, payment, amount=None):
+ amount = int((amount or payment.captured_amount) * 100)
+ try:
+ refund = self.razorpay_client.payment.refund(
+ payment.transaction_id, amount)
+ except razorpay.errors.BadRequestError as exc:
+ raise ValueError(str(exc))
+ refunded_amount = Decimal(refund['amount']) / 100
+ payment.attrs.refund = json.dumps(refund)
+ return refunded_amount
diff --git a/django_payments_razorpay/forms.py b/django_payments_razorpay/forms.py
new file mode 100644
index 0000000..b0c7e7f
--- /dev/null
+++ b/django_payments_razorpay/forms.py
@@ -0,0 +1,50 @@
+import json
+from decimal import Decimal
+
+from django import forms
+from django.utils.translation import ugettext as _
+from payments import PaymentStatus
+from payments.forms import PaymentForm
+
+from .widgets import RazorPayCheckoutWidget
+
+
+class ModalPaymentForm(PaymentForm):
+ razorpay_payment_id = forms.CharField(
+ required=True, widget=forms.HiddenInput)
+
+ def __init__(self, *args, **kwargs):
+ super(ModalPaymentForm, self).__init__(
+ hidden_inputs=False, autosubmit=True, *args, **kwargs)
+
+ widget = RazorPayCheckoutWidget(
+ provider=self.provider, payment=self.payment)
+ self.fields['razorpay'] = forms.CharField(
+ widget=widget, required=False)
+ self.transaction_id = None
+
+ # TODO: add note to the docs saying there is no fraud status
+ def clean(self):
+ data = super(ModalPaymentForm, self).clean()
+
+ if self.payment.transaction_id:
+ msg = _('This payment has already been processed.')
+ self._errors['__all__'] = self.error_class([msg])
+ else:
+ self.transaction_id = data['razorpay_payment_id']
+
+ charge = self.provider.charge(self.transaction_id, self.payment)
+ captured_amount = Decimal(charge['amount']) / 100
+
+ # FIXME: should we handle the case
+ # of having the captured amount invalid?
+ self.payment.attrs.capture = json.dumps(charge)
+ self.payment.captured_amount = captured_amount
+
+ assert captured_amount == self.payment.total
+
+ return data
+
+ def save(self):
+ self.payment.transaction_id = self.transaction_id
+ self.payment.change_status(PaymentStatus.CONFIRMED)
diff --git a/django_payments_razorpay/widgets.py b/django_payments_razorpay/widgets.py
new file mode 100644
index 0000000..6dd621e
--- /dev/null
+++ b/django_payments_razorpay/widgets.py
@@ -0,0 +1,44 @@
+from django.forms.widgets import HiddenInput
+from django.utils.html import format_html
+from django.utils.translation import ugettext_lazy as _
+
+try:
+ from django.forms.utils import flatatt
+except ImportError:
+ from django.forms.util import flatatt
+
+CHECKOUT_SCRIPT_URL = 'https://checkout.razorpay.com/v1/checkout.js'
+
+
+# TODO: add note to docs: you can use any valid card number
+# like 4111 1111 1111 1111 with any future expiry date and CVV in the test mode
+class RazorPayCheckoutWidget(HiddenInput):
+ def __init__(self, provider, payment, *args, **kwargs):
+ override_attrs = kwargs.get('attrs', None)
+ base_attrs = kwargs['attrs'] = {
+ 'src': CHECKOUT_SCRIPT_URL,
+ 'data-key': provider.public_key,
+ 'data-buttontext': _('Pay now with Razorpay'),
+ 'data-image': provider.image,
+ 'data-name': provider.name,
+ 'data-description': payment.description or _('Total payment'),
+ 'data-amount': int(payment.total * 100)
+ }
+
+ if provider.prefill:
+ customer_name = '%s %s' % (
+ payment.billing_last_name,
+ payment.billing_first_name)
+ base_attrs.update({
+ 'data-prefill.name': customer_name,
+ 'data-prefill.email': payment.billing_email
+ })
+
+ if override_attrs:
+ base_attrs.update(override_attrs)
+ super(RazorPayCheckoutWidget, self).__init__(*args, **kwargs)
+
+ def render(self, name, *args, **kwargs):
+ attrs = kwargs.setdefault('attrs', {})
+ attrs.update(self.attrs)
+ return format_html('', flatatt(attrs))
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..b9a74ce
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+from setuptools import setup
+from setuptools.command.test import test as TestCommand
+from sys import version_info, exit
+
+
+REQUIREMENTS = ['django-payments>=0.12.3', 'razorpay>=1.1.1']
+TEST_REQUIREMENTS = ['pytest', 'pytest-django']
+
+if version_info < (3,):
+ TEST_REQUIREMENTS.append('mock')
+
+
+class PyTest(TestCommand):
+ user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")]
+ test_args = []
+
+ def initialize_options(self):
+ TestCommand.initialize_options(self)
+ self.pytest_args = []
+
+ def finalize_options(self):
+ TestCommand.finalize_options(self)
+ self.test_args = []
+ self.test_suite = True
+
+ def run_tests(self):
+ # import here, cause outside the eggs aren't loaded
+ import pytest
+ errno = pytest.main(self.pytest_args)
+ exit(errno)
+
+
+setup(
+ name='django-payments-razorpay',
+ author='NyanKiyoshi',
+ author_email='hello@vanille.bid',
+ url='https://github.com/NyanKiyoshi/django-payments-razorpay',
+ description='Razorpay provider for django-payments.',
+ version='0.0.0',
+ packages=['django_payments_razorpay'],
+ include_package_data=True,
+ classifiers=[
+ 'Environment :: Web Environment',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.4',
+ 'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
+ 'Framework :: Django',
+ 'Topic :: Software Development :: Libraries :: Application Frameworks',
+ 'Topic :: Software Development :: Libraries :: Python Modules'],
+ install_requires=REQUIREMENTS,
+ cmdclass={'test': PyTest},
+ tests_require=TEST_REQUIREMENTS,
+ zip_safe=False)
From 620c6e37aa8b60d6ba65f058c5c891bd66cf538a Mon Sep 17 00:00:00 2001
From: NyanKiyoshi <6186720+NyanKiyoshi@users.noreply.github.com>
Date: Fri, 10 Aug 2018 11:14:24 +0200
Subject: [PATCH 2/7] Added documentation
---
README.md | 48 ++++++++++++++++++++++++++++
django_payments_razorpay/__init__.py | 1 -
django_payments_razorpay/forms.py | 1 -
django_payments_razorpay/widgets.py | 2 --
4 files changed, 48 insertions(+), 4 deletions(-)
create mode 100644 README.md
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..72ce528
--- /dev/null
+++ b/README.md
@@ -0,0 +1,48 @@
+# Razorpay for django-payments
+
+**WARNING:** only the paisa (INR) currency is supported by Razorpay as of now.
+
+## Installation
+Add `django-payments-razorpay` to your project requirements
+and/ or run the installation with:
+```shell
+pip install django-payments-razorpay
+```
+
+
+## Provider parameters
+First of all, to create your API credentials, you need to go in your Razorpay account settings,
+then in the API Keys section ([direct link](https://dashboard.razorpay.com/#/app/keys)).
+
+| Key | Required | Type | Description |
+| ------------ | ------- | --------- | ----------- |
+| `public_key` | Yes | `string` | Your Razorpay **key id** |
+| `secret_key` | Yes | `string` | Your Razorpay **secret key id** |
+| `image` | No | `string` | An absolute or relative link to your store logo |
+| `name` | No | `string` | Your store name |
+| `prefill` | No | `boolean` | Pre-fill the email and customer's full name if set to `True` (disabled by default) |
+
+
+## Example configuration
+
+In your `settings.py` file, you can add the following keys or append the data to them:
+
+```python
+PAYMENT_VARIANTS = {
+ 'razorpay': ('django_payments_razorpay.RazorPayProvider', {
+ 'public_key': 'RAZORPAY_PUBLIC_KEY',
+ 'secret_key': 'RAZORPAY_SECRET_KEY'})}
+```
+
+Note: if you are using **Saleor**, you may want to add Razorpay to the checkout payment choices:
+
+```python
+CHECKOUT_PAYMENT_CHOICES = [
+ ('razorpay', 'RazorPay')]
+```
+
+
+## Notes
+1. Razorpay automatically capture the whole payment amount;
+2. In test mode, you can use `4111 1111 1111 1111` (or any other valid credit card numbers)
+with any future expiry date and CVV to pay orders.
diff --git a/django_payments_razorpay/__init__.py b/django_payments_razorpay/__init__.py
index 1d4138e..ccae59b 100644
--- a/django_payments_razorpay/__init__.py
+++ b/django_payments_razorpay/__init__.py
@@ -18,7 +18,6 @@ def __init__(
public_key, secret_key,
image='', name='', prefill=False, **kwargs):
- # TODO: warn on docs: paisa is the only support currency as of now
self.secret_key = secret_key
self.public_key = public_key
self.image = image
diff --git a/django_payments_razorpay/forms.py b/django_payments_razorpay/forms.py
index b0c7e7f..0b7b30d 100644
--- a/django_payments_razorpay/forms.py
+++ b/django_payments_razorpay/forms.py
@@ -23,7 +23,6 @@ def __init__(self, *args, **kwargs):
widget=widget, required=False)
self.transaction_id = None
- # TODO: add note to the docs saying there is no fraud status
def clean(self):
data = super(ModalPaymentForm, self).clean()
diff --git a/django_payments_razorpay/widgets.py b/django_payments_razorpay/widgets.py
index 6dd621e..bb5583f 100644
--- a/django_payments_razorpay/widgets.py
+++ b/django_payments_razorpay/widgets.py
@@ -10,8 +10,6 @@
CHECKOUT_SCRIPT_URL = 'https://checkout.razorpay.com/v1/checkout.js'
-# TODO: add note to docs: you can use any valid card number
-# like 4111 1111 1111 1111 with any future expiry date and CVV in the test mode
class RazorPayCheckoutWidget(HiddenInput):
def __init__(self, provider, payment, *args, **kwargs):
override_attrs = kwargs.get('attrs', None)
From 6aeee77587b27ee9aaa0c2296c1e92ff23ac3d7b Mon Sep 17 00:00:00 2001
From: NyanKiyoshi <6186720+NyanKiyoshi@users.noreply.github.com>
Date: Fri, 10 Aug 2018 15:38:41 +0200
Subject: [PATCH 3/7] Implemented tests
---
.travis.yml | 42 +++++++++++
README.md | 2 +-
django_payments_razorpay/__init__.py | 4 -
django_payments_razorpay/forms.py | 11 +--
django_payments_razorpay/widgets.py | 5 +-
setup.py | 29 +-------
tests/__init__.py | 5 ++
tests/conftest.py | 107 +++++++++++++++++++++++++++
tests/settings.py | 15 ++++
tests/test_forms.py | 36 +++++++++
tests/test_provider.py | 91 +++++++++++++++++++++++
tests/test_widgets.py | 37 +++++++++
tox.ini | 29 ++++++++
13 files changed, 369 insertions(+), 44 deletions(-)
create mode 100644 .travis.yml
create mode 100644 tests/__init__.py
create mode 100644 tests/conftest.py
create mode 100644 tests/settings.py
create mode 100644 tests/test_forms.py
create mode 100644 tests/test_provider.py
create mode 100644 tests/test_widgets.py
create mode 100644 tox.ini
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..610f2a7
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,42 @@
+sudo: false
+language: python
+python:
+ - "3.4"
+ - "3.5"
+ - "3.6"
+env:
+ - DJANGO="1.11"
+ - DJANGO="2.0"
+ - DJANGO="master"
+matrix:
+ include:
+ - python: "2.7"
+ env: DJANGO="1.11"
+ - python: "3.7"
+ sudo: required
+ dist: xenial
+ env: DJANGO="2.0"
+ - python: "3.7"
+ sudo: required
+ dist: xenial
+ env: DJANGO="2.1"
+ - python: "3.7"
+ sudo: required
+ dist: xenial
+ env: DJANGO="master"
+ allow_failures:
+ - python: "3.4"
+ env: DJANGO="2.0"
+ - python: "3.4"
+ env: DJANGO="master"
+ - python: "3.5"
+ env: DJANGO="2.0"
+ - python: "3.5"
+ env: DJANGO="master"
+ - python: "3.6"
+ env: DJANGO="2.0"
+ - python: "3.6"
+ env: DJANGO="master"
+after_success: codecov
+install: pip install tox-travis codecov
+script: tox
diff --git a/README.md b/README.md
index 72ce528..73eeae6 100644
--- a/README.md
+++ b/README.md
@@ -45,4 +45,4 @@ CHECKOUT_PAYMENT_CHOICES = [
## Notes
1. Razorpay automatically capture the whole payment amount;
2. In test mode, you can use `4111 1111 1111 1111` (or any other valid credit card numbers)
-with any future expiry date and CVV to pay orders.
+with any future expiry date and CVV to pay orders.
diff --git a/django_payments_razorpay/__init__.py b/django_payments_razorpay/__init__.py
index ccae59b..f574967 100644
--- a/django_payments_razorpay/__init__.py
+++ b/django_payments_razorpay/__init__.py
@@ -11,7 +11,6 @@
class RazorPayProvider(BasicProvider):
form_class = ModalPaymentForm
- ACCEPTED_CURRENCIES = 'INR',
def __init__(
self,
@@ -28,8 +27,6 @@ def __init__(
super(RazorPayProvider, self).__init__(**kwargs)
def get_form(self, payment, data=None):
- # TODO: raise error if payment.currency is not in ACCEPTED_CURRENCIES
-
if payment.status == PaymentStatus.WAITING:
payment.change_status(PaymentStatus.INPUT)
@@ -37,7 +34,6 @@ def get_form(self, payment, data=None):
data=data, payment=payment, provider=self)
if form.is_valid():
- form.save()
raise RedirectNeeded(payment.get_success_url())
return form
diff --git a/django_payments_razorpay/forms.py b/django_payments_razorpay/forms.py
index 0b7b30d..59440ec 100644
--- a/django_payments_razorpay/forms.py
+++ b/django_payments_razorpay/forms.py
@@ -35,15 +35,8 @@ def clean(self):
charge = self.provider.charge(self.transaction_id, self.payment)
captured_amount = Decimal(charge['amount']) / 100
- # FIXME: should we handle the case
- # of having the captured amount invalid?
self.payment.attrs.capture = json.dumps(charge)
self.payment.captured_amount = captured_amount
-
- assert captured_amount == self.payment.total
-
+ self.payment.transaction_id = self.transaction_id
+ self.payment.change_status(PaymentStatus.CONFIRMED)
return data
-
- def save(self):
- self.payment.transaction_id = self.transaction_id
- self.payment.change_status(PaymentStatus.CONFIRMED)
diff --git a/django_payments_razorpay/widgets.py b/django_payments_razorpay/widgets.py
index bb5583f..d4bb950 100644
--- a/django_payments_razorpay/widgets.py
+++ b/django_payments_razorpay/widgets.py
@@ -20,7 +20,8 @@ def __init__(self, provider, payment, *args, **kwargs):
'data-image': provider.image,
'data-name': provider.name,
'data-description': payment.description or _('Total payment'),
- 'data-amount': int(payment.total * 100)
+ 'data-amount': int(payment.total * 100),
+ 'data-currency': payment.currency
}
if provider.prefill:
@@ -36,7 +37,7 @@ def __init__(self, provider, payment, *args, **kwargs):
base_attrs.update(override_attrs)
super(RazorPayCheckoutWidget, self).__init__(*args, **kwargs)
- def render(self, name, *args, **kwargs):
+ def render(self, *args, **kwargs):
attrs = kwargs.setdefault('attrs', {})
attrs.update(self.attrs)
return format_html('', flatatt(attrs))
diff --git a/setup.py b/setup.py
index b9a74ce..74b7ece 100755
--- a/setup.py
+++ b/setup.py
@@ -1,35 +1,9 @@
#!/usr/bin/env python
from setuptools import setup
-from setuptools.command.test import test as TestCommand
-from sys import version_info, exit
-
REQUIREMENTS = ['django-payments>=0.12.3', 'razorpay>=1.1.1']
-TEST_REQUIREMENTS = ['pytest', 'pytest-django']
-
-if version_info < (3,):
- TEST_REQUIREMENTS.append('mock')
-
-
-class PyTest(TestCommand):
- user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")]
- test_args = []
-
- def initialize_options(self):
- TestCommand.initialize_options(self)
- self.pytest_args = []
-
- def finalize_options(self):
- TestCommand.finalize_options(self)
- self.test_args = []
- self.test_suite = True
-
- def run_tests(self):
- # import here, cause outside the eggs aren't loaded
- import pytest
- errno = pytest.main(self.pytest_args)
- exit(errno)
+TEST_REQUIREMENTS = ['pytest', 'mock']
setup(
@@ -55,6 +29,5 @@ def run_tests(self):
'Topic :: Software Development :: Libraries :: Application Frameworks',
'Topic :: Software Development :: Libraries :: Python Modules'],
install_requires=REQUIREMENTS,
- cmdclass={'test': PyTest},
tests_require=TEST_REQUIREMENTS,
zip_safe=False)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..6dc39b6
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,5 @@
+import django
+import os
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings')
+django.setup()
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..40fb747
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,107 @@
+from decimal import Decimal
+
+import pytest
+import mock
+from payments import PaymentStatus
+from payments.models import BasePayment
+from django_payments_razorpay import RazorPayProvider
+
+TRANSACTION_ID = 'pay_7IZD7aJ2kkmOjk'
+
+CLIENT_ID = 'abc123'
+SECRET = '123abc'
+PAYMENT_TOKEN = '5a4dae68-2715-4b1e-8bb2-2c2dbe9255f6'
+VARIANT = 'razorpay'
+EMAIL = 'hello@example.com'
+FIRST_NAME = 'John'
+LAST_NAME = 'Doe'
+TOTAL = Decimal(220)
+TOTAL_INT = 22000
+
+
+@pytest.fixture
+def payment():
+ payment_data = {
+ 'description': 'payment',
+ 'currency': 'USD',
+ 'delivery': Decimal(10),
+ 'status': PaymentStatus.WAITING,
+ 'tax': Decimal(10),
+ 'token': PAYMENT_TOKEN,
+ 'total': TOTAL,
+ 'captured_amount': Decimal(0),
+ 'variant': VARIANT,
+ 'transaction_id': None,
+ 'message': '',
+ 'billing_first_name': FIRST_NAME,
+ 'billing_last_name': LAST_NAME,
+ 'billing_email': EMAIL}
+ _payment = BasePayment(**payment_data)
+ _payment.id = 1
+ _payment.save = mock.MagicMock()
+ _payment.get_success_url = mock.MagicMock(
+ new_callable=lambda: 'https://success')
+ return _payment
+
+
+@pytest.fixture
+def valid_capture_data():
+ return {
+ "id": TRANSACTION_ID,
+ "entity": "payment",
+ "amount": TOTAL_INT,
+ "currency": "INR",
+ "status": "captured",
+ "order_id": None,
+ "invoice_id": None,
+ "international": False,
+ "method": "wallet",
+ "amount_refunded": 0,
+ "refund_status": None,
+ "captured": True,
+ "description": "Purchase Description",
+ "wallet": "freecharge",
+ "email": "a@b.com",
+ "contact": "91xxxxxxxx",
+ "notes": {
+ "merchant_order_id": "order id"
+ },
+ "error_code": None,
+ "error_description": None,
+ "created_at": 1400826750
+ }
+
+
+@pytest.fixture
+def valid_full_refund_data():
+ return {
+ "id": "rfnd_5UXHCzSiC02RBz",
+ "entity": "refund",
+ "amount": TOTAL_INT,
+ "currency": "INR",
+ "payment_id": "pay_5UWttxtCjkrldV",
+ "notes": {},
+ "created_at": 1462887226
+ }
+
+
+@pytest.fixture
+def valid_partial_refund_data(valid_full_refund_data):
+ valid_full_refund_data = valid_full_refund_data.copy()
+ valid_full_refund_data['amount'] = 2000
+ return valid_full_refund_data
+
+
+@pytest.fixture
+def provider(valid_capture_data, valid_full_refund_data):
+ _provider = RazorPayProvider(CLIENT_ID, SECRET)
+ mocked_payment = _provider.razorpay_client.payment = mock.MagicMock()
+ mocked_payment.capture.return_value = valid_capture_data
+ mocked_payment.refund.return_value = valid_full_refund_data
+ return _provider
+
+
+@pytest.fixture
+def valid_payment_form_data():
+ return {
+ 'razorpay_payment_id': TRANSACTION_ID}
diff --git a/tests/settings.py b/tests/settings.py
new file mode 100644
index 0000000..961cd37
--- /dev/null
+++ b/tests/settings.py
@@ -0,0 +1,15 @@
+from __future__ import unicode_literals
+import os
+
+PROJECT_ROOT = os.path.normpath(
+ os.path.join(os.path.dirname(__file__), '..', 'django_payments_razorpay'))
+
+SECRET_KEY = 'secret'
+PAYMENT_HOST = 'example.com'
+
+INSTALLED_APPS = ['payments', 'django.contrib.sites']
+
+PAYMENT_VARIANTS = {
+ 'razorpay': ('django_payments_razorpay.RazorPayProvider', {
+ 'public_key': 'RAZORPAY_PUBLIC_KEY',
+ 'secret_key': 'RAZORPAY_SECRET_KEY'})}
diff --git a/tests/test_forms.py b/tests/test_forms.py
new file mode 100644
index 0000000..054e385
--- /dev/null
+++ b/tests/test_forms.py
@@ -0,0 +1,36 @@
+import pytest
+from payments import PaymentStatus
+
+from django_payments_razorpay import ModalPaymentForm
+from tests.conftest import TRANSACTION_ID, TOTAL_INT, TOTAL
+
+
+def test_modal_payment_form_valid_data(
+ provider, payment, valid_payment_form_data):
+ form = ModalPaymentForm(
+ provider=provider, payment=payment, data=valid_payment_form_data)
+ assert form.is_valid()
+ provider.razorpay_client.payment.capture.assert_called_once_with(
+ TRANSACTION_ID, TOTAL_INT)
+ assert payment.captured_amount == TOTAL
+ assert payment.transaction_id == TRANSACTION_ID
+ assert payment.status == PaymentStatus.CONFIRMED
+
+
+def test_modal_payment_form_already_processed(
+ provider, payment, valid_payment_form_data):
+ payment.transaction_id = TRANSACTION_ID
+ form = ModalPaymentForm(
+ provider=provider, payment=payment, data=valid_payment_form_data)
+ assert not form.is_valid()
+ provider.razorpay_client.payment.capture.assert_not_called()
+
+
+def test_modal_payment_form_invalid_data(
+ provider, payment, valid_payment_form_data):
+ form = ModalPaymentForm(
+ provider=provider, payment=payment, data={})
+
+ with pytest.raises(KeyError, message='razorpay_payment_id'):
+ form.is_valid()
+ provider.razorpay_client.payment.capture.assert_not_called()
diff --git a/tests/test_provider.py b/tests/test_provider.py
new file mode 100644
index 0000000..7e23550
--- /dev/null
+++ b/tests/test_provider.py
@@ -0,0 +1,91 @@
+from decimal import Decimal
+
+import mock
+import pytest
+import razorpay.errors
+from payments import PaymentStatus, RedirectNeeded
+from payments.core import provider_factory
+
+from django_payments_razorpay import RazorPayProvider
+from tests.conftest import CLIENT_ID, SECRET, TOTAL, TRANSACTION_ID
+
+
+def test_provider_factory():
+ assert isinstance(provider_factory('razorpay'), RazorPayProvider)
+
+
+def test_authentication(provider):
+ assert provider.razorpay_client.auth == (CLIENT_ID, SECRET)
+
+
+@mock.patch(
+ 'django_payments_razorpay.forms.RazorPayCheckoutWidget', create=True)
+def test_get_form(mocked_razor_checkout, provider, payment):
+ form = provider.get_form(payment)
+ mocked_razor_checkout.assert_called_once_with(
+ provider=provider, payment=payment)
+ assert 'razorpay' in form.fields
+ assert form.fields['razorpay'].widget == mocked_razor_checkout.return_value
+
+
+def test_get_form_invalid_data(provider, payment):
+ with pytest.raises(KeyError, message='razorpay_payment_id'):
+ provider.get_form(payment, data={})
+
+ assert payment.captured_amount == 0
+ assert payment.transaction_id is None
+
+
+def test_get_form_valid_data(valid_payment_form_data, provider, payment):
+ with pytest.raises(RedirectNeeded, message='https://success'):
+ provider.get_form(payment, data=valid_payment_form_data)
+
+ assert payment.save.call_count != 0
+ assert payment.status == PaymentStatus.CONFIRMED
+ assert payment.captured_amount == payment.total
+ assert payment.transaction_id == TRANSACTION_ID
+
+
+@mock.patch('payments.models.provider_factory', create=True)
+@pytest.mark.parametrize(
+ 'partial_refund,expected_status', (
+ (False, PaymentStatus.REFUNDED),
+ (True, PaymentStatus.CONFIRMED)
+ ))
+def test_refund(
+ mocked_provider_factory,
+ partial_refund, expected_status,
+ valid_partial_refund_data, provider, payment):
+
+ mocked_provider_factory.return_value = provider
+ provider.refund = mock.MagicMock(wraps=provider.refund)
+
+ if partial_refund:
+ refund_amount = Decimal(20)
+ expected_captured_amount = Decimal(200)
+ provider.razorpay_client.payment.refund.return_value = (
+ valid_partial_refund_data)
+ else:
+ refund_amount = TOTAL
+ expected_captured_amount = 0
+
+ payment.captured_amount = payment.total
+ payment.status = PaymentStatus.CONFIRMED
+ payment.refund(amount=refund_amount)
+
+ mocked_provider_factory.assert_called_once_with('razorpay')
+ provider.refund.assert_called_once_with(payment, refund_amount)
+ assert payment.captured_amount == expected_captured_amount
+ assert payment.status == expected_status
+
+
+def test_refund_invalid_data(provider, payment):
+ def _raise_fake_error(*args, **kwargs):
+ raise razorpay.errors.BadRequestError('hello world')
+ payment.captured_amount = payment.total
+ provider.razorpay_client.payment.refund.side_effect = _raise_fake_error
+
+ with pytest.raises(ValueError, message='hello world'):
+ provider.refund(payment, Decimal(2220))
+
+ assert payment.captured_amount == payment.total
diff --git a/tests/test_widgets.py b/tests/test_widgets.py
new file mode 100644
index 0000000..3cec29f
--- /dev/null
+++ b/tests/test_widgets.py
@@ -0,0 +1,37 @@
+from django_payments_razorpay.widgets import RazorPayCheckoutWidget
+
+
+def test_checkout_widget_attrs_overriding(provider, payment):
+ base_attrs = RazorPayCheckoutWidget(provider, payment).attrs
+ overridden_attrs = RazorPayCheckoutWidget(
+ provider, payment, attrs={'data-currency': 'INR'}).attrs
+
+ base_attrs['data-currency'] = 'INR'
+ assert base_attrs == overridden_attrs
+
+
+def test_checkout_widget_render_without_prefill(provider, payment):
+ widget = RazorPayCheckoutWidget(provider, payment)
+ assert widget.render() == (
+ '')
+
+
+def test_checkout_widget_render_with_prefill(provider, payment):
+ provider.prefill = True
+ widget = RazorPayCheckoutWidget(provider, payment)
+ assert widget.render() == (
+ '')
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..89002b0
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,29 @@
+[tox]
+envlist = py27-django111, py{34,35,36,37}-django{111,20,_master}
+
+[testenv]
+usedevelop=True
+deps=
+ coverage
+ django111: django>=1.11a1,<1.12
+ django20: Django>=2.0a1,<2.1
+ django_master: https://github.com/django/django/archive/master.tar.gz
+ mock
+ pytest
+ pytest-cov
+commands=pytest --cov --cov-report=
+
+[travis]
+python =
+ 2.7: py27
+ 3.4: py34
+ 3.5: py35
+ 3.6: py36
+ 3.7: py37
+unignore_outcomes = True
+
+[travis:env]
+DJANGO =
+ 1.11: django111
+ 2.0: django2.0
+ master: django_master
From a37582b3080e5fc5c48f9755d6c86a2f6b1750b3 Mon Sep 17 00:00:00 2001
From: NyanKiyoshi <6186720+NyanKiyoshi@users.noreply.github.com>
Date: Fri, 10 Aug 2018 16:04:45 +0200
Subject: [PATCH 4/7] Exclude master of Python3.7, bump the version and add
badges
---
.travis.yml | 4 ++++
README.md | 5 +++++
setup.py | 5 +++--
3 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 610f2a7..bf26bcb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -37,6 +37,10 @@ matrix:
env: DJANGO="2.0"
- python: "3.6"
env: DJANGO="master"
+ - python: "3.7"
+ env: DJANGO="master"
+ sudo: required
+ dist: xenial
after_success: codecov
install: pip install tox-travis codecov
script: tox
diff --git a/README.md b/README.md
index 73eeae6..a627ff5 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,10 @@
# Razorpay for django-payments
+[![PyPi Release](https://img.shields.io/pypi/v/django-payments-razorpay.svg)](https://pypi.org/project/django-payments-razorpay/)
+![python](https://img.shields.io/pypi/pyversions/django-payments-razorpay.svg)
+[![Build Status](https://travis-ci.org/NyanKiyoshi/django-payment-razorpay.svg?branch=master)](https://travis-ci.org/NyanKiyoshi/django-payment-razorpay)
+[![codecov](https://codecov.io/gh/NyanKiyoshi/django-payment-razorpay/branch/master/graph/badge.svg)](https://codecov.io/gh/NyanKiyoshi/django-payment-razorpay)
+
**WARNING:** only the paisa (INR) currency is supported by Razorpay as of now.
## Installation
diff --git a/setup.py b/setup.py
index 74b7ece..f9182db 100755
--- a/setup.py
+++ b/setup.py
@@ -10,9 +10,9 @@
name='django-payments-razorpay',
author='NyanKiyoshi',
author_email='hello@vanille.bid',
- url='https://github.com/NyanKiyoshi/django-payments-razorpay',
+ url='https://github.com/NyanKiyoshi/django-payments-razorpay/',
description='Razorpay provider for django-payments.',
- version='0.0.0',
+ version='0.1.0',
packages=['django_payments_razorpay'],
include_package_data=True,
classifiers=[
@@ -25,6 +25,7 @@
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
'Framework :: Django',
'Topic :: Software Development :: Libraries :: Application Frameworks',
'Topic :: Software Development :: Libraries :: Python Modules'],
From cb9fca1d083e9ab7e93bd4c9acd058ef6834b90f Mon Sep 17 00:00:00 2001
From: NyanKiyoshi <6186720+NyanKiyoshi@users.noreply.github.com>
Date: Fri, 10 Aug 2018 16:07:38 +0200
Subject: [PATCH 5/7] Remove unneeded try import
---
django_payments_razorpay/widgets.py | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/django_payments_razorpay/widgets.py b/django_payments_razorpay/widgets.py
index d4bb950..7292873 100644
--- a/django_payments_razorpay/widgets.py
+++ b/django_payments_razorpay/widgets.py
@@ -1,12 +1,8 @@
+from django.forms.utils import flatatt
from django.forms.widgets import HiddenInput
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
-try:
- from django.forms.utils import flatatt
-except ImportError:
- from django.forms.util import flatatt
-
CHECKOUT_SCRIPT_URL = 'https://checkout.razorpay.com/v1/checkout.js'
From e7040dbecd86e0e32e9e826dd4b8f14a1c40fad5 Mon Sep 17 00:00:00 2001
From: NyanKiyoshi <6186720+NyanKiyoshi@users.noreply.github.com>
Date: Fri, 10 Aug 2018 16:39:57 +0200
Subject: [PATCH 6/7] isort imports, include django21 and 20
---
.travis.yml | 18 ++++++++++++------
setup.py | 12 +++++++++++-
tests/conftest.py | 4 ++--
tests/settings.py | 1 +
tests/test_forms.py | 5 ++---
tests/test_provider.py | 3 +--
tox.ini | 4 +++-
7 files changed, 32 insertions(+), 15 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index bf26bcb..f7255b4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,6 +7,7 @@ python:
env:
- DJANGO="1.11"
- DJANGO="2.0"
+ - DJANGO="2.1"
- DJANGO="master"
matrix:
include:
@@ -25,22 +26,27 @@ matrix:
dist: xenial
env: DJANGO="master"
allow_failures:
- - python: "3.4"
- env: DJANGO="2.0"
- - python: "3.4"
- env: DJANGO="master"
- python: "3.5"
- env: DJANGO="2.0"
+ env: DJANGO="2.1"
- python: "3.5"
env: DJANGO="master"
- python: "3.6"
- env: DJANGO="2.0"
+ env: DJANGO="2.1"
- python: "3.6"
env: DJANGO="master"
+ - python: "3.7"
+ sudo: required
+ dist: xenial
+ env: DJANGO="2.1"
- python: "3.7"
env: DJANGO="master"
sudo: required
dist: xenial
+ exclude:
+ - python: "3.4"
+ env: DJANGO="2.1"
+ - python: "3.4"
+ env: DJANGO="master"
after_success: codecov
install: pip install tox-travis codecov
script: tox
diff --git a/setup.py b/setup.py
index f9182db..222243c 100755
--- a/setup.py
+++ b/setup.py
@@ -1,18 +1,28 @@
#!/usr/bin/env python
+from os.path import isfile
from setuptools import setup
REQUIREMENTS = ['django-payments>=0.12.3', 'razorpay>=1.1.1']
TEST_REQUIREMENTS = ['pytest', 'mock']
+if isfile('README.md'):
+ with open('README.md') as fp:
+ long_description = fp.read()
+else:
+ long_description = ''
+
+
setup(
name='django-payments-razorpay',
author='NyanKiyoshi',
author_email='hello@vanille.bid',
url='https://github.com/NyanKiyoshi/django-payments-razorpay/',
description='Razorpay provider for django-payments.',
- version='0.1.0',
+ long_description=long_description,
+ long_description_content_type='text/markdown',
+ version='0.1.1',
packages=['django_payments_razorpay'],
include_package_data=True,
classifiers=[
diff --git a/tests/conftest.py b/tests/conftest.py
index 40fb747..af8fed8 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,10 +1,10 @@
from decimal import Decimal
-import pytest
import mock
+import pytest
+from django_payments_razorpay import RazorPayProvider
from payments import PaymentStatus
from payments.models import BasePayment
-from django_payments_razorpay import RazorPayProvider
TRANSACTION_ID = 'pay_7IZD7aJ2kkmOjk'
diff --git a/tests/settings.py b/tests/settings.py
index 961cd37..74870a1 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -1,4 +1,5 @@
from __future__ import unicode_literals
+
import os
PROJECT_ROOT = os.path.normpath(
diff --git a/tests/test_forms.py b/tests/test_forms.py
index 054e385..0fac558 100644
--- a/tests/test_forms.py
+++ b/tests/test_forms.py
@@ -1,8 +1,7 @@
import pytest
-from payments import PaymentStatus
-
from django_payments_razorpay import ModalPaymentForm
-from tests.conftest import TRANSACTION_ID, TOTAL_INT, TOTAL
+from payments import PaymentStatus
+from tests.conftest import TOTAL, TOTAL_INT, TRANSACTION_ID
def test_modal_payment_form_valid_data(
diff --git a/tests/test_provider.py b/tests/test_provider.py
index 7e23550..a1a46de 100644
--- a/tests/test_provider.py
+++ b/tests/test_provider.py
@@ -3,10 +3,9 @@
import mock
import pytest
import razorpay.errors
+from django_payments_razorpay import RazorPayProvider
from payments import PaymentStatus, RedirectNeeded
from payments.core import provider_factory
-
-from django_payments_razorpay import RazorPayProvider
from tests.conftest import CLIENT_ID, SECRET, TOTAL, TRANSACTION_ID
diff --git a/tox.ini b/tox.ini
index 89002b0..638b9e5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py27-django111, py{34,35,36,37}-django{111,20,_master}
+envlist = py27-django111, py{34,35,36,37}-django{111,20,21,_master}
[testenv]
usedevelop=True
@@ -7,6 +7,7 @@ deps=
coverage
django111: django>=1.11a1,<1.12
django20: Django>=2.0a1,<2.1
+ django21: Django>=2.1,<2.2
django_master: https://github.com/django/django/archive/master.tar.gz
mock
pytest
@@ -26,4 +27,5 @@ unignore_outcomes = True
DJANGO =
1.11: django111
2.0: django2.0
+ 2.1: django2.1
master: django_master
From 54832c47bee70c0b7881273dfdeeae252964b68e Mon Sep 17 00:00:00 2001
From: NyanKiyoshi <6186720+NyanKiyoshi@users.noreply.github.com>
Date: Fri, 10 Aug 2018 16:47:15 +0200
Subject: [PATCH 7/7] Fix typo in readme
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index a627ff5..72abce2 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
[![PyPi Release](https://img.shields.io/pypi/v/django-payments-razorpay.svg)](https://pypi.org/project/django-payments-razorpay/)
![python](https://img.shields.io/pypi/pyversions/django-payments-razorpay.svg)
-[![Build Status](https://travis-ci.org/NyanKiyoshi/django-payment-razorpay.svg?branch=master)](https://travis-ci.org/NyanKiyoshi/django-payment-razorpay)
-[![codecov](https://codecov.io/gh/NyanKiyoshi/django-payment-razorpay/branch/master/graph/badge.svg)](https://codecov.io/gh/NyanKiyoshi/django-payment-razorpay)
+[![Build Status](https://travis-ci.org/NyanKiyoshi/django-payment-razorpay.svg?branch=master)](https://travis-ci.org/NyanKiyoshi/django-payments-razorpay)
+[![codecov](https://codecov.io/gh/NyanKiyoshi/django-payment-razorpay/branch/master/graph/badge.svg)](https://codecov.io/gh/NyanKiyoshi/django-payments-razorpay)
**WARNING:** only the paisa (INR) currency is supported by Razorpay as of now.