Advertisement
AndrewTishkin

WMLogin by 342106157028

Jul 6th, 2014
666
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 86.63 KB | None | 0 0
  1. #!/usr/bin/python2.6
  2.  
  3. # This file is a part of Metagam project.
  4. #
  5. # Metagam is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # any later version.
  9. #
  10. # Metagam is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with Metagam.  If not, see <http://www.gnu.org/licenses/>.
  17.  
  18. from mg import *
  19. from mg.core.auth import *
  20. from concurrence import Timeout, TimeoutError
  21. from concurrence.http import HTTPConnection, HTTPError, HTTPRequest
  22. from uuid import uuid4
  23. from mg.core.money_classes import *
  24. from PIL import Image, ImageDraw, ImageEnhance, ImageFont, ImageOps, ImageFilter
  25. import cStringIO
  26. import xml.dom.minidom
  27. import hashlib
  28. import re
  29. import random
  30. import math
  31.  
  32. re_uuid_cmd = re.compile(r'^([0-9a-z]+)/(.+)$')
  33. re_valid_code = re.compile(r'^[A-Z]{2,5}$')
  34. re_questions = re.compile('\?')
  35. re_invalid_symbol = re.compile(r'([^\w \-\.,:/])', re.UNICODE)
  36. re_invalid_english_symbol = re.compile(r'([^a-zA-Z_ \-\.,:/])', re.UNICODE)
  37. re_valid_real_price = re.compile(r'[1-9]\d*(?:\.\d\d?|)$')
  38. re_decimal_comma = re.compile(',')
  39. re_valid_project_id = re.compile(r'^[a-z0-9]+$')
  40.  
  41. default_rates = {
  42.     "RUB": 1,
  43.     "USD": 28,
  44.     "EUR": 40,
  45.     "GBP": 45,
  46.     "KZT": 19 / 100,
  47.     "BYR": 56 / 10000,
  48.     "UAH": 34.7092 / 10,
  49. }
  50.  
  51. class DBXsollaActivationRequest(CassandraObject):
  52.     clsname = "XsollaActivationRequest"
  53.     indexes = {
  54.         "all": [[], "created"],
  55.         "project": [["project"]],
  56.     }
  57.  
  58. class DBXsollaActivationRequestList(CassandraObjectList):
  59.     objcls = DBXsollaActivationRequest
  60.  
  61. def getText(nodelist):
  62.     rc = ""
  63.     for node in nodelist:
  64.         if node.nodeType == node.TEXT_NODE:
  65.             rc = rc + node.data
  66.     return rc
  67.  
  68. class MoneyAdmin(Module):
  69.     def register(self):
  70.         self.rhook("ext-admin-money.give", self.admin_money_give, priv="users.money.give")
  71.         self.rhook("headmenu-admin-money.give", self.headmenu_money_give)
  72.         self.rhook("ext-admin-money.take", self.admin_money_take, priv="users.money.give")
  73.         self.rhook("headmenu-admin-money.take", self.headmenu_money_take)
  74.         self.rhook("permissions.list", self.permissions_list)
  75.         self.rhook("auth.user-tables", self.user_tables)
  76.         self.rhook("ext-admin-money.account", self.admin_money_account, priv="users.money")
  77.         self.rhook("headmenu-admin-money.account", self.headmenu_money_account)
  78.         self.rhook("admin-game.recommended-actions", self.recommended_actions)
  79.         self.rhook("ext-admin-money.currencies", self.admin_money_currencies, priv="money.currencies")
  80.         self.rhook("headmenu-admin-money.currencies", self.headmenu_money_currencies)
  81.         self.rhook("menu-admin-root.index", self.menu_root_index)
  82.         self.rhook("menu-admin-economy.index", self.menu_economy_index)
  83.         self.rhook("constructor.project-params", self.project_params)
  84.         self.rhook("objclasses.list", self.objclasses_list)
  85.         self.rhook("queue-gen.schedule", self.schedule)
  86.         self.rhook("admin-money.cleanup", self.cleanup)
  87.         self.rhook("admin-money.stats", self.stats)
  88.         self.rhook("admin-gameinterface.design-files", self.design_files)
  89.  
  90.     def design_files(self, files):
  91.         files.append({"filename": "money-accounts.html", "description": self._("List of money accounts"), "doc": "/doc/design/money"})
  92.         files.append({"filename": "money-operations.html", "description": self._("History of money operations"), "doc": "/doc/design/money"})
  93.  
  94.     def cleanup(self):
  95.         self.objlist(AccountOperationList, query_index="performed", query_finish=self.now(-86400 * 365 / 2)).remove()
  96.  
  97.     def stats(self):
  98.         today = self.nowdate()
  99.         yesterday = prev_date(today)
  100.         lst = self.objlist(AccountOperationList, query_index="performed", query_start=yesterday, query_finish=today)
  101.         lst.load(silent=True)
  102.         total = {}
  103.         descriptions = {}
  104.         for ent in lst:
  105.             currency = ent.get("currency")
  106.             amount = ent.get("amount")
  107.             # total amount
  108.             try:
  109.                 total[currency] += amount
  110.             except KeyError:
  111.                 total[currency] = amount
  112.             # descriptions
  113.             description = ent.get("description")
  114.             try:
  115.                 hsh = descriptions[description]
  116.             except KeyError:
  117.                 hsh = {}
  118.                 descriptions[description] = hsh
  119.             try:
  120.                 hsh[currency] += amount
  121.             except KeyError:
  122.                 hsh[currency] = amount
  123.         kwargs = {}
  124.         # this condition may be modified if we need to make remains recalculation more rare
  125.         if True:
  126.             remains = {}
  127.             lst = self.objlist(AccountList, query_index="all")
  128.             lst.load(silent=True)
  129.             for ent in lst:
  130.                 currency = ent.get("currency")
  131.                 balance = ent.get("balance")
  132.                 try:
  133.                     remains[currency] += balance
  134.                 except KeyError:
  135.                     remains[currency] = balance
  136.             kwargs["remains"] = remains
  137.         self.call("dbexport.add", "money_stats", total=total, descriptions=descriptions, date=yesterday, **kwargs)
  138.  
  139.     def schedule(self, sched):
  140.         sched.add("admin-money.cleanup", "20 1 1 * *", priority=5)
  141.         sched.add("admin-money.stats", "7 0 * * *", priority=10)
  142.  
  143.     def objclasses_list(self, objclasses):
  144.         objclasses["Account"] = (Account, AccountList)
  145.         objclasses["AccountLock"] = (AccountLock, AccountLockList)
  146.         objclasses["AccountOperation"] = (AccountOperation, AccountOperationList)
  147.  
  148.     def menu_root_index(self, menu):
  149.         menu.append({"id": "economy.index", "text": self._("Economy"), "order": 100})
  150.  
  151.     def menu_economy_index(self, menu):
  152.         req = self.req()
  153.         if req.has_access("money.currencies"):
  154.             menu.append({"id": "money/currencies", "text": self._("Currencies"), "leaf": True, "even_unpublished": True})
  155.  
  156.     def headmenu_money_currencies(self, args):
  157.         if args == "new" or args == "prenew":
  158.             return [self._("New currency"), "money/currencies"]
  159.         elif args:
  160.             return [args, "money/currencies"]
  161.         return self._("Currency editor")
  162.  
  163.     def admin_money_currencies(self):
  164.         req = self.req()
  165.         try:
  166.             project = self.app().project
  167.         except AttributeError:
  168.             project = None
  169.         with self.lock(["currencies"]):
  170.             currencies = {}
  171.             self.call("currencies.list", currencies)
  172.             lang = self.call("l10n.lang")
  173.             if req.args == "prenew":
  174.                 if req.ok():
  175.                     errors = {}
  176.                     name_en = req.param("name_en").strip().capitalize()
  177.                     name_local = req.param("name_local").strip().capitalize()
  178.                     if not name_local:
  179.                         errors["name_local"] = self._("Currency name is mandatory")
  180.                     if lang != "en":
  181.                         if not name_en:
  182.                             errors["name_en"] = self._("Currency name is mandatory")
  183.                     if len(errors):
  184.                         self.call("web.response_json", {"success": False, "errors": errors})
  185.                     name_en_s = re.sub(r's$', '', name_en)
  186.                     self.call("admin.redirect", "money/currencies/new", {
  187.                         "name_local": self.call("l10n.literal_values_sample", name_local),
  188.                         "name_plural": name_local,
  189.                         "name_en": u"{1}/{0}".format(name_en, name_en_s),
  190.                     })
  191.                 fields = []
  192.                 fields.append({"name": "name_local", "label": self._('Currency name (ex: Gold, Diamonds, Roubles, Dollars, Coins)')})
  193.                 if lang != "en":
  194.                     fields.append({"name": "name_en", "label": self._('Currency name in English (ex: Gold, Diamonds, Roubles, Dollars, Coins)')})
  195.                 self.call("admin.form", fields=fields)
  196.             elif req.args:
  197.                 if req.ok():
  198.                     self.call("web.upload_handler")
  199.                     code = req.param("code").strip()
  200.                     name_en = req.param("name_en").strip()
  201.                     name_local = req.param("name_local").strip()
  202.                     name_plural = req.param("name_plural").strip()
  203.                     description = req.param("description").strip()
  204.                     precision = intz(req.param("precision"))
  205.                     real = True if req.param("real") else False
  206.                     real_price = req.param("real_price")
  207.                     real_currency = req.param("v_real_currency")
  208.                     order = floatz(req.param("order"))
  209.                     # validating
  210.                     errors = {}
  211.                     errormsg = None
  212.                     if req.args == "new":
  213.                         if not code:
  214.                             errors["code"] = self._("Currency code is mandatory")
  215.                         elif len(code) < 2:
  216.                             errors["code"] = self._("Minimal length is 3 letters")
  217.                         elif len(code) > 5:
  218.                             errors["code"] = self._("Maximal length is 5 letters")
  219.                         elif not re_valid_code.match(code):
  220.                             errors["code"] = self._("Currency code must contain capital latin letters only")
  221.                         elif currencies.get(code):
  222.                             errors["code"] = self._("This currency name is busy")
  223.                         info = {}
  224.                         currencies[code] = info
  225.                     else:
  226.                         info = currencies.get(req.args)
  227.                     # allow not changed name_local when the project is published
  228.                     if name_local and project and (project.get("moderation") or project.get("published")) and info.get("real"):
  229.                         if name_local != info.get("name_local"):
  230.                             errors["name_local"] = self._("You can't change real money currency name after game publication")
  231.                         else:
  232.                             pass
  233.                     elif not name_local:
  234.                         errors["name_local"] = self._("Currency name is mandatory")
  235.                     elif not self.call("l10n.literal_values_valid", name_local):
  236.                         errors["name_local"] = self._("Invalid field format")
  237.                     elif re_questions.search(name_local):
  238.                         errors["name_local"] = self._("Replace '???' with correct endings")
  239.                     else:
  240.                         m = re_invalid_symbol.search(name_local)
  241.                         if m:
  242.                             sym = m.group(1)
  243.                             errors["name_local"] = self._("Invalid symbol: '%s'") % htmlescape(sym)
  244.                     if not name_plural:
  245.                         errors["name_plural"] = self._("Currency name is mandatory")
  246.                     elif re_questions.search(name_plural):
  247.                         errors["name_plural"] = self._("Replace '???' with correct endings")
  248.                     else:
  249.                         m = re_invalid_symbol.search(name_plural)
  250.                         if m:
  251.                             sym = m.group(1)
  252.                             errors["name_plural"] = self._("Invalid symbol: '%s'") % htmlescape(sym)
  253.                         elif project and (project.get("moderation") or project.get("published")) and name_plural != info.get("name_plural") and info.get("real"):
  254.                             errors["name_plural"] = self._("You can't change real money currency name after game publication")
  255.                     if lang != "en":
  256.                         if not name_en:
  257.                             errors["name_en"] = self._("Currency name is mandatory")
  258.                         elif re_questions.search(name_en):
  259.                             errors["name_en"] = self._("Replace '???' with correct endings")
  260.                         else:
  261.                             values = name_en.split("/")
  262.                             if len(values) != 2:
  263.                                 errors["name_en"] = self._("Invalid field format")
  264.                             else:
  265.                                 m = re_invalid_english_symbol.search(name_en)
  266.                                 if m:
  267.                                     sym = m.group(1)
  268.                                     errors["name_en"] = self._("Invalid symbol: '%s'") % htmlescape(sym)
  269.                                 elif project and (project.get("moderation") or project.get("published")) and name_en != info.get("name_en") and info.get("real"):
  270.                                     errors["name_en"] = self._("You can't change real money currency name after game publication")
  271.                                 else:
  272.                                     for val in values:
  273.                                         if val.lower() == "golds":
  274.                                             errors["name_en"] = self._("en///Gold in plural is 'Gold'")
  275.                     if precision < 0:
  276.                         errors["precision"] = self._("Precision can't be negative")
  277.                     elif precision > 4:
  278.                         errors["precision"] = self._("Maximal supported precision is 4")
  279.                     elif project and (project.get("moderation") or project.get("published")) and info.get("real") and precision != info.get("precision"):
  280.                         errors["precision"] = self._("You can't change real money currency precision after game publication")
  281.                     if real:
  282.                         for c, i in currencies.iteritems():
  283.                             if i.get("real") and c != req.args:
  284.                                 errormsg = self._("You already have a real money currency")
  285.                                 break
  286.                         if precision != 0 and precision != 2:
  287.                             errors["precision"] = self._("Real money currency must have precision 0 or 2 (limitation of the payment system)")
  288.                     else:
  289.                         if project and (project.get("moderation") or project.get("published")) and info.get("real"):
  290.                             errormsg = self._("You can't remove real money currency after game publication")
  291.                     if real:
  292.                         if not real_price:
  293.                             errors["real_price"] = self._("You must supply real money price for this currency")
  294.                         elif not re_valid_real_price.match(real_price):
  295.                             errors["real_price"] = self._("Invalid number format")
  296.                         elif project and (project.get("moderation") or project.get("published")) and info.get("real_price") and float(real_price) != float(info.get("real_price")):
  297.                             errors["real_price"] = self._("You can't change real money exchange rate after game publication")
  298.                         if real_currency not in ["RUB", "USD", "EUR", "UAH", "BYR", "GBP", "KZT"]:
  299.                             errors["v_real_currency"] = self._("Select real money currency")
  300.                         elif project and (project.get("moderation") or project.get("published")) and real_currency != info.get("real_currency"):
  301.                             errors["v_real_currency"] = self._("You can't change real money exchange rate after game publication")
  302.                         if "real_price" not in errors and "v_real_currency" not in errors:
  303.                             rate = self.stock_rate(real_currency)
  304.                             real_roubles = float(real_price) * rate
  305.                             min_step = real_roubles * (0.1 ** int(precision))
  306.                             if min_step > 50:
  307.                                 errors["real_price"] = self._("Minimal step of payment in this currency is {min_step:f} roubles (1 {currency} = {rate} RUB, precision = {precision}). It is too big for micropayments. Lower you currency rate").format(min_step=min_step, currency=real_currency, rate=rate, precision=0.1**precision)
  308.                     # images
  309.                     image_data = req.param_raw("image")
  310.                     image_obj = None
  311.                     if image_data:
  312.                         try:
  313.                             image_obj = Image.open(cStringIO.StringIO(image_data))
  314.                             if image_obj.load() is None:
  315.                                 raise IOError
  316.                         except IOError:
  317.                             errors["image"] = self._("Image format not recognized")
  318.                         except OverflowError:
  319.                             errors["image"] = self._("Image format not recognized")
  320.                         else:
  321.                             if image_obj.format == "GIF":
  322.                                 image_ext = "gif"
  323.                                 image_content_type = "image/gif"
  324.                             elif image_obj.format == "PNG":
  325.                                 image_ext = "png"
  326.                                 image_content_type = "image/png"
  327.                             elif image_obj.format == "JPEG":
  328.                                 image_ext = "jpg"
  329.                                 image_content_type = "image/jpeg"
  330.                             else:
  331.                                 errors["image"] = self._("Image format must be GIF, JPEG or PNG")
  332.                     icon_data = req.param_raw("icon")
  333.                     icon_obj = None
  334.                     if icon_data:
  335.                         try:
  336.                             icon_obj = Image.open(cStringIO.StringIO(icon_data))
  337.                             if icon_obj.load() is None:
  338.                                 raise IOError
  339.                         except IOError:
  340.                             errors["icon"] = self._("Image format not recognized")
  341.                         except OverflowError:
  342.                             errors["icon"] = self._("Image format not recognized")
  343.                         else:
  344.                             if icon_obj.format == "GIF":
  345.                                 icon_ext = "gif"
  346.                                 icon_content_type = "image/gif"
  347.                             elif icon_obj.format == "PNG":
  348.                                 icon_ext = "png"
  349.                                 icon_content_type = "image/png"
  350.                             elif icon_obj.format == "JPEG":
  351.                                 icon_ext = "jpg"
  352.                                 icon_content_type = "image/jpeg"
  353.                             else:
  354.                                 errors["icon"] = self._("Image format must be GIF, JPEG or PNG")
  355.                     if len(errors) or errormsg:
  356.                         self.call("web.response_json_html", {"success": False, "errors": errors, "errormsg": errormsg})
  357.                     # storing
  358.                     info["name_local"] = name_local
  359.                     info["name_plural"] = name_plural
  360.                     if lang == "en":
  361.                         info["name_en"] = name_local
  362.                     else:
  363.                         info["name_en"] = name_en
  364.                     info["precision"] = precision
  365.                     info["format"] = "%.{0}f".format(precision) if precision else "%d"
  366.                     info["description"] = description
  367.                     info["real"] = real
  368.                     if real:
  369.                         info["real_price"] = floatz(real_price)
  370.                         info["real_currency"] = real_currency
  371.                         info["real_roubles"] = real_roubles
  372.                     info["order"] = order
  373.                     # storing images
  374.                     old_images = []
  375.                     if image_obj:
  376.                         old_images.append(info.get("image"))
  377.                         info["image"] = self.call("cluster.static_upload", "currencies", image_ext, image_content_type, image_data)
  378.                     if icon_obj:
  379.                         old_images.append(info.get("icon"))
  380.                         info["icon"] = self.call("cluster.static_upload", "currencies", icon_ext, icon_content_type, icon_data)
  381.                     config = self.app().config_updater()
  382.                     config.set("money.currencies", currencies)
  383.                     config.store()
  384.                     for uri in old_images:
  385.                         if uri:
  386.                             self.call("cluster.static_delete", uri)
  387.                     self.call("web.response_json_html", {"success": True, "redirect": "money/currencies"})
  388.                 elif req.args == "new":
  389.                     description = ""
  390.                     real = False
  391.                     name_local = req.param("name_local")
  392.                     name_plural = req.param("name_plural")
  393.                     name_en = req.param("name_en")
  394.                     precision = 2
  395.                     real_price = 30
  396.                     real_currency = "RUB"
  397.                     order = None
  398.                     for c, v in currencies.iteritems():
  399.                         o = v.get("order", 0.0)
  400.                         if order is None or o > order:
  401.                             order = o
  402.                     if order is None:
  403.                         order = 0.0
  404.                     else:
  405.                         order += 10.0
  406.                 else:
  407.                     info = currencies.get(req.args)
  408.                     if not info:
  409.                         self.call("web.not_found")
  410.                     name_local = info.get("name_local")
  411.                     name_plural = info.get("name_plural")
  412.                     name_en = info.get("name_en")
  413.                     precision = info.get("precision")
  414.                     description = info.get("description")
  415.                     real = info.get("real")
  416.                     real_price = info.get("real_price")
  417.                     real_currency = info.get("real_currency")
  418.                     order = info.get("order", 0.0)
  419.                 fields = []
  420.                 if req.args == "new":
  421.                     fields.append({"name": "code", "label": self._('Currency code (for example, GLD for gold, SLVR for silver, DMND for diamonds and so on).<br /><span class="no">You won\'t have an ability to change the code later. Think twice before saving</span>')})
  422.                 fields.append({"name": "order", "label": self._("Sorting order"), "value": order, "inline": True})
  423.                 fields.append({"name": "name_local", "label": self._('Currency name: singular and plural forms delimited by "/". For example: "Dollar/Dollars", "Gold/Gold", "Coin/Coins", "Diamond/Diamonds", "Rouble/Roubles"'), "value": name_local})
  424.                 fields.append({"name": "name_plural", "label": self._('Currency name: plural form. For example: "Dollars", "Gold", "Coins", "Diamonds", "Roubles"'), "value": name_plural})
  425.                 if lang != "en":
  426.                     fields.append({"name": "name_en", "label": self._('Currency name in English: singular and plural forms delimited by "/". For example: "Dollar/Dollars", "Gold/Gold", "Coin/Coins", "Diamond/Diamonds", "Rouble/Roubles"'), "value": name_en})
  427.                 fields.append({"name": "precision", "label": self._("Values precision (number of digits after decimal point)"), "value": precision})
  428.                 fields.append({"name": "description", "label": self._("Currency description"), "type": "textarea", "value": description})
  429.                 fields.append({"name": "real", "label": self._("Real money. Set this checkbox if this currency is sold for real money. Your game must have one real money currency"), "type": "checkbox", "checked": real})
  430.                 fields.append({"name": "real_price", "label": self._("Real money price for 1 unit of the currency"), "value": real_price, "condition": "[real]"})
  431.                 fields.append({"name": "real_currency", "type": "combo", "label": self._("Real money currency"), "value": real_currency, "condition": "[real]", "values": [("RUB", "RUB"), ("USD", "USD"), ("EUR", "EUR"), ("UAH", "UAH"), ("BYR", "BYR"), ("GBP", "GBP"), ("KZT", "KZT")]})
  432.                 fields.append({"name": "image", "label": self._("Currency image (approx 60x60)"), "type": "fileuploadfield"})
  433.                 fields.append({"name": "icon", "label": self._("Currency icon (approx 16x16)"), "type": "fileuploadfield"})
  434.                 self.call("admin.form", fields=fields, modules=["FileUploadField"])
  435.             else:
  436.                 rows = []
  437.                 for code, info in sorted(currencies.iteritems(), cmp=lambda x, y: cmp(x[1].get("order", 0.0), y[1].get("order", 0.0)) or cmp(x[0], y[0])):
  438.                     real = '<center>%s</center>' % ('<img src="/st/img/coins-16x16.png" alt="" /><br />%s %s' % (info.get("real_price"), info.get("real_currency")) if info.get("real") else '-')
  439.                     declensions = []
  440.                     for i in (0, 1, 2, 5, 10, 21, 0.1):
  441.                         declensions.append("<nobr>%s %s</nobr>" % (i, self.call("l10n.literal_value", i, info.get("name_local"))))
  442.                     code = '<hook:admin.link href="money/currencies/{0}" title="{0}" />'.format(code)
  443.                     if info.get("icon"):
  444.                         code += ' <img src="%s" alt="" class="inline-icon" />' % info["icon"]
  445.                     name = info.get("name_plural")
  446.                     if info.get("image"):
  447.                         name += '<br /><img src="%s" alt="" />' % info["image"]
  448.                     rows.append([code, name, real, ", ".join(declensions)])
  449.                 vars = {
  450.                     "tables": [
  451.                         {
  452.                             "links": [
  453.                                 {
  454.                                     "hook": "money/currencies/prenew",
  455.                                     "text": self._("New currency"),
  456.                                     "lst": True,
  457.                                 }
  458.                             ],
  459.                             "header": [self._("Currency code"), self._("Currency name"), self._("Real money"), self._("Declension samples")],
  460.                             "header_nowrap": True,
  461.                             "rows": rows
  462.                         }
  463.                     ]
  464.                 }
  465.                 self.call("admin.response_template", "admin/common/tables.html", vars)
  466.  
  467.     def headmenu_money_give(self, args):
  468.         try:
  469.             user = self.obj(User, args)
  470.         except ObjectNotFoundException:
  471.             return
  472.         return [self._("Give money"), "auth/user-dashboard/%s?active_tab=money" % args]
  473.  
  474.     def admin_money_give(self):
  475.         req = self.req()
  476.         try:
  477.             user = self.obj(User, req.args)
  478.         except ObjectNotFoundException:
  479.             self.call("web.not_found")
  480.         currencies = {}
  481.         self.call("currencies.list", currencies)
  482.         amount = req.param("amount")
  483.         currency = req.param("v_currency")
  484.         if req.param("ok"):
  485.             errors = {}
  486.             self.call("money.valid_amount", amount, currency, errors, "amount", "v_currency")
  487.             user_comment = req.param("user_comment").strip()
  488.             admin_comment = req.param("admin_comment").strip()
  489.             if not admin_comment:
  490.                 errors["admin_comment"] = self._("This field is mandatory")
  491.             if len(errors):
  492.                 self.call("web.response_json", {"success": False, "errors": errors})
  493.             amount = float(amount)
  494.             member = MemberMoney(self.app(), "user", user.uuid)
  495.             member.credit(amount, currency, "admin-give", admin=req.user(), comment=user_comment)
  496.             self.call("security.suspicion", admin=req.user(), action="money.give", member=user.uuid, amount=amount, currency=currency, comment=admin_comment)
  497.             self.call("dossier.write", user=user.uuid, admin=req.user(), content=self._("Given {money_amount}: {comment}").format(money_amount=self.call("money.price-text", amount, currency), comment=admin_comment))
  498.             self.call("admin.redirect", "auth/user-dashboard/%s" % user.uuid, {"active_tab": "money"})
  499.         else:
  500.             amount = "0"
  501.         fields = []
  502.         fields.append({"name": "amount", "label": self._("Give amount"), "value": amount})
  503.         fields.append({"name": "currency", "label": self._("Currency"), "type": "combo", "value": currency, "values": [(code, info["name_plural"]) for code, info in currencies.iteritems()]})
  504.         fields.append({"name": "user_comment", "label": self._("Comment for the user (can be empty)")})
  505.         fields.append({"name": "admin_comment", "label": '%s%s' % (self._("Reason why do you give money to the user. Provide the real reason. It will be inspected by the MMO Constructor Security Dept"), self.call("security.icon") or "")})
  506.         buttons = [{"text": self._("Give")}]
  507.         self.call("admin.form", fields=fields, buttons=buttons)
  508.  
  509.     def headmenu_money_take(self, args):
  510.         try:
  511.             user = self.obj(User, args)
  512.         except ObjectNotFoundException:
  513.             return
  514.         return [self._("Take money"), "auth/user-dashboard/%s?active_tab=money" % args]
  515.  
  516.     def admin_money_take(self):
  517.         req = self.req()
  518.         try:
  519.             user = self.obj(User, req.args)
  520.         except ObjectNotFoundException:
  521.             self.call("web.not_found")
  522.         currencies = {}
  523.         self.call("currencies.list", currencies)
  524.         amount = req.param("amount")
  525.         currency = req.param("v_currency")
  526.         if req.param("ok"):
  527.             errors = {}
  528.             currency_info = currencies.get(currency)
  529.             if currency_info is None:
  530.                 errors["v_currency"] = self._("Invalid currency")
  531.             try:
  532.                 amount = float(amount)
  533.                 if amount <= 0:
  534.                     errors["amount"] = self._("money///Amount must be greater than 0")
  535.                 elif currency_info is not None and amount != float(currency_info["format"] % amount):
  536.                     errors["amount"] = self._("money///Invalid amount precision")
  537.             except ValueError:
  538.                 errors["amount"] = self._("Invalid number format")
  539.             user_comment = req.param("user_comment").strip()
  540.             admin_comment = req.param("admin_comment").strip()
  541.             if not admin_comment:
  542.                 errors["admin_comment"] = self._("This field is mandatory")
  543.             if len(errors):
  544.                 self.call("web.response_json", {"success": False, "errors": errors})
  545.             member = MemberMoney(self.app(), "user", user.uuid)
  546.             member.force_debit(amount, currency, "admin-take", admin=req.user(), comment=user_comment)
  547.             self.call("security.suspicion", admin=req.user(), action="money.take", member=user.uuid, amount=amount, currency=currency, comment=admin_comment)
  548.             self.call("dossier.write", user=user.uuid, admin=req.user(), content=self._("Taken {money_amount}: {comment}").format(money_amount=self.call("money.price-text", amount, currency), comment=admin_comment))
  549.             self.call("admin.redirect", "auth/user-dashboard/%s" % user.uuid, {"active_tab": "money"})
  550.         else:
  551.             amount = "0"
  552.         fields = []
  553.         fields.append({"name": "amount", "label": self._("Take amount"), "value": amount})
  554.         fields.append({"name": "currency", "label": self._("Currency"), "type": "combo", "value": currency, "values": [(code, info["name_plural"]) for code, info in currencies.iteritems()]})
  555.         fields.append({"name": "user_comment", "label": self._("Comment for the user (can be empty)")})
  556.         fields.append({"name": "admin_comment", "label": '%s%s' % (self._("Reason why do you give money to the user. Provide the real reason. It will be inspected by the MMO Constructor Security Dept"), self.call("security.icon") or "")})
  557.         buttons = [{"text": self._("Take")}]
  558.         self.call("admin.form", fields=fields, buttons=buttons)
  559.  
  560.     def headmenu_money_account(self, args):
  561.         try:
  562.             acc = self.obj(Account, args)
  563.         except ObjectNotFoundException:
  564.             return
  565.         return [self._("Account %s") % acc.get("currency"), "auth/user-dashboard/%s?active_tab=money" % acc.get("member")]
  566.  
  567.     def admin_money_account(self):
  568.         req = self.req()
  569.         try:
  570.             account = self.obj(Account, req.args)
  571.         except ObjectNotFoundException:
  572.             return
  573.         currencies = {}
  574.         self.call("currencies.list", currencies)
  575.         operations = []
  576.         lst = self.objlist(AccountOperationList, query_index="account", query_equal=account.uuid, query_reversed=True)
  577.         lst.load(silent=True)
  578.         for op in lst:
  579.             if op.get("override"):
  580.                 rdescription = op.get("override")
  581.             else:
  582.                 rdescription = op.get("description")
  583.                 description = self.call("money-description.%s" % rdescription)
  584.                 if description:
  585.                     if callable(description["text"]):
  586.                         rdescription = description["text"](op.data)
  587.                     else:
  588.                         watchdog = 0
  589.                         while True:
  590.                             watchdog += 1
  591.                             if watchdog >= 100:
  592.                                 break
  593.                             try:
  594.                                 rdescription = description["text"].format(**op.data)
  595.                             except KeyError as e:
  596.                                 op.data[e.args[0]] = "{%s}" % e.args[0]
  597.                             else:
  598.                                 break
  599.             if op.get("comment"):
  600.                 rdescription = "%s: %s" % (rdescription, htmlescape(op.get("comment")))
  601.             operations.append({
  602.                 "performed": self.call("l10n.time_local", op.get("performed")),
  603.                 "amount": op.get("amount"),
  604.                 "balance": op.get("balance"),
  605.                 "description": rdescription,
  606.             })
  607.         vars = {
  608.             "Performed": self._("Performed"),
  609.             "Amount": self._("Amount"),
  610.             "Balance": self._("Balance"),
  611.             "Description": self._("Description"),
  612.             "operations": operations,
  613.             "Update": self._("Update"),
  614.             "account": {
  615.                 "uuid": account.uuid
  616.             }
  617.         }
  618.         self.call("admin.response_template", "admin/money/account.html", vars)
  619.  
  620.     def user_tables(self, user, tables):
  621.         req = self.req()
  622.         if req.has_access("users.money"):
  623.             member = MemberMoney(self.app(), "user", user.uuid)
  624.             links = []
  625.             if req.has_access("users.money.give"):
  626.                 links.append({"hook": "money/give/%s" % user.uuid, "text": self._("Give money")})
  627.                 links.append({"hook": "money/take/%s" % user.uuid, "text": self._("Take money"), "lst": True})
  628.             tbl = {
  629.                 "type": "money",
  630.                 "title": self._("Money"),
  631.                 "order": 20,
  632.                 "links": links,
  633.                 "header": [self._("Account Id"), self._("Currency"), self._("Balance"), self._("Locked"), self._("Low limit")],
  634.                 "rows": [('<hook:admin.link href="money/account/{0}" title="{0}" />'.format(a.uuid), a.get("currency"), a.get("balance"), a.get("locked"), a.get("low_limit")) for a in member.accounts]
  635.             }
  636.             tables.append(tbl)
  637.             if len(member.locks):
  638.                 rows = []
  639.                 for l in member.locks:
  640.                     description_info = member.description(l.get("description"))
  641.                     if description_info:
  642.                         desc = description_info["text"] % l.data
  643.                     else:
  644.                         desc = l.get("description")
  645.                     rows.append((l.uuid, l.get("amount"), l.get("currency"), desc))
  646.                 tbl = {
  647.                     "type": "money_locks",
  648.                     "title": self._("Money locks"),
  649.                     "order": 21,
  650.                     "header": [self._("Lock ID"), self._("Amount"), self._("Currency"), self._("Description")],
  651.                     "rows": rows
  652.                 }
  653.                 tables.append(tbl)
  654.  
  655.     def permissions_list(self, perms):
  656.         perms.append({"id": "users.money", "name": self._("Access to users money")})
  657.         perms.append({"id": "users.money.give", "name": self._("Giving and taking money")})
  658.         perms.append({"id": "money.currencies", "name": self._("Currencies editor")})
  659.  
  660.     def recommended_actions(self, recommended_actions):
  661.         req = self.req()
  662.         if req.has_access("money.currencies"):
  663.             if not self.call("money.real-currency"):
  664.                 recommended_actions.append({"icon": "/st/img/coins.png", "content": u'%s <hook:admin.link href="money/currencies" title="%s" />' % (self._("You have not configured real money currency yet. Before launching your game you must configure its real money system&nbsp;&mdash; set up a currency and set it the 'real money' attribute."), self._("Open currency settings")), "order": 90, "before_launch": True})
  665.  
  666.     def project_params(self, params):
  667.         currencies = {}
  668.         self.call("currencies.list", currencies)
  669.         real_ok = False
  670.         for code, cur in currencies.iteritems():
  671.             if cur.get("real"):
  672.                 params.append({"name": self._("Real money name"), "value": cur.get("name_plural"), "moderated": True, "edit": "money/currencies", "rowspan": 4})
  673.                 params.append({"name": self._("Declensions"), "value": cur.get("name_local")})
  674.                 params.append({"name": self._("Real in English"), "value": cur.get("name_en"), "moderated": True})
  675.                 params.append({"name": self._("Exchange rate"), "value": "1 %s = %s %s" % (code, cur.get("real_price"), cur.get("real_currency")), "moderated": True})
  676.                 params.append({"name": self._("Exchange to roubles"), "value": "1 %s = %s RUB" % (code, cur.get("real_roubles")), "moderated": True})
  677.                 real_ok = True
  678.         if not real_ok:
  679.             params.append({"name": self._("Real money currency name"), "value": '<span class="no">%s</span>' % self._("absent"), "moderated": True})
  680.  
  681.     def stock_rate(self, currency):
  682.         rates = self.stock_rates()
  683.         if rates and currency in rates:
  684.             return rates[currency]
  685.         return default_rates.get(currency, 1)
  686.  
  687.     def stock_rates(self):
  688.         rates = self.main_app().mc.get("cbr-stock-rates")
  689.         if rates:
  690.             return rates
  691.         try:
  692.             data = self.download("http://www.cbr.ru/scripts/XML_daily.asp")
  693.         except DownloadError:
  694.             return None
  695.         rates = {}
  696.         response = xml.dom.minidom.parseString(data)
  697.         if response.documentElement.tagName == "ValCurs":
  698.             valutes = response.documentElement.getElementsByTagName("Valute")
  699.             for valute in valutes:
  700.                 code = None
  701.                 rate = None
  702.                 nominal = None
  703.                 for param in valute.childNodes:
  704.                     if param.nodeType == xml.dom.minidom.Node.ELEMENT_NODE:
  705.                         if param.tagName == "CharCode":
  706.                             code = getText(param.childNodes)
  707.                         elif param.tagName == "Value":
  708.                             rate = float(re_decimal_comma.sub('.', getText(param.childNodes)))
  709.                         elif param.tagName == "Nominal":
  710.                             nominal = float(re_decimal_comma.sub('.', getText(param.childNodes)))
  711.                 if code and rate and nominal and code in default_rates:
  712.                     rates[code] = rate / nominal
  713.         self.debug("Loaded CBR stock rates: %s", [rates])
  714.         self.main_app().mc.set("cbr-stock-rates", rates)
  715.         return rates
  716.  
  717. class Xsolla(Module):
  718.     def register(self):
  719.         self.rhook("ext-ext-payment.2pay", self.payment_xsolla, priv="public")
  720.         self.rhook("ext-ext-payment.xsolla", self.payment_xsolla, priv="public")
  721.         self.rhook("money-description.xsolla-pay", self.money_description_xsolla_pay)
  722.         self.rhook("money-description.xsolla-chargeback", self.money_description_xsolla_chargeback)
  723.         self.rhook("objclasses.list", self.objclasses_list)
  724.         self.rhook("xsolla.payport-params", self.payport_params)
  725.         self.rhook("xsolla.payment-params", self.payment_params)
  726.         self.rhook("xsolla.register", self.register_xsolla)
  727.         self.rhook("gameinterface.render", self.gameinterface_render)
  728.         self.rhook("money.not-enough-funds", self.not_enough_funds, priority=10)
  729.         self.rhook("money.donate-message", self.donate_message)
  730.         self.rhook("money.donate-url", self.donate_url)
  731.         self.rhook("constructor.project-options", self.project_options)
  732.         self.rhook("xsolla.check-activation", self.check)
  733.         self.rhook("xsolla.send-activation-request", self.send_activation_request)
  734.  
  735.     def project_options(self, options):
  736.         if self.req().has_access("constructor.projects-xsolla"):
  737.             options.append({"title": self._("Xsolla integration"), "value": '<hook:admin.link href="constructor/project-xsolla/%s" title="%s" />' % (self.app().tag, self._("open dashboard"))})
  738.  
  739.     def append_args(self, options):
  740.         args = {}
  741.         self.call("xsolla.payment-args", args, options)
  742.         for key in ["v1", "email", "amount"]:
  743.             if key not in args and key in options:
  744.                 args[key] = options[key]
  745.         append = ""
  746.         for key, val in args.iteritems():
  747.             if type(val) == unicode:
  748.                 val = val.encode("cp1251")
  749.             elif type(val) != str:
  750.                 val = str(val)
  751.             append += '&%s=%s' % (key, urlencode(val))
  752.         return append
  753.  
  754.     def donate_message(self, currency, **kwargs):
  755.         project_id = intz(self.conf("xsolla.project-id"))
  756.         if project_id:
  757.             cinfo = self.call("money.currency-info", currency)
  758.             if cinfo and cinfo.get("real"):
  759.                 return '<a href="//2pay.ru/oplata/?id=%d%s" target="_blank" onclick="try { parent.Xsolla.paystation(); return false; } catch (e) { return true; }">%s</a>' % (project_id, self.append_args(kwargs), self._("Open payment interface"))
  760.  
  761.     def donate_url(self, currency, **kwargs):
  762.         project_id = intz(self.conf("xsolla.project-id"))
  763.         if project_id:
  764.             cinfo = self.call("money.currency-info", currency)
  765.             if cinfo and cinfo.get("real"):
  766.                 return '//2pay.ru/oplata/?id=%d%s' % (project_id, self.append_args(kwargs))
  767.  
  768.     def not_enough_funds(self, currency, **kwargs):
  769.         project_id = intz(self.conf("xsolla.project-id"))
  770.         if project_id:
  771.             cinfo = self.call("money.currency-info", currency)
  772.             if cinfo and cinfo.get("real"):
  773.                 raise Hooks.Return('%s <a href="//2pay.ru/oplata/?id=%d%s" target="_blank" onclick="try { parent.Xsolla.paystation(); return false; } catch (e) { return true; }">%s</a>' % (self._("Not enough %s.") % (self.call("l10n.literal_value", 100, cinfo.get("name_local")) if cinfo else htmlescape(currency)), project_id, self.append_args(kwargs), self._("Open payment interface")))
  774.  
  775.     def money_description_xsolla_pay(self):
  776.         return {
  777.             "args": ["payment_id", "payment_performed"],
  778.             "text": self._("Xsolla payment"),
  779.         }
  780.  
  781.     def money_description_xsolla_chargeback(self):
  782.         return {
  783.             "args": ["payment_id"],
  784.             "text": self._("Xsolla chargeback"),
  785.         }
  786.  
  787.     def objclasses_list(self, objclasses):
  788.         objclasses["PaymentXsolla"] = (PaymentXsolla, PaymentXsollaList)
  789.  
  790.     def payment_xsolla(self):
  791.         req = self.req()
  792.         if req.args:
  793.             if re_valid_project_id.match(req.args):
  794.                 app = self.inst.appfactory.get_by_tag(req.args)
  795.                 if not app:
  796.                     self.call("web.not_found")
  797.             else:
  798.                 self.call("web.not_found")
  799.         else:
  800.             app = self.app()
  801.         command = req.param_raw("command")
  802.         sign = req.param_raw("md5")
  803.         result = None
  804.         comment = None
  805.         id = None
  806.         id_shop = None
  807.         sum = None
  808.         self.debug("Xsolla Request: %s", [req.param_dict()])
  809.         try:
  810.             secret = app.config.get("xsolla.secret")
  811.             if type(secret) == unicode:
  812.                 secret = secret.encode("windows-1251")
  813.             if secret is None or secret == "":
  814.                 result = 5
  815.                 comment = "Payments are not accepted for this project"
  816.             elif command == "check":
  817.                 v1 = req.param_raw("v1")
  818.                 if sign is None or sign.lower() != hashlib.md5(command + v1 + secret).hexdigest().lower():
  819.                     result = 3
  820.                     comment = "Invalid MD5 signature"
  821.                 else:
  822.                     v1 = v1.decode("windows-1251")
  823.                     self.debug("Xsolla Request: command=check, v1=%s", v1)
  824.                     if app.call("session.find_user", v1):
  825.                         result = 0
  826.                     else:
  827.                         result = 2
  828.             elif command == "pay":
  829.                 id = req.param_raw("id")
  830.                 sum = req.param_raw("sum")
  831.                 date = req.param_raw("date")
  832.                 v1 = req.param_raw("v1")
  833.                 if sign is None or sign.lower() != hashlib.md5(command + v1 + id + secret).hexdigest().lower():
  834.                     result = 3
  835.                     comment = "Invalid MD5 signature"
  836.                 else:
  837.                     v1 = v1.decode("windows-1251")
  838.                     sum_v = float(sum)
  839.                     self.debug("Xsolla Request: command=pay, id=%s, v1=%s, sum=%s, date=%s", id, v1, sum, date)
  840.                     user = app.call("session.find_user", v1)
  841.                     if user:
  842.                         with app.lock(["PaymentXsolla.%s" % id]):
  843.                             try:
  844.                                 existing = app.obj(PaymentXsolla, id)
  845.                                 result = 0
  846.                                 id_shop = id
  847.                                 sum = str(existing.get("sum"))
  848.                             except ObjectNotFoundException:
  849.                                 currency = app.call("money.real-currency")
  850.                                 cinfo = app.call("money.currency-info", currency)
  851.                                 amount_rub = floatz(req.param("transfer_sum"))
  852. #                               amount_rub = cinfo.get("real_roubles", 1) * sum_v * 0.9
  853.                                 payment = app.obj(PaymentXsolla, id, data={})
  854.                                 payment.set("v1", v1)
  855.                                 payment.set("user", user.uuid)
  856.                                 payment.set("sum", sum_v)
  857.                                 payment.set("date", date)
  858.                                 payment.set("performed", self.now())
  859.                                 payment.set("amount_rub", amount_rub)
  860.                                 member = MemberMoney(app, "user", user.uuid)
  861.                                 member.credit(sum_v, currency, "xsolla-pay", payment_id=id, payment_performed=date)
  862.                                 payment.store()
  863.                                 app.call("dbexport.add", "donate", user=user.uuid, amount=amount_rub)
  864.                                 result = 0
  865.                                 id_shop = id
  866.                     else:
  867.                         result = 2
  868.             elif command == "cancel":
  869.                 id = req.param_raw("id")
  870.                 if sign is None or sign.lower() != hashlib.md5(command + id + secret).hexdigest().lower():
  871.                     result = 3
  872.                     comment = "Invalid MD5 signature"
  873.                 else:
  874.                     self.debug("Xsolla Request: command=cancel, id=%s", id)
  875.                     with app.lock(["PaymentXsolla.%s" % id]):
  876.                         try:
  877.                             payment = app.obj(PaymentXsolla, id)
  878.                             if payment.get("cancelled"):
  879.                                 result = 0
  880.                             else:
  881.                                 payment.set("cancelled", self.now())
  882.                                 member = MemberMoney(app, "user", payment.get("user"))
  883.                                 member.force_debit(payment.get("sum"), app.call("money.real-currency"), "xsolla-chargeback", payment_id=id)
  884.                                 payment.store()
  885.                                 app.call("dbexport.add", "chargeback", user=payment.get("user"), amount=payment.get("amount_rub", 0))
  886.                                 result = 0
  887.                         except ObjectNotFoundException:
  888.                             result = 2
  889.                     result = 0
  890.                     id = None
  891.             elif command is None:
  892.                 result = 4
  893.                 comment = "Command not supplied"
  894.             else:
  895.                 self.debug("Xsolla Request: command=%s", command)
  896.                 result = 4
  897.                 comment = "This command is not implemented"
  898.         except Exception as e:
  899.             result = 1
  900.             comment = str(e)
  901.         doc = xml.dom.minidom.getDOMImplementation().createDocument(None, "response", None)
  902.         response = doc.documentElement
  903.         if id is not None:
  904.             elt = doc.createElement("id")
  905.             elt.appendChild(doc.createTextNode(id))
  906.             response.appendChild(elt)
  907.         if id_shop is not None:
  908.             elt = doc.createElement("id_shop")
  909.             elt.appendChild(doc.createTextNode(id_shop))
  910.             response.appendChild(elt)
  911.         if sum is not None:
  912.             elt = doc.createElement("sum")
  913.             elt.appendChild(doc.createTextNode(sum))
  914.             response.appendChild(elt)
  915.         if result is not None:
  916.             elt = doc.createElement("result")
  917.             elt.appendChild(doc.createTextNode(str(result)))
  918.             response.appendChild(elt)
  919.         if comment is not None:
  920.             elt = doc.createElement("comment")
  921.             elt.appendChild(doc.createTextNode(comment))
  922.             response.appendChild(elt)
  923.         self.debug("Xsolla Response: %s", response.toxml("utf-8"))
  924.         self.call("web.response", doc.toxml("windows-1251"), "application/xml")
  925.  
  926.     def payport_params(self, params, owner_uuid):
  927.         payport = {}
  928.         try:
  929.             owner = self.obj(User, owner_uuid)
  930.         except ObjectNotFoundException:
  931.             owner = None
  932.         else:
  933.             payport["email"] = jsencode(owner.get("email"))
  934.             payport["name"] = jsencode(owner.get("name"))
  935.         payport["project_id"] = self.conf("xsolla.project-id")
  936.         payport["language"] = {"ru": 0, "fr": 2}.get(self.call("l10n.lang"), 1)
  937.         params["xsolla_payport"] = payport
  938.  
  939.     def payment_params(self, params, owner_uuid):
  940.         payment = {}
  941.         try:
  942.             owner = self.obj(User, owner_uuid)
  943.         except ObjectNotFoundException:
  944.             owner = None
  945.         else:
  946.             payment["email"] = urlencode(owner.get("email").encode("windows-1251"))
  947.             payment["name"] = urlencode(owner.get("name").encode("windows-1251"))
  948.         payment["project_id"] = self.conf("xsolla.project-id")
  949.         payment["language"] = {"ru": 0, "fr": 2}.get(self.call("l10n.lang"), 1)
  950.         params["xsolla_payment"] = payment
  951.  
  952.     def register_xsolla(self):
  953.         self.info("Registering in the Xsolla system")
  954.         project = self.app().project
  955.         lang = self.call("l10n.lang")
  956.         doc = xml.dom.minidom.getDOMImplementation().createDocument(None, "response", None)
  957.         request = doc.documentElement
  958.         # Master ID
  959.         master_id = str(self.main_app().config.get("xsolla.contragent-id"))
  960.         elt = doc.createElement("id")
  961.         elt.appendChild(doc.createTextNode(master_id))
  962.         request.appendChild(elt)
  963.         # Project title
  964.         elt = doc.createElement("name")
  965.         elt.setAttribute("loc", lang)
  966.         elt.appendChild(doc.createTextNode(project.get("title_short")))
  967.         request.appendChild(elt)
  968.         if lang != "en":
  969.             elt = doc.createElement("name")
  970.             elt.setAttribute("loc", "en")
  971.             elt.appendChild(doc.createTextNode(project.get("title_en")))
  972.             request.appendChild(elt)
  973.         # Currencies setup
  974.         currencies = {}
  975.         self.call("currencies.list", currencies)
  976.         real_ok = False
  977.         for code, cur in currencies.iteritems():
  978.             if cur.get("real"):
  979.                 # Currency name
  980.                 elt = doc.createElement("currency")
  981.                 elt.setAttribute("loc", lang)
  982.                 elt.appendChild(doc.createTextNode(cur.get("name_plural")))
  983.                 request.appendChild(elt)
  984.                 if lang != "en":
  985.                     elt = doc.createElement("currency")
  986.                     elt.setAttribute("loc", "en")
  987.                     elt.appendChild(doc.createTextNode(cur.get("name_en").split("/")[1]))
  988.                     request.appendChild(elt)
  989.                 # Currency precision
  990.                 elt = doc.createElement("natur")
  991.                 elt.appendChild(doc.createTextNode("0" if cur.get("precision") else "1"))
  992.                 request.appendChild(elt)
  993.                 # Currency rate
  994.                 elt = doc.createElement("price")
  995.                 elt.appendChild(doc.createTextNode(str(cur.get("real_price"))))
  996.                 request.appendChild(elt)
  997.                 elt = doc.createElement("valuta")
  998.                 currencies = {
  999.                     "RUB": "1",
  1000.                     "USD": "2",
  1001.                     "EUR": "3",
  1002.                     "UAH": "4",
  1003.                     "BYR": "5",
  1004.                     "GBP": "7",
  1005.                     "KZT": "77",
  1006.                 }
  1007.                 currency = currencies.get(cur.get("real_currency"), "1")
  1008.                 elt.appendChild(doc.createTextNode(currency))
  1009.                 request.appendChild(elt)
  1010.                 # Minimal and maximal amount
  1011.                 elt = doc.createElement("min")
  1012.                 elt.appendChild(doc.createTextNode(str(0.1 ** cur.get("precision"))))
  1013.                 request.appendChild(elt)
  1014.                 elt = doc.createElement("max")
  1015.                 elt.appendChild(doc.createTextNode("0"))
  1016.                 request.appendChild(elt)
  1017.                 break
  1018.         # Enter character name
  1019.         elt = doc.createElement("v0")
  1020.         elt.setAttribute("loc", lang)
  1021.         elt.appendChild(doc.createTextNode(self._("Enter character name:")))
  1022.         request.appendChild(elt)
  1023.         if lang != "en":
  1024.             elt = doc.createElement("v0")
  1025.             elt.setAttribute("loc", "en")
  1026.             elt.appendChild(doc.createTextNode("Enter character name:"))
  1027.             request.appendChild(elt)
  1028.         # Character name
  1029.         elt = doc.createElement("v1")
  1030.         elt.setAttribute("loc", lang)
  1031.         elt.appendChild(doc.createTextNode(self._("Character name:")))
  1032.         request.appendChild(elt)
  1033.         if lang != "en":
  1034.             elt = doc.createElement("v1")
  1035.             elt.setAttribute("loc", "en")
  1036.             elt.appendChild(doc.createTextNode("Character name:"))
  1037.             request.appendChild(elt)
  1038.         # Secret key
  1039.         secret = uuid4().hex
  1040.         elt = doc.createElement("secretKey")
  1041.         elt.appendChild(doc.createTextNode(secret))
  1042.         request.appendChild(elt)
  1043.         # Random number
  1044.         rnd = str(random.randrange(0, 1000000000))
  1045.         elt = doc.createElement("randomNumber")
  1046.         elt.appendChild(doc.createTextNode(rnd))
  1047.         request.appendChild(elt)
  1048.         # url
  1049.         elt = doc.createElement("url")
  1050.         elt.appendChild(doc.createTextNode(self.app().canonical_domain))
  1051.         request.appendChild(elt)
  1052.         # imageURL
  1053.         elt = doc.createElement("imageURL")
  1054.         elt.appendChild(doc.createTextNode(project.get("logo")))
  1055.         request.appendChild(elt)
  1056.         # payURL
  1057.         elt = doc.createElement("payUrl")
  1058.         elt.appendChild(doc.createTextNode("http://www.%s/ext-payment/xsolla/%s" % (self.main_host, self.app().tag)))
  1059.         request.appendChild(elt)
  1060.         ## Description
  1061.         #elt = doc.createElement("desc")
  1062.         #elt.appendChild(doc.createTextNode(self.conf("gameprofile.description")))
  1063.         #request.appendChild(elt)
  1064.         xmldata = request.toxml("utf-8")
  1065.         self.debug(u"Xsolla request: %s", xmldata)
  1066.         # Signature
  1067.         sign_str = str("%s%s%s") % (master_id, rnd, self.main_app().config.get("xsolla.secret-addgame"))
  1068.         sign = hashlib.md5(sign_str).hexdigest().lower()
  1069.         self.debug(u"Xsolla signing string '%s': %s", sign_str, sign)
  1070.         query = "xml=%s&sign=%s" % (urlencode(xmldata), urlencode(sign))
  1071.         self.debug(u"Xsolla urlencoded query: %s", query)
  1072.         # Server
  1073.         xsolla_api_gate = self.clconf("xsolla_api_gate", "localhost:89").split(":")
  1074.         host = str(xsolla_api_gate[0])
  1075.         port = int(xsolla_api_gate[1])
  1076.         try:
  1077.             with Timeout.push(90):
  1078.                 cnn = HTTPConnection()
  1079.                 cnn.connect((host, port))
  1080.                 try:
  1081.                     request = HTTPRequest()
  1082.                     request.method = "POST"
  1083.                     request.path = "/game/index.php"
  1084.                     request.host = "api.xsolla.com"
  1085.                     request.body = query
  1086.                     request.add_header("Content-type", "application/x-www-form-urlencoded; charset=utf-8")
  1087.                     request.add_header("Connection", "close")
  1088.                     response = cnn.perform(request)
  1089.                     self.debug(u"Xsolla response: %s %s", response.status_code, response.body)
  1090.                     if response.status_code == 200:
  1091.                         response = xml.dom.minidom.parseString(response.body)
  1092.                         if response.documentElement.tagName == "response":
  1093.                             result = response.documentElement.getElementsByTagName("result")
  1094.                             if result and getText(result[0].childNodes) == "OK":
  1095.                                 game_id = response.documentElement.getElementsByTagName("gameId")
  1096.                                 if game_id:
  1097.                                     game_id = getText(game_id[0].childNodes)
  1098.                                     self.debug("game_id: %s", game_id)
  1099.                                     game_id = intz(game_id)
  1100.                                     config = self.app().config_updater()
  1101.                                     config.set("xsolla.secret", secret)
  1102.                                     config.set("xsolla.project-id", game_id)
  1103.                                     config.store()
  1104.                                     self.call("xsolla.check-activation")
  1105.                                     self.call("xsolla.send-activation-request")
  1106.                 finally:
  1107.                     cnn.close()
  1108.         except HTTPError as e:
  1109.             self.error("Error registering in the Xsolla system: %s", e)
  1110.         except IOError as e:
  1111.             self.error("Error registering in the Xsolla system: %s", e)
  1112.         except TimeoutError:
  1113.             self.error("Error registering in the Xsolla system: Timed out")
  1114.  
  1115.     def gameinterface_render(self, character, vars, design):
  1116.         if self.conf("xsolla.project-id"):
  1117.             vars["js_modules"].add("xsolla")
  1118.             vars["js_init"].append("Xsolla.project = %d;" % self.conf("xsolla.project-id"))
  1119.             vars["js_init"].append("Xsolla.name = '%s';" % jsencode(urlencode(character.name)))
  1120.             vars["js_init"].append("Xsolla.lang = '%s';" % self.call("l10n.lang"))
  1121.  
  1122.     def check(self):
  1123.         # get xsolla id
  1124.         xsolla_id = self.conf("xsolla.project-id")
  1125.         if not xsolla_id:
  1126.             return
  1127.         if self.conf("xsolla.project-rejected"):
  1128.             return
  1129.         self.debug("Project %s has Xsolla project id %s", self.app().tag, xsolla_id)
  1130.         # query xsolla
  1131.         xsolla_gate = self.clconf("xsolla_gate", "localhost:88").split(":")
  1132.         host = str(xsolla_gate[0])
  1133.         port = int(xsolla_gate[1])
  1134.         try:
  1135.             with Timeout.push(30):
  1136.                 cnn = HTTPConnection()
  1137.                 cnn.connect((host, port))
  1138.                 try:
  1139.                     request = HTTPRequest()
  1140.                     request.method = "GET"
  1141.                     request.path = "/paystation/?projectid=%s" % xsolla_id
  1142.                     request.host = "secure.xsolla.com"
  1143.                     request.add_header("Connection", "close")
  1144.                     response = cnn.perform(request)
  1145.                     if response.status_code == 200:
  1146.                         reqs = self.main_app().objlist(DBXsollaActivationRequestList, query_index="project", query_equal=self.app().tag)
  1147.                         if response.body.find('"WebMoney"') >= 0 or xsolla_id == 10531:
  1148.                             # project is active
  1149.                             self.debug("Project is active")
  1150.                             if not self.conf("xsolla.project-active"):
  1151.                                 config = self.app().config_updater()
  1152.                                 config.set("xsolla.project-active", 1)
  1153.                                 config.store()
  1154.                                 # notify admin
  1155.                                 admin = self.main_app().obj(User, self.app().project.get("owner"))
  1156.                                 admin_name = admin.get("name")
  1157.                                 admin_email = admin.get("email")
  1158.                                 self.main_app().call("email.send", admin_email, admin_name, self._("Xsolla activation"), self._("Hello, {name}.\n\nXsolla has activated your game '{title}'. Now you can accept payments in your game. If you need to accept Yandex Money and Beeline Mobile Payments, you need to make manual request to the operator of the MMO Constructor project.\n\nPlease, check payments in your game, and notify us if something goes wrong.").format(title=self.app().project.get("title_short"), name=admin_name))
  1159.                             reqs.remove()
  1160.                             return 1
  1161.                         else:
  1162.                             # project is inactive
  1163.                             self.debug("Project is inactive")
  1164.                             config = self.app().config_updater()
  1165.                             config.set("xsolla.project-active", 0)
  1166.                             config.store()
  1167.                             reqs.load(silent=True)
  1168.                             if not len(reqs):
  1169.                                 req = self.main_app().obj(DBXsollaActivationRequest)
  1170.                                 req.set("project", self.app().tag)
  1171.                                 req.set("created", self.now())
  1172.                                 req.set("xsolla_id", xsolla_id)
  1173.                                 req.set("title", self.app().project.get("title_short"))
  1174.                                 req.store()
  1175.                             return 0
  1176.                     self.debug("Project status unknown")
  1177.                     return None
  1178.                 finally:
  1179.                     cnn.close()
  1180.         except IOError as e:
  1181.             self.error("Error checking Xsolla activation: %s", e)
  1182.         except TimeoutError:
  1183.             self.error("Error checking Xsolla activation: Timed out")
  1184.  
  1185.     def send_activation_request(self):
  1186.         xsolla_id = self.conf("xsolla.project-id")
  1187.         if not xsolla_id:
  1188.             return
  1189.         main = self.main_app()
  1190.         main_conf = main.config
  1191.         title = self.app().project.get("title_short")
  1192.         content = main_conf.get("xsolla.act-request-email").format(xsolla_id=xsolla_id, title=title)
  1193.         manager_email = main_conf.get("xsolla.manager-email")
  1194.         manager_name = main_conf.get("xsolla.manager-name")
  1195.         sender_email = main_conf.get("xsolla.sender-email")
  1196.         sender_name = main_conf.get("xsolla.sender-name")
  1197.         if manager_email and manager_name:
  1198.             main.call("email.send", manager_email, manager_name, self._("Activation: %s") % title, content, from_email=sender_email, from_name=sender_name)
  1199.  
  1200. class XsollaAdmin(Module):
  1201.     def register(self):
  1202.         self.rhook("ext-admin-constructor.project-xsolla", self.project_xsolla, priv="constructor.projects-xsolla")
  1203.         self.rhook("headmenu-admin-constructor.project-xsolla", self.headmenu_project_xsolla)
  1204.         self.rhook("permissions.list", self.permissions_list)
  1205.  
  1206.     def project_xsolla(self):
  1207.         req = self.req()
  1208.         uuid = req.args
  1209.         cmd = ""
  1210.         m = re_uuid_cmd.match(req.args)
  1211.         if m:
  1212.             uuid, cmd = m.group(1, 2)
  1213.         app = self.app().inst.appfactory.get_by_tag(uuid)
  1214.         if app is None:
  1215.             self.call("web.not_found")
  1216.         if cmd == "":
  1217.             payments = []
  1218.             lst = app.objlist(PaymentXsollaList, query_index="date", query_reversed=True)
  1219.             lst.load(silent=True)
  1220.             for pay in lst:
  1221.                 payments.append({
  1222.                     "id": pay.uuid,
  1223.                     "performed": self.call("l10n.time_local", pay.get("performed")),
  1224.                     "date": pay.get("date"),
  1225.                     "user": pay.get("user"),
  1226.                     "v1": htmlescape(pay.get("v1")) if pay.get("v1") else pay.get("user"),
  1227.                     "sum": pay.get("sum"),
  1228.                     "cancelled": pay.get("cancelled")
  1229.                 })
  1230.             vars = {
  1231.                 "project": {
  1232.                     "uuid": uuid
  1233.                 },
  1234.                 "EditSettings": self._("Edit settings"),
  1235.                 "PaymentURL": self._("Payment URL"),
  1236.                 "SecretCode": self._("Secret code"),
  1237.                 "SecretCodeAddGame": self._("Secret code for adding games"),
  1238.                 "TimeXsolla": self._("Xsolla time"),
  1239.                 "OurTime": self._("Our time"),
  1240.                 "User": self._("User"),
  1241.                 "Amount": self._("Amount"),
  1242.                 "Chargeback": self._("Chargeback"),
  1243.                 "payments": payments,
  1244.                 "Update": self._("Update"),
  1245.                 "Id": self._("Id"),
  1246.                 "ProjectID": self._("Project ID"),
  1247.                 "ContragentID": self._("Contragent ID"),
  1248.             }
  1249.             vars["settings"] = {
  1250.                 "secret": htmlescape(app.config.get("xsolla.secret")),
  1251.                 "secret_addgame": htmlescape(app.config.get("xsolla.secret-addgame")),
  1252.                 "project_id": htmlescape(app.config.get("xsolla.project-id")),
  1253.                 "contragent_id": htmlescape(app.config.get("xsolla.contragent-id")),
  1254.                 "payment_url": "http://www.%s/ext-payment/2pay/%s" % (self.main_host, app.tag),
  1255.             }
  1256.             self.call("admin.response_template", "admin/money/xsolla-dashboard.html", vars)
  1257.         elif cmd == "settings":
  1258.             secret = req.param("secret")
  1259.             secret_addgame = req.param("secret_addgame")
  1260.             project_id = req.param("project_id")
  1261.             contragent_id = req.param("contragent_id")
  1262.             if req.param("ok"):
  1263.                 config = app.config_updater()
  1264.                 config.set("xsolla.secret", secret)
  1265.                 config.set("xsolla.secret-addgame", secret_addgame)
  1266.                 config.set("xsolla.project-id", project_id)
  1267.                 config.set("xsolla.contragent-id", contragent_id)
  1268.                 config.store()
  1269.                 self.call("admin.redirect", "constructor/project-xsolla/%s" % uuid)
  1270.             else:
  1271.                 secret = app.config.get("xsolla.secret")
  1272.                 secret_addgame = app.config.get("xsolla.secret-addgame")
  1273.                 project_id = app.config.get("xsolla.project-id")
  1274.                 contragent_id = app.config.get("xsolla.contragent-id")
  1275.             fields = []
  1276.             fields.append({"name": "project_id", "label": self._("Xsolla project id"), "value": project_id})
  1277.             fields.append({"name": "contragent_id", "label": self._("Xsolla contragent id"), "value": contragent_id})
  1278.             fields.append({"name": "secret", "label": self._("Xsolla secret"), "value": secret})
  1279.             fields.append({"name": "secret_addgame", "label": self._("Xsolla secret for adding games"), "value": secret_addgame})
  1280.             self.call("admin.form", fields=fields)
  1281.         else:
  1282.             self.call("web.not_found")
  1283.  
  1284.     def headmenu_project_xsolla(self, args):
  1285.         uuid = args
  1286.         cmd = ""
  1287.         m = re_uuid_cmd.match(args)
  1288.         if m:
  1289.             uuid, cmd = m.group(1, 2)
  1290.         if cmd == "":
  1291.             return [self._("Xsolla dashboard"), "constructor/project-dashboard/%s" % uuid]
  1292.         elif cmd == "settings":
  1293.             return [self._("Settings editor"), "constructor/project-xsolla/%s" % uuid]
  1294.  
  1295.     def permissions_list(self, perms):
  1296.         perms.append({"id": "constructor.projects-xsolla", "name": self._("Constructor: Xsolla integration")})
  1297.  
  1298. class Money(Module):
  1299.     def register(self):
  1300.         self.rhook("currencies.list", self.currencies_list, priority=-1000)
  1301.         self.rhook("money-description.admin-give", self.money_description_admin_give)
  1302.         self.rhook("money-description.admin-take", self.money_description_admin_take)
  1303.         self.rhook("money.obj", self.member_money)
  1304.         self.rhook("money.valid_amount", self.valid_amount)
  1305.         self.rhook("money.real-currency", self.real_currency)
  1306.         self.rhook("money.format-price", self.format_price)
  1307.         self.rhook("money.price-text", self.price_text)
  1308.         self.rhook("money.price-html", self.price_html)
  1309.         self.rhook("money.currency-info", self.currency_info)
  1310.         self.rhook("money.not-enough-funds", self.not_enough_funds)
  1311.  
  1312.     def not_enough_funds(self, currency, **kwargs):
  1313.         cinfo = self.call("money.currency-info", currency)
  1314.         return self._("Not enough %s") % (self.call("l10n.literal_value", 100, cinfo.get("name_local")) if cinfo else htmlescape(currency))
  1315.  
  1316.     def currencies_list(self, currencies):
  1317.         lst = self.conf("money.currencies")
  1318.         if lst:
  1319.             for code, info in lst.iteritems():
  1320.                 info["code"] = code
  1321.                 currencies[code] = info
  1322.  
  1323.     def real_currency(self):
  1324.         try:
  1325.             return self._real_currency
  1326.         except AttributeError:
  1327.             self._real_currency = None
  1328.             for code, cur in self.currencies().iteritems():
  1329.                 if cur.get("real"):
  1330.                     self._real_currency = code
  1331.             return self._real_currency
  1332.  
  1333.     def money_description_admin_give(self):
  1334.         return {
  1335.             "args": ["admin"],
  1336.             "text": self._("Given by the administration"),
  1337.         }
  1338.  
  1339.     def money_description_admin_take(self):
  1340.         return {
  1341.             "args": ["admin"],
  1342.             "text": self._("Taken by the administration"),
  1343.         }
  1344.  
  1345.     def valid_amount(self, amount, currency, errors=None, amount_field=None, currency_field=None):
  1346.         valid = True
  1347.         # checking currency
  1348.         currencies = {}
  1349.         self.call("currencies.list", currencies)
  1350.         currency_info = currencies.get(currency)
  1351.         if currency_info is None:
  1352.             valid = False
  1353.             if errors is not None and currency_field:
  1354.                 errors[currency_field] = self._("Invalid currency")
  1355.         # checking amount
  1356.         try:
  1357.             amount = float(amount)
  1358.             if amount <= 0:
  1359.                 valid = False
  1360.                 if errors is not None and amount_field:
  1361.                     errors[amount_field] = self._("Amount must be greater than 0")
  1362.             elif amount >= 1000000000:
  1363.                 valid = False
  1364.                 if errors is not None and amount_field:
  1365.                     errors[amount_field] = self._("Amount must be less than 1000000000")
  1366.             elif currency_info is not None and amount != float(currency_info["format"] % amount):
  1367.                 valid = False
  1368.                 if errors is not None and amount_field:
  1369.                     errors[amount_field] = self._("Invalid amount precision")
  1370.         except ValueError:
  1371.             valid = False
  1372.             if errors is not None and amount_field:
  1373.                 errors[amount_field] = self._("Invalid number format")
  1374.         return valid
  1375.  
  1376.     def member_money(self, member_type, member_uuid):
  1377.         return MemberMoney(self.app(), member_type, member_uuid)
  1378.  
  1379.     def format_price(self, price, currency):
  1380.         cinfo = self.currency_info(currency)
  1381.         if cinfo is None:
  1382.             return None
  1383.         min_val = 0.1 ** cinfo["precision"]
  1384.         price = math.ceil(price / min_val) * min_val
  1385.         if price < min_val:
  1386.             price = min_val
  1387.         return round(price, cinfo["precision"])
  1388.  
  1389.     def price_text(self, price, currency):
  1390.         cinfo = self.currency_info(currency)
  1391.         text_price = cinfo["format"] % price
  1392.         text_currency = cinfo["code"]
  1393.         return '%s %s' % (text_price, text_currency)
  1394.  
  1395.     def price_html(self, price, currency):
  1396.         cinfo = self.currency_info(currency)
  1397.         if cinfo is None:
  1398.             return '%s ???' % price
  1399.         html_price = cinfo["format"] % price
  1400.         html_currency = '<img src="%s" alt="%s" />' % (cinfo["icon"], cinfo["code"]) if cinfo.get("icon") else cinfo["code"]
  1401.         return '<span class="price"><span class="money-amount">%s</span> <span class="money-currency">%s</span></span>' % (html_price, html_currency)
  1402.  
  1403.     def currencies(self):
  1404.         try:
  1405.             return self._currencies
  1406.         except AttributeError:
  1407.             self._currencies = {}
  1408.             self.call("currencies.list", self._currencies)
  1409.             return self._currencies
  1410.  
  1411.     def currency_info(self, currency):
  1412.         return self.currencies().get(currency)
  1413.  
  1414. class WebMoneyAdmin(Module):
  1415.     def register(self):
  1416.         self.rhook("permissions.list", self.permissions_list)
  1417.         self.rhook("menu-admin-auth.index", self.menu_auth_index)
  1418.         self.rhook("ext-admin-wmlogin.settings", self.auth_settings, priv="webmoney.auth")
  1419.  
  1420.     def permissions_list(self, perms):
  1421.         perms.append({"id": "webmoney.auth", "name": self._("WebMoney authentication settings")})
  1422.  
  1423.     def menu_auth_index(self, menu):
  1424.         req = self.req()
  1425.         if req.has_access("webmoney.auth"):
  1426.             menu.append({"id": "wmlogin/settings", "text": self._("WebMoney authentication"), "leaf": True, "order": 20})
  1427.  
  1428.     def auth_settings(self):
  1429.         req = self.req()
  1430.         if req.ok():
  1431.             config = self.app().config_updater()
  1432.             config.set("wmlogin.wmid", req.param("wmid"))
  1433.             config.set("wmlogin.rid", req.param("rid"))
  1434.             config.store()
  1435.             self.call("admin.response", self._("Settings stored"), {})
  1436.         fields = [
  1437.             {"name": "wmid", "label": self._("WMID for WMLogin"), "value": self.conf("wmlogin.wmid")},
  1438.             {"name": "rid", "label": self._("RID for URL '%s'") % ("http://%s/webmoney/checkticket" % req.host()), "value": self.conf("wmlogin.rid")},
  1439.         ]
  1440.         self.call("admin.form", fields=fields)
  1441.  
  1442. class WebMoney(Module):
  1443.     def register(self):
  1444.         self.rhook("ext-webmoney.checkticket", self.check_ticket, priv="logged")
  1445.         self.rhook("wmcert.get", self.wmcert_get)
  1446.         self.rhook("wmlogin.url", self.wmlogin_url)
  1447.  
  1448.     def check_ticket(self):
  1449.         req = self.req()
  1450.         self.debug("WMLogin auth: %s", [req.param_dict()])
  1451.         ticket = req.param("WmLogin_Ticket")
  1452.         authtype = req.param("WmLogin_AuthType")
  1453.         remote_addr = req.param("WmLogin_UserAddress")
  1454.         user_wmid = req.param("WmLogin_WMID")
  1455.         rid = req.param("WmLogin_UrlID")
  1456.         service_wmid = self.conf("wmlogin.wmid")
  1457.         if rid != self.conf("wmlogin.rid"):
  1458.             self.error("WMLogin received rid=%s, expected=%s", rid, self.conf("wmlogin.rid"))
  1459.             self.call("web.forbidden")
  1460.         try:
  1461.             # validating ticket
  1462.             doc = xml.dom.minidom.getDOMImplementation().createDocument(None, "request", None)
  1463.             request = doc.documentElement
  1464.             request.appendChild(doc.createElement("siteHolder")).appendChild(doc.createTextNode(service_wmid))
  1465.             request.appendChild(doc.createElement("user")).appendChild(doc.createTextNode(user_wmid))
  1466.             request.appendChild(doc.createElement("ticket")).appendChild(doc.createTextNode(ticket))
  1467.             request.appendChild(doc.createElement("urlId")).appendChild(doc.createTextNode(rid))
  1468.             request.appendChild(doc.createElement("authType")).appendChild(doc.createTextNode(authtype))
  1469.             request.appendChild(doc.createElement("userAddress")).appendChild(doc.createTextNode(remote_addr))
  1470.             response = self.wm_query(self.clconf("wm_login_gate", "localhost:86"), "login.wmtransfer.com", "/ws/authorize.xiface", request)
  1471.             doc = response.documentElement
  1472.             if doc.tagName != "response":
  1473.                 raise RuntimeError("Unexpected response from WMLogin")
  1474.             retval = doc.getAttribute("retval")
  1475.             sval = doc.getAttribute("sval")
  1476.             if retval == "0":
  1477.                 self.call("wmlogin.authorized", authtype=authtype, remote_addr=remote_addr, wmid=user_wmid)
  1478.             else:
  1479.                 self.error("WMLogin auth failed: retval=%s, sval=%s", retval, sval)
  1480.                 self.call("web.forbidden")
  1481.         except HTTPError:
  1482.             self.call("web.response_global", self._("Error connecting to the WebMoney server. Try again later"), {})
  1483.  
  1484.     def wm_query(self, gate, real_host, url, request):
  1485.         reqdata = request.toxml("utf-8")
  1486.         self.debug("WM reqdata: %s", reqdata)
  1487.         wm_gate = gate.split(":")
  1488.         host = str(wm_gate[0])
  1489.         port = int(wm_gate[1])
  1490.         try:
  1491.             with Timeout.push(20):
  1492.                 cnn = HTTPConnection()
  1493.                 try:
  1494.                     cnn.connect((host, port))
  1495.                 except IOError as e:
  1496.                     raise HTTPError("Error connecting to %s:%d" % (host, port))
  1497.                 try:
  1498.                     request = cnn.post(str(url), reqdata)
  1499.                     request.host = real_host
  1500.                     request.add_header("Content-type", "application/xml")
  1501.                     request.add_header("Connection", "close")
  1502.                     response = cnn.perform(request)
  1503.                     if response.status_code != 200:
  1504.                         raise HTTPError("Error downloading http://%s:%s%s: %s" % (host, port, url, response.status))
  1505.                     response = xml.dom.minidom.parseString(response.body)
  1506.                     self.debug("WM request: %s", response.toxml("utf-8"))
  1507.                     return response
  1508.                 except IOError as e:
  1509.                     raise HTTPError("Error downloading http://%s:%s%s: %s" % (host, port, url, str(e)))
  1510.                 finally:
  1511.                     cnn.close()
  1512.         except TimeoutError:
  1513.             raise HTTPError("Timeout downloading http://%s:%s%s" % (host, port, url))
  1514.  
  1515.     def wmcert_get(self, wmid):
  1516.         doc = xml.dom.minidom.getDOMImplementation().createDocument(None, "request", None)
  1517.         request = doc.documentElement
  1518.         request.appendChild(doc.createElement("wmid")).appendChild(doc.createTextNode(""))
  1519.         request.appendChild(doc.createElement("passportwmid")).appendChild(doc.createTextNode(wmid))
  1520.         request.appendChild(doc.createElement("sign")).appendChild(doc.createTextNode(""))
  1521.         params = request.appendChild(doc.createElement("params"))
  1522.         params.appendChild(doc.createElement("dict")).appendChild(doc.createTextNode("0"))
  1523.         params.appendChild(doc.createElement("info")).appendChild(doc.createTextNode("0"))
  1524.         params.appendChild(doc.createElement("mode")).appendChild(doc.createTextNode("0"))
  1525.         response = self.wm_query(self.clconf("wm_passport_gate", "localhost:87"), "passport.webmoney.ru", "/asp/XMLGetWMPassport.asp", request)
  1526.         doc = response.documentElement
  1527.         if doc.tagName != "response":
  1528.             raise RuntimeError("Unexpected response from WMPassport")
  1529.         if doc.getAttribute("retval") != "0":
  1530.             return 0
  1531.         level = 0
  1532.         for cert in doc.getElementsByTagName("row"):
  1533.             if cert.getAttribute("recalled") == "0":
  1534.                 lvl = int(cert.getAttribute("tid"))
  1535.                 if lvl > level:
  1536.                     level = lvl
  1537.         return level
  1538.    
  1539.     def wmlogin_url(self):
  1540.         lang = self.call("l10n.lang")
  1541.         if lang == "ru":
  1542.             lang = "ru-RU"
  1543.         else:
  1544.             lang = "en-EN"
  1545.         return "https://login.wmtransfer.com/GateKeeper.aspx?RID=%s&lang=%s" % (self.conf("wmlogin.rid"), lang)
  1546.  
  1547. class XsollaActivation(Module):
  1548.     def register(self):
  1549.         self.rhook("permissions.list", self.permissions_list)
  1550.         self.rhook("menu-admin-economy.index", self.menu_economy_index)
  1551.         self.rhook("headmenu-admin-xsolla.inactive", self.headmenu_inactive)
  1552.         self.rhook("ext-admin-xsolla.inactive", self.admin_inactive, priv="xsolla.activation")
  1553.         self.rhook("ext-admin-xsolla.actreject", self.admin_actreject, priv="xsolla.activation")
  1554.         self.rhook("queue-gen.schedule", self.schedule)
  1555.         self.rhook("admin-xsolla.check-inactive", self.check_inactive)
  1556.         self.rhook("headmenu-admin-xsolla.actsettings", self.headmenu_actsettings)
  1557.         self.rhook("ext-admin-xsolla.actsettings", self.admin_actsettings, priv="xsolla.actsettings")
  1558.  
  1559.     def schedule(self, sched):
  1560.         sched.add("admin-xsolla.check-inactive", "30 3 * * *", priority=20)
  1561.  
  1562.     def permissions_list(self, perms):
  1563.         perms.append({"id": "xsolla.activation", "name": self._("Xsolla activation management")})
  1564.         perms.append({"id": "xsolla.actsettings", "name": self._("Xsolla activation settings")})
  1565.  
  1566.     def menu_economy_index(self, menu):
  1567.         req = self.req()
  1568.         if req.has_access("xsolla.activation"):
  1569.             menu.append({"id": "xsolla/inactive", "text": self._("Xsolla inactive projects"), "leaf": True, "order": 20})
  1570.         if req.has_access("xsolla.actsettings"):
  1571.             menu.append({"id": "xsolla/actsettings", "text": self._("Xsolla activation settings"), "leaf": True, "order": 21})
  1572.  
  1573.     def admin_actreject(self):
  1574.         req = self.req()
  1575.         app = self.app().inst.appfactory.get_by_tag(req.args)
  1576.         if app:
  1577.             config = app.config_updater()
  1578.             config.set("xsolla.project-rejected", 1)
  1579.             config.store()
  1580.             reqs = self.objlist(DBXsollaActivationRequestList, query_index="project", query_equal=app.tag)
  1581.             reqs.remove()
  1582.             self.call("admin.redirect", "xsolla/inactive")
  1583.  
  1584.     def headmenu_inactive(self, args):
  1585.         return self._("Xsolla inactive projects")
  1586.  
  1587.     def admin_inactive(self):
  1588.         rows = []
  1589.         lst = self.objlist(DBXsollaActivationRequestList, query_index="all")
  1590.         lst.load(silent=True)
  1591.         for ent in lst:
  1592.             rows.append([
  1593.                 self.call("l10n.time_local", ent.get("created")),
  1594.                 ent.get("xsolla_id"),
  1595.                 htmlescape(ent.get("title")),
  1596.                 u'<a href="%s" target="_blank">%s</a>' % (
  1597.                     "https://secure.xsolla.com/paystation/?projectid=%s" % ent.get("xsolla_id"),
  1598.                     self._("paystation"),
  1599.                 ),
  1600.                 u'<hook:admin.link href="xsolla/actreject/%s" title="%s" confirm="%s" />' % (
  1601.                     ent.get("project"),
  1602.                     self._("reject"),
  1603.                     self._("Are you sure want to reject this request?")
  1604.                 )
  1605.             ])
  1606.         vars = {
  1607.             "tables": [
  1608.                 {
  1609.                     "header": [
  1610.                         self._("Record created"),
  1611.                         self._("Xsolla id"),
  1612.                         self._("Game title"),
  1613.                         self._("Paystation"),
  1614.                         self._("Rejection"),
  1615.                     ],
  1616.                     "rows": rows,
  1617.                 }
  1618.             ]
  1619.         }
  1620.         self.call("admin.response_template", "admin/common/tables.html", vars)
  1621.  
  1622.     def check_inactive(self):
  1623.         lst = self.objlist(DBXsollaActivationRequestList, query_index="all")
  1624.         lst.load(silent=True)
  1625.         for ent in lst:
  1626.             app = self.app().inst.appfactory.get_by_tag(ent.get("project"))
  1627.             app.call("xsolla.check-activation")
  1628.         lst = self.objlist(DBXsollaActivationRequestList, query_index="all")
  1629.         lst.load(silent=True)
  1630.         if len(lst):
  1631.             lines = []
  1632.             for ent in lst:
  1633.                 lines.append(u"%s - %s" % (ent.get("xsolla_id"), ent.get("title")))
  1634.             content = self.conf("xsolla.act-reminder-email").format(content="\n".join(lines))
  1635.             manager_email = self.conf("xsolla.manager-email")
  1636.             manager_name = self.conf("xsolla.manager-name")
  1637.             sender_email = self.conf("xsolla.sender-email")
  1638.             sender_name = self.conf("xsolla.sender-name")
  1639.             if manager_email and manager_name:
  1640.                 self.call("email.send", manager_email, manager_name, self._("Some projects are still inactive"), content, from_email=sender_email, from_name=sender_name)
  1641.  
  1642.     def headmenu_actsettings(self, args):
  1643.         return self._("Xsolla activation settings")
  1644.  
  1645.     def admin_actsettings(self):
  1646.         req = self.req()
  1647.         if req.ok():
  1648.             config = self.app().config_updater()
  1649.             config.set("xsolla.manager-email", req.param("email"))
  1650.             config.set("xsolla.manager-name", req.param("name"))
  1651.             config.set("xsolla.sender-email", req.param("from_email"))
  1652.             config.set("xsolla.sender-name", req.param("from_name"))
  1653.             config.set("xsolla.act-reminder-email", req.param("reminder"))
  1654.             config.set("xsolla.act-request-email", req.param("request"))
  1655.             config.store()
  1656.             self.call("admin.response", self._("Settings stored"), {})
  1657.         fields = [
  1658.             {"name": "email", "label": self._("Manager's e-mail"), "value": self.conf("xsolla.manager-email")},
  1659.             {"name": "name", "label": self._("Manager's name"), "value": self.conf("xsolla.manager-name")},
  1660.             {"name": "from_email", "label": self._("Sender e-mail"), "value": self.conf("xsolla.sender-email")},
  1661.             {"name": "from_name", "label": self._("Sender name"), "value": self.conf("xsolla.sender-name")},
  1662.             {"name": "reminder", "type": "textarea", "label": self._("Email template for the reminder"), "value": self.conf("xsolla.act-reminder-email"), "height": 300},
  1663.             {"name": "request", "type": "textarea", "label": self._("Email template for the request"), "value": self.conf("xsolla.act-request-email"), "height": 300},
  1664.         ]
  1665.         self.call("admin.form", fields=fields)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement