Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ venv/
dist
build
checkout_sdk.egg-info
htmlcov
htmlcov
.cursor/rules/
.cursor/skills/
.vscode/settings.json
52 changes: 52 additions & 0 deletions checkout_sdk/accounts/accounts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from enum import Enum
from typing import Dict

from checkout_sdk.common.common import Phone, Address
from checkout_sdk.common.common import ResidentialStatusType, AccountHolderIdentification
Expand Down Expand Up @@ -382,3 +383,54 @@ class UpdateScheduleRequest:

class PaymentInstrumentsQuery:
status: str


class ReserveRuleType(str, Enum):
ROLLING = 'rolling'


class HoldingDuration:
weeks: int


class RollingReserveRule:
percentage: float
holding_duration: HoldingDuration


class ReserveRuleRequest:
type: ReserveRuleType
rolling: RollingReserveRule
valid_from: str


class FilePurpose(str, Enum):
ADDITIONAL_DOCUMENT = 'additional_document'
ARTICLES_OF_ASSOCIATION = 'articles_of_association'
BANK_VERIFICATION = 'bank_verification'
CERTIFIED_AUTHORISED_SIGNATORY = 'certified_authorised_signatory'
COMPANY_OWNERSHIP = 'company_ownership'
IDENTIFICATION = 'identification'
IDENTITY_VERIFICATION = 'identity_verification'
DISPUTE_EVIDENCE = 'dispute_evidence'
COMPANY_VERIFICATION = 'company_verification'
FINANCIAL_VERIFICATION = 'financial_verification'
TAX_VERIFICATION = 'tax_verification'
PROOF_OF_LEGALITY = 'proof_of_legality'
PROOF_OF_PRINCIPAL_ADDRESS = 'proof_of_principal_address'
SHAREHOLDER_STRUCTURE = 'shareholder_structure'
PROOF_OF_RESIDENTIAL_ADDRESS = 'proof_of_residential_address'
PROOF_OF_REGISTRATION = 'proof_of_registration'


class EntityFileRequest:
purpose: FilePurpose


class EtagHeader:
etag: str

def get_header_mappings(self) -> Dict[str, str]:
return {
'etag': 'If-Match'
}
56 changes: 54 additions & 2 deletions checkout_sdk/accounts/accounts_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

from warnings import warn

from checkout_sdk.accounts.accounts import OnboardEntityRequest, UpdateScheduleRequest, AccountsPaymentInstrument, \
PaymentInstrumentRequest, PaymentInstrumentsQuery, UpdatePaymentInstrumentRequest
from checkout_sdk.accounts.accounts import (
EtagHeader, OnboardEntityRequest, UpdateScheduleRequest, AccountsPaymentInstrument,
PaymentInstrumentRequest, PaymentInstrumentsQuery, UpdatePaymentInstrumentRequest,
ReserveRuleRequest, EntityFileRequest
)
from checkout_sdk.api_client import ApiClient
from checkout_sdk.authorization_type import AuthorizationType
from checkout_sdk.checkout_configuration import CheckoutConfiguration
Expand All @@ -19,6 +22,8 @@ class AccountsClient(Client):
__FILES_PATH = 'files'
__PAYOUT_SCHEDULES_PATH = 'payout-schedules'
__PAYMENT_INSTRUMENTS_PATH = 'payment-instruments'
__MEMBERS_PATH = 'members'
__RESERVE_RULES_PATH = 'reserve-rules'

def __init__(self, api_client: ApiClient,
files_client: ApiClient,
Expand Down Expand Up @@ -106,3 +111,50 @@ def update_payout_schedule(self, entity_id: str, currency: Currency,
self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, entity_id, self.__PAYOUT_SCHEDULES_PATH),
self._sdk_authorization(),
{currency: update_schedule_request})

def get_sub_entity_members(self, entity_id: str):
return self._api_client.get(
self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, entity_id, self.__MEMBERS_PATH),
self._sdk_authorization())

def reinvite_sub_entity_member(self, entity_id: str, user_id: str):
return self._api_client.put(
self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, entity_id, self.__MEMBERS_PATH, user_id),
self._sdk_authorization())

def create_reserve_rule(self, entity_id: str, create_request: ReserveRuleRequest):
return self._api_client.post(
self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, entity_id, self.__RESERVE_RULES_PATH),
self._sdk_authorization(),
create_request)

def get_reserve_rules(self, entity_id: str):
return self._api_client.get(
self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, entity_id, self.__RESERVE_RULES_PATH),
self._sdk_authorization())

def get_reserve_rule_details(self, entity_id: str, reserve_rule_id: str):
path = self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH,
entity_id, self.__RESERVE_RULES_PATH, reserve_rule_id)
return self._api_client.get(path, self._sdk_authorization())

def update_reserve_rule(self, entity_id: str, reserve_rule_id: str, etag: str, update_request: ReserveRuleRequest):
headers = None
if (etag is not None):
headers = EtagHeader()
headers.etag = etag

path = self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH,
entity_id, self.__RESERVE_RULES_PATH, reserve_rule_id)
return self._api_client.put(path, self._sdk_authorization(), update_request, headers=headers)

def upload_entity_file(self, entity_id: str, entity_file_request: EntityFileRequest):
return self.__files_client.post(
self.build_path(self.__ENTITIES_PATH, entity_id, self.__FILES_PATH),
self._sdk_authorization(),
entity_file_request)

def retrieve_entity_file(self, entity_id: str, file_id: str):
return self.__files_client.get(
self.build_path(self.__ENTITIES_PATH, entity_id, self.__FILES_PATH, file_id),
self._sdk_authorization())
Empty file.
92 changes: 92 additions & 0 deletions checkout_sdk/agenticcommerce/agentic_commerce.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from __future__ import absolute_import

from enum import Enum
from typing import List, Dict
from datetime import datetime

from checkout_sdk.common.enums import Country, Currency


class DelegatedPaymentMethodType(str, Enum):
CARD = 'card'


class DelegatedCardNumberType(str, Enum):
FPAN = 'fpan'
NETWORK_TOKEN = 'network_token'


class DelegatedPaymentAllowanceReason(str, Enum):
ONE_TIME = 'one_time'


class DelegatedCardFundingType(str, Enum):
CREDIT = 'credit'
DEBIT = 'debit'
PREPAID = 'prepaid'


class DelegatedPaymentMethodCard:
type: DelegatedPaymentMethodType
card_number_type: DelegatedCardNumberType
number: str
exp_month: str = None
exp_year: str = None
name: str = None
cvc: str = None
cryptogram: str = None
eci_value: str = None
checks_performed: List[str] = None
iin: str = None
display_card_funding_type: DelegatedCardFundingType = None
display_wallet_type: str = None
display_brand: str = None
display_last4: str = None
metadata: Dict[str, str] = None

def __init__(self):
self.type = DelegatedPaymentMethodType.CARD


class DelegatedPaymentAllowance:
reason: DelegatedPaymentAllowanceReason
max_amount: int
currency: Currency
merchant_id: str
checkout_session_id: str
expires_at: datetime


class DelegatedPaymentBillingAddress:
name: str
line_one: str
line_two: str = None
city: str
state: str = None
postal_code: str
country: Country


class DelegatedPaymentRiskSignal:
type: str
score: int
action: str


class DelegatedPaymentRequest:
payment_method: DelegatedPaymentMethodCard
allowance: DelegatedPaymentAllowance
billing_address: DelegatedPaymentBillingAddress = None
risk_signals: List[DelegatedPaymentRiskSignal]
metadata: Dict[str, str]


class DelegatedPaymentHeaders:
signature: str
timestamp: str
api_version: str

def get_header_mappings(self) -> Dict[str, str]:
return {
'api_version': 'API-Version'
}
25 changes: 25 additions & 0 deletions checkout_sdk/agenticcommerce/agentic_commerce_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from __future__ import absolute_import

from checkout_sdk.agenticcommerce.agentic_commerce import DelegatedPaymentRequest, DelegatedPaymentHeaders
from checkout_sdk.api_client import ApiClient
from checkout_sdk.authorization_type import AuthorizationType
from checkout_sdk.checkout_configuration import CheckoutConfiguration
from checkout_sdk.client import Client


class AgenticCommerceClient(Client):
__AGENTIC_COMMERCE_PATH = 'agentic_commerce'
__DELEGATE_PAYMENT_PATH = 'delegate_payment'

def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration):
super().__init__(api_client=api_client,
configuration=configuration,
authorization_type=AuthorizationType.SECRET_KEY)

def create_delegated_payment_token(self, request: DelegatedPaymentRequest, headers: DelegatedPaymentHeaders):
return self._api_client.post(
self.build_path(self.__AGENTIC_COMMERCE_PATH, self.__DELEGATE_PAYMENT_PATH),
self._sdk_authorization(),
request,
headers=headers
)
80 changes: 57 additions & 23 deletions checkout_sdk/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,24 @@ def post(self,
path,
authorization: SdkAuthorization,
request=None,
idempotency_key: str = None):
idempotency_key: str = None,
headers=None):
return self.invoke(method='POST', path=path, authorization=authorization, body=request,
idempotency_key=idempotency_key)
idempotency_key=idempotency_key, headers=headers)

def put(self,
path,
authorization: SdkAuthorization,
request=None):
return self.invoke(method='PUT', path=path, authorization=authorization, body=request)
request=None,
headers=None):
return self.invoke(method='PUT', path=path, authorization=authorization, body=request, headers=headers)

def patch(self,
path,
authorization: SdkAuthorization,
request=None):
return self.invoke(method='PATCH', path=path, authorization=authorization, body=request)
request=None,
headers=None):
return self.invoke(method='PATCH', path=path, authorization=authorization, body=request, headers=headers)

def delete(self,
path,
Expand All @@ -80,37 +83,33 @@ def invoke(self,
idempotency_key: str = None,
params=None,
file_request: FileRequest = None,
multipart_file=None):
multipart_file=None,
headers=None):

headers = {
request_headers = {
'User-Agent': 'checkout-sdk-python/' + VERSION,
'Accept': 'application/json',
'Authorization': authorization.get_authorization_header(),
'Content-Type': 'application/json'}

if idempotency_key is not None:
headers['Cko-Idempotency-Key'] = idempotency_key
request_headers['Cko-Idempotency-Key'] = idempotency_key

if headers is not None:
custom_headers = self._process_custom_headers(headers)
request_headers.update(custom_headers)

base_uri = self._base_uri + path

try:
json_body = None
params_dict = None
files = None

if body is not None:
json_body = json.dumps(body, cls=JsonSerializer)
elif params is not None:
params_dict = json.loads(json.dumps(params, cls=JsonSerializer))
elif file_request is not None:
headers.pop('Content-Type')
files, json_body = get_file_request(file_request, multipart_file)
json_body, params_dict, files = self._prepare_request_payload(
request_headers, body, params, file_request, multipart_file)

self._logger.info(method + ' ' + path)

response = self._http_client.request(method=method,
url=base_uri,
headers=headers,
headers=request_headers,
params=params_dict,
data=json_body,
files=files)
Expand All @@ -130,5 +129,40 @@ def invoke(self,
else:
contents = response.text
return ResponseWrapper(http_metadata, contents)
else:
return ResponseWrapper(http_metadata)
return ResponseWrapper(http_metadata)

def _prepare_request_payload(self, request_headers, body, params, file_request, multipart_file):
json_body = None
params_dict = None
files = None
if body is not None:
json_body = json.dumps(body, cls=JsonSerializer)
elif params is not None:
params_dict = json.loads(json.dumps(params, cls=JsonSerializer))
elif file_request is not None:
request_headers.pop('Content-Type')
files, json_body = get_file_request(file_request, multipart_file)
return json_body, params_dict, files

def _process_custom_headers(self, custom_headers):
if custom_headers is None:
return {}

headers = {}
custom_mappings = {}
if hasattr(custom_headers, 'get_header_mappings'):
custom_mappings = custom_headers.get_header_mappings()

for attr_name in dir(custom_headers):
if attr_name.startswith('_') or callable(getattr(custom_headers, attr_name)):
continue

value = getattr(custom_headers, attr_name)
if value is not None and value != '':
header_name = custom_mappings.get(attr_name, self._convert_property_to_header(attr_name))
headers[header_name] = str(value)

return headers

def _convert_property_to_header(self, property_name):
return '-'.join(word.capitalize() for word in property_name.split('_'))
Loading
Loading