Skip to content

Commit

Permalink
Import
Browse files Browse the repository at this point in the history
  • Loading branch information
keketa committed Sep 6, 2015
0 parents commit 9ad8485
Show file tree
Hide file tree
Showing 21 changed files with 2,893 additions and 0 deletions.
57 changes: 57 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
.DS_Store

# C extensions
*.so

# Distribution / packaging
.Python
env/
bin/
build/
develop-eggs/
dist/
.eggs/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml

# Translations
*.mo

# Mr Developer
.idea
.mr.developer.cfg
.project
.pydevproject

# Rope
.ropeproject

# Django stuff:
*.log
*.pot

# Sphinx documentation
docs/_build/

14 changes: 14 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
language: python
python:
- "2.7"
- "pypy"
- "3.2"
- "3.3"
- "3.4"
script:
- pip install . --use-mirrors
- pip install -r requirements.txt --use-mirrors
cache: apt
matrix:
allow_failures:
- python: pypy
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License

Copyright (c) 2010-2011 Stripe (http://stripe.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# PAY.JP for Python

## Installation

Install from PyPi using [pip](http://www.pip-installer.org/en/latest/), a
package manager for Python.

pip install payjp-python // (準備中)

Or, you can [download the source code
(ZIP)](https://github.com/payjp/payjp-python/zipball/master "payjp-python
source code") for `payjp-python`, and then run:

python setup.py install

## Documentation

Please see our official [documentation](http://docs.pay.jp).

## Dependencies

- requests
- six

Install dependencies from using [pip](http://www.pip-installer.org/en/latest/):

pip install -r requirements.txt
15 changes: 15 additions & 0 deletions payjp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# PAY.JP Python bindings

# Configuration variables

api_key = None
api_base = 'https://api.pay.jp'
api_version = None

# TODO include Card?
__all__ = ['Account', 'Card', 'Charge', 'Customer', 'Event', 'Plan', 'Subscription', 'Token', 'Transfer']

# Resource
from payjp.resource import ( # noqa
Account, Charge, Customer, Event, Plan, Subscription, Token, Transfer)

199 changes: 199 additions & 0 deletions payjp/api_requestor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# coding: utf-8

import base64
import calendar
import datetime
import json
import logging
import platform
import time

from six import PY3
from six.moves.urllib.parse import urlencode, urlsplit, urlunsplit

import payjp
from . import (
error,
http_client,
util,
version,
)

logger = logging.getLogger('payjp')


class APIRequestor(object):

def __init__(self, key=None, client=None, api_base=None, account=None):
if api_base:
self.api_base = api_base
else:
self.api_base = payjp.api_base
self.api_key = key
self.payjp_account = account

self._client = client or http_client.new_default_http_client()

def request(self, method, url, params=None, headers=None):
body, code, my_api_key = self.request_raw(
method.lower(), url, params, headers)
response = self.interpret_response(body, code)
return response, my_api_key

def handle_api_error(self, body, code, response):
try:
err = response['error']
except (KeyError, TypeError):
raise error.APIError(
"Invalid response object from API: %r (HTTP response code "
"was %d)" % (body, code),
body, code, response)

if code in [400, 404]:
raise error.InvalidRequestError(
err.get('message'), err.get('param'), body, code, response)
elif code == 401:
raise error.AuthenticationError(
err.get('message'), body, code, response)
elif code == 402:
raise error.CardError(err.get('message'), err.get('param'),
err.get('code'), body, code, response)
else:
raise error.APIError(err.get('message'), body, code, response)

def request_raw(self, method, url, params=None, supplied_headers=None):

from payjp import api_version

if self.api_key:
my_api_key = self.api_key
else:
from payjp 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 '
'"payjp.api_key = <API-KEY>"). You can generate API keys '
'from the Payjp web interface. See https://docs.pay.jp'
'for details, or email support@pay.jp if you have any '
'questions.')

abs_url = '%s%s' % (self.api_base, url)

encoded_params = urlencode(list(_api_encode(params or {})))

if method in ('get', 'delete'):
if params:
abs_url = _build_api_url(abs_url, encoded_params)
post_data = None
elif method == 'post':
post_data = encoded_params
else:
raise error.APIConnectionError(
'Unrecognized HTTP method %r.' % (method,))

ua = {
'bindings_version': version.VERSION,
'lang': 'python',
'publisher': 'payjp',
'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

if PY3:
encoded_api_key = str(
base64.b64encode(
bytes(''.join([my_api_key, ':']), 'utf-8')), 'utf-8')
else:
encoded_api_key = base64.b64encode(''.join([my_api_key, ':']))

headers = {
'X-Payjp-Client-User-Agent': json.dumps(ua),
'User-Agent': 'Payjp/v1 PythonBindings/%s' % (version.VERSION,),
'Authorization': 'Basic %s' % encoded_api_key
}

if self.payjp_account:
headers['Payjp-Account'] = self.payjp_account

if method == 'post':
headers['Content-Type'] = 'application/x-www-form-urlencoded'

if api_version is not None:
headers['Payjp-Version'] = api_version

if supplied_headers is not None:
for key, value in supplied_headers.items():
headers[key] = value

body, code = self._client.request(
method, abs_url, headers, post_data)

logger.info('%s %s %d', method.upper(), abs_url, code)
logger.debug(
'API request to %s returned (response code, response body) of '
'(%d, %r)',
abs_url, code, body)

return body, code, my_api_key

def interpret_response(self, body, code):
try:
if hasattr(body, 'decode'):
body = body.decode('utf-8')
response = json.loads(body)
except Exception:
raise error.APIError(
"Invalid response body from API: %s "
"(HTTP response code was %d)" % (body, code),
body, code)
if not (200 <= code < 300):
self.handle_api_error(body, code, response)

return response

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 _api_encode(data):
for key, value in data.items():
key = util.utf8(key)
if value is None:
continue
elif hasattr(value, 'payjp_id'):
yield (key, value.payjp_id)
elif isinstance(value, list) or isinstance(value, tuple):
for subvalue in value:
yield ("%s[]" % (key,), util.utf8(subvalue))
elif isinstance(value, dict):
subdict = dict(('%s[%s]' % (key, subkey), subvalue) for
subkey, subvalue in value.items())
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 = urlsplit(url)

if base_query:
query = '%s&%s' % (base_query, query)

return urlunsplit((scheme, netloc, path, query, fragment))

53 changes: 53 additions & 0 deletions payjp/error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# coding: utf-8


class PayjpException(Exception):
def __init__(self, message=None, http_body=None, http_status=None,
json_body=None):
super(PayjpException, 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@pay.jp>')

self.http_body = http_body

self.http_status = http_status
self.json_body = json_body


class APIError(PayjpException):
pass


class APIConnectionError(PayjpException):
pass


class CardError(PayjpException):
def __init__(self, message, param, code, http_body=None,
http_status=None, json_body=None):
super(CardError, self).__init__(message,
http_body, http_status, json_body)
self.param = param
self.code = code


class InvalidRequestError(PayjpException):
pass


class AuthenticationError(PayjpException):
pass


class InvalidRequestError(PayjpException):

def __init__(self, message, param, http_body=None,
http_status=None, json_body=None):
super(InvalidRequestError, self).__init__(
message, http_body, http_status, json_body)
self.param = param
Loading

0 comments on commit 9ad8485

Please sign in to comment.