tappay 0.5.0__py3-none-any.whl

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.
tappay/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+
2
+ __version__ = "0.5.0"
3
+
4
+ from tappay.client import Client
5
+ from tappay.models import Models
6
+ from tappay.exceptions import Exceptions, Error, ClientError, ServerError, AuthenticationError
7
+
8
+ __all__ = [
9
+ "Client",
10
+ "Models",
11
+ "Exceptions",
12
+ "Error",
13
+ "ClientError",
14
+ "ServerError",
15
+ "AuthenticationError",
16
+ ]
tappay/client.py ADDED
@@ -0,0 +1,303 @@
1
+ import logging
2
+ import os
3
+ from platform import python_version
4
+ from typing import Optional, Dict, Any, Union
5
+
6
+ import requests
7
+
8
+ from tappay.models import Models
9
+ from tappay.exceptions import Exceptions
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # We need to access __version__ from somewhere, commonly from a package level or hardcoded here then imported.
14
+ # For now I will hardcode it here or pass it in.
15
+ # Better yet, I will define __version__ in __init__.py and import it here? No, circular import.
16
+ # I will put __version__ in a separate file or keep it in __init__ and pass it to Client if needed,
17
+ # or just duplicate/move it.
18
+ # Let's define it in client.py for now to avoid circular dependency if __init__ imports client.
19
+ # Actually, the original code used it in User-Agent.
20
+ VERSION = "0.5.0"
21
+
22
+
23
+ class Client:
24
+ """Client for interacting with the TapPay API."""
25
+
26
+ def __init__(
27
+ self,
28
+ is_sandbox: bool,
29
+ partner_key: Optional[str] = None,
30
+ merchant_id: Optional[str] = None,
31
+ app_name: Optional[str] = None,
32
+ app_version: Optional[str] = None,
33
+ ):
34
+ """
35
+ Create a Client object to start making calls to TapPay APIs.
36
+
37
+ :param bool is_sandbox: Define runtime environment (sandbox or production)
38
+ :param str partner_key: Your TapPay partner key (optional)
39
+ :param str merchant_id: Your TapPay merchant ID (optional)
40
+ :param str app_name: This optional value is added to the user-agent header
41
+ :param str app_version: This optional value is added to the user-agent header
42
+ """
43
+ if not isinstance(is_sandbox, bool):
44
+ raise TypeError(
45
+ f"expected bool for parameter `is_sandbox`, {type(is_sandbox)} found"
46
+ )
47
+
48
+ self.partner_key = partner_key or os.environ.get("TAPPAY_PARTNER_KEY")
49
+ self.merchant_id = merchant_id or os.environ.get("TAPPAY_MERCHANT_ID")
50
+
51
+ if self.partner_key is None:
52
+ raise ValueError("Missing required value for `partner_key`")
53
+ if self.merchant_id is None:
54
+ raise ValueError("Missing required value for `merchant_id`")
55
+
56
+ subdomain = "sandbox" if is_sandbox else "prod"
57
+ self.api_host = f"{subdomain}.tappaysdk.com"
58
+
59
+ user_agent = f"tappay-python/{VERSION} python/{python_version()}"
60
+
61
+ if app_name and app_version:
62
+ user_agent += f" {app_name}/{app_version}"
63
+
64
+ self.headers = {
65
+ "Content-Type": "application/json",
66
+ "User-Agent": user_agent,
67
+ "x-api-key": self.partner_key,
68
+ }
69
+
70
+ def pay_by_prime(
71
+ self,
72
+ prime: str,
73
+ amount: int,
74
+ details: str,
75
+ card_holder_data: Models.CardHolderData,
76
+ **kwargs: Any,
77
+ ) -> Dict[str, Any]:
78
+ """
79
+ Make a payment using "prime" obtained from TapPay frontend SDK
80
+ Ref: https://docs.tappaysdk.com/tutorial/zh/back.html#pay-by-prime-api
81
+ """
82
+ if not isinstance(card_holder_data, Models.CardHolderData):
83
+ raise TypeError(
84
+ f"expected `CardHolderData` type for parameter `card_holder_data`, {type(card_holder_data)} found"
85
+ )
86
+
87
+ params = {
88
+ "prime": prime,
89
+ "amount": amount,
90
+ "currency": Models.Currencies.TWD,
91
+ "details": details,
92
+ "cardholder": card_holder_data.to_dict(),
93
+ }
94
+
95
+ if kwargs:
96
+ params.update(**kwargs)
97
+
98
+ return self.__post_with_partner_key_and_merchant_id(
99
+ "/tpc/payment/pay-by-prime", params
100
+ )
101
+
102
+ def pay_by_token(
103
+ self,
104
+ card_key: str,
105
+ card_token: str,
106
+ amount: int,
107
+ details: str,
108
+ **kwargs: Any,
109
+ ) -> Dict[str, Any]:
110
+ """
111
+ Make a payment using previously obtained card secrets (key & token)
112
+ Ref: https://docs.tappaysdk.com/tutorial/zh/back.html#pay-by-card-token-api
113
+ """
114
+ params = {
115
+ "card_key": card_key,
116
+ "card_token": card_token,
117
+ "amount": amount,
118
+ "currency": Models.Currencies.TWD,
119
+ "details": details,
120
+ }
121
+
122
+ if kwargs:
123
+ params.update(**kwargs)
124
+
125
+ return self.__post_with_partner_key_and_merchant_id(
126
+ "/tpc/payment/pay-by-token", params
127
+ )
128
+
129
+ def refund(
130
+ self, rec_trade_id: str, amount: int, **kwargs: Any
131
+ ) -> Dict[str, Any]:
132
+ """
133
+ Refund a payment
134
+ Ref: https://docs.tappaysdk.com/tutorial/zh/back.html#refund-api
135
+ """
136
+ params = {
137
+ "rec_trade_id": rec_trade_id,
138
+ "amount": amount,
139
+ }
140
+
141
+ if kwargs:
142
+ params.update(**kwargs)
143
+
144
+ return self.__post_with_partner_key("/tpc/transaction/refund", params)
145
+
146
+ def get_records(
147
+ self,
148
+ filters_dict: Dict[str, Any],
149
+ page: int = 0,
150
+ records_per_page: int = 50,
151
+ order_by_dict: Optional[Dict[str, Any]] = None,
152
+ ) -> Dict[str, Any]:
153
+ """
154
+ Query historical records
155
+ Ref: https://docs.tappaysdk.com/tutorial/zh/back.html#record-api
156
+ """
157
+ params = {
158
+ "records_per_page": records_per_page,
159
+ "page": page,
160
+ "filters": filters_dict,
161
+ }
162
+
163
+ if order_by_dict:
164
+ params["order_by"] = order_by_dict
165
+
166
+ return self.__post_with_partner_key("/tpc/transaction/query", params)
167
+
168
+ def capture_today(self, rec_trade_id: str) -> Dict[str, Any]:
169
+ """
170
+ Capture specific payment record
171
+ Ref: https://docs.tappaysdk.com/tutorial/zh/advanced.html#cap-today-api
172
+ """
173
+ params = {
174
+ "rec_trade_id": rec_trade_id,
175
+ }
176
+
177
+ return self.__post_with_partner_key("/tpc/transaction/cap", params)
178
+
179
+ def cancel_capture(self, rec_trade_id: str) -> Dict[str, Any]:
180
+ """
181
+ Cancel a specific capture
182
+ Ref: https://docs.tappaysdk.com/tutorial/zh/advanced.html#cap-cancel-api
183
+ """
184
+ params = {
185
+ "rec_trade_id": rec_trade_id,
186
+ }
187
+
188
+ return self.__post_with_partner_key("/tpc/transaction/cap/cancel", params)
189
+
190
+ def get_trade_history(self, rec_trade_id: str) -> Dict[str, Any]:
191
+ """
192
+ Get record and status of a specific transaction
193
+ Ref: https://docs.tappaysdk.com/tutorial/zh/advanced.html#trade-history-api
194
+ """
195
+ params = {
196
+ "rec_trade_id": rec_trade_id,
197
+ }
198
+
199
+ return self.__post_with_partner_key(
200
+ "/tpc/transaction/trade-history", params
201
+ )
202
+
203
+ def bind_card(
204
+ self,
205
+ prime: str,
206
+ card_holder_data: Models.CardHolderData,
207
+ **kwargs: Any,
208
+ ) -> Dict[str, Any]:
209
+ """
210
+ Bind new credit card
211
+ Ref: https://docs.tappaysdk.com/tutorial/zh/advanced.html#bind-card-api
212
+ """
213
+ if not isinstance(card_holder_data, Models.CardHolderData):
214
+ raise TypeError(
215
+ f"expected `CardHolderData` type for parameter `card_holder_data`, {type(card_holder_data)} found"
216
+ )
217
+
218
+ params = {
219
+ "prime": prime,
220
+ "currency": Models.Currencies.TWD,
221
+ "cardholder": card_holder_data.to_dict(),
222
+ }
223
+
224
+ if kwargs:
225
+ params.update(**kwargs)
226
+
227
+ return self.__post_with_partner_key_and_merchant_id(
228
+ "/tpc/card/bind", params
229
+ )
230
+
231
+ def remove_card(self, card_key: str, card_token: str) -> Dict[str, Any]:
232
+ """
233
+ Remove bound credit card
234
+ Ref: https://docs.tappaysdk.com/tutorial/zh/advanced.html#remove-card-api
235
+ """
236
+ params = {
237
+ "card_key": card_key,
238
+ "card_token": card_token,
239
+ }
240
+
241
+ return self.__post_with_partner_key("/tpc/card/remove", params)
242
+
243
+ def cancel_refund(
244
+ self, rec_trade_id: str, **kwargs: Any
245
+ ) -> Dict[str, Any]:
246
+ """
247
+ Cancel a single refund
248
+ Ref: https://docs.tappaysdk.com/tutorial/zh/advanced.html#refund-cancel-api
249
+ """
250
+ params = {
251
+ "rec_trade_id": rec_trade_id,
252
+ }
253
+
254
+ if kwargs:
255
+ params.update(**kwargs)
256
+
257
+ return self.__post_with_partner_key(
258
+ "/tpc/transaction/refund/cancel", params
259
+ )
260
+
261
+ def __post_with_partner_key(
262
+ self, request_uri: str, params: Dict[str, Any]
263
+ ) -> Dict[str, Any]:
264
+ params = dict(params, partner_key=self.partner_key)
265
+ return self.__post(request_uri, params)
266
+
267
+ def __post_with_partner_key_and_merchant_id(
268
+ self, request_uri: str, params: Dict[str, Any]
269
+ ) -> Dict[str, Any]:
270
+ params = dict(params, merchant_id=self.merchant_id)
271
+ return self.__post_with_partner_key(request_uri, params)
272
+
273
+ def __post(self, request_uri: str, params: Dict[str, Any]) -> Dict[str, Any]:
274
+ uri = f"https://{self.api_host}{request_uri}"
275
+ params = dict(params)
276
+
277
+ logger.debug(f"POST to: {uri}")
278
+ logger.debug(f"POST headers: {self.headers}")
279
+ logger.debug(f"POST params: {params}")
280
+
281
+ response = requests.post(uri, json=params, headers=self.headers)
282
+ return self.__parse(response)
283
+
284
+ def __parse(self, response: requests.Response) -> Optional[Dict[str, Any]]:
285
+ logger.debug(f"response status: {response.status_code}")
286
+ logger.debug(f"response content: {response.content}")
287
+
288
+ if response.status_code == 401:
289
+ raise Exceptions.AuthenticationError
290
+ elif response.status_code == 204:
291
+ return None
292
+ elif 200 <= response.status_code < 300:
293
+ return response.json()
294
+ elif 400 <= response.status_code < 500:
295
+ message = f"{response.status_code} response from {self.api_host}"
296
+ raise Exceptions.ClientError(message)
297
+ elif 500 <= response.status_code < 600:
298
+ message = f"{response.status_code} response from {self.api_host}"
299
+ raise Exceptions.ServerError(message)
300
+
301
+ # Fallback for unexpected status codes
302
+ message = f"Unexpected status code {response.status_code} from {self.api_host}"
303
+ raise Exceptions.ServerError(message)
tappay/exceptions.py ADDED
@@ -0,0 +1,19 @@
1
+
2
+ class Error(Exception):
3
+ """Base exception for all TapPay errors."""
4
+ pass
5
+
6
+
7
+ class ClientError(Error):
8
+ """Error raised when the client sends an invalid request."""
9
+ pass
10
+
11
+
12
+ class ServerError(Error):
13
+ """Error raised when the TapPay server encounters an issue."""
14
+ pass
15
+
16
+
17
+ class AuthenticationError(ClientError):
18
+ """Error raised when authentication fails."""
19
+ pass
tappay/models.py ADDED
@@ -0,0 +1,50 @@
1
+ from typing import Optional, Dict, Any
2
+
3
+ class Models:
4
+ """Namespace for TapPay models."""
5
+
6
+ class Currencies:
7
+ """Currency constants."""
8
+ TWD = "TWD"
9
+
10
+ class CardHolderData:
11
+ """Card holder data model."""
12
+
13
+ phone_number: Optional[str] = None
14
+ name: Optional[str] = None
15
+ email: Optional[str] = None
16
+ zip_code: Optional[str] = None
17
+ address: Optional[str] = None
18
+ national_id: Optional[str] = None
19
+
20
+ def __init__(
21
+ self,
22
+ phone_number: str,
23
+ name: str,
24
+ email: str,
25
+ zip_code: Optional[str] = None,
26
+ address: Optional[str] = None,
27
+ national_id: Optional[str] = None,
28
+ ):
29
+ self.phone_number = phone_number
30
+ self.name = name
31
+ self.email = email
32
+ self.zip_code = zip_code
33
+ self.address = address
34
+ self.national_id = national_id
35
+
36
+ def to_dict(self) -> Dict[str, Any]:
37
+ result_dict = {
38
+ "phone_number": self.phone_number,
39
+ "name": self.name,
40
+ "email": self.email,
41
+ }
42
+
43
+ if self.zip_code:
44
+ result_dict["zip_code"] = self.zip_code
45
+ if self.address:
46
+ result_dict["address"] = self.address
47
+ if self.national_id:
48
+ result_dict["national_id"] = self.national_id
49
+
50
+ return result_dict
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: tappay
3
+ Version: 0.5.0
4
+ Summary: TapPay Client Library for Python
5
+ Author-email: Shih Wei Chris Lo <shihwei@gmail.com>
6
+ Project-URL: Homepage, https://github.com/shihweilo/tappay-python
7
+ Project-URL: Bug Tracker, https://github.com/shihweilo/tappay-python/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Development Status :: 5 - Production/Stable
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE.txt
22
+ Requires-Dist: requests>=2.4.2
23
+ Dynamic: license-file
24
+
25
+
26
+ # TapPay Python SDK
27
+
28
+ ![CI](https://github.com/shihweilo/tappay-python/workflows/CI/badge.svg)
29
+ [![PyPI version](https://badge.fury.io/py/tappay.svg)](https://badge.fury.io/py/tappay)
30
+ [![Python Versions](https://img.shields.io/pypi/pyversions/tappay.svg)](https://pypi.org/project/tappay/)
31
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
32
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
33
+
34
+ > [!IMPORTANT]
35
+ > **Python 2 Support Dropped**: As of version 0.5.0, this library no longer supports Python 2.7. Please use Python 3.8 or newer.
36
+
37
+ This is the unofficial Python client library for TapPay's Backend API. To use it you'll need a TapPay account. Sign up at [tappaysdk.com](https://www.tappaysdk.com).
38
+
39
+ ## Installation
40
+
41
+ Install using pip:
42
+
43
+ ```bash
44
+ pip install tappay
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ ### Initialization
50
+
51
+ ```python
52
+ import tappay
53
+
54
+ # Initialize the client
55
+ client = tappay.Client(
56
+ is_sandbox=True,
57
+ partner_key="YOUR_PARTNER_KEY",
58
+ merchant_id="YOUR_MERCHANT_ID"
59
+ )
60
+ ```
61
+
62
+ For production, you can set `TAPPAY_PARTNER_KEY` and `TAPPAY_MERCHANT_ID` environment variables and omit them in the constructor:
63
+
64
+ ```python
65
+ client = tappay.Client(is_sandbox=False)
66
+ ```
67
+
68
+ ### Pay by Prime
69
+
70
+ ```python
71
+ # Create cardholder data
72
+ card_holder = tappay.Models.CardHolderData(
73
+ phone_number="0912345678",
74
+ name="Wang Xiao Ming",
75
+ email="test@example.com"
76
+ )
77
+
78
+ # Make payment
79
+ response = client.pay_by_prime(
80
+ prime="prime_token_from_frontend",
81
+ amount=100,
82
+ details="Order #123",
83
+ card_holder_data=card_holder
84
+ )
85
+ print(response)
86
+ ```
87
+
88
+ ### Pay by Token
89
+
90
+ ```python
91
+ response = client.pay_by_token(
92
+ card_key="card_key",
93
+ card_token="card_token",
94
+ amount=100,
95
+ details="Subscription"
96
+ )
97
+ ```
98
+
99
+ ### Refunds
100
+
101
+ ```python
102
+ response = client.refund(
103
+ rec_trade_id="rec_trade_id",
104
+ amount=100
105
+ )
106
+ ```
107
+
108
+ For more API details, please refer to the [TapPay Backend API Documentation](https://docs.tappaysdk.com/tutorial/zh/back.html).
109
+
110
+ ## Development
111
+
112
+ ### Setup
113
+
114
+ ```bash
115
+ git clone https://github.com/shihweilo/tappay-python.git
116
+ cd tappay-python
117
+ pip install -e ".[dev]"
118
+ ```
119
+
120
+ ### Testing
121
+
122
+ Run tests using pytest:
123
+
124
+ ```bash
125
+ pytest
126
+ ```
127
+
128
+ ## Contributing
129
+
130
+ Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
@@ -0,0 +1,9 @@
1
+ tappay/__init__.py,sha256=CFV-ZoZJR5DKe-vfABPMQOTLYQaYEfaJyOGZFpyqGV0,324
2
+ tappay/client.py,sha256=8j_1yCGK5ZF3Eaqb1bn78dqgDw54As861HrRfNSVbbE,10131
3
+ tappay/exceptions.py,sha256=_kfkoAuvsLVehkIZ4ucOu8zVWCB4PBTikmm8YgBmcik,389
4
+ tappay/models.py,sha256=F364tteyUNB3ZBnBKgOOPwMv26BxVNoUOXQlaRIWS54,1461
5
+ tappay-0.5.0.dist-info/licenses/LICENSE.txt,sha256=CpgTk30a7F04kGiQypkY-PXWUWAKwbzrE7qTO93roxE,1084
6
+ tappay-0.5.0.dist-info/METADATA,sha256=4hVfermPUwkV7sWk6PKYEgOqmIhRoBv4AfgGBdxhR1E,3324
7
+ tappay-0.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ tappay-0.5.0.dist-info/top_level.txt,sha256=GZaMpi1PiPG4IbGyB3mN0M0L-pNZDuaUTP3-5u8QQ3E,7
9
+ tappay-0.5.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,24 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Shih Wei Chris Lo
4
+
5
+ Permission is hereby granted, free of charge, to any person
6
+ obtaining a copy of this software and associated documentation
7
+ files (the "Software"), to deal in the Software without
8
+ restriction, including without limitation the rights to use,
9
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the
11
+ Software is furnished to do so, subject to the following
12
+ conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ tappay