python-payway 0.0.4__tar.gz → 0.0.6__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.
Files changed (25) hide show
  1. {python_payway-0.0.4 → python_payway-0.0.6}/PKG-INFO +3 -3
  2. {python_payway-0.0.4 → python_payway-0.0.6}/README.md +2 -2
  3. {python_payway-0.0.4 → python_payway-0.0.6}/payway/client.py +52 -70
  4. {python_payway-0.0.4 → python_payway-0.0.6}/payway/constants.py +22 -1
  5. {python_payway-0.0.4 → python_payway-0.0.6}/payway/customers.py +4 -2
  6. {python_payway-0.0.4 → python_payway-0.0.6}/payway/exceptions.py +6 -6
  7. python_payway-0.0.6/payway/model.py +453 -0
  8. {python_payway-0.0.4 → python_payway-0.0.6}/payway/test_utils.py +2 -1
  9. {python_payway-0.0.4 → python_payway-0.0.6}/payway/transactions.py +1 -1
  10. {python_payway-0.0.4 → python_payway-0.0.6}/pyproject.toml +67 -55
  11. {python_payway-0.0.4 → python_payway-0.0.6}/python_payway.egg-info/PKG-INFO +3 -3
  12. {python_payway-0.0.4 → python_payway-0.0.6}/python_payway.egg-info/SOURCES.txt +0 -1
  13. {python_payway-0.0.4 → python_payway-0.0.6}/tests/test_client.py +15 -16
  14. {python_payway-0.0.4 → python_payway-0.0.6}/tests/test_customers.py +8 -9
  15. {python_payway-0.0.4 → python_payway-0.0.6}/tests/test_transactions.py +2 -3
  16. python_payway-0.0.4/payway/conf.py +0 -8
  17. python_payway-0.0.4/payway/model.py +0 -479
  18. {python_payway-0.0.4 → python_payway-0.0.6}/LICENSE +0 -0
  19. {python_payway-0.0.4 → python_payway-0.0.6}/payway/__init__.py +0 -0
  20. {python_payway-0.0.4 → python_payway-0.0.6}/payway/utils.py +0 -0
  21. {python_payway-0.0.4 → python_payway-0.0.6}/python_payway.egg-info/dependency_links.txt +0 -0
  22. {python_payway-0.0.4 → python_payway-0.0.6}/python_payway.egg-info/requires.txt +0 -0
  23. {python_payway-0.0.4 → python_payway-0.0.6}/python_payway.egg-info/top_level.txt +0 -0
  24. {python_payway-0.0.4 → python_payway-0.0.6}/setup.cfg +0 -0
  25. {python_payway-0.0.4 → python_payway-0.0.6}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-payway
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: Python client for working with Westpac's PayWay REST API
5
5
  Author-email: Ben Napper <reppan197@gmail.com>
6
6
  License: MIT
@@ -232,7 +232,7 @@ Please follow PayWay's advice about reducing your risk of fraudulent transaction
232
232
 
233
233
  ```bash
234
234
  uv python install 3.8.19
235
- uv venv
235
+ uv venv --python 3.8.19
236
236
  source .venv/bin/activate
237
237
  uv sync --extra dev
238
238
  ```
@@ -240,5 +240,5 @@ uv sync --extra dev
240
240
  ## Testing
241
241
 
242
242
  ```bash
243
- python -m unittest discover tests
243
+ uv run pytest tests/ -v
244
244
  ```
@@ -213,7 +213,7 @@ Please follow PayWay's advice about reducing your risk of fraudulent transaction
213
213
 
214
214
  ```bash
215
215
  uv python install 3.8.19
216
- uv venv
216
+ uv venv --python 3.8.19
217
217
  source .venv/bin/activate
218
218
  uv sync --extra dev
219
219
  ```
@@ -221,5 +221,5 @@ uv sync --extra dev
221
221
  ## Testing
222
222
 
223
223
  ```bash
224
- python -m unittest discover tests
224
+ uv run pytest tests/ -v
225
225
  ```
@@ -2,14 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  from logging import getLogger
5
- from typing import NoReturn
5
+ from typing import Any
6
6
 
7
7
  import requests
8
8
 
9
- from payway.conf import CUSTOMER_URL, TOKEN_NO_REDIRECT, TRANSACTION_URL
10
9
  from payway.constants import (
11
10
  BANK_ACCOUNT_PAYMENT_CHOICE,
12
11
  CREDIT_CARD_PAYMENT_CHOICE,
12
+ CUSTOMER_URL,
13
+ PAYWAY_ERROR_RESPONSE_CODES,
14
+ TOKEN_NO_REDIRECT,
15
+ TRANSACTION_URL,
13
16
  VALID_PAYMENT_METHOD_CHOICES,
14
17
  )
15
18
  from payway.customers import CustomerRequest
@@ -46,12 +49,12 @@ class Client(CustomerRequest, TransactionRequest):
46
49
  bank_account_id: str,
47
50
  secret_api_key: str,
48
51
  publishable_api_key: str,
49
- ) -> NoReturn:
52
+ ) -> None:
50
53
  """
51
- :param merchant_id : str = PayWay Merchant ID
52
- :param bank_account_id : str = PayWay Bank Account ID
53
- :param secret_api_key : str = PayWay Secret APi Key
54
- :param publishable_api_key : str = PayWay Publishable API Key
54
+ :param merchant_id: PayWay Merchant ID
55
+ :param bank_account_id: PayWay Bank Account ID
56
+ :param secret_api_key: PayWay Secret APi Key
57
+ :param publishable_api_key: PayWay Publishable API Key
55
58
  """
56
59
  self._validate_credentials(
57
60
  merchant_id,
@@ -63,11 +66,9 @@ class Client(CustomerRequest, TransactionRequest):
63
66
  self.bank_account_id = bank_account_id
64
67
  self.secret_api_key = secret_api_key
65
68
  self.publishable_api_key = publishable_api_key
66
-
67
69
  session = requests.Session()
68
70
  session.auth = (self.secret_api_key, "")
69
- headers = {"content-type": "application/x-www-form-urlencoded"}
70
- session.headers = headers
71
+ session.headers = {"content-type": "application/x-www-form-urlencoded"}
71
72
  self.session = session
72
73
  session_no_headers = requests.Session()
73
74
  session_no_headers.auth = session.auth
@@ -79,17 +80,15 @@ class Client(CustomerRequest, TransactionRequest):
79
80
  bank_account_id: str,
80
81
  secret_api_key: str,
81
82
  publishable_api_key: str,
82
- ) -> NoReturn:
83
- if not merchant_id or not bank_account_id or not secret_api_key or not publishable_api_key:
84
- if not secret_api_key or not publishable_api_key:
85
- logger.error("PayWay API keys not found")
86
- raise PaywayError(
87
- message="PayWay API keys not found",
88
- code="INVALID_API_KEYS",
89
- )
90
- logger.error(
91
- "Merchant ID, bank account ID, secret API key, publishable API key are " "invalid",
83
+ ) -> None:
84
+ if any(not key for key in (secret_api_key, publishable_api_key)):
85
+ logger.error("PayWay API keys not found")
86
+ raise PaywayError(
87
+ message="PayWay API keys not found",
88
+ code="INVALID_API_KEYS",
92
89
  )
90
+ if any(not val for val in (merchant_id, bank_account_id)):
91
+ logger.error("Merchant ID or bank account ID invalid")
93
92
  raise PaywayError(
94
93
  message="Invalid credentials",
95
94
  code="INVALID_API_CREDENTIALS",
@@ -99,7 +98,7 @@ class Client(CustomerRequest, TransactionRequest):
99
98
  return requests.get(url=endpoint, auth=(self.secret_api_key, ""), timeout=30)
100
99
 
101
100
  def post_request(
102
- self, endpoint: str, data: dict, auth: tuple | None = None, idempotency_key: str | None = None
101
+ self, endpoint: str, data: dict[str, Any], auth: tuple[str, str] | None = None, idempotency_key: str | None = None
103
102
  ) -> requests.Response:
104
103
  """
105
104
  Supply an idempotency_key to avoid duplicate POSTs
@@ -112,7 +111,7 @@ class Client(CustomerRequest, TransactionRequest):
112
111
  headers["Idempotency-Key"] = idempotency_key
113
112
  return requests.post(url=endpoint, auth=auth, data=data, headers=headers, timeout=30)
114
113
 
115
- def put_request(self, endpoint: str, data: dict) -> requests.Response:
114
+ def put_request(self, endpoint: str, data: dict[str, Any]) -> requests.Response:
116
115
  return requests.put(
117
116
  url=endpoint,
118
117
  auth=(self.secret_api_key, ""),
@@ -123,7 +122,7 @@ class Client(CustomerRequest, TransactionRequest):
123
122
 
124
123
  def create_token(
125
124
  self, payway_obj: BankAccount | PayWayCard, payment_method: str, idempotency_key: str | None = None
126
- ) -> tuple[TokenResponse, list]:
125
+ ) -> tuple[TokenResponse | None, list[PaymentError] | None]:
127
126
  """
128
127
  Creates a single use token for a Customer's payment setup (credit card or bank account)
129
128
  :param payway_obj: object: one of model.PayWayCard or model.BankAccount object
@@ -146,10 +145,9 @@ class Client(CustomerRequest, TransactionRequest):
146
145
  "paymentMethod": payway_payment_method,
147
146
  },
148
147
  )
149
- endpoint = TOKEN_NO_REDIRECT
150
148
  logger.info("Sending Create Token request to PayWay.")
151
149
  response = self.post_request(
152
- endpoint,
150
+ TOKEN_NO_REDIRECT,
153
151
  data,
154
152
  auth=(self.publishable_api_key, ""),
155
153
  idempotency_key=idempotency_key,
@@ -157,17 +155,20 @@ class Client(CustomerRequest, TransactionRequest):
157
155
  errors = self._validate_response(response)
158
156
  if errors:
159
157
  return None, errors
160
- token_response = TokenResponse().from_dict(response.json())
161
- return token_response, errors
158
+ return TokenResponse().from_dict(response.json()), errors
162
159
 
163
- def create_card_token(self, card: PayWayCard, idempotency_key: str | None = None) -> tuple[TokenResponse, list]:
160
+ def create_card_token(
161
+ self, card: PayWayCard, idempotency_key: str | None = None
162
+ ) -> tuple[TokenResponse | None, list[PaymentError] | None]:
164
163
  """
165
164
  :param card: PayWayCard object represents a customer's credit card details
166
165
  :param idempotency_key: str: unique value to avoid duplicate POSTs
167
166
  """
168
167
  return self.create_token(card, "card", idempotency_key=idempotency_key)
169
168
 
170
- def create_bank_account_token(self, bank_account: BankAccount, idempotency_key: str | None = None) -> str:
169
+ def create_bank_account_token(
170
+ self, bank_account: BankAccount, idempotency_key: str | None = None
171
+ ) -> tuple[TokenResponse | None, list[PaymentError] | None]:
171
172
  """
172
173
  :param bank_account: BankAccount object represents a customer's bank account
173
174
  :param idempotency_key: str: unique value to avoid duplicate POSTs
@@ -179,26 +180,23 @@ class Client(CustomerRequest, TransactionRequest):
179
180
  idempotency_key=idempotency_key,
180
181
  )
181
182
 
182
- def create_customer(self, customer: PayWayCustomer, idempotency_key: str | None = None) -> tuple[PayWayCustomer | None, list]:
183
+ def create_customer(
184
+ self, customer: PayWayCustomer, idempotency_key: str | None = None
185
+ ) -> tuple[PayWayCustomer | None, list[PaymentError] | None]:
183
186
  """
184
187
  Create a customer in PayWay system
185
-
186
188
  POST /customers to have PayWay generate the customer number
187
189
  PUT /customers/{customerNumber} to use your own customer number
188
-
189
190
  :param customer: PayWayCustomer object represents a customer in PayWay
190
191
  :param idempotency_key: str: unique value to avoid duplicate POSTs
191
192
  See model.PayWayCustomer
192
- :return:
193
193
  """
194
194
 
195
195
  data = customer.to_dict()
196
196
  data.update(
197
197
  {"merchantId": self.merchant_id, "bankAccountId": self.bank_account_id},
198
198
  )
199
-
200
199
  logger.info("Sending Create Customer request to PayWay.")
201
-
202
200
  if customer.custom_id:
203
201
  endpoint = f"{CUSTOMER_URL}/{customer.custom_id}"
204
202
  response = self.put_request(endpoint, data)
@@ -209,15 +207,15 @@ class Client(CustomerRequest, TransactionRequest):
209
207
  data,
210
208
  idempotency_key=idempotency_key,
211
209
  )
212
-
213
210
  errors = self._validate_response(response)
214
211
  if errors:
215
212
  return None, errors
216
-
217
213
  customer = PayWayCustomer().from_dict(response.json())
218
214
  return customer, errors
219
215
 
220
- def process_payment(self, payment: PayWayPayment, idempotency_key: str | None = None) -> tuple[PayWayTransaction, list]:
216
+ def process_payment(
217
+ self, payment: PayWayPayment, idempotency_key: str | None = None
218
+ ) -> tuple[PayWayTransaction | None, list[PaymentError] | None]:
221
219
  """
222
220
  Process an individual payment against a Customer with active Recurring Billing setup.
223
221
  :param payment: PayWayPayment object (see model.PayWayPayment)
@@ -234,30 +232,16 @@ class Client(CustomerRequest, TransactionRequest):
234
232
  transaction = PayWayTransaction.from_dict(response.json())
235
233
  return transaction, errors
236
234
 
237
- def _validate_response(self, response: requests.Response) -> list | None:
235
+ def _validate_response(self, response: requests.Response) -> list[PaymentError] | None:
238
236
  """
239
237
  Validates all responses from PayWay to catch documented PayWay errors.
240
238
  :param response: requests response object
241
239
  """
242
- if response.status_code in [
243
- 400,
244
- 401,
245
- 403,
246
- 405,
247
- 406,
248
- 407,
249
- 409,
250
- 410,
251
- 415,
252
- 429,
253
- 501,
254
- 503,
255
- ]:
240
+ if response.status_code in PAYWAY_ERROR_RESPONSE_CODES:
256
241
  http_error_msg = f"{response.status_code} Client Error: {response.reason} for url: {response.url}"
257
- raise PaywayError(code=response.status_code, message=http_error_msg)
242
+ raise PaywayError(code=str(response.status_code), message=http_error_msg)
258
243
 
259
244
  if response.status_code in [404, 422]: # Documented PayWay errors in JSON
260
- # parse error message
261
245
  return PaymentError().from_dict(response.json())
262
246
 
263
247
  if response.status_code == 500:
@@ -265,17 +249,17 @@ class Client(CustomerRequest, TransactionRequest):
265
249
  errors = response.json()
266
250
  except json.JSONDecodeError:
267
251
  raise PaywayError(
268
- code=response.status_code,
252
+ code=str(response.status_code),
269
253
  message="Internal server error",
270
254
  )
271
255
  # Documented PayWay server errors in JSON
272
256
  payway_error = ServerError().from_dict(errors)
273
257
  message = payway_error.to_message()
274
- raise PaywayError(code=response.status_code, message=message)
258
+ raise PaywayError(code=str(response.status_code), message=message)
275
259
 
276
260
  return None
277
261
 
278
- def get_transaction(self, transaction_id: int) -> tuple[PayWayTransaction, list]:
262
+ def get_transaction(self, transaction_id: int) -> tuple[PayWayTransaction | None, list[PaymentError] | None]:
279
263
  """
280
264
  Lookup and return a transaction if found in PayWay
281
265
  :param transaction_id: str A PayWay transaction ID
@@ -288,7 +272,9 @@ class Client(CustomerRequest, TransactionRequest):
288
272
  transaction = PayWayTransaction.from_dict(response.json())
289
273
  return transaction, errors
290
274
 
291
- def void_transaction(self, transaction_id: int, idempotency_key: str | None = None) -> tuple[PayWayTransaction, list]:
275
+ def void_transaction(
276
+ self, transaction_id: int, idempotency_key: str | None = None
277
+ ) -> tuple[PayWayTransaction | None, list[PaymentError] | None]:
292
278
  """
293
279
  Void a transaction in PayWay
294
280
  :param transaction_id: str A PayWay transaction ID
@@ -299,8 +285,7 @@ class Client(CustomerRequest, TransactionRequest):
299
285
  errors = self._validate_response(response)
300
286
  if errors:
301
287
  return None, errors
302
- transaction = PayWayTransaction.from_dict(response.json())
303
- return transaction, errors
288
+ return PayWayTransaction.from_dict(response.json()), errors
304
289
 
305
290
  def refund_transaction(
306
291
  self,
@@ -309,7 +294,7 @@ class Client(CustomerRequest, TransactionRequest):
309
294
  order_id: str | None = None,
310
295
  ip_address: str | None = None,
311
296
  idempotency_key: str | None = None,
312
- ) -> tuple[PayWayTransaction, list]:
297
+ ) -> tuple[PayWayTransaction | None, list[PaymentError] | None]:
313
298
  """
314
299
  Refund a transaction in PayWay
315
300
  :param transaction_id: str A PayWay transaction ID
@@ -331,10 +316,9 @@ class Client(CustomerRequest, TransactionRequest):
331
316
  errors = self._validate_response(response)
332
317
  if errors:
333
318
  return None, errors
334
- transaction = PayWayTransaction.from_dict(response.json())
335
- return transaction, errors
319
+ return PayWayTransaction.from_dict(response.json()), errors
336
320
 
337
- def get_customer(self, customer_id: str) -> tuple[PayWayCustomer, list]:
321
+ def get_customer(self, customer_id: str) -> tuple[PayWayCustomer | None, list[PaymentError] | None]:
338
322
  """
339
323
  Returns a PayWay Customer's Payment Setup, [Payment] Schedule, Contact Details, Custom Fields and Notes
340
324
  :param customer_id str PayWay customer ID in PayWay system
@@ -344,10 +328,9 @@ class Client(CustomerRequest, TransactionRequest):
344
328
  errors = self._validate_response(response)
345
329
  if errors:
346
330
  return None, errors
347
- customer = PayWayCustomer.from_dict(response.json())
348
- return customer, errors
331
+ return PayWayCustomer.from_dict(response.json()), errors
349
332
 
350
- def update_payment_setup(self, token: str, customer_id: str) -> tuple[PaymentSetup, str]:
333
+ def update_payment_setup(self, token: str, customer_id: str) -> tuple[PaymentSetup | None, list[PaymentError] | None]:
351
334
  """
352
335
  Updates the Customer's Payment Setup with a new Credit Card or Bank Account.
353
336
  :param token: PayWay credit card or bank account token
@@ -363,5 +346,4 @@ class Client(CustomerRequest, TransactionRequest):
363
346
  errors = self._validate_response(response)
364
347
  if errors:
365
348
  return None, errors
366
- ps = PaymentSetup.from_dict(response.json())
367
- return ps, errors
349
+ return PaymentSetup.from_dict(response.json()), errors
@@ -1,5 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
+ PAYWAY_API_URL = "https://api.payway.com.au/rest/v1"
4
+ TOKEN_URL = PAYWAY_API_URL + "/single-use-tokens-redirect"
5
+ TRANSACTION_URL = PAYWAY_API_URL + "/transactions"
6
+ CUSTOMER_URL = PAYWAY_API_URL + "/customers"
7
+ OWN_BANK_ACCOUNTS_URL = PAYWAY_API_URL + "/your-bank-accounts"
8
+ TOKEN_NO_REDIRECT = PAYWAY_API_URL + "/single-use-tokens"
3
9
  TRANSACTION_APPROVED = "0"
4
10
 
5
11
  SUMMARY_CODES = {
@@ -69,7 +75,8 @@ EFT_RESPONSE_CODES = {
69
75
  CVN_RESPONSE_CODES = {
70
76
  "M": "Matched", # i.e. the CVN is correct
71
77
  "N": "Not Matched", # i.e. the CVN is incorrect
72
- "P": "Not Processed", # i.e. the CVN was not processed for some reason; do not assume that the CVN is necessarily correct
78
+ "P": "Not Processed",
79
+ # i.e. the CVN was not processed for some reason; do not assume that the CVN is necessarily correct
73
80
  "S": "Suspicious",
74
81
  "U": "Unknown", # i.e. the CVN was not processed for some reason; do not assume that the CVN is necessarily correct
75
82
  }
@@ -132,3 +139,17 @@ DIRECT_DEBIT_CHOICES = (
132
139
  )
133
140
  PAYMENT_METHOD_CHOICES = OTHER_PAYMENT_CHOICES + DIRECT_DEBIT_CHOICES
134
141
  VALID_PAYMENT_METHOD_CHOICES = ["card", "direct_debit"]
142
+ PAYWAY_ERROR_RESPONSE_CODES = [
143
+ 400,
144
+ 401,
145
+ 403,
146
+ 405,
147
+ 406,
148
+ 407,
149
+ 409,
150
+ 410,
151
+ 415,
152
+ 429,
153
+ 501,
154
+ 503,
155
+ ]
@@ -1,8 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import Any
4
+
3
5
  import requests
4
6
 
5
- from payway.conf import CUSTOMER_URL
7
+ from payway.constants import CUSTOMER_URL
6
8
  from payway.model import PayWayCustomer
7
9
  from payway.utils import json_list
8
10
 
@@ -79,7 +81,7 @@ class CustomerRequest:
79
81
 
80
82
  @json_list("update_contact_details")
81
83
  def update_contact_details(
82
- self, customer_number: int, customer: PayWayCustomer | None = None, **options: dict
84
+ self, customer_number: int, customer: PayWayCustomer | None = None, **options: dict[str, Any]
83
85
  ) -> requests.Response:
84
86
  """
85
87
  param: customer_number: PayWay customer number
@@ -1,13 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import NoReturn
3
+ from typing import Any
4
4
 
5
5
 
6
6
  class PaywayError(Exception):
7
- _code: str = None
8
- _message: str = None
7
+ _code: str = ""
8
+ _message: str = ""
9
9
 
10
- def __init__(self, code: str, message: str, *args: dict, **kwargs: dict) -> NoReturn:
10
+ def __init__(self, code: str, message: str, *args: dict[str, Any], **kwargs: dict[str, Any]) -> None:
11
11
  """
12
12
  code : str = PayWay API response/error code
13
13
  message : str = appropriate message
@@ -16,7 +16,7 @@ class PaywayError(Exception):
16
16
  super().__init__(*args, **kwargs)
17
17
 
18
18
  self._code = code
19
- self._message = f"{code}: {message}".encode()
19
+ self._message = f"{code}: {message}"
20
20
 
21
21
  def __str__(self) -> str:
22
- return self.message
22
+ return self._message