Commit 16ed446d authored by Romain Courteaud's avatar Romain Courteaud

slapos_panel: reintroduce payzen interface

parent 0a7dfdde
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_jio_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_jio_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>pay_my_slapos_sale_invoice_transaction</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>60.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Pay My Invoices</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/AccountingTransactionModule_viewCreateExternalPaymentTransactionOnSlaposPanelDialog</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: (context.getWebSiteValue() is not None)</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
data-i18n=Servers data-i18n=Servers
data-i18n=Tickets data-i18n=Tickets
data-i18n=Invoices data-i18n=Invoices
data-i18n=Pay
--> -->
<head> <head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
......
...@@ -244,7 +244,7 @@ ...@@ -244,7 +244,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>1014.6188.10537.18773</string> </value> <value> <string>1014.24988.49418.59323</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -264,7 +264,7 @@ ...@@ -264,7 +264,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1707317565.18</float> <float>1707410622.71</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
...@@ -21,7 +21,8 @@ ...@@ -21,7 +21,8 @@
'Projects', 'Projects',
'Servers', 'Servers',
'Tickets', 'Tickets',
'Invoices' 'Invoices',
'Pay'
]) ])
})) }))
.push(function (result_dict) { .push(function (result_dict) {
...@@ -49,6 +50,10 @@ ...@@ -49,6 +50,10 @@
title: result_dict.translation_dict.Invoices, title: result_dict.translation_dict.Invoices,
jio_key: 'accounting_module', jio_key: 'accounting_module',
erp5_action: 'slapos_panel_view_my_invoice_list' erp5_action: 'slapos_panel_view_my_invoice_list'
}, {
title: result_dict.translation_dict.Pay,
jio_key: 'accounting_module',
erp5_action: 'pay_my_slapos_sale_invoice_transaction'
}]); }]);
}); });
}); });
......
...@@ -246,7 +246,7 @@ ...@@ -246,7 +246,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>1014.24987.41998.51865</string> </value> <value> <string>1014.26539.21375.20974</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -266,7 +266,7 @@ ...@@ -266,7 +266,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1707317522.3</float> <float>1707822073.41</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
<!DOCTYPE html>
<html>
<!--
data-i18n=Payment Result
-->
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
<title>SlapOS master external payment result</title>
<link rel="http://www.renderjs.org/rel/interface" href="interface_page.html">
<!-- renderjs -->
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<script src="domsugar.js" type="text/javascript"></script>
<script src="slapos_master_panel_external_payment_result.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
/*globals console, window, rJS, RSVP, domsugar */
/*jslint indent: 2, nomen: true, maxlen: 80*/
(function (window, rJS, RSVP, domsugar) {
"use strict";
rJS(window)
.declareAcquiredMethod("getUrlFor", "getUrlFor")
.declareAcquiredMethod("updateHeader", "updateHeader")
.declareMethod("render", function (options) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
var message,
advice,
page_title;
if (options.result === "success") {
page_title = "Thank you for your Payment";
message = "Thank you for finalising the payment.";
advice = "It will be processed by PayZen interface.";
} else if (options.result === "cancel") {
page_title = "Payment cancelled";
message = "You have cancelled the payment process.";
advice = "Please consider continuing it as soon as possible, " +
"otherwise you will be not able to use full functionality.";
} else if (options.result === "error") {
page_title = "Payment Error";
message = "There was an error while processing the payment.";
advice = "Please try again later or contact the support.";
} else if (options.result === "referral") {
page_title = "Payment Referral";
message = "Your credit card was refused by payment system.";
advice = "Please contact your bank or use another credit card.";
} else if (options.result === "refused") {
page_title = "Payment Refused";
message = "The payment has been refused.";
advice = "Please contact your bank.";
} else if (options.result === "return") {
page_title = "Payment Unfinished";
message = "You have not finished your payment.";
advice = "Please consider continuing it as soon as possible, " +
"otherwise you will be not able to use full functionality.";
} else if (options.result === "free") {
page_title = "This payment is free";
message = "You are trying to pay a Free invoice.";
advice = "Please, contact us by opening a ticket " +
"to ask more information.";
} else if (options.result === "contact_us") {
page_title = "Please, contact us";
message = "You are trying to pay an invoice, " +
"but the automatic payments are disabled currently.";
advice = "Please contact us by opening a ticket with the " +
"invoice information, we will provide you an " +
"alternative way to pay.";
} else if (options.result === "already_registered") {
page_title = "Payment already registered";
message = "Your payment had already been registered.";
} else {
throw new Error("Unknown action to take: " + options.result);
}
domsugar(gadget.element, {},
[
domsugar("p", {}, [
domsugar("center", {}, [
domsugar("strong", {text: message})
])
]),
domsugar("p", {}, [
domsugar("center", {text: advice})
])
]);
return RSVP.hash({
page_title: page_title,
page_icon: 'home',
front_url: gadget.getUrlFor({command: 'display'})
});
})
.push(function (hash) {
return gadget.updateHeader(hash);
});
});
}(window, rJS, RSVP, domsugar));
\ No newline at end of file
...@@ -57,6 +57,7 @@ ...@@ -57,6 +57,7 @@
"Input is required but no input given.": "Input is required but no input given.", "Input is required but no input given.": "Input is required but no input given.",
"Invalid DateTime": "Invalid DateTime", "Invalid DateTime": "Invalid DateTime",
"Invalid Search Criteria": "Invalid Search Criteria", "Invalid Search Criteria": "Invalid Search Criteria",
"Invoices": "Invoices",
"Jump": "Jump", "Jump": "Jump",
"Jumps": "Jumps", "Jumps": "Jumps",
"Language": "Language", "Language": "Language",
...@@ -74,6 +75,7 @@ ...@@ -74,6 +75,7 @@
"Not equal to": "Not equal to", "Not equal to": "Not equal to",
"Nothing selected": "Nothing selected", "Nothing selected": "Nothing selected",
"Others": "Others", "Others": "Others",
"Pay": "Pay",
"Please fill all required fields to submit": "Please fill all required fields to submit", "Please fill all required fields to submit": "Please fill all required fields to submit",
"Preferences": "Preferences", "Preferences": "Preferences",
"Previous": "Previous", "Previous": "Previous",
...@@ -84,6 +86,7 @@ ...@@ -84,6 +86,7 @@
"Records": "Records", "Records": "Records",
"Reference": "Reference", "Reference": "Reference",
"Reports": "Reports", "Reports": "Reports",
"Request": "Request",
"Reset": "Reset", "Reset": "Reset",
"Save": "Save", "Save": "Save",
"Search": "Search", "Search": "Search",
......
...@@ -240,7 +240,7 @@ ...@@ -240,7 +240,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>1013.50134.3701.30737</string> </value> <value> <string>1014.6188.10537.18773</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -260,7 +260,7 @@ ...@@ -260,7 +260,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1705323025.04</float> <float>1707486997.58</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
portal = context.getPortalObject()
web_site = context.getWebSiteValue()
assert web_site is not None
ledger_uid = portal.portal_categories.ledger.automated.getUid()
# This script will be used to generate the payment
# compatible with external providers
html_content = ''
entity = portal.portal_membership.getAuthenticatedMember().getUserValue()
if entity is None:
return '<p>Nothing to pay</p>'
for currency_uid, secure_service_relative_url in [
(portal.currency_module.EUR.getUid(), portal.Base_getPayzenServiceRelativeUrl()),
# (portal.currency_module.CNY.getUid(), portal.Base_getWechatServiceRelativeUrl())
]:
if secure_service_relative_url is not None:
outstanding_amount_list = entity.Entity_getOutstandingAmountList(
ledger_uid=ledger_uid,
resource_uid=currency_uid
)
for outstanding_amount in outstanding_amount_list:
html_content += """
<p><a href="%(payment_url)s">%(total_price)s %(currency)s</a></p>
""" % {
'total_price': outstanding_amount.total_price,
'currency': outstanding_amount.getPriceCurrencyReference(),
'payment_url': '%s/SaleInvoiceTransaction_createExternalPaymentTransactionFromAmountAndRedirect' % outstanding_amount.absolute_url()
}
if html_content:
if web_site.getLayoutProperty("configuration_payment_url_template", None) is None:
html_content = '<p>Please contact us to handle your payment</p>'
else:
html_content = '<p>Nothing to pay</p>'
return html_content
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>REQUEST=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>AccountingTransactionModule_getCreateExternalPaymentTransactionOnSlaposPanelHTML</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>action_title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list>
<string>your_pay_action_html</string>
</list>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>AccountingTransactionModule_viewCreateExternalPaymentTransactionOnSlaposPanelDialog</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>AccountingTransactionModule_viewCreatePaymentTransactionDialog</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_dialog</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Pay</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
""" Return a dict with vads_urls required for payzen."""
if web_site is None:
web_site = context.getWebSiteValue()
if web_site is None:
raise ValueError("This script must be called from a web site")
base = "%(payment_transaction_url)s/PaymentTransaction_triggerPaymentCheckAlarmAndRedirectToPanel?result=%(result)s"
base_substitution_dict = {
"payment_transaction_url": context.absolute_url(),
"result": "__RESULT__"
}
vads_url = base % base_substitution_dict
return dict(
vads_url_already_registered=vads_url.replace("__RESULT__", "already_registered"),
vads_url_cancel=vads_url.replace("__RESULT__", "cancel"),
vads_url_error=vads_url.replace("__RESULT__", "error"),
vads_url_referral=vads_url.replace("__RESULT__", "referral"),
vads_url_refused=vads_url.replace("__RESULT__", "refused"),
vads_url_success=vads_url.replace("__RESULT__", "success"),
vads_url_return=vads_url.replace("__RESULT__", "return")
)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>web_site</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>PaymentTransaction_getVADSUrlDict</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
web_site = context.getWebSiteValue()
assert web_site is not None
context.Base_reindexAndSenseAlarm([
'slapos_payzen_update_started_payment',
'slapos_wechat_update_started_payment',
'slapos_cancel_sale_invoice_transaction_paied_payment_list'
])
from ZTUtils import make_query
hash_dict = {
'page': 'slapos_master_panel_external_payment_result',
'result': result
}
base = web_site.absolute_url()
# when accessed from web_site_module/xxx_panel , absolute_url does not add the required / prefix
if not base.endswith('/'):
base = base + '/'
return context.REQUEST.RESPONSE.redirect('%s#/?%s' % (base, make_query(hash_dict)))
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>result</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>PaymentTransaction_triggerPaymentCheckAlarmAndRedirectToPanel</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
portal = context.getPortalObject()
from DateTime import DateTime
date = DateTime()
entity = portal.portal_membership.getAuthenticatedMember().getUserValue()
outstanding_amount = context
web_site = context.getWebSiteValue()
assert web_site is not None
assert web_site.getLayoutProperty("configuration_payment_url_template", None) is not None
assert outstanding_amount.getLedgerUid() == portal.portal_categories.ledger.automated.getUid()
assert outstanding_amount.getDestinationSectionUid() == entity.getUid()
payment_mode = None
resource_uid = outstanding_amount.getPriceCurrencyUid()
for accepted_resource_uid, accepted_payment_mode, is_activated in [
(portal.currency_module.EUR.getUid(), 'payzen', portal.Base_getPayzenServiceRelativeUrl()),
]:
if is_activated and (resource_uid == accepted_resource_uid):
payment_mode = accepted_payment_mode
assert payment_mode is not None
def wrapWithShadow(entity, outstanding_amount):
return entity.Entity_createPaymentTransaction(
entity.Entity_getOutstandingAmountList(
section_uid=outstanding_amount.getSourceSectionUid(),
resource_uid=outstanding_amount.getPriceCurrencyUid(),
ledger_uid=outstanding_amount.getLedgerUid(),
group_by_node=False
),
start_date=date,
payment_mode=payment_mode
)
entity = outstanding_amount.getDestinationSectionValue(portal_type="Person")
payment_transaction = entity.Person_restrictMethodAsShadowUser(
shadow_document=entity,
callable_object=wrapWithShadow,
argument_list=[entity, outstanding_amount])
web_site = context.getWebSiteValue()
if (payment_mode == "wechat"):
return payment_transaction.PaymentTransaction_redirectToManualWechatPayment(web_site=web_site)
elif (payment_mode == "payzen"):
return payment_transaction.PaymentTransaction_redirectToManualPayzenPayment(web_site=web_site)
else:
raise NotImplementedError('not implemented')
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SaleInvoiceTransaction_createExternalPaymentTransactionFromAmountAndRedirect</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
Accounting Transaction Module | pay_my_slapos_sale_invoice_transaction
Accounting Transaction Module | slapos_panel_view_my_invoice_list Accounting Transaction Module | slapos_panel_view_my_invoice_list
Allocation Supply Line | slapos_panel_view Allocation Supply Line | slapos_panel_view
Allocation Supply Module | slapos_panel_view Allocation Supply Module | slapos_panel_view
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment