Skip to main content

Python client library for the Garanti BBVA Virtual POS (GVP) payment gateway API with card tokenization and recurring payment support.

Project description

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

pip install garantipay

Or install from source:

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

For development:

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)

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

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)

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

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)

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)

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)

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

Query a Stored Token

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:

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:

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

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:

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:

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 for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

garantipay-1.1.0.tar.gz (27.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

garantipay-1.1.0-py3-none-any.whl (34.5 kB view details)

Uploaded Python 3

File details

Details for the file garantipay-1.1.0.tar.gz.

File metadata

  • Download URL: garantipay-1.1.0.tar.gz
  • Upload date:
  • Size: 27.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for garantipay-1.1.0.tar.gz
Algorithm Hash digest
SHA256 e1d4483ac33ed90dc8d21bf132f6574227b8bdc080b1050bbe0c09088fd0d163
MD5 7ce364787f5956f2b0d572ceded55fa9
BLAKE2b-256 12e36e22d145d92d5ce95fddba3859ba64b6fc6c52e04c521a1b10d28457e948

See more details on using hashes here.

File details

Details for the file garantipay-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: garantipay-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 34.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for garantipay-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f836f36735498a0eac570e4b3e115faac8da99673e1a21af39f7e9a1b4bf888f
MD5 941c612855cb980ff405ab2ac7a7112e
BLAKE2b-256 d49ee62d406b924a93ce3055b508f2617bd868ee3dfafac18df5374b850f50a2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page