connections-sdk 2.0.0__py3-none-any.whl → 3.0.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.
- connections_sdk/exceptions.py +1 -4
- connections_sdk/models.py +26 -5
- connections_sdk/providers/adyen.py +41 -30
- connections_sdk/providers/checkout.py +248 -24
- connections_sdk/utils/model_utils.py +34 -6
- connections_sdk/utils/request_client.py +2 -2
- {connections_sdk-2.0.0.dist-info → connections_sdk-3.0.0.dist-info}/METADATA +1 -1
- connections_sdk-3.0.0.dist-info/RECORD +14 -0
- connections_sdk-2.0.0.dist-info/RECORD +0 -14
- {connections_sdk-2.0.0.dist-info → connections_sdk-3.0.0.dist-info}/LICENSE +0 -0
- {connections_sdk-2.0.0.dist-info → connections_sdk-3.0.0.dist-info}/WHEEL +0 -0
connections_sdk/exceptions.py
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
from connections_sdk.models import ErrorResponse
|
|
2
2
|
|
|
3
3
|
class TransactionError(Exception):
|
|
4
|
+
"""Raised when request validation fails."""
|
|
4
5
|
error_response: ErrorResponse
|
|
5
6
|
def __init__(self, error_response: 'ErrorResponse'):
|
|
6
7
|
self.error_response = error_response
|
|
7
8
|
super().__init__(str(error_response.error_codes))
|
|
8
9
|
|
|
9
|
-
class ValidationError(Exception):
|
|
10
|
-
"""Raised when request validation fails."""
|
|
11
|
-
pass
|
|
12
|
-
|
|
13
10
|
class ConfigurationError(Exception):
|
|
14
11
|
"""Raised when SDK configuration is invalid."""
|
|
15
12
|
pass
|
connections_sdk/models.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from enum import Enum
|
|
3
|
-
from typing import Optional, Any, Dict, List
|
|
3
|
+
from typing import Optional, Any, Dict, List, Literal
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
|
|
6
6
|
|
|
@@ -89,7 +89,6 @@ class ErrorType(Enum):
|
|
|
89
89
|
self.code = code
|
|
90
90
|
self.category = category
|
|
91
91
|
|
|
92
|
-
|
|
93
92
|
@dataclass
|
|
94
93
|
class Amount:
|
|
95
94
|
value: int
|
|
@@ -121,6 +120,7 @@ class Customer:
|
|
|
121
120
|
last_name: Optional[str] = None
|
|
122
121
|
email: Optional[str] = None
|
|
123
122
|
address: Optional[Address] = None
|
|
123
|
+
channel: Optional[Literal['ios', 'android', 'web']] = 'web'
|
|
124
124
|
|
|
125
125
|
|
|
126
126
|
@dataclass
|
|
@@ -133,8 +133,17 @@ class StatementDescription:
|
|
|
133
133
|
class ThreeDS:
|
|
134
134
|
eci: Optional[str] = None
|
|
135
135
|
authentication_value: Optional[str] = None
|
|
136
|
-
xid: Optional[str] = None
|
|
137
136
|
version: Optional[str] = None
|
|
137
|
+
ds_transaction_id: Optional[str] = None
|
|
138
|
+
directory_status_code: Optional[str] = None
|
|
139
|
+
authentication_status_code: Optional[str] = None
|
|
140
|
+
challenge_cancel_reason_code: Optional[str] = None
|
|
141
|
+
challenge_preference_code: Optional[str] = None
|
|
142
|
+
authentication_status_reason_code: Optional[str] = None
|
|
143
|
+
|
|
144
|
+
# API aligned fields (preferred)
|
|
145
|
+
threeds_version: Optional[str] = None
|
|
146
|
+
authentication_status_reason: Optional[str] = None
|
|
138
147
|
|
|
139
148
|
|
|
140
149
|
@dataclass
|
|
@@ -175,6 +184,15 @@ class TransactionSource:
|
|
|
175
184
|
id: str
|
|
176
185
|
provisioned: Optional[ProvisionedSource] = None
|
|
177
186
|
|
|
187
|
+
@dataclass
|
|
188
|
+
class ResponseCode:
|
|
189
|
+
category: str
|
|
190
|
+
code: str
|
|
191
|
+
|
|
192
|
+
@dataclass
|
|
193
|
+
class BasisTheoryExtras:
|
|
194
|
+
trace_id: str
|
|
195
|
+
|
|
178
196
|
|
|
179
197
|
@dataclass
|
|
180
198
|
class TransactionResponse:
|
|
@@ -182,11 +200,12 @@ class TransactionResponse:
|
|
|
182
200
|
reference: str
|
|
183
201
|
amount: Amount
|
|
184
202
|
status: TransactionStatus
|
|
203
|
+
response_code: ResponseCode
|
|
185
204
|
source: TransactionSource
|
|
186
205
|
full_provider_response: Dict[str, Any]
|
|
187
206
|
created_at: datetime
|
|
188
207
|
network_transaction_id: Optional[str] = None
|
|
189
|
-
|
|
208
|
+
basis_theory_extras: Optional[BasisTheoryExtras] = None
|
|
190
209
|
|
|
191
210
|
@dataclass
|
|
192
211
|
class RefundResponse:
|
|
@@ -207,4 +226,6 @@ class ErrorCode:
|
|
|
207
226
|
class ErrorResponse:
|
|
208
227
|
error_codes: List[ErrorCode]
|
|
209
228
|
provider_errors: List[str]
|
|
210
|
-
full_provider_response: Dict[str, Any]
|
|
229
|
+
full_provider_response: Dict[str, Any]
|
|
230
|
+
basis_theory_extras: Optional[BasisTheoryExtras] = None
|
|
231
|
+
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Dict, Any, Tuple, Optional, Union, cast
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
3
|
import requests
|
|
4
|
+
from requests.structures import CaseInsensitiveDict
|
|
4
5
|
from deepmerge import always_merger
|
|
5
6
|
from ..models import (
|
|
6
7
|
TransactionRequest,
|
|
@@ -22,9 +23,10 @@ from ..models import (
|
|
|
22
23
|
ErrorCode,
|
|
23
24
|
TransactionResponse,
|
|
24
25
|
TransactionSource,
|
|
25
|
-
ProvisionedSource
|
|
26
|
+
ProvisionedSource,
|
|
27
|
+
ResponseCode
|
|
26
28
|
)
|
|
27
|
-
from ..utils.model_utils import create_transaction_request, validate_required_fields
|
|
29
|
+
from ..utils.model_utils import create_transaction_request, validate_required_fields, _basis_theory_extras
|
|
28
30
|
from ..utils.request_client import RequestClient
|
|
29
31
|
from ..exceptions import TransactionError
|
|
30
32
|
|
|
@@ -121,6 +123,7 @@ class AdyenClient:
|
|
|
121
123
|
"merchantAccount": self.merchant_account,
|
|
122
124
|
"shopperInteraction": "ContAuth" if request.merchant_initiated else "Ecommerce",
|
|
123
125
|
"storePaymentMethod": request.source.store_with_provider,
|
|
126
|
+
"channel": request.customer.channel if request.customer else 'web'
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
if request.metadata:
|
|
@@ -206,22 +209,32 @@ class AdyenClient:
|
|
|
206
209
|
|
|
207
210
|
# Map 3DS information
|
|
208
211
|
if request.three_ds:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if request.three_ds.eci:
|
|
212
|
-
three_ds_data["eci"] = request.three_ds.eci
|
|
212
|
+
mpi_data: Dict[str, Any] = {}
|
|
213
|
+
three_ds_2_request_data: Dict[str, Any] = {}
|
|
213
214
|
|
|
214
215
|
if request.three_ds.authentication_value:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
mpi_data["cavv"] = request.three_ds.authentication_value
|
|
217
|
+
if request.three_ds.eci:
|
|
218
|
+
mpi_data["eci"] = request.three_ds.eci
|
|
219
|
+
if request.three_ds.ds_transaction_id:
|
|
220
|
+
mpi_data["dsTransID"] = request.three_ds.ds_transaction_id
|
|
221
|
+
if request.three_ds.directory_status_code:
|
|
222
|
+
mpi_data["directoryResponse"] = request.three_ds.directory_status_code
|
|
223
|
+
if request.three_ds.authentication_status_code:
|
|
224
|
+
mpi_data["authenticationResponse"] = request.three_ds.authentication_status_code
|
|
225
|
+
if request.three_ds.threeds_version or request.three_ds.version: # threeds_version from API, fallback to version
|
|
226
|
+
mpi_data["threeDSVersion"] = request.three_ds.threeds_version or request.three_ds.version
|
|
227
|
+
if request.three_ds.challenge_cancel_reason_code:
|
|
228
|
+
mpi_data["challengeCancel"] = request.three_ds.challenge_cancel_reason_code
|
|
229
|
+
|
|
230
|
+
if mpi_data:
|
|
231
|
+
payload["mpiData"] = mpi_data
|
|
219
232
|
|
|
220
|
-
if request.three_ds.
|
|
221
|
-
|
|
233
|
+
if request.three_ds.challenge_preference_code:
|
|
234
|
+
three_ds_2_request_data["threeDSRequestorChallengeInd"] = request.three_ds.challenge_preference_code
|
|
222
235
|
|
|
223
|
-
if
|
|
224
|
-
payload["
|
|
236
|
+
if three_ds_2_request_data:
|
|
237
|
+
payload["threeDS2RequestData"] = three_ds_2_request_data
|
|
225
238
|
|
|
226
239
|
# Override/merge any provider properties if specified
|
|
227
240
|
if request.override_provider_properties:
|
|
@@ -229,25 +242,30 @@ class AdyenClient:
|
|
|
229
242
|
|
|
230
243
|
return payload
|
|
231
244
|
|
|
232
|
-
def _transform_adyen_response(self, response_data: Dict[str, Any], request: TransactionRequest) -> TransactionResponse:
|
|
245
|
+
def _transform_adyen_response(self, response_data: Dict[str, Any], request: TransactionRequest, headers: CaseInsensitiveDict) -> TransactionResponse:
|
|
233
246
|
"""Transform Adyen response to our standardized format."""
|
|
234
247
|
transaction_response = TransactionResponse(
|
|
235
248
|
id=str(response_data.get("pspReference")),
|
|
236
249
|
reference=str(response_data.get("merchantReference")),
|
|
237
250
|
amount=Amount(
|
|
238
|
-
value=int(response_data.get("amount", {}).get("value")),
|
|
239
|
-
currency=str(response_data.get("amount", {}).get("currency"))
|
|
251
|
+
value=int(response_data.get("amount", {}).get("value", request.amount.value)),
|
|
252
|
+
currency=str(response_data.get("amount", {}).get("currency", request.amount.currency))
|
|
240
253
|
),
|
|
241
254
|
status=TransactionStatus(
|
|
242
255
|
code=self._get_status_code(response_data.get("resultCode")),
|
|
243
256
|
provider_code=str(response_data.get("resultCode"))
|
|
244
257
|
),
|
|
258
|
+
response_code=ResponseCode(
|
|
259
|
+
category=ERROR_CODE_MAPPING.get(str(response_data.get("refusalReasonCode")), ErrorType.OTHER).category,
|
|
260
|
+
code=ERROR_CODE_MAPPING.get(str(response_data.get("refusalReasonCode")), ErrorType.OTHER).code
|
|
261
|
+
),
|
|
245
262
|
source=TransactionSource(
|
|
246
263
|
type=request.source.type,
|
|
247
264
|
id=request.source.id,
|
|
248
265
|
),
|
|
249
266
|
network_transaction_id=str(response_data.get("additionalData", {}).get("networkTxReference")),
|
|
250
267
|
full_provider_response=response_data,
|
|
268
|
+
basis_theory_extras=_basis_theory_extras(headers),
|
|
251
269
|
created_at=datetime.now(timezone.utc)
|
|
252
270
|
)
|
|
253
271
|
|
|
@@ -261,7 +279,7 @@ class AdyenClient:
|
|
|
261
279
|
|
|
262
280
|
return transaction_response
|
|
263
281
|
|
|
264
|
-
def _transform_error_response(self, response: requests.Response, response_data: Dict[str, Any]) -> ErrorResponse:
|
|
282
|
+
def _transform_error_response(self, response: requests.Response, response_data: Dict[str, Any], headers: CaseInsensitiveDict) -> ErrorResponse:
|
|
265
283
|
"""Transform error responses to our standardized format.
|
|
266
284
|
|
|
267
285
|
Args:
|
|
@@ -276,10 +294,6 @@ class AdyenClient:
|
|
|
276
294
|
error_type = ErrorType.INVALID_API_KEY
|
|
277
295
|
elif response.status_code == 403:
|
|
278
296
|
error_type = ErrorType.UNAUTHORIZED
|
|
279
|
-
# Handle Adyen-specific error codes for declined transactions
|
|
280
|
-
elif response_data.get("resultCode") in ["Refused", "Error", "Cancelled"]:
|
|
281
|
-
refusal_code = response_data.get("refusalReasonCode", "")
|
|
282
|
-
error_type = ERROR_CODE_MAPPING.get(refusal_code, ErrorType.OTHER)
|
|
283
297
|
else:
|
|
284
298
|
error_type = ErrorType.OTHER
|
|
285
299
|
|
|
@@ -291,7 +305,8 @@ class AdyenClient:
|
|
|
291
305
|
)
|
|
292
306
|
],
|
|
293
307
|
provider_errors=[response_data.get("refusalReason") or response_data.get("message", "")],
|
|
294
|
-
full_provider_response=response_data
|
|
308
|
+
full_provider_response=response_data,
|
|
309
|
+
basis_theory_extras=_basis_theory_extras(headers)
|
|
295
310
|
)
|
|
296
311
|
|
|
297
312
|
|
|
@@ -320,12 +335,8 @@ class AdyenClient:
|
|
|
320
335
|
|
|
321
336
|
response_data = response.json()
|
|
322
337
|
|
|
323
|
-
# Check if it's an error response (non-200 status code or Adyen error)
|
|
324
|
-
if not response.ok or response_data.get("resultCode") in ["Refused", "Error", "Cancelled"]:
|
|
325
|
-
raise TransactionError(self._transform_error_response(response, response_data))
|
|
326
|
-
|
|
327
338
|
# Transform the successful response to our format
|
|
328
|
-
return self._transform_adyen_response(response_data, request_data)
|
|
339
|
+
return self._transform_adyen_response(response_data, request_data, response.headers)
|
|
329
340
|
|
|
330
341
|
except requests.exceptions.HTTPError as e:
|
|
331
342
|
try:
|
|
@@ -333,7 +344,7 @@ class AdyenClient:
|
|
|
333
344
|
except:
|
|
334
345
|
error_data = None
|
|
335
346
|
|
|
336
|
-
raise TransactionError(self._transform_error_response(e.response, error_data))
|
|
347
|
+
raise TransactionError(self._transform_error_response(e.response, error_data, e.response.headers))
|
|
337
348
|
|
|
338
349
|
|
|
339
350
|
def refund_transaction(self, refund_request: RefundRequest) -> RefundResponse:
|
|
@@ -401,5 +412,5 @@ class AdyenClient:
|
|
|
401
412
|
except:
|
|
402
413
|
error_data = None
|
|
403
414
|
|
|
404
|
-
raise TransactionError(self._transform_error_response(e.response, error_data))
|
|
415
|
+
raise TransactionError(self._transform_error_response(e.response, error_data, e.response.headers))
|
|
405
416
|
|
|
@@ -5,7 +5,7 @@ import requests
|
|
|
5
5
|
import os
|
|
6
6
|
import json
|
|
7
7
|
from json.decoder import JSONDecodeError
|
|
8
|
-
|
|
8
|
+
from requests.structures import CaseInsensitiveDict
|
|
9
9
|
from ..models import (
|
|
10
10
|
TransactionRequest,
|
|
11
11
|
Amount,
|
|
@@ -26,10 +26,11 @@ from ..models import (
|
|
|
26
26
|
ErrorCode,
|
|
27
27
|
TransactionResponse,
|
|
28
28
|
TransactionSource,
|
|
29
|
-
ProvisionedSource
|
|
29
|
+
ProvisionedSource,
|
|
30
|
+
ResponseCode
|
|
30
31
|
)
|
|
31
32
|
from connections_sdk.exceptions import TransactionError
|
|
32
|
-
from ..utils.model_utils import create_transaction_request, validate_required_fields
|
|
33
|
+
from ..utils.model_utils import create_transaction_request, validate_required_fields, _basis_theory_extras
|
|
33
34
|
from ..utils.request_client import RequestClient
|
|
34
35
|
|
|
35
36
|
|
|
@@ -169,6 +170,183 @@ ERROR_CODE_MAPPING = {
|
|
|
169
170
|
"refund_authorization_declined": ErrorType.REFUND_DECLINED
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
# Mapping of Checkout.com numerical response codes to our error types
|
|
174
|
+
CHECKOUT_NUMERICAL_CODE_MAPPING = {
|
|
175
|
+
# 20xxx Series - Generally Soft Declines / Informational
|
|
176
|
+
"20001": ErrorType.REFERRAL, # Refer to card issuer
|
|
177
|
+
"20002": ErrorType.REFERRAL, # Refer to card issuer - Special conditions
|
|
178
|
+
"20003": ErrorType.CONFIGURATION_ERROR, # Invalid merchant or service provider
|
|
179
|
+
"20004": ErrorType.BLOCKED_CARD, # Card should be captured
|
|
180
|
+
"20005": ErrorType.REFUSED, # Declined - Do not honour
|
|
181
|
+
"20006": ErrorType.OTHER, # Error / Invalid request parameters
|
|
182
|
+
"20009": ErrorType.OTHER, # Request in progress (treating as a final decline state if listed as error)
|
|
183
|
+
"20012": ErrorType.OTHER, # Invalid transaction
|
|
184
|
+
"20013": ErrorType.INVALID_AMOUNT, # Invalid value/amount
|
|
185
|
+
"20014": ErrorType.INVALID_CARD, # Invalid account number (no such number)
|
|
186
|
+
"20015": ErrorType.NOT_SUPPORTED, # Transaction cannot be processed through debit network
|
|
187
|
+
"20016": ErrorType.RESTRICTED_CARD, # Card not initialised
|
|
188
|
+
"20017": ErrorType.PAYMENT_CANCELLED_BY_CONSUMER, # Customer cancellation
|
|
189
|
+
"20018": ErrorType.OTHER, # Customer dispute (more of a post-transaction event, but if it's an error code at payment time)
|
|
190
|
+
"20019": ErrorType.PAYMENT_CANCELLED, # Re-enter transaction Transaction has expired
|
|
191
|
+
"20020": ErrorType.OTHER, # Invalid response
|
|
192
|
+
"20021": ErrorType.OTHER, # No action taken (unable to back out prior transaction)
|
|
193
|
+
"20022": ErrorType.ACQUIRER_ERROR, # Suspected malfunction
|
|
194
|
+
"20023": ErrorType.OTHER, # Unacceptable transaction fee
|
|
195
|
+
"20024": ErrorType.NOT_SUPPORTED, # File update not supported by the receiver
|
|
196
|
+
"20025": ErrorType.OTHER, # Unable to locate record on file Account number is missing from the inquiry
|
|
197
|
+
"20026": ErrorType.OTHER, # Duplicate file update record
|
|
198
|
+
"20027": ErrorType.OTHER, # File update field edit error
|
|
199
|
+
"20028": ErrorType.OTHER, # File is temporarily unavailable
|
|
200
|
+
"20029": ErrorType.OTHER, # File update not successful
|
|
201
|
+
"20030": ErrorType.OTHER, # Format error
|
|
202
|
+
"20031": ErrorType.NOT_SUPPORTED, # Bank not supported by Switch
|
|
203
|
+
"20032": ErrorType.OTHER, # Completed partially (typically a success state, but if listed as error)
|
|
204
|
+
"20033": ErrorType.OTHER, # Previous scheme transaction ID invalid
|
|
205
|
+
"20038": ErrorType.PIN_TRIES_EXCEEDED, # Allowable PIN tries exceeded
|
|
206
|
+
"20039": ErrorType.INVALID_CARD, # No credit account
|
|
207
|
+
"20040": ErrorType.NOT_SUPPORTED, # Requested function not supported
|
|
208
|
+
"20042": ErrorType.INVALID_AMOUNT, # No universal value/amount
|
|
209
|
+
"20044": ErrorType.INVALID_CARD, # No investment account
|
|
210
|
+
"20045": ErrorType.NOT_SUPPORTED, # The Issuer does not support fallback transactions of hybrid-card
|
|
211
|
+
"20046": ErrorType.BANK_ERROR, # Bank decline
|
|
212
|
+
"20051": ErrorType.INSUFFICENT_FUNDS, # Insufficient funds
|
|
213
|
+
"20052": ErrorType.INVALID_CARD, # No current (checking) account
|
|
214
|
+
"20053": ErrorType.INVALID_CARD, # No savings account
|
|
215
|
+
"20054": ErrorType.EXPIRED_CARD, # Expired card
|
|
216
|
+
"20055": ErrorType.INVALID_PIN, # Incorrect PIN PIN validation not possible
|
|
217
|
+
"20056": ErrorType.INVALID_CARD, # No card record
|
|
218
|
+
"20057": ErrorType.NOT_SUPPORTED, # Transaction not permitted to cardholder
|
|
219
|
+
"20058": ErrorType.NOT_SUPPORTED, # Transaction not permitted to terminal
|
|
220
|
+
"20059": ErrorType.FRAUD, # Suspected fraud
|
|
221
|
+
"20060": ErrorType.REFERRAL, # Card acceptor contact acquirer
|
|
222
|
+
"20061": ErrorType.INSUFFICENT_FUNDS, # Activity amount limit exceeded
|
|
223
|
+
"20062": ErrorType.RESTRICTED_CARD, # Restricted card
|
|
224
|
+
"20063": ErrorType.FRAUD, # Security violation
|
|
225
|
+
"20064": ErrorType.OTHER, # Transaction does not fulfil AML requirement
|
|
226
|
+
"20065": ErrorType.INSUFFICENT_FUNDS, # Exceeds Withdrawal Frequency Limit
|
|
227
|
+
"20066": ErrorType.REFERRAL, # Card acceptor call acquirer security
|
|
228
|
+
"20067": ErrorType.BLOCKED_CARD, # Hard capture - Pick up card at ATM
|
|
229
|
+
"20068": ErrorType.ACQUIRER_ERROR, # Response received too late / Timeout
|
|
230
|
+
"20072": ErrorType.RESTRICTED_CARD, # Account not yet activated
|
|
231
|
+
"20075": ErrorType.PIN_TRIES_EXCEEDED, # Allowable PIN-entry tries exceeded
|
|
232
|
+
"20078": ErrorType.BLOCKED_CARD, # Blocked at first use
|
|
233
|
+
"20081": ErrorType.NOT_SUPPORTED, # Card is local use only
|
|
234
|
+
"20082": ErrorType.CVC_INVALID, # No security model / Negative CAM, dCVV, iCVV, or CVV results
|
|
235
|
+
"20083": ErrorType.INVALID_CARD, # No accounts
|
|
236
|
+
"20084": ErrorType.OTHER, # No PBF
|
|
237
|
+
"20085": ErrorType.OTHER, # PBF update error
|
|
238
|
+
"20086": ErrorType.ACQUIRER_ERROR, # ATM malfunction Invalid authorization type
|
|
239
|
+
"20087": ErrorType.INVALID_CARD, # Bad track data (invalid CVV and/or expiry date)
|
|
240
|
+
"20088": ErrorType.OTHER, # Unable to dispense/process
|
|
241
|
+
"20089": ErrorType.OTHER, # Administration error
|
|
242
|
+
"20090": ErrorType.ACQUIRER_ERROR, # Cut-off in progress
|
|
243
|
+
"20091": ErrorType.BANK_ERROR, # Issuer unavailable or switch is inoperative
|
|
244
|
+
"20092": ErrorType.ACQUIRER_ERROR, # Destination cannot be found for routing
|
|
245
|
+
"20093": ErrorType.NOT_SUPPORTED, # Transaction cannot be completed; violation of law
|
|
246
|
+
"20094": ErrorType.OTHER, # Duplicate transmission / invoice
|
|
247
|
+
"20095": ErrorType.OTHER, # Reconcile error
|
|
248
|
+
"20096": ErrorType.ACQUIRER_ERROR, # System malfunction
|
|
249
|
+
"20097": ErrorType.OTHER, # Reconciliation totals reset
|
|
250
|
+
"20098": ErrorType.OTHER, # MAC error
|
|
251
|
+
"20099": ErrorType.OTHER, # Other / Unidentified responses
|
|
252
|
+
"20197": ErrorType.OTHER, # Catch-all for many sub-errors like CVV2 failure, transaction not supported. Mapping to OTHER due to its composite nature.
|
|
253
|
+
"20100": ErrorType.INVALID_CARD, # Invalid expiry date format
|
|
254
|
+
"20101": ErrorType.INVALID_SOURCE_TOKEN, # No Account / No Customer (Token is incorrect or invalid)
|
|
255
|
+
"20102": ErrorType.CONFIGURATION_ERROR, # Invalid merchant / wallet ID
|
|
256
|
+
"20103": ErrorType.NOT_SUPPORTED, # Card type / payment method not supported
|
|
257
|
+
"20104": ErrorType.OTHER, # Gateway reject - Invalid transaction
|
|
258
|
+
"20105": ErrorType.OTHER, # Gateway reject - Violation
|
|
259
|
+
"20106": ErrorType.NOT_SUPPORTED, # Unsupported currency
|
|
260
|
+
"20107": ErrorType.OTHER, # Billing address is missing (Could be AVS_DECLINE if validation fails, but often a data validation before submission)
|
|
261
|
+
"20108": ErrorType.REFUSED, # Declined - Updated cardholder available
|
|
262
|
+
"20109": ErrorType.OTHER, # Transaction already reversed (voided) Capture is larger than initial authorized value
|
|
263
|
+
"20110": ErrorType.OTHER, # Authorization completed (Not an error, but if returned in error context)
|
|
264
|
+
"20111": ErrorType.OTHER, # Transaction already reversed
|
|
265
|
+
"20112": ErrorType.CONFIGURATION_ERROR, # Merchant not Mastercard SecureCode enabled
|
|
266
|
+
"20113": ErrorType.OTHER, # Invalid property
|
|
267
|
+
"20114": ErrorType.INVALID_SOURCE_TOKEN, # Token is incorrect
|
|
268
|
+
"20115": ErrorType.OTHER, # Missing / Invalid lifetime
|
|
269
|
+
"20116": ErrorType.OTHER, # Invalid encoding
|
|
270
|
+
"20117": ErrorType.CONFIGURATION_ERROR, # Invalid API version
|
|
271
|
+
"20118": ErrorType.OTHER, # Transaction pending
|
|
272
|
+
"20119": ErrorType.OTHER, # Invalid batch data and/or batch data is missing
|
|
273
|
+
"20120": ErrorType.OTHER, # Invalid customer/user
|
|
274
|
+
"20121": ErrorType.OTHER, # Transaction limit for merchant/terminal exceeded
|
|
275
|
+
"20122": ErrorType.NOT_SUPPORTED, # Mastercard installments not supported
|
|
276
|
+
"20123": ErrorType.OTHER, # Missing basic data: zip, addr, member
|
|
277
|
+
"20124": ErrorType.CVC_INVALID, # Missing CVV value, required for ecommerce transaction
|
|
278
|
+
"20150": ErrorType.AUTHENTICATION_FAILURE, # Card not 3D Secure (3DS) enabled
|
|
279
|
+
"20151": ErrorType.AUTHENTICATION_FAILURE, # Cardholder failed 3DS authentication
|
|
280
|
+
"20152": ErrorType.AUTHENTICATION_FAILURE, # Initial 3DS transaction not completed within 15 minutes
|
|
281
|
+
"20153": ErrorType.AUTHENTICATION_FAILURE, # 3DS system malfunction
|
|
282
|
+
"20154": ErrorType.AUTHENTICATION_REQUIRED, # 3DS authentication required
|
|
283
|
+
"20155": ErrorType.AUTHENTICATION_FAILURE, # 3DS authentication service provided invalid authentication result
|
|
284
|
+
"20156": ErrorType.NOT_SUPPORTED, # Requested function not supported by the acquirer
|
|
285
|
+
"20157": ErrorType.CONFIGURATION_ERROR, # Invalid merchant configurations - Contact Support
|
|
286
|
+
"20158": ErrorType.OTHER, # Refund validity period has expired
|
|
287
|
+
"20159": ErrorType.AUTHENTICATION_FAILURE, # ACS Malfunction
|
|
288
|
+
"20179": ErrorType.INVALID_CARD, # Lifecycle (Occurs when transaction has invalid card data)
|
|
289
|
+
"20182": ErrorType.NOT_SUPPORTED, # Policy (Occurs when a transaction does not comply with card policy)
|
|
290
|
+
"20183": ErrorType.FRAUD, # Security (Occurs when a transaction is suspected to be fraudulent)
|
|
291
|
+
"20193": ErrorType.OTHER, # Invalid country code
|
|
292
|
+
|
|
293
|
+
# 30xxx Series - Hard Declines
|
|
294
|
+
"30004": ErrorType.BLOCKED_CARD, # Pick up card (No fraud)
|
|
295
|
+
"30007": ErrorType.BLOCKED_CARD, # Pick up card - Special conditions
|
|
296
|
+
"30015": ErrorType.INVALID_CARD, # No such issuer
|
|
297
|
+
"30016": ErrorType.NOT_SUPPORTED, # Issuer does not allow online gambling payout
|
|
298
|
+
"30017": ErrorType.NOT_SUPPORTED, # Issuer does not allow original credit transaction
|
|
299
|
+
"30018": ErrorType.NOT_SUPPORTED, # Issuer does not allow money transfer payout
|
|
300
|
+
"30019": ErrorType.NOT_SUPPORTED, # Issuer does not allow non-money transfer payout
|
|
301
|
+
"30020": ErrorType.INVALID_AMOUNT, # Invalid amount
|
|
302
|
+
"30021": ErrorType.INSUFFICENT_FUNDS, # Total amount limit reached
|
|
303
|
+
"30022": ErrorType.OTHER, # Total transaction count limit reached
|
|
304
|
+
"30033": ErrorType.EXPIRED_CARD, # Expired card - Pick up
|
|
305
|
+
"30034": ErrorType.FRAUD, # Suspected fraud - Pick up
|
|
306
|
+
"30035": ErrorType.REFERRAL, # Contact acquirer - Pick up
|
|
307
|
+
"30036": ErrorType.RESTRICTED_CARD, # Restricted card - Pick up
|
|
308
|
+
"30037": ErrorType.REFERRAL, # Call acquirer security - Pick up
|
|
309
|
+
"30038": ErrorType.PIN_TRIES_EXCEEDED, # Allowable PIN tries exceeded - Pick up
|
|
310
|
+
"30041": ErrorType.BLOCKED_CARD, # Lost card - Pick up
|
|
311
|
+
"30043": ErrorType.FRAUD, # Stolen card - Pick up
|
|
312
|
+
"30044": ErrorType.NOT_SUPPORTED, # Transaction rejected - AMLD5
|
|
313
|
+
"30045": ErrorType.NOT_SUPPORTED, # Invalid payout fund transfer type
|
|
314
|
+
"30046": ErrorType.INVALID_CARD, # Closed account
|
|
315
|
+
|
|
316
|
+
# 4xxxx Series - Risk Responses
|
|
317
|
+
"40101": ErrorType.FRAUD, # Risk blocked transaction
|
|
318
|
+
"40201": ErrorType.FRAUD, # Gateway reject - card number blocklist
|
|
319
|
+
"40202": ErrorType.FRAUD, # Gateway reject - IP address blocklist
|
|
320
|
+
"40203": ErrorType.FRAUD, # Gateway reject - email blocklist
|
|
321
|
+
"40204": ErrorType.FRAUD, # Gateway reject - phone number blocklist
|
|
322
|
+
"40205": ErrorType.FRAUD, # Gateway Reject - BIN number blocklist
|
|
323
|
+
"41101": ErrorType.FRAUD, # Risk Blocked Transaction (Client-level rule)
|
|
324
|
+
"41201": ErrorType.FRAUD, # Decline list - Card number (Client-level)
|
|
325
|
+
"41202": ErrorType.FRAUD, # Decline list - BIN (Client-level)
|
|
326
|
+
"41203": ErrorType.FRAUD, # Decline list - Email address (Client-level)
|
|
327
|
+
"41204": ErrorType.FRAUD, # Decline list - Phone (Client-level)
|
|
328
|
+
"41205": ErrorType.FRAUD, # Decline list - Payment IP (Client-level) - using client as first seen
|
|
329
|
+
"41206": ErrorType.FRAUD, # Decline list - Email domain (Client-level)
|
|
330
|
+
"41301": ErrorType.FRAUD, # Fraud score exceeds threshold (Client-level)
|
|
331
|
+
"42101": ErrorType.FRAUD, # Risk Blocked Transaction (Entity-level rule)
|
|
332
|
+
"42201": ErrorType.FRAUD, # Decline list - Card number (Client-level)
|
|
333
|
+
"42202": ErrorType.FRAUD, # Decline list - BIN (Client-level)
|
|
334
|
+
"42203": ErrorType.FRAUD, # Decline list - Email address (Client-level)
|
|
335
|
+
"42204": ErrorType.FRAUD, # Decline list - Phone (Client-level)
|
|
336
|
+
"42206": ErrorType.FRAUD, # Decline list - Email domain (Client-level)
|
|
337
|
+
"42301": ErrorType.FRAUD, # Fraud score exceeds threshold
|
|
338
|
+
"43101": ErrorType.FRAUD, # Potential fraud risk
|
|
339
|
+
"43102": ErrorType.FRAUD, # Risk blocked transaction – {Rule group name} (Checkout.com-level)
|
|
340
|
+
"43201": ErrorType.FRAUD, # Decline list - Card number (Checkout.com-level)
|
|
341
|
+
"43202": ErrorType.FRAUD, # Decline list - BIN (Checkout.com-level)
|
|
342
|
+
"43203": ErrorType.FRAUD, # Decline list - Email address (Checkout.com-level)
|
|
343
|
+
"43204": ErrorType.FRAUD, # Decline list - Phone (Checkout.com-level)
|
|
344
|
+
"43205": ErrorType.FRAUD, # Decline list - Payment IP (Checkout.com-level)
|
|
345
|
+
"43206": ErrorType.FRAUD, # Decline list - Email domain (Checkout.com-level)
|
|
346
|
+
"43301": ErrorType.FRAUD, # Fraud score exceeds threshold (Checkout.com-level)
|
|
347
|
+
"44301": ErrorType.AUTHENTICATION_REQUIRED, # 3DS authentication required
|
|
348
|
+
}
|
|
349
|
+
|
|
172
350
|
|
|
173
351
|
class CheckoutClient:
|
|
174
352
|
def __init__(self, private_key: str, processing_channel: str, is_test: bool, bt_api_key: str):
|
|
@@ -268,15 +446,34 @@ class CheckoutClient:
|
|
|
268
446
|
|
|
269
447
|
# Add 3DS information if provided
|
|
270
448
|
if request.three_ds:
|
|
271
|
-
three_ds_data: Dict[str,
|
|
272
|
-
|
|
273
|
-
|
|
449
|
+
three_ds_data: Dict[str, Any] = {
|
|
450
|
+
"enabled": True
|
|
451
|
+
}
|
|
452
|
+
|
|
274
453
|
if request.three_ds.authentication_value:
|
|
275
454
|
three_ds_data["cryptogram"] = request.three_ds.authentication_value
|
|
276
|
-
if request.three_ds.
|
|
277
|
-
three_ds_data["
|
|
278
|
-
if request.three_ds.version:
|
|
279
|
-
three_ds_data["version"] = request.three_ds.version
|
|
455
|
+
if request.three_ds.eci:
|
|
456
|
+
three_ds_data["eci"] = request.three_ds.eci
|
|
457
|
+
if request.three_ds.threeds_version or request.three_ds.version: # threeds_version from API, fallback to version
|
|
458
|
+
three_ds_data["version"] = request.three_ds.threeds_version or request.three_ds.version
|
|
459
|
+
if request.three_ds.ds_transaction_id: # ds_transaction_id in BT, xid in Checkout
|
|
460
|
+
three_ds_data["xid"] = request.three_ds.ds_transaction_id
|
|
461
|
+
if request.three_ds.authentication_status_code:
|
|
462
|
+
three_ds_data["status"] = request.three_ds.authentication_status_code
|
|
463
|
+
if request.three_ds.authentication_status_reason_code:
|
|
464
|
+
three_ds_data["status_reason_code"] = request.three_ds.authentication_status_reason_code
|
|
465
|
+
|
|
466
|
+
if request.three_ds.challenge_preference_code:
|
|
467
|
+
challenge_indicator_mapping = {
|
|
468
|
+
"no-preference": "no_preference",
|
|
469
|
+
"no-challenge": "no_challenge_requested",
|
|
470
|
+
"challenge-requested": "challenge_requested",
|
|
471
|
+
"challenge-mandated": "challenge_requested_mandate"
|
|
472
|
+
}
|
|
473
|
+
checkout_challenge_indicator = challenge_indicator_mapping.get(request.three_ds.challenge_preference_code)
|
|
474
|
+
if checkout_challenge_indicator: # Only add if a valid mapping exists
|
|
475
|
+
three_ds_data["challenge_indicator"] = checkout_challenge_indicator
|
|
476
|
+
|
|
280
477
|
payload["3ds"] = three_ds_data
|
|
281
478
|
|
|
282
479
|
# Override/merge any provider properties if specified
|
|
@@ -285,19 +482,34 @@ class CheckoutClient:
|
|
|
285
482
|
|
|
286
483
|
return payload
|
|
287
484
|
|
|
288
|
-
def _transform_checkout_response(self, response_data: Dict[str, Any], request: TransactionRequest) -> TransactionResponse:
|
|
485
|
+
def _transform_checkout_response(self, response_data: Dict[str, Any], request: TransactionRequest, headers: CaseInsensitiveDict, error_data: Optional[Dict[str, Any]] = None) -> TransactionResponse:
|
|
289
486
|
"""Transform Checkout.com response to our standardized format."""
|
|
487
|
+
response_code = ResponseCode(
|
|
488
|
+
category=CHECKOUT_NUMERICAL_CODE_MAPPING.get(str(response_data.get("response_code")), ErrorType.OTHER).category,
|
|
489
|
+
code=CHECKOUT_NUMERICAL_CODE_MAPPING.get(str(response_data.get("response_code")), ErrorType.OTHER).code
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
if error_data and isinstance(error_data, dict):
|
|
493
|
+
error_codes = error_data.get("error_codes", [])
|
|
494
|
+
if error_codes and len(error_codes) > 0:
|
|
495
|
+
first_error = str(error_codes[0])
|
|
496
|
+
response_code = ResponseCode(
|
|
497
|
+
category=ERROR_CODE_MAPPING.get(first_error, ErrorType.OTHER).category,
|
|
498
|
+
code=ERROR_CODE_MAPPING.get(first_error, ErrorType.OTHER).code
|
|
499
|
+
)
|
|
500
|
+
|
|
290
501
|
return TransactionResponse(
|
|
291
502
|
id=str(response_data.get("id")),
|
|
292
503
|
reference=str(response_data.get("reference")),
|
|
293
504
|
amount=Amount(
|
|
294
|
-
value=int(str(response_data.get("amount"))),
|
|
295
|
-
currency=str(response_data.get("currency"))
|
|
505
|
+
value=int(str(response_data.get("amount", request.amount.value))),
|
|
506
|
+
currency=str(response_data.get("currency", request.amount.currency))
|
|
296
507
|
),
|
|
297
508
|
status=TransactionStatus(
|
|
298
|
-
code=self._get_status_code(response_data.get("status")),
|
|
299
|
-
provider_code=str(response_data.get("status"))
|
|
509
|
+
code=self._get_status_code(response_data.get("status", TransactionStatusCode.DECLINED)),
|
|
510
|
+
provider_code=str(response_data.get("status", ""))
|
|
300
511
|
),
|
|
512
|
+
response_code=response_code,
|
|
301
513
|
source=TransactionSource(
|
|
302
514
|
type=request.source.type,
|
|
303
515
|
id=request.source.id,
|
|
@@ -306,8 +518,9 @@ class CheckoutClient:
|
|
|
306
518
|
) if response_data.get("source", {}).get("id") else None
|
|
307
519
|
),
|
|
308
520
|
full_provider_response=response_data,
|
|
521
|
+
basis_theory_extras=_basis_theory_extras(headers),
|
|
309
522
|
created_at=datetime.fromisoformat(response_data["processed_on"].split(".")[0] + "+00:00") if response_data.get("processed_on") else datetime.now(timezone.utc),
|
|
310
|
-
network_transaction_id=
|
|
523
|
+
network_transaction_id=response_data.get("scheme_id")
|
|
311
524
|
)
|
|
312
525
|
|
|
313
526
|
def _get_error_code(self, error: ErrorType) -> Dict[str, Any]:
|
|
@@ -322,14 +535,17 @@ class CheckoutClient:
|
|
|
322
535
|
code=error.code
|
|
323
536
|
)
|
|
324
537
|
|
|
325
|
-
def _transform_error_response_object(self, response, error_data=None) -> ErrorResponse:
|
|
538
|
+
def _transform_error_response_object(self, response, error_data=None, headers=None) -> ErrorResponse:
|
|
326
539
|
"""Transform error response from Checkout.com to SDK format."""
|
|
327
540
|
error_codes = []
|
|
541
|
+
provider_errors = error_data.get('error_codes', []) if error_data else []
|
|
328
542
|
|
|
329
543
|
if response.status_code == 401:
|
|
330
544
|
error_codes.append(self._get_error_code_object(ErrorType.INVALID_API_KEY))
|
|
331
545
|
elif response.status_code == 403:
|
|
332
546
|
error_codes.append(self._get_error_code_object(ErrorType.UNAUTHORIZED))
|
|
547
|
+
elif response.status_code == 404:
|
|
548
|
+
error_codes.append(self._get_error_code_object(ErrorType.REFUND_FAILED))
|
|
333
549
|
elif error_data is not None:
|
|
334
550
|
for error_code in error_data.get('error_codes', []):
|
|
335
551
|
mapped_error = ERROR_CODE_MAPPING.get(error_code, ErrorType.OTHER)
|
|
@@ -342,7 +558,8 @@ class CheckoutClient:
|
|
|
342
558
|
|
|
343
559
|
return ErrorResponse(
|
|
344
560
|
error_codes=error_codes,
|
|
345
|
-
provider_errors=
|
|
561
|
+
provider_errors=provider_errors,
|
|
562
|
+
basis_theory_extras=_basis_theory_extras(headers),
|
|
346
563
|
full_provider_response=error_data
|
|
347
564
|
)
|
|
348
565
|
|
|
@@ -352,7 +569,7 @@ class CheckoutClient:
|
|
|
352
569
|
validate_required_fields(request_data)
|
|
353
570
|
# Transform request to Checkout.com format
|
|
354
571
|
payload = self._transform_to_checkout_payload(request_data)
|
|
355
|
-
|
|
572
|
+
|
|
356
573
|
# Set up common headers
|
|
357
574
|
headers = {
|
|
358
575
|
"Authorization": f"Bearer {self.api_key}",
|
|
@@ -368,6 +585,11 @@ class CheckoutClient:
|
|
|
368
585
|
data=payload,
|
|
369
586
|
use_bt_proxy=request_data.source.type != SourceType.PROCESSOR_TOKEN
|
|
370
587
|
)
|
|
588
|
+
|
|
589
|
+
response_data = response.json()
|
|
590
|
+
|
|
591
|
+
print(f"Response data: {response_data}")
|
|
592
|
+
|
|
371
593
|
except requests.exceptions.HTTPError as e:
|
|
372
594
|
# Check if this is a BT error
|
|
373
595
|
if hasattr(e, 'bt_error_response'):
|
|
@@ -375,13 +597,16 @@ class CheckoutClient:
|
|
|
375
597
|
|
|
376
598
|
try:
|
|
377
599
|
error_data = e.response.json()
|
|
600
|
+
|
|
601
|
+
if "card_expired" in error_data.get("error_codes", []) or "card_disabled" in error_data.get("error_codes", []):
|
|
602
|
+
return self._transform_checkout_response(error_data, request_data, e.response.headers, error_data)
|
|
378
603
|
except:
|
|
379
604
|
error_data = None
|
|
380
605
|
|
|
381
|
-
raise TransactionError(self._transform_error_response_object(e.response, error_data))
|
|
606
|
+
raise TransactionError(self._transform_error_response_object(e.response, error_data, e.response.headers))
|
|
382
607
|
|
|
383
608
|
# Transform response to SDK format
|
|
384
|
-
return self._transform_checkout_response(response.json(), request_data)
|
|
609
|
+
return self._transform_checkout_response(response.json(), request_data, response.headers)
|
|
385
610
|
|
|
386
611
|
def refund_transaction(self, refund_request: RefundRequest) -> RefundResponse:
|
|
387
612
|
"""
|
|
@@ -416,7 +641,7 @@ class CheckoutClient:
|
|
|
416
641
|
)
|
|
417
642
|
|
|
418
643
|
response_data = response.json()
|
|
419
|
-
|
|
644
|
+
|
|
420
645
|
# Transform the response to a standardized format
|
|
421
646
|
return RefundResponse(
|
|
422
647
|
id=response_data.get('action_id'),
|
|
@@ -434,5 +659,4 @@ class CheckoutClient:
|
|
|
434
659
|
except:
|
|
435
660
|
error_data = None
|
|
436
661
|
|
|
437
|
-
raise TransactionError(self._transform_error_response_object(e.response, error_data))
|
|
438
|
-
|
|
662
|
+
raise TransactionError(self._transform_error_response_object(e.response, error_data, e.response.headers))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from typing import Dict, Any, Optional
|
|
2
|
+
from requests.structures import CaseInsensitiveDict
|
|
2
3
|
from ..models import (
|
|
3
4
|
TransactionRequest,
|
|
4
5
|
Amount,
|
|
@@ -8,10 +9,29 @@ from ..models import (
|
|
|
8
9
|
Address,
|
|
9
10
|
StatementDescription,
|
|
10
11
|
ThreeDS,
|
|
11
|
-
RecurringType
|
|
12
|
+
RecurringType,
|
|
13
|
+
ErrorType,
|
|
14
|
+
ErrorResponse,
|
|
15
|
+
ErrorCode,
|
|
16
|
+
BasisTheoryExtras
|
|
12
17
|
)
|
|
13
|
-
from ..exceptions import
|
|
18
|
+
from ..exceptions import TransactionError
|
|
14
19
|
|
|
20
|
+
def _error_code(error_type: ErrorType) -> ErrorCode:
|
|
21
|
+
"""
|
|
22
|
+
Validate the amount in a transaction request.
|
|
23
|
+
"""
|
|
24
|
+
return ErrorCode(
|
|
25
|
+
category=error_type.category,
|
|
26
|
+
code=error_type.code
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def _basis_theory_extras(headers: Optional[CaseInsensitiveDict]) -> Optional[BasisTheoryExtras]:
|
|
30
|
+
if headers and "bt-trace-id" in headers:
|
|
31
|
+
return BasisTheoryExtras(
|
|
32
|
+
trace_id=headers.get("bt-trace-id", "")
|
|
33
|
+
)
|
|
34
|
+
return None
|
|
15
35
|
|
|
16
36
|
def validate_required_fields(data: TransactionRequest) -> None:
|
|
17
37
|
"""
|
|
@@ -21,12 +41,20 @@ def validate_required_fields(data: TransactionRequest) -> None:
|
|
|
21
41
|
data: TransactionRequest containing transaction request data
|
|
22
42
|
|
|
23
43
|
Raises:
|
|
24
|
-
|
|
44
|
+
TransactionError: If required fields are missing
|
|
25
45
|
"""
|
|
26
46
|
if data.amount is None or data.amount.value is None:
|
|
27
|
-
raise
|
|
47
|
+
raise TransactionError(ErrorResponse(
|
|
48
|
+
error_codes=[_error_code(ErrorType.INVALID_AMOUNT)],
|
|
49
|
+
provider_errors=[],
|
|
50
|
+
full_provider_response={}
|
|
51
|
+
))
|
|
28
52
|
if not data.source or not data.source.type or not data.source.id:
|
|
29
|
-
raise
|
|
53
|
+
raise TransactionError(ErrorResponse(
|
|
54
|
+
error_codes=[_error_code(ErrorType.INVALID_SOURCE_TOKEN)],
|
|
55
|
+
provider_errors=[],
|
|
56
|
+
full_provider_response={}
|
|
57
|
+
))
|
|
30
58
|
|
|
31
59
|
|
|
32
60
|
def create_transaction_request(data: Dict[str, Any]) -> TransactionRequest:
|
|
@@ -40,7 +68,7 @@ def create_transaction_request(data: Dict[str, Any]) -> TransactionRequest:
|
|
|
40
68
|
TransactionRequest: A fully populated TransactionRequest object
|
|
41
69
|
|
|
42
70
|
Raises:
|
|
43
|
-
|
|
71
|
+
TransactionError: If required fields are missing
|
|
44
72
|
"""
|
|
45
73
|
return TransactionRequest(
|
|
46
74
|
amount=Amount(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Dict, Any, Optional,
|
|
1
|
+
from typing import Dict, Any, Optional, List
|
|
2
2
|
import requests
|
|
3
3
|
from requests.models import Response
|
|
4
4
|
from ..models import ErrorType, ErrorCode, ErrorResponse
|
|
@@ -41,7 +41,7 @@ class RequestClient:
|
|
|
41
41
|
ErrorCode(
|
|
42
42
|
category=error_type.category,
|
|
43
43
|
code=error_type.code
|
|
44
|
-
)
|
|
44
|
+
)
|
|
45
45
|
],
|
|
46
46
|
provider_errors=provider_errors,
|
|
47
47
|
full_provider_response=response_data
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
connections_sdk/__init__.py,sha256=IYYzlDWAgIeJs_1Ly2_SNsUYtMjGexysfY3Q9-L1Pn8,174
|
|
2
|
+
connections_sdk/client.py,sha256=FkZq68S_syvaJTVRSVPtpA4FwsybRdC9FivRi2SOkoI,3165
|
|
3
|
+
connections_sdk/config.py,sha256=OPH-J11ecQYP17ubtIXuB_VVuso3t-Ym9V8f5j-G1Xk,398
|
|
4
|
+
connections_sdk/exceptions.py,sha256=AuLet9gpV6OmQrKLO9ozgyrWw1KCPuUxV8Own7gTEmk,761
|
|
5
|
+
connections_sdk/models.py,sha256=L_AntoEFsD9I3TeAi8j-h7HGQK1mNXsHnwZOT5vn7iI,7414
|
|
6
|
+
connections_sdk/providers/adyen.py,sha256=j2-FRLsqGJFNYMxq9-8hy1R5zLzmYJ_h-uHwPtcAk9Y,18509
|
|
7
|
+
connections_sdk/providers/checkout.py,sha256=j3YwmpFWt66Zbb9j-pO8vyxYxV_cupbrw0aixvBdKy4,35153
|
|
8
|
+
connections_sdk/utils/__init__.py,sha256=iKwqW2egmbc_LO0oQoNLiP5-RBXUd9OpMW6sdEVB_Fs,153
|
|
9
|
+
connections_sdk/utils/model_utils.py,sha256=FB6Cf-dMFr52gi-F6yMEAPJU413Sjv_8zx3IUNBI-Q8,4015
|
|
10
|
+
connections_sdk/utils/request_client.py,sha256=M9eqEpRVoIFUMS51jGJ-C2R1L9_NWw3v3saAq31hVoI,3251
|
|
11
|
+
connections_sdk-3.0.0.dist-info/LICENSE,sha256=OJSDpWNs9gHwRBdHonZIkQ2-rUpFMxn0V8xxjfz4UQQ,11342
|
|
12
|
+
connections_sdk-3.0.0.dist-info/METADATA,sha256=CwqdNMn8KCXHaSRk-10MFWcqMxtnE2dT1MyGIbkyQZI,2821
|
|
13
|
+
connections_sdk-3.0.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
14
|
+
connections_sdk-3.0.0.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
connections_sdk/__init__.py,sha256=IYYzlDWAgIeJs_1Ly2_SNsUYtMjGexysfY3Q9-L1Pn8,174
|
|
2
|
-
connections_sdk/client.py,sha256=FkZq68S_syvaJTVRSVPtpA4FwsybRdC9FivRi2SOkoI,3165
|
|
3
|
-
connections_sdk/config.py,sha256=OPH-J11ecQYP17ubtIXuB_VVuso3t-Ym9V8f5j-G1Xk,398
|
|
4
|
-
connections_sdk/exceptions.py,sha256=3L3X1OCp7EIPg5l4NPfcEUrPPahfEft-qnZ5QfxXR0k,805
|
|
5
|
-
connections_sdk/models.py,sha256=slAgi-v9qZjWNGvHrJc8myXcN-v1X2lP5gXFjj6fUCQ,6650
|
|
6
|
-
connections_sdk/providers/adyen.py,sha256=6iOyy_7Sh1cZTaV9Lu4MHaazDcL0aUtgYnYluIAMIk4,17458
|
|
7
|
-
connections_sdk/providers/checkout.py,sha256=2hTWkZwnMazlQn4MpICoQ5GZDfGOEhrjYp5QknbuCG4,19543
|
|
8
|
-
connections_sdk/utils/__init__.py,sha256=iKwqW2egmbc_LO0oQoNLiP5-RBXUd9OpMW6sdEVB_Fs,153
|
|
9
|
-
connections_sdk/utils/model_utils.py,sha256=B1lIam4Ctajnhi1H9eB7hpM4IVbemHgpHzBVruoPKcU,3159
|
|
10
|
-
connections_sdk/utils/request_client.py,sha256=zbCGoJajAQ9RUNUb5x5edSaqdxJxPhoedAwUBT0S3ek,3261
|
|
11
|
-
connections_sdk-2.0.0.dist-info/LICENSE,sha256=OJSDpWNs9gHwRBdHonZIkQ2-rUpFMxn0V8xxjfz4UQQ,11342
|
|
12
|
-
connections_sdk-2.0.0.dist-info/METADATA,sha256=bcoDDC_r3AhbyIKend4Zylkw7Pup1RvALske8rGKIN4,2821
|
|
13
|
-
connections_sdk-2.0.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
14
|
-
connections_sdk-2.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|