Merge pull request #9955 from Mangesh-Khairnar/paytm-integration
This commit is contained in:
commit
32a9c3a83f
11 changed files with 345 additions and 2 deletions
|
|
@ -27,6 +27,11 @@ def get_data():
|
|||
"name": "Stripe Settings",
|
||||
"description": _("Stripe payment gateway settings"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Paytm Settings",
|
||||
"description": _("Paytm payment gateway settings"),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
0
frappe/integrations/doctype/paytm_settings/__init__.py
Normal file
0
frappe/integrations/doctype/paytm_settings/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Paytm Settings', {
|
||||
refresh: function(frm) {
|
||||
frm.dashboard.set_headline(__("For more information, {0}.", [`<a href='https://erpnext.com/docs/user/manual/en/erpnext_integration/paytm-integration'>${__('Click here')}</a>`]));
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-04-02 00:11:22.846697",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"merchant_id",
|
||||
"merchant_key",
|
||||
"staging",
|
||||
"column_break_4",
|
||||
"industry_type_id",
|
||||
"website"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "merchant_id",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Merchant ID",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "merchant_key",
|
||||
"fieldtype": "Password",
|
||||
"in_list_view": 1,
|
||||
"label": "Merchant Key",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "staging",
|
||||
"fieldtype": "Check",
|
||||
"label": "Staging",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: !doc.staging",
|
||||
"fieldname": "website",
|
||||
"fieldtype": "Data",
|
||||
"label": "Website",
|
||||
"mandatory_depends_on": "eval: !doc.staging",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: !doc.staging",
|
||||
"fieldname": "industry_type_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Industry Type ID",
|
||||
"mandatory_depends_on": "eval: !doc.staging",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-08 13:36:09.703143",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Paytm Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
159
frappe/integrations/doctype/paytm_settings/paytm_settings.py
Normal file
159
frappe/integrations/doctype/paytm_settings/paytm_settings.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import json
|
||||
import requests
|
||||
from six.moves.urllib.parse import urlencode
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import get_url, call_hook_method, cint, flt, cstr
|
||||
from frappe.integrations.utils import create_request_log, create_payment_gateway
|
||||
from frappe.utils import get_request_site_address
|
||||
from paytmchecksum import generateSignature, verifySignature
|
||||
from frappe.utils.password import get_decrypted_password
|
||||
|
||||
class PaytmSettings(Document):
|
||||
supported_currencies = ["INR"]
|
||||
|
||||
def validate(self):
|
||||
create_payment_gateway('Paytm')
|
||||
call_hook_method('payment_gateway_enabled', gateway='Paytm')
|
||||
|
||||
def validate_transaction_currency(self, currency):
|
||||
if currency not in self.supported_currencies:
|
||||
frappe.throw(_("Please select another payment method. Paytm does not support transactions in currency '{0}'").format(currency))
|
||||
|
||||
def get_payment_url(self, **kwargs):
|
||||
'''Return payment url with several params'''
|
||||
# create unique order id by making it equal to the integration request
|
||||
integration_request = create_request_log(kwargs, "Host", "Paytm")
|
||||
kwargs.update(dict(order_id=integration_request.name))
|
||||
|
||||
return get_url("./integrations/paytm_checkout?{0}".format(urlencode(kwargs)))
|
||||
|
||||
def get_paytm_config():
|
||||
''' Returns paytm config '''
|
||||
|
||||
paytm_config = frappe.db.get_singles_dict('Paytm Settings')
|
||||
paytm_config.update(dict(merchant_key=get_decrypted_password('Paytm Settings', 'Paytm Settings', 'merchant_key')))
|
||||
|
||||
if cint(paytm_config.staging):
|
||||
paytm_config.update(dict(
|
||||
website="WEBSTAGING",
|
||||
url='https://securegw-stage.paytm.in/order/process',
|
||||
transaction_status_url='https://securegw-stage.paytm.in/order/status',
|
||||
industry_type_id='RETAIL'
|
||||
))
|
||||
else:
|
||||
paytm_config.update(dict(
|
||||
url='https://securegw.paytm.in/order/process',
|
||||
transaction_status_url='https://securegw.paytm.in/order/status',
|
||||
))
|
||||
return paytm_config
|
||||
|
||||
def get_paytm_params(payment_details, order_id, paytm_config):
|
||||
|
||||
# initialize a dictionary
|
||||
paytm_params = dict()
|
||||
|
||||
redirect_uri = get_request_site_address(True) + "/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.verify_transaction"
|
||||
|
||||
|
||||
paytm_params.update({
|
||||
"MID" : paytm_config.merchant_id,
|
||||
"WEBSITE" : paytm_config.website,
|
||||
"INDUSTRY_TYPE_ID" : paytm_config.industry_type_id,
|
||||
"CHANNEL_ID" : "WEB",
|
||||
"ORDER_ID" : order_id,
|
||||
"CUST_ID" : payment_details['payer_email'],
|
||||
"EMAIL" : payment_details['payer_email'],
|
||||
"TXN_AMOUNT" : cstr(flt(payment_details['amount'], 2)),
|
||||
"CALLBACK_URL" : redirect_uri,
|
||||
})
|
||||
|
||||
checksum = generateSignature(paytm_params, paytm_config.merchant_key)
|
||||
|
||||
paytm_params.update({
|
||||
"CHECKSUMHASH" : checksum
|
||||
})
|
||||
|
||||
return paytm_params
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def verify_transaction(**paytm_params):
|
||||
'''Verify checksum for received data in the callback and then verify the transaction'''
|
||||
paytm_config = get_paytm_config()
|
||||
is_valid_checksum = False
|
||||
|
||||
paytm_params.pop('cmd', None)
|
||||
paytm_checksum = paytm_params.pop('CHECKSUMHASH', None)
|
||||
|
||||
if paytm_params and paytm_config and paytm_checksum:
|
||||
# Verify checksum
|
||||
is_valid_checksum = verifySignature(paytm_params, paytm_config.merchant_key, paytm_checksum)
|
||||
|
||||
if is_valid_checksum and paytm_params.get('RESPCODE') == '01':
|
||||
verify_transaction_status(paytm_config, paytm_params['ORDERID'])
|
||||
else:
|
||||
frappe.respond_as_web_page("Payment Failed",
|
||||
"Transaction failed to complete. In case of any deductions, deducted amount will get refunded to your account.",
|
||||
http_status_code=401, indicator_color='red')
|
||||
frappe.log_error("Order unsuccessful. Failed Response:"+cstr(paytm_params), 'Paytm Payment Failed')
|
||||
|
||||
def verify_transaction_status(paytm_config, order_id):
|
||||
'''Verify transaction completion after checksum has been verified'''
|
||||
paytm_params=dict(
|
||||
MID=paytm_config.merchant_id,
|
||||
ORDERID= order_id
|
||||
)
|
||||
|
||||
checksum = generateSignature(paytm_params, paytm_config.merchant_key)
|
||||
paytm_params["CHECKSUMHASH"] = checksum
|
||||
|
||||
post_data = json.dumps(paytm_params)
|
||||
url = paytm_config.transaction_status_url
|
||||
|
||||
response = requests.post(url, data = post_data, headers = {"Content-type": "application/json"}).json()
|
||||
finalize_request(order_id, response)
|
||||
|
||||
def finalize_request(order_id, transaction_response):
|
||||
request = frappe.get_doc('Integration Request', order_id)
|
||||
transaction_data = frappe._dict(json.loads(request.data))
|
||||
redirect_to = transaction_data.get('redirect_to') or None
|
||||
redirect_message = transaction_data.get('redirect_message') or None
|
||||
|
||||
if transaction_response['STATUS'] == "TXN_SUCCESS":
|
||||
if transaction_data.reference_doctype and transaction_data.reference_docname:
|
||||
custom_redirect_to = None
|
||||
try:
|
||||
custom_redirect_to = frappe.get_doc(transaction_data.reference_doctype,
|
||||
transaction_data.reference_docname).run_method("on_payment_authorized", 'Completed')
|
||||
request.db_set('status', 'Completed')
|
||||
except Exception:
|
||||
request.db_set('status', 'Failed')
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
|
||||
if custom_redirect_to:
|
||||
redirect_to = custom_redirect_to
|
||||
|
||||
redirect_url = '/integrations/payment-success'
|
||||
else:
|
||||
request.db_set('status', 'Failed')
|
||||
redirect_url = '/integrations/payment-failed'
|
||||
|
||||
if redirect_to:
|
||||
redirect_url += '?' + urlencode({'redirect_to': redirect_to})
|
||||
if redirect_message:
|
||||
redirect_url += '&' + urlencode({'redirect_message': redirect_message})
|
||||
|
||||
frappe.local.response['type'] = 'redirect'
|
||||
frappe.local.response['location'] = redirect_url
|
||||
|
||||
def get_gateway_controller(doctype, docname):
|
||||
reference_doc = frappe.get_doc(doctype, docname)
|
||||
gateway_controller = frappe.db.get_value("Payment Gateway", reference_doc.payment_gateway, "gateway_controller")
|
||||
return gateway_controller
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestPaytmSettings(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -186,7 +186,7 @@ login.login_handlers = (function() {
|
|||
} else if(data.message == 'Password Reset'){
|
||||
window.location.href = frappe.utils.sanitise_redirect(data.redirect_to);
|
||||
} else if(data.message=="No App") {
|
||||
login.set_indicator("{{ _("Success") }}", 'green');
|
||||
login.set_indicator("{{ _('Success') }}", 'green');
|
||||
if(localStorage) {
|
||||
var last_visited =
|
||||
localStorage.getItem("last_visited")
|
||||
|
|
|
|||
43
frappe/templates/pages/integrations/paytm_checkout.html
Normal file
43
frappe/templates/pages/integrations/paytm_checkout.html
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block title %} Payment {% endblock %}
|
||||
|
||||
{%- block header -%}
|
||||
<head>
|
||||
<title>Merchant Checkout Page</title>
|
||||
</head>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script defer type="text/javascript">
|
||||
document.paytm_form.submit();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{%- block page_content -%}
|
||||
<body>
|
||||
<div class="centered">
|
||||
<h2>Please do not refresh this page...</h2>
|
||||
|
||||
<form method="post" action="{{ url }}" name="paytm_form">
|
||||
{% for name, value in payment_details.items() %}
|
||||
<input type="hidden" name="{{ name }}" value="{{ value }}">
|
||||
{% endfor %}
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
{% endblock %}
|
||||
|
||||
{% block style %}
|
||||
<style>
|
||||
.centered {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.web-footer {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
27
frappe/templates/pages/integrations/paytm_checkout.py
Normal file
27
frappe/templates/pages/integrations/paytm_checkout.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
import json
|
||||
from frappe.integrations.doctype.paytm_settings.paytm_settings import get_paytm_params, get_paytm_config
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
paytm_config = get_paytm_config()
|
||||
|
||||
try:
|
||||
doc = frappe.get_doc("Integration Request", frappe.form_dict['order_id'])
|
||||
|
||||
context.payment_details = get_paytm_params(json.loads(doc.data), doc.name, paytm_config)
|
||||
|
||||
context.url = paytm_config.url
|
||||
|
||||
except Exception:
|
||||
frappe.log_error()
|
||||
frappe.redirect_to_message(_('Invalid Token'),
|
||||
_('Seems token you are using is invalid!'),
|
||||
http_status_code=400, indicator_color='red')
|
||||
|
||||
frappe.local.flags.redirect_location = frappe.local.response.location
|
||||
raise frappe.Redirect
|
||||
|
|
@ -24,7 +24,7 @@ html, body {
|
|||
<span class='indicator {{ indicator_color or "blue" }}'>
|
||||
{{ title or _("Message") }}</span>
|
||||
</h5>
|
||||
<div class="page-card-body ellipsis">
|
||||
<div class="page-card-body">
|
||||
{% block message_body %}
|
||||
<p>{{ message or "" }}</p>
|
||||
{% if primary_action %}
|
||||
|
|
|
|||
|
|
@ -68,3 +68,5 @@ Werkzeug==0.16.1
|
|||
Whoosh==2.7.4
|
||||
xlrd==1.2.0
|
||||
zxcvbn-python==4.4.24
|
||||
pycryptodome==3.9.8
|
||||
paytmchecksum==1.7.0
|
||||
Loading…
Add table
Reference in a new issue