connections-sdk 0.1.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/__init__.py +4 -0
- connections_sdk/client.py +106 -0
- connections_sdk/config.py +18 -0
- connections_sdk/exceptions.py +24 -0
- connections_sdk/models.py +210 -0
- connections_sdk/providers/adyen.py +405 -0
- connections_sdk/providers/checkout.py +440 -0
- connections_sdk/utils/__init__.py +4 -0
- connections_sdk/utils/model_utils.py +94 -0
- connections_sdk/utils/request_client.py +89 -0
- connections_sdk-0.1.0.dist-info/LICENSE +202 -0
- connections_sdk-0.1.0.dist-info/METADATA +91 -0
- connections_sdk-0.1.0.dist-info/RECORD +14 -0
- connections_sdk-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from typing import Optional, Dict, Any, cast
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from .providers.adyen import AdyenClient
|
|
4
|
+
from .providers.checkout import CheckoutClient
|
|
5
|
+
from .exceptions import ConfigurationError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class AdyenConfig:
|
|
10
|
+
api_key: str
|
|
11
|
+
merchant_account: str
|
|
12
|
+
production_prefix: str = ""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class CheckoutConfig:
|
|
17
|
+
private_key: str
|
|
18
|
+
processing_channel: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ProviderConfig:
|
|
23
|
+
adyen: Optional[AdyenConfig] = None
|
|
24
|
+
checkout: Optional[CheckoutConfig] = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Connections:
|
|
28
|
+
_instance: Optional['Connections'] = None
|
|
29
|
+
|
|
30
|
+
def __init__(self) -> None:
|
|
31
|
+
self.is_test: bool = False
|
|
32
|
+
self.bt_api_key: str = ""
|
|
33
|
+
self.provider_config: Optional[ProviderConfig] = None
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def init(cls, config: Dict[str, Any]) -> 'Connections':
|
|
37
|
+
"""Initialize the Connections SDK with the provided configuration."""
|
|
38
|
+
if cls._instance is None:
|
|
39
|
+
cls._instance = cls()
|
|
40
|
+
|
|
41
|
+
if 'is_test' not in config:
|
|
42
|
+
config['is_test'] = False
|
|
43
|
+
if 'bt_api_key' not in config:
|
|
44
|
+
raise ConfigurationError("'bt_api_key' parameter is required")
|
|
45
|
+
if 'provider_config' not in config:
|
|
46
|
+
raise ConfigurationError("'provider_config' parameter is required")
|
|
47
|
+
|
|
48
|
+
instance = cast(Connections, cls._instance)
|
|
49
|
+
instance.is_test = config['is_test']
|
|
50
|
+
instance.bt_api_key = config['bt_api_key']
|
|
51
|
+
|
|
52
|
+
provider_config = config['provider_config']
|
|
53
|
+
instance.provider_config = ProviderConfig()
|
|
54
|
+
|
|
55
|
+
# Initialize Adyen configuration if provided
|
|
56
|
+
if 'adyen' in provider_config:
|
|
57
|
+
adyen_config = provider_config.get('adyen')
|
|
58
|
+
instance.provider_config.adyen = AdyenConfig(
|
|
59
|
+
api_key=adyen_config.get('api_key'),
|
|
60
|
+
merchant_account=adyen_config.get('merchant_account'),
|
|
61
|
+
production_prefix=adyen_config.get('production_prefix', "")
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Initialize Checkout.com configuration if provided
|
|
65
|
+
if 'checkout' in provider_config:
|
|
66
|
+
checkout_config = provider_config.get('checkout')
|
|
67
|
+
instance.provider_config.checkout = CheckoutConfig(
|
|
68
|
+
private_key=checkout_config.get('private_key'),
|
|
69
|
+
processing_channel=checkout_config.get('processing_channel')
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return instance
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def get_instance(cls) -> 'Connections':
|
|
76
|
+
"""Get the initialized SDK instance."""
|
|
77
|
+
if cls._instance is None:
|
|
78
|
+
raise ConfigurationError("Connections must be initialized with init() before use")
|
|
79
|
+
return cls._instance
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def adyen(self) -> AdyenClient:
|
|
83
|
+
"""Get the Adyen client instance."""
|
|
84
|
+
if not self.provider_config or not self.provider_config.adyen:
|
|
85
|
+
raise ConfigurationError("Adyen is not configured")
|
|
86
|
+
|
|
87
|
+
return AdyenClient(
|
|
88
|
+
api_key=self.provider_config.adyen.api_key,
|
|
89
|
+
merchant_account=self.provider_config.adyen.merchant_account,
|
|
90
|
+
is_test=self.is_test,
|
|
91
|
+
bt_api_key=self.bt_api_key,
|
|
92
|
+
production_prefix=self.provider_config.adyen.production_prefix
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def checkout(self) -> CheckoutClient:
|
|
97
|
+
"""Get the Checkout client instance."""
|
|
98
|
+
if not self.provider_config or not self.provider_config.checkout:
|
|
99
|
+
raise ConfigurationError("Checkout is not configured")
|
|
100
|
+
|
|
101
|
+
return CheckoutClient(
|
|
102
|
+
private_key=self.provider_config.checkout.private_key,
|
|
103
|
+
processing_channel=self.provider_config.checkout.processing_channel,
|
|
104
|
+
is_test=self.is_test,
|
|
105
|
+
bt_api_key=self.bt_api_key
|
|
106
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class AdyenConfig:
|
|
6
|
+
api_key: str
|
|
7
|
+
merchant_account: str
|
|
8
|
+
production_prefix: Optional[str] = ""
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class CheckoutConfig:
|
|
12
|
+
private_key: str
|
|
13
|
+
processing_channel: Optional[str] = None
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ProviderConfig:
|
|
17
|
+
adyen: Optional[AdyenConfig] = None
|
|
18
|
+
checkout: Optional[CheckoutConfig] = None
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from connections_sdk.models import ErrorResponse
|
|
2
|
+
|
|
3
|
+
class TransactionError(Exception):
|
|
4
|
+
error_response: ErrorResponse
|
|
5
|
+
def __init__(self, error_response: 'ErrorResponse'):
|
|
6
|
+
self.error_response = error_response
|
|
7
|
+
super().__init__(str(error_response.error_codes))
|
|
8
|
+
|
|
9
|
+
class ValidationError(Exception):
|
|
10
|
+
"""Raised when request validation fails."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
class ConfigurationError(Exception):
|
|
14
|
+
"""Raised when SDK configuration is invalid."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class BasisTheoryError(Exception):
|
|
18
|
+
"""Raised when Basis Theory returns an error."""
|
|
19
|
+
error_response: ErrorResponse
|
|
20
|
+
status: int
|
|
21
|
+
def __init__(self, error_response: 'ErrorResponse', status):
|
|
22
|
+
self.error_response = error_response
|
|
23
|
+
self.status = status
|
|
24
|
+
super().__init__(str(error_response.error_codes))
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Optional, Any, Dict, List
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TransactionStatusCode(str, Enum):
|
|
8
|
+
AUTHORIZED = "Authorized"
|
|
9
|
+
PENDING = "Pending"
|
|
10
|
+
CARD_VERIFIED = "Card Verified"
|
|
11
|
+
DECLINED = "Declined"
|
|
12
|
+
RETRY_SCHEDULED = "Retry Scheduled"
|
|
13
|
+
CANCELLED = "Cancelled"
|
|
14
|
+
CHALLENGE_SHOPPER = "ChallengeShopper"
|
|
15
|
+
RECEIVED = "Received"
|
|
16
|
+
PARTIALLY_AUTHORIZED = "PartiallyAuthorised"
|
|
17
|
+
REFUNDED = "Refunded"
|
|
18
|
+
|
|
19
|
+
class RecurringType(str, Enum):
|
|
20
|
+
ONE_TIME = "ONE_TIME"
|
|
21
|
+
CARD_ON_FILE = "CARD_ON_FILE"
|
|
22
|
+
SUBSCRIPTION = "SUBSCRIPTION"
|
|
23
|
+
UNSCHEDULED = "UNSCHEDULED"
|
|
24
|
+
|
|
25
|
+
class RefundReason(str, Enum):
|
|
26
|
+
FRAUD = "FRAUD"
|
|
27
|
+
CUSTOMER_REQUEST = "CUSTOMER_REQUEST"
|
|
28
|
+
RETURN = "RETURN"
|
|
29
|
+
DUPLICATE = "DUPLICATE"
|
|
30
|
+
OTHER = "OTHER"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SourceType(str, Enum):
|
|
34
|
+
BASIS_THEORY_TOKEN = "basis_theory_token"
|
|
35
|
+
BASIS_THEORY_TOKEN_INTENT = "basis_theory_token_intent"
|
|
36
|
+
PROCESSOR_TOKEN = "processor_token"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ErrorCategory(str, Enum):
|
|
40
|
+
AUTHENTICATION_ERROR = "authentication_error"
|
|
41
|
+
PAYMENT_METHOD_ERROR = "payment_method_error"
|
|
42
|
+
PROCESSING_ERROR = "processing_error"
|
|
43
|
+
VALIDATION_ERROR = "validation_error"
|
|
44
|
+
BASIS_THEORY_ERROR = "basis_theory_error"
|
|
45
|
+
FRAUD_DECLINE = "Fraud Decline"
|
|
46
|
+
OTHER = "Other"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ErrorType(Enum):
|
|
50
|
+
"""Enum for error types."""
|
|
51
|
+
REFUSED = ("refused", ErrorCategory.PROCESSING_ERROR)
|
|
52
|
+
REFERRAL = ("referral", ErrorCategory.PROCESSING_ERROR)
|
|
53
|
+
ACQUIRER_ERROR = ("acquirer_error", ErrorCategory.OTHER)
|
|
54
|
+
BLOCKED_CARD = ("blocked_card", ErrorCategory.PAYMENT_METHOD_ERROR)
|
|
55
|
+
EXPIRED_CARD = ("expired_card", ErrorCategory.PAYMENT_METHOD_ERROR)
|
|
56
|
+
INVALID_AMOUNT = ("invalid_amount", ErrorCategory.OTHER)
|
|
57
|
+
INVALID_CARD = ("invalid_card", ErrorCategory.PAYMENT_METHOD_ERROR)
|
|
58
|
+
INVALID_SOURCE_TOKEN = ("invalid_source_token", ErrorCategory.PAYMENT_METHOD_ERROR)
|
|
59
|
+
OTHER = ("other", ErrorCategory.OTHER)
|
|
60
|
+
NOT_SUPPORTED = ("not_supported", ErrorCategory.PROCESSING_ERROR)
|
|
61
|
+
AUTHENTICATION_FAILURE = ("authentication_failure", ErrorCategory.AUTHENTICATION_ERROR)
|
|
62
|
+
INSUFFICENT_FUNDS = ("insufficient_funds", ErrorCategory.PAYMENT_METHOD_ERROR)
|
|
63
|
+
FRAUD = ("fraud", ErrorCategory.FRAUD_DECLINE)
|
|
64
|
+
PAYMENT_CANCELLED = ("payment_cancelled", ErrorCategory.OTHER)
|
|
65
|
+
PAYMENT_CANCELLED_BY_CONSUMER = ("payment_cancelled_by_consumer", ErrorCategory.PROCESSING_ERROR)
|
|
66
|
+
INVALID_PIN = ("invalid_pin", ErrorCategory.PAYMENT_METHOD_ERROR)
|
|
67
|
+
PIN_TRIES_EXCEEDED = ("pin_tries_exceeded", ErrorCategory.PAYMENT_METHOD_ERROR)
|
|
68
|
+
CVC_INVALID = ("cvc_invalid", ErrorCategory.PAYMENT_METHOD_ERROR)
|
|
69
|
+
RESTRICTED_CARD = ("restricted_card", ErrorCategory.PROCESSING_ERROR)
|
|
70
|
+
STOP_PAYMENT = ("stop_payment", ErrorCategory.PROCESSING_ERROR)
|
|
71
|
+
AVS_DECLINE = ("avs_decline", ErrorCategory.PROCESSING_ERROR)
|
|
72
|
+
PIN_REQUIRED = ("pin_required", ErrorCategory.PROCESSING_ERROR)
|
|
73
|
+
BANK_ERROR = ("bank_error", ErrorCategory.PROCESSING_ERROR)
|
|
74
|
+
CONTACTLESS_FALLBACK = ("contactless_fallback", ErrorCategory.PROCESSING_ERROR)
|
|
75
|
+
AUTHENTICATION_REQUIRED = ("authentication_required", ErrorCategory.PROCESSING_ERROR)
|
|
76
|
+
PROCESSOR_BLOCKED = ("processor_blocked", ErrorCategory.PROCESSING_ERROR)
|
|
77
|
+
INVALID_API_KEY = ("invalid_api_key", ErrorCategory.OTHER)
|
|
78
|
+
UNAUTHORIZED = ("unauthorized", ErrorCategory.OTHER)
|
|
79
|
+
CONFIGURATION_ERROR = ("configuration_error", ErrorCategory.OTHER)
|
|
80
|
+
REFUND_FAILED = ("refund_failed", ErrorCategory.PROCESSING_ERROR)
|
|
81
|
+
REFUND_AMOUNT_EXCEEDS_BALANCE = ("refund_amount_exceeds_balance", ErrorCategory.PROCESSING_ERROR)
|
|
82
|
+
REFUND_DECLINED = ("refund_declined", ErrorCategory.PROCESSING_ERROR)
|
|
83
|
+
BT_UNAUTHENTICATED = ("unauthenticated", ErrorCategory.BASIS_THEORY_ERROR)
|
|
84
|
+
BT_UNAUTHORIZED = ("unauthorized", ErrorCategory.BASIS_THEORY_ERROR)
|
|
85
|
+
BT_REQUEST_ERROR = ("request_error", ErrorCategory.BASIS_THEORY_ERROR)
|
|
86
|
+
BT_UNEXPECTED = ("unexpected", ErrorCategory.BASIS_THEORY_ERROR)
|
|
87
|
+
|
|
88
|
+
def __init__(self, code: str, category: ErrorCategory):
|
|
89
|
+
self.code = code
|
|
90
|
+
self.category = category
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class Amount:
|
|
95
|
+
value: int
|
|
96
|
+
currency: str = "USD"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class Source:
|
|
101
|
+
type: SourceType
|
|
102
|
+
id: str
|
|
103
|
+
store_with_provider: bool = False
|
|
104
|
+
holder_name: Optional[str] = None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class Address:
|
|
109
|
+
address_line1: Optional[str] = None
|
|
110
|
+
address_line2: Optional[str] = None
|
|
111
|
+
city: Optional[str] = None
|
|
112
|
+
state: Optional[str] = None
|
|
113
|
+
zip: Optional[str] = None
|
|
114
|
+
country: Optional[str] = None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass
|
|
118
|
+
class Customer:
|
|
119
|
+
reference: Optional[str] = None
|
|
120
|
+
first_name: Optional[str] = None
|
|
121
|
+
last_name: Optional[str] = None
|
|
122
|
+
email: Optional[str] = None
|
|
123
|
+
address: Optional[Address] = None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass
|
|
127
|
+
class StatementDescription:
|
|
128
|
+
name: Optional[str] = None
|
|
129
|
+
city: Optional[str] = None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class ThreeDS:
|
|
134
|
+
eci: Optional[str] = None
|
|
135
|
+
authentication_value: Optional[str] = None
|
|
136
|
+
xid: Optional[str] = None
|
|
137
|
+
version: Optional[str] = None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass
|
|
141
|
+
class TransactionRequest:
|
|
142
|
+
amount: Amount
|
|
143
|
+
source: Source
|
|
144
|
+
reference: Optional[str] = None
|
|
145
|
+
merchant_initiated: bool = False
|
|
146
|
+
type: Optional[RecurringType] = None
|
|
147
|
+
customer: Optional[Customer] = None
|
|
148
|
+
statement_description: Optional[StatementDescription] = None
|
|
149
|
+
three_ds: Optional[ThreeDS] = None
|
|
150
|
+
previous_network_transaction_id: Optional[str] = None
|
|
151
|
+
override_provider_properties: Optional[Dict[str, Any]] = None
|
|
152
|
+
metadata: Optional[Dict[str, str]] = None
|
|
153
|
+
|
|
154
|
+
@dataclass
|
|
155
|
+
class RefundRequest:
|
|
156
|
+
original_transaction_id: str
|
|
157
|
+
reference: str
|
|
158
|
+
amount: Amount
|
|
159
|
+
reason: Optional[RefundReason] = None
|
|
160
|
+
|
|
161
|
+
# Response Models
|
|
162
|
+
@dataclass
|
|
163
|
+
class TransactionStatus:
|
|
164
|
+
code: TransactionStatusCode
|
|
165
|
+
provider_code: str
|
|
166
|
+
|
|
167
|
+
@dataclass
|
|
168
|
+
class ProvisionedSource:
|
|
169
|
+
id: str
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclass
|
|
173
|
+
class TransactionSource:
|
|
174
|
+
type: str
|
|
175
|
+
id: str
|
|
176
|
+
provisioned: Optional[ProvisionedSource] = None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass
|
|
180
|
+
class TransactionResponse:
|
|
181
|
+
id: str
|
|
182
|
+
reference: str
|
|
183
|
+
amount: Amount
|
|
184
|
+
status: TransactionStatus
|
|
185
|
+
source: TransactionSource
|
|
186
|
+
full_provider_response: Dict[str, Any]
|
|
187
|
+
created_at: datetime
|
|
188
|
+
network_transaction_id: Optional[str] = None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@dataclass
|
|
192
|
+
class RefundResponse:
|
|
193
|
+
id: str
|
|
194
|
+
reference: str
|
|
195
|
+
amount: Amount
|
|
196
|
+
status: TransactionStatus
|
|
197
|
+
full_provider_response: Dict[str, Any]
|
|
198
|
+
created_at: datetime
|
|
199
|
+
refunded_transaction_id: Optional[str] = None
|
|
200
|
+
|
|
201
|
+
@dataclass
|
|
202
|
+
class ErrorCode:
|
|
203
|
+
category: str
|
|
204
|
+
code: str
|
|
205
|
+
|
|
206
|
+
@dataclass
|
|
207
|
+
class ErrorResponse:
|
|
208
|
+
error_codes: List[ErrorCode]
|
|
209
|
+
provider_errors: List[str]
|
|
210
|
+
full_provider_response: Dict[str, Any]
|