Metadata-Version: 2.4
Name: garantipay
Version: 1.1.0
Summary: Python client library for the Garanti BBVA Virtual POS (GVP) payment gateway API with card tokenization and recurring payment support.
Project-URL: Homepage, https://github.com/hbtechsoftware/garantipay
Project-URL: Documentation, https://github.com/hbtechsoftware/garantipay#readme
Project-URL: Repository, https://github.com/hbtechsoftware/garantipay
Project-URL: Issues, https://github.com/hbtechsoftware/garantipay/issues
Project-URL: Changelog, https://github.com/hbtechsoftware/garantipay/blob/main/CHANGELOG.md
Author: Opul Tech Software
License-Expression: MIT
License-File: LICENSE
Keywords: card-storage,credit-card,garanti,garantibbva,gvp,payment,payment-gateway,pos,recurring-payment,sanal-pos,subscription,switch,tokenization,turkey,virtual-pos
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: requests>=2.28.0
Description-Content-Type: text/markdown

# garantipay

A modern, fully-typed Python client library for the **Garanti BBVA Virtual POS (GVP)** payment gateway API.

Process credit card payments, refunds, and voids through Garanti BBVA's virtual POS system with a clean, Pythonic API.

## Features

- **One-time payments** -- Sale, refund, and void via the VPServlet XML API
- **Card tokenization** -- Securely store cards on Garanti servers via the Switch JSON API
- **Recurring/subscription payments** -- Charge stored tokens without asking for card details
- **Token management** -- Generate, query, and delete stored card tokens
- **3D Secure support** -- Process transactions authenticated via 3D Secure
- **Fully typed** -- Complete type annotations and PEP 561 `py.typed` marker
- **Comprehensive error handling** -- Typed exception hierarchy for all failure modes
- **Zero magic** -- Explicit, readable code with no hidden behavior

## Installation

```bash
pip install garantipay
```

Or install from source:

```bash
git clone https://github.com/hbtechsoftware/garantipay.git
cd garantipay
pip install .
```

For development:

```bash
pip install -e ".[dev]"
```

## Quick Start

### High-Level API (Recommended)

The `GarantiClient` class handles hash computation, request assembly, and provides convenient methods for common transaction types.

#### Sale (Payment)

```python
from garantipay import GarantiClient

client = GarantiClient(
    terminal_id="12345678",       # Terminal No (from Garanti BBVA)
    merchant_id="1234567",        # Isyeri No (from Garanti BBVA)
    prov_user_id="1234567",       # Provisioning user ID
    user_id="99999999999",        # API user ID
    password="your_password",     # PROVAUT user password
    mode="TEST",                  # "TEST" for sandbox, "PROD" for production
)

response = client.sale(
    card_number="1234567890123456",
    card_expire_date="0326",       # MMYY format
    card_cvv2="123",
    amount="100",                  # 100 = 1.00 TRY (last 2 digits are kuruş)
    customer_ip="1.2.3.4",
    currency="TRY",                # optional, defaults to "TRY"
    installment_count="",          # empty for single payment
)

if response.is_successful:
    print(f"Payment approved! Ref: {response.transaction.retref_num}")
else:
    print(f"Declined: {response.error_message}")
```

#### Refund

```python
response = client.refund(
    order_id="ORIGINAL_ORDER_ID",  # order ID from the original sale
    amount="100",                  # refund amount (must not exceed original)
    customer_ip="1.2.3.4",
    currency="TRY",
    prov_user_id="PROVRFN",        # typically "PROVRFN" for refunds
)

if response.is_successful:
    print("Refund approved!")
```

#### Void (Cancel)

```python
response = client.void(
    order_id="ORIGINAL_ORDER_ID",
    amount="100",
    customer_ip="1.2.3.4",
    retref_num="original_retref_num",  # from the sale response
    auth_code="original_auth_code",    # from the sale response
)

if response.is_successful:
    print("Transaction voided!")
```

#### 3D Secure Sale

```python
from garantipay import GarantiClient, Secure3D

client = GarantiClient(
    terminal_id="12345678",
    merchant_id="1234567",
    prov_user_id="PROVAUT",
    user_id="99999999999",
    password="your_password",
    mode="TEST",
)

# Data received from the 3D Secure callback
secure_3d = Secure3D(
    md="merchant_data_from_callback",
    txn_id="transaction_id_from_callback",
    security_level="3D",
    authentication_code="auth_code_from_callback",
)

response = client.sale(
    card_number="1234567890123456",
    card_expire_date="0326",
    card_cvv2="123",
    amount="100",
    customer_ip="1.2.3.4",
    secure_3d=secure_3d,
    cardholder_present_code="13",
)
```

### Recurring / Subscription Payments (Switch API)

The Switch API uses card tokenization to enable recurring payments. Store the card once, then charge monthly using the token -- no need to ask the customer for card details again.

> **Note**: The Switch API is a separate system from the VPServlet API. It uses JSON (not XML), SHA-256 (not SHA-1), and requires different credentials (Switch ID + Switch Password).

#### Step 1: Store the Card (Generate Token)

```python
from garantipay import SwitchClient

switch = SwitchClient(
    swt_id="YOUR_SWITCH_ID",         # from Garanti BBVA
    swt_password="YOUR_SWITCH_PASS",  # from merchant management screens
    user_id="my_system",
    mode="TEST",                      # "TEST" or "PROD"
)

response = switch.generate_token(
    card_number="5549601634451019",
    expire_month="02",
    expire_year="25",
)

if response.is_successful:
    token = response.card.token  # Save this in your database!
    print(f"Token: {token}")
    print(f"Card: {response.card.masked_number}")
```

#### Step 2: Charge Monthly (Sale with Token)

```python
payment = switch.sale_with_token(
    token="SAVED_TOKEN_FROM_STEP_1",
    amount="29.90",             # decimal format: 29.90 TRY
    customer_ip="192.168.1.1",
    currency_code="949",        # TRY
)

if payment.is_successful:
    print(f"Payment approved! Ref: {payment.acquirer_response.ret_ref_num}")
else:
    print(f"Failed: {payment.error_message}")
```

#### Step 3: Cancel Subscription (Delete Token)

```python
result = switch.delete_token("SAVED_TOKEN")
if result.is_successful:
    print("Card removed from Garanti servers.")
```

#### Query a Stored Token

```python
inquiry = switch.inquiry_token(original_request_id="REQUEST_ID_FROM_GENERATE")
if inquiry.is_successful:
    print(f"Token: {inquiry.card.token}")
    print(f"Bank: {inquiry.card.bank_name}")
```

#### Switch API Amount Format

Unlike the VPServlet API, the Switch API uses **decimal notation** with a period separator:

| Amount String | Actual Value |
|---------------|--------------|
| `"1.00"`      | 1.00 TRY     |
| `"10.50"`     | 10.50 TRY    |
| `"29.90"`     | 29.90 TRY    |
| `"999.99"`    | 999.99 TRY   |

#### Switch API Test Credentials

| Field            | Test Value                           |
|------------------|--------------------------------------|
| Switch ID        | `CC82C381E078482AB328943FCCB7100C`   |
| Switch Password  | `123asdASD@`                         |

For production credentials, contact ETicaretDestek@garantibbva.com.tr with your Garanti Sanalpos terminal number.

### Low-Level API

For advanced use cases where you need full control over every XML field:

```python
from garantipay import (
    API, Request, Terminal, Card, Customer,
    Transaction, Order, CURRENCIES, sha1,
)

api = API()
request = Request()
request.mode = "TEST"
request.version = "v1.0"

request.terminal = Terminal()
request.terminal.id = "12345678"
request.terminal.merchant_id = "1234567"
request.terminal.user_id = "99999999999"
request.terminal.prov_user_id = "PROVAUT"

request.card = Card()
request.card.number = "1234567890123456"
request.card.expire_date = "0326"
request.card.cvv2 = "123"

request.customer = Customer()
request.customer.ip_address = "1.2.3.4"

request.transaction = Transaction()
request.transaction.amount = "100"
request.transaction.currency_code = CURRENCIES["TRY"]
request.transaction.type = "sales"

request.order = Order()
request.order.order_id = ""

# Compute the required hash
password = "your_password"
hash_password = sha1(password + f"{int(request.terminal.id):09d}").upper()
hash_data = (
    f"{request.order.order_id or ''}"
    f"{request.terminal.id or ''}"
    f"{request.card.number or ''}"
    f"{request.transaction.amount or ''}"
    f"{hash_password}"
)
request.terminal.hash_data = sha1(hash_data).upper()

response = api.transaction(request)
```

## Response Handling

The `Response` object provides convenient properties for checking results:

```python
response = client.sale(...)

# Check success
response.is_successful       # True if response code is "00"

# Access details
response.response_code       # e.g. "00" for success
response.response_message    # e.g. "Approved"
response.error_message       # Error description if failed

# Transaction details (when successful)
response.transaction.retref_num       # Retrieval reference number
response.transaction.auth_code        # Authorization code
response.transaction.card_number_masked  # e.g. "12345678****3456"
```

### Pretty-Print XML Response

```python
from garantipay import response_to_pretty_xml

pretty = response_to_pretty_xml(response)
print(pretty)
```

## Error Handling

All exceptions inherit from `GarantiPayError` for easy catch-all handling:

```python
from garantipay.exceptions import (
    GarantiPayError,
    GarantiPayConnectionError,
    GarantiPayValidationError,
    GarantiPayXMLError,
)

try:
    response = client.sale(...)
except GarantiPayValidationError as exc:
    # Invalid input parameters (caught before any network call)
    print(f"Validation error: {exc}")
except GarantiPayConnectionError as exc:
    # Network/timeout error communicating with the API
    print(f"Connection error: {exc}")
except GarantiPayXMLError as exc:
    # Unexpected response format from the API
    print(f"XML parsing error: {exc}")
except GarantiPayError as exc:
    # Catch-all for any library error
    print(f"Error: {exc}")
```

## Supported Currencies

| Code  | Currency           | ISO 4217 |
|-------|--------------------|----------|
| `TRY` | Turkish Lira       | 949      |
| `USD` | US Dollar          | 840      |
| `EUR` | Euro               | 978      |
| `GBP` | British Pound      | 826      |
| `JPY` | Japanese Yen       | 392      |

Aliases `TL`, `YTL`, and `TRL` also map to Turkish Lira (949).

## Amount Format

The API expects amounts as strings where the **last 2 digits represent the fractional part** (kuruş/cents):

| Amount String | Actual Value |
|---------------|--------------|
| `"100"`       | 1.00 TRY     |
| `"1000"`      | 10.00 TRY    |
| `"1050"`      | 10.50 TRY    |
| `"99999"`     | 999.99 TRY   |

## Logging

The library uses Python's standard `logging` module under the `"garantipay"` logger name. Enable debug logging to see raw XML requests and responses:

```python
import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("garantipay")
logger.setLevel(logging.DEBUG)
```

> **Warning**: Debug logs may contain sensitive data (card numbers, passwords). Never enable debug logging in production.

## Project Structure

```
garantipay/
├── pyproject.toml              # Package metadata and build config
├── README.md                   # This file
├── LICENSE                     # MIT license
├── CHANGELOG.md                # Version history
├── src/
│   └── garantipay/
│       ├── __init__.py         # Public API exports
│       ├── py.typed            # PEP 561 typed package marker
│       ├── client.py           # API and GarantiClient (VPServlet)
│       ├── constants.py        # VPServlet endpoints and currencies
│       ├── exceptions.py       # Exception hierarchy
│       ├── hash.py             # SHA-1 hashing (VPServlet)
│       ├── models.py           # VPServlet request/response models
│       ├── xml_builder.py      # Request XML serializer
│       ├── xml_parser.py       # Response XML parser
│       ├── switch_client.py    # SwitchClient (tokenization & payments)
│       ├── switch_constants.py # Switch API endpoints
│       ├── switch_hash.py      # SHA-256 hashing (Switch)
│       └── switch_models.py    # Switch JSON request/response models
└── examples/
    ├── sale.py                 # Sale transaction example
    ├── refund.py               # Refund transaction example
    ├── sale_3d_secure.py       # 3D Secure sale example
    ├── low_level_api.py        # Low-level API example
    ├── token_generate.py       # Card tokenization example
    └── token_recurring_payment.py  # Full recurring payment flow
```

## Requirements

- Python 3.9+
- `requests` >= 2.28.0

## License

This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
