diff --git a/payment_wechat/README.rst b/payment_wechat/README.rst new file mode 100644 index 0000000000..f058b051c5 --- /dev/null +++ b/payment_wechat/README.rst @@ -0,0 +1,52 @@ +.. image:: https://img.shields.io/badge/license-MIT-blue.svg + :target: https://opensource.org/licenses/MIT + :alt: License: MIT + +================= + WeChat payments +================= + +Technical module to integrate WeChat payments with odoo POS, eCommerce or backend. As in WeChat QR codes are used, addional modules are required to show QR code in POS or eCommerce. Following methods are supported: + +* TODO User scans QR and authorise payment +* TODO User opens eCommerce website via WeChat's browser, fills the cart and is redirected to WeChat App UI to authorise the payment + +Note, that this module doesn't implement *Quick Pay* method, i.e. the one where buyer shows QR code and vendor scans. + +Credits +======= + +Contributors +------------ +* `Ivan Yelizariev `__ + +Sponsors +-------- +* `IT-Projects LLC `__ + +Maintainers +----------- +* `IT-Projects LLC `__ + + To get a guaranteed support + you are kindly requested to purchase the module + at `odoo apps store `__. + + Thank you for understanding! + + `IT-Projects Team `__ + +Further information +=================== + +Demo: http://runbot.it-projects.info/demo/misc-addons/11.0 + +HTML Description: https://apps.odoo.com/apps/modules/11.0/payment_wechat/ + +Usage instructions: ``_ + +Changelog: ``_ + +Notifications on updates: `via Atom `_, `by Email `_ + +Tested on Odoo 11.0 4d0a1330e05bd688265bea14df4ad12838f9f2d7 diff --git a/payment_wechat/__init__.py b/payment_wechat/__init__.py new file mode 100644 index 0000000000..f7209b1710 --- /dev/null +++ b/payment_wechat/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import controllers diff --git a/payment_wechat/__manifest__.py b/payment_wechat/__manifest__.py new file mode 100644 index 0000000000..33193fd89a --- /dev/null +++ b/payment_wechat/__manifest__.py @@ -0,0 +1,28 @@ +# Copyright 2018 Ivan Yelizariev +# License MIT (https://opensource.org/licenses/MIT). +{ + "name": """WeChat payments""", + "summary": """The most popular Chinese payment method""", + "category": "Accounting", + # "live_test_url": "", + "images": [], + "version": "11.0.1.0.0", + "application": False, + "author": "IT-Projects LLC, Ivan Yelizariev", + "support": "apps@it-projects.info", + "website": "https://it-projects.info/team/yelizariev", + "license": "Other OSI approved licence", # MIT + # "price": 9.00, + # "currency": "EUR", + "depends": [], + "external_dependencies": {"python": [], "bin": []}, + "data": [], + "demo": ["demo/w_p_demo.xml"], + "qweb": [], + "post_load": None, + "pre_init_hook": None, + "post_init_hook": None, + "uninstall_hook": None, + "auto_install": False, + "installable": False, +} diff --git a/payment_wechat/controllers/__init__.py b/payment_wechat/controllers/__init__.py new file mode 100644 index 0000000000..15a22e1505 --- /dev/null +++ b/payment_wechat/controllers/__init__.py @@ -0,0 +1 @@ +from . import p_w_controllers diff --git a/payment_wechat/controllers/p_w_controllers.py b/payment_wechat/controllers/p_w_controllers.py new file mode 100644 index 0000000000..9a69346e12 --- /dev/null +++ b/payment_wechat/controllers/p_w_controllers.py @@ -0,0 +1,126 @@ +from __future__ import unicode_literals + +import logging +import random +import time + +import requests + +import odoo +from odoo.http import request + +_logger = logging.getLogger(__name__) + +try: + from odoo.addons.bus.controllers.main import BusController +except ImportError: + _logger.error("pos_multi_session_sync inconsisten with odoo version") + BusController = object + + +class Controller(BusController): + @odoo.http.route("/wechat/getsignkey", type="json", auth="public") + def getSignKey(self, message): + data = {} + data["mch_id"] = request.env["ir.config_parameter"].get_param("wechat.mchId") + wcc = request.env["wechat.config"] + data["nonce_str"] = (wcc.getRandomNumberGeneration(message))[:32] + data["sign"] = ( + str(time.time()).replace(".", "") + + "{:010}".format(random.randint(1, 9999999999)) + + "{:010}".format(random.randint(1, 9999999999)) + )[:32] + post = wcc.makeXmlPost(data) + url = "https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey" + r1 = requests.post(url, data=post, timeout=30) + message = {} + message["resp1"] = r1.text + return message + + @odoo.http.route("/wechat/test", type="json", auth="public") + def testAccessToken(self, message): + wcc = request.env["wechat.config"] + if not wcc: + wcc = wcc.create({"token_validity": 7000, "access_token": "test"}) + wcc.getAccessToken() + + @odoo.http.route("/wechat/payment_commence", type="json", auth="public") + def micropay(self, message): + # data = message['data'] + # data['order_id'] = '{0:06}'.format(message['data']['order_id']) + # data['cashier_id'] = '{0:05}'.format(message['data']['cashier_id']) + # data['session_id'] = '{0:05}'.format(message['data']['session_id']) + data = {} + data["auth_code"] = message["data"]["auth_code"] + data["appid"] = request.env["ir.config_parameter"].get_param("wechat.appId") + data["mch_id"] = request.env["ir.config_parameter"].get_param("wechat.mchId") + data["body"] = message["data"]["order_short"] + + data["out_trade_no"] = ( + str(time.time()).replace(".", "") + + "{:010}".format(random.randint(1, 9999999999)) + + "{:010}".format(random.randint(1, 9999999999)) + )[:32] + wcc = request.env["wechat.config"] + if not wcc: + wcc = wcc.create({"token_validity": 1, "access_token": ""}) + data["total_fee"] = message["data"]["total_fee"] + data["spbill_create_ip"] = wcc.getIpList()[0] + # data['auth_code'] = message['data']['auth_code'] + # + # device_info = + # sign_type = + # detail = + # attach = + # fee_type = + # goods_tag = + # limit_pay = + # scene_info = + # + data["nonce_str"] = (wcc.getRandomNumberGeneration(message))[:32] + data["sign"] = (wcc.getRandomNumberGeneration(message))[:32] + + post = wcc.makeXmlPost(data) + r1 = requests.post( + "https://api.mch.weixin.qq.com/sandboxnew/pay/micropay", + data=post, + timeout=30, + ) + message = {} + message["resp1"] = r1 + message["resp_text1"] = r1.text + message["resp_cont1"] = r1.content + # message['encode_text1'] = r1.text.encode('iso-8859-1').decode('utf-8') + # print(r1.text.encode('utf-8')) + time.sleep(5) + # return request.redirect('/wechat/payment_query') + # + # @odoo.http.route('/wechat/payment_query', type="json", auth="public") + # def queryOrderApi(self, message): + data_qa = {} + data_qa["appid"] = data["appid"] + data_qa["mch_id"] = data["mch_id"] + data_qa["out_trade_no"] = data["out_trade_no"] + data_qa["nonce_str"] = data["nonce_str"] + data_qa["sign"] = data["sign"] + if hasattr(data, "sign_type"): + data_qa["sign_type"] = data["sign_type"] + + post = wcc.makeXmlPost(data_qa) + r2 = requests.post( + "https://api.mch.weixin.qq.com/sandboxnew/pay/orderquery", + data=post, + timeout=30, + ) + message["resp2"] = r2 + message["resp_text2"] = r2.text + message["resp_cont2"] = r2.content + # message['encode_text2'] = r2.text.encode('iso-8859-1').decode('utf-8') + # with open('txt.txt', 'w+') as fil: + # fil.write(r1.text, r2.text) + # print(r2.text.encode('utf-8')) + # for each_unicode_character in r2.text.encode('utf-8').decode('utf-8'): + # print(each_unicode_character) + # print(message['encode_text1']) + # print(message['encode_text2']) + return message diff --git a/payment_wechat/demo/w_p_demo.xml b/payment_wechat/demo/w_p_demo.xml new file mode 100644 index 0000000000..7e9f9677ec --- /dev/null +++ b/payment_wechat/demo/w_p_demo.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/payment_wechat/doc/changelog.rst b/payment_wechat/doc/changelog.rst new file mode 100644 index 0000000000..9ee2b48b8e --- /dev/null +++ b/payment_wechat/doc/changelog.rst @@ -0,0 +1,4 @@ +`1.0.0` +------- + +- Init version diff --git a/payment_wechat/doc/index.rst b/payment_wechat/doc/index.rst new file mode 100644 index 0000000000..85a4a4a079 --- /dev/null +++ b/payment_wechat/doc/index.rst @@ -0,0 +1,12 @@ +================= + WeChat payments +================= + +Follow instructions of `WeChat API `__. + +Usage +===== + +Following instruction covers backend usage only. For POS and eCommerce use instructions of corresponding modules. + +* open menu TODO diff --git a/payment_wechat/models/__init__.py b/payment_wechat/models/__init__.py new file mode 100644 index 0000000000..f84001a85e --- /dev/null +++ b/payment_wechat/models/__init__.py @@ -0,0 +1 @@ +from . import wechat_models diff --git a/payment_wechat/models/wechat_models.py b/payment_wechat/models/wechat_models.py new file mode 100644 index 0000000000..c8e27762e1 --- /dev/null +++ b/payment_wechat/models/wechat_models.py @@ -0,0 +1,84 @@ +from __future__ import absolute_import, unicode_literals + +import hashlib +import json +import time + +import requests + +from odoo import api, fields, models +from odoo.http import request + + +class AccountJournal(models.Model): + _inherit = "account.journal" + + wechat_payment = fields.Boolean( + string="Allow WeChat payments", + default=False, + help="Check this box if this account allows pay via WeChat", + ) + + +# class PosOrder(models.Model): +# _inherit = "pos.order" +# +# auth_code = fields.Integer(string='Code obtained from customers QR or BarCode', default=0) + + +class WechatConfiguration(models.Model): + _name = "wechat.config" + + # auth_code = fields.Integer(string='Code obtained from customers QR or BarCode', default=0) + access_token = fields.Char(string="access_token") + token_validity = fields.Float(string="validity time") + + @api.multi + def getAccessToken(self): + if not self.token_validity: + self.createVals() + if self.token_validity < time.time(): + appId = request.env["ir.config_parameter"].get_param("wechat.appId") + appSecret = request.env["ir.config_parameter"].get_param("wechat.appSecret") + url = ( + "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s" + % (appId, appSecret) + ) + response = requests.get(url, timeout=30) + access_token = json.loads(response.text)["access_token"] + self.write( + {"token_validity": time.time() + 7000, "access_token": access_token} + ) + else: + access_token = self.access_token + return access_token + + def getIpList(self): + token = self.getAccessToken() + url = "https://api.wechat.com/cgi-bin/getcallbackip?access_token=%s" % token + response = requests.get(url, timeout=30) + return json.loads(response.text)["ip_list"] + + def sortData(self, message): + arrA = [] + data = message["data"] + for key in data: + if data[key]: + arrA.append(str(key) + "=" + str(data[key])) + arrA.sort() + return arrA + + def getRandomNumberGeneration(self, message): + data = self.sortData(message) + strA = " & ".join(data) + return hashlib.sha256(strA.encode("utf-8")).hexdigest().upper() + + def makeXmlPost(self, data): + xml_str = [""] + for key in sorted(data): + if data[key]: + xml_str.append( + "<" + str(key) + ">" + str(data[key]) + "" + ) + xml_str.append("") + return "\n".join(xml_str) diff --git a/payment_wechat/static/description/icon.png b/payment_wechat/static/description/icon.png new file mode 100644 index 0000000000..b43a0a135f Binary files /dev/null and b/payment_wechat/static/description/icon.png differ diff --git a/payment_wechat/views/views.xml b/payment_wechat/views/views.xml new file mode 100644 index 0000000000..ab29778dbd --- /dev/null +++ b/payment_wechat/views/views.xml @@ -0,0 +1,14 @@ + + + + account.journal.form + account.journal + + + + + + + + +