Skip to content
Merged
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
103 changes: 103 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,65 @@ confirm_result: str = client.confirmWebhook("https://your-webhook.com")
confirm_result: ConfirmWebhookResponse = client.webhooks.confirm("https://your-webhook.com")
```

### Attribute naming convention

v0.x uses **camelCase** for all type attributes. v1.x uses **snake_case** for all type attributes.

```python
# before (v0.x - camelCase)
result.checkoutUrl
result.orderCode
result.paymentLinkId
payment_link.amountPaid
payment_link.createdAt
webhook_data.transactionDateTime

# after (v1.x - snake_case)
result.checkout_url
result.order_code
result.payment_link_id
payment_link.amount_paid
payment_link.created_at
webhook_data.transaction_date_time
```

### `to_json()` method behavior change

The `to_json()` method behavior has changed between v0.x and v1.x:

| Version | `to_json()` returns | Key format |
| ------- | ------------------- | ---------- |
| v0.x | `dict` | camelCase |
| v1.x | `str` (JSON string) | snake_case |

```python
# before (v0.x) - returns dict with camelCase keys
result = client.createPaymentLink(payment_data)
json_dict = result.to_json() # Returns: {"orderCode": 123, "checkoutUrl": "...", ...}
type(json_dict) # <class 'dict'>

# after (v1.x) - returns JSON string with snake_case keys
result = client.payment_requests.create(payment_data)
json_str = result.to_json() # Returns: '{"order_code": 123, "checkout_url": "...", ...}'
type(json_str) # <class 'str'>
```

To get equivalent v0.x behavior in v1.x, use `model_dump_camel_case()`:

```python
# v1.x - get dict with camelCase keys (equivalent to v0.x to_json())
result = client.payment_requests.create(payment_data)
json_dict = result.model_dump_camel_case() # Returns: {"orderCode": 123, "checkoutUrl": "...", ...}
type(json_dict) # <class 'dict'>

# v1.x - other serialization options
result.model_dump() # dict with snake_case keys
result.model_dump(by_alias=True) # dict with camelCase keys (same as model_dump_camel_case)
result.model_dump_snake_case() # dict with snake_case keys (same as model_dump)
result.model_dump_json() # JSON string with snake_case keys (same as to_json)
result.model_dump_json(by_alias=True) # JSON string with camelCase keys
```

### Handling errors

The library now raise exception as `PayOSError`, API related errors as `APIError`, webhook related errors as `WebhookError` and signature related errors as `InvalidSignatureError` instead of raise `PayOSError` for related API errors and `Error` for other errors.
Expand All @@ -82,3 +141,47 @@ try:
except APIError as e:
print(e)
```

## Backward compatibility layer

v1.x includes a backward compatibility layer that allows v0.x code to work with deprecation warnings. This gives you time to migrate gradually.

### Using the compatibility layer

Your existing v0.x code will continue to work:

```python
from payos import PayOS
from payos.type import PaymentData, ItemData

client = PayOS(client_id, api_key, checksum_key)

# v0.x style - still works with deprecation warnings
item = ItemData(name="Product", quantity=1, price=1000)
payment_data = PaymentData(
orderCode=123,
amount=1000,
description="Order",
cancelUrl="http://cancel",
returnUrl="http://return",
items=[item],
)

# Legacy methods return the exact same types as v0.x
result = client.createPaymentLink(payment_data)
print(result.checkoutUrl) # camelCase works
print(result.to_json()) # Returns dict with camelCase keys (v0.x behavior)

info = client.getPaymentLinkInformation(123)
print(info.orderCode) # camelCase works
print(info.amountPaid) # camelCase works

# Legacy methods also throw the same errors as v0.x
from payos.custom_error import PayOSError
try:
result = client.createPaymentLink(payment_data)
except PayOSError as e:
print(e.code, e.message) # Same error interface as v0.x
```

We recommend migrating to the new API before v2.0.0 release.
111 changes: 111 additions & 0 deletions examples/legacy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""Legacy API example - demonstrates v0.x compatibility with v1.x SDK.

Run with: python examples/legacy.py
"""

import os
import warnings
import time

# Set to "ignore" for cleaner output
warnings.filterwarnings("ignore", category=DeprecationWarning)

from payos import PayOS
from payos.type import PaymentData, ItemData
from payos.constants import ERROR_MESSAGE, ERROR_CODE
from payos.custom_error import PayOSError as LegacyPayOSError
from payos.utils import (
convertObjToQueryStr,
sortObjDataByKey,
createSignatureFromObj,
createSignatureOfPaymentRequest,
)


# Legacy constants
print(f"ERROR_MESSAGE: {ERROR_MESSAGE}")
print(f"ERROR_CODE: {ERROR_CODE}\n")

# Legacy error with (code, message) signature
legacy_error = LegacyPayOSError(code="20", message="Internal Server Error")

# Legacy types: PaymentData, ItemData
item = ItemData(name="Mi tom hao hao ly", quantity=1, price=1000)
payment_data = PaymentData(
orderCode=int(time.time()),
amount=1000,
description="Thanh toan don hang",
cancelUrl="http://localhost:8000/cancel",
returnUrl="http://localhost:8000/success",
items=[item],
buyerName="Nguyen Van A",
buyerEmail="test@example.com",
buyerPhone="0123456789",
)
webhook_body = {
"code": "00",
"desc": "success",
"success": True,
"data": {
"orderCode": 123,
"amount": 3000,
"description": "VQRIO123",
"accountNumber": "12345678",
"reference": "TF230204212323",
"transactionDateTime": "2023-02-04 18:25:00",
"currency": "VND",
"paymentLinkId": "124c33293c43417ab7879e14c8d9eb18",
"code": "00",
"desc": "Thành công",
"counterAccountBankId": "",
"counterAccountBankName": "",
"counterAccountName": "",
"counterAccountNumber": "",
"virtualAccountName": "",
"virtualAccountNumber": "",
},
"signature": "",
}

# Legacy utils
test_key = "test_checksum_key"
sorted_obj = sortObjDataByKey({"b": 2, "a": 1})
query_str = convertObjToQueryStr({"amount": 1000, "orderCode": 123})
sig_from_obj = createSignatureFromObj({"amount": 1000}, test_key)
sig_payment = createSignatureOfPaymentRequest(payment_data, test_key)

# Initialize client
client_id = "your_client_id"
api_key = "your_api_key"
checksum_key = "your_checksum_key"

if all([client_id, api_key, checksum_key]):
payOS = PayOS(client_id=client_id, api_key=api_key, checksum_key=checksum_key)

try:
# Legacy: createPaymentLink() -> New: payment_requests.create()
result = payOS.createPaymentLink(payment_data)
print(f"Created: {result.checkoutUrl}")
print(f"to json: {result.to_json()}")

# Legacy: getPaymentLinkInformation() -> New: payment_requests.get()
info = payOS.getPaymentLinkInformation(payment_data.orderCode)
print(f"Status: {info}")

# Legacy: cancelPaymentLink() -> New: payment_requests.cancel()
cancelled = payOS.cancelPaymentLink(payment_data.orderCode, "Test")
print(f"Cancelled: {cancelled}")

# Legacy: confirmWebhook() -> New: webhooks.confirm()
webhook_response = payOS.confirmWebhook("https://your-domain.com/webhook")
print(f"Webhook confirmed: {webhook_response}")

# Legacy: verifyPaymentWebhookData() -> New: webhooks.verify()
webhook_body["signature"] = createSignatureFromObj(webhook_body["data"], checksum_key)
verified = payOS.verifyPaymentWebhookData(webhook_body)
print(f"Webhook verified: {verified}")

except LegacyPayOSError as e:
print(f"Error: {e}")
else:
print("Set PAYOS_CLIENT_ID, PAYOS_API_KEY, PAYOS_CHECKSUM_KEY to test API calls")
Loading