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.
- flittpayments-1.0.3/LICENSE +21 -0
- flittpayments-1.0.3/PKG-INFO +34 -0
- flittpayments-1.0.3/README.md +46 -0
- flittpayments-1.0.3/flittpayments/__init__.py +6 -0
- flittpayments-1.0.3/flittpayments/api.py +117 -0
- flittpayments-1.0.3/flittpayments/checkout.py +137 -0
- flittpayments-1.0.3/flittpayments/configuration.py +5 -0
- flittpayments-1.0.3/flittpayments/exceptions.py +45 -0
- flittpayments-1.0.3/flittpayments/helpers.py +104 -0
- flittpayments-1.0.3/flittpayments/order.py +111 -0
- flittpayments-1.0.3/flittpayments/payment.py +126 -0
- flittpayments-1.0.3/flittpayments/resources.py +67 -0
- flittpayments-1.0.3/flittpayments/utils.py +163 -0
- flittpayments-1.0.3/flittpayments.egg-info/PKG-INFO +34 -0
- flittpayments-1.0.3/flittpayments.egg-info/SOURCES.txt +19 -0
- flittpayments-1.0.3/flittpayments.egg-info/dependency_links.txt +1 -0
- flittpayments-1.0.3/flittpayments.egg-info/requires.txt +2 -0
- flittpayments-1.0.3/flittpayments.egg-info/top_level.txt +1 -0
- flittpayments-1.0.3/setup.cfg +4 -0
- flittpayments-1.0.3/setup.py +38 -0
- flittpayments-1.0.3/tests/tests_helper.py +37 -0
|
@@ -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,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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
flittpayments
|
|
@@ -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)
|