Metadata-Version: 2.4
Name: moka-python
Version: 1.0.2
Summary: Moka United sanal POS API'si için Python istemcisi. Bu kütüphane bağımsız olarak geliştirilmiştir; Moka United'ın resmi ürünü değildir, Moka United tarafından geliştirilmemiş, onaylanmamış ve desteklenmemektedir.
Author-email: Hasan Cagri Gungor <hasancagrigungor@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/hasancagrigungor/moka-python
Project-URL: Documentation, https://github.com/hasancagrigungor/moka-python#readme
Project-URL: Source, https://github.com/hasancagrigungor/moka-python
Project-URL: Issues, https://github.com/hasancagrigungor/moka-python/issues
Project-URL: Moka United API, https://developer.mokaunited.com
Keywords: moka,moka united,sanal pos,odeme,payment,virtual pos,turkey
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: Turkish
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
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: Topic :: Office/Business :: Financial :: Point-Of-Sale
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# moka-python

> **Bu kütüphane bağımsız olarak geliştirilmiştir; Moka United'ın resmi ürünü değildir, Moka United tarafından geliştirilmemiş, onaylanmamış ve desteklenmemektedir.**

Moka United sanal POS API'si için Python istemcisi.

Bu kütüphane, [Moka United API](https://developer.mokaunited.com) servislerine Python uygulamalarından erişim sağlar. Resmi PHP istemcisindeki (moka-php) tüm servisleri ve istek modellerini birebir kapsar. Harici hiçbir bağımlılığı yoktur; yalnızca Python standart kütüphanesini kullanır.

## Özellikler

- 3D Secure olmadan ödeme (Non-3D)
- 3D Secure ile ödeme
- 3D Secure ile mobil ödeme
- Ön provizyon ve capture işlemi
- Havuz ödemesi onaylama ve onay iptali
- Ödeme iptali (void) ve iade talebi
- Ödeme güncelleme
- Ödeme linki oluşturma (ödeme isteği gönderme)
- Ödeme listesi, transaction listesi ve ödeme detay sorgulama
- Bin sorgulama, taksit tablosu ve karttan çekilecek tutar hesaplama
- Kart saklama servisleri (kart ekleme, güncelleme, silme, listeleme)
- Müşteri yönetimi servisleri
- Tekrarlayan ödeme servisleri (satış, takvim, ödeme planı, ürün)
- Bayi bilgisi, muhasebe ve ekstre raporları
- CheckKey üretimi ve 3D Secure hash doğrulaması
- Moka United test kartları ve banka hata kodları sözlüğü

## Gereksinimler

- Python 3.8 ve üzeri

## Kurulum

```bash
pip install moka-python
```

Kaynak koddan kurulum:

```bash
cd moka-python
pip install .
```

## Ortam Adresleri

| Ortam | Adres |
| --- | --- |
| Test ortamı | https://service.refmokaunited.com |
| Canlı ortam | https://service.mokaunited.com |

İstemci varsayılan olarak canlı ortama bağlanır. Test ortamı için `base_url` parametresi verilmelidir. Eski `service.moka.com` ve `service.refmoka.com` adresleri için `LEGACY_API_BASE` ve `LEGACY_TEST_API_BASE` sabitleri de mevcuttur.

Moka United servisleri PCI-DSS kuralları gereği yalnızca TLS 1.2 ve üstü protokollere izin verir. Bu kütüphanenin HTTP katmanı TLS 1.2 zorunluluğunu otomatik olarak uygular.

## Hızlı Başlangıç

```python
from moka import MokaClient, TEST_API_BASE, models

client = MokaClient(
    dealer_code="bayi kodunuz",
    username="api kullanici adiniz",
    password="api sifreniz",
    base_url=TEST_API_BASE,  # canli ortam icin bu satiri kaldirin
)
```

Kimlik doğrulamada kullanılan CheckKey değeri (DealerCode + "MK" + Username + "PD" + Password bilgisinin SHA-256 özeti) her istekte otomatik olarak üretilir ve eklenir.

### Alan adları

İstek modelleri, Moka United dokümantasyonundaki alan adlarıyla (PascalCase) birebir aynı alanları taşır. Alanlar hem API'deki adıyla hem de Python üslubundaki snake_case adıyla kullanılabilir:

```python
istek = models.CreatePaymentRequest(CardNumber="...", Amount=10)
istek.card_holder_full_name = "Ali Yilmaz"   # snake_case
istek.ClientIP = "192.168.1.116"             # PascalCase
```

### Yanıt nesnesi

Tüm servisler `ApiResponse` nesnesi döndürür:

| Alan | Açıklama |
| --- | --- |
| `data` | İstek başarılı ise servis verisi (dict), aksi halde None |
| `result_code` | Başarılı istekte "Success", hatada Moka hata kodu |
| `result_message` | Hataya ilişkin varsa açıklama |
| `exception` | Beklenmeyen hata oluştuğunda (EX) açıklama |
| `is_success` | İstek Moka United tarafında işlendiyse True |
| `is_payment_successful` | İstek ve banka işlemi birlikte başarılıysa True |

Önemli: `is_success` yalnızca isteğin Moka United tarafında işlendiğini gösterir. Ödeme işlemlerinde bankanın işlemi onaylayıp onaylamadığını görmek için `is_payment_successful` özelliği veya `data["IsSuccessful"]` alanı kontrol edilmelidir.

## Ödeme İşlemleri

### 3D Secure Olmadan Ödeme (Non-3D)

```python
from moka import models

istek = models.CreatePaymentRequest(
    CardHolderFullName="Ali Yilmaz",
    CardNumber="5127541122223332",
    ExpMonth="12",
    ExpYear="2030",
    CvcNumber="000",
    Amount=0.01,
    Currency="TL",
    InstallmentNumber=1,
    ClientIP="192.168.1.116",
    OtherTrxCode="SIPARIS-2026-0001",
    IsPoolPayment=0,
    IsTokenized=0,
    Software="yazilim adiniz",
    IsPreAuth=0,
    BuyerInformation=models.Buyer(
        BuyerFullName="Ali Yilmaz",
        BuyerGsmNumber="5551110022",
        BuyerEmail="ali@ornek.com",
        BuyerAddress="Tasdelen / Cekmekoy",
    ),
)

yanit = client.payments().create(istek)

if yanit.is_payment_successful:
    # Iptal, iade ve havuz onayi islemleri icin bu deger saklanmalidir
    siparis_no = yanit.data["VirtualPosOrderId"]
elif yanit.is_success:
    # Banka islemi reddetti
    print(yanit.data["ResultCode"], yanit.data["ResultMessage"])
else:
    # Istek Moka United tarafinda islenemedi
    print(yanit.result_code, yanit.result_message)
```

### 3D Secure ile Ödeme

3D ödemede `ReturnHash=1` ve `RedirectUrl` zorunludur. Yanıttaki `Url` değerine kullanıcı yönlendirilir; `CodeForHash` değeri veritabanında saklanır.

```python
istek = models.CreatePaymentRequest(
    CardHolderFullName="Ali Yilmaz",
    CardNumber="5127541122223332",
    ExpMonth="12",
    ExpYear="2030",
    CvcNumber="000",
    Amount=100.50,
    Currency="TL",
    InstallmentNumber=1,
    ClientIP="192.168.1.116",
    OtherTrxCode="SIPARIS-2026-0002",
    Software="yazilim adiniz",
    ReturnHash=1,
    RedirectUrl="https://www.siteniz.com/odeme-sonucu?islem=SIPARIS-2026-0002",
    RedirectType=0,
)

yanit = client.payments().create_threeds(istek)

if yanit.is_success:
    yonlendirme_adresi = yanit.data["Url"]
    code_for_hash = yanit.data["CodeForHash"]  # saklayin
```

Kart doğrulaması tamamlandığında Moka United, `RedirectUrl` adresinize `hashValue`, `resultCode`, `resultMessage`, `trxCode` ve `OtherTrxCode` alanlarını POST eder. Sonuç şu şekilde doğrulanır:

```python
from moka import verify_threeds_result

sonuc = verify_threeds_result(code_for_hash, gelen_hash_value)

if sonuc is True:
    # SHA256(CodeForHash + "T") eslesti: odeme basarili
    ...
elif sonuc is False:
    # SHA256(CodeForHash + "F") eslesti: odeme basarisiz
    ...
else:
    # Hash eslesmedi: istek gecersiz veya kurcalanmis
    ...
```

Başarılı işlemde `trxCode` alanında dönen OrderId değeri saklanmalıdır; iptal, iade ve havuz onayı işlemleri bu değerle yapılır.

### 3D Secure ile Mobil Ödeme

```python
istek = models.CreateMobilePaymentRequest(
    PaymentType=1,
    Amount=100,
    Currency="TL",
    InstallmentNumber=1,
    ClientIP="192.168.1.116",
    RedirectURL="https://www.siteniz.com/odeme-sonucu",
    OtherTrxCode="SIPARIS-2026-0003",
    Software="yazilim adiniz",
)

yanit = client.payments().create_threeds_mobile(istek)
```

### Ön Provizyon ve Capture

Ön provizyon için ödeme isteği `IsPreAuth=1` ile gönderilir; daha sonra capture ile ödemeye çevrilir:

```python
istek = models.CaptureRequest(
    VirtualPosOrderId="ORDER-...",
    OtherTrxCode="",
    Amount=100.50,
    ClientIP="192.168.1.116",
)

yanit = client.payments().capture(istek)
```

### Havuzdaki Ödemeyi Onaylama ve Onay İptali

Havuz ödemesi için ödeme isteği `IsPoolPayment=1` ile gönderilir. Ürün veya hizmet teslim edildikten sonra ödeme onaylanır:

```python
yanit = client.payments().approval(
    models.ApprovalRequest(VirtualPosOrderId="ORDER-...")
)

yanit = client.payments().cancel_approval(
    models.CancelApprovalRequest(VirtualPosOrderId="ORDER-...")
)
```

### Ödeme İptali ve İade

```python
# Iptal (void)
yanit = client.payments().cancel(
    models.CancelPaymentRequest(
        VirtualPosOrderId="ORDER-...",
        ClientIP="192.168.1.116",
        VoidRefundReason=2,
    )
)

# Iade talebi (tutar verilerek kismi iade de yapilabilir)
yanit = client.refunds().create(
    models.CreateRefundRequest(
        VirtualPosOrderId="ORDER-...",
        Amount=50.25,
    )
)
```

### Ödeme Güncelleme

```python
yanit = client.payments().update(
    models.UpdatePaymentRequest(
        DealerPaymentId=12345,
        Description="Guncellenmis aciklama",
    )
)
```

### Ödeme Linki Oluşturma

```python
istek = models.CreatePaymentLinkRequest(
    OtherTrxCode="SIPARIS-2026-0004",
    FullName="Ali Yilmaz",
    GsmNumber="5551110022",
    Email="ali@ornek.com",
    Amount=250,
    Currency="TL",
    IsThreeD=1,
)

yanit = client.payment_links().create(istek)
```

## Bilgi Alma İşlemleri

```python
# Odeme listesi
yanit = client.payments().all(
    models.RetrievePaymentListRequest(
        PaymentStartDate="2026-01-01",
        PaymentEndDate="2026-01-31",
    )
)

# Transaction listesi
yanit = client.transactions().all(
    models.RetrieveTransactionListRequest(
        TrxStartDate="2026-01-01",
        TrxEndDate="2026-01-31",
    )
)

# Odeme detayi (kendi islem kodunuzla sorgulayabilirsiniz)
yanit = client.payments().retrieve(
    models.RetrievePaymentDetailRequest(OtherTrxCode="SIPARIS-2026-0001")
)

# Bin sorgulama
yanit = client.bin_number().retrieve(
    models.RetrieveBinNumberRequest(BinNumber="512754")
)

# Taksit tablosu hesaplama
yanit = client.payments().retrieve_installment_info(
    models.RetrieveInstallmentInfoRequest(
        BinNumber="512754",
        Currency="TL",
        OrderAmount=1000,
        IsThreeD=1,
    )
)

# Karttan cekilecek tutar hesaplama
yanit = client.payments().retrieve_amount(
    models.RetrievePaymentAmountRequest(
        BinNumber="512754",
        Currency="TL",
        OrderAmount=1000,
        InstallmentNumber=3,
        IsThreeD=1,
    )
)
```

## Kart Saklama Servisleri

Kart saklama servislerini kullanabilmek için bayinin Moka United tarafında kart saklama hizmetinin aktive edilmiş olması gerekir.

```python
# Musteri ekleme
yanit = client.customers().create(
    models.CreateCustomerRequest(
        CustomerCode="MUSTERI-1",
        FirstName="Ali",
        LastName="Yilmaz",
        Email="ali@ornek.com",
    )
)

# Kartiyla birlikte musteri ekleme
yanit = client.customers().create_with_card(
    models.CreateCustomerWithCardRequest(
        CustomerCode="MUSTERI-2",
        FirstName="Veli",
        LastName="Yilmaz",
        CardHolderFullName="Veli Yilmaz",
        CardNumber="5127541122223332",
        ExpMonth="12",
        ExpYear="2030",
        CardName="Is kartim",
    )
)

# Musteriye kart ekleme
yanit = client.cards().create(
    models.CreateCardRequest(
        CustomerCode="MUSTERI-1",
        CardHolderFullName="Ali Yilmaz",
        CardNumber="5127541122223332",
        ExpMonth="12",
        ExpYear="2030",
        CardName="Maximum kartim",
    )
)

# Kart listesi, bilgi alma, guncelleme, silme
yanit = client.cards().all(models.RetrieveCardListRequest(CustomerCode="MUSTERI-1"))
yanit = client.cards().retrieve(models.RetrieveCardRequest(CardToken="..."))
yanit = client.cards().update(models.UpdateCardRequest(CardToken="...", CardName="Yeni ad"))
yanit = client.cards().delete(models.DeleteCardRequest(CardToken="..."))

# Musteri listesi, bilgi alma, guncelleme, silme
yanit = client.customers().all()
yanit = client.customers().retrieve(models.RetrieveCustomerRequest(CustomerCode="MUSTERI-1"))
yanit = client.customers().update(models.UpdateCustomerRequest(CustomerCode="MUSTERI-1", Email="yeni@ornek.com"))
yanit = client.customers().delete(models.DeleteCustomerRequest(CustomerCode="MUSTERI-1"))
```

Saklı kartla ödeme yapmak için ödeme isteğinde kart bilgileri yerine `CardToken` gönderilir.

## Tekrarlayan Ödeme Servisleri

```python
# Urun tanimlama
yanit = client.products().create(
    models.CreateProductRequest(ProductName="Aylik Uyelik", ProductCode="UYELIK-AY")
)

# Takvim tanimlama (ornek: her ayin 1'i)
yanit = client.schedules().create(
    models.CreateScheduleRequest(
        ScheduleName="Aylik",
        DailyWeeklyMonthly=3,
        EveryX=1,
        DaysOfMonth="1",
    )
)

# Satis olusturma
yanit = client.sales().create(
    models.CreateSaleRequest(
        CustomerCode="MUSTERI-1",
        ProductCode="UYELIK-AY",
        SaleCode="SATIS-1",
        Amount=99.90,
        Currency="TL",
    )
)

# Odeme plani islemleri
yanit = client.payment_plans().all(models.RetrievePaymentPlanListRequest(SaleCode="SATIS-1"))
yanit = client.payment_plans().create(models.CreatePaymentPlanRequest(SaleCode="SATIS-1", PaymentDate="2026-08-01", Amount=99.90))
yanit = client.payment_plans().retrieve_history(models.RetrievePaymentPlanHistoryListRequest(DealerPaymentPlanId=1))
```

## Muhasebe ve Raporlama

```python
# Bayi muhasebesi
yanit = client.reporting().accounting(
    models.ReportingAccountingListRequest(
        TransferStartDate="2026-01-01",
        TransferEndDate="2026-01-31",
    )
)

# Bayi ekstresi
yanit = client.reporting().statement(
    models.ReportingStatementListRequest(
        StatementStartDate="2026-01-01",
        StatementEndDate="2026-01-31",
    )
)

# Bayi bilgisi
yanit = client.dealers().retrieve(models.RetrieveDealerRequest())
```

## Hata Kodları

Bankadan dönen işlem hatalarının Türkçe karşılıkları `moka.error_codes` modülünde yer alır:

```python
from moka import get_bank_error_message

mesaj = get_bank_error_message("002")  # "Limit Yetersiz"
```

Moka United tarafından dönen servis hata kodları (örneğin `PaymentDealer.CheckPaymentDealerAuthentication.InvalidAccount`) `ApiResponse.result_code` alanında bulunur; tam liste için [Moka United dokümantasyonuna](https://developer.mokaunited.com) bakınız.

## Test Kartları

Test kartlarıyla yapılan ödemeler bankaya gönderilmez; cevap Moka United sisteminden döner. Güncel test kartı listesi için resmi dokümantasyona bakınız (liste zaman içinde değişebilir):

https://developer.mokaunited.com/home.php?page=test-kartlari

Geliştirme kolaylığı için kartlara kod içinden `moka.test_cards.TEST_CARDS` listesiyle veya `get_test_card` fonksiyonuyla da erişilebilir:

```python
from moka import get_test_card

kart = get_test_card(bank="Garanti Bankasi")
kart = get_test_card(card_type="Troy")
```

## Testler

Birim testler ağ bağlantısı gerektirmez:

```bash
python -m unittest discover -s tests -v
```

Moka United test ortamına karşı gerçek istek atan canlı testler, aşağıdaki ortam değişkenleri tanımlandığında otomatik olarak çalışır:

```bash
export MOKA_DEALER_CODE="bayi kodunuz"
export MOKA_USERNAME="api kullanici adiniz"
export MOKA_PASSWORD="api sifreniz"
python -m unittest tests.test_live -v
```

## PyPI Yayınlama (twine)

```bash
pip install build twine
python -m build
twine upload dist/*
```

## Örnek Kodlar

`samples/` klasöründe çalıştırılabilir örnekler yer alır:

- `create_payment.py`: Non-3D ödeme
- `create_threeds_payment.py`: 3D Secure ile ödeme
- `retrieve_bin.py`: Bin sorgulama
- `retrieve_installment_info.py`: Taksit tablosu hesaplama

## Lisans

MIT lisansı ile dağıtılmaktadır. Ayrıntılar için LICENSE dosyasına bakınız.
