flittpayments 1.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 flittpayments
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.2
2
+ Name: flittpayments
3
+ Version: 1.0.3
4
+ Summary: Python SDK for Flitt clients.
5
+ Home-page: https://github.com/flittpayments/python/
6
+ Author: Dmitriy Miroshnikov
7
+ License: MIT
8
+ Classifier: Environment :: Web Environment
9
+ Classifier: Natural Language :: English
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 2.7
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.4
16
+ Classifier: Programming Language :: Python :: 3.6
17
+ Classifier: Programming Language :: Python :: 3.7
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ License-File: LICENSE
20
+ Requires-Dist: requests
21
+ Requires-Dist: six
22
+ Dynamic: author
23
+ Dynamic: classifier
24
+ Dynamic: description
25
+ Dynamic: home-page
26
+ Dynamic: license
27
+ Dynamic: requires-dist
28
+ Dynamic: summary
29
+
30
+
31
+ Flitt python sdk.
32
+ Docs - https://https://docs.flitt.com/
33
+ README - https://https://github.com/flittpayments/python/blob/master/README.md
34
+
@@ -0,0 +1,46 @@
1
+ # flittpayments Python SDK client
2
+
3
+
4
+ ## Payment service provider
5
+ A payment service provider (PSP) offers shops online services for accepting electronic payments by a variety of payment methods including credit card, bank-based payments such as direct debit, bank transfer, and real-time bank transfer based on online banking. Typically, they use a software as a service model and form a single payment gateway for their clients (merchants) to multiple payment methods.
6
+ [read more](https://en.wikipedia.org/wiki/Payment_service_provider)
7
+
8
+ Requirements
9
+ ------------
10
+ - Python (2.4, 2.7, 3.3, 3.4, 3.5, 3.6, 3.7)
11
+
12
+ Dependencies
13
+ ------------
14
+ - requests
15
+ - six
16
+
17
+ Installation
18
+ ------------
19
+ ```bash
20
+ pip install flittpayments
21
+ ```
22
+ ### Simple start
23
+
24
+ ```python
25
+ from flittpayments import Api, Checkout
26
+ api = Api(merchant_id=1549901,
27
+ secret_key='test')
28
+ checkout = Checkout(api=api)
29
+ data = {
30
+ "currency": "USD",
31
+ "amount": 10000
32
+ }
33
+ url = checkout.url(data).get('checkout_url')
34
+ ```
35
+
36
+ Tests
37
+ -----------------
38
+ First, install `tox` `<http://tox.readthedocs.org/en/latest/>`
39
+
40
+ To run testing:
41
+
42
+ ```bash
43
+ tox
44
+ ```
45
+
46
+ This will run all tests, against all supported Python versions.
@@ -0,0 +1,6 @@
1
+ from flittpayments.configuration import __version__, __api_url__
2
+ from flittpayments.api import Api
3
+ from flittpayments.checkout import Checkout
4
+ from flittpayments.order import Order
5
+ from flittpayments.payment import Payment, Pcidss
6
+ from flittpayments.resources import Resource
@@ -0,0 +1,117 @@
1
+ from __future__ import absolute_import, unicode_literals
2
+ from flittpayments.configuration import (__api_url__, __protocol__, __r_type__)
3
+ from flittpayments import exceptions
4
+
5
+ import os
6
+ import requests
7
+ import logging
8
+ import flittpayments.helpers as helper
9
+ import flittpayments.utils as utils
10
+
11
+ log = logging.getLogger(__name__)
12
+
13
+
14
+ class Api(object):
15
+ user_agent = 'Python SDK'
16
+
17
+ def __init__(self, **kwargs):
18
+ """
19
+ :param kwargs: args
20
+ :arg merchant_id Merchant id numeric
21
+ :arg secret_key Secret key string
22
+ :arg request_type request type allowed json, xml, form
23
+ :arg api_domain api domain
24
+ :arg api_protocol allowed protocols 1.0, 2.0
25
+ """
26
+ self.merchant_id = kwargs.get('merchant_id', '')
27
+ self.secret_key = kwargs.get('secret_key', '')
28
+ self.request_type = kwargs.get('request_type', __r_type__)
29
+ if not self.merchant_id or not self.secret_key:
30
+ self.merchant_id = os.environ.get('CLOUDIPSP_MERCHANT_ID', '')
31
+ self.secret_key = os.environ.get('CLOUDIPSP_SECRETKEY', '')
32
+ domain = kwargs.get('api_domain', 'pay.flitt.com')
33
+ self.api_url = __api_url__.format(api_domain=domain)
34
+ self.api_protocol = kwargs.get('api_protocol', __protocol__)
35
+ if self.api_protocol not in ('1.0', '2.0'):
36
+ raise ValueError('Incorrect protocol version')
37
+ if self.api_protocol == '2.0' and self.request_type != 'json':
38
+ raise ValueError('In protocol \'2.0\' only json allowed')
39
+
40
+ def _headers(self):
41
+ """
42
+ :return: request headers
43
+ """
44
+ return {
45
+ 'User-Agent': self.user_agent,
46
+ 'Content-Type': helper.get_request_type(self.request_type),
47
+ }
48
+
49
+ def _request(self, url, method, data, headers):
50
+ """
51
+ :param url: request url
52
+ :param method: request method, POST default
53
+ :param data: request data
54
+ :param headers: request headers
55
+ :return: api response
56
+ """
57
+ log.debug('Request Type: %s' % self.request_type)
58
+ log.debug('URL: %s' % url)
59
+ log.debug('Data: %s' % str(data))
60
+ log.debug('Headers: %s' % str(headers))
61
+
62
+ response = requests.request(method, url, data=data, headers=headers)
63
+ return self._response(response, response.content.decode('utf-8'))
64
+
65
+ def _response(self, response, content):
66
+ """
67
+ :param response: api response
68
+ :param content: api response body
69
+ :return: if response header 200 or 201 return response data
70
+ """
71
+ status = response.status_code
72
+
73
+ log.debug('Status: %s' % str(status))
74
+ log.debug('Content: %s' % content)
75
+
76
+ if status in (200, 201):
77
+ return content
78
+
79
+ raise exceptions.ServiceError(
80
+ 'Response code is: {status}'.format(status=status))
81
+
82
+ def post(self, url, data=list, headers=None):
83
+ """
84
+ :param url: endpoint api url
85
+ :param data: request data
86
+ :param headers: request headers
87
+ :return: request
88
+ """
89
+ log.debug('Protocol version: %s' % self.request_type)
90
+
91
+ if 'merchant_id' not in data:
92
+ data['merchant_id'] = self.merchant_id
93
+ if 'reservation_data' in data:
94
+ data['reservation_data'] = utils.to_b64(
95
+ data['reservation_data'])
96
+
97
+ if self.api_protocol == '2.0':
98
+ b64_data = utils.to_b64({'order': data})
99
+ data_v2 = {
100
+ 'data': b64_data,
101
+ 'version': self.api_protocol,
102
+ 'signature': helper.get_signature(self.secret_key,
103
+ b64_data,
104
+ self.api_protocol)
105
+ }
106
+ data_string = utils.to_json({'request': data_v2})
107
+ else:
108
+ if 'signature' not in data:
109
+ data['signature'] = helper.get_signature(self.secret_key,
110
+ data,
111
+ self.api_protocol)
112
+ data_string = helper.get_data({'request': data}, self.request_type)
113
+
114
+ return self._request(
115
+ utils.join_url(self.api_url, url), 'POST',
116
+ data=data_string,
117
+ headers=utils.merge_dict(headers, self._headers()))
@@ -0,0 +1,137 @@
1
+ from __future__ import absolute_import, unicode_literals
2
+ from flittpayments.resources import Resource
3
+ from datetime import datetime
4
+
5
+ import flittpayments.helpers as helper
6
+
7
+
8
+ class Checkout(Resource):
9
+ def url(self, data):
10
+ """
11
+ Method to generate checkout url
12
+ :param data: order data
13
+ :return: api response
14
+ """
15
+ path = '/checkout/url/'
16
+ params = self._required(data)
17
+ result = self.api.post(path, data=params, headers=self.__headers__)
18
+
19
+ return self.response(result)
20
+
21
+ def token(self, data):
22
+ """
23
+ Method to generate checkout token
24
+ :param data: order data
25
+ :return: api response
26
+ """
27
+ path = '/checkout/token/'
28
+ params = self._required(data)
29
+ result = self.api.post(path, data=params, headers=self.__headers__)
30
+
31
+ return self.response(result)
32
+
33
+ def verification(self, data):
34
+ """
35
+ Method to generate checkout verification url
36
+ :param data: order data
37
+ :return: api response
38
+ """
39
+ path = '/checkout/url/'
40
+ verification_data = {
41
+ 'verification': 'Y',
42
+ 'verification_type': data.get('verification_type', 'code')
43
+ }
44
+ data.update(verification_data)
45
+ params = self._required(data)
46
+ result = self.api.post(path, data=params, headers=self.__headers__)
47
+
48
+ return self.response(result)
49
+
50
+ def subscription(self, data):
51
+ """
52
+ Method to generate checkout url with calendar
53
+ :param data: order data
54
+ data = {
55
+ "currency": "UAH", -> currency ('UAH', 'GEL', 'USD')
56
+ "amount": 10000, -> amount of the order (int)
57
+ "recurring_data": {
58
+ "every": 1, -> frequency of the recurring order (int)
59
+ "amount": 10000, -> amount of the recurring order (int)
60
+ "period": 'month', -> period of the recurring order ('day', 'month', 'year')
61
+ "start_time": '2020-07-24', -> start date of the recurring order ('YYYY-MM-DD')
62
+ "readonly": 'y', -> possibility to change parameters of the recurring order by user ('y', 'n')
63
+ "state": 'y' -> default state of the recurring order after opening url of the order ('y', 'n')
64
+ }
65
+ }
66
+ :return: api response
67
+ """
68
+ if self.api.api_protocol != '2.0':
69
+ raise Exception('This method allowed only for v2.0')
70
+ path = '/checkout/url/'
71
+ recurring_data = data.get('recurring_data', '')
72
+ subscription_data = {
73
+ 'subscription': 'Y',
74
+ 'recurring_data': {
75
+ 'start_time': recurring_data.get('start_time', ''),
76
+ 'amount': recurring_data.get('amount', ''),
77
+ 'every': recurring_data.get('every', ''),
78
+ 'period': recurring_data.get('period', ''),
79
+ 'readonly': recurring_data.get('readonly', ''),
80
+ 'state': recurring_data.get('state', '')
81
+ }
82
+ }
83
+
84
+ helper.check_data(subscription_data['recurring_data'])
85
+ self._validate_recurring_data(subscription_data['recurring_data'])
86
+ subscription_data.update(data)
87
+ params = self._required(subscription_data)
88
+ result = self.api.post(path, data=params, headers=self.__headers__)
89
+
90
+ return self.response(result)
91
+
92
+ def subscription_stop(self, order_id):
93
+ """
94
+ Stop calendar payments
95
+ """
96
+ if self.api.api_protocol != '2.0':
97
+ raise Exception('This method allowed only for v2.0')
98
+ path = '/subscription/'
99
+ params = {'order_id': order_id, 'action': 'stop'}
100
+ result = self.api.post(path, data=params, headers=self.__headers__)
101
+
102
+ return self.response(result)
103
+
104
+ @staticmethod
105
+ def _validate_recurring_data(data):
106
+ """
107
+ Validation recurring data params
108
+ :param data: recurring data
109
+ :return: exception
110
+ """
111
+ try:
112
+ datetime.strptime(data['start_time'], '%Y-%m-%d')
113
+ except ValueError:
114
+ raise ValueError(
115
+ "Incorrect date format. 'Y-m-d' is allowed")
116
+ if data['period'] not in ('day', 'week', 'month'):
117
+ raise ValueError(
118
+ "Incorrect period. ('day','week','month') is allowed")
119
+
120
+ def _required(self, data):
121
+ """
122
+ Required data to send
123
+ :param data:
124
+ :return: parameters to send
125
+ """
126
+ self.order_id = data.get('order_id') or helper.generate_order_id()
127
+ order_desc = data.get('order_desc') or helper.get_desc(self.order_id)
128
+ params = {
129
+ 'order_id': self.order_id,
130
+ 'order_desc': order_desc,
131
+ 'amount': data.get('amount', ''),
132
+ 'currency': data.get('currency', '')
133
+ }
134
+ helper.check_data(params)
135
+ params.update(data)
136
+
137
+ return params
@@ -0,0 +1,5 @@
1
+ __version__ = '1.0.3'
2
+ __api_url__ = 'https://{api_domain}/api'
3
+ __sign_sep__ = '|'
4
+ __protocol__ = '1.0'
5
+ __r_type__ = 'json'
@@ -0,0 +1,45 @@
1
+ from __future__ import absolute_import, unicode_literals
2
+
3
+
4
+ class RequestError(Exception):
5
+ """
6
+ Some required param is missing.
7
+ """
8
+
9
+ def __init__(self, param):
10
+ self.param = param
11
+
12
+ def __str__(self):
13
+ return "Required parameter '%s' is missing." % self.param
14
+
15
+
16
+ class ResponseError(Exception):
17
+ """
18
+ Handling api response error.
19
+ """
20
+
21
+ def __init__(self, response):
22
+ self.response = response
23
+
24
+ def __str__(self):
25
+ message = ''
26
+ if 'response_status' in self.response:
27
+ message += "Response status is %s." \
28
+ % self.response.get('response_status', '')
29
+ if 'error_message' in self.response:
30
+ message += " Error message: %s." \
31
+ % (self.response.get('error_message'))
32
+ if 'error_code' in self.response:
33
+ message += " Error code: %s." \
34
+ % (self.response.get('error_code'))
35
+ if 'request_id' in self.response:
36
+ message += " Request id: %s." \
37
+ % (self.response.get('request_id'))
38
+ message += " Check parameters"
39
+ return message
40
+
41
+
42
+ class ServiceError(Exception):
43
+ """
44
+ If response code not in (200, 201).
45
+ """
@@ -0,0 +1,104 @@
1
+ from __future__ import absolute_import, unicode_literals
2
+ from hashlib import sha1
3
+ from flittpayments.configuration import __sign_sep__ as sep
4
+ from flittpayments.exceptions import RequestError
5
+
6
+ import flittpayments.utils as utils
7
+ import uuid
8
+
9
+
10
+ def get_data(data, req_type):
11
+ """
12
+ :param data: data to prepare
13
+ :param req_type: request type
14
+ :return: prepared data
15
+ """
16
+ if req_type == 'json':
17
+ return utils.to_json(data)
18
+ if req_type == 'xml':
19
+ return utils.to_xml(data)
20
+ if req_type == 'form':
21
+ return utils.to_form(data.get('request'))
22
+
23
+
24
+ def get_request_type(req_type):
25
+ """
26
+ :param req_type: request type
27
+ :return: post header
28
+ """
29
+ types = {
30
+ 'json': 'application/json; charset=utf-8',
31
+ 'xml': 'application/xml; charset=utf-8',
32
+ 'form': 'application/x-www-form-urlencoded; charset=utf-8'
33
+ }
34
+ return types.get(req_type, types['json'])
35
+
36
+
37
+ def get_signature(secret_key, params, protocol):
38
+ """
39
+ :param secret_key: merchant secret
40
+ :param params: post params
41
+ :param protocol: api protocol version
42
+ :return: signature string
43
+ """
44
+ if protocol == '2.0':
45
+ str_sign = sep.join([secret_key, params])
46
+ calc_sign = sha1(str_sign.encode('utf-8')).hexdigest()
47
+ return calc_sign
48
+ else:
49
+ data = [secret_key]
50
+ data.extend([str(params[key]) for key in sorted(iter(params.keys()))
51
+ if params[key] != '' and not params[key] is None])
52
+ return sha1(sep.join(data).encode('utf-8')).hexdigest()
53
+
54
+
55
+ def get_desc(order_id):
56
+ """
57
+ :param order_id: order id
58
+ :return: description string
59
+ """
60
+ return 'Pay for order #: %s' % order_id
61
+
62
+
63
+ def generate_order_id():
64
+ """
65
+ :return: unic order id
66
+ """
67
+ return str(uuid.uuid4())
68
+
69
+
70
+ def check_data(data):
71
+ """
72
+ :param data: required data
73
+ :return: checking required data not empty
74
+ """
75
+ for key, value in data.items():
76
+ if value == '' or None:
77
+ raise RequestError(key)
78
+ if key == 'amount':
79
+ try:
80
+ int(value)
81
+ except ValueError:
82
+ raise ValueError('Amount must numeric')
83
+
84
+
85
+ def is_valid(data, secret_key, protocol):
86
+ if 'signature' in data:
87
+ result_signature = data['signature']
88
+ del data['signature']
89
+ else:
90
+ raise ValueError('Incorrect data')
91
+ if 'response_signature_string' in data:
92
+ del data['response_signature_string']
93
+ signature = get_signature(secret_key=secret_key,
94
+ params=data,
95
+ protocol=protocol)
96
+ return result_signature == signature
97
+
98
+
99
+ def is_approved(data, secret_key, protocol):
100
+ if 'order_status' not in data:
101
+ raise ValueError('Incorrect data')
102
+ if not is_valid(data, secret_key, protocol):
103
+ raise Exception('Payment invalid')
104
+ return data.get('order_status') == 'approved'
@@ -0,0 +1,111 @@
1
+ from __future__ import absolute_import, unicode_literals
2
+ from flittpayments.resources import Resource
3
+
4
+ import flittpayments.utils as utils
5
+ import flittpayments.helpers as helper
6
+
7
+
8
+ class Order(Resource):
9
+ def settlement(self, data):
10
+ """
11
+ Method for create split order
12
+ :param data: split order data
13
+ :return: api response
14
+ """
15
+ if self.api.api_protocol != '2.0':
16
+ raise Exception('This method allowed only for v2.0')
17
+ path = '/settlement/'
18
+ params = {
19
+ 'order_type': data.get('order_type', 'settlement'),
20
+ 'order_id': data.get('order_id') or helper.generate_order_id(),
21
+ 'operation_id': data.get('operation_id', ''),
22
+ 'receiver': data.get('receiver', [])
23
+ }
24
+ helper.check_data(params)
25
+ params.update(data)
26
+ result = self.api.post(path, data=params, headers=self.__headers__)
27
+
28
+ return self.response(result)
29
+
30
+ def capture(self, data):
31
+ """
32
+ Method for capturing order
33
+ :param data: capture order data
34
+ :return: api response
35
+ """
36
+ path = '/capture/order_id/'
37
+ params = {
38
+ 'order_id': data.get('order_id', ''),
39
+ 'amount': data.get('amount', ''),
40
+ 'currency': data.get('currency', '')
41
+ }
42
+ helper.check_data(params)
43
+ params.update(data)
44
+ result = self.api.post(path, data=params, headers=self.__headers__)
45
+ return self.response(result)
46
+
47
+ def reverse(self, data):
48
+ """
49
+ Method to reverse order
50
+ :param data: reverse order data
51
+ :return: api response
52
+ """
53
+ path = '/reverse/order_id/'
54
+ params = {
55
+ 'order_id': data.get('order_id', ''),
56
+ 'amount': data.get('amount', ''),
57
+ 'currency': data.get('currency', '')
58
+ }
59
+ helper.check_data(params)
60
+ params.update(data)
61
+ result = self.api.post(path, data=params, headers=self.__headers__)
62
+ return self.response(result)
63
+
64
+ def status(self, data):
65
+ """
66
+ Method for checking order status
67
+ :param data: order data
68
+ :return: api response
69
+ """
70
+ path = '/status/order_id/'
71
+ params = {
72
+ 'order_id': data.get('order_id', '')
73
+ }
74
+ helper.check_data(params)
75
+ params.update(data)
76
+ result = self.api.post(path, data=params, headers=self.__headers__)
77
+ return self.response(result)
78
+
79
+ def transaction_list(self, data):
80
+ """
81
+ Method for getting order transaction list
82
+ :param data: order data
83
+ :return: api response
84
+ """
85
+ path = '/transaction_list/'
86
+ params = {
87
+ 'order_id': data.get('order_id', '')
88
+ }
89
+ helper.check_data(params)
90
+ params.update(data)
91
+ """
92
+ only json allowed all other methods returns 500 error
93
+ """
94
+ self.api.request_type = 'json'
95
+ result = self.api.post(path, data=params, headers=self.__headers__)
96
+ return self.response(result)
97
+
98
+ def atol_logs(self, data):
99
+ """
100
+ Method for getting order atol logs
101
+ :param data: order data
102
+ :return: api response
103
+ """
104
+ path = '/get_atol_logs/'
105
+ params = {
106
+ 'order_id': data.get('order_id', '')
107
+ }
108
+ helper.check_data(params)
109
+ params.update(data)
110
+ result = self.api.post(path, data=params, headers=self.__headers__)
111
+ return utils.from_json(result).get('response')
@@ -0,0 +1,126 @@
1
+ from __future__ import absolute_import, unicode_literals
2
+ from flittpayments.resources import Resource
3
+ from datetime import datetime
4
+
5
+ import flittpayments.helpers as helper
6
+
7
+
8
+ class Pcidss(Resource):
9
+ def step_one(self, data):
10
+ """
11
+ Accept purchase Pcidss step one
12
+ :param data: order data
13
+ :return: payment result or step two data
14
+ """
15
+ path = '/3dsecure_step1/'
16
+ self.order_id = data.get('order_id') or helper.generate_order_id()
17
+ order_desc = data.get('order_desc') or helper.get_desc(self.order_id)
18
+ params = {
19
+ 'order_id': self.order_id,
20
+ 'order_desc': order_desc,
21
+ 'currency': data.get('currency', ''),
22
+ 'amount': data.get('amount', ''),
23
+ 'card_number': data.get('card_number', ''),
24
+ 'cvv2': data.get('cvv2', ''),
25
+ 'expiry_date': data.get('expiry_date', '')
26
+ }
27
+ helper.check_data(params)
28
+ params.update(data)
29
+ result = self.api.post(path, data=params, headers=self.__headers__)
30
+ return self.response(result)
31
+
32
+ def step_two(self, data):
33
+ """
34
+ Accept purchase Pcidss step two
35
+ :param data: order data
36
+ :return: payment result
37
+ """
38
+ path = '/3dsecure_step2/'
39
+ params = {
40
+ 'order_id': data.get('order_id', ''),
41
+ 'pares': data.get('pares', ''),
42
+ 'md': data.get('md', '')
43
+ }
44
+ helper.check_data(params)
45
+ params.update(data)
46
+ result = self.api.post(path, data=params, headers=self.__headers__)
47
+ return self.response(result)
48
+
49
+
50
+ class Payment(Resource):
51
+ def p2pcredit(self, data):
52
+ """
53
+ Method P2P card credit
54
+ :param data: order data
55
+ :return: api response
56
+ """
57
+ path = '/p2pcredit/'
58
+ self.order_id = data.get('order_id') or helper.generate_order_id()
59
+ order_desc = data.get('order_desc') or helper.get_desc(self.order_id)
60
+ params = {
61
+ 'order_id': self.order_id,
62
+ 'order_desc': order_desc,
63
+ 'amount': data.get('amount', ''),
64
+ 'currency': data.get('currency', '')
65
+ }
66
+ helper.check_data(params)
67
+ params.update(data)
68
+ result = self.api.post(path, data=params, headers=self.__headers__)
69
+ return self.response(result)
70
+
71
+ def reports(self, data):
72
+ """
73
+ Method to get payment reports from date range
74
+ :param data: date range
75
+ :return: api response
76
+ """
77
+ path = '/reports/'
78
+ params = {
79
+ 'date_from': data.get('date_from', ''),
80
+ 'date_to': data.get('date_to', '')
81
+ }
82
+ helper.check_data(params)
83
+ """
84
+ from api only one response if data invalid "General Decline"
85
+ """
86
+ self._validate_reports_date(params)
87
+ params.update(data)
88
+ result = self.api.post(path, data=params, headers=self.__headers__)
89
+ return self.response(result)
90
+
91
+ def recurring(self, data):
92
+ """
93
+ Method for recurring payment
94
+ :param data: order data
95
+ :return: api response
96
+ """
97
+ path = '/recurring/'
98
+ self.order_id = data.get('order_id') or helper.generate_order_id()
99
+ order_desc = data.get('order_desc') or helper.get_desc(self.order_id)
100
+ params = {
101
+ 'order_id': self.order_id,
102
+ 'order_desc': order_desc,
103
+ 'amount': data.get('amount', ''),
104
+ 'currency': data.get('currency', ''),
105
+ 'rectoken': data.get('rectoken', '')
106
+ }
107
+ helper.check_data(params)
108
+ params.update(data)
109
+ result = self.api.post(path, data=params, headers=self.__headers__)
110
+ return self.response(result)
111
+
112
+ @staticmethod
113
+ def _validate_reports_date(date):
114
+ """
115
+ Validating date range
116
+ :param date: date
117
+ """
118
+ try:
119
+ date_from = datetime.strptime(
120
+ date['date_from'], '%d.%m.%Y %H:%M:%S')
121
+ date_to = datetime.strptime(
122
+ date['date_to'], '%d.%m.%Y %H:%M:%S')
123
+ except ValueError:
124
+ raise ValueError("Incorrect date format.")
125
+ if date_from > date_to:
126
+ raise ValueError("`date_from` can't be greater than `date_to`")
@@ -0,0 +1,67 @@
1
+ from __future__ import absolute_import, unicode_literals
2
+ from flittpayments import utils
3
+ from flittpayments import exceptions
4
+
5
+
6
+ class Resource(object):
7
+ def __init__(self, api=None, headers=None):
8
+ self.__dict__['api'] = api
9
+
10
+ super(Resource, self).__setattr__('__data__', {})
11
+ super(Resource, self).__setattr__('__headers__', headers or {})
12
+ super(Resource, self).__setattr__('order_id', None)
13
+
14
+ def __str__(self):
15
+ return self.__data__.__str__()
16
+
17
+ def __repr__(self):
18
+ return self.__data__.__str__()
19
+
20
+ def __getattr__(self, name):
21
+ try:
22
+ return self.__data__[name]
23
+ except KeyError:
24
+ return super(Resource, self).__getattribute__(name)
25
+
26
+ def __setattr__(self, name, value):
27
+ try:
28
+ super(Resource, self).__setattr__(name, value)
29
+ except AttributeError:
30
+ self.__data__[name] = self.convert(name, value)
31
+
32
+ def __contains__(self, name):
33
+ return name in self.__data__
34
+
35
+ def get_url(self):
36
+ if 'checkout_url' in self.__data__:
37
+ return self.__getattr__('checkout_url')
38
+
39
+ def response(self, response):
40
+ """
41
+ :param response: api response
42
+ :return: result
43
+ """
44
+ try:
45
+ result = None
46
+ if self.api.request_type == 'json':
47
+ result = utils.from_json(response).get('response', '')
48
+ if self.api.request_type == 'xml':
49
+ result = utils.from_xml(response).get('response', '')
50
+ if self.api.request_type == 'form':
51
+ result = utils.from_form(response)
52
+ return self._get_result(result)
53
+ except KeyError:
54
+ raise ValueError('Undefined format error.')
55
+
56
+ def _get_result(self, result):
57
+ """
58
+ in some api param response_status not exist...
59
+ :param result: api result
60
+ :return: exception
61
+ """
62
+ if 'error_message' in result:
63
+ raise exceptions.ResponseError(result)
64
+ if 'data' in result and self.api.api_protocol == '2.0':
65
+ result['data'] = utils.from_b64(result['data'])
66
+ self.__data__ = result
67
+ return result
@@ -0,0 +1,163 @@
1
+ from __future__ import absolute_import, unicode_literals
2
+ from collections import OrderedDict
3
+
4
+ import re
5
+ import json
6
+ import base64
7
+ import six.moves.urllib as urllib
8
+ import xml.etree.cElementTree as ElementTree
9
+
10
+
11
+ def to_b64(data):
12
+ """
13
+ Encoding data string base64 algorithm
14
+ """
15
+ return base64.b64encode(json.dumps(data).encode('utf-8')).decode('utf-8')
16
+
17
+
18
+ def from_b64(data):
19
+ """
20
+ Encoding data string base64 algorithm
21
+ """
22
+ return base64.b64decode(json.dumps(data).encode('utf-8')).decode('utf-8')
23
+
24
+
25
+ def to_xml(data, start='<?xml version="1.0" encoding="UTF-8"?>'):
26
+ """
27
+ :param data: params to convert to xml
28
+ :param start: start xml string
29
+ :return: xml string
30
+ """
31
+ data = OrderedDict(sorted(data.items()))
32
+ return start + _data2xml(data)
33
+
34
+
35
+ def to_json(data):
36
+ """
37
+ to json string
38
+ :param data: params to convert to xml
39
+ :return: json string
40
+ """
41
+ return json.dumps(data)
42
+
43
+
44
+ def to_form(data):
45
+ """
46
+ to form string
47
+ :param data: params to convert to form data
48
+ :return: encoded url string
49
+ """
50
+ data = OrderedDict(sorted(data.items()))
51
+ return urllib.parse.urlencode(data)
52
+
53
+
54
+ def merge_dict(x, y):
55
+ """
56
+ :param x: firs dict
57
+ :param y: second dict
58
+ :return: merged dict
59
+ """
60
+ z = x.copy()
61
+ z.update(y)
62
+ return z
63
+
64
+
65
+ def join_url(url, *paths):
66
+ """
67
+ :param url: api url
68
+ :param paths: endpoint
69
+ :return: full url
70
+ """
71
+ for path in paths:
72
+ url = re.sub(r'/?$', re.sub(r'^/?', '/', path), url)
73
+ return url
74
+
75
+
76
+ def from_json(json_string):
77
+ """
78
+ :param json_string: json data string to encode
79
+ :return: data dict
80
+ """
81
+ return json.loads(json_string)
82
+
83
+
84
+ def from_form(form_string):
85
+ """
86
+ :param form_string: form data string to encode
87
+ :return: data dict
88
+ """
89
+ return dict(urllib.parse.parse_qsl(form_string))
90
+
91
+
92
+ def from_xml(xml):
93
+ """
94
+ :param xml: xml string to encode
95
+ :return: data dict
96
+ """
97
+ element = ElementTree.fromstring(xml)
98
+ return _xml_to_dict(element.tag, _parse(element), element.attrib)
99
+
100
+
101
+ def _data2xml(d):
102
+ result_list = list()
103
+
104
+ if isinstance(d, list):
105
+ for sub_elem in d:
106
+ result_list.append(_data2xml(sub_elem))
107
+
108
+ return ''.join(d)
109
+
110
+ if isinstance(d, dict):
111
+ for tag_name, sub_obj in d.items():
112
+ result_list.append("<%s>" % tag_name)
113
+ result_list.append(_data2xml(sub_obj))
114
+ result_list.append("</%s>" % tag_name)
115
+
116
+ return ''.join(result_list)
117
+
118
+ return "%s" % d
119
+
120
+
121
+ def _parse(node):
122
+ tree = {}
123
+ for c in node.getchildren():
124
+ c_tag = c.tag
125
+ c_attr = c.attrib
126
+ ctext = c.text.strip() if c.text is not None else ''
127
+ c_tree = _parse(c)
128
+
129
+ if not c_tree:
130
+ c_dict = _xml_to_dict(c_tag, ctext, c_attr)
131
+ else:
132
+ c_dict = _xml_to_dict(c_tag, c_tree, c_attr)
133
+ if c_tag not in tree:
134
+ tree.update(c_dict)
135
+ continue
136
+ atag = '@' + c_tag
137
+ atree = tree[c_tag]
138
+ if not isinstance(atree, list):
139
+ if not isinstance(atree, dict):
140
+ atree = {}
141
+ if atag in tree:
142
+ atree['#' + c_tag] = tree[atag]
143
+ del tree[atag]
144
+ tree[c_tag] = [atree]
145
+
146
+ if c_attr:
147
+ c_tree['#' + c_tag] = c_attr
148
+
149
+ tree[c_tag].append(c_tree)
150
+ return tree
151
+
152
+
153
+ def _xml_to_dict(tag, value, attr=None):
154
+ ret = {tag: value}
155
+ if attr:
156
+ atag = '@' + tag
157
+ aattr = {}
158
+ for k, v in attr.items():
159
+ aattr[k] = v
160
+ ret[atag] = aattr
161
+ del atag
162
+ del aattr
163
+ return ret
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.2
2
+ Name: flittpayments
3
+ Version: 1.0.3
4
+ Summary: Python SDK for Flitt clients.
5
+ Home-page: https://github.com/flittpayments/python/
6
+ Author: Dmitriy Miroshnikov
7
+ License: MIT
8
+ Classifier: Environment :: Web Environment
9
+ Classifier: Natural Language :: English
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 2.7
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.4
16
+ Classifier: Programming Language :: Python :: 3.6
17
+ Classifier: Programming Language :: Python :: 3.7
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ License-File: LICENSE
20
+ Requires-Dist: requests
21
+ Requires-Dist: six
22
+ Dynamic: author
23
+ Dynamic: classifier
24
+ Dynamic: description
25
+ Dynamic: home-page
26
+ Dynamic: license
27
+ Dynamic: requires-dist
28
+ Dynamic: summary
29
+
30
+
31
+ Flitt python sdk.
32
+ Docs - https://https://docs.flitt.com/
33
+ README - https://https://github.com/flittpayments/python/blob/master/README.md
34
+
@@ -0,0 +1,19 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ flittpayments/__init__.py
5
+ flittpayments/api.py
6
+ flittpayments/checkout.py
7
+ flittpayments/configuration.py
8
+ flittpayments/exceptions.py
9
+ flittpayments/helpers.py
10
+ flittpayments/order.py
11
+ flittpayments/payment.py
12
+ flittpayments/resources.py
13
+ flittpayments/utils.py
14
+ flittpayments.egg-info/PKG-INFO
15
+ flittpayments.egg-info/SOURCES.txt
16
+ flittpayments.egg-info/dependency_links.txt
17
+ flittpayments.egg-info/requires.txt
18
+ flittpayments.egg-info/top_level.txt
19
+ tests/tests_helper.py
@@ -0,0 +1,2 @@
1
+ requests
2
+ six
@@ -0,0 +1 @@
1
+ flittpayments
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,38 @@
1
+ from setuptools import find_packages, setup
2
+
3
+ from flittpayments.configuration import __version__
4
+
5
+ desc = """
6
+ Flitt python sdk.
7
+ Docs - https://https://docs.flitt.com/
8
+ README - https://https://github.com/flittpayments/python/blob/master/README.md
9
+ """
10
+
11
+ requires_list = [
12
+ 'requests',
13
+ 'six'
14
+ ]
15
+
16
+ setup(
17
+ name='flittpayments',
18
+ version=__version__,
19
+ url='https://github.com/flittpayments/python/',
20
+ license='MIT',
21
+ description='Python SDK for Flitt clients.',
22
+ long_description=desc,
23
+ author='Dmitriy Miroshnikov',
24
+ packages=find_packages(where='.', exclude=('tests*',)),
25
+ install_requires=requires_list,
26
+ classifiers=[
27
+ 'Environment :: Web Environment',
28
+ 'Natural Language :: English',
29
+ 'License :: OSI Approved :: MIT License',
30
+ 'Operating System :: OS Independent',
31
+ 'Programming Language :: Python',
32
+ 'Programming Language :: Python :: 2.7',
33
+ 'Programming Language :: Python :: 3',
34
+ 'Programming Language :: Python :: 3.4',
35
+ 'Programming Language :: Python :: 3.6',
36
+ 'Programming Language :: Python :: 3.7',
37
+ 'Programming Language :: Python :: 3.8',
38
+ ])
@@ -0,0 +1,37 @@
1
+ import os
2
+ import json
3
+
4
+ from unittest import TestCase
5
+ from flittpayments import Api, Pcidss, helpers
6
+
7
+
8
+ class TestCase(TestCase):
9
+ def get_api(self):
10
+ self.data = self.get_dummy_data()
11
+ return Api(merchant_id=self.data['merchant']['id'],
12
+ secret_key=self.data['merchant']['secret'])
13
+
14
+ def get_dummy_data(self):
15
+ dummy_data = os.path.join(os.path.dirname(__file__),
16
+ 'data',
17
+ 'test_data.json')
18
+ with open(dummy_data) as f:
19
+ self.data = json.load(f)
20
+ return self.data
21
+
22
+ def create_order(self):
23
+ pcidss = Pcidss(api=self.get_api())
24
+ params = {
25
+ "preauth": "Y",
26
+ "required_rectoken": "Y"
27
+ }
28
+ params.update(self.data['payment_pcidss_non3ds'])
29
+ return pcidss.step_one(params)
30
+
31
+ def test_validate_order(self):
32
+ payment = self.create_order()
33
+ is_valid = helpers.is_valid(data=payment,
34
+ secret_key=self.data['merchant']['secret'],
35
+ protocol='1.0')
36
+ self.assertEqual(payment.get('response_status'), 'success')
37
+ self.assertEqual(is_valid, True)