python3 compatibility + webapp to flask refactoring

This commit is contained in:
Kaan Soral
2024-01-27 20:37:44 +03:00
parent 5180a9c31c
commit 53b3de28ab
81 changed files with 11213 additions and 1085 deletions
+6 -1
View File
@@ -1,4 +1,7 @@
import httplib
try:
import httplib
except:
import http.client as httplib #for python3
import urllib
import hashlib
import hmac
@@ -7,6 +10,8 @@ import base64
from datetime import datetime
from xml.etree.ElementTree import XML
if 1/2 != 0: basestring=str
def encodeUnicodez(args):
if args and type(args)==type({}):
for key,value in args.items():
+343
View File
@@ -0,0 +1,343 @@
try:
import http.client
except:
import http.client as httplib #for python3
import urllib.request, urllib.parse, urllib.error
import hashlib
import hmac
import logging
import base64
from datetime import datetime
from xml.etree.ElementTree import XML
if 1/2 != 0: str=str
def encodeUnicodez(args):
if args and type(args)==type({}):
for key,value in list(args.items()):
if type(value)==type(""):
args[key]=value.encode("utf-8")
log = logging.getLogger(__name__)
class AmazonSES:
def __init__(self, accessKeyID, secretAccessKey):
self._accessKeyID = accessKeyID
self._secretAccessKey = secretAccessKey
self._responseParser = AmazonResponseParser()
def _getSignature(self, dateValue):
h = hmac.new(key=self._secretAccessKey, msg=dateValue, digestmod=hashlib.sha256)
return base64.b64encode(h.digest()).decode()
def _getHeaders(self,oparams,params):
#headers = { 'Content-type': 'application/x-www-form-urlencoded' }
#d = datetime.utcnow()
#dateValue = d.strftime('%a, %d %b %Y %H:%M:%S GMT')
#headers['Date'] = dateValue
#signature = self._getSignature(dateValue)
#headers['X-Amzn-Authorization'] = 'AWS3-HTTPS AWSAccessKeyId=%s, Algorithm=HMACSHA256, Signature=%s' % (self._accessKeyID, signature)
def sign(key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
def getSignatureKey(key, dateStamp, regionName, serviceName):
kDate = sign(('AWS4' + key).encode('utf-8'), dateStamp)
kRegion = sign(kDate, regionName)
kService = sign(kRegion, serviceName)
kSigning = sign(kService, 'aws4_request')
return kSigning
# Read AWS access key from env. variables or configuration file. Best practice is NOT
# to embed credentials in code.
access_key = self._accessKeyID
secret_key = self._secretAccessKey
# Create a date for headers and the credential string
t = datetime.utcnow()
amzdate = t.strftime('%Y%m%dT%H%M%SZ')
datestamp = t.strftime('%Y%m%d') # Date w/o time, used in credential scope
# ************* TASK 1: CREATE A CANONICAL REQUEST *************
# http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
# Step 1 is to define the verb (GET, POST, etc.)--already done.
# Step 2: Create canonical URI--the part of the URI from domain to query
# string (use '/' if no path)
content_type='application/x-www-form-urlencoded'
service = 'ec2'; service='email'
host = 'email.us-east-1.amazonaws.com'
region = 'us-east-1'
endpoint = 'https://email.us-east-1.amazonaws.com'
canonical_uri = '/'
method='POST'
# Step 3: Create the canonical query string. In this example (a GET request),
# request parameters are in the query string. Query string values must
# be URL-encoded (space=%20). The parameters must be sorted by name.
# For this example, the query string is pre-formatted in the request_parameters variable.
#canonical_querystring = request_parameters = 'Action=%s'%oparams['Action']
canonical_querystring=params
canonical_querystring=''
# Step 4: Create the canonical headers and signed headers. Header names
# must be trimmed and lowercase, and sorted in code point order from
# low to high. Note that there is a trailing \n.
canonical_headers = 'host:' + host + '\n' + 'x-amz-date:' + amzdate + '\n'
# Step 5: Create the list of signed headers. This lists the headers
# in the canonical_headers list, delimited with ";" and in alpha order.
# Note: The request can include any headers; canonical_headers and
# signed_headers lists those that you want to be included in the
# hash of the request. "Host" and "x-amz-date" are always required.
signed_headers = 'host;x-amz-date'
# Step 6: Create payload hash (hash of the request body content). For GET
# requests, the payload is an empty string ("").
payload_hash = hashlib.sha256((params).encode('utf-8')).hexdigest()
# Step 7: Combine elements to create canonical request
canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash
# ************* TASK 2: CREATE THE STRING TO SIGN*************
# Match the algorithm to the hashing algorithm you use, either SHA-1 or
# SHA-256 (recommended)
algorithm = 'AWS4-HMAC-SHA256'
credential_scope = datestamp + '/' + region + '/' + service + '/' + 'aws4_request'
string_to_sign = algorithm + '\n' + amzdate + '\n' + credential_scope + '\n' + hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()
logging.info(string_to_sign)
# ************* TASK 3: CALCULATE THE SIGNATURE *************
# Create the signing key using the function defined above.
signing_key = getSignatureKey(secret_key, datestamp, region, service)
# Sign the string_to_sign using the signing_key
signature = hmac.new(signing_key, (string_to_sign).encode('utf-8'), hashlib.sha256).hexdigest()
authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + credential_scope + ', ' + 'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature
headers = {'Content-Type':content_type,
#'X-Amz-Algorithm':'AWS4-HMAC-SHA256',
'X-Amz-Date':amzdate,
'Authorization':authorization_header}
return headers
def _performAction(self, actionName, params=None):
if not params:
params = {}
params['Action'] = actionName
oparams=params
#https://email.us-east-1.amazonaws.com/
conn = http.client.HTTPSConnection('email.us-east-1.amazonaws.com')
encodeUnicodez(params)
params = urllib.parse.urlencode(params)
conn.request('POST', '/', params, self._getHeaders(oparams,params))
response = conn.getresponse()
responseResult = response.read()
conn.close()
return self._responseParser.parse(actionName, response.status, response.reason, responseResult)
def verifyEmailAddress(self, emailAddress):
params = { 'EmailAddress': emailAddress }
return self._performAction('VerifyEmailAddress', params)
def deleteVerifiedEmailAddress(self, emailAddress):
params = { 'EmailAddress': emailAddress }
return self._performAction('DeleteVerifiedEmailAddress', params)
def getSendQuota(self):
return self._performAction('GetSendQuota')
def getSendStatistics(self):
return self._performAction('GetSendStatistics')
def listVerifiedEmailAddresses(self):
return self._performAction('ListVerifiedEmailAddresses')
def sendEmail(self, source, toAddresses, message, replyToAddresses=None, returnPath=None, ccAddresses=None, bccAddresses=None):
params = { 'Source': source }
for objName, addresses in zip(["ToAddresses", "CcAddresses", "BccAddresses"], [toAddresses, ccAddresses, bccAddresses]):
if addresses:
if not isinstance(addresses, str) and getattr(addresses, '__iter__', False):
for i, address in enumerate(addresses, 1):
params['Destination.%s.member.%d' % (objName, i)] = address
else:
params['Destination.%s.member.1' % objName] = addresses
if not returnPath:
returnPath = source
params['ReturnPath'] = returnPath
params['Message.Subject.Charset'] = message.charset
params['Message.Subject.Data'] = message.subject
if message.bodyText:
params['Message.Body.Text.Charset'] = message.charset
params['Message.Body.Text.Data'] = message.bodyText
if message.bodyHtml:
params['Message.Body.Html.Charset'] = message.charset
params['Message.Body.Html.Data'] = message.bodyHtml
return self._performAction('SendEmail', params)
class EmailMessage:
def __init__(self):
self.charset = 'UTF-8'
self.subject = None
self.bodyHtml = None
self.bodyText = None
class AmazonError(Exception):
def __init__(self, errorType, code, message):
self.errorType = errorType
self.code = code
self.message = message
logging.info(message)
class AmazonAPIError(Exception):
def __init__(self, message):
self.message = message
class AmazonResult:
def __init__(self, requestId):
self.requestId = requestId
class AmazonSendEmailResult(AmazonResult):
def __init__(self, requestId, messageId):
self.requestId = requestId
self.messageId = messageId
class AmazonSendQuota(AmazonResult):
def __init__(self, requestId, max24HourSend, maxSendRate, sentLast24Hours):
self.requestId = requestId
self.max24HourSend = max24HourSend
self.maxSendRate = maxSendRate
self.sentLast24Hours = sentLast24Hours
class AmazonSendDataPoint:
def __init__(self, bounces, complaints, deliveryAttempts, rejects, timestamp):
self.bounces = bounces
self.complaints = complaints
self.deliveryAttempts = deliveryAttempts
self.rejects = rejects
self.timestamp = timestamp
class AmazonSendStatistics(AmazonResult):
def __init__(self, requestId):
self.requestId = requestId
self.members = []
class AmazonVerifiedEmails(AmazonSendStatistics):
pass
class AmazonResponseParser:
class XmlResponse:
def __init__(self, str):
self._rootElement = XML(str)
self._namespace = self._rootElement.tag[1:].split("}")[0]
def checkResponseName(self, name):
if self._rootElement.tag == self._fixTag(self._namespace, name):
return True
else:
raise AmazonAPIError('ErrorResponse is invalid.')
def checkActionName(self, actionName):
if self._rootElement.tag == self._fixTag(self._namespace, ('%sResponse' % actionName)):
return True
else:
raise AmazonAPIError('Response of action "%s" is invalid.' % actionName)
def getChild(self, *itemPath):
node = self._findNode(self._rootElement, self._namespace, *itemPath)
if node != None:
return node
else:
raise AmazonAPIError('Node with the specified path was not found.')
def getChildText(self, *itemPath):
node = self.getChild(*itemPath)
return node.text
def _fixTag(self, namespace, tag):
return '{%s}%s' % (namespace, tag)
def _findNode(self, rootElement, namespace, *args):
match = '.'
for s in args:
match += '/{%s}%s' % (namespace, s)
return rootElement.find(match)
def __init__(self):
self._simpleResultActions = ['DeleteVerifiedEmailAddress', 'VerifyEmailAddress']
def _parseSimpleResult(self, actionName, xmlResponse):
if xmlResponse.checkActionName(actionName):
requestId = xmlResponse.getChildText('ResponseMetadata', 'RequestId')
return AmazonResult(requestId)
def _parseSendQuota(self, actionName, xmlResponse):
if xmlResponse.checkActionName(actionName):
requestId = xmlResponse.getChildText('ResponseMetadata', 'RequestId')
value = xmlResponse.getChildText('GetSendQuotaResult', 'Max24HourSend')
max24HourSend = float(value)
value = xmlResponse.getChildText('GetSendQuotaResult', 'MaxSendRate')
maxSendRate = float(value)
value = xmlResponse.getChildText('GetSendQuotaResult', 'SentLast24Hours')
sentLast24Hours = float(value)
return AmazonSendQuota(requestId, max24HourSend, maxSendRate, sentLast24Hours)
#def _parseSendStatistics(self, actionName, xmlResponse):
# if xmlResponse.checkActionName(actionName):
# requestId = xmlResponse.getChildText('ResponseMetadata', 'RequestId')
def _parseListVerifiedEmails(self, actionName, xmlResponse):
if xmlResponse.checkActionName(actionName):
requestId = xmlResponse.getChildText('ResponseMetadata', 'RequestId')
node = xmlResponse.getChild('ListVerifiedEmailAddressesResult', 'VerifiedEmailAddresses')
result = AmazonVerifiedEmails(requestId)
for addr in node:
result.members.append(addr.text)
return result
def _parseSendEmail(self, actionName, xmlResponse):
if xmlResponse.checkActionName(actionName):
requestId = xmlResponse.getChildText('ResponseMetadata', 'RequestId')
messageId = xmlResponse.getChildText('SendEmailResult', 'MessageId')
return AmazonSendEmailResult(requestId, messageId)
def _raiseError(self, xmlResponse):
if xmlResponse.checkResponseName('ErrorResponse'):
errorType = xmlResponse.getChildText('Error', 'Type')
code = xmlResponse.getChildText('Error', 'Code')
message = xmlResponse.getChildText('Error', 'Message')
raise AmazonError(errorType, code, message)
def parse(self, actionName, statusCode, reason, responseResult):
xmlResponse = self.XmlResponse(responseResult)
log.info('Response status code: %s, reason: %s', statusCode, reason)
log.debug(responseResult)
result = None
if statusCode != 200:
self._raiseError(xmlResponse)
else:
if actionName in self._simpleResultActions:
result = self._parseSimpleResult(actionName, xmlResponse)
elif actionName in ['SendEmail']:
result = self._parseSendEmail(actionName, xmlResponse)
elif actionName == 'GetSendQuota':
result = self._parseSendQuota(actionName, xmlResponse)
#elif actionName == 'GetSendStatistics':
# result = self._parseSendStatistics(actionName, xmlResponse)
elif actionName == 'ListVerifiedEmailAddresses':
result = self._parseListVerifiedEmails(actionName, xmlResponse)
else:
raise AmazonAPIError('Action %s is not supported. Please contact: vladimir@tagmask.com' % (actionName,))
return result
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
"""
get_image_size.py
+2 -2
View File
@@ -2,8 +2,8 @@ import calendar
import datetime
import platform
import time
import urllib
import urlparse
#import urllib
#import urlparse
import warnings
import libraries.stripe
+2
View File
@@ -4,6 +4,8 @@ import textwrap
import warnings
import email
if 1/2 != 0: basestring=str
from libraries.stripe import error, util
+2
View File
@@ -2,6 +2,8 @@ import urllib
import warnings
import sys
if 1/2 != 0: basestring=str
from libraries.stripe import api_requestor, error, util, upload_api_base
+2
View File
@@ -6,6 +6,8 @@ import string
import sys
import unittest2
if 1/2 != 0: basestring=str
from mock import patch, Mock
import libraries.stripe
@@ -1,6 +1,8 @@
import sys
import unittest2
if 1/2 != 0: basestring=str
from mock import Mock, patch
import libraries.stripe
@@ -4,6 +4,8 @@ import sys
import unittest2
import libraries.stripe
if 1/2 != 0: basestring=str
from mock import patch
from libraries.stripe.test.helper import (StripeTestCase, NOW, DUMMY_CHARGE, DUMMY_CARD)
+8 -2
View File
@@ -10,13 +10,19 @@ try:
# When cStringIO is available
import cStringIO as StringIO
except ImportError:
import StringIO
try:
import StringIO
except:
from io import StringIO #for Python 3
try:
from urlparse import parse_qsl
except ImportError:
# Python < 2.6
from cgi import parse_qsl
try:
from cgi import parse_qsl
except:
from urllib.parse import parse_qsl #for Python 3
try:
import json
+78
View File
@@ -0,0 +1,78 @@
# Stripe Python bindings
# API docs at http://stripe.com/docs/api
# Authors:
# Patrick Collison <patrick@stripe.com>
# Greg Brockman <gdb@stripe.com>
# Andrew Metcalf <andrew@stripe.com>
# Configuration variables
api_key = None
api_base = 'https://api.stripe.com'
upload_api_base = 'https://uploads.stripe.com'
api_version = None
verify_ssl_certs = True
default_http_client = None
# Resource
from libraries.stripe.resource import ( # noqa
Account,
AlipayAccount,
ApplicationFee,
Balance,
BalanceTransaction,
BankAccount,
BitcoinReceiver,
BitcoinTransaction,
Card,
Charge,
CountrySpec,
Coupon,
Customer,
Dispute,
Event,
FileUpload,
Invoice,
InvoiceItem,
Order,
OrderReturn,
Plan,
Product,
Recipient,
Refund,
SKU,
Subscription,
ThreeDSecure,
Token,
Transfer)
# Error imports. Note that we may want to move these out of the root
# namespace in the future and you should prefer to access them via
# the fully qualified `stripe.error` module.
from libraries.stripe.error import ( # noqa
APIConnectionError,
APIError,
AuthenticationError,
RateLimitError,
CardError,
InvalidRequestError,
StripeError)
# DEPRECATED: These imports will be moved out of the root stripe namespace
# in version 2.0
from libraries.stripe.version import VERSION # noqa
from libraries.stripe.api_requestor import APIRequestor # noqa
from libraries.stripe.resource import ( # noqa
APIResource,
CreateableAPIResource,
DeletableAPIResource,
ListObject,
ListableAPIResource,
SingletonAPIResource,
StripeObject,
StripeObjectEncoder,
UpdateableAPIResource,
convert_to_stripe_object)
from libraries.stripe.util import json, logger # noqa
+342
View File
@@ -0,0 +1,342 @@
import calendar
import datetime
import platform
import time
import urllib.request, urllib.parse, urllib.error
import urllib.parse
import warnings
import libraries.stripe
from libraries.stripe import error, http_client, version, util
from libraries.stripe.multipart_data_generator import MultipartDataGenerator
def _encode_datetime(dttime):
if dttime.tzinfo and dttime.tzinfo.utcoffset(dttime) is not None:
utc_timestamp = calendar.timegm(dttime.utctimetuple())
else:
utc_timestamp = time.mktime(dttime.timetuple())
return int(utc_timestamp)
def _encode_nested_dict(key, data, fmt='%s[%s]'):
d = {}
for subkey, subvalue in data.items():
d[fmt % (key, subkey)] = subvalue
return d
def _api_encode(data):
for key, value in data.items():
key = util.utf8(key)
if value is None:
continue
elif hasattr(value, 'stripe_id'):
yield (key, value.stripe_id)
elif isinstance(value, list) or isinstance(value, tuple):
for sv in value:
if isinstance(sv, dict):
subdict = _encode_nested_dict(key, sv, fmt='%s[][%s]')
for k, v in _api_encode(subdict):
yield (k, v)
else:
yield ("%s[]" % (key,), util.utf8(sv))
elif isinstance(value, dict):
subdict = _encode_nested_dict(key, value)
for subkey, subvalue in _api_encode(subdict):
yield (subkey, subvalue)
elif isinstance(value, datetime.datetime):
yield (key, _encode_datetime(value))
else:
yield (key, util.utf8(value))
def _build_api_url(url, query):
scheme, netloc, path, base_query, fragment = urllib.parse.urlsplit(url)
if base_query:
query = '%s&%s' % (base_query, query)
return urllib.parse.urlunsplit((scheme, netloc, path, query, fragment))
class APIRequestor(object):
def __init__(self, key=None, client=None, api_base=None, account=None):
self.api_base = api_base or libraries.stripe.api_base
self.api_key = key
self.stripe_account = account
from libraries.stripe import verify_ssl_certs as verify
self._client = client or libraries.stripe.default_http_client or \
http_client.new_default_http_client(verify_ssl_certs=verify)
@classmethod
def api_url(cls, url=''):
warnings.warn(
'The `api_url` class method of APIRequestor is '
'deprecated and will be removed in version 2.0.'
'If you need public access to this function, please email us '
'at support@stripe.com.',
DeprecationWarning)
return '%s%s' % (stripe.api_base, url)
@classmethod
def _deprecated_encode(cls, stk, key, value):
warnings.warn(
'The encode_* class methods of APIRequestor are deprecated and '
'will be removed in version 2.0. '
'If you need public access to this function, please email us '
'at support@stripe.com.',
DeprecationWarning, stacklevel=2)
stk.extend(_api_encode({key: value}))
@classmethod
def encode_dict(cls, stk, key, value):
cls._deprecated_encode(stk, key, value)
@classmethod
def encode_list(cls, stk, key, value):
cls._deprecated_encode(stk, key, value)
@classmethod
def encode_datetime(cls, stk, key, value):
cls._deprecated_encode(stk, key, value)
@classmethod
def encode_none(cls, stk, key, value):
cls._deprecated_encode(stk, key, value)
@classmethod
def encode(cls, d):
"""
Internal: encode a string for url representation
"""
warnings.warn(
'The `encode` class method of APIRequestor is deprecated and '
'will be removed in version 2.0.'
'If you need public access to this function, please email us '
'at support@stripe.com.',
DeprecationWarning)
return urllib.parse.urlencode(list(_api_encode(d)))
@classmethod
def build_url(cls, url, params):
warnings.warn(
'The `build_url` class method of APIRequestor is deprecated and '
'will be removed in version 2.0.'
'If you need public access to this function, please email us '
'at support@stripe.com.',
DeprecationWarning)
return _build_api_url(url, cls.encode(params))
def request(self, method, url, params=None, headers=None):
rbody, rcode, rheaders, my_api_key = self.request_raw(
method.lower(), url, params, headers)
resp = self.interpret_response(rbody, rcode, rheaders)
return resp, my_api_key
def handle_api_error(self, rbody, rcode, resp, rheaders):
try:
err = resp['error']
except (KeyError, TypeError):
raise error.APIError(
"Invalid response object from API: %r (HTTP response code "
"was %d)" % (rbody, rcode),
rbody, rcode, resp)
# Rate limits were previously coded as 400's with code 'rate_limit'
if rcode == 429 or (rcode == 400 and err.get('code') == 'rate_limit'):
raise error.RateLimitError(
err.get('message'), rbody, rcode, resp, rheaders)
elif rcode in [400, 404]:
raise error.InvalidRequestError(
err.get('message'), err.get('param'),
rbody, rcode, resp, rheaders)
elif rcode == 401:
raise error.AuthenticationError(
err.get('message'), rbody, rcode, resp,
rheaders)
elif rcode == 402:
raise error.CardError(err.get('message'), err.get('param'),
err.get('code'), rbody, rcode, resp,
rheaders)
else:
raise error.APIError(err.get('message'), rbody, rcode, resp,
rheaders)
def request_raw(self, method, url, params=None, supplied_headers=None):
"""
Mechanism for issuing an API call
"""
from libraries.stripe import api_version
if self.api_key:
my_api_key = self.api_key
else:
from libraries.stripe import api_key
my_api_key = api_key
if my_api_key is None:
raise error.AuthenticationError(
'No API key provided. (HINT: set your API key using '
'"stripe.api_key = <API-KEY>"). You can generate API keys '
'from the Stripe web interface. See https://stripe.com/api '
'for details, or email support@stripe.com if you have any '
'questions.')
abs_url = '%s%s' % (self.api_base, url)
encoded_params = urllib.parse.urlencode(list(_api_encode(params or {})))
if method == 'get' or method == 'delete':
if params:
abs_url = _build_api_url(abs_url, encoded_params)
post_data = None
elif method == 'post':
if supplied_headers is not None and \
supplied_headers.get("Content-Type") == \
"multipart/form-data":
generator = MultipartDataGenerator()
generator.add_params(params or {})
post_data = generator.get_post_data()
supplied_headers["Content-Type"] = \
"multipart/form-data; boundary=%s" % (generator.boundary,)
else:
post_data = encoded_params
else:
raise error.APIConnectionError(
'Unrecognized HTTP method %r. This may indicate a bug in the '
'Stripe bindings. Please contact support@stripe.com for '
'assistance.' % (method,))
ua = {
'bindings_version': version.VERSION,
'lang': 'python',
'publisher': 'stripe',
'httplib': self._client.name,
}
for attr, func in [['lang_version', platform.python_version],
['platform', platform.platform],
['uname', lambda: ' '.join(platform.uname())]]:
try:
val = func()
except Exception as e:
val = "!! %s" % (e,)
ua[attr] = val
headers = {
'X-Stripe-Client-User-Agent': util.json.dumps(ua),
'User-Agent': 'Stripe/v1 PythonBindings/%s' % (version.VERSION,),
'Authorization': 'Bearer %s' % (my_api_key,)
}
if self.stripe_account:
headers['Stripe-Account'] = self.stripe_account
if method == 'post':
headers['Content-Type'] = 'application/x-www-form-urlencoded'
if api_version is not None:
headers['Stripe-Version'] = api_version
if supplied_headers is not None:
for key, value in list(supplied_headers.items()):
headers[key] = value
rbody, rcode, rheaders = self._client.request(
method, abs_url, headers, post_data)
util.logger.info('%s %s %d', method.upper(), abs_url, rcode)
util.logger.debug(
'API request to %s returned (response code, response body) of '
'(%d, %r)',
abs_url, rcode, rbody)
return rbody, rcode, rheaders, my_api_key
def interpret_response(self, rbody, rcode, rheaders):
try:
if hasattr(rbody, 'decode'):
rbody = rbody.decode('utf-8')
resp = util.json.loads(rbody)
except Exception:
raise error.APIError(
"Invalid response body from API: %s "
"(HTTP response code was %d)" % (rbody, rcode),
rbody, rcode, rheaders)
if not (200 <= rcode < 300):
self.handle_api_error(rbody, rcode, resp, rheaders)
return resp
# Deprecated request handling. Will all be removed in 2.0
def _deprecated_request(self, impl, method, url, headers, params):
warnings.warn(
'The *_request functions of APIRequestor are deprecated and '
'will be removed in version 2.0. Please use the client classes '
' in `stripe.http_client` instead',
DeprecationWarning, stacklevel=2)
method = method.lower()
if method == 'get' or method == 'delete':
if params:
url = self.build_url(url, params)
post_data = None
elif method == 'post':
post_data = self.encode(params)
else:
raise error.APIConnectionError(
'Unrecognized HTTP method %r. This may indicate a bug in the '
'Stripe bindings. Please contact support@stripe.com for '
'assistance.' % (method,))
client = impl(verify_ssl_certs=self._client._verify_ssl_certs)
return client.request(method, url, headers, post_data)
def _deprecated_handle_error(self, impl, *args):
warnings.warn(
'The handle_*_error functions of APIRequestor are deprecated and '
'will be removed in version 2.0. Please use the client classes '
' in `stripe.http_client` instead',
DeprecationWarning, stacklevel=2)
client = impl(verify_ssl_certs=self._client._verify_ssl_certs)
return client._handle_request_error(*args)
def requests_request(self, meth, abs_url, headers, params):
from libraries.stripe.http_client import RequestsClient
return self._deprecated_request(RequestsClient, meth, abs_url,
headers, params)
def handle_requests_error(self, err):
from libraries.stripe.http_client import RequestsClient
return self._deprecated_handle_error(RequestsClient, err)
def pycurl_request(self, meth, abs_url, headers, params):
from libraries.stripe.http_client import PycurlClient
return self._deprecated_request(PycurlClient, meth, abs_url,
headers, params)
def handle_pycurl_error(self, err):
from libraries.stripe.http_client import PycurlClient
return self._deprecated_handle_error(PycurlClient, err)
def urlfetch_request(self, meth, abs_url, headers, params):
from libraries.stripe.http_client import UrlFetchClient
return self._deprecated_request(UrlFetchClient, meth, abs_url,
headers, params)
def handle_urlfetch_error(self, err, abs_url):
from libraries.stripe.http_client import UrlFetchClient
return self._deprecated_handle_error(UrlFetchClient, err, abs_url)
def urllib2_request(self, meth, abs_url, headers, params):
from libraries.stripe.http_client import Urllib2Client
return self._deprecated_request(Urllib2Client, meth, abs_url,
headers, params)
def handle_urllib2_error(self, err, abs_url):
from libraries.stripe.http_client import Urllib2Client
return self._deprecated_handle_error(Urllib2Client, err)
File diff suppressed because it is too large Load Diff
+74
View File
@@ -0,0 +1,74 @@
# Exceptions
import sys
class StripeError(Exception):
def __init__(self, message=None, http_body=None, http_status=None,
json_body=None, headers=None):
super(StripeError, self).__init__(message)
if http_body and hasattr(http_body, 'decode'):
try:
http_body = http_body.decode('utf-8')
except:
http_body = ('<Could not decode body as utf-8. '
'Please report to support@stripe.com>')
self._message = message
self.http_body = http_body
self.http_status = http_status
self.json_body = json_body
self.headers = headers or {}
self.request_id = self.headers.get('request-id', None)
def __unicode__(self):
if self.request_id is not None:
msg = self._message or "<empty message>"
return "Request {0}: {1}".format(self.request_id, msg)
else:
return self._message
if sys.version_info > (3, 0):
def __str__(self):
return self.__unicode__()
else:
def __str__(self):
return str(self).encode('utf-8')
class APIError(StripeError):
pass
class APIConnectionError(StripeError):
pass
class CardError(StripeError):
def __init__(self, message, param, code, http_body=None,
http_status=None, json_body=None, headers=None):
super(CardError, self).__init__(
message, http_body, http_status, json_body,
headers)
self.param = param
self.code = code
class InvalidRequestError(StripeError):
def __init__(self, message, param, http_body=None,
http_status=None, json_body=None, headers=None):
super(InvalidRequestError, self).__init__(
message, http_body, http_status, json_body,
headers)
self.param = param
class AuthenticationError(StripeError):
pass
class RateLimitError(StripeError):
pass
+297
View File
@@ -0,0 +1,297 @@
import os
import sys
import textwrap
import warnings
import email
if 1/2 != 0: str=str
from libraries.stripe import error, util
# - Requests is the preferred HTTP library
# - Google App Engine has urlfetch
# - Use Pycurl if it's there (at least it verifies SSL certs)
# - Fall back to urllib2 with a warning if needed
try:
import urllib.request, urllib.error, urllib.parse
except ImportError:
pass
try:
import pycurl
except ImportError:
pycurl = None
try:
import requests
except ImportError:
requests = None
else:
try:
# Require version 0.8.8, but don't want to depend on distutils
version = requests.__version__
major, minor, patch = [int(i) for i in version.split('.')]
except Exception:
# Probably some new-fangled version, so it should support verify
pass
else:
if (major, minor, patch) < (0, 8, 8):
sys.stderr.write(
'Warning: the Stripe library requires that your Python '
'"requests" library be newer than version 0.8.8, but your '
'"requests" library is version %s. Stripe will fall back to '
'an alternate HTTP library so everything should work. We '
'recommend upgrading your "requests" library. If you have any '
'questions, please contact support@stripe.com. (HINT: running '
'"pip install -U requests" should upgrade your requests '
'library to the latest version.)' % (version,))
requests = None
try:
from google.appengine.api import urlfetch
except ImportError:
urlfetch = None
def new_default_http_client(*args, **kwargs):
if urlfetch:
impl = UrlFetchClient
elif requests:
impl = RequestsClient
elif pycurl:
impl = PycurlClient
else:
impl = Urllib2Client
warnings.warn(
"Warning: the Stripe library is falling back to urllib2/urllib "
"because neither requests nor pycurl are installed. "
"urllib2's SSL implementation doesn't verify server "
"certificates. For improved security, we suggest installing "
"requests.")
return impl(*args, **kwargs)
class HTTPClient(object):
def __init__(self, verify_ssl_certs=True):
self._verify_ssl_certs = verify_ssl_certs
def request(self, method, url, headers, post_data=None):
raise NotImplementedError(
'HTTPClient subclasses must implement `request`')
class RequestsClient(HTTPClient):
name = 'requests'
def request(self, method, url, headers, post_data=None):
kwargs = {}
if self._verify_ssl_certs:
kwargs['verify'] = os.path.join(
os.path.dirname(__file__), 'data/ca-certificates.crt')
else:
kwargs['verify'] = False
try:
try:
result = requests.request(method,
url,
headers=headers,
data=post_data,
timeout=80,
**kwargs)
except TypeError as e:
raise TypeError(
'Warning: It looks like your installed version of the '
'"requests" library is not compatible with Stripe\'s '
'usage thereof. (HINT: The most likely cause is that '
'your "requests" library is out of date. You can fix '
'that by running "pip install -U requests".) The '
'underlying error was: %s' % (e,))
# This causes the content to actually be read, which could cause
# e.g. a socket timeout. TODO: The other fetch methods probably
# are susceptible to the same and should be updated.
content = result.content
status_code = result.status_code
except Exception as e:
# Would catch just requests.exceptions.RequestException, but can
# also raise ValueError, RuntimeError, etc.
self._handle_request_error(e)
return content, status_code, result.headers
def _handle_request_error(self, e):
if isinstance(e, requests.exceptions.RequestException):
msg = ("Unexpected error communicating with Stripe. "
"If this problem persists, let us know at "
"support@stripe.com.")
err = "%s: %s" % (type(e).__name__, str(e))
else:
msg = ("Unexpected error communicating with Stripe. "
"It looks like there's probably a configuration "
"issue locally. If this problem persists, let us "
"know at support@stripe.com.")
err = "A %s was raised" % (type(e).__name__,)
if str(e):
err += " with error message %s" % (str(e),)
else:
err += " with no error message"
msg = textwrap.fill(msg) + "\n\n(Network error: %s)" % (err,)
raise error.APIConnectionError(msg)
class UrlFetchClient(HTTPClient):
name = 'urlfetch'
def __init__(self, verify_ssl_certs=True, deadline=55):
self._verify_ssl_certs = verify_ssl_certs
# GAE requests time out after 60 seconds, so make sure to default
# to 55 seconds to allow for a slow Stripe
self._deadline = deadline
def request(self, method, url, headers, post_data=None):
try:
result = urlfetch.fetch(
url=url,
method=method,
headers=headers,
# Google App Engine doesn't let us specify our own cert bundle.
# However, that's ok because the CA bundle they use recognizes
# api.stripe.com.
validate_certificate=self._verify_ssl_certs,
deadline=self._deadline,
payload=post_data
)
except urlfetch.Error as e:
self._handle_request_error(e, url)
return result.content, result.status_code, result.headers
def _handle_request_error(self, e, url):
if isinstance(e, urlfetch.InvalidURLError):
msg = ("The Stripe library attempted to fetch an "
"invalid URL (%r). This is likely due to a bug "
"in the Stripe Python bindings. Please let us know "
"at support@stripe.com." % (url,))
elif isinstance(e, urlfetch.DownloadError):
msg = "There was a problem retrieving data from Stripe."
elif isinstance(e, urlfetch.ResponseTooLargeError):
msg = ("There was a problem receiving all of your data from "
"Stripe. This is likely due to a bug in Stripe. "
"Please let us know at support@stripe.com.")
else:
msg = ("Unexpected error communicating with Stripe. If this "
"problem persists, let us know at support@stripe.com.")
msg = textwrap.fill(msg) + "\n\n(Network error: " + str(e) + ")"
raise error.APIConnectionError(msg)
class PycurlClient(HTTPClient):
name = 'pycurl'
def parse_headers(self, data):
if '\r\n' not in data:
return {}
raw_headers = data.split('\r\n', 1)[1]
headers = email.message_from_string(raw_headers)
return dict((k.lower(), v) for k, v in dict(headers).items())
def request(self, method, url, headers, post_data=None):
s = util.StringIO.StringIO()
rheaders = util.StringIO.StringIO()
curl = pycurl.Curl()
if method == 'get':
curl.setopt(pycurl.HTTPGET, 1)
elif method == 'post':
curl.setopt(pycurl.POST, 1)
curl.setopt(pycurl.POSTFIELDS, post_data)
else:
curl.setopt(pycurl.CUSTOMREQUEST, method.upper())
# pycurl doesn't like unicode URLs
curl.setopt(pycurl.URL, util.utf8(url))
curl.setopt(pycurl.WRITEFUNCTION, s.write)
curl.setopt(pycurl.HEADERFUNCTION, rheaders.write)
curl.setopt(pycurl.NOSIGNAL, 1)
curl.setopt(pycurl.CONNECTTIMEOUT, 30)
curl.setopt(pycurl.TIMEOUT, 80)
curl.setopt(pycurl.HTTPHEADER, ['%s: %s' % (k, v)
for k, v in headers.items()])
if self._verify_ssl_certs:
curl.setopt(pycurl.CAINFO, os.path.join(
os.path.dirname(__file__), 'data/ca-certificates.crt'))
else:
curl.setopt(pycurl.SSL_VERIFYHOST, False)
try:
curl.perform()
except pycurl.error as e:
self._handle_request_error(e)
rbody = s.getvalue()
rcode = curl.getinfo(pycurl.RESPONSE_CODE)
return rbody, rcode, self.parse_headers(rheaders.getvalue())
def _handle_request_error(self, e):
if e[0] in [pycurl.E_COULDNT_CONNECT,
pycurl.E_COULDNT_RESOLVE_HOST,
pycurl.E_OPERATION_TIMEOUTED]:
msg = ("Could not connect to Stripe. Please check your "
"internet connection and try again. If this problem "
"persists, you should check Stripe's service status at "
"https://twitter.com/stripestatus, or let us know at "
"support@stripe.com.")
elif (e[0] in [pycurl.E_SSL_CACERT,
pycurl.E_SSL_PEER_CERTIFICATE]):
msg = ("Could not verify Stripe's SSL certificate. Please make "
"sure that your network is not intercepting certificates. "
"If this problem persists, let us know at "
"support@stripe.com.")
else:
msg = ("Unexpected error communicating with Stripe. If this "
"problem persists, let us know at support@stripe.com.")
msg = textwrap.fill(msg) + "\n\n(Network error: " + e[1] + ")"
raise error.APIConnectionError(msg)
class Urllib2Client(HTTPClient):
if sys.version_info >= (3, 0):
name = 'urllib.request'
else:
name = 'urllib2'
def request(self, method, url, headers, post_data=None):
if sys.version_info >= (3, 0) and isinstance(post_data, str):
post_data = post_data.encode('utf-8')
req = urllib.request.Request(url, post_data, headers)
if method not in ('get', 'post'):
req.get_method = lambda: method.upper()
try:
response = urllib.request.urlopen(req)
rbody = response.read()
rcode = response.code
headers = dict(response.info())
except urllib.error.HTTPError as e:
rcode = e.code
rbody = e.read()
headers = dict(e.info())
except (urllib.error.URLError, ValueError) as e:
self._handle_request_error(e)
lh = dict((k.lower(), v) for k, v in dict(headers).items())
return rbody, rcode, lh
def _handle_request_error(self, e):
msg = ("Unexpected error communicating with Stripe. "
"If this problem persists, let us know at support@stripe.com.")
msg = textwrap.fill(msg) + "\n\n(Network error: " + str(e) + ")"
raise error.APIConnectionError(msg)
+16
View File
@@ -0,0 +1,16 @@
import warnings
warnings.warn("The importers module is deprecated and will be removed in "
"version 2.0. Check the `util` module for json imports",
DeprecationWarning)
def import_json():
warnings.warn(
"'import_json function is deprecated and will be removed in version "
"2.0 of the Stripe python bindings. Please use"
"`from importer import json` instead'",
DeprecationWarning)
from libraries.stripe.util import json
return json
+76
View File
@@ -0,0 +1,76 @@
import random
import sys
import io
class MultipartDataGenerator(object):
def __init__(self, chunk_size=1028):
self.data = io.BytesIO()
self.line_break = "\r\n"
self.boundary = self._initialize_boundary()
self.chunk_size = chunk_size
def add_params(self, params):
for key, value in params.items():
if value is None:
continue
self._write(self.param_header())
self._write(self.line_break)
if hasattr(value, 'read'):
self._write("Content-Disposition: form-data; name=\"")
self._write(key)
self._write("\"; filename=\"")
self._write(value.name)
self._write("\"")
self._write(self.line_break)
self._write("Content-Type: application/octet-stream")
self._write(self.line_break)
self._write(self.line_break)
self._write_file(value)
else:
self._write("Content-Disposition: form-data; name=\"")
self._write(key)
self._write("\"")
self._write(self.line_break)
self._write(self.line_break)
self._write(value)
self._write(self.line_break)
def param_header(self):
return "--%s" % self.boundary
def get_post_data(self):
self._write("--%s--" % (self.boundary,))
self._write(self.line_break)
return self.data.getvalue()
def _write(self, value):
if sys.version_info < (3,):
binary_type = str
text_type = str
else:
binary_type = bytes
text_type = str
if isinstance(value, binary_type):
array = bytearray(value)
elif isinstance(value, text_type):
array = bytearray(value, encoding='utf-8')
else:
raise TypeError("unexpected type: {value_type}"
.format(value_type=type(value)))
self.data.write(array)
def _write_file(self, f):
while True:
file_contents = f.read(self.chunk_size)
if not file_contents:
break
self._write(file_contents)
def _initialize_boundary(self):
return random.randint(0, 2**63)
+979
View File
@@ -0,0 +1,979 @@
import urllib.request, urllib.parse, urllib.error
import warnings
import sys
if 1/2 != 0: str=str
from libraries.stripe import api_requestor, error, util, upload_api_base
def convert_to_stripe_object(resp, api_key, account):
types = {
'account': Account,
'alipay_account': AlipayAccount,
'application_fee': ApplicationFee,
'bank_account': BankAccount,
'bitcoin_receiver': BitcoinReceiver,
'bitcoin_transaction': BitcoinTransaction,
'card': Card,
'charge': Charge,
'country_spec': CountrySpec,
'coupon': Coupon,
'customer': Customer,
'dispute': Dispute,
'event': Event,
'fee_refund': ApplicationFeeRefund,
'file_upload': FileUpload,
'invoice': Invoice,
'invoiceitem': InvoiceItem,
'list': ListObject,
'plan': Plan,
'recipient': Recipient,
'refund': Refund,
'subscription': Subscription,
'three_d_secure': ThreeDSecure,
'token': Token,
'transfer': Transfer,
'transfer_reversal': Reversal,
'product': Product,
'sku': SKU,
'order': Order,
'order_return': OrderReturn
}
if isinstance(resp, list):
return [convert_to_stripe_object(i, api_key, account) for i in resp]
elif isinstance(resp, dict) and not isinstance(resp, StripeObject):
resp = resp.copy()
klass_name = resp.get('object')
if isinstance(klass_name, str):
klass = types.get(klass_name, StripeObject)
else:
klass = StripeObject
return klass.construct_from(resp, api_key, stripe_account=account)
else:
return resp
def populate_headers(idempotency_key):
if idempotency_key is not None:
return {"Idempotency-Key": idempotency_key}
return None
def _compute_diff(current, previous):
if isinstance(current, dict):
previous = previous or {}
diff = current.copy()
for key in set(previous.keys()) - set(diff.keys()):
diff[key] = ""
return diff
return current if current is not None else ""
def _serialize_list(array, previous):
array = array or []
previous = previous or []
params = {}
for i, v in enumerate(array):
previous_item = previous[i] if len(previous) > i else None
if hasattr(v, 'serialize'):
params[str(i)] = v.serialize(previous_item)
else:
params[str(i)] = _compute_diff(v, previous_item)
return params
class StripeObject(dict):
def __init__(self, id=None, api_key=None, stripe_account=None, **params):
super(StripeObject, self).__init__()
self._unsaved_values = set()
self._transient_values = set()
self._retrieve_params = params
self._previous = None
object.__setattr__(self, 'api_key', api_key)
object.__setattr__(self, 'stripe_account', stripe_account)
if id:
self['id'] = id
def update(self, update_dict):
for k in update_dict:
self._unsaved_values.add(k)
return super(StripeObject, self).update(update_dict)
def __setattr__(self, k, v):
if k[0] == '_' or k in self.__dict__:
return super(StripeObject, self).__setattr__(k, v)
else:
self[k] = v
def __getattr__(self, k):
if k[0] == '_':
raise AttributeError(k)
try:
return self[k]
except KeyError as err:
raise AttributeError(*err.args)
def __delattr__(self, k):
if k[0] == '_' or k in self.__dict__:
return super(StripeObject, self).__delattr__(k)
else:
del self[k]
def __setitem__(self, k, v):
if v == "":
raise ValueError(
"You cannot set %s to an empty string. "
"We interpret empty strings as None in requests."
"You may set %s.%s = None to delete the property" % (
k, str(self), k))
super(StripeObject, self).__setitem__(k, v)
# Allows for unpickling in Python 3.x
if not hasattr(self, '_unsaved_values'):
self._unsaved_values = set()
self._unsaved_values.add(k)
def __getitem__(self, k):
try:
return super(StripeObject, self).__getitem__(k)
except KeyError as err:
if k in self._transient_values:
raise KeyError(
"%r. HINT: The %r attribute was set in the past."
"It was then wiped when refreshing the object with "
"the result returned by Stripe's API, probably as a "
"result of a save(). The attributes currently "
"available on this object are: %s" %
(k, k, ', '.join(list(self.keys()))))
else:
raise err
def __delitem__(self, k):
super(StripeObject, self).__delitem__(k)
# Allows for unpickling in Python 3.x
if hasattr(self, '_unsaved_values'):
self._unsaved_values.remove(k)
@classmethod
def construct_from(cls, values, key, stripe_account=None):
instance = cls(values.get('id'), api_key=key,
stripe_account=stripe_account)
instance.refresh_from(values, api_key=key,
stripe_account=stripe_account)
return instance
def refresh_from(self, values, api_key=None, partial=False,
stripe_account=None):
self.api_key = api_key or getattr(values, 'api_key', None)
self.stripe_account = \
stripe_account or getattr(values, 'stripe_account', None)
# Wipe old state before setting new. This is useful for e.g.
# updating a customer, where there is no persistent card
# parameter. Mark those values which don't persist as transient
if partial:
self._unsaved_values = (self._unsaved_values - set(values))
else:
removed = set(self.keys()) - set(values)
self._transient_values = self._transient_values | removed
self._unsaved_values = set()
self.clear()
self._transient_values = self._transient_values - set(values)
for k, v in values.items():
super(StripeObject, self).__setitem__(
k, convert_to_stripe_object(v, api_key, stripe_account))
self._previous = values
@classmethod
def api_base(cls):
return None
def request(self, method, url, params=None, headers=None):
if params is None:
params = self._retrieve_params
requestor = api_requestor.APIRequestor(
key=self.api_key, api_base=self.api_base(),
account=self.stripe_account)
response, api_key = requestor.request(method, url, params, headers)
return convert_to_stripe_object(response, api_key, self.stripe_account)
def __repr__(self):
ident_parts = [type(self).__name__]
if isinstance(self.get('object'), str):
ident_parts.append(self.get('object'))
if isinstance(self.get('id'), str):
ident_parts.append('id=%s' % (self.get('id'),))
unicode_repr = '<%s at %s> JSON: %s' % (
' '.join(ident_parts), hex(id(self)), str(self))
if sys.version_info[0] < 3:
return unicode_repr.encode('utf-8')
else:
return unicode_repr
def __str__(self):
return util.json.dumps(self, sort_keys=True, indent=2)
def to_dict(self):
warnings.warn(
'The `to_dict` method is deprecated and will be removed in '
'version 2.0 of the Stripe bindings. The StripeObject is '
'itself now a subclass of `dict`.',
DeprecationWarning)
return dict(self)
@property
def stripe_id(self):
return self.id
def serialize(self, previous):
params = {}
unsaved_keys = self._unsaved_values or set()
previous = previous or self._previous or {}
for k, v in list(self.items()):
if k == 'id' or (isinstance(k, str) and k.startswith('_')):
continue
elif isinstance(v, APIResource):
continue
elif hasattr(v, 'serialize'):
params[k] = v.serialize(previous.get(k, None))
elif k in unsaved_keys:
params[k] = _compute_diff(v, previous.get(k, None))
elif k == 'additional_owners' and v is not None:
params[k] = _serialize_list(v, previous.get(k, None))
return params
class StripeObjectEncoder(util.json.JSONEncoder):
def __init__(self, *args, **kwargs):
warnings.warn(
'`StripeObjectEncoder` is deprecated and will be removed in '
'version 2.0 of the Stripe bindings. StripeObject is now a '
'subclass of `dict` and is handled natively by the built-in '
'json library.',
DeprecationWarning)
super(StripeObjectEncoder, self).__init__(*args, **kwargs)
class APIResource(StripeObject):
@classmethod
def retrieve(cls, id, api_key=None, **params):
instance = cls(id, api_key, **params)
instance.refresh()
return instance
def refresh(self):
self.refresh_from(self.request('get', self.instance_url()))
return self
@classmethod
def class_name(cls):
if cls == APIResource:
raise NotImplementedError(
'APIResource is an abstract class. You should perform '
'actions on its subclasses (e.g. Charge, Customer)')
return str(urllib.parse.quote_plus(cls.__name__.lower()))
@classmethod
def class_url(cls):
cls_name = cls.class_name()
return "/v1/%ss" % (cls_name,)
def instance_url(self):
id = self.get('id')
if not id:
raise error.InvalidRequestError(
'Could not determine which URL to request: %s instance '
'has invalid ID: %r' % (type(self).__name__, id), 'id')
id = util.utf8(id)
base = self.class_url()
extn = urllib.parse.quote_plus(id)
return "%s/%s" % (base, extn)
class ListObject(StripeObject):
def list(self, **params):
return self.request('get', self['url'], params)
def all(self, **params):
warnings.warn("The `all` method is deprecated and will"
"be removed in future versions. Please use the "
"`list` method instead",
DeprecationWarning)
return self.list(**params)
def auto_paging_iter(self):
page = self
params = dict(self._retrieve_params)
while True:
item_id = None
for item in page:
item_id = item.get('id', None)
yield item
if not getattr(page, 'has_more', False) or item_id is None:
return
params['starting_after'] = item_id
page = self.list(**params)
def create(self, idempotency_key=None, **params):
headers = populate_headers(idempotency_key)
return self.request('post', self['url'], params, headers)
def retrieve(self, id, **params):
base = self.get('url')
id = util.utf8(id)
extn = urllib.parse.quote_plus(id)
url = "%s/%s" % (base, extn)
return self.request('get', url, params)
def __iter__(self):
return getattr(self, 'data', []).__iter__()
class SingletonAPIResource(APIResource):
@classmethod
def retrieve(cls, **params):
return super(SingletonAPIResource, cls).retrieve(None, **params)
@classmethod
def class_url(cls):
cls_name = cls.class_name()
return "/v1/%s" % (cls_name,)
def instance_url(self):
return self.class_url()
# Classes of API operations
class ListableAPIResource(APIResource):
@classmethod
def all(cls, *args, **params):
warnings.warn("The `all` class method is deprecated and will"
"be removed in future versions. Please use the "
"`list` class method instead",
DeprecationWarning)
return cls.list(*args, **params)
@classmethod
def auto_paging_iter(self, *args, **params):
return self.list(*args, **params).auto_paging_iter()
@classmethod
def list(cls, api_key=None, idempotency_key=None,
stripe_account=None, **params):
requestor = api_requestor.APIRequestor(api_key,
api_base=cls.api_base(),
account=stripe_account)
url = cls.class_url()
response, api_key = requestor.request('get', url, params)
stripe_object = convert_to_stripe_object(response, api_key,
stripe_account)
stripe_object._retrieve_params = params
return stripe_object
class CreateableAPIResource(APIResource):
@classmethod
def create(cls, api_key=None, idempotency_key=None,
stripe_account=None, **params):
requestor = api_requestor.APIRequestor(api_key, account=stripe_account)
url = cls.class_url()
headers = populate_headers(idempotency_key)
response, api_key = requestor.request('post', url, params, headers)
return convert_to_stripe_object(response, api_key, stripe_account)
class UpdateableAPIResource(APIResource):
@classmethod
def _modify(cls, url, api_key=None, idempotency_key=None,
stripe_account=None, **params):
requestor = api_requestor.APIRequestor(api_key, account=stripe_account)
headers = populate_headers(idempotency_key)
response, api_key = requestor.request('post', url, params, headers)
return convert_to_stripe_object(response, api_key, stripe_account)
@classmethod
def modify(cls, sid, **params):
url = "%s/%s" % (cls.class_url(), urllib.parse.quote_plus(util.utf8(sid)))
return cls._modify(url, **params)
def save(self, idempotency_key=None):
updated_params = self.serialize(None)
headers = populate_headers(idempotency_key)
if updated_params:
self.refresh_from(self.request('post', self.instance_url(),
updated_params, headers))
else:
util.logger.debug("Trying to save already saved object %r", self)
return self
class DeletableAPIResource(APIResource):
def delete(self, **params):
self.refresh_from(self.request('delete', self.instance_url(), params))
return self
# API objects
class Account(CreateableAPIResource, ListableAPIResource,
UpdateableAPIResource, DeletableAPIResource):
@classmethod
def retrieve(cls, id=None, api_key=None, **params):
instance = cls(id, api_key, **params)
instance.refresh()
return instance
@classmethod
def modify(cls, id=None, **params):
return cls._modify(cls._build_instance_url(id), **params)
@classmethod
def _build_instance_url(cls, sid):
if not sid:
return "/v1/account"
sid = util.utf8(sid)
base = cls.class_url()
extn = urllib.parse.quote_plus(sid)
return "%s/%s" % (base, extn)
def instance_url(self):
return self._build_instance_url(self.get('id'))
def reject(self, reason=None, idempotency_key=None):
url = self.instance_url() + '/reject'
headers = populate_headers(idempotency_key)
if reason:
params = {"reason": reason}
else:
params = {}
self.refresh_from(
self.request('post', url, params, headers)
)
return self
class AlipayAccount(UpdateableAPIResource, DeletableAPIResource):
@classmethod
def _build_instance_url(cls, customer, sid):
token = util.utf8(sid)
extn = urllib.parse.quote_plus(token)
customer = util.utf8(customer)
base = Customer.class_url()
owner_extn = urllib.parse.quote_plus(customer)
return "%s/%s/sources/%s" % (base, owner_extn, extn)
def instance_url(self):
return self._build_instance_url(self.customer, self.id)
@classmethod
def modify(cls, customer, id, **params):
url = cls._build_instance_url(customer, id)
return cls._modify(url, **params)
@classmethod
def retrieve(cls, id, api_key=None, stripe_account=None, **params):
raise NotImplementedError(
"Can't retrieve an Alipay account without a customer ID. "
"Use customer.sources.retrieve('alipay_account_id') instead.")
class Balance(SingletonAPIResource):
pass
class BalanceTransaction(ListableAPIResource):
@classmethod
def class_url(cls):
return '/v1/balance/history'
class Card(UpdateableAPIResource, DeletableAPIResource):
def instance_url(self):
token = util.utf8(self.id)
extn = urllib.parse.quote_plus(token)
if (hasattr(self, 'customer')):
customer = util.utf8(self.customer)
base = Customer.class_url()
owner_extn = urllib.parse.quote_plus(customer)
class_base = "sources"
elif (hasattr(self, 'recipient')):
recipient = util.utf8(self.recipient)
base = Recipient.class_url()
owner_extn = urllib.parse.quote_plus(recipient)
class_base = "cards"
elif (hasattr(self, 'account')):
account = util.utf8(self.account)
base = Account.class_url()
owner_extn = urllib.parse.quote_plus(account)
class_base = "external_accounts"
else:
raise error.InvalidRequestError(
"Could not determine whether card_id %s is "
"attached to a customer, recipient, or "
"account." % token, 'id')
return "%s/%s/%s/%s" % (base, owner_extn, class_base, extn)
@classmethod
def modify(cls, sid, **params):
raise NotImplementedError(
"Can't modify a card without a customer, recipient or account "
"ID. Call save on customer.sources.retrieve('card_id'), "
"recipient.cards.retrieve('card_id'), or "
"account.external_accounts.retrieve('card_id') instead.")
@classmethod
def retrieve(cls, id, api_key=None, stripe_account=None, **params):
raise NotImplementedError(
"Can't retrieve a card without a customer, recipient or account "
"ID. Use customer.sources.retrieve('card_id'), "
"recipient.cards.retrieve('card_id'), or "
"account.external_accounts.retrieve('card_id') instead.")
class VerifyMixin(object):
def verify(self, idempotency_key=None, **params):
url = self.instance_url() + '/verify'
headers = populate_headers(idempotency_key)
self.refresh_from(self.request('post', url, params, headers))
return self
class BankAccount(UpdateableAPIResource, DeletableAPIResource, VerifyMixin):
def instance_url(self):
token = util.utf8(self.id)
extn = urllib.parse.quote_plus(token)
if (hasattr(self, 'customer')):
customer = util.utf8(self.customer)
base = Customer.class_url()
owner_extn = urllib.parse.quote_plus(customer)
class_base = "sources"
elif (hasattr(self, 'account')):
account = util.utf8(self.account)
base = Account.class_url()
owner_extn = urllib.parse.quote_plus(account)
class_base = "external_accounts"
else:
raise error.InvalidRequestError(
"Could not determine whether bank_account_id %s is "
"attached to a customer or an account." % token, 'id')
return "%s/%s/%s/%s" % (base, owner_extn, class_base, extn)
@classmethod
def modify(cls, sid, **params):
raise NotImplementedError(
"Can't modify a bank account without a customer or account ID. "
"Call save on customer.sources.retrieve('bank_account_id') or "
"account.external_accounts.retrieve('bank_account_id') instead.")
@classmethod
def retrieve(cls, id, api_key=None, stripe_account=None, **params):
raise NotImplementedError(
"Can't retrieve a bank account without a customer or account ID. "
"Use customer.sources.retrieve('bank_account_id') or "
"account.external_accounts.retrieve('bank_account_id') instead.")
class Charge(CreateableAPIResource, ListableAPIResource,
UpdateableAPIResource):
def refund(self, idempotency_key=None, **params):
url = self.instance_url() + '/refund'
headers = populate_headers(idempotency_key)
self.refresh_from(self.request('post', url, params, headers))
return self
def capture(self, idempotency_key=None, **params):
url = self.instance_url() + '/capture'
headers = populate_headers(idempotency_key)
self.refresh_from(self.request('post', url, params, headers))
return self
def update_dispute(self, idempotency_key=None, **params):
requestor = api_requestor.APIRequestor(self.api_key,
account=self.stripe_account)
url = self.instance_url() + '/dispute'
headers = populate_headers(idempotency_key)
response, api_key = requestor.request('post', url, params, headers)
self.refresh_from({'dispute': response}, api_key, True)
return self.dispute
def close_dispute(self, idempotency_key=None):
requestor = api_requestor.APIRequestor(self.api_key,
account=self.stripe_account)
url = self.instance_url() + '/dispute/close'
headers = populate_headers(idempotency_key)
response, api_key = requestor.request('post', url, {}, headers)
self.refresh_from({'dispute': response}, api_key, True)
return self.dispute
def mark_as_fraudulent(self, idempotency_key=None):
params = {
'fraud_details': {'user_report': 'fraudulent'}
}
url = self.instance_url()
headers = populate_headers(idempotency_key)
self.refresh_from(self.request('post', url, params, headers))
return self
def mark_as_safe(self, idempotency_key=None):
params = {
'fraud_details': {'user_report': 'safe'}
}
url = self.instance_url()
headers = populate_headers(idempotency_key)
self.refresh_from(self.request('post', url, params, headers))
return self
class Dispute(CreateableAPIResource, ListableAPIResource,
UpdateableAPIResource):
def close(self, idempotency_key=None):
url = self.instance_url() + '/close'
headers = populate_headers(idempotency_key)
self.refresh_from(self.request('post', url, {}, headers))
return self
class Customer(CreateableAPIResource, UpdateableAPIResource,
ListableAPIResource, DeletableAPIResource):
def add_invoice_item(self, idempotency_key=None, **params):
params['customer'] = self.id
ii = InvoiceItem.create(self.api_key,
idempotency_key=idempotency_key, **params)
return ii
def invoices(self, **params):
params['customer'] = self.id
invoices = Invoice.list(self.api_key, **params)
return invoices
def invoice_items(self, **params):
params['customer'] = self.id
iis = InvoiceItem.list(self.api_key, **params)
return iis
def charges(self, **params):
params['customer'] = self.id
charges = Charge.list(self.api_key, **params)
return charges
def update_subscription(self, idempotency_key=None, **params):
warnings.warn(
'The `update_subscription` method is deprecated. Instead, use the '
'`subscriptions` resource on the customer object to update a '
'subscription',
DeprecationWarning)
requestor = api_requestor.APIRequestor(self.api_key,
account=self.stripe_account)
url = self.instance_url() + '/subscription'
headers = populate_headers(idempotency_key)
response, api_key = requestor.request('post', url, params, headers)
self.refresh_from({'subscription': response}, api_key, True)
return self.subscription
def cancel_subscription(self, idempotency_key=None, **params):
warnings.warn(
'The `cancel_subscription` method is deprecated. Instead, use the '
'`subscriptions` resource on the customer object to cancel a '
'subscription',
DeprecationWarning)
requestor = api_requestor.APIRequestor(self.api_key,
account=self.stripe_account)
url = self.instance_url() + '/subscription'
headers = populate_headers(idempotency_key)
response, api_key = requestor.request('delete', url, params, headers)
self.refresh_from({'subscription': response}, api_key, True)
return self.subscription
def delete_discount(self, **params):
requestor = api_requestor.APIRequestor(self.api_key,
account=self.stripe_account)
url = self.instance_url() + '/discount'
_, api_key = requestor.request('delete', url)
self.refresh_from({'discount': None}, api_key, True)
class Invoice(CreateableAPIResource, ListableAPIResource,
UpdateableAPIResource):
def pay(self, idempotency_key=None):
headers = populate_headers(idempotency_key)
return self.request('post', self.instance_url() + '/pay', {}, headers)
@classmethod
def upcoming(cls, api_key=None, stripe_account=None, **params):
requestor = api_requestor.APIRequestor(api_key,
account=stripe_account)
url = cls.class_url() + '/upcoming'
response, api_key = requestor.request('get', url, params)
return convert_to_stripe_object(response, api_key, stripe_account)
class InvoiceItem(CreateableAPIResource, UpdateableAPIResource,
ListableAPIResource, DeletableAPIResource):
pass
class Plan(CreateableAPIResource, DeletableAPIResource,
UpdateableAPIResource, ListableAPIResource):
pass
class Subscription(CreateableAPIResource, DeletableAPIResource,
UpdateableAPIResource, ListableAPIResource):
def delete_discount(self, **params):
requestor = api_requestor.APIRequestor(self.api_key,
account=self.stripe_account)
url = self.instance_url() + '/discount'
_, api_key = requestor.request('delete', url)
self.refresh_from({'discount': None}, api_key, True)
class Refund(CreateableAPIResource, ListableAPIResource,
UpdateableAPIResource):
pass
class Token(CreateableAPIResource):
pass
class Coupon(CreateableAPIResource, UpdateableAPIResource,
DeletableAPIResource, ListableAPIResource):
pass
class Event(ListableAPIResource):
pass
class Transfer(CreateableAPIResource, UpdateableAPIResource,
ListableAPIResource):
def cancel(self):
self.refresh_from(self.request('post',
self.instance_url() + '/cancel'))
class Reversal(UpdateableAPIResource):
def instance_url(self):
token = util.utf8(self.id)
transfer = util.utf8(self.transfer)
base = Transfer.class_url()
cust_extn = urllib.parse.quote_plus(transfer)
extn = urllib.parse.quote_plus(token)
return "%s/%s/reversals/%s" % (base, cust_extn, extn)
@classmethod
def modify(cls, sid, **params):
raise NotImplementedError(
"Can't modify a reversal without a transfer"
"ID. Call save on transfer.reversals.retrieve('reversal_id')")
@classmethod
def retrieve(cls, id, api_key=None, **params):
raise NotImplementedError(
"Can't retrieve a reversal without a transfer"
"ID. Use transfer.reversals.retrieve('reversal_id')")
class Recipient(CreateableAPIResource, UpdateableAPIResource,
ListableAPIResource, DeletableAPIResource):
def transfers(self, **params):
params['recipient'] = self.id
transfers = Transfer.list(self.api_key, **params)
return transfers
class FileUpload(ListableAPIResource):
@classmethod
def api_base(cls):
return upload_api_base
@classmethod
def class_name(cls):
return 'file'
@classmethod
def create(cls, api_key=None, stripe_account=None, **params):
requestor = api_requestor.APIRequestor(
api_key, api_base=cls.api_base(), account=stripe_account)
url = cls.class_url()
supplied_headers = {
"Content-Type": "multipart/form-data"
}
response, api_key = requestor.request(
'post', url, params=params, headers=supplied_headers)
return convert_to_stripe_object(response, api_key, stripe_account)
class ApplicationFee(ListableAPIResource):
@classmethod
def class_name(cls):
return 'application_fee'
def refund(self, idempotency_key=None, **params):
headers = populate_headers(idempotency_key)
url = self.instance_url() + '/refund'
self.refresh_from(self.request('post', url, params, headers))
return self
class ApplicationFeeRefund(UpdateableAPIResource):
@classmethod
def _build_instance_url(cls, fee, sid):
fee = util.utf8(fee)
sid = util.utf8(sid)
base = ApplicationFee.class_url()
cust_extn = urllib.parse.quote_plus(fee)
extn = urllib.parse.quote_plus(sid)
return "%s/%s/refunds/%s" % (base, cust_extn, extn)
@classmethod
def modify(cls, fee, sid, **params):
url = cls._build_instance_url(fee, sid)
return cls._modify(url, **params)
def instance_url(self):
return self._build_instance_url(self.fee, self.id)
@classmethod
def retrieve(cls, id, api_key=None, **params):
raise NotImplementedError(
"Can't retrieve a refund without an application fee ID. "
"Use application_fee.refunds.retrieve('refund_id') instead.")
class BitcoinReceiver(CreateableAPIResource, UpdateableAPIResource,
DeletableAPIResource, ListableAPIResource):
def instance_url(self):
token = util.utf8(self.id)
extn = urllib.parse.quote_plus(token)
if (hasattr(self, 'customer')):
customer = util.utf8(self.customer)
base = Customer.class_url()
cust_extn = urllib.parse.quote_plus(customer)
return "%s/%s/sources/%s" % (base, cust_extn, extn)
else:
base = BitcoinReceiver.class_url()
return "%s/%s" % (base, extn)
@classmethod
def class_url(cls):
return '/v1/bitcoin/receivers'
class BitcoinTransaction(StripeObject):
pass
class Product(CreateableAPIResource, UpdateableAPIResource,
ListableAPIResource, DeletableAPIResource):
pass
class SKU(CreateableAPIResource, UpdateableAPIResource,
ListableAPIResource, DeletableAPIResource):
pass
class Order(CreateableAPIResource, UpdateableAPIResource,
ListableAPIResource):
def pay(self, idempotency_key=None, **params):
headers = populate_headers(idempotency_key)
return self.request(
'post', self.instance_url() + '/pay', params, headers)
def return_order(self, idempotency_key=None, **params):
headers = populate_headers(idempotency_key)
return self.request(
'post', self.instance_url() + '/returns', params, headers)
class OrderReturn(ListableAPIResource):
pass
@classmethod
def class_url(cls):
return '/v1/order_returns'
class CountrySpec(ListableAPIResource):
@classmethod
def class_name(cls):
return 'country_spec'
class ThreeDSecure(CreateableAPIResource):
@classmethod
def class_url(cls):
return '/v1/3d_secure'
@classmethod
def retrieve(cls, id, api_key=None, stripe_account=None, **params):
raise NotImplementedError("Can't retrieve 3D Secure objects.")
+13
View File
@@ -0,0 +1,13 @@
import os
import unittest2
def all():
path = os.path.dirname(os.path.realpath(__file__))
return unittest2.defaultTestLoader.discover(path)
def resources():
path = os.path.dirname(os.path.realpath(__file__))
return unittest2.defaultTestLoader.discover(
os.path.join(path, "resources"))
+240
View File
@@ -0,0 +1,240 @@
import datetime
import os
import random
import re
import string
import sys
import unittest2
if 1/2 != 0: str=str
from mock import patch, Mock
import libraries.stripe
NOW = datetime.datetime.now()
DUMMY_CARD = {
'number': '4242424242424242',
'exp_month': NOW.month,
'exp_year': NOW.year + 4
}
DUMMY_DEBIT_CARD = {
'number': '4000056655665556',
'exp_month': NOW.month,
'exp_year': NOW.year + 4
}
DUMMY_CHARGE = {
'amount': 100,
'currency': 'usd',
'card': DUMMY_CARD
}
DUMMY_DISPUTE = {
'status': 'needs_response',
'currency': 'usd',
'metadata': {}
}
DUMMY_PLAN = {
'amount': 2000,
'interval': 'month',
'name': 'Amazing Gold Plan',
'currency': 'usd',
'id': ('stripe-test-gold-' +
''.join(random.choice(string.ascii_lowercase) for x in range(10)))
}
DUMMY_COUPON = {
'percent_off': 25,
'duration': 'repeating',
'duration_in_months': 5,
'metadata': {}
}
DUMMY_RECIPIENT = {
'name': 'John Doe',
'type': 'individual'
}
DUMMY_TRANSFER = {
'amount': 400,
'currency': 'usd',
'recipient': 'self'
}
DUMMY_INVOICE_ITEM = {
'amount': 456,
'currency': 'usd',
}
SAMPLE_INVOICE = libraries.stripe.util.json.loads("""
{
"amount_due": 1305,
"attempt_count": 0,
"attempted": true,
"charge": "ch_wajkQ5aDTzFs5v",
"closed": true,
"customer": "cus_osllUe2f1BzrRT",
"date": 1338238728,
"discount": null,
"ending_balance": 0,
"id": "in_t9mHb2hpK7mml1",
"livemode": false,
"next_payment_attempt": null,
"object": "invoice",
"paid": true,
"period_end": 1338238728,
"period_start": 1338238716,
"starting_balance": -8695,
"subtotal": 10000,
"total": 10000,
"lines": {
"invoiceitems": [],
"prorations": [],
"subscriptions": [
{
"plan": {
"interval": "month",
"object": "plan",
"identifier": "expensive",
"currency": "usd",
"livemode": false,
"amount": 10000,
"name": "Expensive Plan",
"trial_period_days": null,
"id": "expensive"
},
"period": {
"end": 1340917128,
"start": 1338238728
},
"amount": 10000
}
]
}
}
""")
class StripeTestCase(unittest2.TestCase):
RESTORE_ATTRIBUTES = ('api_version', 'api_key')
def setUp(self):
super(StripeTestCase, self).setUp()
self._stripe_original_attributes = {}
for attr in self.RESTORE_ATTRIBUTES:
self._stripe_original_attributes[attr] = getattr(stripe, attr)
api_base = os.environ.get('STRIPE_API_BASE')
if api_base:
libraries.stripe.api_base = api_base
libraries.stripe.api_key = os.environ.get(
'STRIPE_API_KEY', 'tGN0bIwXnHdwOa85VABjPdSn8nWY7G7I')
def tearDown(self):
super(StripeTestCase, self).tearDown()
for attr in self.RESTORE_ATTRIBUTES:
setattr(stripe, attr, self._stripe_original_attributes[attr])
# Python < 2.7 compatibility
def assertRaisesRegexp(self, exception, regexp, callable, *args, **kwargs):
try:
callable(*args, **kwargs)
except exception as err:
if regexp is None:
return True
if isinstance(regexp, str):
regexp = re.compile(regexp)
if not regexp.search(str(err)):
raise self.failureException('"%s" does not match "%s"' %
(regexp.pattern, str(err)))
else:
raise self.failureException(
'%s was not raised' % (exception.__name__,))
class StripeUnitTestCase(StripeTestCase):
REQUEST_LIBRARIES = ['urlfetch', 'requests', 'pycurl']
if sys.version_info >= (3, 0):
REQUEST_LIBRARIES.append('urllib.request')
else:
REQUEST_LIBRARIES.append('urllib2')
def setUp(self):
super(StripeUnitTestCase, self).setUp()
self.request_patchers = {}
self.request_mocks = {}
for lib in self.REQUEST_LIBRARIES:
patcher = patch("stripe.http_client.%s" % (lib,))
self.request_mocks[lib] = patcher.start()
self.request_patchers[lib] = patcher
def tearDown(self):
super(StripeUnitTestCase, self).tearDown()
for patcher in self.request_patchers.values():
patcher.stop()
class StripeApiTestCase(StripeTestCase):
def setUp(self):
super(StripeApiTestCase, self).setUp()
self.requestor_patcher = patch('stripe.api_requestor.APIRequestor')
requestor_class_mock = self.requestor_patcher.start()
self.requestor_mock = requestor_class_mock.return_value
def tearDown(self):
super(StripeApiTestCase, self).tearDown()
self.requestor_patcher.stop()
def mock_response(self, res):
self.requestor_mock.request = Mock(return_value=(res, 'reskey'))
class StripeResourceTest(StripeApiTestCase):
def setUp(self):
super(StripeResourceTest, self).setUp()
self.mock_response({})
class MyResource(stripe.resource.APIResource):
pass
class MySingleton(stripe.resource.SingletonAPIResource):
pass
class MyListable(stripe.resource.ListableAPIResource):
pass
class MyCreatable(stripe.resource.CreateableAPIResource):
pass
class MyUpdateable(stripe.resource.UpdateableAPIResource):
pass
class MyDeletable(stripe.resource.DeletableAPIResource):
pass
class MyComposite(stripe.resource.ListableAPIResource,
libraries.stripe.resource.CreateableAPIResource,
libraries.stripe.resource.UpdateableAPIResource,
libraries.stripe.resource.DeletableAPIResource):
pass
View File
+154
View File
@@ -0,0 +1,154 @@
import libraries.stripe
from libraries.stripe.test.helper import StripeResourceTest
class AccountTest(StripeResourceTest):
def test_retrieve_account_deprecated(self):
libraries.stripe.Account.retrieve()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/account',
{},
None
)
def test_retrieve_account(self):
libraries.stripe.Account.retrieve('acct_foo')
self.requestor_mock.request.assert_called_with(
'get',
'/v1/accounts/acct_foo',
{},
None
)
def test_list_accounts(self):
libraries.stripe.Account.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/accounts',
{}
)
def test_create_account(self):
pii = {
'type': 'individual',
'first_name': 'Joe',
'last_name': 'Smith',
}
libraries.stripe.Account.create(legal_entity=pii)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/accounts',
{
'legal_entity': pii,
},
None,
)
def test_update_account(self):
acct = libraries.stripe.Account.construct_from({
'id': 'acct_update',
'legal_entity': {'first_name': 'Joe'},
}, 'api_key')
acct.legal_entity['first_name'] = 'Bob'
acct.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/accounts/acct_update',
{
'legal_entity': {
'first_name': 'Bob',
},
},
None,
)
def test_account_delete_bank_account(self):
source = libraries.stripe.BankAccount.construct_from({
'account': 'acc_delete_ba',
'id': 'ba_delete_ba',
}, 'api_key')
source.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/accounts/acc_delete_ba/external_accounts/ba_delete_ba',
{},
None
)
def test_reject_account(self):
self.mock_response({
'id': 'acct_reject',
'verification': {
'disabled_reason': 'rejected.fraud'
},
})
obj = libraries.stripe.Account.construct_from({
'id': 'acct_reject'
}, 'mykey')
self.assertTrue(obj is obj.reject(reason='fraud'))
self.assertEqual('rejected.fraud', obj.verification['disabled_reason'])
self.assertEqual('acct_reject', obj.id)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/accounts/acct_reject/reject',
{'reason': 'fraud'},
None
)
def test_reject_account_without_reason(self):
self.mock_response({
'id': 'acct_reject',
'verification': {
'disabled_reason': 'rejected.fraud'
},
})
obj = libraries.stripe.Account.construct_from({
'id': 'acct_reject'
}, 'mykey')
self.assertTrue(obj is obj.reject())
self.assertEqual('rejected.fraud', obj.verification['disabled_reason'])
self.assertEqual('acct_reject', obj.id)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/accounts/acct_reject/reject',
{},
None
)
def test_verify_additional_owner(self):
acct = libraries.stripe.Account.construct_from({
'id': 'acct_update',
'additional_owners': [{
'first_name': 'Alice',
'verification': {},
}]
}, 'api_key')
owner = acct.additional_owners[0]
owner.verification.document = 'file_foo'
acct.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/accounts/acct_update',
{
'additional_owners': {
'0': {
'verification': {
'document': 'file_foo',
},
},
},
},
None,
)
+86
View File
@@ -0,0 +1,86 @@
import libraries.stripe
from libraries.stripe.test.helper import (
StripeApiTestCase, MyResource, MySingleton
)
class APIResourceTests(StripeApiTestCase):
def test_retrieve_and_refresh(self):
self.mock_response({
'id': 'foo2',
'bobble': 'scrobble',
})
res = MyResource.retrieve('foo*', myparam=5)
url = '/v1/myresources/foo%2A'
self.requestor_mock.request.assert_called_with(
'get', url, {'myparam': 5}, None
)
self.assertEqual('scrobble', res.bobble)
self.assertEqual('foo2', res.id)
self.assertEqual('reskey', res.api_key)
self.mock_response({
'frobble': 5,
})
res = res.refresh()
url = '/v1/myresources/foo2'
self.requestor_mock.request.assert_called_with(
'get', url, {'myparam': 5}, None
)
self.assertEqual(5, res.frobble)
self.assertRaises(KeyError, res.__getitem__, 'bobble')
def test_convert_to_stripe_object(self):
sample = {
'foo': 'bar',
'adict': {
'object': 'charge',
'id': 42,
'amount': 7,
},
'alist': [
{
'object': 'customer',
'name': 'chilango'
}
]
}
converted = libraries.stripe.resource.convert_to_stripe_object(
sample, 'akey', None)
# Types
self.assertTrue(isinstance(converted, libraries.stripe.resource.StripeObject))
self.assertTrue(isinstance(converted.adict, libraries.stripe.Charge))
self.assertEqual(1, len(converted.alist))
self.assertTrue(isinstance(converted.alist[0], libraries.stripe.Customer))
# Values
self.assertEqual('bar', converted.foo)
self.assertEqual(42, converted.adict.id)
self.assertEqual('chilango', converted.alist[0].name)
# Stripping
# TODO: We should probably be stripping out this property
# self.assertRaises(AttributeError, getattr, converted.adict, 'object')
class SingletonAPIResourceTests(StripeApiTestCase):
def test_retrieve(self):
self.mock_response({
'single': 'ton'
})
res = MySingleton.retrieve()
self.requestor_mock.request.assert_called_with(
'get', '/v1/mysingleton', {}, None)
self.assertEqual('ton', res.single)
+113
View File
@@ -0,0 +1,113 @@
import libraries.stripe
from libraries.stripe.test.helper import StripeResourceTest
class ApplicationFeeTest(StripeResourceTest):
def test_list_application_fees(self):
libraries.stripe.ApplicationFee.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/application_fees',
{}
)
class ApplicationFeeRefundTest(StripeResourceTest):
def test_fetch_refund(self):
fee = libraries.stripe.ApplicationFee.construct_from({
'id': 'fee_get_refund',
'refunds': {
'object': 'list',
'url': '/v1/application_fees/fee_get_refund/refunds',
}
}, 'api_key')
fee.refunds.retrieve("ref_get")
self.requestor_mock.request.assert_called_with(
'get',
'/v1/application_fees/fee_get_refund/refunds/ref_get',
{},
None
)
def test_list_refunds(self):
fee = libraries.stripe.ApplicationFee.construct_from({
'id': 'fee_get_refund',
'refunds': {
'object': 'list',
'url': '/v1/application_fees/fee_get_refund/refunds',
}
}, 'api_key')
fee.refunds.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/application_fees/fee_get_refund/refunds',
{},
None
)
def test_update_refund(self):
def side_effect(*args):
raise libraries.stripe.InvalidRequestError('invalid', 'foo')
self.requestor_mock.request.side_effect = side_effect
refund = libraries.stripe.resource.ApplicationFeeRefund.construct_from({
'id': "ref_update",
'fee': "fee_update",
'metadata': {},
}, 'api_key')
refund.metadata["key"] = "foo"
try:
refund.save()
except libraries.stripe.InvalidRequestError:
pass
self.requestor_mock.request.assert_called_with(
'post',
'/v1/application_fees/fee_update/refunds/ref_update',
{
'metadata': {
'key': 'foo',
}
},
None
)
self.requestor_mock.request.side_effect = None
refund.metadata["key"] = "bar"
refund.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/application_fees/fee_update/refunds/ref_update',
{
'metadata': {
'key': 'bar',
}
},
None
)
def test_modify_refund(self):
libraries.stripe.resource.ApplicationFeeRefund.modify("fee_update", "ref_update",
metadata={'key': 'foo'},
api_key='api_key')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/application_fees/fee_update/refunds/ref_update',
{
'metadata': {
'key': 'foo',
}
},
None
)
+26
View File
@@ -0,0 +1,26 @@
import libraries.stripe
from libraries.stripe.test.helper import StripeResourceTest
class BalanceTest(StripeResourceTest):
def test_retrieve_balance(self):
libraries.stripe.Balance.retrieve()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/balance',
{},
None
)
class BalanceTransactionTest(StripeResourceTest):
def test_list_balance_transactions(self):
libraries.stripe.BalanceTransaction.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/balance/history',
{}
)
+111
View File
@@ -0,0 +1,111 @@
import libraries.stripe
from libraries.stripe.test.helper import StripeResourceTest
class BitcoinReceiverTest(StripeResourceTest):
def test_retrieve_resource(self):
libraries.stripe.BitcoinReceiver.retrieve("btcrcv_test_receiver")
self.requestor_mock.request.assert_called_with(
'get',
'/v1/bitcoin/receivers/btcrcv_test_receiver',
{},
None
)
def test_list_receivers(self):
libraries.stripe.BitcoinReceiver.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/bitcoin/receivers',
{},
)
def test_create_receiver(self):
libraries.stripe.BitcoinReceiver.create(amount=100, description="some details",
currency="usd",
email="do+fill_now@stripe.com")
self.requestor_mock.request.assert_called_with(
'post',
'/v1/bitcoin/receivers',
{
'amount': 100,
'description': 'some details',
'currency': 'usd',
'email': 'do+fill_now@stripe.com'
},
None
)
def test_update_receiver_without_customer(self):
params = {'id': 'receiver', 'amount': 100,
'description': "some details", 'currency': "usd"}
r = libraries.stripe.BitcoinReceiver.construct_from(params, 'api_key')
r.description = "some other details"
r.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/bitcoin/receivers/receiver',
{
'description': 'some other details',
},
None
)
def test_update_receiver_with_customer(self):
params = {'id': 'receiver', 'amount': 100,
'description': "some details", 'currency': "usd",
'customer': "cust"}
r = libraries.stripe.BitcoinReceiver.construct_from(params, 'api_key')
r.description = "some other details"
r.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/customers/cust/sources/receiver',
{
'description': 'some other details',
},
None
)
def test_delete_receiver_without_customer(self):
params = {'id': 'receiver', 'amount': 100,
'description': "some details", 'currency': "usd"}
r = libraries.stripe.BitcoinReceiver.construct_from(params, 'api_key')
r.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/bitcoin/receivers/receiver',
{},
None
)
def test_delete_receiver_with_customer(self):
params = {'id': 'receiver', 'amount': 100,
'description': "some details", 'currency': "usd",
'customer': "cust"}
r = libraries.stripe.BitcoinReceiver.construct_from(params, 'api_key')
r.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/customers/cust/sources/receiver',
{},
None
)
def test_list_transactions(self):
receiver = libraries.stripe.BitcoinReceiver.construct_from({
'id': 'btcrcv_foo',
'transactions': {
'object': 'list',
'url': '/v1/bitcoin/receivers/btcrcv_foo/transactions',
}
}, 'api_key')
receiver.transactions.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/bitcoin/receivers/btcrcv_foo/transactions',
{},
None
)
+113
View File
@@ -0,0 +1,113 @@
import libraries.stripe
from libraries.stripe.test.helper import (
StripeResourceTest, NOW, DUMMY_CHARGE
)
class ChargeTest(StripeResourceTest):
def test_charge_list(self):
libraries.stripe.Charge.list(created={'lt': NOW})
self.requestor_mock.request.assert_called_with(
'get',
'/v1/charges',
{
'created': {'lt': NOW},
}
)
def test_charge_create(self):
libraries.stripe.Charge.create(idempotency_key='foo', **DUMMY_CHARGE)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/charges',
DUMMY_CHARGE,
{'Idempotency-Key': 'foo'},
)
def test_charge_retrieve(self):
libraries.stripe.Charge.retrieve('ch_test_id')
self.requestor_mock.request.assert_called_with(
'get',
'/v1/charges/ch_test_id',
{},
None
)
def test_charge_modify(self):
libraries.stripe.Charge.modify('ch_test_id', refund=True)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/charges/ch_test_id',
{
'refund': True,
},
None
)
def test_charge_update_dispute(self):
charge = libraries.stripe.Charge(id='ch_update_id')
charge.update_dispute(idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/charges/ch_update_id/dispute',
{},
{'Idempotency-Key': 'foo'},
)
def test_charge_close_dispute(self):
charge = libraries.stripe.Charge(id='ch_update_id')
charge.close_dispute(idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/charges/ch_update_id/dispute/close',
{},
{'Idempotency-Key': 'foo'},
)
def test_mark_as_fraudulent(self):
charge = libraries.stripe.Charge(id='ch_update_id')
charge.mark_as_fraudulent(idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/charges/ch_update_id',
{
'fraud_details': {'user_report': 'fraudulent'}
},
{'Idempotency-Key': 'foo'},
)
def test_mark_as_safe(self):
charge = libraries.stripe.Charge(id='ch_update_id')
charge.mark_as_safe(idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/charges/ch_update_id',
{
'fraud_details': {'user_report': 'safe'}
},
{'Idempotency-Key': 'foo'},
)
def test_create_with_source_param(self):
libraries.stripe.Charge.create(amount=100, currency='usd',
source='btcrcv_test_receiver')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/charges',
{
'amount': 100,
'currency': 'usd',
'source': 'btcrcv_test_receiver'
},
None,
)
+23
View File
@@ -0,0 +1,23 @@
import libraries.stripe
from libraries.stripe.test.helper import StripeResourceTest
class CountrySpecTest(StripeResourceTest):
def test_country_spec_list(self):
libraries.stripe.CountrySpec.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/country_specs',
{}
)
def test_country_spec_retrieve(self):
libraries.stripe.CountrySpec.retrieve('US')
self.requestor_mock.request.assert_called_with(
'get',
'/v1/country_specs/US',
{},
None
)
+55
View File
@@ -0,0 +1,55 @@
import libraries.stripe
from libraries.stripe.test.helper import (
StripeResourceTest, DUMMY_COUPON
)
class CouponTest(StripeResourceTest):
def test_create_coupon(self):
libraries.stripe.Coupon.create(**DUMMY_COUPON)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/coupons',
DUMMY_COUPON,
None
)
def test_update_coupon(self):
coup = libraries.stripe.Coupon.construct_from({
'id': 'cu_update',
'metadata': {},
}, 'api_key')
coup.metadata["key"] = "value"
coup.save()
self.requestor_mock.request.assert_called_with(
'post',
"/v1/coupons/cu_update",
{
'metadata': {
'key': 'value',
}
},
None
)
def test_delete_coupon(self):
c = libraries.stripe.Coupon(id='cu_delete')
c.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/coupons/cu_delete',
{},
None
)
def test_detach_coupon(self):
customer = libraries.stripe.Customer(id="cus_delete_discount")
customer.delete_discount()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/customers/cus_delete_discount/discount',
)
+35
View File
@@ -0,0 +1,35 @@
import libraries.stripe
from libraries.stripe.test.helper import (
StripeApiTestCase, MyCreatable
)
class CreateableAPIResourceTests(StripeApiTestCase):
def test_create(self):
self.mock_response({
'object': 'charge',
'foo': 'bar',
})
res = MyCreatable.create()
self.requestor_mock.request.assert_called_with(
'post', '/v1/mycreatables', {}, None)
self.assertTrue(isinstance(res, libraries.stripe.Charge))
self.assertEqual('bar', res.foo)
def test_idempotent_create(self):
self.mock_response({
'object': 'charge',
'foo': 'bar',
})
res = MyCreatable.create(idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post', '/v1/mycreatables', {}, {'Idempotency-Key': 'foo'})
self.assertTrue(isinstance(res, libraries.stripe.Charge))
self.assertEqual('bar', res.foo)
+370
View File
@@ -0,0 +1,370 @@
import datetime
import time
import warnings
import libraries.stripe
from libraries.stripe.test.helper import (
StripeResourceTest, DUMMY_CARD, DUMMY_PLAN
)
class CustomerTest(StripeResourceTest):
def test_list_customers(self):
libraries.stripe.Customer.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/customers',
{},
)
def test_create_customer(self):
libraries.stripe.Customer.create(description="foo bar", card=DUMMY_CARD,
coupon='cu_discount', idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/customers',
{
'coupon': 'cu_discount',
'description': 'foo bar',
'card': DUMMY_CARD
},
{'Idempotency-Key': 'foo'}
)
def test_unset_description(self):
customer = libraries.stripe.Customer(id="cus_unset_desc")
customer.description = "Hey"
customer.save(idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/customers/cus_unset_desc',
{
'description': 'Hey',
},
{'Idempotency-Key': 'foo'}
)
def test_del_coupon(self):
customer = libraries.stripe.Customer(id="cus_unset_desc")
customer.description = "bar"
customer.coupon = "foo"
del customer.coupon
customer.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/customers/cus_unset_desc',
{
'description': 'bar'
},
None
)
def test_cannot_set_empty_string(self):
customer = libraries.stripe.Customer()
self.assertRaises(ValueError, setattr, customer, "description", "")
def test_customer_add_card(self):
customer = libraries.stripe.Customer.construct_from({
'id': 'cus_add_card',
'sources': {
'object': 'list',
'url': '/v1/customers/cus_add_card/sources',
},
}, 'api_key')
customer.sources.create(card=DUMMY_CARD, idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/customers/cus_add_card/sources',
{
'card': DUMMY_CARD,
},
{'Idempotency-Key': 'foo'}
)
def test_customer_add_source(self):
customer = libraries.stripe.Customer.construct_from({
'id': 'cus_add_source',
'sources': {
'object': 'list',
'url': '/v1/customers/cus_add_source/sources',
},
}, 'api_key')
customer.sources.create(source=DUMMY_CARD, idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/customers/cus_add_source/sources',
{
'source': DUMMY_CARD,
},
{'Idempotency-Key': 'foo'}
)
def test_customer_update_card(self):
card = libraries.stripe.Card.construct_from({
'customer': 'cus_update_card',
'id': 'ca_update_card',
}, 'api_key')
card.name = 'The Best'
card.save(idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/customers/cus_update_card/sources/ca_update_card',
{
'name': 'The Best',
},
{'Idempotency-Key': 'foo'}
)
def test_customer_update_source(self):
source = libraries.stripe.BitcoinReceiver.construct_from({
'customer': 'cus_update_source',
'id': 'btcrcv_update_source',
}, 'api_key')
source.name = 'The Best'
source.save(idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/customers/cus_update_source/sources/btcrcv_update_source',
{
'name': 'The Best',
},
{'Idempotency-Key': 'foo'}
)
def test_customer_update_alipay_account(self):
aa = libraries.stripe.AlipayAccount.construct_from({
'customer': 'cus_update_alipay',
'id': 'ali_update',
}, 'api_key')
aa.metadata = {'name': 'The Best'}
aa.save(idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/customers/cus_update_alipay/sources/ali_update',
{
'metadata': {'name': 'The Best'},
},
{'Idempotency-Key': 'foo'}
)
def test_customer_delete_card(self):
card = libraries.stripe.Card.construct_from({
'customer': 'cus_delete_card',
'id': 'ca_delete_card',
}, 'api_key')
card.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/customers/cus_delete_card/sources/ca_delete_card',
{},
None
)
def test_customer_delete_source(self):
source = libraries.stripe.BitcoinReceiver.construct_from({
'customer': 'cus_delete_source',
'id': 'btcrcv_delete_source',
}, 'api_key')
source.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/customers/cus_delete_source/sources/btcrcv_delete_source',
{},
None
)
def test_customer_delete_alipay_account(self):
aa = libraries.stripe.AlipayAccount.construct_from({
'customer': 'cus_delete_alipay',
'id': 'ali_delete',
}, 'api_key')
aa.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/customers/cus_delete_alipay/sources/ali_delete',
{},
None
)
def test_customer_delete_bank_account(self):
source = libraries.stripe.BankAccount.construct_from({
'customer': 'cus_delete_source',
'id': 'ba_delete_source',
}, 'api_key')
source.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/customers/cus_delete_source/sources/ba_delete_source',
{},
None
)
def test_customer_verify_bank_account(self):
source = libraries.stripe.BankAccount.construct_from({
'customer': 'cus_verify_source',
'id': 'ba_verify_source',
}, 'api_key')
source.verify()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/customers/cus_verify_source/sources/ba_verify_source/verify',
{},
None
)
class CustomerPlanTest(StripeResourceTest):
def test_create_customer(self):
libraries.stripe.Customer.create(plan=DUMMY_PLAN['id'], card=DUMMY_CARD)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/customers',
{
'card': DUMMY_CARD,
'plan': DUMMY_PLAN['id'],
},
None
)
def test_legacy_update_subscription(self):
with warnings.catch_warnings(record=True) as w:
customer = libraries.stripe.Customer(id="cus_legacy_sub_update")
customer.update_subscription(idempotency_key='foo',
plan=DUMMY_PLAN['id'])
self.requestor_mock.request.assert_called_with(
'post',
'/v1/customers/cus_legacy_sub_update/subscription',
{
'plan': DUMMY_PLAN['id'],
},
{'Idempotency-Key': 'foo'}
)
self.assertEqual(len(w), 1)
self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
def test_legacy_delete_subscription(self):
with warnings.catch_warnings(record=True) as w:
customer = libraries.stripe.Customer(id="cus_legacy_sub_delete")
customer.cancel_subscription()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/customers/cus_legacy_sub_delete/subscription',
{},
None
)
self.assertEqual(len(w), 1)
self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
def test_list_customer_subscriptions(self):
customer = libraries.stripe.Customer.construct_from({
'id': 'cus_foo',
'subscriptions': {
'object': 'list',
'url': 'v1/customers/cus_foo/subscriptions',
}
}, 'api_key')
customer.subscriptions.all()
self.requestor_mock.request.assert_called_with(
'get',
'v1/customers/cus_foo/subscriptions',
{},
None
)
def test_create_customer_subscription(self):
customer = libraries.stripe.Customer.construct_from({
'id': 'cus_sub_create',
'subscriptions': {
'object': 'list',
'url': '/v1/customers/cus_sub_create/subscriptions',
}
}, 'api_key')
customer.subscriptions.create(plan=DUMMY_PLAN['id'], coupon='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/customers/cus_sub_create/subscriptions',
{
'plan': DUMMY_PLAN['id'],
'coupon': 'foo',
},
None
)
def test_retrieve_customer_subscription(self):
customer = libraries.stripe.Customer.construct_from({
'id': 'cus_foo',
'subscriptions': {
'object': 'list',
'url': '/v1/customers/cus_foo/subscriptions',
}
}, 'api_key')
customer.subscriptions.retrieve('sub_cus')
self.requestor_mock.request.assert_called_with(
'get',
'/v1/customers/cus_foo/subscriptions/sub_cus',
{},
None
)
def test_update_customer_subscription(self):
subscription = libraries.stripe.Subscription.construct_from({
'id': "sub_update",
'customer': "cus_foo",
}, 'api_key')
trial_end_dttm = datetime.datetime.now() + datetime.timedelta(days=15)
trial_end_int = int(time.mktime(trial_end_dttm.timetuple()))
subscription.trial_end = trial_end_int
subscription.plan = DUMMY_PLAN['id']
subscription.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/subscriptions/sub_update',
{
'plan': DUMMY_PLAN['id'],
'trial_end': trial_end_int,
},
None
)
def test_delete_customer_subscription(self):
subscription = libraries.stripe.Subscription.construct_from({
'id': "sub_delete",
'customer': "cus_foo",
}, 'api_key')
subscription.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/subscriptions/sub_delete',
{},
None
)
+21
View File
@@ -0,0 +1,21 @@
from libraries.stripe.test.helper import (
StripeApiTestCase, MyDeletable
)
class DeletableAPIResourceTests(StripeApiTestCase):
def test_delete(self):
self.mock_response({
'id': 'mid',
'deleted': True,
})
obj = MyDeletable.construct_from({
'id': 'mid'
}, 'mykey')
self.assertTrue(obj is obj.delete())
self.assertEqual(True, obj.deleted)
self.assertEqual('mid', obj.id)
+70
View File
@@ -0,0 +1,70 @@
import libraries.stripe
from libraries.stripe.test.helper import (
StripeResourceTest, DUMMY_DISPUTE, NOW
)
class DisputeTest(StripeResourceTest):
def test_list_all_disputes(self):
libraries.stripe.Dispute.list(created={'lt': NOW})
self.requestor_mock.request.assert_called_with(
'get',
'/v1/disputes',
{
'created': {'lt': NOW},
}
)
def test_create_dispute(self):
libraries.stripe.Dispute.create(idempotency_key='foo', **DUMMY_DISPUTE)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/disputes',
DUMMY_DISPUTE,
{'Idempotency-Key': 'foo'},
)
def test_retrieve_dispute(self):
libraries.stripe.Dispute.retrieve('dp_test_id')
self.requestor_mock.request.assert_called_with(
'get',
'/v1/disputes/dp_test_id',
{},
None
)
def test_update_dispute(self):
dispute = libraries.stripe.Dispute.construct_from({
'id': 'dp_update_id',
'evidence': {
'product_description': 'description',
},
}, 'api_key')
dispute.evidence['customer_name'] = 'customer'
dispute.evidence['uncategorized_text'] = 'text'
dispute.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/disputes/dp_update_id',
{'evidence': {
'customer_name': 'customer',
'uncategorized_text': 'text',
}},
None
)
def test_close_dispute(self):
dispute = libraries.stripe.Dispute(id='dp_close_id')
dispute.close(idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/disputes/dp_close_id/close',
{},
{'Idempotency-Key': 'foo'},
)
+40
View File
@@ -0,0 +1,40 @@
import tempfile
import libraries.stripe
from libraries.stripe.test.helper import StripeResourceTest
class FileUploadTest(StripeResourceTest):
def test_create_file_upload(self):
test_file = tempfile.TemporaryFile()
libraries.stripe.FileUpload.create(
purpose='dispute_evidence',
file=test_file
)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/files',
params={
'purpose': 'dispute_evidence',
'file': test_file
},
headers={'Content-Type': 'multipart/form-data'}
)
def test_fetch_file_upload(self):
libraries.stripe.FileUpload.retrieve("fil_foo")
self.requestor_mock.request.assert_called_with(
'get',
'/v1/files/fil_foo',
{},
None
)
def test_list_file_uploads(self):
libraries.stripe.FileUpload.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/files',
{}
)
+76
View File
@@ -0,0 +1,76 @@
import libraries.stripe
from libraries.stripe.test.helper import (
StripeResourceTest, DUMMY_INVOICE_ITEM
)
class InvoiceTest(StripeResourceTest):
def test_add_invoice_item(self):
customer = libraries.stripe.Customer(id="cus_invoice_items")
customer.add_invoice_item(**DUMMY_INVOICE_ITEM)
expected = DUMMY_INVOICE_ITEM.copy()
expected['customer'] = 'cus_invoice_items'
self.requestor_mock.request.assert_called_with(
'post',
'/v1/invoiceitems',
expected,
None,
)
def test_retrieve_invoice_items(self):
customer = libraries.stripe.Customer(id="cus_get_invoice_items")
customer.invoice_items()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/invoiceitems',
{'customer': 'cus_get_invoice_items'},
)
def test_invoice_create(self):
customer = libraries.stripe.Customer(id="cus_invoice")
libraries.stripe.Invoice.create(customer=customer.id)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/invoices',
{
'customer': 'cus_invoice',
},
None
)
def test_retrieve_customer_invoices(self):
customer = libraries.stripe.Customer(id="cus_invoice_items")
customer.invoices()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/invoices',
{
'customer': 'cus_invoice_items',
},
)
def test_pay_invoice(self):
invoice = libraries.stripe.Invoice(id="ii_pay")
invoice.pay()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/invoices/ii_pay/pay',
{},
None
)
def test_upcoming_invoice(self):
libraries.stripe.Invoice.upcoming()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/invoices/upcoming',
{},
)
+123
View File
@@ -0,0 +1,123 @@
import libraries.stripe
from libraries.stripe.test.helper import StripeApiTestCase
class ListObjectTests(StripeApiTestCase):
def setUp(self):
super(ListObjectTests, self).setUp()
self.lo = libraries.stripe.resource.ListObject.construct_from({
'id': 'me',
'url': '/my/path',
'data': ['foo'],
}, 'mykey')
self.mock_response([{
'object': 'charge',
'foo': 'bar',
}])
def assertResponse(self, res):
self.assertTrue(isinstance(res[0], libraries.stripe.Charge))
self.assertEqual('bar', res[0].foo)
def test_for_loop(self):
seen = []
for item in self.lo:
seen.append(item)
self.assertEqual(['foo'], seen)
def test_list(self):
res = self.lo.list(myparam='you')
self.requestor_mock.request.assert_called_with(
'get', '/my/path', {'myparam': 'you'}, None)
self.assertResponse(res)
def test_create(self):
res = self.lo.create(myparam='eter')
self.requestor_mock.request.assert_called_with(
'post', '/my/path', {'myparam': 'eter'}, None)
self.assertResponse(res)
def test_retrieve(self):
res = self.lo.retrieve('myid', myparam='cow')
self.requestor_mock.request.assert_called_with(
'get', '/my/path/myid', {'myparam': 'cow'}, None)
self.assertResponse(res)
class AutoPagingTests(StripeApiTestCase):
@staticmethod
def pageable_model_response(ids, has_more):
return {
'object': 'list',
'url': '/v1/pageablemodels',
'data': [{'id': id, 'object': 'pageablemodel'} for id in ids],
'has_more': has_more,
}
def test_iter_one_page(self):
lo = libraries.stripe.resource.ListObject.construct_from(
self.pageable_model_response(['pm_123', 'pm_124'], False),
'mykey'
)
self.requestor_mock.request.assert_not_called()
seen = [item['id'] for item in lo.auto_paging_iter()]
self.assertEqual(['pm_123', 'pm_124'], seen)
def test_iter_two_pages(self):
lo = libraries.stripe.resource.ListObject.construct_from(
self.pageable_model_response(['pm_123', 'pm_124'], True),
'mykey'
)
lo._retrieve_params = {'foo': 'bar'}
self.mock_response(
self.pageable_model_response(['pm_125', 'pm_126'], False)
)
seen = [item['id'] for item in lo.auto_paging_iter()]
self.requestor_mock.request.assert_called_with(
'get',
'/v1/pageablemodels',
{
'starting_after': 'pm_124',
'foo': 'bar'
},
None
)
self.assertEqual(['pm_123', 'pm_124', 'pm_125', 'pm_126'], seen)
def test_class_method_two_pages(self):
self.mock_response({
'object': 'list',
'data': [{'id': 'ch_001'}],
'url': '/v1/charges',
'has_more': False,
})
seen = [item['id'] for item in libraries.stripe.Charge.auto_paging_iter(
limit=25,
foo='bar'
)]
self.requestor_mock.request.assert_called_with(
'get', '/v1/charges', {'limit': 25, 'foo': 'bar'}
)
self.assertEqual(['ch_001'], seen)
+35
View File
@@ -0,0 +1,35 @@
import libraries.stripe
from libraries.stripe.test.helper import (
StripeApiTestCase, MyListable
)
class ListableAPIResourceTests(StripeApiTestCase):
def test_all(self):
self.mock_response({
'object': 'list',
'data': [
{
'object': 'charge',
'name': 'jose',
},
{
'object': 'charge',
'name': 'curly',
}
],
'url': '/v1/charges',
'has_more': False,
})
res = MyListable.list()
self.requestor_mock.request.assert_called_with(
'get', '/v1/mylistables', {})
self.assertEqual(2, len(res.data))
self.assertTrue(all(isinstance(obj, libraries.stripe.Charge)
for obj in res.data))
self.assertEqual('jose', res.data[0].name)
self.assertEqual('curly', res.data[1].name)
+61
View File
@@ -0,0 +1,61 @@
import libraries.stripe
from libraries.stripe.test.helper import StripeResourceTest
class MetadataTest(StripeResourceTest):
def test_noop_metadata(self):
charge = libraries.stripe.Charge(id='ch_foo')
charge.description = 'test'
charge.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/charges/ch_foo',
{
'description': 'test',
},
None
)
def test_unset_metadata(self):
charge = libraries.stripe.Charge(id='ch_foo')
charge.metadata = {}
charge.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/charges/ch_foo',
{
'metadata': {},
},
None
)
def test_whole_update(self):
charge = libraries.stripe.Charge(id='ch_foo')
charge.metadata = {'whole': 'update'}
charge.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/charges/ch_foo',
{
'metadata': {'whole': 'update'},
},
None
)
def test_individual_delete(self):
charge = libraries.stripe.Charge(id='ch_foo')
charge.metadata = {'whole': None}
charge.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/charges/ch_foo',
{
'metadata': {'whole': None},
},
None
)
+42
View File
@@ -0,0 +1,42 @@
import libraries.stripe
from libraries.stripe.test.helper import (
StripeResourceTest, DUMMY_PLAN
)
class PlanTest(StripeResourceTest):
def test_create_plan(self):
libraries.stripe.Plan.create(**DUMMY_PLAN)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/plans',
DUMMY_PLAN,
None
)
def test_delete_plan(self):
p = libraries.stripe.Plan(id="pl_delete")
p.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/plans/pl_delete',
{},
None
)
def test_update_plan(self):
p = libraries.stripe.Plan(id="pl_update")
p.name = "Plan Name"
p.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/plans/pl_update',
{
'name': 'Plan Name',
},
None
)
+75
View File
@@ -0,0 +1,75 @@
import libraries.stripe
from libraries.stripe.test.helper import (
StripeResourceTest, DUMMY_CARD
)
class RecipientTest(StripeResourceTest):
def test_list_recipients(self):
libraries.stripe.Recipient.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/recipients',
{}
)
def test_recipient_transfers(self):
recipient = libraries.stripe.Recipient(id='rp_transfer')
recipient.transfers()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/transfers',
{'recipient': 'rp_transfer'},
)
def test_recipient_add_card(self):
recipient = libraries.stripe.Recipient.construct_from({
'id': 'rp_add_card',
'sources': {
'object': 'list',
'url': '/v1/recipients/rp_add_card/sources',
},
}, 'api_key')
recipient.sources.create(card=DUMMY_CARD)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/recipients/rp_add_card/sources',
{
'card': DUMMY_CARD,
},
None
)
def test_recipient_update_card(self):
card = libraries.stripe.Card.construct_from({
'recipient': 'rp_update_card',
'id': 'ca_update_card',
}, 'api_key')
card.name = 'The Best'
card.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/recipients/rp_update_card/cards/ca_update_card',
{
'name': 'The Best',
},
None
)
def test_recipient_delete_card(self):
card = libraries.stripe.Card.construct_from({
'recipient': 'rp_delete_card',
'id': 'ca_delete_card',
}, 'api_key')
card.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/recipients/rp_delete_card/cards/ca_delete_card',
{},
None
)
+161
View File
@@ -0,0 +1,161 @@
import libraries.stripe
from libraries.stripe.test.helper import StripeResourceTest
class RefundTest(StripeResourceTest):
def test_create_refund(self):
libraries.stripe.Refund.create(charge='ch_foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/refunds',
{'charge': 'ch_foo'},
None
)
def test_fetch_refund(self):
libraries.stripe.Refund.retrieve('re_foo')
self.requestor_mock.request.assert_called_with(
'get',
'/v1/refunds/re_foo',
{},
None
)
def test_list_refunds(self):
libraries.stripe.Refund.list(limit=3, charge='ch_foo')
self.requestor_mock.request.assert_called_with(
'get',
'/v1/refunds',
{'limit': 3, 'charge': 'ch_foo'}
)
def test_update_refund(self):
refund = libraries.stripe.resource.Refund.construct_from({
'id': "ref_update",
'charge': "ch_update",
'metadata': {},
}, 'api_key')
refund.metadata["key"] = "value"
refund.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/refunds/ref_update',
{
'metadata': {
'key': 'value',
}
},
None
)
class ChargeRefundTest(StripeResourceTest):
def test_create_refund(self):
charge = libraries.stripe.Charge.construct_from({
'id': 'ch_foo',
'refunds': {
'object': 'list',
'url': '/v1/charges/ch_foo/refunds',
}
}, 'api_key')
charge.refunds.create()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/charges/ch_foo/refunds',
{},
None
)
def test_non_recursive_save(self):
charge = libraries.stripe.Charge.construct_from({
'id': 'ch_nested_update',
'customer': {
'object': 'customer',
'description': 'foo',
},
'refunds': {
'object': 'list',
'url': '/v1/charges/ch_foo/refunds',
'data': [{
'id': 'ref_123',
}],
},
}, 'api_key')
charge.customer.description = 'bar'
charge.refunds.has_more = True
charge.refunds.data[0].description = 'bar'
charge.save()
# Note: ideally, we'd want the library to NOT issue requests in this
# case (i.e. the assert should actually be `assert_not_called()`).
self.requestor_mock.request.assert_called_with(
'post',
'/v1/charges/ch_nested_update',
{'refunds': {'has_more': True}},
None
)
def test_fetch_refund(self):
charge = libraries.stripe.Charge.construct_from({
'id': 'ch_get_refund',
'refunds': {
'object': 'list',
'url': '/v1/charges/ch_get_refund/refunds',
}
}, 'api_key')
charge.refunds.retrieve("ref_get")
self.requestor_mock.request.assert_called_with(
'get',
'/v1/charges/ch_get_refund/refunds/ref_get',
{},
None
)
def test_list_refunds(self):
charge = libraries.stripe.Charge.construct_from({
'id': 'ch_get_refund',
'refunds': {
'object': 'list',
'url': '/v1/charges/ch_get_refund/refunds',
}
}, 'api_key')
charge.refunds.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/charges/ch_get_refund/refunds',
{},
None
)
def test_update_refund(self):
refund = libraries.stripe.resource.Refund.construct_from({
'id': "ref_update",
'charge': "ch_update",
'metadata': {},
}, 'api_key')
refund.metadata["key"] = "value"
refund.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/refunds/ref_update',
{
'metadata': {
'key': 'value',
}
},
None
)
+90
View File
@@ -0,0 +1,90 @@
import libraries.stripe
from libraries.stripe.test.helper import StripeResourceTest
class ProductTest(StripeResourceTest):
def test_list_products(self):
libraries.stripe.Product.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/products',
{}
)
def test_delete_products(self):
p = libraries.stripe.Product(id='product_to_delete')
p.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/products/product_to_delete',
{},
None
)
class SKUTest(StripeResourceTest):
def test_list_skus(self):
libraries.stripe.SKU.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/skus',
{}
)
def test_delete_skus(self):
sku = libraries.stripe.SKU(id='sku_delete')
sku.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/skus/sku_delete',
{},
None
)
class OrderTest(StripeResourceTest):
def test_list_orders(self):
libraries.stripe.Order.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/orders',
{}
)
def test_pay_order(self):
order = libraries.stripe.Order(id="or_pay")
order.pay()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/orders/or_pay/pay',
{},
None
)
def test_return_order(self):
order = libraries.stripe.Order(id="or_return")
order.return_order()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/orders/or_return/returns',
{},
None
)
class OrderReturnTest(StripeResourceTest):
def test_list_order_returns(self):
libraries.stripe.OrderReturn.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/order_returns',
{}
)
+61
View File
@@ -0,0 +1,61 @@
import libraries.stripe
from libraries.stripe.test.helper import StripeResourceTest
class ReversalTest(StripeResourceTest):
def test_fetch_reversal(self):
transfer = libraries.stripe.Charge.construct_from({
'id': 'tr_get',
'reversals': {
'object': 'list',
'url': '/v1/transfers/tr_get/reversals',
}
}, 'api_key')
transfer.reversals.retrieve("foo")
self.requestor_mock.request.assert_called_with(
'get',
'/v1/transfers/tr_get/reversals/foo',
{},
None
)
def test_list_reversals(self):
transfer = libraries.stripe.Charge.construct_from({
'id': 'tr_list',
'reversals': {
'object': 'list',
'url': '/v1/transfers/tr_list/reversals',
}
}, 'api_key')
transfer.reversals.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/transfers/tr_list/reversals',
{},
None
)
def test_update_transfer(self):
reversal = libraries.stripe.resource.Reversal.construct_from({
'id': "rev_update",
'transfer': "tr_update",
'metadata': {},
}, 'api_key')
reversal.metadata["key"] = "value"
reversal.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/transfers/tr_update/reversals/rev_update',
{
'metadata': {
'key': 'value',
}
},
None
)
+164
View File
@@ -0,0 +1,164 @@
import pickle
import sys
import libraries.stripe
from libraries.stripe import util
from libraries.stripe.test.helper import StripeUnitTestCase, SAMPLE_INVOICE
class StripeObjectTests(StripeUnitTestCase):
def test_initializes_with_parameters(self):
obj = libraries.stripe.resource.StripeObject(
'foo', 'bar', myparam=5, yourparam='boo')
self.assertEqual('foo', obj.id)
self.assertEqual('bar', obj.api_key)
def test_access(self):
obj = libraries.stripe.resource.StripeObject('myid', 'mykey', myparam=5)
# Empty
self.assertRaises(AttributeError, getattr, obj, 'myattr')
self.assertRaises(KeyError, obj.__getitem__, 'myattr')
self.assertEqual('def', obj.get('myattr', 'def'))
self.assertEqual(None, obj.get('myattr'))
# Setters
obj.myattr = 'myval'
obj['myitem'] = 'itval'
self.assertEqual('sdef', obj.setdefault('mydef', 'sdef'))
# Getters
self.assertEqual('myval', obj.setdefault('myattr', 'sdef'))
self.assertEqual('myval', obj.myattr)
self.assertEqual('myval', obj['myattr'])
self.assertEqual('myval', obj.get('myattr'))
self.assertEqual(['id', 'myattr', 'mydef', 'myitem'],
sorted(obj.keys()))
self.assertEqual(['itval', 'myid', 'myval', 'sdef'],
sorted(obj.values()))
# Illegal operations
self.assertRaises(ValueError, setattr, obj, 'foo', '')
def test_refresh_from(self):
obj = libraries.stripe.resource.StripeObject.construct_from({
'foo': 'bar',
'trans': 'me',
}, 'mykey')
self.assertEqual('mykey', obj.api_key)
self.assertEqual('bar', obj.foo)
self.assertEqual('me', obj['trans'])
self.assertEqual(None, obj.stripe_account)
obj.refresh_from({
'foo': 'baz',
'johnny': 5,
}, 'key2', stripe_account='acct_foo')
self.assertEqual(5, obj.johnny)
self.assertEqual('baz', obj.foo)
self.assertRaises(AttributeError, getattr, obj, 'trans')
self.assertEqual('key2', obj.api_key)
self.assertEqual('acct_foo', obj.stripe_account)
obj.refresh_from({
'trans': 4,
'metadata': {'amount': 42}
}, 'key2', True)
self.assertEqual('baz', obj.foo)
self.assertEqual(4, obj.trans)
def test_passing_nested_refresh(self):
obj = libraries.stripe.resource.StripeObject.construct_from({
'foos': {
'type': 'list',
'data': [
{'id': 'nested'}
],
}
}, 'key', stripe_account='acct_foo')
nested = obj.foos.data[0]
self.assertEqual('key', obj.api_key)
self.assertEqual('nested', nested.id)
self.assertEqual('key', nested.api_key)
self.assertEqual('acct_foo', nested.stripe_account)
def test_refresh_from_nested_object(self):
obj = libraries.stripe.resource.StripeObject.construct_from(
SAMPLE_INVOICE, 'key')
self.assertEqual(1, len(obj.lines.subscriptions))
self.assertTrue(
isinstance(obj.lines.subscriptions[0],
libraries.stripe.resource.StripeObject))
self.assertEqual('month', obj.lines.subscriptions[0].plan.interval)
def test_to_json(self):
obj = libraries.stripe.resource.StripeObject.construct_from(
SAMPLE_INVOICE, 'key')
self.check_invoice_data(util.json.loads(str(obj)))
def check_invoice_data(self, data):
# Check rough structure
self.assertEqual(20, len(list(data.keys())))
self.assertEqual(3, len(list(data['lines'].keys())))
self.assertEqual(0, len(data['lines']['invoiceitems']))
self.assertEqual(1, len(data['lines']['subscriptions']))
# Check various data types
self.assertEqual(1338238728, data['date'])
self.assertEqual(None, data['next_payment_attempt'])
self.assertEqual(False, data['livemode'])
self.assertEqual('month',
data['lines']['subscriptions'][0]['plan']['interval'])
def test_repr(self):
obj = libraries.stripe.resource.StripeObject(
'foo', 'bar', myparam=5)
obj['object'] = '\u4e00boo\u1f00'
res = repr(obj)
if sys.version_info[0] < 3:
res = str(repr(obj), 'utf-8')
self.assertTrue('<StripeObject \u4e00boo\u1f00' in res)
self.assertTrue('id=foo' in res)
def test_pickling(self):
obj = libraries.stripe.resource.StripeObject(
'foo', 'bar', myparam=5)
obj['object'] = 'boo'
obj.refresh_from({'fala': 'lalala'}, api_key='bar', partial=True)
self.assertEqual('lalala', obj.fala)
pickled = pickle.dumps(obj)
newobj = pickle.loads(pickled)
self.assertEqual('foo', newobj.id)
self.assertEqual('bar', newobj.api_key)
self.assertEqual('boo', newobj['object'])
self.assertEqual('lalala', newobj.fala)
def test_deletion(self):
obj = libraries.stripe.resource.StripeObject('id', 'key')
obj.coupon = "foo"
self.assertEqual('foo', obj.coupon)
del obj.coupon
self.assertRaises(AttributeError, getattr, obj, 'coupon')
obj.refresh_from({'coupon': 'foo'}, api_key='bar', partial=True)
self.assertEqual('foo', obj.coupon)
+116
View File
@@ -0,0 +1,116 @@
import datetime
import time
import libraries.stripe
from libraries.stripe.test.helper import (
StripeResourceTest, DUMMY_PLAN
)
class SubscriptionTest(StripeResourceTest):
def test_list_subscriptions(self):
libraries.stripe.Subscription.all(customer="test_cus", plan=DUMMY_PLAN['id'],
limit=3)
self.requestor_mock.request.assert_called_with(
'get',
'/v1/subscriptions',
{
'customer': 'test_cus',
'plan': DUMMY_PLAN['id'],
'limit': 3,
},
)
def test_retrieve_subscription(self):
libraries.stripe.Subscription.retrieve('test_sub')
self.requestor_mock.request.assert_called_with(
'get',
'/v1/subscriptions/test_sub',
{},
None
)
def test_create_subscription(self):
libraries.stripe.Subscription.create(customer="test_cus", plan=DUMMY_PLAN['id'])
self.requestor_mock.request.assert_called_with(
'post',
'/v1/subscriptions',
{
'customer': 'test_cus',
'plan': DUMMY_PLAN['id']
},
None
)
def test_update_subscription(self):
subscription = libraries.stripe.Subscription.construct_from({
'id': 'test_sub',
'customer': 'test_cus',
}, 'api_key')
trial_end_dttm = datetime.datetime.now() + datetime.timedelta(days=15)
trial_end_int = int(time.mktime(trial_end_dttm.timetuple()))
subscription.plan = DUMMY_PLAN['id']
subscription.trial_end = trial_end_int
subscription.save()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/subscriptions/test_sub',
{
'plan': DUMMY_PLAN['id'],
'trial_end': trial_end_int
},
None
)
def test_modify_subscription(self):
trial_end_dttm = datetime.datetime.now() + datetime.timedelta(days=15)
trial_end_int = int(time.mktime(trial_end_dttm.timetuple()))
libraries.stripe.Subscription.modify('test_sub',
plan=DUMMY_PLAN['id'],
trial_end=trial_end_int)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/subscriptions/test_sub',
{
'plan': DUMMY_PLAN['id'],
'trial_end': trial_end_int
},
None
)
def test_delete_subscription(self):
subscription = libraries.stripe.Subscription.construct_from({
'id': 'test_sub',
'customer': 'test_cus',
}, 'api_key')
subscription.delete()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/subscriptions/test_sub',
{},
None
)
def test_delete_subscription_discount(self):
subscription = libraries.stripe.Subscription.construct_from({
'id': 'test_sub',
'customer': 'test_cus',
'coupon': 'test_discount'
}, 'api_key')
subscription.delete_discount()
self.requestor_mock.request.assert_called_with(
'delete',
'/v1/subscriptions/test_sub/discount'
)
+25
View File
@@ -0,0 +1,25 @@
import libraries.stripe
from libraries.stripe.test.helper import StripeResourceTest
class ThreeDSecureTest(StripeResourceTest):
def test_threedsecure_create(self):
libraries.stripe.ThreeDSecure.create(
card="tok_test",
amount=1500,
currency="usd",
return_url="https://example.org/3d-secure-result"
)
self.requestor_mock.request.assert_called_with(
'post',
'/v1/3d_secure',
{
'card': 'tok_test',
'amount': 1500,
'currency': 'usd',
'return_url': 'https://example.org/3d-secure-result'
},
None
)
+24
View File
@@ -0,0 +1,24 @@
import libraries.stripe
from libraries.stripe.test.helper import StripeResourceTest
class TransferTest(StripeResourceTest):
def test_list_transfers(self):
libraries.stripe.Transfer.list()
self.requestor_mock.request.assert_called_with(
'get',
'/v1/transfers',
{}
)
def test_cancel_transfer(self):
transfer = libraries.stripe.Transfer(id='tr_cancel')
transfer.cancel()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/transfers/tr_cancel/cancel',
{},
None
)
+366
View File
@@ -0,0 +1,366 @@
from libraries.stripe.test.helper import (
StripeApiTestCase, MyUpdateable
)
class UpdateableAPIResourceTests(StripeApiTestCase):
def setUp(self):
super(UpdateableAPIResourceTests, self).setUp()
self.mock_response({
'thats': 'it'
})
self.obj = MyUpdateable.construct_from({
'id': 'myid',
'foo': 'bar',
'baz': 'boz',
'metadata': {
'size': 'l',
'score': 4,
'height': 10
}
}, 'mykey')
def checkSave(self):
self.assertTrue(self.obj is self.obj.save())
self.assertEqual('it', self.obj.thats)
# TODO: Should we force id to be retained?
# self.assertEqual('myid', obj.id)
self.assertRaises(AttributeError, getattr, self.obj, 'baz')
def test_idempotent_save(self):
self.obj.baz = 'updated'
self.obj.save(idempotency_key='foo')
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{
'metadata': {},
'baz': 'updated',
},
{
'Idempotency-Key': 'foo',
},
)
def test_save(self):
self.obj.baz = 'updated'
self.obj.other = 'newval'
self.obj.metadata.size = 'm'
self.obj.metadata.info = 'a2'
self.obj.metadata.height = None
self.checkSave()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{
'baz': 'updated',
'other': 'newval',
'metadata': {
'size': 'm',
'info': 'a2',
'height': '',
}
},
None
)
def test_add_key_to_nested_object(self):
acct = MyUpdateable.construct_from({
'id': 'myid',
'legal_entity': {
'size': 'l',
'score': 4,
'height': 10
}
}, 'mykey')
acct.legal_entity['first_name'] = 'bob'
self.assertTrue(acct is acct.save())
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{
'legal_entity': {
'first_name': 'bob',
}
},
None
)
def test_save_nothing(self):
acct = MyUpdateable.construct_from({
'id': 'myid',
'metadata': {
'key': 'value',
}
}, 'mykey')
self.assertTrue(acct is acct.save())
# Note: ideally, we'd want the library to NOT issue requests in this
# case (i.e. the assert should actually be `assert_not_called()`).
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{'metadata': {}},
None
)
def test_replace_nested_object(self):
acct = MyUpdateable.construct_from({
'id': 'myid',
'legal_entity': {
'last_name': 'smith',
}
}, 'mykey')
acct.legal_entity = {
'first_name': 'bob',
}
self.assertTrue(acct is acct.save())
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{
'legal_entity': {
'first_name': 'bob',
'last_name': '',
}
},
None
)
def test_array_setting(self):
acct = MyUpdateable.construct_from({
'id': 'myid',
'legal_entity': {}
}, 'mykey')
acct.legal_entity.additional_owners = [{'first_name': 'Bob'}]
self.assertTrue(acct is acct.save())
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{
'legal_entity': {
'additional_owners': [
{'first_name': 'Bob'}
]
}
},
None
)
def test_array_none(self):
acct = MyUpdateable.construct_from({
'id': 'myid',
'legal_entity': {
'additional_owners': None,
}
}, 'mykey')
acct.foo = 'bar'
self.assertTrue(acct is acct.save())
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{
'foo': 'bar',
'legal_entity': {},
},
None
)
def test_array_insertion(self):
acct = MyUpdateable.construct_from({
'id': 'myid',
'legal_entity': {
'additional_owners': []
}
}, 'mykey')
acct.legal_entity.additional_owners.append({'first_name': 'Bob'})
self.assertTrue(acct is acct.save())
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{
'legal_entity': {
'additional_owners': {
'0': {'first_name': 'Bob'},
}
}
},
None
)
def test_array_update(self):
acct = MyUpdateable.construct_from({
'id': 'myid',
'legal_entity': {
'additional_owners': [
{'first_name': 'Bob'},
{'first_name': 'Jane'}
]
}
}, 'mykey')
acct.legal_entity.additional_owners[1].first_name = 'Janet'
self.assertTrue(acct is acct.save())
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{
'legal_entity': {
'additional_owners': {
'0': {},
'1': {'first_name': 'Janet'}
}
}
},
None
)
def test_array_noop(self):
acct = MyUpdateable.construct_from({
'id': 'myid',
'legal_entity': {
'additional_owners': [{'first_name': 'Bob'}]
},
'currencies_supported': ['usd', 'cad']
}, 'mykey')
self.assertTrue(acct is acct.save())
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{
'legal_entity': {'additional_owners': {'0': {}}}
},
None
)
def test_hash_noop(self):
acct = MyUpdateable.construct_from({
'id': 'myid',
'legal_entity': {
'address': {'line1': '1 Two Three'}
}
}, 'mykey')
self.assertTrue(acct is acct.save())
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{'legal_entity': {'address': {}}},
None
)
def test_save_replace_metadata_with_number(self):
self.obj.baz = 'updated'
self.obj.other = 'newval'
self.obj.metadata = 3
self.checkSave()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{
'baz': 'updated',
'other': 'newval',
'metadata': 3,
},
None
)
def test_save_overwrite_metadata(self):
self.obj.metadata = {}
self.checkSave()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{
'metadata': {
'size': '',
'score': '',
'height': '',
}
},
None
)
def test_save_replace_metadata(self):
self.obj.baz = 'updated'
self.obj.other = 'newval'
self.obj.metadata = {
'size': 'm',
'info': 'a2',
'score': 4,
}
self.checkSave()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{
'baz': 'updated',
'other': 'newval',
'metadata': {
'size': 'm',
'info': 'a2',
'height': '',
'score': 4,
}
},
None
)
def test_save_update_metadata(self):
self.obj.baz = 'updated'
self.obj.other = 'newval'
self.obj.metadata.update({
'size': 'm',
'info': 'a2',
'score': 4,
})
self.checkSave()
self.requestor_mock.request.assert_called_with(
'post',
'/v1/myupdateables/myid',
{
'baz': 'updated',
'other': 'newval',
'metadata': {
'size': 'm',
'info': 'a2',
'score': 4,
}
},
None
)
+37
View File
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
import sys
import unittest2
from libraries.stripe import StripeError
from libraries.stripe.test.helper import StripeUnitTestCase
class StripeErrorTests(StripeUnitTestCase):
def test_formatting(self):
err = StripeError('öre')
if sys.version_info > (3, 0):
assert str(err) == 'öre'
else:
assert str(err) == '\xc3\xb6re'
assert str(err) == 'öre'
def test_formatting_with_request_id(self):
err = StripeError('öre', headers={'request-id': '123'})
if sys.version_info > (3, 0):
assert str(err) == 'Request 123: öre'
else:
assert str(err) == 'Request 123: \xc3\xb6re'
assert str(err) == 'Request 123: öre'
def test_formatting_with_none(self):
err = StripeError(None, headers={'request-id': '123'})
if sys.version_info > (3, 0):
assert str(err) == 'Request 123: <empty message>'
else:
assert str(err) == 'Request 123: <empty message>'
assert str(err) == 'Request 123: <empty message>'
if __name__ == '__main__':
unittest2.main()
+266
View File
@@ -0,0 +1,266 @@
import sys
import unittest2
if 1/2 != 0: str=str
from mock import Mock, patch
import libraries.stripe
from libraries.stripe.test.helper import StripeUnitTestCase
VALID_API_METHODS = ('get', 'post', 'delete')
class HttpClientTests(StripeUnitTestCase):
def setUp(self):
super(HttpClientTests, self).setUp()
self.original_filters = libraries.stripe.http_client.warnings.filters[:]
libraries.stripe.http_client.warnings.simplefilter('ignore')
def tearDown(self):
libraries.stripe.http_client.warnings.filters = self.original_filters
super(HttpClientTests, self).tearDown()
def check_default(self, none_libs, expected):
for lib in none_libs:
setattr(stripe.http_client, lib, None)
inst = libraries.stripe.http_client.new_default_http_client()
self.assertTrue(isinstance(inst, expected))
def test_new_default_http_client_urlfetch(self):
self.check_default((),
libraries.stripe.http_client.UrlFetchClient)
def test_new_default_http_client_requests(self):
self.check_default(('urlfetch',),
libraries.stripe.http_client.RequestsClient)
def test_new_default_http_client_pycurl(self):
self.check_default(('urlfetch', 'requests',),
libraries.stripe.http_client.PycurlClient)
def test_new_default_http_client_urllib2(self):
self.check_default(('urlfetch', 'requests', 'pycurl'),
libraries.stripe.http_client.Urllib2Client)
class ClientTestBase():
@property
def request_mock(self):
return self.request_mocks[self.request_client.name]
@property
def valid_url(self, path='/foo'):
return 'https://api.stripe.com%s' % (path,)
def make_request(self, method, url, headers, post_data):
client = self.request_client(verify_ssl_certs=True)
return client.request(method, url, headers, post_data)
def mock_response(self, body, code):
raise NotImplementedError(
'You must implement this in your test subclass')
def mock_error(self, error):
raise NotImplementedError(
'You must implement this in your test subclass')
def check_call(self, meth, abs_url, headers, params):
raise NotImplementedError(
'You must implement this in your test subclass')
def test_request(self):
self.mock_response(self.request_mock, '{"foo": "baz"}', 200)
for meth in VALID_API_METHODS:
abs_url = self.valid_url
data = ''
if meth != 'post':
abs_url = '%s?%s' % (abs_url, data)
data = None
headers = {'my-header': 'header val'}
body, code, _ = self.make_request(
meth, abs_url, headers, data)
self.assertEqual(200, code)
self.assertEqual('{"foo": "baz"}', body)
self.check_call(self.request_mock, meth, abs_url,
data, headers)
def test_exception(self):
self.mock_error(self.request_mock)
self.assertRaises(stripe.error.APIConnectionError,
self.make_request,
'get', self.valid_url, {}, None)
class RequestsVerify(object):
def __eq__(self, other):
return other and other.endswith('stripe/data/ca-certificates.crt')
class RequestsClientTests(StripeUnitTestCase, ClientTestBase):
request_client = libraries.stripe.http_client.RequestsClient
def mock_response(self, mock, body, code):
result = Mock()
result.content = body
result.status_code = code
mock.request = Mock(return_value=result)
def mock_error(self, mock):
mock.exceptions.RequestException = Exception
mock.request.side_effect = mock.exceptions.RequestException()
def check_call(self, mock, meth, url, post_data, headers):
mock.request.assert_called_with(meth, url,
headers=headers,
data=post_data,
verify=RequestsVerify(),
timeout=80)
class UrlFetchClientTests(StripeUnitTestCase, ClientTestBase):
request_client = libraries.stripe.http_client.UrlFetchClient
def mock_response(self, mock, body, code):
result = Mock()
result.content = body
result.status_code = code
mock.fetch = Mock(return_value=result)
def mock_error(self, mock):
mock.Error = mock.InvalidURLError = Exception
mock.fetch.side_effect = mock.InvalidURLError()
def check_call(self, mock, meth, url, post_data, headers):
mock.fetch.assert_called_with(
url=url,
method=meth,
headers=headers,
validate_certificate=True,
deadline=55,
payload=post_data
)
class Urllib2ClientTests(StripeUnitTestCase, ClientTestBase):
request_client = libraries.stripe.http_client.Urllib2Client
def mock_response(self, mock, body, code):
response = Mock
response.read = Mock(return_value=body)
response.code = code
response.info = Mock(return_value={})
self.request_object = Mock()
mock.Request = Mock(return_value=self.request_object)
mock.urlopen = Mock(return_value=response)
def mock_error(self, mock):
mock.urlopen.side_effect = ValueError
def check_call(self, mock, meth, url, post_data, headers):
if sys.version_info >= (3, 0) and isinstance(post_data, str):
post_data = post_data.encode('utf-8')
mock.Request.assert_called_with(url, post_data, headers)
mock.urlopen.assert_called_with(self.request_object)
class PycurlClientTests(StripeUnitTestCase, ClientTestBase):
request_client = libraries.stripe.http_client.PycurlClient
@property
def request_mock(self):
if not hasattr(self, 'curl_mock'):
lib_mock = self.request_mocks[self.request_client.name]
self.curl_mock = Mock()
lib_mock.Curl = Mock(return_value=self.curl_mock)
return self.curl_mock
def setUp(self):
super(PycurlClientTests, self).setUp()
self.sio_patcher = patch('stripe.util.StringIO.StringIO')
sio_mock = Mock()
self.sio_patcher.start().return_value = sio_mock
self.sio_getvalue = sio_mock.getvalue
def tearDown(self):
super(PycurlClientTests, self).tearDown()
self.sio_patcher.stop()
def mock_response(self, mock, body, code):
self.sio_getvalue.return_value = body
mock.getinfo.return_value = code
def mock_error(self, mock):
class FakeException(BaseException):
def __getitem__(self, i):
return 'foo'
libraries.stripe.http_client.pycurl.error = FakeException
mock.perform.side_effect = libraries.stripe.http_client.pycurl.error
def check_call(self, mock, meth, url, post_data, headers):
# TODO: Check the setopt calls
pass
class APIEncodeTest(StripeUnitTestCase):
def test_encode_dict(self):
body = {
'foo': {
'dob': {
'month': 1,
},
'name': 'bat'
},
}
values = [t for t in libraries.stripe.api_requestor._api_encode(body)]
self.assertTrue(('foo[dob][month]', 1) in values)
self.assertTrue(('foo[name]', 'bat') in values)
def test_encode_array(self):
body = {
'foo': [{
'dob': {
'month': 1,
},
'name': 'bat'
}],
}
values = [t for t in libraries.stripe.api_requestor._api_encode(body)]
self.assertTrue(('foo[][dob][month]', 1) in values)
self.assertTrue(('foo[][name]', 'bat') in values)
if __name__ == '__main__':
unittest2.main()
+178
View File
@@ -0,0 +1,178 @@
# -*- coding: utf-8 -*-
import os
import sys
import unittest2
import libraries.stripe
if 1/2 != 0: str=str
from mock import patch
from libraries.stripe.test.helper import (StripeTestCase, NOW, DUMMY_CHARGE, DUMMY_CARD)
class FunctionalTests(StripeTestCase):
request_client = libraries.stripe.http_client.Urllib2Client
def setUp(self):
super(FunctionalTests, self).setUp()
def get_http_client(*args, **kwargs):
return self.request_client(*args, **kwargs)
self.client_patcher = patch(
'stripe.http_client.new_default_http_client')
client_mock = self.client_patcher.start()
client_mock.side_effect = get_http_client
def tearDown(self):
super(FunctionalTests, self).tearDown()
self.client_patcher.stop()
def test_dns_failure(self):
api_base = libraries.stripe.api_base
try:
libraries.stripe.api_base = 'https://my-invalid-domain.ireallywontresolve/v1'
self.assertRaises(stripe.error.APIConnectionError,
libraries.stripe.Customer.create)
finally:
libraries.stripe.api_base = api_base
def test_run(self):
charge = libraries.stripe.Charge.create(**DUMMY_CHARGE)
self.assertFalse(charge.refunded)
charge.refund()
self.assertTrue(charge.refunded)
def test_refresh(self):
charge = libraries.stripe.Charge.create(**DUMMY_CHARGE)
charge2 = libraries.stripe.Charge.retrieve(charge.id)
self.assertEqual(charge2.created, charge.created)
charge2.junk = 'junk'
charge2.refresh()
self.assertRaises(AttributeError, lambda: charge2.junk)
def test_list_accessors(self):
customer = libraries.stripe.Customer.create(card=DUMMY_CARD)
self.assertEqual(customer['created'], customer.created)
customer['foo'] = 'bar'
self.assertEqual(customer.foo, 'bar')
def test_raise(self):
EXPIRED_CARD = DUMMY_CARD.copy()
EXPIRED_CARD['exp_month'] = NOW.month - 2
EXPIRED_CARD['exp_year'] = NOW.year - 2
self.assertRaises(stripe.error.CardError, libraries.stripe.Charge.create,
amount=100, currency='usd', card=EXPIRED_CARD)
def test_response_headers(self):
EXPIRED_CARD = DUMMY_CARD.copy()
EXPIRED_CARD['exp_month'] = NOW.month - 2
EXPIRED_CARD['exp_year'] = NOW.year - 2
try:
libraries.stripe.Charge.create(amount=100, currency='usd', card=EXPIRED_CARD)
self.fail('charge creation with expired card did not fail')
except libraries.stripe.error.CardError as e:
self.assertTrue(e.request_id.startswith('req_'))
def test_unicode(self):
# Make sure unicode requests can be sent
self.assertRaises(stripe.error.InvalidRequestError,
libraries.stripe.Charge.retrieve,
id='☃')
def test_none_values(self):
customer = libraries.stripe.Customer.create(plan=None)
self.assertTrue(customer.id)
def test_missing_id(self):
customer = libraries.stripe.Customer()
self.assertRaises(stripe.error.InvalidRequestError, customer.refresh)
class RequestsFunctionalTests(FunctionalTests):
request_client = libraries.stripe.http_client.RequestsClient
class UrlfetchFunctionalTests(FunctionalTests):
request_client = 'urlfetch'
def setUp(self):
if libraries.stripe.http_client.urlfetch is None:
self.skipTest(
'`urlfetch` from Google App Engine is unavailable.')
else:
super(UrlfetchFunctionalTests, self).setUp()
class PycurlFunctionalTests(FunctionalTests):
def setUp(self):
if not os.environ.get('STRIPE_TEST_PYCURL'):
self.skipTest('Pycurl skipped as STRIPE_TEST_PYCURL is not set')
if sys.version_info >= (3, 0):
self.skipTest('Pycurl is not supported in Python 3')
else:
super(PycurlFunctionalTests, self).setUp()
request_client = libraries.stripe.http_client.PycurlClient
class AuthenticationErrorTest(StripeTestCase):
def test_invalid_credentials(self):
key = libraries.stripe.api_key
try:
libraries.stripe.api_key = 'invalid'
libraries.stripe.Customer.create()
except libraries.stripe.error.AuthenticationError as e:
self.assertEqual(401, e.http_status)
self.assertTrue(isinstance(e.http_body, str))
self.assertTrue(isinstance(e.json_body, dict))
# Note that an invalid API key bypasses many of the standard
# facilities in the API server so currently no Request ID is
# returned.
finally:
libraries.stripe.api_key = key
class CardErrorTest(StripeTestCase):
def test_declined_card_props(self):
EXPIRED_CARD = DUMMY_CARD.copy()
EXPIRED_CARD['exp_month'] = NOW.month - 2
EXPIRED_CARD['exp_year'] = NOW.year - 2
try:
libraries.stripe.Charge.create(amount=100, currency='usd', card=EXPIRED_CARD)
except libraries.stripe.error.CardError as e:
self.assertEqual(402, e.http_status)
self.assertTrue(isinstance(e.http_body, str))
self.assertTrue(isinstance(e.json_body, dict))
self.assertTrue(e.request_id.startswith('req_'))
class InvalidRequestErrorTest(StripeTestCase):
def test_nonexistent_object(self):
try:
libraries.stripe.Charge.retrieve('invalid')
except libraries.stripe.error.InvalidRequestError as e:
self.assertEqual(404, e.http_status)
self.assertTrue(isinstance(e.http_body, str))
self.assertTrue(isinstance(e.json_body, dict))
self.assertTrue(e.request_id.startswith('req_'))
def test_invalid_data(self):
try:
libraries.stripe.Charge.create()
except libraries.stripe.error.InvalidRequestError as e:
self.assertEqual(400, e.http_status)
self.assertTrue(isinstance(e.http_body, str))
self.assertTrue(isinstance(e.json_body, dict))
self.assertTrue(e.request_id.startswith('req_'))
if __name__ == '__main__':
unittest2.main()
+52
View File
@@ -0,0 +1,52 @@
# -*- encoding: utf-8 -*-
import re
import sys
from libraries.stripe.multipart_data_generator import MultipartDataGenerator
from libraries.stripe.test.helper import StripeTestCase
class MultipartDataGeneratorTests(StripeTestCase):
def run_test_multipart_data_with_file(self, test_file):
params = {
"key1": b"ASCII value",
"key2": "Üñìçôdé value",
"key3": test_file
}
generator = MultipartDataGenerator()
generator.add_params(params)
http_body = generator.get_post_data()
if sys.version_info >= (3,):
http_body = http_body.decode('utf-8')
self.assertTrue(re.search(
r"Content-Disposition: form-data; name=\"key1\"", http_body))
self.assertTrue(re.search(r"ASCII value", http_body))
self.assertTrue(re.search(
r"Content-Disposition: form-data; name=\"key2\"", http_body))
self.assertTrue(re.search(r"Üñìçôdé value", http_body))
self.assertTrue(re.search(
r"Content-Disposition: form-data; name=\"key3\"; "
r"filename=\".+\"",
http_body))
self.assertTrue(re.search(
r"Content-Type: application/octet-stream", http_body))
test_file.seek(0)
file_contents = test_file.read()
if sys.version_info >= (3,) and isinstance(file_contents, bytes):
file_contents = file_contents.decode('utf-8')
self.assertNotEqual(-1, http_body.find(file_contents))
def test_multipart_data_file_text(self):
with open(__file__, mode='r') as test_file:
self.run_test_multipart_data_with_file(test_file)
def test_multipart_data_file_binary(self):
with open(__file__, mode='rb') as test_file:
self.run_test_multipart_data_with_file(test_file)
+417
View File
@@ -0,0 +1,417 @@
import datetime
import unittest2
import urllib.parse
from mock import Mock, ANY
import libraries.stripe
from libraries.stripe.test.helper import StripeUnitTestCase
VALID_API_METHODS = ('get', 'post', 'delete')
class GMT1(datetime.tzinfo):
def utcoffset(self, dt):
return datetime.timedelta(hours=1)
def dst(self, dt):
return datetime.timedelta(0)
def tzname(self, dt):
return "Europe/Prague"
class APIHeaderMatcher(object):
EXP_KEYS = ['X-Stripe-Client-User-Agent', 'User-Agent', 'Authorization']
METHOD_EXTRA_KEYS = {"post": ["Content-Type"]}
def __init__(self, api_key=None, extra={}, request_method=None):
self.request_method = request_method
self.api_key = api_key or libraries.stripe.api_key
self.extra = extra
def __eq__(self, other):
return (self._keys_match(other) and
self._auth_match(other) and
self._extra_match(other))
def _keys_match(self, other):
expected_keys = self.EXP_KEYS + list(self.extra.keys())
if self.request_method is not None and self.request_method in \
self.METHOD_EXTRA_KEYS:
expected_keys.extend(self.METHOD_EXTRA_KEYS[self.request_method])
return (sorted(other.keys()) == sorted(expected_keys))
def _auth_match(self, other):
return other['Authorization'] == "Bearer %s" % (self.api_key,)
def _extra_match(self, other):
for k, v in self.extra.items():
if other[k] != v:
return False
return True
class QueryMatcher(object):
def __init__(self, expected):
self.expected = sorted(expected)
def __eq__(self, other):
query = urllib.parse.urlsplit(other).query or other
parsed = libraries.stripe.util.parse_qsl(query)
return self.expected == sorted(parsed)
class UrlMatcher(object):
def __init__(self, expected):
self.exp_parts = urllib.parse.urlsplit(expected)
def __eq__(self, other):
other_parts = urllib.parse.urlsplit(other)
for part in ('scheme', 'netloc', 'path', 'fragment'):
expected = getattr(self.exp_parts, part)
actual = getattr(other_parts, part)
if expected != actual:
print('Expected %s "%s" but got "%s"' % (
part, expected, actual))
return False
q_matcher = QueryMatcher(stripe.util.parse_qsl(self.exp_parts.query))
return q_matcher == other
class APIRequestorRequestTests(StripeUnitTestCase):
ENCODE_INPUTS = {
'dict': {
'astring': 'bar',
'anint': 5,
'anull': None,
'adatetime': datetime.datetime(2013, 1, 1, tzinfo=GMT1()),
'atuple': (1, 2),
'adict': {'foo': 'bar', 'boz': 5},
'alist': ['foo', 'bar'],
},
'list': [1, 'foo', 'baz'],
'string': 'boo',
'unicode': '\u1234',
'datetime': datetime.datetime(2013, 1, 1, second=1, tzinfo=GMT1()),
'none': None,
}
ENCODE_EXPECTATIONS = {
'dict': [
('%s[astring]', 'bar'),
('%s[anint]', 5),
('%s[adatetime]', 1356994800),
('%s[adict][foo]', 'bar'),
('%s[adict][boz]', 5),
('%s[alist][]', 'foo'),
('%s[alist][]', 'bar'),
('%s[atuple][]', 1),
('%s[atuple][]', 2),
],
'list': [
('%s[]', 1),
('%s[]', 'foo'),
('%s[]', 'baz'),
],
'string': [('%s', 'boo')],
'unicode': [('%s', libraries.stripe.util.utf8('\u1234'))],
'datetime': [('%s', 1356994801)],
'none': [],
}
def setUp(self):
super(APIRequestorRequestTests, self).setUp()
self.http_client = Mock(stripe.http_client.HTTPClient)
self.http_client._verify_ssl_certs = True
self.http_client.name = 'mockclient'
self.requestor = libraries.stripe.api_requestor.APIRequestor(
client=self.http_client)
def mock_response(self, return_body, return_code, requestor=None,
headers=None):
if not requestor:
requestor = self.requestor
self.http_client.request = Mock(
return_value=(return_body, return_code, headers or {}))
def check_call(self, meth, abs_url=None, headers=None,
post_data=None, requestor=None):
if not abs_url:
abs_url = 'https://api.stripe.com%s' % (self.valid_path,)
if not requestor:
requestor = self.requestor
if not headers:
headers = APIHeaderMatcher(request_method=meth)
self.http_client.request.assert_called_with(
meth, abs_url, headers, post_data)
@property
def valid_path(self):
return '/foo'
def encoder_check(self, key):
stk_key = "my%s" % (key,)
value = self.ENCODE_INPUTS[key]
expectation = [(k % (stk_key,), v) for k, v in
self.ENCODE_EXPECTATIONS[key]]
stk = []
fn = getattr(stripe.api_requestor.APIRequestor, "encode_%s" % (key,))
fn(stk, stk_key, value)
if isinstance(value, dict):
expectation.sort()
stk.sort()
self.assertEqual(expectation, stk)
def _test_encode_naive_datetime(self):
stk = []
libraries.stripe.api_requestor.APIRequestor.encode_datetime(
stk, 'test', datetime.datetime(2013, 1, 1))
# Naive datetimes will encode differently depending on your system
# local time. Since we don't know the local time of your system,
# we just check that naive encodings are within 24 hours of correct.
self.assertTrue(60 * 60 * 24 > abs(stk[0][1] - 1356994800))
def test_param_encoding(self):
self.mock_response('{}', 200)
self.requestor.request('get', '', self.ENCODE_INPUTS)
expectation = []
for type_, values in self.ENCODE_EXPECTATIONS.items():
expectation.extend([(k % (type_,), str(v)) for k, v in values])
self.check_call('get', QueryMatcher(expectation))
def test_dictionary_list_encoding(self):
params = {
'foo': {
'0': {
'bar': 'bat',
}
}
}
encoded = list(stripe.api_requestor._api_encode(params))
key, value = encoded[0]
self.assertEqual('foo[0][bar]', key)
self.assertEqual('bat', value)
def test_url_construction(self):
CASES = (
('https://api.stripe.com?foo=bar', '', {'foo': 'bar'}),
('https://api.stripe.com?foo=bar', '?', {'foo': 'bar'}),
('https://api.stripe.com', '', {}),
(
'https://api.stripe.com/%20spaced?foo=bar%24&baz=5',
'/%20spaced?foo=bar%24',
{'baz': '5'}
),
(
'https://api.stripe.com?foo=bar&foo=bar',
'?foo=bar',
{'foo': 'bar'}
),
)
for expected, url, params in CASES:
self.mock_response('{}', 200)
self.requestor.request('get', url, params)
self.check_call('get', expected)
def test_empty_methods(self):
for meth in VALID_API_METHODS:
self.mock_response('{}', 200)
body, key = self.requestor.request(meth, self.valid_path, {})
if meth == 'post':
post_data = ''
else:
post_data = None
self.check_call(meth, post_data=post_data)
self.assertEqual({}, body)
def test_methods_with_params_and_response(self):
for meth in VALID_API_METHODS:
self.mock_response('{"foo": "bar", "baz": 6}', 200)
params = {
'alist': [1, 2, 3],
'adict': {'frobble': 'bits'},
'adatetime': datetime.datetime(2013, 1, 1, tzinfo=GMT1())
}
encoded = ('adict%5Bfrobble%5D=bits&adatetime=1356994800&'
'alist%5B%5D=1&alist%5B%5D=2&alist%5B%5D=3')
body, key = self.requestor.request(meth, self.valid_path,
params)
self.assertEqual({'foo': 'bar', 'baz': 6}, body)
if meth == 'post':
self.check_call(
meth,
post_data=QueryMatcher(stripe.util.parse_qsl(encoded)))
else:
abs_url = "https://api.stripe.com%s?%s" % (
self.valid_path, encoded)
self.check_call(meth, abs_url=UrlMatcher(abs_url))
def test_uses_headers(self):
self.mock_response('{}', 200)
self.requestor.request('get', self.valid_path, {}, {'foo': 'bar'})
self.check_call('get', headers=APIHeaderMatcher(extra={'foo': 'bar'}))
def test_uses_instance_key(self):
key = 'fookey'
requestor = libraries.stripe.api_requestor.APIRequestor(key,
client=self.http_client)
self.mock_response('{}', 200, requestor=requestor)
body, used_key = requestor.request('get', self.valid_path, {})
self.check_call('get', headers=APIHeaderMatcher(
key, request_method='get'), requestor=requestor)
self.assertEqual(key, used_key)
def test_passes_api_version(self):
libraries.stripe.api_version = 'fooversion'
self.mock_response('{}', 200)
body, key = self.requestor.request('get', self.valid_path, {})
self.check_call('get', headers=APIHeaderMatcher(
extra={'Stripe-Version': 'fooversion'}, request_method='get'))
def test_uses_instance_account(self):
account = 'acct_foo'
requestor = libraries.stripe.api_requestor.APIRequestor(account=account,
client=self.http_client)
self.mock_response('{}', 200, requestor=requestor)
requestor.request('get', self.valid_path, {})
self.check_call(
'get',
requestor=requestor,
headers=APIHeaderMatcher(
extra={'Stripe-Account': account},
request_method='get'
),
)
def test_fails_without_api_key(self):
libraries.stripe.api_key = None
self.assertRaises(stripe.error.AuthenticationError,
self.requestor.request,
'get', self.valid_path, {})
def test_not_found(self):
self.mock_response('{"error": {}}', 404)
self.assertRaises(stripe.error.InvalidRequestError,
self.requestor.request,
'get', self.valid_path, {})
def test_authentication_error(self):
self.mock_response('{"error": {}}', 401)
self.assertRaises(stripe.error.AuthenticationError,
self.requestor.request,
'get', self.valid_path, {})
def test_card_error(self):
self.mock_response('{"error": {}}', 402)
self.assertRaises(stripe.error.CardError,
self.requestor.request,
'get', self.valid_path, {})
def test_rate_limit_error(self):
self.mock_response('{"error": {}}', 429)
self.assertRaises(stripe.error.RateLimitError,
self.requestor.request,
'get', self.valid_path, {})
def test_old_rate_limit_error(self):
"""
Tests legacy rate limit error pre-2015-09-18
"""
self.mock_response('{"error": {"code":"rate_limit"}}', 400)
self.assertRaises(stripe.error.RateLimitError,
self.requestor.request,
'get', self.valid_path, {})
def test_server_error(self):
self.mock_response('{"error": {}}', 500)
self.assertRaises(stripe.error.APIError,
self.requestor.request,
'get', self.valid_path, {})
def test_invalid_json(self):
self.mock_response('{', 200)
self.assertRaises(stripe.error.APIError,
self.requestor.request,
'get', self.valid_path, {})
def test_invalid_method(self):
self.assertRaises(stripe.error.APIConnectionError,
self.requestor.request,
'foo', 'bar')
class DefaultClientTests(unittest2.TestCase):
def setUp(self):
libraries.stripe.default_http_client = None
libraries.stripe.api_key = 'foo'
def test_default_http_client_called(self):
hc = Mock(stripe.http_client.HTTPClient)
hc._verify_ssl_certs = True
hc.name = 'mockclient'
hc.request = Mock(return_value=("{}", 200, {}))
libraries.stripe.default_http_client = hc
libraries.stripe.Charge.list(limit=3)
hc.request.assert_called_with(
'get', 'https://api.stripe.com/v1/charges?limit=3', ANY, None)
def tearDown(self):
libraries.stripe.api_key = None
libraries.stripe.default_http_client = None
if __name__ == '__main__':
unittest2.main()
+63
View File
@@ -0,0 +1,63 @@
import logging
import sys
import os
logger = logging.getLogger('stripe')
__all__ = ['StringIO', 'parse_qsl', 'json', 'utf8']
try:
# When cStringIO is available
import io as StringIO
except ImportError:
try:
import io
except:
from io import StringIO #for Python 3
try:
from urllib.parse import parse_qsl
except ImportError:
# Python < 2.6
try:
from cgi import parse_qsl
except:
from urllib.parse import parse_qsl #for Python 3
try:
import json
except ImportError:
json = None
if not (json and hasattr(json, 'loads')):
try:
import simplejson as json
except ImportError:
if not json:
raise ImportError(
"Stripe requires a JSON library, such as simplejson. "
"HINT: Try installing the "
"python simplejson library via 'pip install simplejson' or "
"'easy_install simplejson', or contact support@stripe.com "
"with questions.")
else:
raise ImportError(
"Stripe requires a JSON library with the same interface as "
"the Python 2.6 'json' library. You appear to have a 'json' "
"library with a different interface. Please install "
"the simplejson library. HINT: Try installing the "
"python simplejson library via 'pip install simplejson' "
"or 'easy_install simplejson', or contact support@stripe.com"
"with questions.")
def utf8(value):
if isinstance(value, str) and sys.version_info < (3, 0):
return value.encode('utf-8')
else:
return value
def is_appengine_dev():
return ('APPENGINE_RUNTIME' in os.environ and
'Dev' in os.environ.get('SERVER_SOFTWARE', ''))
+1
View File
@@ -0,0 +1 @@
VERSION = '1.37.0'