import logging
from typing import Optional
from urllib.parse import urlencode
import requests
from config import get_api_key, get_test_card_dict
from pin_payments.base import Base
[docs]
class Charges(Base):
"""
A class to interact with the Charges API for processing payment card charges.
This class provides methods to create, void, capture, list, search, and verify charges.
:param api_key: The API key for authentication.
:param mode: The mode of operation, either 'live' or 'test'. Default is 'live'.
"""
def __init__(
self,
api_key: str,
mode: str = 'live'
):
"""
Initializes the Charges class with the provided API key and mode
:param api_key: The API key for authentication.
:param mode: The mode of operation, either 'live' or 'test'. Default is 'live'.
"""
super().__init__(api_key=api_key, mode=mode)
self._base_url += 'charges/'
[docs]
def create(
self,
email: str,
description: str,
amount: int,
ip_address: str,
currency: Optional[str] = None,
capture: Optional[bool] = None,
reference: Optional[str] = None,
metadata: Optional[dict] = None,
three_d_secure: Optional[dict] = None,
platform_adjustment: Optional[dict] = None,
# need to use one of the following
card: Optional[dict] = None,
card_token: Optional[str] = None,
payment_source_token: Optional[str] = None,
customer_token: Optional[str] = None
) -> dict:
"""
Creates a new charge and returns its details
This method requires one of the following parameters to be provided:
card, card_token, payment_source_token, or customer_token
:param email: The email address of the purchaser.
:param description: A description of the item purchased.
:param amount: The amount to charge in the currency’s base unit.
:param ip_address: The IP address of the person submitting the payment.
:param currency: The three-character ISO 4217 currency code (optional).
:param capture: Whether to immediately capture the charge (optional).
:param reference: A custom text string for the customer's bank statement (optional).
:param metadata: Arbitrary key-value data associated with the charge (optional).
:param three_d_secure: Information required to enable 3D Secure on payments (optional).
:param platform_adjustment: Specify an amount to withhold from the merchant entitlement (optional).
:param card: The full details of the payment card to be charged (optional).
:param card_token: The token of the card to be charged (optional).
:param payment_source_token: The token of the payment source to be charged (optional).
:param customer_token: The token of the customer to be charged (optional).
:return: A dictionary containing the response details or an error message.
:raises ValueError: If more than one payment detail parameter is provided.
"""
payment_details = [card, card_token, payment_source_token, customer_token]
if sum(detail is not None for detail in payment_details) != 1:
raise ValueError(
'Only one of the parameters is required '
'[card, card_token, payment_source_token, customer_token]')
data = {
"email": email,
"description": description,
"amount": amount,
"ip_address": ip_address,
"currency": currency,
"capture": capture,
"reference": reference
}
if card:
for key, value in card.items():
data[f'card[{key}]'] = value
elif card_token:
data['card_token'] = card_token
elif payment_source_token:
data['payment_source_token'] = payment_source_token
elif customer_token:
data['customer_token'] = customer_token
if metadata:
for key, value in metadata.items():
data[f'metadata[{key}]'] = value
if three_d_secure:
data['three_d_secure'] = three_d_secure
if platform_adjustment:
data['platform_adjustment'] = platform_adjustment
data = {k: v for k, v in data.items() if v is not None}
response = requests.post(self._base_url, auth=self._auth, data=data)
if response.status_code in [201, 202]:
return response.json()
error_message = f"Error in Charges.create: {response.status_code}, {response.text}"
logging.error(error_message)
return {"error": error_message}
[docs]
def void(
self,
charge_token: str
) -> dict:
"""
Voids a previously authorized charge
:param charge_token: The token of the charge to be voided.
:return: A dictionary containing the void details.
"""
url = f"{self._base_url}{charge_token}/void"
response = requests.put(url, auth=self._auth)
return self._handle_response(
response=response,
function_name='Charges.void',
required_status_code=200
)
[docs]
def capture(
self,
charge_token: str
) -> dict:
"""
Captures a previously authorized charge
:param charge_token: The token of the charge to be captured.
:return: A dictionary containing the capture details.
"""
url = f"{self._base_url}{charge_token}/capture"
response = requests.put(url, auth=self._auth)
return self._handle_response(
response=response,
function_name='Charges.capture',
required_status_code=201
)
[docs]
def list(self) -> dict:
"""
Returns a paginated list of all charges
:return: A dictionary containing the list of charges.
"""
response = requests.get(self._base_url, auth=self._auth)
return self._handle_response(
response=response,
function_name='Charges.list',
required_status_code=200
)
[docs]
def search(
self,
query: Optional[str] = None,
start_date: Optional[str] = None,
end_date: Optional[str] = None,
sort: Optional[str] = None,
direction: Optional[int] = None,
) -> dict:
"""
Searches for charges based on the provided criteria
:param query: Return only charges whose fields match the query (optional).
:param start_date: Return only charges created on or after this date (optional).
:param end_date: Return only charges created before this date (optional).
:param sort: The field used to sort the charges (optional).
:param direction: The direction in which to sort the charges (optional).
:return: A dictionary containing the search results.
"""
params = {
"query": query,
"start_date": start_date,
"end_date": end_date,
"sort": sort,
"direction": direction
}
filtered_params = {k: v for k, v in params.items() if v is not None}
url = f"{self._base_url}search?" + urlencode(filtered_params)
response = requests.get(url, auth=self._auth)
return self._handle_response(
response=response,
function_name='Charges.search',
required_status_code=200
)
[docs]
def charge(
self,
charge_token: str
) -> dict:
"""
Retrieves the details of a specific charge
:param charge_token: The token of the charge to retrieve.
:return: A dictionary containing the charge details.
"""
url = f"{self._base_url}{charge_token}/"
response = requests.get(url, auth=self._auth)
return self._handle_response(
response=response,
function_name='Charges.charge',
required_status_code=200
)
[docs]
def verify(
self,
session_token: str
) -> dict:
"""
Verifies the result of a 3D Secure enabled charge
:param session_token: The session token for verification.
:return: A dictionary containing the verification details.
"""
url = f"{self._base_url}verify?session_token={session_token}"
response = requests.get(url, auth=self._auth)
return self._handle_response(
response=response,
function_name='Charges.verify',
required_status_code=200
)
if __name__ == '__main__':
charges_api = Charges(api_key=get_api_key(), mode='test')
charge_creation_response = charges_api.create(
email='test@gmail.com',
description='Test Charge',
amount=400,
ip_address='192.0.2.1',
card=get_test_card_dict()
)
print("Charge Creation Response:", charge_creation_response)
charge_token = charge_creation_response['response']['token']
charges_list = charges_api.list()
print("Charges List:", charges_list)
search_results = charges_api.search()
print("Search Results:", search_results)
charge_details = charges_api.charge(charge_token)
print("Charge Details:", charge_details)
void_response = charges_api.void(charge_token)
print("Void Response:", void_response)
capture_response = charges_api.capture(charge_token)
print("Capture Response:", capture_response)
# Assuming a 3D Secure session token is needed and available
# Uncomment and modify the next lines according to your actual 3D Secure handling
# secure_token = "YOUR_3D_SECURE_SESSION_TOKEN_HERE"
# verify_response = charges_api.verify(charge_token, secure_token)
# print("Verify Response:", verify_response)