diff --git a/payments/payment_gateways/doctype/paypal_settings/paypal_settings.js b/payments/payment_gateways/doctype/paypal_settings/paypal_settings.js
index 602b047..63480bc 100644
--- a/payments/payment_gateways/doctype/paypal_settings/paypal_settings.js
+++ b/payments/payment_gateways/doctype/paypal_settings/paypal_settings.js
@@ -1,31 +1,8 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.provide("frappe.integration_service")
-
frappe.ui.form.on('PayPal Settings', {
refresh: function(frm) {
}
});
-
-frappe.integration_service.paypal_settings = Class.extend({
- init: function(frm) {
-
- },
-
- get_scheduler_job_info: function() {
- return {}
- },
-
- get_service_info: function(frm) {
- frappe.call({
- method: "frappe.integrations.doctype.paypal_settings.paypal_settings.get_service_details",
- callback: function(r){
- var integration_service_help = frm.fields_dict.integration_service_help.wrapper;
- $(integration_service_help).empty();
- $(integration_service_help).append(r.message);
- }
- })
- }
-})
\ No newline at end of file
diff --git a/payments/payment_gateways/doctype/paypal_settings/paypal_settings.json b/payments/payment_gateways/doctype/paypal_settings/paypal_settings.json
index 44e032d..eb95f4c 100644
--- a/payments/payment_gateways/doctype/paypal_settings/paypal_settings.json
+++ b/payments/payment_gateways/doctype/paypal_settings/paypal_settings.json
@@ -22,6 +22,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "API Username",
"length": 0,
"no_copy": 0,
@@ -30,6 +31,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@@ -48,6 +50,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "API Password",
"length": 0,
"no_copy": 0,
@@ -56,6 +59,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@@ -74,6 +78,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Signature",
"length": 0,
"no_copy": 0,
@@ -82,6 +87,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@@ -101,6 +107,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Use Sandbox",
"length": 0,
"no_copy": 0,
@@ -109,6 +116,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -128,6 +136,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Redirect To",
"length": 0,
"no_copy": 0,
@@ -136,6 +145,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -153,7 +163,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-09-26 02:00:45.145155",
+ "modified": "2016-12-29 14:40:31.574789",
"modified_by": "Administrator",
"module": "Integrations",
"name": "PayPal Settings",
@@ -170,6 +180,7 @@
"export": 1,
"if_owner": 0,
"import": 0,
+ "is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@@ -186,5 +197,6 @@
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
+ "track_changes": 1,
"track_seen": 0
}
\ No newline at end of file
diff --git a/payments/payment_gateways/doctype/paypal_settings/paypal_settings.py b/payments/payment_gateways/doctype/paypal_settings/paypal_settings.py
index 33f1484..28311dc 100644
--- a/payments/payment_gateways/doctype/paypal_settings/paypal_settings.py
+++ b/payments/payment_gateways/doctype/paypal_settings/paypal_settings.py
@@ -9,9 +9,9 @@
Example:
- from frappe.integration_broker.doctype.integration_service.integration_service import get_integration_controller
+ from frappe.integrations.utils import get_payment_gateway_controller
- controller = get_integration_controller("PayPal")
+ controller = get_payment_gateway_controller("PayPal")
controller().validate_transaction_currency(currency)
### 2. Redirect for payment
@@ -27,7 +27,8 @@ Example:
"payer_email": "NuranVerkleij@example.com",
"payer_name": "Nuran Verkleij",
"order_id": "111",
- "currency": "USD"
+ "currency": "USD",
+ "payment_gateway": "Razorpay"
}
# redirect the user to this url
@@ -60,12 +61,11 @@ import json
from frappe import _
from frappe.utils import get_url, call_hook_method, cint
from urllib import urlencode
-from frappe.integration_broker.doctype.integration_service.integration_service import IntegrationService
+from frappe.model.document import Document
import urllib
+from frappe.integrations.utils import create_request_log, make_post_request, create_payment_gateway
-class PayPalSettings(IntegrationService):
- service_name = "PayPal"
-
+class PayPalSettings(Document):
supported_currencies = ["AUD", "BRL", "CAD", "CZK", "DKK", "EUR", "HKD", "HUF", "ILS", "JPY", "MYR", "MXN",
"TWD", "NZD", "NOK", "PHP", "PLN", "GBP", "RUB", "SGD", "SEK", "CHF", "THB", "TRY", "USD"]
@@ -77,20 +77,17 @@ class PayPalSettings(IntegrationService):
setattr(self, "use_sandbox", cint(frappe._dict(data).use_sandbox) or 0)
def validate(self):
+ create_payment_gateway("PayPal")
+ call_hook_method('payment_gateway_enabled', gateway="PayPal")
if not self.flags.ignore_mandatory:
self.validate_paypal_credentails()
def on_update(self):
pass
- def enable(self):
- call_hook_method('payment_gateway_enabled', gateway=self.service_name)
- if not self.flags.ignore_mandatory:
- self.validate_paypal_credentails()
-
def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies:
- frappe.throw(_("Please select another payment method. {0} does not support transactions in currency '{1}'").format(self.service_name, currency))
+ frappe.throw(_("Please select another payment method. PayPal does not support transactions in currency '{0}'").format(currency))
def get_paypal_params_and_url(self):
params = {
@@ -117,7 +114,7 @@ class PayPalSettings(IntegrationService):
params = urlencode(params)
try:
- res = self.post_request(url=url, data=params.encode("utf-8"))
+ res = make_post_request(url=url, data=params.encode("utf-8"))
if res["ACK"][0] == "Failure":
raise Exception
@@ -140,7 +137,7 @@ class PayPalSettings(IntegrationService):
"correlation_id": response.get("CORRELATIONID")[0]
})
- self.integration_request = self.create_request(kwargs, "Remote", self.service_name, response.get("TOKEN")[0])
+ self.integration_request = create_request_log(kwargs, "Remote", "PayPal", response.get("TOKEN")[0])
return return_url.format(kwargs["token"])
@@ -157,49 +154,12 @@ class PayPalSettings(IntegrationService):
params = urlencode(params)
- response = self.post_request(url, data=params.encode("utf-8"))
+ response = make_post_request(url, data=params.encode("utf-8"))
if response.get("ACK")[0] != "Success":
frappe.throw("Looks like something is wrong with this site's Paypal configuration.")
return response
-@frappe.whitelist()
-def get_service_details():
- return """
-
-
Steps to configure Service
-
- - Get PayPal api credentials from link:
-
- https://developer.paypal.com/docs/classic/api/apiCredentials/
-
-
-
- - Setup credentials on PayPal settings doctype.
- Click on
-
- top right corner
-
-
- -
- After saving settings,
-
- PayPal Integration Service and Save a document.
-
-
- -
- To view PayPal payment logs,
-
-
-
-
- """
-
@frappe.whitelist(allow_guest=True, xss_safe=True)
def get_express_checkout_details(token):
try:
@@ -212,12 +172,12 @@ def get_express_checkout_details(token):
"TOKEN": token
})
- response = doc.post_request(url, data=params)
+ response = make_post_request(url, data=params)
if response.get("ACK")[0] != "Success":
frappe.respond_as_web_page(_("Something went wrong"),
_("Looks like something went wrong during the transaction. Since we haven't confirmed the payment, Paypal will automatically refund you this amount. If it doesn't, please send us an email and mention the Correlation ID: {0}.").format(response.get("CORRELATIONID", [None])[0]),
- success=False,
+ indicator_color='red',
http_status_code=frappe.ValidationError.http_status_code)
return
@@ -259,7 +219,7 @@ def confirm_payment(token):
"PAYMENTREQUEST_0_CURRENCYCODE": data.get("currency").upper()
})
- response = doc.post_request(url, data=params)
+ response = make_post_request(url, data=params)
if response.get("ACK")[0] == "Success":
update_integration_request_status(token, {
@@ -291,14 +251,3 @@ def confirm_payment(token):
def update_integration_request_status(token, data, status, error=False):
frappe.get_doc("Integration Request", token).update_status(data, status)
-
-@frappe.whitelist(allow_guest=True, xss_safe=True)
-def get_checkout_url(**kwargs):
- try:
- doc = frappe.get_doc("PayPal Settings")
- return doc.get_payment_url(**kwargs)
- except Exception:
- frappe.respond_as_web_page(_("Something went wrong"),
- _("Looks like something is wrong with this site's Paypal configuration. Don't worry! No payment has been made from your Paypal account."),
- success=False,
- http_status_code=frappe.ValidationError.http_status_code)
\ No newline at end of file
diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.js b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.js
index 9c61aba..1343fae 100644
--- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.js
+++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.js
@@ -1,33 +1,8 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.provide("frappe.integration_service")
-
frappe.ui.form.on('Razorpay Settings', {
refresh: function(frm) {
}
-});
-
-frappe.integration_service.razorpay_settings = Class.extend({
- init: function(frm) {
-
- },
-
- get_scheduler_job_info: function() {
- return {
- "Execute on every few minits of interval": " Captures all authorised payments"
- }
- },
-
- get_service_info: function(frm) {
- frappe.call({
- method: "frappe.integrations.doctype.razorpay_settings.razorpay_settings.get_service_details",
- callback: function(r){
- var integration_service_help = frm.fields_dict.integration_service_help.wrapper;
- $(integration_service_help).empty();
- $(integration_service_help).append(r.message);
- }
- })
- }
-})
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.json b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.json
index a0aeb0b..838d5ac 100644
--- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.json
+++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.json
@@ -22,6 +22,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "API Key",
"length": 0,
"no_copy": 0,
@@ -30,6 +31,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@@ -48,6 +50,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "API Secret",
"length": 0,
"no_copy": 0,
@@ -56,6 +59,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@@ -75,6 +79,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "in_standard_filter": 0,
"label": "Redirect To",
"length": 0,
"no_copy": 0,
@@ -83,6 +88,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -100,7 +106,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-09-26 02:00:03.604912",
+ "modified": "2016-12-29 14:40:31.658270",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Razorpay Settings",
@@ -117,6 +123,7 @@
"export": 1,
"if_owner": 0,
"import": 0,
+ "is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@@ -133,5 +140,6 @@
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
+ "track_changes": 1,
"track_seen": 0
}
\ No newline at end of file
diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py
index e987763..c982424 100644
--- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py
+++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py
@@ -9,9 +9,9 @@
Example:
- from frappe.integration_broker.doctype.integration_service.integration_service import get_integration_controller
+ from frappe.integrations.utils import get_payment_gateway_controller
- controller = get_integration_controller("Razorpay")
+ controller = get_payment_gateway_controller("Razorpay")
controller().validate_transaction_currency(currency)
### 2. Redirect for payment
@@ -27,7 +27,8 @@ Example:
"payer_email": "NuranVerkleij@example.com",
"payer_name": "Nuran Verkleij",
"order_id": "111",
- "currency": "INR"
+ "currency": "INR",
+ "payment_gateway": "Razorpay"
}
# Redirect the user to this url
@@ -53,45 +54,32 @@ For razorpay payment status is Authorized
from __future__ import unicode_literals
import frappe
-from frappe.utils import get_url, call_hook_method, cint
from frappe import _
import urllib, json
-from frappe.integration_broker.doctype.integration_service.integration_service import IntegrationService
+from frappe.model.document import Document
+from frappe.utils import get_url, call_hook_method, cint
+from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway
-class RazorpaySettings(IntegrationService):
- service_name = "Razorpay"
+class RazorpaySettings(Document):
supported_currencies = ["INR"]
- scheduler_events = {
- "all": [
- "frappe.integrations.doctype.razorpay_settings.razorpay_settings.capture_payment"
- ]
- }
-
def validate(self):
- if not self.flags.ignore_mandatory:
- self.validate_razorpay_credentails()
-
- def on_update(self):
- pass
-
- def enable(self):
+ create_payment_gateway('Razorpay')
call_hook_method('payment_gateway_enabled', gateway='Razorpay')
-
if not self.flags.ignore_mandatory:
self.validate_razorpay_credentails()
def validate_razorpay_credentails(self):
if self.api_key and self.api_secret:
try:
- self.get_request(url="https://api.razorpay.com/v1/payments",
+ make_get_request(url="https://api.razorpay.com/v1/payments",
auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)))
except Exception:
frappe.throw(_("Seems API Key or API Secret is wrong !!!"))
def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies:
- frappe.throw(_("Please select another payment method. {0} does not support transactions in currency '{1}'").format(self.service_name, currency))
+ frappe.throw(_("Please select another payment method. Razorpay does not support transactions in currency '{0}'").format(currency))
def get_payment_url(self, **kwargs):
return get_url("./integrations/razorpay_checkout?{0}".format(urllib.urlencode(kwargs)))
@@ -100,8 +88,7 @@ class RazorpaySettings(IntegrationService):
self.data = frappe._dict(data)
try:
- self.integration_request = super(RazorpaySettings, self).create_request(self.data, "Host", \
- "Razorpay")
+ self.integration_request = create_request_log(self.data, "Host", "Razorpay")
return self.authorize_payment()
except Exception:
@@ -124,7 +111,7 @@ class RazorpaySettings(IntegrationService):
redirect_message = data.get('notes', {}).get('redirect_message') or None
try:
- resp = self.get_request("https://api.razorpay.com/v1/payments/{0}"
+ resp = make_get_request("https://api.razorpay.com/v1/payments/{0}"
.format(self.data.razorpay_payment_id), auth=(settings.api_key,
settings.api_secret))
@@ -201,7 +188,7 @@ def capture_payment(is_sandbox=False, sanbox_response=None):
data = json.loads(doc.data)
settings = controller.get_settings(data)
- resp = controller.post_request("https://api.razorpay.com/v1/payments/{0}/capture".format(data.get("razorpay_payment_id")),
+ resp = make_post_request("https://api.razorpay.com/v1/payments/{0}/capture".format(data.get("razorpay_payment_id")),
auth=(settings.api_key, settings.api_secret), data={"amount": data.get("amount")})
if resp.get("status") == "captured":
@@ -211,51 +198,4 @@ def capture_payment(is_sandbox=False, sanbox_response=None):
doc = frappe.get_doc("Integration Request", doc.name)
doc.status = "Failed"
doc.error = frappe.get_traceback()
- frappe.log_error(doc.error, '{0} Failed'.format(doc.name))
-
-@frappe.whitelist(allow_guest=True, xss_safe=True)
-def get_checkout_url(**kwargs):
- try:
- return frappe.get_doc("Razorpay Settings").get_payment_url(**kwargs)
- except Exception:
- frappe.respond_as_web_page(_("Something went wrong"),
- _("Looks like something is wrong with this site's Razorpay configuration. Don't worry! No payment has been made."),
- success=False,
- http_status_code=frappe.ValidationError.http_status_code)
-
-@frappe.whitelist()
-def get_service_details():
- return """
-
-
Steps to configure Service
-
- - Get Razorpay api credentials by login to:
-
- https://razorpay.com/
-
-
-
- - Setup credentials on Razorpay Settings doctype.
- Click on
-
- top right corner
-
-
- -
- After saving settings,
-
- Razorpay Integration Service and Save a document.
-
-
- -
- To view Razorpays payment logs,
-
-
-
-
- """
\ No newline at end of file
+ frappe.log_error(doc.error, '{0} Failed'.format(doc.name))
\ No newline at end of file
diff --git a/payments/payments/doctype/payment_gateway/__init__.py b/payments/payments/doctype/payment_gateway/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/payments/payments/doctype/payment_gateway/payment_gateway.js b/payments/payments/doctype/payment_gateway/payment_gateway.js
new file mode 100644
index 0000000..0eff5a5
--- /dev/null
+++ b/payments/payments/doctype/payment_gateway/payment_gateway.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Payment Gateway', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/payments/payments/doctype/payment_gateway/payment_gateway.json b/payments/payments/doctype/payment_gateway/payment_gateway.json
new file mode 100644
index 0000000..c7c551a
--- /dev/null
+++ b/payments/payments/doctype/payment_gateway/payment_gateway.json
@@ -0,0 +1,91 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "field:gateway",
+ "beta": 0,
+ "creation": "2015-12-15 22:26:45.221162",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "fields": [
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "gateway",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Gateway",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 1,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2017-03-09 12:40:56.176464",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Payment Gateway",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 0,
+ "delete": 0,
+ "email": 0,
+ "export": 0,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 0,
+ "read": 1,
+ "report": 0,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 0,
+ "submit": 0,
+ "write": 0
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 0,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/payments/payments/doctype/payment_gateway/payment_gateway.py b/payments/payments/doctype/payment_gateway/payment_gateway.py
new file mode 100644
index 0000000..80799e3
--- /dev/null
+++ b/payments/payments/doctype/payment_gateway/payment_gateway.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class PaymentGateway(Document):
+ pass
\ No newline at end of file
diff --git a/payments/payments/doctype/payment_gateway/test_payment_gateway.py b/payments/payments/doctype/payment_gateway/test_payment_gateway.py
new file mode 100644
index 0000000..2faf1a7
--- /dev/null
+++ b/payments/payments/doctype/payment_gateway/test_payment_gateway.py
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+# test_records = frappe.get_test_records('Payment Gateway')
+
+class TestPaymentGateway(unittest.TestCase):
+ pass