python-payway 0.0.5__tar.gz → 0.0.7__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.
- {python_payway-0.0.5 → python_payway-0.0.7}/PKG-INFO +6 -3
- {python_payway-0.0.5 → python_payway-0.0.7}/README.md +2 -2
- {python_payway-0.0.5 → python_payway-0.0.7}/payway/client.py +22 -49
- {python_payway-0.0.5 → python_payway-0.0.7}/payway/constants.py +22 -1
- {python_payway-0.0.5 → python_payway-0.0.7}/payway/customers.py +1 -1
- {python_payway-0.0.5 → python_payway-0.0.7}/payway/model.py +166 -192
- {python_payway-0.0.5 → python_payway-0.0.7}/payway/transactions.py +1 -1
- {python_payway-0.0.5 → python_payway-0.0.7}/pyproject.toml +71 -54
- {python_payway-0.0.5 → python_payway-0.0.7}/python_payway.egg-info/PKG-INFO +6 -3
- {python_payway-0.0.5 → python_payway-0.0.7}/python_payway.egg-info/SOURCES.txt +0 -1
- python_payway-0.0.5/payway/conf.py +0 -8
- {python_payway-0.0.5 → python_payway-0.0.7}/LICENSE +0 -0
- {python_payway-0.0.5 → python_payway-0.0.7}/payway/__init__.py +0 -0
- {python_payway-0.0.5 → python_payway-0.0.7}/payway/exceptions.py +0 -0
- {python_payway-0.0.5 → python_payway-0.0.7}/payway/test_utils.py +0 -0
- {python_payway-0.0.5 → python_payway-0.0.7}/payway/utils.py +0 -0
- {python_payway-0.0.5 → python_payway-0.0.7}/python_payway.egg-info/dependency_links.txt +0 -0
- {python_payway-0.0.5 → python_payway-0.0.7}/python_payway.egg-info/requires.txt +0 -0
- {python_payway-0.0.5 → python_payway-0.0.7}/python_payway.egg-info/top_level.txt +0 -0
- {python_payway-0.0.5 → python_payway-0.0.7}/setup.cfg +0 -0
- {python_payway-0.0.5 → python_payway-0.0.7}/tests/__init__.py +0 -0
- {python_payway-0.0.5 → python_payway-0.0.7}/tests/test_client.py +0 -0
- {python_payway-0.0.5 → python_payway-0.0.7}/tests/test_customers.py +0 -0
- {python_payway-0.0.5 → python_payway-0.0.7}/tests/test_transactions.py +0 -0
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-payway
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
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
|
|
7
|
+
Project-URL: Homepage, https://github.com/napper1/python-payway
|
|
8
|
+
Project-URL: Repository, https://github.com/napper1/python-payway
|
|
9
|
+
Project-URL: Issues, https://github.com/napper1/python-payway/issues
|
|
7
10
|
Keywords: payway,westpac,api,client
|
|
8
11
|
Classifier: Programming Language :: Python :: 3
|
|
9
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -232,7 +235,7 @@ Please follow PayWay's advice about reducing your risk of fraudulent transaction
|
|
|
232
235
|
|
|
233
236
|
```bash
|
|
234
237
|
uv python install 3.8.19
|
|
235
|
-
uv venv
|
|
238
|
+
uv venv --python 3.8.19
|
|
236
239
|
source .venv/bin/activate
|
|
237
240
|
uv sync --extra dev
|
|
238
241
|
```
|
|
@@ -240,5 +243,5 @@ uv sync --extra dev
|
|
|
240
243
|
## Testing
|
|
241
244
|
|
|
242
245
|
```bash
|
|
243
|
-
|
|
246
|
+
uv run pytest tests/ -v
|
|
244
247
|
```
|
|
@@ -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
|
-
|
|
224
|
+
uv run pytest tests/ -v
|
|
225
225
|
```
|
|
@@ -6,10 +6,13 @@ 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
|
|
@@ -48,10 +51,10 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
48
51
|
publishable_api_key: str,
|
|
49
52
|
) -> None:
|
|
50
53
|
"""
|
|
51
|
-
:param merchant_id
|
|
52
|
-
:param bank_account_id
|
|
53
|
-
:param secret_api_key
|
|
54
|
-
:param 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,7 +66,6 @@ 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
71
|
session.headers = {"content-type": "application/x-www-form-urlencoded"}
|
|
@@ -79,16 +81,14 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
79
81
|
secret_api_key: str,
|
|
80
82
|
publishable_api_key: str,
|
|
81
83
|
) -> None:
|
|
82
|
-
if not
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
code="INVALID_API_KEYS",
|
|
88
|
-
)
|
|
89
|
-
logger.error(
|
|
90
|
-
"Merchant ID, bank account ID, secret API key, publishable API key are " "invalid",
|
|
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",
|
|
91
89
|
)
|
|
90
|
+
if any(not val for val in (merchant_id, bank_account_id)):
|
|
91
|
+
logger.error("Merchant ID or bank account ID invalid")
|
|
92
92
|
raise PaywayError(
|
|
93
93
|
message="Invalid credentials",
|
|
94
94
|
code="INVALID_API_CREDENTIALS",
|
|
@@ -145,10 +145,9 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
145
145
|
"paymentMethod": payway_payment_method,
|
|
146
146
|
},
|
|
147
147
|
)
|
|
148
|
-
endpoint = TOKEN_NO_REDIRECT
|
|
149
148
|
logger.info("Sending Create Token request to PayWay.")
|
|
150
149
|
response = self.post_request(
|
|
151
|
-
|
|
150
|
+
TOKEN_NO_REDIRECT,
|
|
152
151
|
data,
|
|
153
152
|
auth=(self.publishable_api_key, ""),
|
|
154
153
|
idempotency_key=idempotency_key,
|
|
@@ -156,8 +155,7 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
156
155
|
errors = self._validate_response(response)
|
|
157
156
|
if errors:
|
|
158
157
|
return None, errors
|
|
159
|
-
|
|
160
|
-
return token_response, errors
|
|
158
|
+
return TokenResponse().from_dict(response.json()), errors
|
|
161
159
|
|
|
162
160
|
def create_card_token(
|
|
163
161
|
self, card: PayWayCard, idempotency_key: str | None = None
|
|
@@ -187,23 +185,18 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
187
185
|
) -> tuple[PayWayCustomer | None, list[PaymentError] | None]:
|
|
188
186
|
"""
|
|
189
187
|
Create a customer in PayWay system
|
|
190
|
-
|
|
191
188
|
POST /customers to have PayWay generate the customer number
|
|
192
189
|
PUT /customers/{customerNumber} to use your own customer number
|
|
193
|
-
|
|
194
190
|
:param customer: PayWayCustomer object represents a customer in PayWay
|
|
195
191
|
:param idempotency_key: str: unique value to avoid duplicate POSTs
|
|
196
192
|
See model.PayWayCustomer
|
|
197
|
-
:return:
|
|
198
193
|
"""
|
|
199
194
|
|
|
200
195
|
data = customer.to_dict()
|
|
201
196
|
data.update(
|
|
202
197
|
{"merchantId": self.merchant_id, "bankAccountId": self.bank_account_id},
|
|
203
198
|
)
|
|
204
|
-
|
|
205
199
|
logger.info("Sending Create Customer request to PayWay.")
|
|
206
|
-
|
|
207
200
|
if customer.custom_id:
|
|
208
201
|
endpoint = f"{CUSTOMER_URL}/{customer.custom_id}"
|
|
209
202
|
response = self.put_request(endpoint, data)
|
|
@@ -214,11 +207,9 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
214
207
|
data,
|
|
215
208
|
idempotency_key=idempotency_key,
|
|
216
209
|
)
|
|
217
|
-
|
|
218
210
|
errors = self._validate_response(response)
|
|
219
211
|
if errors:
|
|
220
212
|
return None, errors
|
|
221
|
-
|
|
222
213
|
customer = PayWayCustomer().from_dict(response.json())
|
|
223
214
|
return customer, errors
|
|
224
215
|
|
|
@@ -246,25 +237,11 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
246
237
|
Validates all responses from PayWay to catch documented PayWay errors.
|
|
247
238
|
:param response: requests response object
|
|
248
239
|
"""
|
|
249
|
-
if response.status_code in
|
|
250
|
-
400,
|
|
251
|
-
401,
|
|
252
|
-
403,
|
|
253
|
-
405,
|
|
254
|
-
406,
|
|
255
|
-
407,
|
|
256
|
-
409,
|
|
257
|
-
410,
|
|
258
|
-
415,
|
|
259
|
-
429,
|
|
260
|
-
501,
|
|
261
|
-
503,
|
|
262
|
-
]:
|
|
240
|
+
if response.status_code in PAYWAY_ERROR_RESPONSE_CODES:
|
|
263
241
|
http_error_msg = f"{response.status_code} Client Error: {response.reason} for url: {response.url}"
|
|
264
242
|
raise PaywayError(code=str(response.status_code), message=http_error_msg)
|
|
265
243
|
|
|
266
244
|
if response.status_code in [404, 422]: # Documented PayWay errors in JSON
|
|
267
|
-
# parse error message
|
|
268
245
|
return PaymentError().from_dict(response.json())
|
|
269
246
|
|
|
270
247
|
if response.status_code == 500:
|
|
@@ -308,8 +285,7 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
308
285
|
errors = self._validate_response(response)
|
|
309
286
|
if errors:
|
|
310
287
|
return None, errors
|
|
311
|
-
|
|
312
|
-
return transaction, errors
|
|
288
|
+
return PayWayTransaction.from_dict(response.json()), errors
|
|
313
289
|
|
|
314
290
|
def refund_transaction(
|
|
315
291
|
self,
|
|
@@ -340,8 +316,7 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
340
316
|
errors = self._validate_response(response)
|
|
341
317
|
if errors:
|
|
342
318
|
return None, errors
|
|
343
|
-
|
|
344
|
-
return transaction, errors
|
|
319
|
+
return PayWayTransaction.from_dict(response.json()), errors
|
|
345
320
|
|
|
346
321
|
def get_customer(self, customer_id: str) -> tuple[PayWayCustomer | None, list[PaymentError] | None]:
|
|
347
322
|
"""
|
|
@@ -353,8 +328,7 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
353
328
|
errors = self._validate_response(response)
|
|
354
329
|
if errors:
|
|
355
330
|
return None, errors
|
|
356
|
-
|
|
357
|
-
return customer, errors
|
|
331
|
+
return PayWayCustomer.from_dict(response.json()), errors
|
|
358
332
|
|
|
359
333
|
def update_payment_setup(self, token: str, customer_id: str) -> tuple[PaymentSetup | None, list[PaymentError] | None]:
|
|
360
334
|
"""
|
|
@@ -372,5 +346,4 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
372
346
|
errors = self._validate_response(response)
|
|
373
347
|
if errors:
|
|
374
348
|
return None, errors
|
|
375
|
-
|
|
376
|
-
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",
|
|
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 dataclasses import dataclass
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
|
|
7
|
+
@dataclass
|
|
6
8
|
class BankAccount:
|
|
7
9
|
"""
|
|
8
10
|
account_name: str: Name used to open bank account.
|
|
@@ -10,10 +12,9 @@ class BankAccount:
|
|
|
10
12
|
account_number: str: bank account number
|
|
11
13
|
"""
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
self.account_number = account_number
|
|
15
|
+
account_name: str
|
|
16
|
+
bsb: str
|
|
17
|
+
account_number: str
|
|
17
18
|
|
|
18
19
|
def to_dict(self) -> dict[str, Any]:
|
|
19
20
|
return {
|
|
@@ -23,20 +24,13 @@ class BankAccount:
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
@dataclass
|
|
26
28
|
class PayWayCard:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
expiry_date_month: str | None = None,
|
|
33
|
-
expiry_date_year: str | None = None,
|
|
34
|
-
) -> None:
|
|
35
|
-
self.card_number = card_number
|
|
36
|
-
self.cvn = cvn
|
|
37
|
-
self.card_holder_name = card_holder_name
|
|
38
|
-
self.expiry_date_month = expiry_date_month
|
|
39
|
-
self.expiry_date_year = expiry_date_year
|
|
29
|
+
card_number: str | None = None
|
|
30
|
+
cvn: str | None = None
|
|
31
|
+
card_holder_name: str | None = None
|
|
32
|
+
expiry_date_month: str | None = None
|
|
33
|
+
expiry_date_year: str | None = None
|
|
40
34
|
|
|
41
35
|
def to_dict(self) -> dict[str, Any]:
|
|
42
36
|
return {
|
|
@@ -49,58 +43,36 @@ class PayWayCard:
|
|
|
49
43
|
|
|
50
44
|
@staticmethod
|
|
51
45
|
def from_dict(payway_card: dict[str, Any]) -> PayWayCard:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
card.expiry_date_year = payway_card.get("expiryDateYear")
|
|
61
|
-
return card
|
|
46
|
+
card_number = payway_card.get("maskedCardNumber") or payway_card.get("cardNumber")
|
|
47
|
+
return PayWayCard(
|
|
48
|
+
card_number=card_number,
|
|
49
|
+
cvn=payway_card.get("cvn"),
|
|
50
|
+
card_holder_name=payway_card.get("cardholderName"),
|
|
51
|
+
expiry_date_month=payway_card.get("expiryDateMonth"),
|
|
52
|
+
expiry_date_year=payway_card.get("expiryDateYear"),
|
|
53
|
+
)
|
|
62
54
|
|
|
63
55
|
|
|
56
|
+
@dataclass
|
|
64
57
|
class PayWayCustomer:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
custom_field_3: str | None = None,
|
|
84
|
-
custom_field_4: str | None = None,
|
|
85
|
-
) -> None:
|
|
86
|
-
self.custom_id = custom_id
|
|
87
|
-
self.customer_name = customer_name
|
|
88
|
-
self.email_address = email_address
|
|
89
|
-
self.send_email_receipts = send_email_receipts
|
|
90
|
-
self.phone_number = phone_number
|
|
91
|
-
self.street = street
|
|
92
|
-
self.street2 = street2
|
|
93
|
-
self.city_name = city_name
|
|
94
|
-
self.state = state
|
|
95
|
-
self.postal_code = postal_code
|
|
96
|
-
self.token = token
|
|
97
|
-
self.customer_number = customer_number
|
|
98
|
-
self.payment_setup = payment_setup
|
|
99
|
-
self.notes = notes
|
|
100
|
-
self.customField1 = custom_field_1
|
|
101
|
-
self.customField2 = custom_field_2
|
|
102
|
-
self.customField3 = custom_field_3
|
|
103
|
-
self.customField4 = custom_field_4
|
|
58
|
+
custom_id: str | None = None
|
|
59
|
+
customer_name: str | None = None
|
|
60
|
+
email_address: str | None = None
|
|
61
|
+
send_email_receipts: bool | None = None
|
|
62
|
+
phone_number: str | None = None
|
|
63
|
+
street: str | None = None
|
|
64
|
+
street2: str | None = None
|
|
65
|
+
city_name: str | None = None
|
|
66
|
+
state: str | None = None
|
|
67
|
+
postal_code: str | None = None
|
|
68
|
+
token: str | None = None
|
|
69
|
+
customer_number: str | None = None
|
|
70
|
+
payment_setup: PaymentSetup | None = None
|
|
71
|
+
notes: str | None = None
|
|
72
|
+
custom_field_1: str | None = None
|
|
73
|
+
custom_field_2: str | None = None
|
|
74
|
+
custom_field_3: str | None = None
|
|
75
|
+
custom_field_4: str | None = None
|
|
104
76
|
|
|
105
77
|
def to_dict(self) -> dict[str, Any]:
|
|
106
78
|
customer = {
|
|
@@ -114,10 +86,10 @@ class PayWayCustomer:
|
|
|
114
86
|
"state": self.state,
|
|
115
87
|
"postalCode": self.postal_code,
|
|
116
88
|
"notes": self.notes,
|
|
117
|
-
"customField1": self.
|
|
118
|
-
"customField2": self.
|
|
119
|
-
"customField3": self.
|
|
120
|
-
"customField4": self.
|
|
89
|
+
"customField1": self.custom_field_1,
|
|
90
|
+
"customField2": self.custom_field_2,
|
|
91
|
+
"customField3": self.custom_field_3,
|
|
92
|
+
"customField4": self.custom_field_4,
|
|
121
93
|
}
|
|
122
94
|
if self.token:
|
|
123
95
|
customer.update({"singleUseTokenId": self.token})
|
|
@@ -130,36 +102,33 @@ class PayWayCustomer:
|
|
|
130
102
|
:param response: dict PayWay response dictionary
|
|
131
103
|
:return:
|
|
132
104
|
"""
|
|
133
|
-
customer = PayWayCustomer()
|
|
134
105
|
contact = response.get("contact", {})
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
customer.send_email_receipts = contact.get("sendEmailReceipts")
|
|
138
|
-
customer.phone_number = contact.get("phoneNumber")
|
|
139
|
-
address = contact.get("address")
|
|
140
|
-
customer.street = address.get("street1")
|
|
141
|
-
customer.street2 = address.get("street2")
|
|
142
|
-
customer.city_name = address.get("cityName")
|
|
143
|
-
customer.state = address.get("state")
|
|
144
|
-
customer.postal_code = address.get("postalCode")
|
|
145
|
-
customer.customer_number = response.get("customerNumber")
|
|
146
|
-
|
|
106
|
+
address = contact.get("address", {})
|
|
107
|
+
payment_setup = None
|
|
147
108
|
if response.get("paymentSetup") is not None:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
109
|
+
payment_setup = PaymentSetup.from_dict(response.get("paymentSetup", {}))
|
|
110
|
+
custom_fields = response.get("customFields", {})
|
|
111
|
+
return PayWayCustomer(
|
|
112
|
+
customer_name=contact.get("customerName"),
|
|
113
|
+
email_address=contact.get("emailAddress"),
|
|
114
|
+
send_email_receipts=contact.get("sendEmailReceipts"),
|
|
115
|
+
phone_number=contact.get("phoneNumber"),
|
|
116
|
+
street=address.get("street1"),
|
|
117
|
+
street2=address.get("street2"),
|
|
118
|
+
city_name=address.get("cityName"),
|
|
119
|
+
state=address.get("state"),
|
|
120
|
+
postal_code=address.get("postalCode"),
|
|
121
|
+
customer_number=response.get("customerNumber"),
|
|
122
|
+
payment_setup=payment_setup,
|
|
123
|
+
notes=response.get("notes"),
|
|
124
|
+
custom_field_1=custom_fields.get("customField1"),
|
|
125
|
+
custom_field_2=custom_fields.get("customField2"),
|
|
126
|
+
custom_field_3=custom_fields.get("customField3"),
|
|
127
|
+
custom_field_4=custom_fields.get("customField4"),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass
|
|
163
132
|
class PaymentError:
|
|
164
133
|
field_name: str | None = None
|
|
165
134
|
message: str | None = None
|
|
@@ -172,14 +141,14 @@ class PaymentError:
|
|
|
172
141
|
:param: payway_response: dict PayWay response dictionary
|
|
173
142
|
"""
|
|
174
143
|
errors = payway_response.get("data", [])
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
144
|
+
return [
|
|
145
|
+
PaymentError(
|
|
146
|
+
field_name=error.get("fieldName"),
|
|
147
|
+
message=error.get("message"),
|
|
148
|
+
field_value=error.get("fieldValue"),
|
|
149
|
+
)
|
|
150
|
+
for error in errors
|
|
151
|
+
]
|
|
183
152
|
|
|
184
153
|
def to_message(self) -> str:
|
|
185
154
|
return f"Field: {self.field_name} Message: {self.message} Field Value: {self.field_value}"
|
|
@@ -199,6 +168,7 @@ class PaymentError:
|
|
|
199
168
|
return message
|
|
200
169
|
|
|
201
170
|
|
|
171
|
+
@dataclass
|
|
202
172
|
class ServerError:
|
|
203
173
|
error_number: int | None = None
|
|
204
174
|
trace_code: str | None = None
|
|
@@ -208,15 +178,16 @@ class ServerError:
|
|
|
208
178
|
"""
|
|
209
179
|
:param: response: dict PayWay response dictionary
|
|
210
180
|
"""
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
181
|
+
return ServerError(
|
|
182
|
+
error_number=response.get("errorNumber"),
|
|
183
|
+
trace_code=response.get("traceCode"),
|
|
184
|
+
)
|
|
215
185
|
|
|
216
186
|
def to_message(self) -> str:
|
|
217
187
|
return f"Error number: {self.error_number} Trace code: {self.trace_code}"
|
|
218
188
|
|
|
219
189
|
|
|
190
|
+
@dataclass
|
|
220
191
|
class PayWayPayment:
|
|
221
192
|
"""
|
|
222
193
|
customer_number: Customer to which this payment belongs.
|
|
@@ -230,27 +201,15 @@ class PayWayPayment:
|
|
|
230
201
|
merchant_id: This merchant will be used for processing.
|
|
231
202
|
"""
|
|
232
203
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
token: str | None = None,
|
|
243
|
-
merchant_id: str | None = None,
|
|
244
|
-
) -> None:
|
|
245
|
-
self.transaction_type = transaction_type
|
|
246
|
-
self.customer_number = customer_number
|
|
247
|
-
self.amount = amount
|
|
248
|
-
self.currency = currency
|
|
249
|
-
self.order_number = order_number
|
|
250
|
-
self.ip_address = ip_address
|
|
251
|
-
self.parent_transaction_id = parent_transaction_id
|
|
252
|
-
self.token = token
|
|
253
|
-
self.merchant_id = merchant_id
|
|
204
|
+
transaction_type: str
|
|
205
|
+
customer_number: str | None = None
|
|
206
|
+
amount: float | None = None
|
|
207
|
+
currency: str | None = None
|
|
208
|
+
order_number: str | None = None
|
|
209
|
+
ip_address: str | None = None
|
|
210
|
+
parent_transaction_id: str | None = None
|
|
211
|
+
token: str | None = None
|
|
212
|
+
merchant_id: str | None = None
|
|
254
213
|
|
|
255
214
|
def to_dict(self) -> dict[str, Any]:
|
|
256
215
|
payment = {
|
|
@@ -271,6 +230,7 @@ class PayWayPayment:
|
|
|
271
230
|
return payment
|
|
272
231
|
|
|
273
232
|
|
|
233
|
+
@dataclass
|
|
274
234
|
class PayWayTransaction:
|
|
275
235
|
transaction_id: int | None = None
|
|
276
236
|
receipt_number: str | None = None
|
|
@@ -328,7 +288,7 @@ class PayWayTransaction:
|
|
|
328
288
|
"creditCard": self.card.to_dict() if self.card is not None else {},
|
|
329
289
|
"merchant": self.merchant.to_dict() if self.merchant is not None else {},
|
|
330
290
|
"virtualAccount": self.virtual_account,
|
|
331
|
-
"
|
|
291
|
+
"australiaPost": self.australia_post,
|
|
332
292
|
"bpay": self.bpay,
|
|
333
293
|
"yourBankAccount": self.your_bank_account,
|
|
334
294
|
"customerPayPalAccount": self.customer_paypal_account,
|
|
@@ -352,49 +312,53 @@ class PayWayTransaction:
|
|
|
352
312
|
"""
|
|
353
313
|
:param: response: dict PayWay response dictionary
|
|
354
314
|
"""
|
|
355
|
-
|
|
356
|
-
transaction.transaction_id = response.get("transactionId")
|
|
357
|
-
transaction.receipt_number = response.get("receiptNumber")
|
|
358
|
-
transaction.status = response.get("status")
|
|
359
|
-
transaction.response_code = response.get("responseCode")
|
|
360
|
-
transaction.response_text = response.get("responseText")
|
|
361
|
-
transaction.transaction_type = response.get("transactionType")
|
|
362
|
-
transaction.customer_number = response.get("customerNumber")
|
|
363
|
-
transaction.customer_name = response.get("customerName")
|
|
364
|
-
transaction.customer_email = response.get("customerEmail")
|
|
365
|
-
transaction.currency = response.get("currency")
|
|
366
|
-
transaction.principal_amount = response.get("principalAmount")
|
|
367
|
-
transaction.surcharge_amount = response.get("surchargeAmount")
|
|
368
|
-
transaction.payment_amount = response.get("paymentAmount")
|
|
369
|
-
transaction.payment_method = response.get("paymentMethod")
|
|
370
|
-
|
|
315
|
+
card = None
|
|
371
316
|
if response.get("creditCard") is not None:
|
|
372
|
-
|
|
317
|
+
card = PayWayCard.from_dict(response.get("creditCard", {}))
|
|
373
318
|
|
|
319
|
+
merchant = None
|
|
374
320
|
if response.get("merchant") is not None:
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
321
|
+
merchant = Merchant.from_dict(response.get("merchant", {}))
|
|
322
|
+
|
|
323
|
+
return PayWayTransaction(
|
|
324
|
+
transaction_id=response.get("transactionId"),
|
|
325
|
+
receipt_number=response.get("receiptNumber"),
|
|
326
|
+
status=response.get("status"),
|
|
327
|
+
response_code=response.get("responseCode"),
|
|
328
|
+
response_text=response.get("responseText"),
|
|
329
|
+
transaction_type=response.get("transactionType"),
|
|
330
|
+
customer_number=response.get("customerNumber"),
|
|
331
|
+
customer_name=response.get("customerName"),
|
|
332
|
+
customer_email=response.get("customerEmail"),
|
|
333
|
+
currency=response.get("currency"),
|
|
334
|
+
principal_amount=response.get("principalAmount"),
|
|
335
|
+
surcharge_amount=response.get("surchargeAmount"),
|
|
336
|
+
payment_amount=response.get("paymentAmount"),
|
|
337
|
+
payment_method=response.get("paymentMethod"),
|
|
338
|
+
card=card,
|
|
339
|
+
merchant=merchant,
|
|
340
|
+
virtual_account=response.get("virtualAccount"),
|
|
341
|
+
australia_post=response.get("australiaPost"),
|
|
342
|
+
bpay=response.get("bpay"),
|
|
343
|
+
your_bank_account=response.get("yourBankAccount"),
|
|
344
|
+
customer_paypal_account=response.get("customerPayPalAccount"),
|
|
345
|
+
your_paypal_account=response.get("yourPayPalAccount"),
|
|
346
|
+
transaction_date_time=response.get("transactionDateTime"),
|
|
347
|
+
user=response.get("user"),
|
|
348
|
+
settlement_date=response.get("settlementDate"),
|
|
349
|
+
declined_date=response.get("declinedDate"),
|
|
350
|
+
parent_transaction=response.get("parentTransaction"),
|
|
351
|
+
ip_address=response.get("customerIpAddress"),
|
|
352
|
+
fraud_result=response.get("fraudResult"),
|
|
353
|
+
ip_country=response.get("customerIpCountry"),
|
|
354
|
+
card_country=response.get("cardCountry"),
|
|
355
|
+
custom_fields=response.get("customFields"),
|
|
356
|
+
is_voidable=response.get("isVoidable"),
|
|
357
|
+
is_refundable=response.get("isRefundable"),
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@dataclass
|
|
398
362
|
class Merchant:
|
|
399
363
|
"""
|
|
400
364
|
merchantId Issued by us to uniquely identify a merchant facility
|
|
@@ -428,16 +392,17 @@ class Merchant:
|
|
|
428
392
|
"""
|
|
429
393
|
:param: payway_obj: dict PayWay response dictionary
|
|
430
394
|
"""
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
395
|
+
return Merchant(
|
|
396
|
+
merchant_id=payway_obj.get("merchantId"),
|
|
397
|
+
merchant_name=payway_obj.get("merchantName"),
|
|
398
|
+
settlement_bsb=payway_obj.get("settlementBsb"),
|
|
399
|
+
settlement_account_number=payway_obj.get("settlementAccountNumber"),
|
|
400
|
+
surcharge_bsb=payway_obj.get("surchargeBsb"),
|
|
401
|
+
surcharge_account_number=payway_obj.get("surchargeAccountNumber"),
|
|
402
|
+
)
|
|
439
403
|
|
|
440
404
|
|
|
405
|
+
@dataclass
|
|
441
406
|
class PaymentSetup:
|
|
442
407
|
payment_method: str | None = None
|
|
443
408
|
stopped: bool | None = None
|
|
@@ -449,16 +414,23 @@ class PaymentSetup:
|
|
|
449
414
|
"""
|
|
450
415
|
:param: response: dict PayWay response dictionary
|
|
451
416
|
"""
|
|
452
|
-
|
|
453
|
-
ps.payment_method = response.get("paymentMethod")
|
|
454
|
-
ps.stopped = response.get("stopped")
|
|
417
|
+
credit_card = None
|
|
455
418
|
if response.get("creditCard") is not None:
|
|
456
|
-
|
|
419
|
+
credit_card = PayWayCard.from_dict(response.get("creditCard", {}))
|
|
420
|
+
|
|
421
|
+
merchant = None
|
|
457
422
|
if response.get("merchant") is not None:
|
|
458
|
-
|
|
459
|
-
|
|
423
|
+
merchant = Merchant.from_dict(response.get("merchant", {}))
|
|
424
|
+
|
|
425
|
+
return PaymentSetup(
|
|
426
|
+
payment_method=response.get("paymentMethod"),
|
|
427
|
+
stopped=response.get("stopped"),
|
|
428
|
+
credit_card=credit_card,
|
|
429
|
+
merchant=merchant,
|
|
430
|
+
)
|
|
460
431
|
|
|
461
432
|
|
|
433
|
+
@dataclass
|
|
462
434
|
class TokenResponse:
|
|
463
435
|
token: str | None = None
|
|
464
436
|
payment_method: str | None = None
|
|
@@ -470,10 +442,12 @@ class TokenResponse:
|
|
|
470
442
|
"""
|
|
471
443
|
:param: response: dict PayWay response dictionary
|
|
472
444
|
"""
|
|
473
|
-
|
|
474
|
-
tr.token = response.get("singleUseTokenId")
|
|
475
|
-
tr.payment_method = response.get("paymentMethod")
|
|
445
|
+
card = None
|
|
476
446
|
if response.get("creditCard") is not None:
|
|
477
|
-
card = PayWayCard
|
|
478
|
-
|
|
479
|
-
return
|
|
447
|
+
card = PayWayCard.from_dict(response["creditCard"])
|
|
448
|
+
|
|
449
|
+
return TokenResponse(
|
|
450
|
+
token=response.get("singleUseTokenId"),
|
|
451
|
+
payment_method=response.get("paymentMethod"),
|
|
452
|
+
card=card,
|
|
453
|
+
)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-payway"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.7"
|
|
8
8
|
description = "Python client for working with Westpac's PayWay REST API"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Ben Napper", email = "reppan197@gmail.com" }
|
|
@@ -22,11 +22,16 @@ dependencies = [
|
|
|
22
22
|
"requests>=2.20.0",
|
|
23
23
|
]
|
|
24
24
|
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/napper1/python-payway"
|
|
27
|
+
Repository = "https://github.com/napper1/python-payway"
|
|
28
|
+
Issues = "https://github.com/napper1/python-payway/issues"
|
|
29
|
+
|
|
25
30
|
[project.optional-dependencies]
|
|
26
31
|
dev = [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
"pre-commit>=3.5.0",
|
|
33
|
+
"ruff==0.6.6",
|
|
34
|
+
"mypy>=0.971",
|
|
30
35
|
]
|
|
31
36
|
|
|
32
37
|
[tool.mypy]
|
|
@@ -48,53 +53,53 @@ target-version = "py38"
|
|
|
48
53
|
|
|
49
54
|
[tool.ruff.lint]
|
|
50
55
|
select = [
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
56
|
+
"F",
|
|
57
|
+
"E",
|
|
58
|
+
"W",
|
|
59
|
+
"C90",
|
|
60
|
+
"I",
|
|
61
|
+
"N",
|
|
62
|
+
"UP",
|
|
63
|
+
"YTT",
|
|
64
|
+
"ANN",
|
|
65
|
+
"ASYNC",
|
|
66
|
+
"S",
|
|
67
|
+
"BLE",
|
|
68
|
+
"FBT",
|
|
69
|
+
"B",
|
|
70
|
+
"A",
|
|
71
|
+
"COM",
|
|
72
|
+
"C4",
|
|
73
|
+
"DTZ",
|
|
74
|
+
"T10",
|
|
75
|
+
"DJ",
|
|
76
|
+
"EM",
|
|
77
|
+
"EXE",
|
|
78
|
+
"FA",
|
|
79
|
+
'ISC',
|
|
80
|
+
"ICN",
|
|
81
|
+
"G",
|
|
82
|
+
'INP',
|
|
83
|
+
'PIE',
|
|
84
|
+
"T20",
|
|
85
|
+
'PYI',
|
|
86
|
+
'PT',
|
|
87
|
+
"Q",
|
|
88
|
+
"RSE",
|
|
89
|
+
"RET",
|
|
90
|
+
"SLF",
|
|
91
|
+
"SLOT",
|
|
92
|
+
"SIM",
|
|
93
|
+
"TID",
|
|
94
|
+
"INT",
|
|
95
|
+
"ERA",
|
|
96
|
+
"PD",
|
|
97
|
+
"PGH",
|
|
98
|
+
"PL",
|
|
99
|
+
"TRY",
|
|
100
|
+
"FLY",
|
|
101
|
+
"PERF",
|
|
102
|
+
"RUF",
|
|
98
103
|
]
|
|
99
104
|
ignore = ["ANN101", "COM812", "ISC001"]
|
|
100
105
|
|
|
@@ -116,6 +121,10 @@ required-imports = ["from __future__ import annotations"]
|
|
|
116
121
|
[tool.pyright]
|
|
117
122
|
typeCheckingMode = "strict"
|
|
118
123
|
|
|
124
|
+
[tool.ty.environment]
|
|
125
|
+
extra-paths = []
|
|
126
|
+
python-version = "3.8"
|
|
127
|
+
|
|
119
128
|
[tool.pytest.ini_options]
|
|
120
129
|
log_cli = true
|
|
121
130
|
log_cli_level = "INFO"
|
|
@@ -123,11 +132,19 @@ log_format = "%(asctime)s %(levelname)s %(message)s"
|
|
|
123
132
|
log_date_format = "%Y-%m-%d %H:%M:%S"
|
|
124
133
|
addopts = "-v -ra -q"
|
|
125
134
|
filterwarnings = [
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
"ignore:.*defines default_app_config.*",
|
|
136
|
+
"ignore:The providing_args argument is deprecated..*",
|
|
137
|
+
"ignore::DeprecationWarning",
|
|
129
138
|
]
|
|
130
139
|
|
|
131
140
|
[tool.bandit]
|
|
132
141
|
exclude_dirs = ["tests", "migrations"]
|
|
133
142
|
tests = ["B201", "B301"]
|
|
143
|
+
|
|
144
|
+
[dependency-groups]
|
|
145
|
+
dev = [
|
|
146
|
+
"mypy>=0.971",
|
|
147
|
+
"pytest>=8.3.5",
|
|
148
|
+
"ruff>=0.6.6",
|
|
149
|
+
"ty>=0.0.1a33",
|
|
150
|
+
]
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-payway
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
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
|
|
7
|
+
Project-URL: Homepage, https://github.com/napper1/python-payway
|
|
8
|
+
Project-URL: Repository, https://github.com/napper1/python-payway
|
|
9
|
+
Project-URL: Issues, https://github.com/napper1/python-payway/issues
|
|
7
10
|
Keywords: payway,westpac,api,client
|
|
8
11
|
Classifier: Programming Language :: Python :: 3
|
|
9
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -232,7 +235,7 @@ Please follow PayWay's advice about reducing your risk of fraudulent transaction
|
|
|
232
235
|
|
|
233
236
|
```bash
|
|
234
237
|
uv python install 3.8.19
|
|
235
|
-
uv venv
|
|
238
|
+
uv venv --python 3.8.19
|
|
236
239
|
source .venv/bin/activate
|
|
237
240
|
uv sync --extra dev
|
|
238
241
|
```
|
|
@@ -240,5 +243,5 @@ uv sync --extra dev
|
|
|
240
243
|
## Testing
|
|
241
244
|
|
|
242
245
|
```bash
|
|
243
|
-
|
|
246
|
+
uv run pytest tests/ -v
|
|
244
247
|
```
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
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"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|