├── .coverage ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile └── source │ ├── apireference.rst │ ├── conf.py │ ├── configuration.rst │ └── index.rst ├── payu ├── __init__.py ├── admin.py ├── apps.py ├── gateway.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py └── tests.py ├── setup.py └── test_runner.py /.coverage: -------------------------------------------------------------------------------- 1 | !coverage.py: This is a private format, don't read it directly!{"lines": {"/home/anjaneyulu/Desktop/Company/Github/django-payu/django-payu/payu/admin.py": [], "/home/anjaneyulu/Desktop/Company/Github/django-payu/django-payu/payu/tests.py": [8, 1, 4, 6, 7], "/home/anjaneyulu/Desktop/Company/Github/django-payu/django-payu/payu/apps.py": [], "/home/anjaneyulu/Desktop/Company/Github/django-payu/django-payu/payu/__init__.py": [1, 2], "/home/anjaneyulu/Desktop/Company/Github/django-payu/django-payu/payu/gateway.py": [1, 2, 3, 4, 5, 6, 7, 80, 259, 11, 13, 205, 16, 232, 147, 180, 100, 90, 38, 135, 157], "/home/anjaneyulu/Desktop/Company/Github/django-payu/django-payu/payu/models.py": [1, 6, 8, 11, 14, 17, 18, 19, 22, 23, 26, 27, 28, 32, 33, 35, 36, 39, 40, 43, 46, 49, 50, 53, 56, 58, 59, 61, 62]}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | db.sqlite3 4 | env 5 | dist 6 | *.egg-info/ 7 | docs/build/* 8 | .coverage -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "3.4" 5 | - "3.5" 6 | 7 | install: 8 | - python setup.py install 9 | - pip install coveralls 10 | 11 | script: 12 | - python test_runner.py makemigrations 13 | - python test_runner.py migrate 14 | - python test_runner.py test 15 | - coverage run --source=payu test_runner.py test 16 | 17 | 18 | after_success: 19 | coveralls 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 MicroPyramid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Django-PayU 2 | ============== 3 | .. image:: http://travis-ci.org/MicroPyramid/django-payu.svg?branch=master 4 | :target: http://travis-ci.org/MicroPyramid/django-payu 5 | .. image:: https://coveralls.io/repos/github/MicroPyramid/django-payu/badge.svg?branch=master 6 | :target: https://coveralls.io/github/MicroPyramid/django-payu?branch=master 7 | .. image:: https://img.shields.io/github/license/micropyramid/django-payu.svg 8 | :target: https://pypi.python.org/pypi/django-payu/ 9 | .. image:: https://landscape.io/github/MicroPyramid/django-payu/master/landscape.svg?style=flat 10 | :target: https://landscape.io/github/MicroPyramid/django-payu/master 11 | :alt: Code Health 12 | 13 | 14 | This package provides integration between `Django` and `PayU Payment Gateway`. 15 | 16 | 17 | Quick start 18 | ------------ 19 | 20 | 1. Install 'django-payu' using the following command:: 21 | 22 | pip install django-payu 23 | 24 | 2. Add "payu" to your INSTALLED_APPS setting like this:: 25 | 26 | INSTALLED_APPS = [ 27 | .... 28 | .... 29 | 'payu', 30 | ] 31 | 32 | 3. Add the following settings in the setting file using the details from your PayU account:: 33 | 34 | PAYU_MERCHANT_KEY = "Your MerchantID", 35 | 36 | PAYU_MERCHANT_SALT = "Your MerchantSALT", 37 | 38 | # And add the PAYU_MODE to 'TEST' for testing and 'LIVE' for production. 39 | PAYU_MODE = "TEST" 40 | 41 | 4. Finally, run the following commands:: 42 | 43 | python manage.py migrate 44 | 45 | 5. Run the tests:: 46 | 47 | python test_runner.py test -v 2 48 | 49 | 50 | 51 | 52 | Visit our Django E-commerce Development page `Here`_ 53 | 54 | For additional help you can `Contact us here`_ 55 | 56 | .. _contact us here: https://micropyramid.com/contact-us/ 57 | .. _Here: https://micropyramid.com/django-ecommerce-development/ 58 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django_payu.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django_payu.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django_payu" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django_payu" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/source/apireference.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | API Reference 3 | ============== 4 | 5 | `Django PayU` is an API for integrating `PayU Payment Gateway`_ and your website in respect of powering online transactions. 6 | 7 | Django PayU supports a wide variety of transactions through the API. This page contains specific information on the API functions. 8 | 9 | .. _`PayU Payment Gateway`: https://www.payu.in 10 | 11 | 12 | ----------------------- 13 | 1. PayU URL (payu_url) 14 | ----------------------- 15 | 16 | This function returns the `PayU` respective url based on the *mode* from settings file. 17 | 18 | **Example** 19 | 20 | .. code-block:: python 21 | 22 | from payu.gateway import payu_url 23 | 24 | payu_url = payu_url() 25 | 26 | 27 | -------------------------------- 28 | 2. Hash (Checksum) - (get_hash) 29 | -------------------------------- 30 | 31 | This function returns the hash value computed by the checksum formula as specified by the PayU Payment Gateway. 32 | 33 | A checksum is generated by a mathematical function using the Merchant ID(key), message and the Salt as input. The Checksum algorithm used is SHA512. 34 | 35 | `Hash or Checksum Formula before transaction` - 36 | 37 | .. code-block:: python 38 | 39 | from hashlib import sha512 40 | 41 | sha512(key|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5||||||) 42 | 43 | **Parameters** 44 | 45 | * `key` – Merchant ID provided by PayU. 46 | * `txnid` – Transaction number that merchant uses to track order. (Must be unique every time if already successful, otherwise you get an error of duplicate transaction). 47 | * `amount` – Payment amount (Type cast the amount to float). 48 | * `productinfo` – Product Description. 49 | * udf1 to udf10 – User defined fields. 50 | * `SALT` – SALT will be provided by PayU.in 51 | 52 | **Example** 53 | 54 | .. code-block:: python 55 | 56 | from payu.gateway import get_hash 57 | from uuid import uuid4 58 | 59 | data = { 60 | 'txnid':uuid4().hex, 'amount':10.00, 'productinfo': 'Sample Product', 61 | 'firstname': 'test', 'email': 'test@example.com', 'udf1': 'Userdefined field', 62 | } 63 | hash_value = get_hash(data) 64 | 65 | 66 | ------------------------------------- 67 | 3. Confirm transaction (check_hash) 68 | ------------------------------------- 69 | 70 | This function is used to compute the hash (or checksum) after the transaction. It is strongly recommended that the hash (or checksum) is computed again after the 71 | transaction and is compared with what we post in the return parameters. 72 | 73 | `Hash or Checksum Formula after transaction` - 74 | 75 | .. code-block:: python 76 | 77 | from hashlib import sha512 78 | 79 | sha512(|status||||||udf5|udf4|udf3|udf2|udf1|email|firstname|productinfo|amount|txnid|key) 80 | 81 | **Parameters** 82 | 83 | After the transaction has been completed, the PayU returns the data to our success url along with data sent by us in a POST method. The whole data can be passed to the check_hash() function to verify that data is sent to and from the PayU. 84 | 85 | Same parameters as get_hash() function along with the following additional parameter - 86 | 87 | * `status` – SUCCESS/PENDING/FAILURE sent from PayU.in in the POST request. 88 | 89 | **Return Value** 90 | 91 | The check_hash() function returns either True or False. 92 | 93 | **Example** 94 | 95 | .. code-block:: python 96 | 97 | from django.http import HttpResponse 98 | from payu.gateway import check_hash 99 | from uuid import uuid4 100 | 101 | def success_response(request): 102 | hash_value = check_hash(request.POST) 103 | if check_hash(request.POST): 104 | return HttpResponse("Transaction has been Successful.") 105 | 106 | 107 | ---------------------------------------- 108 | 4. Verify transaction (verify_payment) 109 | ---------------------------------------- 110 | 111 | This function is used to reconcile the transaction with PayU. It returns MIHPayID ( Unique ID generated for a transaction by PayU.in), amount, discount, mode and status of transaction. 112 | 113 | **Parameters** 114 | 115 | * `txnid` – Transaction ID generated by you to uniquely identify a transaction. 116 | 117 | **Example** 118 | 119 | .. code-block:: python 120 | 121 | from payu.gateway import verify_payment 122 | 123 | response = verify_payment("Your txnid") 124 | print response 125 | 126 | # Example Successful Response 127 | {"status":1,"msg":"Transaction Fetched Successfully", "transaction_details": {"mihpayid": "MIHPayID","request_id":"","bank_ref_num":"Bank Reference Number","amt":"Amount", "disc":"Discount","mode":"Transaction Mode (NB for Netbanking, CC for credit card, DC for Debit card, '-' for unknown)", "status":"Transaction Status"}} 128 | 129 | 130 | ---------------------------------------- 131 | 5. Check transaction (check_payment) 132 | ---------------------------------------- 133 | 134 | This function is used to check payment after transaction. It returns all the parameters for a given transaction. 135 | 136 | **Parameters** 137 | 138 | * `mihpayid` – Payu id (mihpayid) of transaction (Unique ID generated for a transaction by PayU.in). 139 | 140 | **Example** 141 | 142 | .. code-block:: python 143 | 144 | from payu.gateway import check_payment 145 | 146 | response = check_payment("Your mihpayid") 147 | print response 148 | 149 | # Example Successful Response 150 | {"status":1,"msg":"Transaction Fetched Successfully","transaction_details": {"request_id":"RequestID","bank_ref_num":"Bank Reference Number","net_amount": "Amount received by merchant after all deductions","mihpayid":"MIHPayID", "amt":"Amount","disc":"Transaction Discount","mode": "Transaction Mode (NB for Netbanking, CC for credit card, DC for Debit card, '-' for unknown)","txnid": "TransactionID","amount":"Transaction Fees Amount","amount_paid":"Transaction Amount","discount":"Transaction Discount","additional_charges":"0.00"}} 151 | 152 | 153 | --------------------------------------------- 154 | 6. Capture transaction (capture_transaction) 155 | --------------------------------------------- 156 | 157 | This function is used to capture an auth transaction . The return parameters 158 | are: request_id, bank_ref_num. 159 | 160 | **Parameters** 161 | 162 | * `mihpayid` – Payu id (mihpayid) of transaction (Unique ID generated for a transaction by PayU.in). 163 | 164 | **Example** 165 | 166 | .. code-block:: python 167 | 168 | from payu.gateway import capture_transaction 169 | 170 | response = capture_transaction("Your mihpayid") 171 | print response 172 | 173 | # Example Failure Response 174 | {"status": 0, "msg":" Capture failed", "error_code": 111} 175 | 176 | # Example Successful Response 177 | {"status": 1, "msg": "Capture Request Queued", "request_id": "RequestID", "bank_ref_num": "Bank Reference Number"} 178 | 179 | 180 | --------------------------------------------- 181 | 7. Refund transaction (refund_transaction) 182 | --------------------------------------------- 183 | 184 | This function is used to refund a captured transaction. The return parameters are 185 | status, request_id,bank_ref_num. Refund can occur only after one day of capture. 186 | 187 | **Parameters** 188 | 189 | * `mihpayid` – Payu id (mihpayid) of transaction (Unique ID generated for a transaction by PayU.in). 190 | * `amount` – Amount to be refunded. 191 | 192 | **Example** 193 | 194 | .. code-block:: python 195 | 196 | from payu.gateway import refund_transaction 197 | 198 | response = refund_transaction("Your mihpayid", "Amount") 199 | print response 200 | 201 | # Example Successful Responses 202 | 203 | # if capture is done on the same day 204 | {"status": 1, "msg": "Capture is done today, please check for refund status tomorrow", "request_id": "RequestID", "mihpayid": "MIHPayID", "bank_ref_num": "Bank Reference Number"} 205 | 206 | # if request is queued for further processing. 207 | {"status": 1, "msg": "Refund Request Queued", "request_id": "RequestID", "mihpayid": "MIHPayID", "bank_ref_num": "Bank Reference Number"} 208 | 209 | # Example Failure Response 210 | {"status": 0, "msg":"Refund request failed"} 211 | 212 | 213 | --------------------------------------------- 214 | 8. Cancel transaction (cancel_transaction) 215 | --------------------------------------------- 216 | 217 | This function is used to cancel an auth transaction. The return parameters are status, 218 | txn_update_id, bank_ref_num. 219 | 220 | **Parameters** 221 | 222 | * `mihpayid` – Payu id (mihpayid) of transaction (Unique ID generated for a transaction by PayU.in). 223 | * `amount` – Amount. 224 | 225 | **Example** 226 | 227 | .. code-block:: python 228 | 229 | from payu.gateway import cancel_transaction 230 | 231 | response = cancel_transaction("Your mihpayid", "Amount") 232 | print response 233 | 234 | # Example Successful Responses 235 | {"status": 1, "msg": "Cancel Request Queued", "request_id": "RequestID", "mihpayid": "MIHPayID", "bank_ref_num": "Bank Reference Number"} 236 | 237 | # Example Failure Response 238 | {"status": 0, "msg":"Cancel request failed"} 239 | 240 | 241 | -------------------------------------------------------------- 242 | 9. Cancel or Refund transaction (cancel_refund_transaction) 243 | -------------------------------------------------------------- 244 | 245 | This function is used to cancel an auth transaction and refund a captured transaction. The return parameters are status, msg. 246 | 247 | **Parameters** 248 | 249 | * `mihpayid` – Payu id (mihpayid) of transaction (Unique ID generated for a transaction by PayU.in). 250 | * `amount` – Amount which needs to be refunded. Please note that both partial and full refunds are allowed. 251 | 252 | **Example** 253 | 254 | .. code-block:: python 255 | 256 | from payu.gateway import cancel_refund_transaction 257 | 258 | response = cancel_refund_transaction("Your mihpayid", "Amount") 259 | print response 260 | 261 | # Example Successful Responses 262 | 263 | # On successful processing at our end 264 | {"status": 1, "msg": "Cancel Request Queued", "request_id": "RequestID", "mihpayid": "MIHPayID", "bank_ref_num": "Bank Reference Number"} 265 | 266 | # On successful processing on our end for captured transactions 267 | {'status': 1, 'bank_ref_num': "Bank Reference Number", 'mihpayid': MIHPayID, 'request_id': 'RequestID', 'msg': 'Refund Request Queued'} 268 | 269 | 270 | -------------------------------------------------------------- 271 | 10. Check action status (check_action_status) 272 | -------------------------------------------------------------- 273 | 274 | This function is used to check status of refund/cancel requests. The return parameter are MIHPayID, Amount, Discount, Mode and Status of transaction. 275 | 276 | **Parameters** 277 | 278 | * `request_id` – Payu id (Cancel/Refund Request ID). 279 | 280 | **Example** 281 | 282 | .. code-block:: python 283 | 284 | from payu.gateway import check_action_status 285 | 286 | response = check_action_status("Request id") 287 | print response 288 | 289 | # Example Successful Responses 290 | {"status": 1, "msg": "Transaction Fetched Successfully", "transaction_details": {"status": "Request Status", "bank_ref_num": "Bank Reference Number", "mihpayid": "MIHPayID", "amount_settled": "Amount settled", "settlement_id": "Settlement id", "mode": "Transaction Mode (NB for Netbanking, CC for credit card, DC for Debit card, '-' for unknown)", "request_id": "RequestID", "action": "Request Action", "amt": "Amount", "disc": "Discount"}} 291 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django_payu documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Aug 22 16:31:28 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | ] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['ntemplates'] 38 | 39 | # The suffix(es) of source filenames. 40 | # You can specify multiple suffix as a list of string: 41 | # source_suffix = ['.rst', '.md'] 42 | source_suffix = '.rst' 43 | 44 | # The encoding of source files. 45 | #source_encoding = 'utf-8-sig' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = u'django_payu' 52 | copyright = u'2015, Micro Pyramid' 53 | author = u'Micro Pyramid' 54 | 55 | # The version info for the project you're documenting, acts as replacement for 56 | # |version| and |release|, also used in various other places throughout the 57 | # built documents. 58 | # 59 | # The short X.Y version. 60 | version = '0.5' 61 | # The full version, including alpha/beta/rc tags. 62 | release = '0.5' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = None 70 | 71 | # There are two options for replacing |today|: either, you set today to some 72 | # non-false value, then it is used: 73 | #today = '' 74 | # Else, today_fmt is used as the format for a strftime call. 75 | #today_fmt = '%B %d, %Y' 76 | 77 | # List of patterns, relative to source directory, that match files and 78 | # directories to ignore when looking for source files. 79 | exclude_patterns = [] 80 | 81 | # The reST default role (used for this markup: `text`) to use for all 82 | # documents. 83 | #default_role = None 84 | 85 | # If true, '()' will be appended to :func: etc. cross-reference text. 86 | #add_function_parentheses = True 87 | 88 | # If true, the current module name will be prepended to all description 89 | # unit titles (such as .. function::). 90 | #add_module_names = True 91 | 92 | # If true, sectionauthor and moduleauthor directives will be shown in the 93 | # output. They are ignored by default. 94 | #show_authors = False 95 | 96 | # The name of the Pygments (syntax highlighting) style to use. 97 | pygments_style = 'sphinx' 98 | 99 | # A list of ignored prefixes for module index sorting. 100 | #modindex_common_prefix = [] 101 | 102 | # If true, keep warnings as "system message" paragraphs in the built documents. 103 | #keep_warnings = False 104 | 105 | # If true, `todo` and `todoList` produce output, else they produce nothing. 106 | todo_include_todos = False 107 | 108 | 109 | # -- Options for HTML output ---------------------------------------------- 110 | 111 | # The theme to use for HTML and HTML Help pages. See the documentation for 112 | # a list of builtin themes. 113 | html_theme = 'alabaster' 114 | 115 | # Theme options are theme-specific and customize the look and feel of a theme 116 | # further. For a list of options available for each theme, see the 117 | # documentation. 118 | #html_theme_options = {} 119 | 120 | # Add any paths that contain custom themes here, relative to this directory. 121 | #html_theme_path = [] 122 | 123 | # The name for this set of Sphinx documents. If None, it defaults to 124 | # " v documentation". 125 | #html_title = None 126 | 127 | # A shorter title for the navigation bar. Default is the same as html_title. 128 | #html_short_title = None 129 | 130 | # The name of an image file (relative to this directory) to place at the top 131 | # of the sidebar. 132 | #html_logo = None 133 | 134 | # The name of an image file (within the static path) to use as favicon of the 135 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 136 | # pixels large. 137 | #html_favicon = None 138 | 139 | # Add any paths that contain custom static files (such as style sheets) here, 140 | # relative to this directory. They are copied after the builtin static files, 141 | # so a file named "default.css" will overwrite the builtin "default.css". 142 | html_static_path = ['nstatic'] 143 | 144 | # Add any extra paths that contain custom files (such as robots.txt or 145 | # .htaccess) here, relative to this directory. These files are copied 146 | # directly to the root of the documentation. 147 | #html_extra_path = [] 148 | 149 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 150 | # using the given strftime format. 151 | #html_last_updated_fmt = '%b %d, %Y' 152 | 153 | # If true, SmartyPants will be used to convert quotes and dashes to 154 | # typographically correct entities. 155 | #html_use_smartypants = True 156 | 157 | # Custom sidebar templates, maps document names to template names. 158 | #html_sidebars = {} 159 | 160 | # Additional templates that should be rendered to pages, maps page names to 161 | # template names. 162 | #html_additional_pages = {} 163 | 164 | # If false, no module index is generated. 165 | #html_domain_indices = True 166 | 167 | # If false, no index is generated. 168 | #html_use_index = True 169 | 170 | # If true, the index is split into individual pages for each letter. 171 | #html_split_index = False 172 | 173 | # If true, links to the reST sources are added to the pages. 174 | #html_show_sourcelink = True 175 | 176 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 177 | #html_show_sphinx = True 178 | 179 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 180 | #html_show_copyright = True 181 | 182 | # If true, an OpenSearch description file will be output, and all pages will 183 | # contain a tag referring to it. The value of this option must be the 184 | # base URL from which the finished HTML is served. 185 | #html_use_opensearch = '' 186 | 187 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 188 | #html_file_suffix = None 189 | 190 | # Language to be used for generating the HTML full-text search index. 191 | # Sphinx supports the following languages: 192 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 193 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 194 | #html_search_language = 'en' 195 | 196 | # A dictionary with options for the search language support, empty by default. 197 | # Now only 'ja' uses this config value 198 | #html_search_options = {'type': 'default'} 199 | 200 | # The name of a javascript file (relative to the configuration directory) that 201 | # implements a search results scorer. If empty, the default will be used. 202 | #html_search_scorer = 'scorer.js' 203 | 204 | # Output file base name for HTML help builder. 205 | htmlhelp_basename = 'django_payudoc' 206 | 207 | # -- Options for LaTeX output --------------------------------------------- 208 | 209 | latex_elements = { 210 | # The paper size ('letterpaper' or 'a4paper'). 211 | #'papersize': 'letterpaper', 212 | 213 | # The font size ('10pt', '11pt' or '12pt'). 214 | #'pointsize': '10pt', 215 | 216 | # Additional stuff for the LaTeX preamble. 217 | #'preamble': '', 218 | 219 | # Latex figure (float) alignment 220 | #'figure_align': 'htbp', 221 | } 222 | 223 | # Grouping the document tree into LaTeX files. List of tuples 224 | # (source start file, target name, title, 225 | # author, documentclass [howto, manual, or own class]). 226 | latex_documents = [ 227 | (master_doc, 'django_payu.tex', u'django\\_payu Documentation', 228 | u'Micro Pyramid', 'manual'), 229 | ] 230 | 231 | # The name of an image file (relative to this directory) to place at the top of 232 | # the title page. 233 | #latex_logo = None 234 | 235 | # For "manual" documents, if this is true, then toplevel headings are parts, 236 | # not chapters. 237 | #latex_use_parts = False 238 | 239 | # If true, show page references after internal links. 240 | #latex_show_pagerefs = False 241 | 242 | # If true, show URL addresses after external links. 243 | #latex_show_urls = False 244 | 245 | # Documents to append as an appendix to all manuals. 246 | #latex_appendices = [] 247 | 248 | # If false, no module index is generated. 249 | #latex_domain_indices = True 250 | 251 | 252 | # -- Options for manual page output --------------------------------------- 253 | 254 | # One entry per manual page. List of tuples 255 | # (source start file, name, description, authors, manual section). 256 | man_pages = [ 257 | (master_doc, 'django_payu', u'django_payu Documentation', 258 | [author], 1) 259 | ] 260 | 261 | # If true, show URL addresses after external links. 262 | #man_show_urls = False 263 | 264 | 265 | # -- Options for Texinfo output ------------------------------------------- 266 | 267 | # Grouping the document tree into Texinfo files. List of tuples 268 | # (source start file, target name, title, author, 269 | # dir menu entry, description, category) 270 | texinfo_documents = [ 271 | (master_doc, 'django_payu', u'django_payu Documentation', 272 | author, 'django_payu', 'One line description of project.', 273 | 'Miscellaneous'), 274 | ] 275 | 276 | # Documents to append as an appendix to all manuals. 277 | #texinfo_appendices = [] 278 | 279 | # If false, no module index is generated. 280 | #texinfo_domain_indices = True 281 | 282 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 283 | #texinfo_show_urls = 'footnote' 284 | 285 | # If true, do not generate a @detailmenu in the "Top" node's menu. 286 | #texinfo_no_detailmenu = False 287 | -------------------------------------------------------------------------------- /docs/source/configuration.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Configuration Settings 3 | ====================== 4 | 5 | In order to test your integration, first create a **PayU** Account. Once this has been created, you will be given the following... 6 | 7 | * *Key (MerchantID)* – This ID is generated at the time of activation of your site and helps to uniquely identify you to PayU. 8 | * *SALT* – This will be provided by PayU.in. 9 | 10 | Add the following settings to the PAYU_INFO dictionary using the details from your PayU account:: 11 | 12 | PAYU_MERCHANT_KEY = "Your MerchantID", 13 | 14 | PAYU_MERCHANT_SALT = "Your MerchantSALT", 15 | 16 | # And add the PAYU_MODE to 'TEST' for testing and 'LIVE' for production. 17 | PAYU_MODE = "TEST" 18 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. django_payu documentation master file, created by 2 | sphinx-quickstart on Sat Aug 22 16:31:28 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to django_payu's documentation! 7 | ======================================= 8 | 9 | This package provides integration between `Django`_ and `PayU Payment Gateway`_. 10 | 11 | .. _`Django`: https://www.djangoproject.com/ 12 | .. _`PayU Payment Gateway`: https://www.payu.in 13 | 14 | Installation 15 | ------------ 16 | 17 | Install:: 18 | 19 | pip install django-payu 20 | 21 | Next, add ``payu`` to your ``INSTALLED_APPS`` setting like this:: 22 | 23 | INSTALLED_APPS = [ 24 | .... 25 | .... 26 | 'payu', 27 | ] 28 | 29 | Then, add the following settings in the setting file using the details from your PayU account:: 30 | 31 | PAYU_MERCHANT_KEY = "Your MerchantID", 32 | 33 | PAYU_MERCHANT_SALT = "Your MerchantSALT", 34 | 35 | # Change the PAYU_MODE to 'LIVE' for production. 36 | PAYU_MODE = "TEST" 37 | 38 | Finally, run the following commands:: 39 | 40 | python manage.py migrate 41 | 42 | 43 | Contents: 44 | 45 | .. toctree:: 46 | :maxdepth: 2 47 | 48 | configuration 49 | apireference 50 | 51 | 52 | 53 | Indices and tables 54 | ================== 55 | 56 | * :ref:`genindex` 57 | * :ref:`modindex` 58 | * :ref:`search` 59 | 60 | -------------------------------------------------------------------------------- /payu/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'payu.apps.PayuConfig' 2 | # from payu.gateway import * 3 | -------------------------------------------------------------------------------- /payu/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from payu.models import Transaction, CancelRefundCaptureRequests 3 | 4 | # Register your models here. 5 | 6 | admin.site.register(Transaction) 7 | admin.site.register(CancelRefundCaptureRequests) 8 | -------------------------------------------------------------------------------- /payu/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from django.utils.translation import ugettext_lazy as _ 3 | 4 | 5 | class PayuConfig(AppConfig): 6 | name = 'payu' 7 | verbose_name = _("django payu") 8 | -------------------------------------------------------------------------------- /payu/gateway.py: -------------------------------------------------------------------------------- 1 | from payu.models import Transaction, CancelRefundCaptureRequests 2 | from django.core.exceptions import ObjectDoesNotExist 3 | from django.utils.http import urlencode 4 | from django.conf import settings 5 | from hashlib import sha512 6 | from uuid import uuid4 7 | try: 8 | import urllib.request as urllib2 9 | except ImportError: 10 | import urllib2 11 | import json 12 | 13 | 14 | KEYS = ('txnid', 'amount', 'productinfo', 'firstname', 'email', 15 | 'udf1', 'udf2', 'udf3', 'udf4', 'udf5', 'udf6', 'udf7', 'udf8', 16 | 'udf9', 'udf10') 17 | 18 | Webservicekeys = ('key', 'command', 'var1') 19 | 20 | 21 | def get_hash(data): 22 | # Generate hash sequence before posting the transaction to PayU: 23 | # sha512(key|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5||||||SALT) 24 | 25 | hash_value = str(getattr(settings, 'PAYU_MERCHANT_KEY', None)) 26 | 27 | for key in KEYS: 28 | if data.get(key) == None: 29 | hash_value = str(hash_value) + str('|') 30 | else: 31 | hash_value = str(hash_value) + str('|') + str(data.get(key, '')) 32 | 33 | hash_value = str(hash_value) + str('|') + str(getattr(settings, 'PAYU_MERCHANT_SALT', None)) 34 | 35 | # Create transaction record 36 | Transaction.objects.create( 37 | transaction_id=data.get('txnid'), amount=data.get('amount')) 38 | #return hash_value 39 | return sha512(str(hash_value).encode('utf-8')).hexdigest().lower() 40 | 41 | 42 | def check_hash(data): 43 | # Generate hash sequence and verify it with the hash sent by PayU in the Post Response 44 | 45 | Reversedkeys = reversed(KEYS) 46 | if data.get('additionalCharges'): 47 | # if the additionalCharges parameter is posted in the transaction response,then hash formula is: 48 | # sha512(additionalCharges|SALT|status||||||udf5|udf4|udf3|udf2|udf1|email|firstname|productinfo|amount|txnid|key) 49 | 50 | hash_value = sha512(str(data.get('additionalCharges')).encode('utf-8')) 51 | hash_value.update(("%s%s" % ('|', getattr(settings, 'PAYU_MERCHANT_SALT', None))).encode('utf-8')) 52 | else: 53 | # If additionalCharges parameter is not posted in the transaction response, then hash formula is the generic reverse hash formula 54 | # sha512(SALT|status||||||udf5|udf4|udf3|udf2|udf1|email|firstname|productinfo|amount|txnid|key) 55 | 56 | hash_value = sha512(getattr(settings, 'PAYU_MERCHANT_SALT', None)) 57 | 58 | hash_value = str(getattr(settings, 'PAYU_MERCHANT_SALT', None)) 59 | 60 | for key in Reversedkeys: 61 | if data.get(key) == None: 62 | hash_value = str(hash_value) + str('|') 63 | else: 64 | hash_value = str(hash_value) + str('|') + str(data.get(key, '')) 65 | 66 | hash_value = str(hash_value) + str('|') + str(getattr(settings, 'PAYU_MERCHANT_KEY', None)) 67 | 68 | # Updating the transaction 69 | transaction = Transaction.objects.get(transaction_id=data.get('txnid')) 70 | transaction.payment_gateway_type = data.get('PG_TYPE') 71 | transaction.transaction_date_time = data.get('addedon') 72 | transaction.mode = data.get('mode') 73 | transaction.status = data.get('status') 74 | transaction.amount = data.get('amount') 75 | transaction.mihpayid = data.get('mihpayid') 76 | transaction.bankcode = data.get('bankcode') 77 | transaction.bank_ref_num = data.get('bank_ref_num') 78 | transaction.discount = data.get('discount') 79 | transaction.additional_charges = data.get('additionalCharges', 0) 80 | transaction.txn_status_on_payu = data.get('unmappedstatus') 81 | transaction.hash_status = "Success" if hash_value.hexdigest().lower() == data.get('hash') else "Failed" 82 | transaction.save() 83 | 84 | return (sha512(str(hash_value).encode('utf-8')).hexdigest().lower() == data.get('hash')) 85 | 86 | 87 | def get_webservice_hash(data): 88 | # Generate hash sequence using the string sha512(key|command|var1|salt) 89 | hash_value = sha512(''.encode("utf-8")) 90 | for key in Webservicekeys: 91 | hash_value.update(("%s%s" % (str(data.get(key, '')), '|')).encode("utf-8")) 92 | 93 | hash_value.update(getattr(settings, 'PAYU_MERCHANT_SALT', None).encode("utf-8")) 94 | return hash_value.hexdigest().lower() 95 | 96 | 97 | def payu_url(): 98 | # Return the url based on mode of the environment. 99 | if getattr(settings, 'PAYU_MODE', 'TEST').lower() == "test": 100 | return 'https://test.payu.in/_payment' 101 | elif getattr(settings, 'PAYU_MODE', 'TEST').lower() == "live": 102 | return 'https://secure.payu.in/_payment' 103 | else: 104 | return None 105 | 106 | 107 | def post(params): 108 | if not params['command'] == "check_action_status" or "verify_payment": 109 | try: 110 | # Check whether the transaction exists or not 111 | Transaction.objects.get(mihpayid=params['var1']) 112 | except ObjectDoesNotExist: 113 | # if not return error message 114 | error_message = "Transaction with this mihpayid does not exist." 115 | return error_message 116 | 117 | params = params 118 | params['key'] = getattr(settings, 'PAYU_MERCHANT_KEY', None) 119 | 120 | # Generate the hash value 121 | params['hash'] = get_webservice_hash(params) 122 | 123 | if getattr(settings, 'PAYU_MODE', 'TEST').lower() == "test": 124 | url = 'https://test.payu.in/merchant/postservice.php?form=2' 125 | elif getattr(settings, 'PAYU_MODE', 'TEST').lower() == "live": 126 | url = 'https://info.payu.in/merchant/postservice.php?form=2' 127 | else: 128 | return None 129 | 130 | payload = urlencode(params) 131 | 132 | request = urllib2.Request(url) 133 | request.add_data(payload) 134 | response = (urllib2.urlopen(request)) 135 | 136 | response = json.loads(response.read()) 137 | 138 | return response 139 | 140 | 141 | # This command is used to verify the transaction with PayU. 142 | def verify_payment(txnid): 143 | params = {} 144 | params['command'] = "verify_payment" 145 | 146 | # Put all the txnid(Your transaction ID/order ID) values in a pipe separated form. Ex:100123|100124|100125|100126 147 | # Ex: params['var1'] = "316a5043e00c46aab55f94dfae1dd2d9|239de01417164b1898550e07f83fccff" 148 | params['var1'] = txnid 149 | 150 | return post(params) 151 | 152 | 153 | # This command is used to check payment after transaction. 154 | def check_payment(mihpayid): 155 | params = {} 156 | params['command'] = "check_payment" 157 | # Pass the Payu id (mihpayid) of the transaction to check. 158 | params['var1'] = mihpayid 159 | 160 | return post(params) 161 | 162 | 163 | # API is applicable only for transactions in auth status and nothing else. 164 | def capture_transaction(mihpayid): 165 | params = {} 166 | params['command'] = "capture_transaction" 167 | params['var1'] = mihpayid # Pass the Payu id (mihpayid) of the transaction to capture. 168 | params['var2'] = uuid4().hex # token ID(unique token from merchant) 169 | 170 | response = post(params) 171 | 172 | if type(response) == type(dict()) and 'request_id' in response.keys(): 173 | CancelRefundCaptureRequests.objects.create( 174 | request_id=response['request_id'], 175 | request_type="Capture", 176 | transaction=Transaction.objects.get(mihpayid=mihpayid), 177 | status=response['status'], 178 | message=response['msg'], 179 | mihpayid=response['mihpayid'] if response['mihpayid'] else mihpayid, 180 | bank_ref_num=response['bank_ref_num'], 181 | error_code=response['error_code'] if response['error_code'] else "") 182 | return response 183 | 184 | 185 | # This command is used to refund a captured transaction. Refund can occur only after one day of capture. 186 | def refund_transaction(mihpayid, amount): 187 | params = {} 188 | params['command'] = "refund_transaction" 189 | params['var1'] = mihpayid # Pass the Payu id (mihpayid) of the transaction to capture. 190 | params['var2'] = uuid4().hex # token ID(unique token from merchant) 191 | params['var3'] = amount 192 | 193 | response = post(params) 194 | 195 | if type(response) == type(dict()) and 'request_id' in response.keys(): 196 | CancelRefundCaptureRequests.objects.create( 197 | request_id=response['request_id'], 198 | request_type="Refund", 199 | transaction=Transaction.objects.get(mihpayid=mihpayid), 200 | status=response['status'], 201 | message=response['msg'], 202 | amount=amount, 203 | mihpayid=response['mihpayid'] if response['mihpayid'] else mihpayid, 204 | bank_ref_num=response['bank_ref_num'], 205 | error_code=response['error_code'] if response['error_code'] else "") 206 | return response 207 | 208 | 209 | # This command is used to cancel an auth transaction. 210 | def cancel_transaction(mihpayid, amount): 211 | params = {} 212 | params['command'] = "cancel_transaction" 213 | params['var1'] = mihpayid # Pass the Payu id (mihpayid) of the transaction to capture. 214 | params['var2'] = uuid4().hex # token ID(unique token from merchant) 215 | params['var3'] = amount 216 | 217 | response = post(params) 218 | if type(response) == type(dict()) and 'request_id' or 'txn_update_id' in response.keys(): 219 | CancelRefundCaptureRequests.objects.create( 220 | request_id=response['request_id'] if response['request_id'] else response['txn_update_id'], 221 | request_type="Cancel", 222 | transaction=Transaction.objects.get(mihpayid=mihpayid), 223 | status=response['status'], 224 | message=response['msg'], 225 | amount=amount, 226 | mihpayid=response['mihpayid'] if response['mihpayid'] else mihpayid, 227 | bank_ref_num=response['bank_ref_num'], 228 | error_code=response['error_code'] if response['error_code'] else "") 229 | return response 230 | 231 | 232 | # This command can be used for 2 different purposes: 233 | # 1.To cancel a transaction which is in auth state at the moment 234 | # 2.To refund a transaction which is in captured state at the moment 235 | def cancel_refund_transaction(mihpayid, amount): 236 | params = {} 237 | params['command'] = "cancel_refund_transaction" 238 | params['var1'] = mihpayid # Pass the Payu id (mihpayid) of the transaction to cancel/ Refund. 239 | params['var2'] = uuid4().hex # token ID(unique token from merchant) for only the refund request. 240 | 241 | # Amount which needs to be refunded. Please note that both partial and full refunds are allowed. 242 | params['var3'] = amount 243 | 244 | response = post(params) 245 | 246 | if type(response) == type(dict()) and 'request_id' in response.keys(): 247 | CancelRefundCaptureRequests.objects.create( 248 | request_id=response['request_id'], 249 | request_type="Cancel/Refund", 250 | transaction=Transaction.objects.get(mihpayid=mihpayid), 251 | status=response['status'], 252 | message=response['msg'], 253 | amount=amount, 254 | mihpayid=response['mihpayid'] if response['mihpayid'] else mihpayid, 255 | bank_ref_num=response['bank_ref_num'], 256 | error_code=response['error_code']) 257 | return response 258 | 259 | 260 | # This API is used to check the status of refund/cancel requests 261 | def check_action_status(request_id): 262 | params = {} 263 | params['command'] = "check_action_status" 264 | params['var1'] = request_id # Pass the Cancel Refund Request ID. 265 | 266 | return post(params) 267 | -------------------------------------------------------------------------------- /payu/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-06-03 15:50 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='CancelRefundCaptureRequests', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('request_id', models.CharField(max_length=100)), 22 | ('request_type', models.CharField(max_length=20)), 23 | ('status', models.CharField(max_length=15)), 24 | ('message', models.CharField(max_length=100)), 25 | ('mihpayid', models.CharField(max_length=100)), 26 | ('bank_ref_num', models.CharField(blank=True, max_length=100, null=True)), 27 | ('amount', models.DecimalField(decimal_places=6, default=0, max_digits=19)), 28 | ('error_code', models.CharField(max_length=10)), 29 | ], 30 | ), 31 | migrations.CreateModel( 32 | name='Transaction', 33 | fields=[ 34 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 35 | ('transaction_id', models.CharField(max_length=100)), 36 | ('payment_gateway_type', models.CharField(blank=True, max_length=20, null=True)), 37 | ('transaction_date_time', models.DateTimeField(blank=True, null=True)), 38 | ('mode', models.CharField(blank=True, max_length=10, null=True)), 39 | ('status', models.CharField(blank=True, max_length=15, null=True)), 40 | ('amount', models.DecimalField(decimal_places=6, max_digits=19)), 41 | ('mihpayid', models.CharField(blank=True, max_length=100, null=True)), 42 | ('bankcode', models.CharField(blank=True, max_length=10, null=True)), 43 | ('bank_ref_num', models.CharField(blank=True, max_length=100, null=True)), 44 | ('discount', models.DecimalField(decimal_places=6, default=0, max_digits=19)), 45 | ('additional_charges', models.DecimalField(decimal_places=6, default=0, max_digits=19)), 46 | ('txn_status_on_payu', models.CharField(blank=True, max_length=20, null=True)), 47 | ('hash_status', models.CharField(blank=True, max_length=100, null=True)), 48 | ], 49 | ), 50 | migrations.AddField( 51 | model_name='cancelrefundcapturerequests', 52 | name='transaction', 53 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='payu.Transaction'), 54 | ), 55 | ] 56 | -------------------------------------------------------------------------------- /payu/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicroPyramid/django-payu/340cba80546640f82eac6a4a637edb96262988b5/payu/migrations/__init__.py -------------------------------------------------------------------------------- /payu/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | 5 | 6 | class Transaction(models.Model): 7 | # Transaction id sent by merchant. 8 | transaction_id = models.CharField(max_length=100) 9 | 10 | # Payment gateway type used in transaction 11 | payment_gateway_type = models.CharField(max_length=20, null=True, blank=True) # Map to PG_TYPE 12 | 13 | # Map to addedon 14 | transaction_date_time = models.DateTimeField(null=True, blank=True) 15 | 16 | # mode (credit card/ CD - Cheque / Net Banking) 17 | mode = models.CharField(max_length=10, null=True, blank=True) 18 | status = models.CharField(max_length=15, null=True, blank=True) 19 | amount = models.DecimalField(max_digits=19, decimal_places=6) 20 | 21 | # Unique id from PayU.in 22 | mihpayid = models.CharField(max_length=100, null=True, blank=True) 23 | bankcode = models.CharField(max_length=10, null=True, blank=True) 24 | 25 | # Reference number for the payment gateway (received in PG_TYPE) 26 | bank_ref_num = models.CharField(max_length=100, null=True, blank=True) 27 | discount = models.DecimalField(max_digits=19, decimal_places=6, default=0) 28 | additional_charges = models.DecimalField(max_digits=19, decimal_places=6, default=0) # Charged by Payu 29 | 30 | # Status of transaction in PayU system 31 | # Map to unmappedstatus(initiated/ in progress /dropped / bounced / captured / auth/ failed / usercancelled/ pending) 32 | txn_status_on_payu = models.CharField(max_length=20, null=True, blank=True) 33 | hash_status = models.CharField(max_length=100, null=True, blank=True) 34 | 35 | class Meta: 36 | app_label = 'payu' 37 | 38 | 39 | class CancelRefundCaptureRequests(models.Model): 40 | transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE) 41 | 42 | # PayU Request ID for a request in a Transaction. 43 | request_id = models.CharField(max_length=100) 44 | 45 | # Cancel or Refund or Capture Request 46 | request_type = models.CharField(max_length=20) 47 | 48 | # Status of webservice call 49 | status = models.CharField(max_length=15) 50 | message = models.CharField(max_length=100) 51 | 52 | # PayU ID 53 | mihpayid = models.CharField(max_length=100) 54 | 55 | # Bank Reference Number 56 | bank_ref_num = models.CharField(max_length=100, null=True, blank=True) 57 | 58 | amount = models.DecimalField(max_digits=19, decimal_places=6, default=0) 59 | error_code = models.CharField(max_length=10) 60 | 61 | class Meta: 62 | app_label = 'payu' 63 | -------------------------------------------------------------------------------- /payu/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.conf import settings 3 | from payu.models import * 4 | from payu.gateway import ( 5 | get_hash, 6 | check_hash, 7 | get_webservice_hash, 8 | payu_url, 9 | post, 10 | verify_payment, 11 | check_payment, 12 | capture_transaction, 13 | refund_transaction, 14 | cancel_transaction, 15 | check_action_status, 16 | cancel_refund_transaction, 17 | ) 18 | 19 | 20 | setattr(settings, 'PAYU_MERCHANT_KEY', 'JBZaLc') 21 | setattr(settings, 'PAYU_MERCHANT_SALT', 'GQs7yium') 22 | 23 | 24 | class Sample(TestCase): 25 | 26 | def test_addition(self): 27 | Sum = 5 + 4 28 | self.assertEqual(Sum, 9) 29 | 30 | 31 | class GetHash(TestCase): 32 | 33 | def test_get_hash(self): 34 | data = {'email': 'hello@micropyramid.com', 35 | 'txnid': 123456, 36 | 'amount': 300} 37 | response = get_hash(data) 38 | self.assertTrue(response) 39 | data['email'] = None 40 | response = get_hash(data) 41 | self.assertTrue(response) 42 | 43 | 44 | class CheckHash(TestCase): 45 | 46 | def setUp(self): 47 | self.transaction = Transaction.objects.create(transaction_id=123456, 48 | amount=300) 49 | 50 | def test_check_hash(self): 51 | data = {'additionalCharges': 220, 52 | 'txnid': 123456, 53 | 'amount': 500, 54 | 'discount': 50} 55 | response = check_hash(data) 56 | self.assertFalse(response) 57 | 58 | 59 | class GetWebServiceHash(TestCase): 60 | 61 | def test_get_webservice_hash(self): 62 | data = {'key': 12345} 63 | response = get_webservice_hash(data) 64 | self.assertTrue(response) 65 | 66 | 67 | class PayuUrl(TestCase): 68 | 69 | def test_payu_url(self): 70 | setattr(settings, 'PAYU_MODE', 'TEST') 71 | reslut = payu_url() 72 | setattr(settings, 'PAYU_MODE', 'LIVE') 73 | reslut = payu_url() 74 | self.assertTrue(reslut) 75 | setattr(settings, 'PAYU_MODE', 'None') 76 | reslut = payu_url() 77 | self.assertFalse(reslut) 78 | 79 | 80 | class Post(TestCase): 81 | 82 | def get_trans(self): 83 | self.transaction = Transaction.objects.create(transaction_id=123456, 84 | amount=300, 85 | mihpayid=123) 86 | 87 | def test_post(self): 88 | params = {'command': 'check_action_status', 89 | 'key': 'dummy11', 90 | 'hash': '12355', 91 | 'var1': 55} 92 | response = post(params) 93 | self.assertTrue(response) 94 | self.get_trans() 95 | params['var1'] = 123 96 | response = post(params) 97 | self.assertFalse(response) 98 | 99 | def tearDown(self): 100 | if hasattr(self, 'transaction'): 101 | self.transaction.delete() 102 | 103 | 104 | class VerifyPayment(TestCase): 105 | 106 | def setUp(self): 107 | Transaction.objects.create(transaction_id=123456, 108 | amount=300, 109 | mihpayid=123) 110 | # setattr(settings, 'PAYU_MODE', 'TEST') 111 | 112 | def test_verify_payment(self): 113 | r = verify_payment(txnid=123) 114 | self.assertFalse(r) 115 | 116 | def test_check_payment(self): 117 | r = check_payment(123) 118 | self.assertFalse(r) 119 | 120 | def test_capture_transaction(self): 121 | capture_transaction(123) 122 | 123 | def test_refund_transaction(self): 124 | refund_transaction(123, 123) 125 | 126 | def test_check_action_status(self): 127 | check_action_status(123) 128 | 129 | def test_cancel_refund_transaction(self): 130 | cancel_refund_transaction(123, 10) 131 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: 5 | README = readme.read() 6 | 7 | # allow setup.py to be run from any path 8 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 9 | PROJECT_NAME = 'payu' 10 | 11 | data_files = [] 12 | for dirpath, dirnames, filenames in os.walk(PROJECT_NAME): 13 | for i, dirname in enumerate(dirnames): 14 | if dirname.startswith('.'): 15 | del dirnames[i] 16 | if '__init__.py' in filenames: 17 | continue 18 | elif filenames: 19 | for f in filenames: 20 | data_files.append(os.path.join( 21 | dirpath[len(PROJECT_NAME) + 1:], f)) 22 | 23 | setup( 24 | name='django-payu', 25 | version='1.0', 26 | packages=['payu', 'payu.migrations'], 27 | include_package_data=True, 28 | description='A simple PayU app for Django.', 29 | long_description=README, 30 | url='https://github.com/MicroPyramid/django-payu', 31 | author='Ashwin Kumar', 32 | author_email='ashwin@micropyramid.com', 33 | classifiers=[ 34 | 'Environment :: Web Environment', 35 | 'Framework :: Django', 36 | 'Intended Audience :: Developers', 37 | 'Operating System :: OS Independent', 38 | 'License :: OSI Approved :: MIT License', 39 | 'Programming Language :: Python', 40 | 'Programming Language :: Python :: 2.7', 41 | 'Programming Language :: Python :: 3', 42 | 'Programming Language :: Python :: 3.2', 43 | 'Programming Language :: Python :: 3.3', 44 | 'Topic :: Internet :: WWW/HTTP', 45 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 46 | ], 47 | install_requires=[ 48 | 'django>=1.7,<=3.0.3', 49 | ], 50 | ) 51 | -------------------------------------------------------------------------------- /test_runner.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import django 3 | from django.conf import settings 4 | 5 | 6 | if __name__ == "__main__": 7 | settings.configure( 8 | DATABASES={ 9 | 'default': { 10 | 'ENGINE': 'django.db.backends.sqlite3', 11 | 'NAME': './db.sqlite3' 12 | } 13 | }, 14 | INSTALLED_APPS=['payu'], 15 | ) 16 | django.setup() 17 | if sys.argv: 18 | from django.core.management import execute_from_command_line 19 | execute_from_command_line(sys.argv) 20 | --------------------------------------------------------------------------------