Skip to content

Commit

Permalink
Merge pull request #1 from NyanKiyoshi/base-release
Browse files Browse the repository at this point in the history
Razorpay implementation
  • Loading branch information
NyanKiyoshi authored Aug 10, 2018
2 parents 0ea6fba + 54832c4 commit 6129288
Show file tree
Hide file tree
Showing 13 changed files with 605 additions and 0 deletions.
52 changes: 52 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
sudo: false
language: python
python:
- "3.4"
- "3.5"
- "3.6"
env:
- DJANGO="1.11"
- DJANGO="2.0"
- DJANGO="2.1"
- 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.5"
env: DJANGO="2.1"
- python: "3.5"
env: DJANGO="master"
- python: "3.6"
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
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 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-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.

## 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.
54 changes: 54 additions & 0 deletions django_payments_razorpay/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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

def __init__(
self,
public_key, secret_key,
image='', name='', prefill=False, **kwargs):

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):
if payment.status == PaymentStatus.WAITING:
payment.change_status(PaymentStatus.INPUT)

form = self.form_class(
data=data, payment=payment, provider=self)

if form.is_valid():
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
42 changes: 42 additions & 0 deletions django_payments_razorpay/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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

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

self.payment.attrs.capture = json.dumps(charge)
self.payment.captured_amount = captured_amount
self.payment.transaction_id = self.transaction_id
self.payment.change_status(PaymentStatus.CONFIRMED)
return data
39 changes: 39 additions & 0 deletions django_payments_razorpay/widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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 _

CHECKOUT_SCRIPT_URL = 'https://checkout.razorpay.com/v1/checkout.js'


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),
'data-currency': payment.currency
}

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, *args, **kwargs):
attrs = kwargs.setdefault('attrs', {})
attrs.update(self.attrs)
return format_html('<script{0}></script>', flatatt(attrs))
44 changes: 44 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/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='[email protected]',
url='https://github.com/NyanKiyoshi/django-payments-razorpay/',
description='Razorpay provider for django-payments.',
long_description=long_description,
long_description_content_type='text/markdown',
version='0.1.1',
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',
'Programming Language :: Python :: 3.7',
'Framework :: Django',
'Topic :: Software Development :: Libraries :: Application Frameworks',
'Topic :: Software Development :: Libraries :: Python Modules'],
install_requires=REQUIREMENTS,
tests_require=TEST_REQUIREMENTS,
zip_safe=False)
5 changes: 5 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import django
import os

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings')
django.setup()
Loading

0 comments on commit 6129288

Please sign in to comment.