From ee667b6ccb8a2f75f61d90681e59463b5ec7fb71 Mon Sep 17 00:00:00 2001 From: Brett Nekolny Date: Fri, 22 Nov 2013 11:37:21 -0700 Subject: [PATCH 001/103] Updating the python-zuora library to work as a pypi install. --- MANIFEST.in | 1 + setup.py | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..d63e416 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include *.wsdl diff --git a/setup.py b/setup.py index d4730d6..29ed9f5 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,16 @@ #!/usr/bin/python -tt setupArgs = { - 'name': 'zuora', - 'version': '1.0.0.8', + 'name': 'mmf-zuora', + 'version': '1.0.0.9', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', 'description': 'Zuora client library.', - 'packages': ['zuora'], + 'packages': [ + 'zuora', + 'zuora.rest_wrapper', + ], } try: From 863972b91e03b2950d43f25adca10975277bdb09 Mon Sep 17 00:00:00 2001 From: Brad Culberson Date: Thu, 19 Dec 2013 14:31:15 -0700 Subject: [PATCH 002/103] use requests --- pip-requirements.txt | 1 + zuora/client.py | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/pip-requirements.txt b/pip-requirements.txt index d61e8cd..649e87c 100644 --- a/pip-requirements.txt +++ b/pip-requirements.txt @@ -1,2 +1,3 @@ # Suds, patched version of 0.3.6 by KCal. (Suds 0.3.6 through 0.4 have a bug around suds/sax/document.py) #-e git+https://github.com/kevincal/suds-patched@f9869d36fac3077da3b1351eb169518bbdd92ac3#egg=suds-dev +requests==1.2.3 diff --git a/zuora/client.py b/zuora/client.py index 8d753be..776709e 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -19,14 +19,20 @@ from datetime import datetime, date from os import path import re +import requests +import urllib2 as u2 from suds import WebFault from suds.client import Client from suds.sax.element import Element from suds.xsd.doctor import Import, ImportDoctor +from suds.transport.http import HttpAuthenticated, HttpTransport, TransportError +from suds.transport import Reply + import logging log = logging.getLogger(__name__) +log.setLevel(logging.DEBUG) # Tell suds to stop logging and stfu (it logs noise as errors) log_suds = logging.getLogger('suds') @@ -38,10 +44,30 @@ from rest_client import RestClient +class HttpTransportWithKeepAlive(HttpAuthenticated): + s = requests.Session() + + def open(self, request): + self.addcredentials(request) + return HttpTransport.open(self, request) + + def send(self, request): + self.addcredentials(request) + try: + req_response = HttpTransportWithKeepAlive.s.post(request.url, data=request.message, headers=request.headers) + if req_response.status_code in (202, 204): + return None + else: + return Reply(200, dict(req_response.headers), req_response.content) + except requests.exceptions.RequestException as e: + raise TransportError(e.message, None) + + class ZuoraException(Exception): """This is our base exception for the Zuora lib""" pass + class DoesNotExist(ZuoraException): """ Exception for when objects don't exist in Zuora @@ -103,7 +129,7 @@ def __init__(self, zuora_settings): self.base_dir + "/" + self.wsdl_file) self.client = Client(url=wsdl_file, doctor=schema_doctor, - cache=None) + cache=None, transport=HttpTransportWithKeepAlive()) # Force No Cache self.client.set_options(cache=None) From 8f5e337038e6f011f192a7956aab697fc8f0fa90 Mon Sep 17 00:00:00 2001 From: Brad Culberson Date: Fri, 20 Dec 2013 09:20:03 -0700 Subject: [PATCH 003/103] remove loggging level --- zuora/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 776709e..2dc0a38 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -32,7 +32,6 @@ import logging log = logging.getLogger(__name__) -log.setLevel(logging.DEBUG) # Tell suds to stop logging and stfu (it logs noise as errors) log_suds = logging.getLogger('suds') @@ -60,7 +59,7 @@ def send(self, request): else: return Reply(200, dict(req_response.headers), req_response.content) except requests.exceptions.RequestException as e: - raise TransportError(e.message, None) + raise TransportError(e.message, ) class ZuoraException(Exception): From 85fc9c69b46520aa7c24cdf976e791b848e000bf Mon Sep 17 00:00:00 2001 From: Brad Culberson Date: Thu, 26 Dec 2013 11:09:16 -0700 Subject: [PATCH 004/103] log and keep session on self --- zuora/client.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 2dc0a38..f6c5718 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -43,8 +43,11 @@ from rest_client import RestClient -class HttpTransportWithKeepAlive(HttpAuthenticated): - s = requests.Session() +class HttpTransportWithKeepAlive(HttpAuthenticated, object): + + def __init__(self): + super(HttpTransportWithKeepAlive, self).__init__() + self.s = requests.Session() def open(self, request): self.addcredentials(request) @@ -53,7 +56,9 @@ def open(self, request): def send(self, request): self.addcredentials(request) try: - req_response = HttpTransportWithKeepAlive.s.post(request.url, data=request.message, headers=request.headers) + req_response = self.s.post(request.url, data=request.message, headers=request.headers) + if req_response.status_code > 200: + log.warning("RESPONSE %s %s", req_response.status_code, req_response.content) if req_response.status_code in (202, 204): return None else: From a8737b8b3ba46e647c0c9bbfc737aa0c5818ba9d Mon Sep 17 00:00:00 2001 From: Brad Culberson Date: Thu, 26 Dec 2013 11:18:11 -0700 Subject: [PATCH 005/103] add logging --- zuora/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index f6c5718..8a31753 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -58,7 +58,7 @@ def send(self, request): try: req_response = self.s.post(request.url, data=request.message, headers=request.headers) if req_response.status_code > 200: - log.warning("RESPONSE %s %s", req_response.status_code, req_response.content) + log.debug("RESPONSE %s %s", req_response.status_code, req_response.content) if req_response.status_code in (202, 204): return None else: From f411e8a80c9778d886cdec9c76bc07dd33df4d34 Mon Sep 17 00:00:00 2001 From: Brad Culberson Date: Thu, 26 Dec 2013 11:24:47 -0700 Subject: [PATCH 006/103] cleanup logging --- zuora/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 8a31753..f473fd7 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -58,7 +58,11 @@ def send(self, request): try: req_response = self.s.post(request.url, data=request.message, headers=request.headers) if req_response.status_code > 200: - log.debug("RESPONSE %s %s", req_response.status_code, req_response.content) + # INVALID_SESSION is common worflow to login + if "INVALID_SESSION" not in req_response.content: + log.error("RESPONSE %s %s", req_response.status_code, req_response.content) + else: + log.debug("RESPONSE %s %s", req_response.status_code, req_response.content) if req_response.status_code in (202, 204): return None else: From 682cef92bfb85225a0299e8d074d60c6983321eb Mon Sep 17 00:00:00 2001 From: Brad Culberson Date: Thu, 26 Dec 2013 12:51:51 -0700 Subject: [PATCH 007/103] use urllib2 --- pip-requirements.txt | 2 +- zuora/client.py | 25 ++++++++----------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/pip-requirements.txt b/pip-requirements.txt index 649e87c..62d3601 100644 --- a/pip-requirements.txt +++ b/pip-requirements.txt @@ -1,3 +1,3 @@ # Suds, patched version of 0.3.6 by KCal. (Suds 0.3.6 through 0.4 have a bug around suds/sax/document.py) #-e git+https://github.com/kevincal/suds-patched@f9869d36fac3077da3b1351eb169518bbdd92ac3#egg=suds-dev -requests==1.2.3 +httplib2==0.7.2 diff --git a/zuora/client.py b/zuora/client.py index f473fd7..2d64f6d 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -19,8 +19,7 @@ from datetime import datetime, date from os import path import re -import requests -import urllib2 as u2 +import httplib2 from suds import WebFault from suds.client import Client @@ -47,7 +46,7 @@ class HttpTransportWithKeepAlive(HttpAuthenticated, object): def __init__(self): super(HttpTransportWithKeepAlive, self).__init__() - self.s = requests.Session() + self.http = httplib2.Http() def open(self, request): self.addcredentials(request) @@ -55,20 +54,12 @@ def open(self, request): def send(self, request): self.addcredentials(request) - try: - req_response = self.s.post(request.url, data=request.message, headers=request.headers) - if req_response.status_code > 200: - # INVALID_SESSION is common worflow to login - if "INVALID_SESSION" not in req_response.content: - log.error("RESPONSE %s %s", req_response.status_code, req_response.content) - else: - log.debug("RESPONSE %s %s", req_response.status_code, req_response.content) - if req_response.status_code in (202, 204): - return None - else: - return Reply(200, dict(req_response.headers), req_response.content) - except requests.exceptions.RequestException as e: - raise TransportError(e.message, ) + if request.message: + headers, message = self.http.request(request.url, "POST", body=request.message, headers=request.headers) + else: + headers, message = self.http.request(request.url, "GET", headers=request.headers) + response = Reply(200, headers, message) + return response class ZuoraException(Exception): From df382b930e56aad3e07a72aaf71b5d7fa559a7f6 Mon Sep 17 00:00:00 2001 From: Brad Culberson Date: Thu, 26 Dec 2013 12:59:12 -0700 Subject: [PATCH 008/103] handle login faster --- zuora/client.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 2d64f6d..86bf498 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -146,20 +146,14 @@ def call(self, fn, *args, **kwargs): """ try: + # this is low cost because of the keep alive + # an auth failure gives a 500 which closes connection + self.login() response = fn(*args, **kwargs) except WebFault as err: - if err.fault.faultcode == "fns:INVALID_SESSION": - self.login() - try: - response = fn(*args, **kwargs) - except Exception as error: - log.error("Zuora: Unexpected Error. %s" % error) - raise ZuoraException("Zuora: Unexpected Error. %s"\ - % error) - else: - log.error("WebFault. Invalid Session. %s" % err.__dict__) - raise ZuoraException("WebFault. Invalid Session. %s"\ - % err.__dict__) + log.error("WebFault. Invalid Session. %s" % err.__dict__) + raise ZuoraException("WebFault. Invalid Session. %s"\ + % err.__dict__) except Exception as error: log.error("Zuora: Unexpected Error. %s" % error) raise ZuoraException("Zuora: Unexpected Error. %s" % error) From e148d6bbe0d32018b5004e2a509d38217c8f4646 Mon Sep 17 00:00:00 2001 From: Brad Culberson Date: Thu, 26 Dec 2013 13:11:00 -0700 Subject: [PATCH 009/103] cleanup unneeded code --- zuora/client.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 86bf498..168d672 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -25,7 +25,7 @@ from suds.client import Client from suds.sax.element import Element from suds.xsd.doctor import Import, ImportDoctor -from suds.transport.http import HttpAuthenticated, HttpTransport, TransportError +from suds.transport.http import HttpAuthenticated, HttpTransport from suds.transport import Reply @@ -49,15 +49,10 @@ def __init__(self): self.http = httplib2.Http() def open(self, request): - self.addcredentials(request) return HttpTransport.open(self, request) def send(self, request): - self.addcredentials(request) - if request.message: - headers, message = self.http.request(request.url, "POST", body=request.message, headers=request.headers) - else: - headers, message = self.http.request(request.url, "GET", headers=request.headers) + headers, message = self.http.request(request.url, "POST", body=request.message, headers=request.headers) response = Reply(200, headers, message) return response From 8d681de1f045b2cd8116387f051010f41f399bdc Mon Sep 17 00:00:00 2001 From: Brad Culberson Date: Thu, 26 Dec 2013 13:32:47 -0700 Subject: [PATCH 010/103] fix self vs static --- zuora/client.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 168d672..65a248d 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -78,15 +78,6 @@ class MissingRequired(ZuoraException): # main class class Zuora: - #: Soap Service Client - client = None - - #: Currency - currency = 'USD' - - #: SessionID (TODO: put this into memcache) - session_id = None - def __init__(self, zuora_settings): """ Usage example: @@ -131,6 +122,8 @@ def __init__(self, zuora_settings): # Create the rest client self.rest_client = RestClient(zuora_settings) + self.session_id = None + # Client Create def call(self, fn, *args, **kwargs): """ @@ -141,14 +134,20 @@ def call(self, fn, *args, **kwargs): """ try: - # this is low cost because of the keep alive - # an auth failure gives a 500 which closes connection - self.login() + if self.session_id is None: + self.login() response = fn(*args, **kwargs) except WebFault as err: - log.error("WebFault. Invalid Session. %s" % err.__dict__) - raise ZuoraException("WebFault. Invalid Session. %s"\ - % err.__dict__) + if err.fault.faultcode == "fns:INVALID_SESSION": + self.login() + try: + response = fn(*args, **kwargs) + except Exception as error: + log.error("Zuora: Unexpected Error. %s" % error) + raise ZuoraException("Zuora: Unexpected Error. %s" % error) + else: + log.error("WebFault. Invalid Session. %s" % err.__dict__) + raise ZuoraException("WebFault. Invalid Session. %s" % err.__dict__) except Exception as error: log.error("Zuora: Unexpected Error. %s" % error) raise ZuoraException("Zuora: Unexpected Error. %s" % error) From 76db3f4ff92fb6824f19ec68fdb523089fcdf4f0 Mon Sep 17 00:00:00 2001 From: Brad Culberson Date: Fri, 27 Dec 2013 08:28:12 -0700 Subject: [PATCH 011/103] put an expiration on previous session usage --- zuora/client.py | 49 ++++++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 65a248d..52f3ddf 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -27,7 +27,7 @@ from suds.xsd.doctor import Import, ImportDoctor from suds.transport.http import HttpAuthenticated, HttpTransport from suds.transport import Reply - +from suds.sax.text import Text import logging log = logging.getLogger(__name__) @@ -124,6 +124,10 @@ def __init__(self, zuora_settings): self.session_id = None + def reset_transport(self): + self.client.options.transport = HttpTransportWithKeepAlive() + self.session_id = None + # Client Create def call(self, fn, *args, **kwargs): """ @@ -133,24 +137,29 @@ def call(self, fn, *args, **kwargs): :returns: the client response """ - try: - if self.session_id is None: - self.login() - response = fn(*args, **kwargs) - except WebFault as err: - if err.fault.faultcode == "fns:INVALID_SESSION": + for i in range(0, 3): + if self.session_id is None or self.session_expiration <= datetime.datetime.now(): self.login() - try: - response = fn(*args, **kwargs) - except Exception as error: - log.error("Zuora: Unexpected Error. %s" % error) - raise ZuoraException("Zuora: Unexpected Error. %s" % error) - else: - log.error("WebFault. Invalid Session. %s" % err.__dict__) - raise ZuoraException("WebFault. Invalid Session. %s" % err.__dict__) - except Exception as error: - log.error("Zuora: Unexpected Error. %s" % error) - raise ZuoraException("Zuora: Unexpected Error. %s" % error) + try: + response = fn(*args, **kwargs) + # REMOVE THIS REALLY UNEXPECTED CASE + # AFTER WE VALIDATE IT NEVER HAPPENS IN PROD + if isinstance(response, Text): + log.error("Zuora: REALLY Unexpected Response!!!! %s, RESETTING TO RETRY", response) + self.reset_transport() + else: + log.debug("Zuora: Successful Response %s", response) + break + except WebFault as err: + if err.fault.faultcode == "fns:INVALID_SESSION": + log.warn("Zuora: Invalid Session, LOGGING IN") + self.session_id = None + else: + log.error("WebFault. %s", err.__dict__) + raise ZuoraException("WebFault. %s" % err.__dict__) + except Exception as error: + log.error("Zuora: Unexpected Error. %s" % error) + raise ZuoraException("Zuora: Unexpected Error. %s" % error) return response @@ -199,8 +208,10 @@ def login(self): Creates the SOAP SessionHeader with the correct session_id from Zuora TODO: investigate methodology to persist session_id across sessions - - look at custom capabilities -- sqlalchemy caching - WEB-935 perhaps + we are currently keeping the session in memory for < 8 hours + which is the session expiration time of Zuora """ + self.session_expiration = datetime.datetime.now() + datetime.timedelta(hours=7, minutes=55) login_response = self.client.service.login(username=self.username, password=self.password) self.session_id = login_response.Session From 857b8b6e4bcc6e6af765fa527a41f3222b2bc6e7 Mon Sep 17 00:00:00 2001 From: Brad Culberson Date: Fri, 27 Dec 2013 08:45:01 -0700 Subject: [PATCH 012/103] fix datetime compare --- zuora/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 52f3ddf..b8616b5 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -16,7 +16,7 @@ z = zuora.Zuora(SETTINGS) account = z.get_account(23432) """ -from datetime import datetime, date +from datetime import datetime, date, timedelta from os import path import re import httplib2 @@ -138,7 +138,7 @@ def call(self, fn, *args, **kwargs): """ for i in range(0, 3): - if self.session_id is None or self.session_expiration <= datetime.datetime.now(): + if self.session_id is None or self.session_expiration <= datetime.now(): self.login() try: response = fn(*args, **kwargs) @@ -211,7 +211,7 @@ def login(self): we are currently keeping the session in memory for < 8 hours which is the session expiration time of Zuora """ - self.session_expiration = datetime.datetime.now() + datetime.timedelta(hours=7, minutes=55) + self.session_expiration = datetime.now() + timedelta(hours=7, minutes=55) login_response = self.client.service.login(username=self.username, password=self.password) self.session_id = login_response.Session From 1017d578baca8a3d2a667d5902d03c1a8c0e3440 Mon Sep 17 00:00:00 2001 From: Brad Culberson Date: Tue, 7 Jan 2014 17:27:35 -0600 Subject: [PATCH 013/103] add better retry on error, timeout nicely --- zuora/client.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index b8616b5..f43e2ed 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -52,7 +52,7 @@ def open(self, request): return HttpTransport.open(self, request) def send(self, request): - headers, message = self.http.request(request.url, "POST", body=request.message, headers=request.headers) + headers, message = self.http.request(request.url, "POST", body=request.message, headers=request.headers, timeout=1) response = Reply(200, headers, message) return response @@ -68,6 +68,7 @@ class DoesNotExist(ZuoraException): """ pass + class MissingRequired(ZuoraException): """ Exception for when a required parameter is missing @@ -136,20 +137,21 @@ def call(self, fn, *args, **kwargs): :returns: the client response """ + last_error = None for i in range(0, 3): if self.session_id is None or self.session_expiration <= datetime.now(): self.login() try: response = fn(*args, **kwargs) - # REMOVE THIS REALLY UNEXPECTED CASE - # AFTER WE VALIDATE IT NEVER HAPPENS IN PROD + # THIS OCCASIONALLY HAPPENS + # AND ITS BAD WE NEED TO RESET if isinstance(response, Text): log.error("Zuora: REALLY Unexpected Response!!!! %s, RESETTING TO RETRY", response) self.reset_transport() else: log.debug("Zuora: Successful Response %s", response) - break + return response except WebFault as err: if err.fault.faultcode == "fns:INVALID_SESSION": log.warn("Zuora: Invalid Session, LOGGING IN") @@ -159,9 +161,10 @@ def call(self, fn, *args, **kwargs): raise ZuoraException("WebFault. %s" % err.__dict__) except Exception as error: log.error("Zuora: Unexpected Error. %s" % error) - raise ZuoraException("Zuora: Unexpected Error. %s" % error) + last_error = error + self.reset_transport() - return response + raise ZuoraException("Zuora: Unexpected Error. %s" % last_error) # Client Create def create(self, z_object): From 82ab876ee51430f8444e3b58d4c3fbb5009f8019 Mon Sep 17 00:00:00 2001 From: Dylan Mankey Date: Wed, 8 Jan 2014 10:26:56 -0600 Subject: [PATCH 014/103] fixing name_underscore_fix to strip the name field --- zuora/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 2dc0a38..d3aaf8f 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1934,6 +1934,6 @@ def name_underscore_fix(name_field): """ Make sure the name field has a value, otherwise return an underscore """ - if name_field and name_field != '': + if name_field and name_field.strip() != '': return name_field return '_' From d60edf576b838807b2623600eff7d8ab5d43a3ab Mon Sep 17 00:00:00 2001 From: Dylan Mankey Date: Wed, 8 Jan 2014 17:40:02 -0600 Subject: [PATCH 015/103] updating version from 0.9 to 0.14 due to some strange state in pip-freeze-prod which was pointing at version 0.13 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 29ed9f5..81ed1b3 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'mmf-zuora', - 'version': '1.0.0.9', + 'version': '1.0.0.14', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From 838f9b2dd9d221328761868df327ecb7b7d93b2d Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Mon, 16 Dec 2013 17:23:12 -0700 Subject: [PATCH 016/103] Added the create_payment_method method. --- zuora/client.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/zuora/client.py b/zuora/client.py index 48cff48..7f68ed0 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -396,6 +396,24 @@ def cancel_subscription(self, subscription_key, effective_date=None): subscription_key, jsonParams={'cancellationEffectiveDate': effective_date}) return response + + def create_payment_method(self, baid=None, user_email=None): + payment_method = self.client.factory.create('ns2:PaymentMethod') + if baid: + payment_method.PaypalBaid = baid + # Paypal user e-mail required + payment_method.PaypalEmail = user_email + payment_method.PaypalType = 'ExpressCheckout' + payment_method.Type = 'PayPal' + + # Create Payment Method + response = self.create(payment_method) + if not isinstance(response, list) or not response[0].Success: + raise ZuoraException( + "Unknown Error creating Payment Method. %s" % response) + payment_method.Id = response[0].Id + + return payment_method def create_active_account(self, zAccount=None, zContact=None, payment_method_id=None, user=None, From 0145846c7ecfa4b162b18814c0f3f8bc4ba37f9e Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 1 Jan 2014 20:20:41 -0700 Subject: [PATCH 017/103] Fixed the create payment method to include account id. --- zuora/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 7f68ed0..5249b2f 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -397,8 +397,10 @@ def cancel_subscription(self, subscription_key, effective_date=None): jsonParams={'cancellationEffectiveDate': effective_date}) return response - def create_payment_method(self, baid=None, user_email=None): + def create_payment_method(self, baid=None, user_email=None, + account_id=None): payment_method = self.client.factory.create('ns2:PaymentMethod') + payment_method.AccountId = account_id if baid: payment_method.PaypalBaid = baid # Paypal user e-mail required From b1007502ad54c202405f1751a4aed8e6f53e4ce2 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 2 Jan 2014 11:50:34 -0700 Subject: [PATCH 018/103] Added the ability to specify a gateway name. --- zuora/client.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 5249b2f..c474475 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -420,14 +420,16 @@ def create_payment_method(self, baid=None, user_email=None, def create_active_account(self, zAccount=None, zContact=None, payment_method_id=None, user=None, billing_address=None, shipping_address=None, - site_name=None, prepaid=False): + site_name=None, prepaid=False, + gateway_name=None): """ Create an Active Account for use in Subscribe() """ # Create Account if it doesn't exist if not zAccount: zAccount = self.make_account(user=user, site_name=site_name, - billing_address=billing_address) + billing_address=billing_address, + gateway_name=gateway_name) # Create Bill-To Contact on Account if not zContact: @@ -1496,7 +1498,8 @@ def get_subscriptions(self, subscription_id=None, account_id=None, return zRecords def make_account(self, user=None, currency='USD', status="Draft", - lazy=False, site_name=None, billing_address=None): + lazy=False, site_name=None, billing_address=None, + gateway_name=None): """ The customer's account. Zuora uses the Account object to track all subscriptions, usage, and transactions for a single account to be @@ -1541,8 +1544,10 @@ def make_account(self, user=None, currency='USD', status="Draft", zAccount.PaymentTerm = 'Due Upon Receipt' zAccount.Status = status + if gateway_name: + zAccount.PaymentGateway = gateway_name # Determine which Payment Gateway to use, if specified - if self.authorize_gateway: + elif self.authorize_gateway: zAccount.PaymentGateway = self.authorize_gateway if self.create_test_users: From a500a30c33955da768b70b816e4c322fdefec866 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 2 Jan 2014 13:56:11 -0700 Subject: [PATCH 019/103] Temporary logging change for the jerks at Zuora. --- zuora/client.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index c474475..d511ef7 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -33,8 +33,13 @@ log = logging.getLogger(__name__) # Tell suds to stop logging and stfu (it logs noise as errors) -log_suds = logging.getLogger('suds') -log_suds.propagate = False +#log_suds = logging.getLogger('suds') +#log_suds.propagate = False + +# Enable these two logging lines if you need to see all the SOAP info +# PLEASE KEEP THESE COMMENTED OUT IF NOT ACTIVELY USING (EXTREMLY VERBOSE) +logging.getLogger('suds.client').setLevel(logging.DEBUG) +logging.getLogger('suds.transport').setLevel(logging.DEBUG) SOAP_TIMESTAMP = '%Y-%m-%dT%H:%M:%S-06:00' From bf870eb7770b4ebfb2fc48969d5712c4f1e73564 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 2 Jan 2014 14:08:42 -0700 Subject: [PATCH 020/103] Debug logging again... --- zuora/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index d511ef7..1782142 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -33,8 +33,8 @@ log = logging.getLogger(__name__) # Tell suds to stop logging and stfu (it logs noise as errors) -#log_suds = logging.getLogger('suds') -#log_suds.propagate = False +log_suds = logging.getLogger('suds') +log_suds.propagate = False # Enable these two logging lines if you need to see all the SOAP info # PLEASE KEEP THESE COMMENTED OUT IF NOT ACTIVELY USING (EXTREMLY VERBOSE) From c198b79fa1641f0a25cbd9b87096350af5517863 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 2 Jan 2014 16:27:22 -0700 Subject: [PATCH 021/103] More logging for debug stuff. --- zuora/client.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 1782142..b6c875e 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -36,11 +36,6 @@ log_suds = logging.getLogger('suds') log_suds.propagate = False -# Enable these two logging lines if you need to see all the SOAP info -# PLEASE KEEP THESE COMMENTED OUT IF NOT ACTIVELY USING (EXTREMLY VERBOSE) -logging.getLogger('suds.client').setLevel(logging.DEBUG) -logging.getLogger('suds.transport').setLevel(logging.DEBUG) - SOAP_TIMESTAMP = '%Y-%m-%dT%H:%M:%S-06:00' @@ -149,6 +144,8 @@ def call(self, fn, *args, **kwargs): self.login() try: response = fn(*args, **kwargs) + log.info("Call sent: %s" % self.client.last_sent()) + log.info("Call received: %s" % self.client.last_received()) # THIS OCCASIONALLY HAPPENS # AND ITS BAD WE NEED TO RESET if isinstance(response, Text): From b08f777ea3b182b924170c46161b221b2228f4e4 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 2 Jan 2014 22:41:53 -0700 Subject: [PATCH 022/103] Made the payment method create lazy. --- zuora/client.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index b6c875e..89e4642 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -399,23 +399,14 @@ def cancel_subscription(self, subscription_key, effective_date=None): jsonParams={'cancellationEffectiveDate': effective_date}) return response - def create_payment_method(self, baid=None, user_email=None, - account_id=None): + def create_payment_method(self, baid=None, user_email=None): payment_method = self.client.factory.create('ns2:PaymentMethod') - payment_method.AccountId = account_id if baid: payment_method.PaypalBaid = baid # Paypal user e-mail required payment_method.PaypalEmail = user_email payment_method.PaypalType = 'ExpressCheckout' payment_method.Type = 'PayPal' - - # Create Payment Method - response = self.create(payment_method) - if not isinstance(response, list) or not response[0].Success: - raise ZuoraException( - "Unknown Error creating Payment Method. %s" % response) - payment_method.Id = response[0].Id return payment_method @@ -1649,7 +1640,6 @@ def make_rate_plan_data(self, product_rate_plan_id): # Build Rate Plan zRatePlan = self.client.factory.create('ns0:RatePlan') - zRatePlan.AmendmentType = "NewProduct" zRatePlan.ProductRatePlanId = product_rate_plan_id # Build Rate Plan Data @@ -1762,7 +1752,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, user=None, billing_address=None, shipping_address=None, start_date=None, site_name=None, discount_product_rate_plan_id=None, - external_payment_method=None): + external_payment_method=None, gateway_name=None): """ The subscribe() call bundles the information required to create one or more new subscriptions. This is a combined call that you can use @@ -1783,12 +1773,12 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, :param str subscription_name: The name of the subscription. This is a\ unique identifier. If not specified, Zuora will auto-create a name. """ - # zAccount = self.client.factory.create('ns2:Account') #Used to be called even if account existed, pulling it out for now # Get or Create Account if not zAccount: zAccount = self.make_account(user=user, site_name=site_name, - billing_address=billing_address) + billing_address=billing_address, + gateway_name=gateway_name) if not zContact and not zAccount.Id: # Create Contact From d2ce8a5b359e77959d374900d429df03b0fe10bf Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 2 Jan 2014 23:20:47 -0700 Subject: [PATCH 023/103] MMMMMMOOOOOOAAARRRRR --- zuora/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zuora/client.py b/zuora/client.py index 89e4642..1efde48 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1792,6 +1792,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, billing_address=shipping_address, zAccount=zAccount) + log.info("product_rate_plan_id: %s" % product_rate_plan_id) # Get Rate Plan & Build Rate Plan Data zRatePlanData = self.make_rate_plan_data(product_rate_plan_id) From 3a34c67326d870d690cc0a7d9cd8c5440d9831d4 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 2 Jan 2014 23:26:44 -0700 Subject: [PATCH 024/103] Why even add commit messages at this point... --- zuora/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 1efde48..4f96e8c 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1641,6 +1641,7 @@ def make_rate_plan_data(self, product_rate_plan_id): # Build Rate Plan zRatePlan = self.client.factory.create('ns0:RatePlan') zRatePlan.ProductRatePlanId = product_rate_plan_id + log.info("zRatePlan:product_rate_plan_id: %s" % product_rate_plan_id) # Build Rate Plan Data zRatePlanData = self.client.factory.create('ns0:RatePlanData') @@ -1792,7 +1793,6 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, billing_address=shipping_address, zAccount=zAccount) - log.info("product_rate_plan_id: %s" % product_rate_plan_id) # Get Rate Plan & Build Rate Plan Data zRatePlanData = self.make_rate_plan_data(product_rate_plan_id) From 2a4749b254b347ea99b119cf7ecf36aaf3c495b2 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 2 Jan 2014 23:33:47 -0700 Subject: [PATCH 025/103] dfasfdsafljkda --- zuora/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zuora/client.py b/zuora/client.py index 4f96e8c..f54b608 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1794,9 +1794,11 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, zAccount=zAccount) # Get Rate Plan & Build Rate Plan Data + log.info("make_rate_plan_data executing once!") zRatePlanData = self.make_rate_plan_data(product_rate_plan_id) if discount_product_rate_plan_id: + log.info("make_rate_plan_data executing twice!") zDiscountRatePlanData = self.make_rate_plan_data(discount_product_rate_plan_id) else: zDiscountRatePlanData = None From 497958b251bba931cef9d3a1a3dd21a96cd8b79d Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 2 Jan 2014 23:37:15 -0700 Subject: [PATCH 026/103] new debug --- zuora/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index f54b608..35d0b1c 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1798,7 +1798,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, zRatePlanData = self.make_rate_plan_data(product_rate_plan_id) if discount_product_rate_plan_id: - log.info("make_rate_plan_data executing twice!") + log.info("discount_product_rate_plan_id: %s" % discount_product_rate_plan_id) zDiscountRatePlanData = self.make_rate_plan_data(discount_product_rate_plan_id) else: zDiscountRatePlanData = None From 23395f4b46eff96f6f4b190945d3553541d5dcc1 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Fri, 3 Jan 2014 00:21:15 -0700 Subject: [PATCH 027/103] Added the ability to auto-activate the account. --- zuora/client.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 35d0b1c..60cea27 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1492,7 +1492,7 @@ def get_subscriptions(self, subscription_id=None, account_id=None, def make_account(self, user=None, currency='USD', status="Draft", lazy=False, site_name=None, billing_address=None, - gateway_name=None): + gateway_name=None, auto_activate=False): """ The customer's account. Zuora uses the Account object to track all subscriptions, usage, and transactions for a single account to be @@ -1535,7 +1535,9 @@ def make_account(self, user=None, currency='USD', status="Draft", (name_underscore_fix(user["last_name"]), name_underscore_fix(user["first_name"])) zAccount.PaymentTerm = 'Due Upon Receipt' - zAccount.Status = status + # Don't specify a status if you want the account to auto-activate + if not auto_activate: + zAccount.Status = status if gateway_name: zAccount.PaymentGateway = gateway_name @@ -1753,7 +1755,8 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, user=None, billing_address=None, shipping_address=None, start_date=None, site_name=None, discount_product_rate_plan_id=None, - external_payment_method=None, gateway_name=None): + external_payment_method=None, gateway_name=None, + auto_activate=False): """ The subscribe() call bundles the information required to create one or more new subscriptions. This is a combined call that you can use @@ -1779,7 +1782,8 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, if not zAccount: zAccount = self.make_account(user=user, site_name=site_name, billing_address=billing_address, - gateway_name=gateway_name) + gateway_name=gateway_name, + auto_activate=auto_activate) if not zContact and not zAccount.Id: # Create Contact From 0234e15b5da4e84cddb09dca15aa66e774682001 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Fri, 3 Jan 2014 00:52:08 -0700 Subject: [PATCH 028/103] Switched to setting the account status instead of auto-activate. --- zuora/client.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 60cea27..0ae5941 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1492,7 +1492,7 @@ def get_subscriptions(self, subscription_id=None, account_id=None, def make_account(self, user=None, currency='USD', status="Draft", lazy=False, site_name=None, billing_address=None, - gateway_name=None, auto_activate=False): + gateway_name=None): """ The customer's account. Zuora uses the Account object to track all subscriptions, usage, and transactions for a single account to be @@ -1535,9 +1535,7 @@ def make_account(self, user=None, currency='USD', status="Draft", (name_underscore_fix(user["last_name"]), name_underscore_fix(user["first_name"])) zAccount.PaymentTerm = 'Due Upon Receipt' - # Don't specify a status if you want the account to auto-activate - if not auto_activate: - zAccount.Status = status + zAccount.Status = status if gateway_name: zAccount.PaymentGateway = gateway_name @@ -1756,7 +1754,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, start_date=None, site_name=None, discount_product_rate_plan_id=None, external_payment_method=None, gateway_name=None, - auto_activate=False): + account_status="Draft"): """ The subscribe() call bundles the information required to create one or more new subscriptions. This is a combined call that you can use @@ -1783,7 +1781,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, zAccount = self.make_account(user=user, site_name=site_name, billing_address=billing_address, gateway_name=gateway_name, - auto_activate=auto_activate) + status=account_status) if not zContact and not zAccount.Id: # Create Contact From d5a393e18d8b947451b4749faf5a8acc18113766 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Fri, 3 Jan 2014 10:04:14 -0700 Subject: [PATCH 029/103] Made the subscription call only pass the account id if the gateway was specified. --- zuora/client.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 0ae5941..8eee729 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1537,6 +1537,7 @@ def make_account(self, user=None, currency='USD', status="Draft", zAccount.PaymentTerm = 'Due Upon Receipt' zAccount.Status = status + # Specify what gateway to use for payments for the user if gateway_name: zAccount.PaymentGateway = gateway_name # Determine which Payment Gateway to use, if specified @@ -1641,7 +1642,6 @@ def make_rate_plan_data(self, product_rate_plan_id): # Build Rate Plan zRatePlan = self.client.factory.create('ns0:RatePlan') zRatePlan.ProductRatePlanId = product_rate_plan_id - log.info("zRatePlan:product_rate_plan_id: %s" % product_rate_plan_id) # Build Rate Plan Data zRatePlanData = self.client.factory.create('ns0:RatePlanData') @@ -1753,8 +1753,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, user=None, billing_address=None, shipping_address=None, start_date=None, site_name=None, discount_product_rate_plan_id=None, - external_payment_method=None, gateway_name=None, - account_status="Draft"): + external_payment_method=None, gateway_name=None): """ The subscribe() call bundles the information required to create one or more new subscriptions. This is a combined call that you can use @@ -1780,8 +1779,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, if not zAccount: zAccount = self.make_account(user=user, site_name=site_name, billing_address=billing_address, - gateway_name=gateway_name, - status=account_status) + gateway_name=gateway_name) if not zContact and not zAccount.Id: # Create Contact @@ -1796,11 +1794,9 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, zAccount=zAccount) # Get Rate Plan & Build Rate Plan Data - log.info("make_rate_plan_data executing once!") zRatePlanData = self.make_rate_plan_data(product_rate_plan_id) if discount_product_rate_plan_id: - log.info("discount_product_rate_plan_id: %s" % discount_product_rate_plan_id) zDiscountRatePlanData = self.make_rate_plan_data(discount_product_rate_plan_id) else: zDiscountRatePlanData = None @@ -1857,7 +1853,15 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # Subscribe zSubscribeRequest = self.client.factory.create('ns0:SubscribeRequest') - zSubscribeRequest.Account = zAccount + # If we defined the gateway, the account is created by now + # And requires sending only the id of the existing account + if gateway_name: + # Build Account + subscribe_account = self.client.factory.create('ns2:Account') + subscribe_account.Id = zAccount.Id + else: + subscribe_account = zAccount + zSubscribeRequest.Account = subscribe_account zSubscribeRequest.BillToContact = zContact # Add the shipping contact if it exists if zShippingContact: From 90595d5a25b41ab11797f45b9d170df2aff25171 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Fri, 3 Jan 2014 13:25:11 -0700 Subject: [PATCH 030/103] Made sure we are creating all the required fields (BillTo, SoldTo, Currency, Billing Batch and Payment Term). --- zuora/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 8eee729..98a7792 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1540,6 +1540,7 @@ def make_account(self, user=None, currency='USD', status="Draft", # Specify what gateway to use for payments for the user if gateway_name: zAccount.PaymentGateway = gateway_name + # Determine which Payment Gateway to use, if specified elif self.authorize_gateway: zAccount.PaymentGateway = self.authorize_gateway @@ -1781,7 +1782,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, billing_address=billing_address, gateway_name=gateway_name) - if not zContact and not zAccount.Id: + if not zContact: # Create Contact zContact = self.make_contact(user=user, billing_address=billing_address, @@ -1866,6 +1867,9 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # Add the shipping contact if it exists if zShippingContact: zSubscribeRequest.SoldToContact = zShippingContact + # Otherwise default to the billing contact + else: + zSubscribeRequest.SoldToContact = zSubscribeRequest.BillToContact zSubscribeRequest.SubscriptionData = zSubscriptionData zSubscribeRequest.SubscribeOptions = zSubscriptionOptions From a4c690b914af966dff9db3949fb8913d45ed7c37 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Fri, 3 Jan 2014 14:38:50 -0700 Subject: [PATCH 031/103] Added an update to activate the account if it wasn't yet active. --- zuora/client.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 98a7792..1f55eeb 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -444,6 +444,18 @@ def create_active_account(self, zAccount=None, zContact=None, else: zPaymentMethod = None + self.activate_account(zAccount, zContact, + zShippingContact=zShippingContact, + payment_method_id=payment_method_id, + prepaid=prepaid) + + return {'account': zAccount, 'contact': zContact, + 'payment_method': zPaymentMethod, + 'shipping_contact': zShippingContact} + + def activate_account(self, zAccount, zContact, zShippingContact=None, + payment_method=None, payment_method_id=None, + prepaid=None): # Now Update the Draft Account to be Active zAccountUpdate = self.client.factory.create('ns2:Account') zAccountUpdate.Id = zAccount.Id @@ -457,6 +469,8 @@ def create_active_account(self, zAccount=None, zContact=None, if payment_method_id and not prepaid: zAccountUpdate.DefaultPaymentMethodId = payment_method_id zAccountUpdate.AutoPay = True + elif payment_method: + zAccountUpdate.AutoPay = True else: zAccountUpdate.AutoPay = False response = self.update(zAccountUpdate) @@ -464,10 +478,6 @@ def create_active_account(self, zAccount=None, zContact=None, raise ZuoraException( "Unknown Error updating Account. %s" % response) - return {'account': zAccount, 'contact': zContact, - 'payment_method': zPaymentMethod, - 'shipping_contact': zShippingContact} - def get_account(self, user_id): """ Checks to see if the loaded user has an account @@ -1793,7 +1803,13 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, zShippingContact = self.make_contact(user=user, billing_address=shipping_address, zAccount=zAccount) - + + # Update the account if not active yet, requires BuildToId and SoldToId + if getattr(zAccount, 'Status') != 'Active': + self.activate_account(zAccount, zContact, + zShippingContact=zShippingContact, + payment_method=payment_method) + # Get Rate Plan & Build Rate Plan Data zRatePlanData = self.make_rate_plan_data(product_rate_plan_id) From d728c371d77ede7511675a91416a7d814b6448b1 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Fri, 3 Jan 2014 15:36:55 -0700 Subject: [PATCH 032/103] Removed the AutoPay set to True for Paypal. --- zuora/client.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 1f55eeb..d0de7b9 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -454,8 +454,7 @@ def create_active_account(self, zAccount=None, zContact=None, 'shipping_contact': zShippingContact} def activate_account(self, zAccount, zContact, zShippingContact=None, - payment_method=None, payment_method_id=None, - prepaid=None): + payment_method_id=None, prepaid=None): # Now Update the Draft Account to be Active zAccountUpdate = self.client.factory.create('ns2:Account') zAccountUpdate.Id = zAccount.Id @@ -469,8 +468,6 @@ def activate_account(self, zAccount, zContact, zShippingContact=None, if payment_method_id and not prepaid: zAccountUpdate.DefaultPaymentMethodId = payment_method_id zAccountUpdate.AutoPay = True - elif payment_method: - zAccountUpdate.AutoPay = True else: zAccountUpdate.AutoPay = False response = self.update(zAccountUpdate) @@ -1807,8 +1804,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # Update the account if not active yet, requires BuildToId and SoldToId if getattr(zAccount, 'Status') != 'Active': self.activate_account(zAccount, zContact, - zShippingContact=zShippingContact, - payment_method=payment_method) + zShippingContact=zShippingContact) # Get Rate Plan & Build Rate Plan Data zRatePlanData = self.make_rate_plan_data(product_rate_plan_id) From fd2891287ae3a8bc04e659c04e8af52447cc526f Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Fri, 3 Jan 2014 17:23:40 -0700 Subject: [PATCH 033/103] Making it so that specifying a gateway causes the account, contact, billtocontact, and soldtocontact to get created at the time of the subscribe call. --- zuora/client.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index d0de7b9..8ba6e94 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1558,8 +1558,10 @@ def make_account(self, user=None, currency='USD', status="Draft", if site_name: zAccount.User_Site__c = site_name - - if lazy: + + # If specifying a gateway, the account will be created + # during the subscribe call + if lazy or gateway_name: return zAccount response = self.create(zAccount) @@ -1572,7 +1574,7 @@ def make_account(self, user=None, currency='USD', status="Draft", return zAccount def make_contact(self, user=None, billing_address=None, zAccount=None, - lazy=False): + lazy=False, gateway_name=None): """ This defines the contact (the end user) for the account. There are two types of contacts that need to be created as part of the customer @@ -1625,7 +1627,9 @@ def make_contact(self, user=None, billing_address=None, zAccount=None, if zAccount is not None and hasattr(zAccount, 'Id'): zContact.AccountId = zAccount.Id - if lazy: + # If specifying a gateway, the contact will be created + # during the subscribe call + if lazy or gateway_name: return zContact response = self.create(zContact) @@ -1793,18 +1797,15 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # Create Contact zContact = self.make_contact(user=user, billing_address=billing_address, - zAccount=zAccount) + zAccount=zAccount, + gateway_name=gateway_name) # Add the shipping contact if it exists if not zShippingContact and shipping_address: zShippingContact = self.make_contact(user=user, billing_address=shipping_address, - zAccount=zAccount) - - # Update the account if not active yet, requires BuildToId and SoldToId - if getattr(zAccount, 'Status') != 'Active': - self.activate_account(zAccount, zContact, - zShippingContact=zShippingContact) + zAccount=zAccount, + gateway_name=gateway_name) # Get Rate Plan & Build Rate Plan Data zRatePlanData = self.make_rate_plan_data(product_rate_plan_id) From b5f4e7b656659ed6c90f16ce7e7c569efdf78f5f Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Fri, 3 Jan 2014 17:27:43 -0700 Subject: [PATCH 034/103] Removed the unecessary extra creation of an Account object. Took out the old gateway condtional that is no longer needed. --- zuora/client.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 8ba6e94..6344c11 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1867,15 +1867,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # Subscribe zSubscribeRequest = self.client.factory.create('ns0:SubscribeRequest') - # If we defined the gateway, the account is created by now - # And requires sending only the id of the existing account - if gateway_name: - # Build Account - subscribe_account = self.client.factory.create('ns2:Account') - subscribe_account.Id = zAccount.Id - else: - subscribe_account = zAccount - zSubscribeRequest.Account = subscribe_account + zSubscribeRequest.Account = zAccount zSubscribeRequest.BillToContact = zContact # Add the shipping contact if it exists if zShippingContact: From 6b241a195fd05aebb15d9ac0577b8943dab09ac7 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Mon, 13 Jan 2014 17:10:12 -0700 Subject: [PATCH 035/103] Removed the timeout parameter, since I don't think httplib2 on devint supports it. --- zuora/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 6344c11..cacdca3 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -52,7 +52,9 @@ def open(self, request): return HttpTransport.open(self, request) def send(self, request): - headers, message = self.http.request(request.url, "POST", body=request.message, headers=request.headers, timeout=1) + headers, message = self.http.request(request.url, "POST", + body=request.message, + headers=request.headers) response = Reply(200, headers, message) return response From 1af3cd6a8c02c6ccd7c704c62631366d5b2494d4 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Mon, 13 Jan 2014 17:32:16 -0700 Subject: [PATCH 036/103] Moved the timeout. --- zuora/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index cacdca3..3514497 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -46,7 +46,7 @@ class HttpTransportWithKeepAlive(HttpAuthenticated, object): def __init__(self): super(HttpTransportWithKeepAlive, self).__init__() - self.http = httplib2.Http() + self.http = httplib2.Http(timeout=1) def open(self, request): return HttpTransport.open(self, request) From 84cb3c6085f434d13c9c263ba135bd5511857bec Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Mon, 13 Jan 2014 17:36:27 -0700 Subject: [PATCH 037/103] Changed the version to v1.0.0.15 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 81ed1b3..fabf7d1 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'mmf-zuora', - 'version': '1.0.0.14', + 'version': '1.0.0.15', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From 3b6ea4ed27cce79090dac1e12d6ac7368df0378a Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Tue, 14 Jan 2014 13:59:55 -0700 Subject: [PATCH 038/103] Increased Zuora timeout --- zuora/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 3514497..48e48ce 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -46,7 +46,7 @@ class HttpTransportWithKeepAlive(HttpAuthenticated, object): def __init__(self): super(HttpTransportWithKeepAlive, self).__init__() - self.http = httplib2.Http(timeout=1) + self.http = httplib2.Http(timeout=5) def open(self, request): return HttpTransport.open(self, request) From 30f83292f50699e00a54c93ae46508be0250af15 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Tue, 14 Jan 2014 14:06:04 -0700 Subject: [PATCH 039/103] Updated to v1.0.0.16 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fabf7d1..44c8c91 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'mmf-zuora', - 'version': '1.0.0.15', + 'version': '1.0.0.16', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From a4927fdc3d953a7bce9db0f2c0c5febface2b75f Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 22 Jan 2014 11:14:42 -0700 Subject: [PATCH 040/103] Bumped the timeout to 20 seconds. Changed the version to v.1.0.0.17 --- setup.py | 2 +- zuora/client.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 44c8c91..19672d2 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'mmf-zuora', - 'version': '1.0.0.16', + 'version': '1.0.0.17', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', diff --git a/zuora/client.py b/zuora/client.py index 48e48ce..40293a0 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -46,7 +46,7 @@ class HttpTransportWithKeepAlive(HttpAuthenticated, object): def __init__(self): super(HttpTransportWithKeepAlive, self).__init__() - self.http = httplib2.Http(timeout=5) + self.http = httplib2.Http(timeout=20) def open(self, request): return HttpTransport.open(self, request) From 61b110af2b3cd289f9697f6474abfe5c1486becb Mon Sep 17 00:00:00 2001 From: Dave McLain Date: Wed, 22 Jan 2014 13:20:38 -0600 Subject: [PATCH 041/103] rename back to just zuora and bump to v1.0.1 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 19672d2..2905714 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,8 @@ #!/usr/bin/python -tt setupArgs = { - 'name': 'mmf-zuora', - 'version': '1.0.0.17', + 'name': 'zuora', + 'version': '1.0.1', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From 36f2b7d7760505ec77ff0abc2754f6a4ae2f32ce Mon Sep 17 00:00:00 2001 From: Dave McLain Date: Wed, 22 Jan 2014 13:23:08 -0600 Subject: [PATCH 042/103] v1.0.2 fix MANIFEST.in bug --- MANIFEST.in | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index d63e416..489ab2e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -include *.wsdl +include zuora/*.wsdl diff --git a/setup.py b/setup.py index 2905714..9b14513 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.1', + 'version': '1.0.2', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From 4e578d679dd30566eccb253e7ba417dd4971d851 Mon Sep 17 00:00:00 2001 From: Dave McLain Date: Thu, 23 Jan 2014 09:53:23 -0600 Subject: [PATCH 043/103] v1.0.3 package data properly --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9b14513..6f5f121 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.2', + 'version': '1.0.3', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', @@ -11,6 +11,7 @@ 'zuora', 'zuora.rest_wrapper', ], + 'package_data': {'zuora': ['./*.wsdl']}, } try: From 617bbaeffccce23d49a0a01f68cbbff9153d2bbb Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Mon, 27 Jan 2014 21:43:43 -0700 Subject: [PATCH 044/103] Added disable_ssl_certificate_validation until we get the SSL certs from Zuora working. --- zuora/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 40293a0..2334828 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -46,7 +46,8 @@ class HttpTransportWithKeepAlive(HttpAuthenticated, object): def __init__(self): super(HttpTransportWithKeepAlive, self).__init__() - self.http = httplib2.Http(timeout=20) + self.http = httplib2.Http(timeout=20, + disable_ssl_certificate_validation=True) def open(self, request): return HttpTransport.open(self, request) From 64cc2c585588bccf93699cbab2b8882a8fca0220 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Tue, 28 Jan 2014 10:52:05 -0700 Subject: [PATCH 045/103] Updated the version of python-zuora. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6f5f121..7fe8e9f 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.3', + 'version': '1.0.4', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From 80e9b523954eb0e4903a39dac3446ae351336d00 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Tue, 4 Feb 2014 23:32:54 -0700 Subject: [PATCH 046/103] Fixed the account gateways if the user needs to make a purchase through a different gateway. --- zuora/client.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 2334828..2380f7b 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -483,7 +483,10 @@ def get_account(self, user_id): Checks to see if the loaded user has an account """ qs = """ - SELECT Id FROM Account + SELECT + Id, AccountNumber, AutoPay, Balance, + PaymentGateway, Name, Status, UpdatedDate + FROM Account WHERE AccountNumber = '%s' or AccountNumber = 'A-%s' """ % (user_id, user_id) @@ -1500,6 +1503,57 @@ def get_subscriptions(self, subscription_id=None, account_id=None, # Return the Match return zRecords + def gateway_confirm(self, account, user, gateway_name): + # Make sure the account exists already, otherwise the gateway will be + # specified on account creation + if not account or not getattr(account, 'PaymentGateway', None): + try: + zAccount = self.get_account(user.id) + except DoesNotExist: + return + else: + zAccount = account + + # If the Payment Gateway still isn't specified, set it and change it + if not getattr(account, 'PaymentGateway', None): + if gateway_name: + update_dict = {'PaymentGateway': gateway_name} + else: + update_dict = {'PaymentGateway': self.authorize_gateway} + self.update_account(zAccount.Id, update_dict) + return + + # If no gateway was specified, and the gateway is set + # to the default gateway + if not gateway_name \ + and zAccount.PaymentGateway == self.authorize_gateway: + # Do nothing + return + # If there isn't a gateway specified, and they aren't set to the + # default gateway + elif not gateway_name \ + and zAccount.PaymentGateway != self.authorize_gateway: + # Update the account to the default gateway + update_dict = {'PaymentGateway': self.authorize_gateway} + self.update_account(zAccount.Id, update_dict) + # If a gateway was specified, but their account is already + # set to that gateway + elif gateway_name and gateway_name == zAccount.PaymentGateway: + # Do nothing + return + # If a gateway was specified, but their account is set to a + # different gateway + elif gateway_name and gateway_name != zAccount.PaymentGateway: + # Update the gateway to the specified gateway + update_dict = {'PaymentGateway': gateway_name} + self.update_account(zAccount.Id, update_dict) + # We should never see this condition + else: + logging.error( + "Unexpected gateway conditions. gateway: %s acct_gateway: %s" \ + % (gateway_name, zAccount.PaymentGateway)) + return + def make_account(self, user=None, currency='USD', status="Draft", lazy=False, site_name=None, billing_address=None, gateway_name=None): @@ -1789,7 +1843,9 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, :param str subscription_name: The name of the subscription. This is a\ unique identifier. If not specified, Zuora will auto-create a name. """ - #Used to be called even if account existed, pulling it out for now + # Account Gateway Check/Switch + self.gateway_confirm(zAccount, user, gateway_name) + # Get or Create Account if not zAccount: zAccount = self.make_account(user=user, site_name=site_name, From 5306a20d10e4d2d6d519281640eedc02901941bf Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 5 Feb 2014 01:01:06 -0700 Subject: [PATCH 047/103] Changed the version to v1.0.5 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7fe8e9f..2b1f583 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.4', + 'version': '1.0.5', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From 38a09883564286daa135315a503ebfa57fd72d89 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 5 Feb 2014 10:46:24 -0700 Subject: [PATCH 048/103] For existing accounts, just add the id. --- zuora/client.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 2380f7b..e5fdb3b 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1848,9 +1848,12 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # Get or Create Account if not zAccount: + existing_account = False zAccount = self.make_account(user=user, site_name=site_name, billing_address=billing_address, gateway_name=gateway_name) + else: + existing_account = True if not zContact: # Create Contact @@ -1926,7 +1929,14 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # Subscribe zSubscribeRequest = self.client.factory.create('ns0:SubscribeRequest') - zSubscribeRequest.Account = zAccount + # If the account already exists, just add the id to the + # subscribe request + if existing_account: + sub_account = self.client.factory.create('ns2:Account') + sub_account.Id = zAccount.Id + zSubscribeRequest.Account = sub_account + else: + zSubscribeRequest.Account = zAccount zSubscribeRequest.BillToContact = zContact # Add the shipping contact if it exists if zShippingContact: From 30f523783ab220fd9014a6c977201cf960646df2 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 5 Feb 2014 10:53:30 -0700 Subject: [PATCH 049/103] Added logging. --- zuora/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zuora/client.py b/zuora/client.py index e5fdb3b..48f2710 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1931,6 +1931,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, zSubscribeRequest = self.client.factory.create('ns0:SubscribeRequest') # If the account already exists, just add the id to the # subscribe request + log.info("***Existing account: %s" % existing_account) if existing_account: sub_account = self.client.factory.create('ns2:Account') sub_account.Id = zAccount.Id From 0dd1988bad9642942ead6fcae0d26655cd5675e8 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 5 Feb 2014 11:00:33 -0700 Subject: [PATCH 050/103] Moved where we figure out if the account exists already or not. --- zuora/client.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 48f2710..8133946 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1504,13 +1504,18 @@ def get_subscriptions(self, subscription_id=None, account_id=None, return zRecords def gateway_confirm(self, account, user, gateway_name): + """Switches the gateway if the user is purchasing with a different + gateway. + + Returns True or False if the account already exists or not + """ # Make sure the account exists already, otherwise the gateway will be # specified on account creation if not account or not getattr(account, 'PaymentGateway', None): try: zAccount = self.get_account(user.id) except DoesNotExist: - return + return False else: zAccount = account @@ -1521,14 +1526,14 @@ def gateway_confirm(self, account, user, gateway_name): else: update_dict = {'PaymentGateway': self.authorize_gateway} self.update_account(zAccount.Id, update_dict) - return + return True # If no gateway was specified, and the gateway is set # to the default gateway if not gateway_name \ and zAccount.PaymentGateway == self.authorize_gateway: # Do nothing - return + pass # If there isn't a gateway specified, and they aren't set to the # default gateway elif not gateway_name \ @@ -1540,7 +1545,7 @@ def gateway_confirm(self, account, user, gateway_name): # set to that gateway elif gateway_name and gateway_name == zAccount.PaymentGateway: # Do nothing - return + pass # If a gateway was specified, but their account is set to a # different gateway elif gateway_name and gateway_name != zAccount.PaymentGateway: @@ -1552,7 +1557,8 @@ def gateway_confirm(self, account, user, gateway_name): logging.error( "Unexpected gateway conditions. gateway: %s acct_gateway: %s" \ % (gateway_name, zAccount.PaymentGateway)) - return + + return True def make_account(self, user=None, currency='USD', status="Draft", lazy=False, site_name=None, billing_address=None, @@ -1844,16 +1850,13 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, unique identifier. If not specified, Zuora will auto-create a name. """ # Account Gateway Check/Switch - self.gateway_confirm(zAccount, user, gateway_name) + existing_account = self.gateway_confirm(zAccount, user, gateway_name) # Get or Create Account if not zAccount: - existing_account = False zAccount = self.make_account(user=user, site_name=site_name, billing_address=billing_address, gateway_name=gateway_name) - else: - existing_account = True if not zContact: # Create Contact From 4d2243e050d81b9d8930b20edb6a01522e819462 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 5 Feb 2014 11:06:07 -0700 Subject: [PATCH 051/103] Chaged to ns1 --- zuora/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 8133946..b5d30a6 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1936,7 +1936,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # subscribe request log.info("***Existing account: %s" % existing_account) if existing_account: - sub_account = self.client.factory.create('ns2:Account') + sub_account = self.client.factory.create('ns1:Account') sub_account.Id = zAccount.Id zSubscribeRequest.Account = sub_account else: From b495bbf4b91471f5c1f2f39ff1a7e6c3b17bb686 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 5 Feb 2014 11:16:17 -0700 Subject: [PATCH 052/103] Moved to not creating a new Account xml element. --- zuora/client.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index b5d30a6..e6d1b5e 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1936,9 +1936,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # subscribe request log.info("***Existing account: %s" % existing_account) if existing_account: - sub_account = self.client.factory.create('ns1:Account') - sub_account.Id = zAccount.Id - zSubscribeRequest.Account = sub_account + zSubscribeRequest.Account.Id = zAccount.Id else: zSubscribeRequest.Account = zAccount zSubscribeRequest.BillToContact = zContact From ec2b5b1a000500e361ee2592576c348c84f039f9 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 5 Feb 2014 11:31:40 -0700 Subject: [PATCH 053/103] Changed to creating temporary account again. --- zuora/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index e6d1b5e..9bdfc81 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1936,7 +1936,10 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # subscribe request log.info("***Existing account: %s" % existing_account) if existing_account: - zSubscribeRequest.Account.Id = zAccount.Id + sub_account = self.client.factory.create('ns2:Account') + sub_account.Id = zAccount.Id + zSubscribeRequest.Account = sub_account + log.info("***Subscribe account: %s" % zSubscribeRequest.Account) else: zSubscribeRequest.Account = zAccount zSubscribeRequest.BillToContact = zContact From 58dc71532e2d34aeb5f7d567e2dba57ad954e992 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 5 Feb 2014 11:45:46 -0700 Subject: [PATCH 054/103] Added a fetching of the account if the account exists. --- zuora/client.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 9bdfc81..a840b4d 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -478,17 +478,21 @@ def activate_account(self, zAccount, zContact, zShippingContact=None, raise ZuoraException( "Unknown Error updating Account. %s" % response) - def get_account(self, user_id): + def get_account(self, user_id, id_only=False): """ Checks to see if the loaded user has an account """ + if id_only: + fields = 'Id' + else: + fields = """Id, AccountNumber, AutoPay, Balance, + PaymentGateway, Name, Status, UpdatedDate""" qs = """ SELECT - Id, AccountNumber, AutoPay, Balance, - PaymentGateway, Name, Status, UpdatedDate + %s FROM Account WHERE AccountNumber = '%s' or AccountNumber = 'A-%s' - """ % (user_id, user_id) + """ % (fields, user_id, user_id) response = self.query(qs) if getattr(response, "records") and len(response.records) > 0: @@ -1936,9 +1940,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # subscribe request log.info("***Existing account: %s" % existing_account) if existing_account: - sub_account = self.client.factory.create('ns2:Account') - sub_account.Id = zAccount.Id - zSubscribeRequest.Account = sub_account + zSubscribeRequest.Account = self.get_account(user.id, id_only=True) log.info("***Subscribe account: %s" % zSubscribeRequest.Account) else: zSubscribeRequest.Account = zAccount From 885a6f34604aca620e0e1d53730b23a7696294d6 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 5 Feb 2014 14:25:03 -0700 Subject: [PATCH 055/103] Removed a couple of unnecessary log lines. Updated the version to v1.0.6 --- setup.py | 2 +- zuora/client.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 2b1f583..a04f121 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.5', + 'version': '1.0.6', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', diff --git a/zuora/client.py b/zuora/client.py index a840b4d..21029e7 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1938,10 +1938,8 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, zSubscribeRequest = self.client.factory.create('ns0:SubscribeRequest') # If the account already exists, just add the id to the # subscribe request - log.info("***Existing account: %s" % existing_account) if existing_account: zSubscribeRequest.Account = self.get_account(user.id, id_only=True) - log.info("***Subscribe account: %s" % zSubscribeRequest.Account) else: zSubscribeRequest.Account = zAccount zSubscribeRequest.BillToContact = zContact From 60c285a415863ad8f865701023635b6af1178d3f Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 6 Feb 2014 09:37:44 -0700 Subject: [PATCH 056/103] Made sure user exists. --- zuora/client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 21029e7..1d27191 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1853,8 +1853,11 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, :param str subscription_name: The name of the subscription. This is a\ unique identifier. If not specified, Zuora will auto-create a name. """ - # Account Gateway Check/Switch - existing_account = self.gateway_confirm(zAccount, user, gateway_name) + if user: + # Account Gateway Check/Switch + existing_account = self.gateway_confirm(zAccount, user, gateway_name) + else: + existing_account = False # Get or Create Account if not zAccount: From 19d1b6fc4903e69136f6ba1f1e7208ea305fe91c Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 6 Feb 2014 09:41:46 -0700 Subject: [PATCH 057/103] Changed the version to v.1.0.7 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a04f121..10d2991 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.6', + 'version': '1.0.7', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From 68cc96c23362bdff9ede071521d97fc1994b3504 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Tue, 18 Feb 2014 14:51:12 -0700 Subject: [PATCH 058/103] Added logging around gateway stuff. Switched to version v1.0.8 --- setup.py | 2 +- zuora/client.py | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 10d2991..1b86114 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.7', + 'version': '1.0.8', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', diff --git a/zuora/client.py b/zuora/client.py index 1d27191..a6059df 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1519,16 +1519,22 @@ def gateway_confirm(self, account, user, gateway_name): try: zAccount = self.get_account(user.id) except DoesNotExist: + logging.info("Gateway: Account DNE. user: %s" % (user.id)) return False + logging.info("Gateway: Fetched Account. user: %s" % (user.id)) else: + logging.info( + "Gateway: Account and Gateway existed. user: %s" % (user.id)) zAccount = account # If the Payment Gateway still isn't specified, set it and change it - if not getattr(account, 'PaymentGateway', None): + if not getattr(zAccount, 'PaymentGateway', None): if gateway_name: update_dict = {'PaymentGateway': gateway_name} else: update_dict = {'PaymentGateway': self.authorize_gateway} + logging.info("Gateway: switched user: %s update: %s" \ + % (user.id, update_dict)) self.update_account(zAccount.Id, update_dict) return True @@ -1537,6 +1543,8 @@ def gateway_confirm(self, account, user, gateway_name): if not gateway_name \ and zAccount.PaymentGateway == self.authorize_gateway: # Do nothing + logging.info("Gateway: same default gateway user: %s gateway: %s" \ + % (user.id, zAccount.PaymentGateway)) pass # If there isn't a gateway specified, and they aren't set to the # default gateway @@ -1545,10 +1553,15 @@ def gateway_confirm(self, account, user, gateway_name): # Update the account to the default gateway update_dict = {'PaymentGateway': self.authorize_gateway} self.update_account(zAccount.Id, update_dict) + logging.info("Gateway: switched to default user: %s gateway: %s" \ + % (user.id, update_dict)) # If a gateway was specified, but their account is already # set to that gateway elif gateway_name and gateway_name == zAccount.PaymentGateway: # Do nothing + logging.info( + "Gateway: same specified gateway user: %s gateway: %s" \ + % (user.id, gateway_name)) pass # If a gateway was specified, but their account is set to a # different gateway @@ -1556,6 +1569,9 @@ def gateway_confirm(self, account, user, gateway_name): # Update the gateway to the specified gateway update_dict = {'PaymentGateway': gateway_name} self.update_account(zAccount.Id, update_dict) + logging.info( + "Gateway: switched to specified gateway user: %s gateway: %s" \ + % (user.id, update_dict)) # We should never see this condition else: logging.error( @@ -1854,8 +1870,11 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, unique identifier. If not specified, Zuora will auto-create a name. """ if user: + logging.error("Gateway: confirming gateway user: %s" % (user.id)) # Account Gateway Check/Switch - existing_account = self.gateway_confirm(zAccount, user, gateway_name) + existing_account = self.gateway_confirm(zAccount, + user, + gateway_name) else: existing_account = False @@ -1943,6 +1962,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # subscribe request if existing_account: zSubscribeRequest.Account = self.get_account(user.id, id_only=True) + logging.error("Fetched just the account id, user: %s" % (user.id)) else: zSubscribeRequest.Account = zAccount zSubscribeRequest.BillToContact = zContact From 16edaab565f2d90f3628988b8cd82720c1d00d5b Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 19 Feb 2014 17:21:58 -0700 Subject: [PATCH 059/103] Added the ability to set the default payment method for accounts switching gateways. --- zuora/client.py | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index a6059df..57676e3 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1507,7 +1507,8 @@ def get_subscriptions(self, subscription_id=None, account_id=None, # Return the Match return zRecords - def gateway_confirm(self, account, user, gateway_name): + def gateway_confirm(self, account, user, gateway_name, + payment_method): """Switches the gateway if the user is purchasing with a different gateway. @@ -1551,8 +1552,9 @@ def gateway_confirm(self, account, user, gateway_name): elif not gateway_name \ and zAccount.PaymentGateway != self.authorize_gateway: # Update the account to the default gateway - update_dict = {'PaymentGateway': self.authorize_gateway} - self.update_account(zAccount.Id, update_dict) + self.update_account_payment(zAccount.Id, + self.authorize_gateway, + payment_method) logging.info("Gateway: switched to default user: %s gateway: %s" \ % (user.id, update_dict)) # If a gateway was specified, but their account is already @@ -1567,8 +1569,9 @@ def gateway_confirm(self, account, user, gateway_name): # different gateway elif gateway_name and gateway_name != zAccount.PaymentGateway: # Update the gateway to the specified gateway - update_dict = {'PaymentGateway': gateway_name} - self.update_account(zAccount.Id, update_dict) + self.update_account_payment(zAccount.Id, + gateway_name, + payment_method) logging.info( "Gateway: switched to specified gateway user: %s gateway: %s" \ % (user.id, update_dict)) @@ -1579,6 +1582,27 @@ def gateway_confirm(self, account, user, gateway_name): % (gateway_name, zAccount.PaymentGateway)) return True + + def update_account_payment(self, account_id, gateway, payment_method): + # If the payment method hasn't been created yet + if payment_method and getattr(payment_method, 'Id', None) is None: + logging.info( + "Creating Payment Method. Account id: %s" % account_id) + response = self.create(payment_method) + if not isinstance(response, list) or not response[0].Success: + raise ZuoraException( + "Error creating Payment Method. Account id: %s resp: %s" \ + % (account_id, response)) + payment_method_id = response[0].Id + elif payment_method is None: + raise ZuoraException( + "Missing Payment Method. Account id: %s" % account_id) + else: + payment_method_id = payment_method.Id + # Update the account fields + update_dict = {'PaymentGateway': gateway, + 'DefaultPaymentMethodId': payment_method_id} + self.update_account(account_id, update_dict) def make_account(self, user=None, currency='USD', status="Draft", lazy=False, site_name=None, billing_address=None, @@ -1871,10 +1895,17 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, """ if user: logging.error("Gateway: confirming gateway user: %s" % (user.id)) + # Get the payment method + if external_payment_method: + gateway_pm = external_payment_method + else: + gateway_pm = payment_method # Account Gateway Check/Switch - existing_account = self.gateway_confirm(zAccount, - user, - gateway_name) + existing_account = self.gateway_confirm( + zAccount, + user, + gateway_name, + gateway_pm) else: existing_account = False From 8a55bbdd8989903e2da2e4d3b0b842cd271a974e Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 19 Feb 2014 17:31:49 -0700 Subject: [PATCH 060/103] Added the account id to the Payment Method create. --- zuora/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zuora/client.py b/zuora/client.py index 57676e3..d46497e 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1586,6 +1586,7 @@ def gateway_confirm(self, account, user, gateway_name, def update_account_payment(self, account_id, gateway, payment_method): # If the payment method hasn't been created yet if payment_method and getattr(payment_method, 'Id', None) is None: + payment_method.AccountId = account_id logging.info( "Creating Payment Method. Account id: %s" % account_id) response = self.create(payment_method) From de161fe7508d8f05c6c15859819bd2829b85ce0a Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 19 Feb 2014 17:40:17 -0700 Subject: [PATCH 061/103] Fixed logging to have variables that exist. --- zuora/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index d46497e..ba6172c 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1556,7 +1556,7 @@ def gateway_confirm(self, account, user, gateway_name, self.authorize_gateway, payment_method) logging.info("Gateway: switched to default user: %s gateway: %s" \ - % (user.id, update_dict)) + % (user.id, self.authorize_gateway)) # If a gateway was specified, but their account is already # set to that gateway elif gateway_name and gateway_name == zAccount.PaymentGateway: @@ -1574,7 +1574,7 @@ def gateway_confirm(self, account, user, gateway_name, payment_method) logging.info( "Gateway: switched to specified gateway user: %s gateway: %s" \ - % (user.id, update_dict)) + % (user.id, gateway_name)) # We should never see this condition else: logging.error( From 5ea7ccf78a12dcc23fd2b3eb0311de7fd806ab0c Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 20 Feb 2014 09:58:00 -0700 Subject: [PATCH 062/103] Fixed the updating the account gateway before trying to attach the account to the payment method. --- zuora/client.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index ba6172c..2bbf70c 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1584,26 +1584,36 @@ def gateway_confirm(self, account, user, gateway_name, return True def update_account_payment(self, account_id, gateway, payment_method): + # These steps cannot be combined, and have to be executed in this order + # Update the account gateway + logging.info( + "Gateway: Updating Account Gateway. Account id: %s" % account_id) + gateway_dict = {'PaymentGateway': gateway} + self.update_account(account_id, gateway_dict) # If the payment method hasn't been created yet if payment_method and getattr(payment_method, 'Id', None) is None: payment_method.AccountId = account_id logging.info( - "Creating Payment Method. Account id: %s" % account_id) + "Gateway: Creating Payment Method. Account: %s" % account_id) response = self.create(payment_method) if not isinstance(response, list) or not response[0].Success: raise ZuoraException( "Error creating Payment Method. Account id: %s resp: %s" \ % (account_id, response)) payment_method_id = response[0].Id + # No Payment Method specified elif payment_method is None: raise ZuoraException( "Missing Payment Method. Account id: %s" % account_id) + # Payment Method exists already else: payment_method_id = payment_method.Id - # Update the account fields - update_dict = {'PaymentGateway': gateway, - 'DefaultPaymentMethodId': payment_method_id} - self.update_account(account_id, update_dict) + logging.info( + "Gateway: Updating DefaultPayment Method. Account id: %s" \ + % account_id) + # Update the default payment method on the account + dpm_dict = {'DefaultPaymentMethodId': payment_method_id} + self.update_account(account_id, dpm_dict) def make_account(self, user=None, currency='USD', status="Draft", lazy=False, site_name=None, billing_address=None, From 0aaba6fab12b5bd1b6bd1f787745454aad21f071 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 20 Feb 2014 10:27:04 -0700 Subject: [PATCH 063/103] Updated the version to v1.0. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1b86114..f60f6bd 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.8', + 'version': '1.0.9', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From 5efb62240f575e38c22e9643a7413b98a70f82ea Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Tue, 18 Feb 2014 16:14:23 -0700 Subject: [PATCH 064/103] Added the initial certificate. --- zuora/PCA-3G5.pem | 28 ++++++++++++++++++++++++++++ zuora/client.py | 20 +++++++++++++++----- 2 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 zuora/PCA-3G5.pem diff --git a/zuora/PCA-3G5.pem b/zuora/PCA-3G5.pem new file mode 100644 index 0000000..a9490be --- /dev/null +++ b/zuora/PCA-3G5.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW +ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 +nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex +t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz +SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG +BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ +rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ +NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH +BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv +MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE +p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y +5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK +WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ +4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N +hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- \ No newline at end of file diff --git a/zuora/client.py b/zuora/client.py index 2bbf70c..e011c40 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -44,9 +44,14 @@ class HttpTransportWithKeepAlive(HttpAuthenticated, object): - def __init__(self): + def __init__(self, use_cert=False): super(HttpTransportWithKeepAlive, self).__init__() - self.http = httplib2.Http(timeout=20, + if use_cert: + cert_file = 'file://%s' % path.abspath( + path.dirname(__file__) + "/PCA-3G5.pem") + self.http = httplib2.Http(timeout=20, ca_certs=cert_file) + else: + self.http = httplib2.Http(timeout=20, disable_ssl_certificate_validation=True) def open(self, request): @@ -107,6 +112,7 @@ def __init__(self, zuora_settings): self.base_dir = path.dirname(__file__) self.authorize_gateway = zuora_settings.get("gateway_name", None) self.create_test_users = zuora_settings.get("test_users", None) + self.use_cert = zuora_settings.get("SSL", False) # Build Client imp = Import('http://object.api.zuora.com/') @@ -117,8 +123,11 @@ def __init__(self, zuora_settings): wsdl_file = 'file://%s' % path.abspath( self.base_dir + "/" + self.wsdl_file) - self.client = Client(url=wsdl_file, doctor=schema_doctor, - cache=None, transport=HttpTransportWithKeepAlive()) + self.client = Client( + url=wsdl_file, + doctor=schema_doctor, + cache=None, + transport=HttpTransportWithKeepAlive(self.use_cert)) # Force No Cache self.client.set_options(cache=None) @@ -129,7 +138,8 @@ def __init__(self, zuora_settings): self.session_id = None def reset_transport(self): - self.client.options.transport = HttpTransportWithKeepAlive() + self.client.options.transport = HttpTransportWithKeepAlive( + self.use_cert) self.session_id = None # Client Create From b29fc139f8cd579020dad0d803283d55e501fd0b Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Fri, 21 Feb 2014 16:43:05 -0700 Subject: [PATCH 065/103] Trying to connect with just plain old, straight up ssl. No dice. --- zuora/client.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index e011c40..24bb987 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -44,12 +44,24 @@ class HttpTransportWithKeepAlive(HttpAuthenticated, object): - def __init__(self, use_cert=False): + def __init__(self, use_cert=False, cert_domain=None): super(HttpTransportWithKeepAlive, self).__init__() if use_cert: - cert_file = 'file://%s' % path.abspath( - path.dirname(__file__) + "/PCA-3G5.pem") - self.http = httplib2.Http(timeout=20, ca_certs=cert_file) + import socket + import ssl + + path_to_certs = path.abspath(path.dirname(__file__)) + cert_file = path_to_certs + "/PCA-3G5.pem" + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # require a certificate from the server + ssl_sock = ssl.wrap_socket(s, + ca_certs=cert_file, + cert_reqs=ssl.CERT_REQUIRED) + + ssl_sock.connect((cert_domain, 443)) + print "***cert_file, ", cert_file + self.http = httplib2.Http(timeout=20) else: self.http = httplib2.Http(timeout=20, disable_ssl_certificate_validation=True) @@ -113,6 +125,7 @@ def __init__(self, zuora_settings): self.authorize_gateway = zuora_settings.get("gateway_name", None) self.create_test_users = zuora_settings.get("test_users", None) self.use_cert = zuora_settings.get("SSL", False) + self.cert_domain = zuora_settings.get("SSL_domain") # Build Client imp = Import('http://object.api.zuora.com/') @@ -127,7 +140,8 @@ def __init__(self, zuora_settings): url=wsdl_file, doctor=schema_doctor, cache=None, - transport=HttpTransportWithKeepAlive(self.use_cert)) + transport=HttpTransportWithKeepAlive(self.use_cert, + self.cert_domain)) # Force No Cache self.client.set_options(cache=None) @@ -139,7 +153,8 @@ def __init__(self, zuora_settings): def reset_transport(self): self.client.options.transport = HttpTransportWithKeepAlive( - self.use_cert) + self.use_cert, + self.cert_domain) self.session_id = None # Client Create From 685f3af773f997a7f090663edd7fd521716d7520 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Mon, 24 Feb 2014 16:19:36 -0700 Subject: [PATCH 066/103] Removed the extra SSL test bit. Added the 'Use Default Gateway' as the default gateway. --- zuora/client.py | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 24bb987..f6c6b1c 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -44,24 +44,12 @@ class HttpTransportWithKeepAlive(HttpAuthenticated, object): - def __init__(self, use_cert=False, cert_domain=None): + def __init__(self, use_cert=False): super(HttpTransportWithKeepAlive, self).__init__() if use_cert: - import socket - import ssl - path_to_certs = path.abspath(path.dirname(__file__)) cert_file = path_to_certs + "/PCA-3G5.pem" - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # require a certificate from the server - ssl_sock = ssl.wrap_socket(s, - ca_certs=cert_file, - cert_reqs=ssl.CERT_REQUIRED) - - ssl_sock.connect((cert_domain, 443)) - print "***cert_file, ", cert_file - self.http = httplib2.Http(timeout=20) + self.http = httplib2.Http(timeout=20, ca_certs=cert_file) else: self.http = httplib2.Http(timeout=20, disable_ssl_certificate_validation=True) @@ -122,10 +110,10 @@ def __init__(self, zuora_settings): self.password = zuora_settings["password"] self.wsdl_file = zuora_settings["wsdl_file"] self.base_dir = path.dirname(__file__) - self.authorize_gateway = zuora_settings.get("gateway_name", None) + self.authorize_gateway = zuora_settings.get("gateway_name", + 'Use Default Gateway') self.create_test_users = zuora_settings.get("test_users", None) self.use_cert = zuora_settings.get("SSL", False) - self.cert_domain = zuora_settings.get("SSL_domain") # Build Client imp = Import('http://object.api.zuora.com/') @@ -140,8 +128,7 @@ def __init__(self, zuora_settings): url=wsdl_file, doctor=schema_doctor, cache=None, - transport=HttpTransportWithKeepAlive(self.use_cert, - self.cert_domain)) + transport=HttpTransportWithKeepAlive(self.use_cert)) # Force No Cache self.client.set_options(cache=None) @@ -153,8 +140,7 @@ def __init__(self, zuora_settings): def reset_transport(self): self.client.options.transport = HttpTransportWithKeepAlive( - self.use_cert, - self.cert_domain) + self.use_cert) self.session_id = None # Client Create From f92fc8283f9dfc33482de51b682f65578afdf57d Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Mon, 24 Feb 2014 16:52:50 -0700 Subject: [PATCH 067/103] Removed an unnecessary default. --- zuora/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index f6c6b1c..958a952 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -110,8 +110,7 @@ def __init__(self, zuora_settings): self.password = zuora_settings["password"] self.wsdl_file = zuora_settings["wsdl_file"] self.base_dir = path.dirname(__file__) - self.authorize_gateway = zuora_settings.get("gateway_name", - 'Use Default Gateway') + self.authorize_gateway = zuora_settings.get("gateway_name") self.create_test_users = zuora_settings.get("test_users", None) self.use_cert = zuora_settings.get("SSL", False) From 619ba36666098bee8222e0869f54119eb6c659b6 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Mon, 24 Feb 2014 17:07:34 -0700 Subject: [PATCH 068/103] Updated the version to 1.0.10 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f60f6bd..55fb7d1 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.9', + 'version': '1.0.10', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From 6e79d3da26bd8c457595db366e2b206a7d4f9adf Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 13 Mar 2014 16:40:23 -0600 Subject: [PATCH 069/103] Checked for a False string as well as bool False. --- zuora/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 958a952..9b4cba7 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1403,7 +1403,7 @@ def get_product_rate_plan_charge_pricing(self, product_rate_plan_id): price = pricing_dict[charge_model][charge_type] for rpct in rpc["rate_charge_tiers"]: is_overage_price = rpct["is_overage_price"] - if is_overage_price == False: + if is_overage_price in [False, 'False']: price = price + float(rpct["price"]) pricing_dict[charge_model][charge_type] = price From cf9b42685b4fda148649b30e64f3d30a2d3ec157 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 13 Mar 2014 16:47:38 -0600 Subject: [PATCH 070/103] Updated the version to v1.0.11 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 55fb7d1..88f220e 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.10', + 'version': '1.0.11', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From 9a2272ed1637db37389b7b021a558b1c7a0a5293 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Mon, 5 May 2014 14:29:30 -0600 Subject: [PATCH 071/103] Changed how accounts and invoices can be fetched. Added the ability to make payments. Changed the version to 1.0.12. --- setup.py | 2 +- zuora/client.py | 60 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/setup.py b/setup.py index 88f220e..43326d4 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.11', + 'version': '1.0.12', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', diff --git a/zuora/client.py b/zuora/client.py index 9b4cba7..5c52fea 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -488,21 +488,30 @@ def activate_account(self, zAccount, zContact, zShippingContact=None, raise ZuoraException( "Unknown Error updating Account. %s" % response) - def get_account(self, user_id, id_only=False): + def get_account(self, user_id, account_id=None, id_only=False): """ Checks to see if the loaded user has an account """ if id_only: fields = 'Id' else: - fields = """Id, AccountNumber, AutoPay, Balance, + fields = """Id, AccountNumber, AutoPay, Balance, DefaultPaymentMethodId, PaymentGateway, Name, Status, UpdatedDate""" - qs = """ - SELECT - %s - FROM Account - WHERE AccountNumber = '%s' or AccountNumber = 'A-%s' - """ % (fields, user_id, user_id) + # If no account id was specified + if not account_id: + qs = """ + SELECT + %s + FROM Account + WHERE AccountNumber = '%s' or AccountNumber = 'A-%s' + """ % (fields, user_id, user_id) + else: + qs = """ + SELECT + %s + FROM Account + WHERE Id = '%s' + """ % (fields, account_id) response = self.query(qs) if getattr(response, "records") and len(response.records) > 0: @@ -591,7 +600,8 @@ def get_invoice_pdf(self, invoice_id=None): raise DoesNotExist("Unable to find Invoice for Id %s"\ % invoice_id) - def get_invoices(self, account_id=None): + def get_invoices(self, account_id=None, minimum_balance=None, + status=None): """ Gets the Invoices matching criteria. @@ -603,6 +613,10 @@ def get_invoices(self, account_id=None): if account_id: qs_filter.append("AccountId = '%s'" % account_id) + if minimum_balance: + qs_filter.append("Balance > '%s'" % minimum_balance) + if status: + qs_filter.append("Status = '%s'" % status) if qs_filter: qs = """ @@ -1769,6 +1783,34 @@ def make_contact(self, user=None, billing_address=None, zAccount=None, # Return return zContact + def make_payment(self, account_id, invoice_id, invoice_amount, + payment_method_id, payment_type='External', + payment_status='Processed', effective_date=None): + if not effective_date: + effective_date = date.today().strftime(SOAP_TIMESTAMP) + else: + effective_date = effective_date.strftime(SOAP_TIMESTAMP) + # Create the Payment + zPayment = self.client.factory.create('ns2:Payment') + zPayment.AccountId = account_id + zPayment.InvoiceId = invoice_id + zPayment.AppliedInvoiceAmount = invoice_amount + zPayment.PaymentMethodId = payment_method_id + zPayment.Type = payment_type + zPayment.Status = payment_status + zPayment.EffectiveDate = effective_date + + response = self.create(zPayment) + # If the Payment creation failed + if not isinstance(response, list) or not response[0].Success: + logging.error("Error creating Payment, account: %s response: %s" \ + % (account_id, response)) + else: + zPayment.Id = response[0].Id + + # Return + return zPayment + def make_rate_plan_data(self, product_rate_plan_id): """ RatePlanData is used to pass complex data to the subscribe() call. From 9fa0140ec1e9caf36b351907274ff311b5e007ff Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 7 May 2014 09:27:07 -0600 Subject: [PATCH 072/103] Fixed the IAP to set the default payment method. Added the ability to set the default payment method id. Added the ability to do a payment dry run. --- zuora/client.py | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 5c52fea..c5fac71 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -481,6 +481,9 @@ def activate_account(self, zAccount, zContact, zShippingContact=None, if payment_method_id and not prepaid: zAccountUpdate.DefaultPaymentMethodId = payment_method_id zAccountUpdate.AutoPay = True + elif payment_method_id: + zAccountUpdate.DefaultPaymentMethodId = payment_method_id + zAccountUpdate.AutoPay = False else: zAccountUpdate.AutoPay = False response = self.update(zAccountUpdate) @@ -1530,6 +1533,16 @@ def get_subscriptions(self, subscription_id=None, account_id=None, # Return the Match return zRecords + + def set_default_payment_method_id(self, account_id, payment_method_id, + auto_pay=None): + # Update the default payment method on the account + account_dict = {'DefaultPaymentMethodId': payment_method_id} + if auto_pay == True: + account_dict['AutoPay'] = True + elif auto_pay == False: + account_dict['AutoPay'] = False + self.update_account(account_id, account_dict) def gateway_confirm(self, account, user, gateway_name, payment_method): @@ -1785,7 +1798,8 @@ def make_contact(self, user=None, billing_address=None, zAccount=None, def make_payment(self, account_id, invoice_id, invoice_amount, payment_method_id, payment_type='External', - payment_status='Processed', effective_date=None): + payment_status='Processed', effective_date=None, + dry_run=False): if not effective_date: effective_date = date.today().strftime(SOAP_TIMESTAMP) else: @@ -1800,13 +1814,20 @@ def make_payment(self, account_id, invoice_id, invoice_amount, zPayment.Status = payment_status zPayment.EffectiveDate = effective_date - response = self.create(zPayment) - # If the Payment creation failed - if not isinstance(response, list) or not response[0].Success: - logging.error("Error creating Payment, account: %s response: %s" \ - % (account_id, response)) + # If it's not a dry run, create the payment + if not dry_run: + response = self.create(zPayment) + # If the Payment creation failed + if not isinstance(response, list) or not response[0].Success: + logging.error("Error creating Payment, account: %s response: %s" \ + % (account_id, response)) + else: + zPayment.Id = response[0].Id + logging.info("Made Payment. Payment: %s Account: %s" % ( + zPayment.Id, account_id)) + # else, just output the dry run of the payment data else: - zPayment.Id = response[0].Id + logging.info("Dry run Payment. account: %s Payment: %s" % (account_id, zPayment)) # Return return zPayment From f1d0321ab87d59411b1478b02929f6d4feeb05e8 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Wed, 7 May 2014 16:18:49 -0600 Subject: [PATCH 073/103] Updated the version to 1.0.13. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 43326d4..f56415d 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.12', + 'version': '1.0.13', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From 2285dadf6d3160ea1e072f7cbf286580ef93ddfd Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 15 May 2014 14:37:19 -0600 Subject: [PATCH 074/103] Set AutoPay to True for paypal accounts. --- zuora/client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zuora/client.py b/zuora/client.py index c5fac71..aaabc97 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1702,6 +1702,9 @@ def make_account(self, user=None, currency='USD', status="Draft", # Specify what gateway to use for payments for the user if gateway_name: zAccount.PaymentGateway = gateway_name + # If it's paypal. Set AutoPay to True + if 'paypal' in gateway_name.lower(): + zAccount.AutoPay = True # Determine which Payment Gateway to use, if specified elif self.authorize_gateway: From 3900dae1943737c5c052ab27116a7a6f9fece61c Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 15 May 2014 15:32:24 -0600 Subject: [PATCH 075/103] Put a conditional into the gateway fix that makes sure paypal has AutoPay set to True. --- zuora/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index aaabc97..5b5c900 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1565,6 +1565,10 @@ def gateway_confirm(self, account, user, gateway_name, "Gateway: Account and Gateway existed. user: %s" % (user.id)) zAccount = account + # If the gateway is paypal, make sure AutoPay is set to True + if gateway_name and 'paypal' in gateway_name.lower(): + self.update_account(zAccount.Id, {'AutoPay': True}) + # If the Payment Gateway still isn't specified, set it and change it if not getattr(zAccount, 'PaymentGateway', None): if gateway_name: @@ -1981,7 +1985,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, unique identifier. If not specified, Zuora will auto-create a name. """ if user: - logging.error("Gateway: confirming gateway user: %s" % (user.id)) + logging.info("Gateway: confirming gateway user: %s" % (user.id)) # Get the payment method if external_payment_method: gateway_pm = external_payment_method From dd4f82a3ca0503fef37aea98b25c5221b19f3552 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Thu, 15 May 2014 16:38:11 -0600 Subject: [PATCH 076/103] Updated the version to 1.0.14. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f56415d..26bfb3b 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.13', + 'version': '1.0.14', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From 92bcbcc3c8e3c33a753c712c5b9566ccfd204900 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Tue, 20 May 2014 12:17:55 -0600 Subject: [PATCH 077/103] Moved the marking of PayPal accounts to AutoPay after the subscribe call. Hopefully that gives the account enough time to update, and we really only need AutoPay to be enabled by the next billing run. --- zuora/client.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 5b5c900..6aa3bd0 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1565,10 +1565,6 @@ def gateway_confirm(self, account, user, gateway_name, "Gateway: Account and Gateway existed. user: %s" % (user.id)) zAccount = account - # If the gateway is paypal, make sure AutoPay is set to True - if gateway_name and 'paypal' in gateway_name.lower(): - self.update_account(zAccount.Id, {'AutoPay': True}) - # If the Payment Gateway still isn't specified, set it and change it if not getattr(zAccount, 'PaymentGateway', None): if gateway_name: @@ -1706,9 +1702,6 @@ def make_account(self, user=None, currency='USD', status="Draft", # Specify what gateway to use for payments for the user if gateway_name: zAccount.PaymentGateway = gateway_name - # If it's paypal. Set AutoPay to True - if 'paypal' in gateway_name.lower(): - zAccount.AutoPay = True # Determine which Payment Gateway to use, if specified elif self.authorize_gateway: @@ -2111,6 +2104,10 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, log.info("***Subscribe Request: %s" % zSubscribeRequest) response = self.call(fn, zSubscribeRequest) log.info("***Subscribe Response: %s" % response) + + # If the gateway is paypal, make sure AutoPay is set to True + if gateway_name and 'paypal' in gateway_name.lower(): + self.update_account(zAccount.Id, {'AutoPay': True}) # return the response return response From 38298575db8d631a262e4d6bfe0111aa13da5848 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Tue, 20 May 2014 13:59:18 -0600 Subject: [PATCH 078/103] Fixed where we were getting the account id from. --- zuora/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 6aa3bd0..4961c7e 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -2077,7 +2077,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # subscribe request if existing_account: zSubscribeRequest.Account = self.get_account(user.id, id_only=True) - logging.error("Fetched just the account id, user: %s" % (user.id)) + logging.info("Fetched just the account id, user: %s" % (user.id)) else: zSubscribeRequest.Account = zAccount zSubscribeRequest.BillToContact = zContact @@ -2107,7 +2107,7 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # If the gateway is paypal, make sure AutoPay is set to True if gateway_name and 'paypal' in gateway_name.lower(): - self.update_account(zAccount.Id, {'AutoPay': True}) + self.update_account(zSubscribeRequest.Account.Id, {'AutoPay': True}) # return the response return response From bee08ee302530faea1ea96dc67fe5c5a83dadea9 Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Tue, 20 May 2014 14:24:28 -0600 Subject: [PATCH 079/103] Fixed the account update to use the subscription response account id. --- zuora/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 4961c7e..6f8276b 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -2107,7 +2107,11 @@ def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, # If the gateway is paypal, make sure AutoPay is set to True if gateway_name and 'paypal' in gateway_name.lower(): - self.update_account(zSubscribeRequest.Account.Id, {'AutoPay': True}) + if isinstance(response, list): + SubscribeResponse = response[0] + else: + SubscribeResponse = response + self.update_account(SubscribeResponse.AccountId, {'AutoPay': True}) # return the response return response From b583806a98a573f0f100a42e598014cc76c6c20c Mon Sep 17 00:00:00 2001 From: Brandon Fredericks Date: Tue, 20 May 2014 16:05:38 -0600 Subject: [PATCH 080/103] Updated the version to 1.0.15 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 26bfb3b..e46aa9c 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.14', + 'version': '1.0.15', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From afb172e68eb5ade0673cd345f72bb0cf57fb6ac4 Mon Sep 17 00:00:00 2001 From: Dylan Mankey Date: Mon, 25 Aug 2014 15:11:53 -0500 Subject: [PATCH 081/103] adding verify=False to REST requests calls --- zuora/rest_wrapper/account_manager.py | 12 +++++++---- zuora/rest_wrapper/catalog_manager.py | 3 ++- zuora/rest_wrapper/payment_method_manager.py | 13 ++++++++---- zuora/rest_wrapper/request_base.py | 3 ++- zuora/rest_wrapper/subscription_manager.py | 22 +++++++++++++------- zuora/rest_wrapper/transaction_manager.py | 9 +++++--- zuora/rest_wrapper/usage_manager.py | 3 ++- 7 files changed, 44 insertions(+), 21 deletions(-) diff --git a/zuora/rest_wrapper/account_manager.py b/zuora/rest_wrapper/account_manager.py index d76f640..53cb50e 100644 --- a/zuora/rest_wrapper/account_manager.py +++ b/zuora/rest_wrapper/account_manager.py @@ -19,7 +19,8 @@ def create_account(self, **kwargs): return None response = requests.post(fullUrl, data=data, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) @rest_client_reconnect @@ -27,14 +28,16 @@ def get_account_summary(self, accountKey): fullUrl = self.zuora_config.base_url + 'accounts/' + accountKey + \ '/summary' - response = requests.get(fullUrl, headers=self.zuora_config.headers) + response = requests.get(fullUrl, headers=self.zuora_config.headers, + verify=False) return self.get_json(response) @rest_client_reconnect def get_account(self, accountKey): fullUrl = self.zuora_config.baseUrl + 'accounts/' + accountKey - response = requests.get(fullUrl, headers=self.zuora_config.headers) + response = requests.get(fullUrl, headers=self.zuora_config.headers, + verify=False) return self.get_json(response) @rest_client_reconnect @@ -47,5 +50,6 @@ def update_account(self, accountKey, **kwargs): data = None response = requests.put(fullUrl, data=data, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) diff --git a/zuora/rest_wrapper/catalog_manager.py b/zuora/rest_wrapper/catalog_manager.py index d89e5a9..f599248 100644 --- a/zuora/rest_wrapper/catalog_manager.py +++ b/zuora/rest_wrapper/catalog_manager.py @@ -14,5 +14,6 @@ def get_catalog(self, pageSize=10, page=1): 'page': page} response = requests.get(fullUrl, params=params, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) diff --git a/zuora/rest_wrapper/payment_method_manager.py b/zuora/rest_wrapper/payment_method_manager.py index bb596b1..5e8f512 100644 --- a/zuora/rest_wrapper/payment_method_manager.py +++ b/zuora/rest_wrapper/payment_method_manager.py @@ -18,7 +18,8 @@ def create_payment_method(self, **kwargs): data = json.dumps(kwargs) response = requests.post(fullUrl, data=data, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) @rest_client_reconnect @@ -28,7 +29,8 @@ def get_payment_methods(self, accountKey, pageSize=10): data = {'pageSize': pageSize} response = requests.get(fullUrl, params=data, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) @rest_client_reconnect @@ -44,7 +46,9 @@ def update_payment_method(self, paymentMethodId, **kwargs): return None response = requests.put(fullUrl, data=data, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False + ) return self.get_json(response) @rest_client_reconnect @@ -52,5 +56,6 @@ def delete_payment_method(self, paymentMethodId): fullUrl = self.zuora_config.base_url + 'payment-methods/' + \ paymentMethodId - response = requests.delete(fullUrl, headers=self.zuora_config.headers) + response = requests.delete(fullUrl, headers=self.zuora_config.headers, + verify=False) return self.get_json(response) diff --git a/zuora/rest_wrapper/request_base.py b/zuora/rest_wrapper/request_base.py index c942481..abc2aa6 100644 --- a/zuora/rest_wrapper/request_base.py +++ b/zuora/rest_wrapper/request_base.py @@ -29,7 +29,8 @@ def __init__(self, zuora_config): def login(self): fullUrl = self.zuora_config.base_url + 'connections' - response = requests.post(fullUrl, headers=self.zuora_config.headers) + response = requests.post(fullUrl, headers=self.zuora_config.headers, + verify=False) return self.get_json(response) def get_json(self, response): diff --git a/zuora/rest_wrapper/subscription_manager.py b/zuora/rest_wrapper/subscription_manager.py index 82a02b5..aaa4c2b 100644 --- a/zuora/rest_wrapper/subscription_manager.py +++ b/zuora/rest_wrapper/subscription_manager.py @@ -16,13 +16,15 @@ def get_subscriptions_by_account(self, accountKey, pageSize=10): data = {'pageSize': pageSize} response = requests.get(fullUrl, params=data, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) @rest_client_reconnect def get_subscriptions_by_key(self, subsKey): fullUrl = self.zuora_config.base_url + 'subscriptions/' + subsKey - response = requests.get(fullUrl, headers=self.zuora_config.headers) + response = requests.get(fullUrl, headers=self.zuora_config.headers, + verify=False) return self.get_json(response) @rest_client_reconnect @@ -32,7 +34,9 @@ def renew_subscription(self, subsKey, '/renew' data = json.dumps(jsonParams) response = requests.put(fullUrl, data=data, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False + ) return self.get_json(response) @rest_client_reconnect @@ -44,7 +48,8 @@ def cancel_subscription(self, subsKey, jsonParams={}): data = json.dumps(jsonParams) log.info("Zuora REST: Canceling subscription: %s" % subsKey) response = requests.put(fullUrl, data=data, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) @rest_client_reconnect @@ -52,7 +57,8 @@ def preview_subscription(self, jsonParams): fullUrl = self.zuora_config.base_url + 'subscriptions/preview' data = json.dumps(jsonParams) response = requests.post(fullUrl, data=data, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) @rest_client_reconnect @@ -60,7 +66,8 @@ def create_subscription(self, jsonParams): fullUrl = self.zuora_config.base_url + 'subscriptions' data = json.dumps(jsonParams) response = requests.post(fullUrl, data=data, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) @rest_client_reconnect @@ -68,6 +75,7 @@ def update_subscription(self, subsKey, jsonParams): fullUrl = self.zuora_config.base_url + 'subscriptions/' + subsKey data = json.dumps(jsonParams) response = requests.put(fullUrl, data=data, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) \ No newline at end of file diff --git a/zuora/rest_wrapper/transaction_manager.py b/zuora/rest_wrapper/transaction_manager.py index a719b82..639e88f 100644 --- a/zuora/rest_wrapper/transaction_manager.py +++ b/zuora/rest_wrapper/transaction_manager.py @@ -13,7 +13,8 @@ def get_invoices(self, accountKey, pageSize=10): 'pageSize': pageSize } response = requests.get(fullUrl, params=params, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) @rest_client_reconnect @@ -24,7 +25,8 @@ def get_payments(self, accountKey, pageSize=10): 'pageSize': pageSize } response = requests.get(fullUrl, params=params, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) @rest_client_reconnect @@ -32,5 +34,6 @@ def invoice_and_collect(self, jsonParams): fullUrl = self.zuora_config.base_url + 'operations/invoice-collect' data = json.dumps(jsonParams) response = requests.post(fullUrl, data=data, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) diff --git a/zuora/rest_wrapper/usage_manager.py b/zuora/rest_wrapper/usage_manager.py index 58943a1..00c6cc7 100644 --- a/zuora/rest_wrapper/usage_manager.py +++ b/zuora/rest_wrapper/usage_manager.py @@ -10,5 +10,6 @@ def get_usage(self, accountKey, pageSize=10): accountKey params = {'pageSize': pageSize} response = requests.get(fullUrl, params=params, - headers=self.zuora_config.headers) + headers=self.zuora_config.headers, + verify=False) return self.get_json(response) From 360071274651005334760dc43c684c67534e4073 Mon Sep 17 00:00:00 2001 From: Dylan Mankey Date: Mon, 25 Aug 2014 15:21:33 -0500 Subject: [PATCH 082/103] updating version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e46aa9c..4f6c898 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setupArgs = { 'name': 'zuora', - 'version': '1.0.15', + 'version': '1.0.16', 'author': 'MapMyFitness', 'author_email': 'brandon.fredericks@mapmyfitness.com', 'url': 'http://github.com/mapmyfitness/python-zuora', From bacd1afcef475b7f5a328bb2bfb5257edbdd4ac1 Mon Sep 17 00:00:00 2001 From: Stephanie Tai Date: Thu, 19 Mar 2015 14:59:33 -0700 Subject: [PATCH 083/103] Wrapped query() and query_more() into query_all(), which returns the last result (from query(), if <=2000 records in result, else from last query_more()) with result.records replaced by a concatenation of all records over all results. --- pip-requirements.txt | 3 --- zuora/client.py | 59 +++++++++++++++++++++++++++++--------------- 2 files changed, 39 insertions(+), 23 deletions(-) delete mode 100644 pip-requirements.txt diff --git a/pip-requirements.txt b/pip-requirements.txt deleted file mode 100644 index 62d3601..0000000 --- a/pip-requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Suds, patched version of 0.3.6 by KCal. (Suds 0.3.6 through 0.4 have a bug around suds/sax/document.py) -#-e git+https://github.com/kevincal/suds-patched@f9869d36fac3077da3b1351eb169518bbdd92ac3#egg=suds-dev -httplib2==0.7.2 diff --git a/zuora/client.py b/zuora/client.py index 6f8276b..e929b36 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -249,6 +249,25 @@ def login(self): SessionHeader.append(session) self.client.set_options(soapheaders=[SessionHeader]) + def query_all(self, query_string): + """ + Stitch together records from query(), query_more() results as needed. + + :param string query_string: ZQL query string + :returns: the API response + """ + current_response = self.query(query_string) + all_records = current_response.records + done = getattr(current_response, 'done') + while not done: + query_locator = getattr(current_response, 'queryLocator') + current_response = self.query_more(query_locator) + all_records += current_response.records + done = getattr(current_response, 'done') + response = current_response + response.records = all_records + return response + def query(self, query_string): """ Pass the zosql querystring into the query() SOAP method @@ -516,7 +535,7 @@ def get_account(self, user_id, account_id=None, id_only=False): WHERE Id = '%s' """ % (fields, account_id) - response = self.query(qs) + response = self.query_all(qs) if getattr(response, "records") and len(response.records) > 0: zAccount = response.records[0] return zAccount @@ -547,7 +566,7 @@ def get_contact(self, email=None, account_id=None): WHERE %s """ % " AND ".join(qs_filter) - response = self.query(qs) + response = self.query_all(qs) if getattr(response, "records") and len(response.records) > 0: zContact = response.records[0] return zContact @@ -573,7 +592,7 @@ def get_invoice(self, invoice_id=None): WHERE Id = '%s' """ % invoice_id - response = self.query(qs) + response = self.query_all(qs) if getattr(response, "records") and len(response.records) > 0: zInvoice = response.records[0] return zInvoice @@ -595,7 +614,7 @@ def get_invoice_pdf(self, invoice_id=None): WHERE Id = '%s' """ % invoice_id - response = self.query(qs) + response = self.query_all(qs) if getattr(response, "records") and len(response.records) > 0: zInvoice = response.records[0] return zInvoice.Body @@ -634,7 +653,7 @@ def get_invoices(self, account_id=None, minimum_balance=None, WHERE %s """ % " AND ".join(qs_filter) - response = self.query(qs) + response = self.query_all(qs) zInvoices = response.records # Return the Match @@ -677,7 +696,7 @@ def get_invoice_items(self, invoice_id=None, subscription_id=None): WHERE %s """ % " AND ".join(qs_filter) - response = self.query(qs) + response = self.query_all(qs) zRecords = response.records # Return the Match @@ -719,7 +738,7 @@ def get_invoice_payment(self, invoice_payment_id=None): WHERE Id = '%s' """ % invoice_payment_id - response = self.query(qs) + response = self.query_all(qs) if getattr(response, "records") and len(response.records) > 0: zInvoicePayment = response.records[0] return zInvoicePayment @@ -750,7 +769,7 @@ def get_invoice_payments(self, invoice_id=None, payment_id=None): FROM InvoicePayment WHERE %s """ % " AND ".join(qs_filter) - response = self.query(qs) + response = self.query_all(qs) zInvoicePayments = response.records # Return the Match @@ -781,7 +800,7 @@ def get_payment(self, payment_id=None): WHERE Id = '%s' """ % payment_id - response = self.query(qs) + response = self.query_all(qs) if getattr(response, "records") and len(response.records) > 0: zPayment = response.records[0] return zPayment @@ -819,7 +838,7 @@ def get_payments(self, account_id=None): WHERE %s """ % " AND ".join(qs_filter) - response = self.query(qs) + response = self.query_all(qs) zPayments = response.records # Return the Match @@ -849,7 +868,7 @@ def get_payment_method(self, payment_method_id): WHERE Id = '%s' """ % payment_method_id - response = self.query(qs) + response = self.query_all(qs) if getattr(response, "records") and len(response.records) > 0: zPaymentMethod = response.records[0] return zPaymentMethod @@ -881,7 +900,7 @@ def get_payment_methods(self, account_id=None, account_number=None, WHERE AccountNumber = '%s' or AccountNumber = 'A-%s' """ % (account_number, account_number) - response = self.query(qs) + response = self.query_all(qs) if getattr(response, "records") and len(response.records) > 0: zAccount = response.records[0] # Check for a default payment method @@ -918,7 +937,7 @@ def get_payment_methods(self, account_id=None, account_number=None, WHERE %s """ % " AND ".join(qs_filter) - response = self.query(qs) + response = self.query_all(qs) zPaymentMethods = response.records # Return the Match @@ -953,7 +972,7 @@ def get_products(self, product_id=None, shortcodes=None): if qs_filter: qs += " WHERE %s" % qs_filter - response = self.query(qs) + response = self.query_all(qs) try: zProducts = response.records return zProducts @@ -1008,7 +1027,7 @@ def get_rate_plan_charges(self, rate_plan_id=None, qs_filter = " OR ".join(id_filter_list) qs += " WHERE %s" % qs_filter - response = self.query(qs) + response = self.query_all(qs) try: return response.records except: @@ -1064,7 +1083,7 @@ def get_product_rate_plans(self, product_rate_plan_id=None, qs += " WHERE %s" % qs_filter - response = self.query(qs) + response = self.query_all(qs) try: zProductRatePlans = response.records return zProductRatePlans @@ -1116,7 +1135,7 @@ def get_product_rate_plan_charges(self, product_rate_plan_id=None, qs += " WHERE %s" % qs_filter - response = self.query(qs) + response = self.query_all(qs) try: return response.records except: @@ -1158,7 +1177,7 @@ def get_product_rate_plan_charge_tiers( qs += " WHERE %s" % qs_filter - response = self.query(qs) + response = self.query_all(qs) try: zProductRatePlanChargeTiers = response.records return zProductRatePlanChargeTiers @@ -1458,7 +1477,7 @@ def get_rate_plans(self, product_rate_plan_id=None, subscription_id=None): if qs_filter: qs += "WHERE %s" % " AND ".join(qs_filter) - response = self.query(qs) + response = self.query_all(qs) zRecords = response.records # Return the Match @@ -1528,7 +1547,7 @@ def get_subscriptions(self, subscription_id=None, account_id=None, if qs_filter: qs += "WHERE %s" % " AND ".join(qs_filter) - response = self.query(qs) + response = self.query_all(qs) zRecords = response.records # Return the Match From 063622a888722db71001cc79e3fd6f1836f6c5e5 Mon Sep 17 00:00:00 2001 From: Stephanie Tai Date: Tue, 24 Mar 2015 11:05:59 -0700 Subject: [PATCH 084/103] Methods/parameters added for easier wrapping of commonly used functions. Added get_accounts. Modified parameters for one or more of: get_contacts, get_subscriptions, get_rate_plans, get_rate_plan_charges, get_products, get_product_rate_plans, get_product_rate_plan_charges. --- zuora/client.py | 168 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 113 insertions(+), 55 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index e929b36..146f037 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -252,6 +252,7 @@ def login(self): def query_all(self, query_string): """ Stitch together records from query(), query_more() results as needed. + https://knowledgecenter.zuora.com/BC_Developers/SOAP_API/M_Zuora_Object_Query_Language :param string query_string: ZQL query string :returns: the API response @@ -543,17 +544,75 @@ def get_account(self, user_id, account_id=None, id_only=False): raise DoesNotExist("Unable to find Account for User ID %s"\ % user_id) - def get_contact(self, email=None, account_id=None): + def get_accounts(self, account_id_list=None, account_id=None, status=None, created_start=None, created_end=None, + id_only=False): """ - Checks to see if the loaded user has a contact + Gets the Accounts matching criteria. + Note: If account_id_list provided, all other criteria are ignored. """ + if id_only: + fields = 'Id' + else: + fields = """Id, AccountNumber, AutoPay, Balance, CreatedDate, DefaultPaymentMethodId, + PaymentGateway, Name, Status, UpdatedDate""" + + # Defaults qs_filter = [] - if account_id: - qs_filter.append("AccountId = '%s'" % account_id) + if account_id_list: + qs_filter.append("%s" % " OR ".join(["Id = '%s'" % account_id for account_id in account_id_list])) + else: + if account_id: + qs_filter.append("Id = '%s'" % account_id) + if status: + qs_filter.append("Status = '%s'" % status) + if created_start: + qs_filter.append("CreatedDate >= '%s'" % created_start) + if created_end: + qs_filter.append("CreatedDate <= '%s'" % created_end) - if email: - qs_filter.append("PersonalEmail = '%s'" % email) + if qs_filter: + qs = """ + SELECT + %s + FROM Account + WHERE %s + """ % (fields, " AND ".join(qs_filter)) + else: + qs = """ + SELECT + %s + FROM Account + """ % fields + + response = self.query_all(qs) + + if getattr(response, "records") and len(response.records) > 0: + zAccount = response.records + return zAccount + else: + raise DoesNotExist("Unable to find Accounts") + + def get_contacts(self, email_list=None, account_id_list=None, email=None, account_id=None, + first_name=None, last_name=None): + """ + Checks to see if the loaded users have a contact + """ + qs_filter = [] + + if email_list: + qs_filter.append("%s" % " OR ".join(["PersonalEmail = '%s'" % email for email in email_list])) + elif account_id_list: + qs_filter.append("%s" % " OR ".join(["AccountId = '%s'" % account_id for account_id in account_id_list])) + else: + if account_id: + qs_filter.append("AccountId = '%s'" % account_id) + if email: + qs_filter.append("PersonalEmail = '%s'" % email) + if first_name: + qs_filter.append("FirstName = '%s'" % first_name) + if last_name: + qs_filter.append("LastName = '%s'" % last_name) qs = """ SELECT @@ -568,7 +627,7 @@ def get_contact(self, email=None, account_id=None): response = self.query_all(qs) if getattr(response, "records") and len(response.records) > 0: - zContact = response.records[0] + zContact = response.records return zContact else: raise DoesNotExist("Unable to find Contact for Email %s"\ @@ -944,30 +1003,24 @@ def get_payment_methods(self, account_id=None, account_number=None, return zPaymentMethods return [] - def get_products(self, product_id=None, shortcodes=None): + def get_products(self, product_id=None): """ Gets the Product. :param str product_id: ProductID - :param list shortcodes: List of shortcode strings """ qs_filter = None qs = """ SELECT Description, EffectiveEndDate, EffectiveStartDate, - Id, SKU, Name, ShortCode__c + Id, SKU, Name FROM Product """ # If we're looking for one specific product if product_id: qs_filter = "Id = '%s'" % product_id - # If we're pulling multiple products by their shortcodes - elif shortcodes: - qs_filter_list = ["ShortCode__c = '%s'" % code - for code in shortcodes] - qs_filter = " OR ".join(qs_filter_list) if qs_filter: qs += " WHERE %s" % qs_filter @@ -1017,6 +1070,8 @@ def get_rate_plan_charges(self, rate_plan_id=None, # If only querying with one rate plan id if rate_plan_id: qs_filter = where_id_string % rate_plan_id + elif product_rate_plan_charge_id: + qs_filter = "ProductRatePlanChargeId = '%s'" % product_rate_plan_charge_id # Otherwise we're querying with multiple rate plan id's else: qs_filter = None @@ -1048,10 +1103,8 @@ def get_product_rate_plans(self, product_rate_plan_id=None, """ qs = """ SELECT - ActivityLevel__c, AgeGroup__c, Description, EffectiveEndDate, EffectiveStartDate, - Gender__c, Id, Name, - Priority__c, ProductId, Site__c, Term__c + Id, Name, ProductId FROM ProductRatePlan """ @@ -1081,7 +1134,8 @@ def get_product_rate_plans(self, product_rate_plan_id=None, else: qs_filter = date_where - qs += " WHERE %s" % qs_filter + if qs_filter: + qs += " WHERE %s" % qs_filter response = self.query_all(qs) try: @@ -1105,14 +1159,13 @@ def get_product_rate_plan_charges(self, product_rate_plan_id=None, SELECT AccountingCode, BillCycleDay, BillCycleType, BillingPeriod, BillingPeriodAlignment, ChargeModel, ChargeType, - CustomImageURL__c, DefaultQuantity, Description, - ExclusiveOfferFlag__c, - HiddenBenefitText__c, Id, IncludedUnits, MaxQuantity, + DefaultQuantity, Description, + Id, IncludedUnits, MaxQuantity, MinQuantity, Name, NumberOfPeriod, OverageCalculationOption, OverageUnusedUnitsCreditOption, PriceIncreasePercentage, ProductRatePlanId, - RevRecCode, RevRecTriggerCondition, ShortCode__c, - SmoothingModel, SortOrder__c, SpecificBillingPeriod, + RevRecCode, RevRecTriggerCondition, + SmoothingModel, SpecificBillingPeriod, TriggerEvent, UOM, UpToPeriods, UseDiscountSpecificAccountingCode FROM ProductRatePlanCharge @@ -1133,7 +1186,8 @@ def get_product_rate_plan_charges(self, product_rate_plan_id=None, # Combine the product rate plan ids for the WHERE clause qs_filter = " OR ".join(id_filter_list) - qs += " WHERE %s" % qs_filter + if qs_filter: + qs += " WHERE %s" % qs_filter response = self.query_all(qs) try: @@ -1447,7 +1501,8 @@ def get_product_rate_plan_charge_pricing(self, product_rate_plan_id): # Run Aggregates return pricing_dict - def get_rate_plans(self, product_rate_plan_id=None, subscription_id=None): + def get_rate_plans(self, product_rate_plan_id_list=None, subscription_id_list=None, + product_rate_plan_id=None, subscription_id=None): """ Gets the RatePlan matching criteria. @@ -1458,11 +1513,15 @@ def get_rate_plans(self, product_rate_plan_id=None, subscription_id=None): # Defaults qs_filter = [] - if product_rate_plan_id: - qs_filter.append("ProductRatePlanId = '%s'" % product_rate_plan_id) - - if subscription_id: - qs_filter.append("SubscriptionId = '%s'" % subscription_id) + if product_rate_plan_id_list: + qs_filter.append("%s" % " OR ".join(["ProductRatePlanId = '%s'" % i for i in product_rate_plan_id_list])) + elif subscription_id_list: + qs_filter.append("%s" % " OR ".join(["SubscriptionId = '%s'" % i for i in subscription_id_list])) + else: + if product_rate_plan_id: + qs_filter.append("ProductRatePlanId = '%s'" % product_rate_plan_id) + if subscription_id: + qs_filter.append("SubscriptionId = '%s'" % subscription_id) # Build Query qs = """ @@ -1483,7 +1542,8 @@ def get_rate_plans(self, product_rate_plan_id=None, subscription_id=None): # Return the Match return zRecords - def get_subscriptions(self, subscription_id=None, account_id=None, + def get_subscriptions(self, subscription_id_list=None, account_id_list=None, + subscription_id=None, account_id=None, auto_renew=None, status=None, term_type=None, term_end_date=None, term_start_date=None, subscription_number=None): @@ -1504,29 +1564,27 @@ def get_subscriptions(self, subscription_id=None, account_id=None, # Defaults qs_filter = [] - if subscription_id: - qs_filter.append("Id = '%s'" % subscription_id) - - if subscription_number: - qs_filter.append("Name = '%s'" % subscription_number) - - if account_id: - qs_filter.append("AccountId = '%s'" % account_id) - - if auto_renew: - qs_filter.append("AutoRenew = %s" % auto_renew.lower()) - - if status: - qs_filter.append("Status = '%s'" % status) - - if term_type: - qs_filter.append("TermType = '%s'" % term_type) - - if term_end_date: - qs_filter.append("TermEndDate = '%s'" % term_end_date) - - if term_start_date: - qs_filter.append("TermStartDate = '%s'" % term_start_date) + if subscription_id_list: + qs_filter.append("%s" % " OR ".join(["Id = '%s'" % i for i in subscription_id_list])) + elif account_id_list: + qs_filter.append("%s" % " OR ".join(["AccountId = '%s'" % i for i in account_id_list])) + else: + if subscription_id: + qs_filter.append("Id = '%s'" % subscription_id) + if subscription_number: + qs_filter.append("Name = '%s'" % subscription_number) + if account_id: + qs_filter.append("AccountId = '%s'" % account_id) + if auto_renew: + qs_filter.append("AutoRenew = %s" % auto_renew.lower()) + if status: + qs_filter.append("Status = '%s'" % status) + if term_type: + qs_filter.append("TermType = '%s'" % term_type) + if term_end_date: + qs_filter.append("TermEndDate = '%s'" % term_end_date) + if term_start_date: + qs_filter.append("TermStartDate = '%s'" % term_start_date) # Build Query qs = """ From eb904206c72189ddef766ce9a49b69b5ac80cb3c Mon Sep 17 00:00:00 2001 From: Stephanie Tai Date: Tue, 31 Mar 2015 18:08:40 -0700 Subject: [PATCH 085/103] Removed id_only parameter from get_accounts() (assume id_only=False). --- zuora/client.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 146f037..59c56f5 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -544,17 +544,13 @@ def get_account(self, user_id, account_id=None, id_only=False): raise DoesNotExist("Unable to find Account for User ID %s"\ % user_id) - def get_accounts(self, account_id_list=None, account_id=None, status=None, created_start=None, created_end=None, - id_only=False): + def get_accounts(self, account_id_list=None, account_id=None, status=None, created_start=None, created_end=None): """ Gets the Accounts matching criteria. Note: If account_id_list provided, all other criteria are ignored. """ - if id_only: - fields = 'Id' - else: - fields = """Id, AccountNumber, AutoPay, Balance, CreatedDate, DefaultPaymentMethodId, - PaymentGateway, Name, Status, UpdatedDate""" + fields = """Id, AccountNumber, AutoPay, Balance, CreatedDate, DefaultPaymentMethodId, + PaymentGateway, Name, Status, UpdatedDate""" # Defaults qs_filter = [] From b1ce3c86452b2794516f4a2e3660e115cd70db8a Mon Sep 17 00:00:00 2001 From: Stephanie Tai Date: Thu, 16 Apr 2015 16:02:15 -0700 Subject: [PATCH 086/103] Modified get_accounts() to additionally take list of account numbers. --- zuora/client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 59c56f5..3be5c66 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -544,7 +544,8 @@ def get_account(self, user_id, account_id=None, id_only=False): raise DoesNotExist("Unable to find Account for User ID %s"\ % user_id) - def get_accounts(self, account_id_list=None, account_id=None, status=None, created_start=None, created_end=None): + def get_accounts(self, account_number_list=None, account_id_list=None, + account_id=None, status=None, created_start=None, created_end=None): """ Gets the Accounts matching criteria. Note: If account_id_list provided, all other criteria are ignored. @@ -555,7 +556,9 @@ def get_accounts(self, account_id_list=None, account_id=None, status=None, creat # Defaults qs_filter = [] - if account_id_list: + if account_number_list: + qs_filter.append("%s" % " OR ".join(["AccountNumber = '%s'" % number for number in account_number_list])) + elif account_id_list: qs_filter.append("%s" % " OR ".join(["Id = '%s'" % account_id for account_id in account_id_list])) else: if account_id: From 22e9a69e11024eb80cc7ca3ea3fd1345efb8bc0b Mon Sep 17 00:00:00 2001 From: Nate Yoder Date: Wed, 1 Jul 2015 00:12:41 -0700 Subject: [PATCH 087/103] Add updated WSDL --- zuora/zuora.a.68.0.wsdl | 1994 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1994 insertions(+) create mode 100644 zuora/zuora.a.68.0.wsdl diff --git a/zuora/zuora.a.68.0.wsdl b/zuora/zuora.a.68.0.wsdl new file mode 100644 index 0000000..0ab0c6c --- /dev/null +++ b/zuora/zuora.a.68.0.wsdl @@ -0,0 +1,1994 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Gets the next batch of sObjects from a query + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 6ce23471201720cffb853fff935f0a5751585370 Mon Sep 17 00:00:00 2001 From: Nate Yoder Date: Tue, 28 Jul 2015 15:19:45 -0700 Subject: [PATCH 088/103] allow the contact email type to be specified --- zuora/client.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 3be5c66..85121b4 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -593,21 +593,25 @@ def get_accounts(self, account_number_list=None, account_id_list=None, raise DoesNotExist("Unable to find Accounts") def get_contacts(self, email_list=None, account_id_list=None, email=None, account_id=None, - first_name=None, last_name=None): + first_name=None, last_name=None, email_type='Personal'): """ Checks to see if the loaded users have a contact """ qs_filter = [] + if email_type not in ['Personal', 'Work']: + SyntaxError('Only Work or Personal emails are supported') + if email_list: - qs_filter.append("%s" % " OR ".join(["PersonalEmail = '%s'" % email for email in email_list])) + qs_filter.append("%s" % " OR ".join(["{type}Email = '{email}'".format(type=email_type, email=email) + for email in email_list])) elif account_id_list: qs_filter.append("%s" % " OR ".join(["AccountId = '%s'" % account_id for account_id in account_id_list])) else: if account_id: qs_filter.append("AccountId = '%s'" % account_id) if email: - qs_filter.append("PersonalEmail = '%s'" % email) + qs_filter.append("{type}Email = '{email}'".format(type=email_type, email=email)) if first_name: qs_filter.append("FirstName = '%s'" % first_name) if last_name: From b865aa4dbc93fe73b3bf7e9c7fdc64ace394bd8d Mon Sep 17 00:00:00 2001 From: Stephanie Tai Date: Thu, 18 Jun 2015 12:40:18 -0700 Subject: [PATCH 089/103] Added DeviceID__c (custom field) to subscription query. --- zuora/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 85121b4..9ee8a95 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1595,7 +1595,7 @@ def get_subscriptions(self, subscription_id_list=None, account_id_list=None, AccountId, AutoRenew, CancelledDate, ContractAcceptanceDate, ContractEffectiveDate, - CreatedById, CreatedDate, InitialTerm, + CreatedById, CreatedDate, DeviceID__c, InitialTerm, IsInvoiceSeparate, Name, Notes, OriginalCreatedDate, OriginalId, PreviousSubscriptionId, RenewalTerm, ServiceActivationDate, Status, From a54cdc41dea96052144a165d861f3f6dbf1dfc8d Mon Sep 17 00:00:00 2001 From: Stephanie Tai Date: Fri, 19 Jun 2015 12:54:05 -0700 Subject: [PATCH 090/103] Modified get_payment_methods() to take account_id_list and to return different fields. --- zuora/client.py | 67 ++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 9ee8a95..366c913 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -938,7 +938,7 @@ def get_payment_method(self, payment_method_id): raise DoesNotExist("Unable to find Payment Method for %s. %s"\ % (payment_method_id, response)) - def get_payment_methods(self, account_id=None, account_number=None, + def get_payment_methods(self, account_id_list=None, account_id=None, account_number=None, email=None, phone=None): """ Gets the Payment Methods matching criteria. @@ -953,48 +953,53 @@ def get_payment_methods(self, account_id=None, account_number=None, # Defaults qs_filter = [] - # Account Number - if account_number: - qs = """ - SELECT - DefaultPaymentMethodId - FROM Account - WHERE AccountNumber = '%s' or AccountNumber = 'A-%s' - """ % (account_number, account_number) - - response = self.query_all(qs) - if getattr(response, "records") and len(response.records) > 0: - zAccount = response.records[0] - # Check for a default payment method - try: - payment_method_id = zAccount.DefaultPaymentMethodId - except: - return [] + if account_id_list: + qs_filter.append("%s" % " OR ".join(["AccountId = '%s'" % i for i in account_id_list])) + else: - # Return as a List - return [self.get_payment_method(payment_method_id)] + # Account Number + if account_number: + qs = """ + SELECT + DefaultPaymentMethodId + FROM Account + WHERE AccountNumber = '%s' or AccountNumber = 'A-%s' + """ % (account_number, account_number) + + response = self.query_all(qs) + if getattr(response, "records") and len(response.records) > 0: + zAccount = response.records[0] + # Check for a default payment method + try: + payment_method_id = zAccount.DefaultPaymentMethodId + except: + return [] + + # Return as a List + return [self.get_payment_method(payment_method_id)] - if account_id: - qs_filter.append("AccountId = '%s'" % account_id) + if account_id: + qs_filter.append("AccountId = '%s'" % account_id) - if email: - qs_filter.append("Email = '%s'" % email) + if email: + qs_filter.append("Email = '%s'" % email) - if phone: - qs_filter.append("Phone = '%s'" % phone) + if phone: + qs_filter.append("Phone = '%s'" % phone) if qs_filter: qs = """ SELECT AccountId, Active, CreatedById, CreatedDate, - CreditCardAddress1, CreditCardAddress2, - CreditCardCity, CreditCardCountry, CreditCardExpirationMonth, CreditCardExpirationYear, CreditCardHolderName, CreditCardMaskNumber, - CreditCardPostalCode, CreditCardState, CreditCardType, - Email, Name, PaypalBaid, PaypalEmail, - PaypalPreapprovalKey, PaypalType, Phone, Type + CreditCardType, Id, + LastFailedSaleTransactionDate, LastTransactionDateTime, LastTransactionStatus, + MaxConsecutivePaymentFailures, Name, NumConsecutiveFailures, + PaymentMethodStatus, PaymentRetryWindow, + TotalNumberOfErrorPayments, TotalNumberOfProcessedPayments, + Type, UpdatedById, UpdatedDate FROM PaymentMethod WHERE %s """ % " AND ".join(qs_filter) From 1a81b08cf2eaa057ef25ebc89d2fa45dd82f8fcc Mon Sep 17 00:00:00 2001 From: Stephanie Tai Date: Fri, 19 Jun 2015 15:23:07 -0700 Subject: [PATCH 091/103] Modified get_payments() to take account_id_list and to return different fields. --- zuora/client.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 366c913..0547ecf 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -870,7 +870,7 @@ def get_payment(self, payment_id=None): raise DoesNotExist("Unable to find Payment for Id %s"\ % payment_id) - def get_payments(self, account_id=None): + def get_payments(self, account_id_list=None, account_id=None): """ Gets the Payments matching criteria. @@ -880,21 +880,22 @@ def get_payments(self, account_id=None): # Defaults qs_filter = [] - if account_id: + if account_id_list: + qs_filter.append("%s" % " OR ".join(["AccountId = '%s'" % i for i in account_id_list])) + elif account_id: qs_filter.append("AccountId = '%s'" % account_id) if qs_filter: qs = """ SELECT - AccountID, AccountingCode, Amount, - AppliedCreditBalanceAmount, AuthTransactionId, + AccountID, Amount, BankIdentificationNumber, CancelledOn, Comment, - CreatedById, CreatedDate, EffectiveDate, GatewayOrderId, - GatewayResponse, GatewayResponseCode, GatewayState, + CreatedById, CreatedDate, EffectiveDate, + Gateway, GatewayOrderId, GatewayResponse, GatewayResponseCode, GatewayState, + Id, MarkedForSubmissionOn, - PaymentMethodID, PaymentNumber, ReferenceId, RefundAmount, - SecondPaymentReferenceId, SettledOn, SoftDescriptor, - Status, SubmittedOn, TransferredToAccounting, + PaymentMethodID, ReferenceId, RefundAmount, + Status, SubmittedOn, Type, UpdatedById, UpdatedDate FROM Payment WHERE %s From 047b31d74f86b5cb05d46cdc19fd58634f861b7c Mon Sep 17 00:00:00 2001 From: Nate Yoder Date: Tue, 18 Aug 2015 14:45:58 -0700 Subject: [PATCH 092/103] add GUID and notes to accounts --- zuora/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 0547ecf..926bee8 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -545,13 +545,16 @@ def get_account(self, user_id, account_id=None, id_only=False): % user_id) def get_accounts(self, account_number_list=None, account_id_list=None, - account_id=None, status=None, created_start=None, created_end=None): + account_id=None, status=None, created_start=None, created_end=None, + additional_fields_str='GUID__c, Notes'): """ Gets the Accounts matching criteria. Note: If account_id_list provided, all other criteria are ignored. """ fields = """Id, AccountNumber, AutoPay, Balance, CreatedDate, DefaultPaymentMethodId, PaymentGateway, Name, Status, UpdatedDate""" + if additional_fields_str: + fields += ', {}'.format(additional_fields_str) # Defaults qs_filter = [] From 49fe4616794d7167c9b12e693081b205a4b20e40 Mon Sep 17 00:00:00 2001 From: Nate Yoder Date: Tue, 25 Aug 2015 09:13:33 -0700 Subject: [PATCH 093/103] enable finding an account by GUID --- zuora/client.py | 12 ++++++------ zuora/tests.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 926bee8..9d02386 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -511,7 +511,7 @@ def activate_account(self, zAccount, zContact, zShippingContact=None, raise ZuoraException( "Unknown Error updating Account. %s" % response) - def get_account(self, user_id, account_id=None, id_only=False): + def get_account(self, user_guid=None, account_id=None, id_only=False): """ Checks to see if the loaded user has an account """ @@ -526,13 +526,13 @@ def get_account(self, user_id, account_id=None, id_only=False): SELECT %s FROM Account - WHERE AccountNumber = '%s' or AccountNumber = 'A-%s' - """ % (fields, user_id, user_id) + WHERE GUID__c = '%s' + """ % (fields, user_guid) else: qs = """ SELECT %s - FROM Account + FROM Accountb WHERE Id = '%s' """ % (fields, account_id) @@ -541,8 +541,8 @@ def get_account(self, user_id, account_id=None, id_only=False): zAccount = response.records[0] return zAccount else: - raise DoesNotExist("Unable to find Account for User ID %s"\ - % user_id) + raise DoesNotExist("Unable to find Account for User GUID %s and AccountId %s"\ + % (user_guid, account_id)) def get_accounts(self, account_number_list=None, account_id_list=None, account_id=None, status=None, created_start=None, created_end=None, diff --git a/zuora/tests.py b/zuora/tests.py index 19f435a..e16120a 100644 --- a/zuora/tests.py +++ b/zuora/tests.py @@ -94,7 +94,7 @@ def test_get_account_query_called(self): response = mock.Mock() response.records = [1] z.query.return_value = response - z.get_account(user_id=42) + z.get_account(user_guid=42) assert z.query.call_count == 1 def test_get_contact_query_called(self): From 0ea21cc35ecad92186a474eb7f18d31bc218b6a0 Mon Sep 17 00:00:00 2001 From: Nate Yoder Date: Wed, 16 Sep 2015 11:57:33 -0700 Subject: [PATCH 094/103] All a subscription to be found and transfered from one account to another --- zuora/client.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 9d02386..376b9a7 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -37,6 +37,7 @@ log_suds.propagate = False SOAP_TIMESTAMP = '%Y-%m-%dT%H:%M:%S-06:00' +UTC_TIMESTAMP = '%Y-%m-%dT%H:%M:%S+00:00' from rest_client import RestClient @@ -443,6 +444,33 @@ def create_payment_method(self, baid=None, user_email=None): return payment_method + def transfer_subscription(self, subscription_id, destination_account_id, name='MergeAccountsByGuid'): + + cur_sub = self.get_subscription(subscription_id) + + # Make Amendment + zAmendment = self.client.factory.create('ns2:Amendment') + effective_date = datetime.utcnow().strftime(UTC_TIMESTAMP) + zAmendment.EffectiveDate = effective_date + zAmendment.Name = "{} {}".format(name, effective_date) + + zAmendment.SubscriptionId = subscription_id + zAmendment.DestinationAccountId = destination_account_id + zAmendment.DestinationInvoiceOwnerId = destination_account_id + zAmendment.Type = "OwnerTransfer" + zAmendment.Description = "Transfer from account {c} to {d} based on GUID".format(c=cur_sub.AccountId, + d=destination_account_id) + zAmendment.Status = "Completed" + + # Create Amendment + response = self.create(zAmendment) + if not isinstance(response, list) or not response[0].Success: + raise ZuoraException( + "Unknown Error creating Amendment. %s" % response) + zAmendment.Id = response[0].Id + + return zAmendment + def create_active_account(self, zAccount=None, zContact=None, payment_method_id=None, user=None, billing_address=None, shipping_address=None, @@ -532,7 +560,7 @@ def get_account(self, user_guid=None, account_id=None, id_only=False): qs = """ SELECT %s - FROM Accountb + FROM Account WHERE Id = '%s' """ % (fields, account_id) @@ -544,6 +572,31 @@ def get_account(self, user_guid=None, account_id=None, id_only=False): raise DoesNotExist("Unable to find Account for User GUID %s and AccountId %s"\ % (user_guid, account_id)) + def get_subscription(self, subscription_id=None): + """ + Checks to see if the loaded user has an account + """ + + fields = """AccountId, AutoRenew, CancelledDate, ContractAcceptanceDate, ContractEffectiveDate, + CreatedById, CreatedDate, DeviceID__c, InitialTerm, IsInvoiceSeparate, Name, Notes, + OriginalCreatedDate, OriginalId, PreviousSubscriptionId, RenewalTerm, + ServiceActivationDate, Status, SubscriptionEndDate, SubscriptionStartDate, + TermEndDate, TermStartDate, TermType, UpdatedById, UpdatedDate, Version""" + + qs = """ + SELECT + %s + FROM Subscription + WHERE Id = '%s' + """ % (fields, subscription_id) + + response = self.query_all(qs) + if getattr(response, "records") and len(response.records) > 0: + zSubscription = response.records[0] + return zSubscription + else: + raise DoesNotExist("Unable to find Subscription {s}".format(subscription_id)) + def get_accounts(self, account_number_list=None, account_id_list=None, account_id=None, status=None, created_start=None, created_end=None, additional_fields_str='GUID__c, Notes'): @@ -2035,6 +2088,42 @@ def remove_product_amendment(self, subscription_id, rate_plan_id): return response + def change_subscription_owner(self, subscription_id, destination_account_id): + """ + Use Amendment to make changes to a subscription. For example, if you + wish to change the terms and conditions of a subscription, you would + use an Amendment. + + :param str subscription_id: The identification number for the\ + subscription that is being amended. + :param str rate_plan_id: RatePlanID + + :returns: response + """ + effective_date = datetime.now().strftime(SOAP_TIMESTAMP) + + # Create the product amendment removal + zAmendment = self.create_product_amendment( + effective_date, + subscription_id, + name_prepend="Remove Product Amendment", + amendment_type='RemoveProduct') + + # Make Rate Plan + zRatePlan = self.client.factory.create('ns0:RatePlan') + zRatePlan.AmendmentType = "RemoveProduct" + zRatePlan.AmendmentId = zAmendment.Id + zRatePlan.AmendmentSubscriptionRatePlanId = rate_plan_id + response = self.create(zRatePlan) + if not isinstance(response, list) or not response[0].Success: + raise ZuoraException( + "Unknown Error creating RatePlan. %s" % response) + + # Update the product amendment + response = self.update_product_amendment(effective_date, zAmendment) + + return response + def subscribe(self, product_rate_plan_id, monthly_term, zAccount=None, zContact=None, zShippingContact=None, process_payments_flag=True, From 5a1b2d929218768a194bc1655bc6e1b55c38bc9e Mon Sep 17 00:00:00 2001 From: Nate Yoder Date: Mon, 21 Sep 2015 08:57:34 -0700 Subject: [PATCH 095/103] allow autopay and default payment method id to be specified independently --- zuora/client.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 376b9a7..121df8e 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1676,15 +1676,21 @@ def get_subscriptions(self, subscription_id_list=None, account_id_list=None, # Return the Match return zRecords - def set_default_payment_method_id(self, account_id, payment_method_id, - auto_pay=None): + def setup_default_payment_method(self, account_id, payment_method_id=None, auto_pay=None): # Update the default payment method on the account - account_dict = {'DefaultPaymentMethodId': payment_method_id} - if auto_pay == True: - account_dict['AutoPay'] = True - elif auto_pay == False: - account_dict['AutoPay'] = False - self.update_account(account_id, account_dict) + account_dict = dict() + + if payment_method_id: + account_dict = {'DefaultPaymentMethodId': payment_method_id} + + if auto_pay is not None: + if auto_pay: + account_dict['AutoPay'] = True + elif auto_pay: + account_dict['AutoPay'] = False + + if account_dict: + self.update_account(account_id, account_dict) def gateway_confirm(self, account, user, gateway_name, payment_method): From 8cb1256aba35c0c5e71e2ff05e7fe4bd95e2eab5 Mon Sep 17 00:00:00 2001 From: Nate Yoder Date: Mon, 21 Sep 2015 11:24:33 -0700 Subject: [PATCH 096/103] allow the specification of the cancellation date to automatically switch the cancellationPolicy to be a specific date --- zuora/rest_wrapper/subscription_manager.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/zuora/rest_wrapper/subscription_manager.py b/zuora/rest_wrapper/subscription_manager.py index aaa4c2b..02c3928 100644 --- a/zuora/rest_wrapper/subscription_manager.py +++ b/zuora/rest_wrapper/subscription_manager.py @@ -41,8 +41,11 @@ def renew_subscription(self, subsKey, @rest_client_reconnect def cancel_subscription(self, subsKey, jsonParams={}): - jsonParams.setdefault('cancellationPolicy', - self.zuora_config.default_cancellation_policy) + if 'cancellationEffectiveDate' in jsonParams: + jsonParams['cancellationPolicy'] = 'SpecificDate' + else: + jsonParams.setdefault('cancellationPolicy', + self.zuora_config.default_cancellation_policy) fullUrl = self.zuora_config.base_url + 'subscriptions/' + subsKey + \ '/cancel' data = json.dumps(jsonParams) From 533bbdf08dc4307208d9df5a80342a120c903d49 Mon Sep 17 00:00:00 2001 From: Nate Yoder Date: Wed, 23 Sep 2015 15:57:37 -0700 Subject: [PATCH 097/103] add GUID to the fields when you get an account --- zuora/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zuora/client.py b/zuora/client.py index 121df8e..49ab17c 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -539,7 +539,7 @@ def activate_account(self, zAccount, zContact, zShippingContact=None, raise ZuoraException( "Unknown Error updating Account. %s" % response) - def get_account(self, user_guid=None, account_id=None, id_only=False): + def get_account(self, user_guid=None, account_id=None, id_only=False, extra_fields=['GUID__c']): """ Checks to see if the loaded user has an account """ @@ -548,6 +548,10 @@ def get_account(self, user_guid=None, account_id=None, id_only=False): else: fields = """Id, AccountNumber, AutoPay, Balance, DefaultPaymentMethodId, PaymentGateway, Name, Status, UpdatedDate""" + + if extra_fields: + fields += ', '.join(extra_fields) + # If no account id was specified if not account_id: qs = """ From 7dc5cb3aa3b5c626ff47b306b4346facdb015033 Mon Sep 17 00:00:00 2001 From: Stephanie Tai Date: Thu, 24 Sep 2015 16:44:41 -0700 Subject: [PATCH 098/103] Added fix to get_account() to properly read in extra_fields. --- zuora/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zuora/client.py b/zuora/client.py index 49ab17c..366ef37 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -550,6 +550,7 @@ def get_account(self, user_guid=None, account_id=None, id_only=False, extra_fiel PaymentGateway, Name, Status, UpdatedDate""" if extra_fields: + fields += ', ' fields += ', '.join(extra_fields) # If no account id was specified From eb6003df3f03cb27f6edc58ae0f5544a3f5dac63 Mon Sep 17 00:00:00 2001 From: Stephanie Tai Date: Thu, 24 Sep 2015 18:15:02 -0700 Subject: [PATCH 099/103] Added base_url to ZuoraConfig(). --- zuora/rest_client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zuora/rest_client.py b/zuora/rest_client.py index bd17eb2..b741fd8 100644 --- a/zuora/rest_client.py +++ b/zuora/rest_client.py @@ -15,11 +15,12 @@ ## sampleSubsNumber: change to some Subscription Number in your Zuora tenant #baseUrl = 'https://apisandbox-api.zuora.com/rest/v1/' +baseUrl = 'https://api.zuora.com/rest/v1/' #username = 'rest.user@test.com' #password = 'Zuora001!' #Payment Id of Default Credit Card (specific per tenant) -hpmCreditCardPaymentMethodId = '2c92c0f93cf64d94013cfe2d20db61a7' +#hpmCreditCardPaymentMethodId = '2c92c0f93cf64d94013cfe2d20db61a7' class ZuoraConfig(object): @@ -30,6 +31,7 @@ def __init__(self, zuora_settings): self.headers = {'apiAccessKeyId': zuora_settings['username'], 'apiSecretAccessKey': zuora_settings['password'], 'Content-Type': 'application/json'} + self.base_url = baseUrl class RestClient(object): From 797311eda827490db6548719344ebcd327167d60 Mon Sep 17 00:00:00 2001 From: Stephanie Tai Date: Tue, 13 Oct 2015 13:48:25 -0700 Subject: [PATCH 100/103] Let get_payments() take payment_method_id_list, payment_method_id. --- zuora/client.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 366ef37..5800a66 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -931,7 +931,7 @@ def get_payment(self, payment_id=None): raise DoesNotExist("Unable to find Payment for Id %s"\ % payment_id) - def get_payments(self, account_id_list=None, account_id=None): + def get_payments(self, account_id_list=None, account_id=None, payment_method_id_list=None, payment_method_id=None): """ Gets the Payments matching criteria. @@ -943,8 +943,13 @@ def get_payments(self, account_id_list=None, account_id=None): if account_id_list: qs_filter.append("%s" % " OR ".join(["AccountId = '%s'" % i for i in account_id_list])) - elif account_id: - qs_filter.append("AccountId = '%s'" % account_id) + elif payment_method_id_list: + qs_filter.append("%s" % " OR ".join(["PaymentMethodID = '%s'" % i for i in account_id_list])) + else: + if account_id: + qs_filter.append("AccountId = '%s'" % account_id) + if payment_method_id: + qs_filter.append("PaymentMethodID = '%s'" % payment_method_id) if qs_filter: qs = """ From ef930e3e37941ec2adc87ea978079664ebc4867c Mon Sep 17 00:00:00 2001 From: Nate Yoder Date: Thu, 15 Oct 2015 11:12:38 -0700 Subject: [PATCH 101/103] update get_invoices to allow for list of account_ids --- zuora/client.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/zuora/client.py b/zuora/client.py index 5800a66..b7fa2b0 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -746,7 +746,7 @@ def get_invoice_pdf(self, invoice_id=None): % invoice_id) def get_invoices(self, account_id=None, minimum_balance=None, - status=None): + status=None, invoice_id_list=None, account_id_list=None): """ Gets the Invoices matching criteria. @@ -758,11 +758,18 @@ def get_invoices(self, account_id=None, minimum_balance=None, if account_id: qs_filter.append("AccountId = '%s'" % account_id) + if minimum_balance: qs_filter.append("Balance > '%s'" % minimum_balance) + if status: qs_filter.append("Status = '%s'" % status) + if invoice_id_list: + qs_filter.append("%s" % " OR ".join(["Id = '%s'" % i for i in invoice_id_list])) + elif account_id_list: + qs_filter.append("%s" % " OR ".join(["AccountID = '%s'" % i for i in account_id_list])) + if qs_filter: qs = """ SELECT @@ -1138,7 +1145,8 @@ def get_rate_plan_charges(self, rate_plan_id=None, Segment, TCV, TriggerDate, TriggerEvent, UnusedUnitsCreditRates, UOM, UpdatedById, UpdatedDate, UpToPeriods, UsageRecordRatingOption, - UseDiscountSpecificAccountingCode, Version + UseDiscountSpecificAccountingCode, Version, + Alacarte__c, SKU__c, FeatureStatus__c, FeatureCode__c FROM RatePlanCharge """ % pricing_info where_id_string = "RatePlanId = '%s'" @@ -1673,7 +1681,8 @@ def get_subscriptions(self, subscription_id_list=None, account_id_list=None, RenewalTerm, ServiceActivationDate, Status, SubscriptionEndDate, SubscriptionStartDate, TermEndDate, TermStartDate, TermType, - UpdatedById, UpdatedDate, Version + UpdatedById, UpdatedDate, Version, + Petname__c, KitID__c, TrialDays__c, Type__c, SubscriptionGroup__c, OrderSource__c FROM Subscription """ From 7949d85a9bcd73a5d0cfbe98612f375760117546 Mon Sep 17 00:00:00 2001 From: Stephanie Tai Date: Fri, 16 Oct 2015 13:20:16 -0700 Subject: [PATCH 102/103] Added billing information to get_payment_methods(). --- zuora/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zuora/client.py b/zuora/client.py index b7fa2b0..1a2eeec 100644 --- a/zuora/client.py +++ b/zuora/client.py @@ -1068,6 +1068,8 @@ def get_payment_methods(self, account_id_list=None, account_id=None, account_num CreatedById, CreatedDate, CreditCardExpirationMonth, CreditCardExpirationYear, CreditCardHolderName, CreditCardMaskNumber, + CreditCardAddress1, CreditCardAddress2, + CreditCardCity, CreditCardState, CreditCardPostalCode, CreditCardCountry, CreditCardType, Id, LastFailedSaleTransactionDate, LastTransactionDateTime, LastTransactionStatus, MaxConsecutivePaymentFailures, Name, NumConsecutiveFailures, From d7c09df05575da6afe1c609616bfb1d8db26abd1 Mon Sep 17 00:00:00 2001 From: Nate Yoder Date: Thu, 6 Oct 2016 22:55:55 -0700 Subject: [PATCH 103/103] Update setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 4f6c898..276bd79 100644 --- a/setup.py +++ b/setup.py @@ -38,6 +38,7 @@ def run(self): setupArgs.update({ 'tests_require': ['pytest'], 'cmdclass': {'test': TestRunner}, + 'install_requires': ['httplib2', 'suds-jurko==0.6'], #'install_requires': ['suds >= 0.4', 'python-requests'], Worrying about getting dependency chain right later 'zip_safe': False, })