kirimel-python 0.1.7__tar.gz → 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/PKG-INFO +204 -3
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/README.md +203 -2
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/kirimel/__init__.py +1 -0
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/kirimel/client.py +79 -0
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/kirimel/exceptions.py +15 -2
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/kirimel/http_client.py +11 -4
- kirimel_python-2.0.0/kirimel/loyalty_http_client.py +141 -0
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/kirimel/resources/__init__.py +72 -4
- kirimel_python-2.0.0/kirimel/resources/loyalty/__init__.py +8 -0
- kirimel_python-2.0.0/kirimel/resources/loyalty/customers.py +98 -0
- kirimel_python-2.0.0/kirimel/resources/loyalty/points.py +58 -0
- kirimel_python-2.0.0/kirimel/resources/loyalty/vouchers.py +70 -0
- kirimel_python-2.0.0/kirimel/resources/loyalty/wallet.py +34 -0
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/kirimel_python.egg-info/PKG-INFO +204 -3
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/kirimel_python.egg-info/SOURCES.txt +6 -0
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/pyproject.toml +2 -2
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/LICENSE +0 -0
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/kirimel_python.egg-info/dependency_links.txt +0 -0
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/kirimel_python.egg-info/requires.txt +0 -0
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/kirimel_python.egg-info/top_level.txt +0 -0
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/setup.cfg +0 -0
- {kirimel_python-0.1.7 → kirimel_python-2.0.0}/tests/test_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kirimel-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Official KiriMel Python SDK
|
|
5
5
|
Author-email: KiriMel <support@kirimel.com>
|
|
6
6
|
License: MIT
|
|
@@ -30,7 +30,15 @@ Dynamic: license-file
|
|
|
30
30
|
|
|
31
31
|
# KiriMel Python SDK
|
|
32
32
|
|
|
33
|
-
Official Python SDK for KiriMel Email Marketing API.
|
|
33
|
+
Official Python SDK for KiriMel Email Marketing API & Loyalty API.
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- **Email API**: Manage campaigns, subscribers, lists, templates, forms, workflows & more
|
|
38
|
+
- **Loyalty API**: Customer loyalty with points, vouchers, tiers, and wallet management
|
|
39
|
+
- **Unified Client**: Single SDK for both APIs with different authentication methods
|
|
40
|
+
- **Type Hints**: Full type annotations for Python 3.8+
|
|
41
|
+
- **Retry Logic**: Built-in exponential backoff for failed requests
|
|
34
42
|
|
|
35
43
|
## Installation
|
|
36
44
|
|
|
@@ -454,6 +462,199 @@ node_types = client.workflows.node_types()
|
|
|
454
462
|
data = client.workflows.get_data(id)
|
|
455
463
|
```
|
|
456
464
|
|
|
465
|
+
### Email (Transactional)
|
|
466
|
+
|
|
467
|
+
Send transactional emails with attachment support:
|
|
468
|
+
|
|
469
|
+
```python
|
|
470
|
+
# Send transactional email
|
|
471
|
+
result = client.email.send({
|
|
472
|
+
'to': 'user@example.com',
|
|
473
|
+
'subject': 'Welcome to our service!',
|
|
474
|
+
'html': '<h1>Welcome!</h1><p>Thanks for signing up.</p>',
|
|
475
|
+
'text': 'Welcome! Thanks for signing up.',
|
|
476
|
+
'from_name': 'My App',
|
|
477
|
+
'reply_to': 'support@example.com',
|
|
478
|
+
'cc': 'cc@example.com',
|
|
479
|
+
'bcc': ['bcc1@example.com', 'bcc2@example.com'],
|
|
480
|
+
'attachments': [
|
|
481
|
+
{
|
|
482
|
+
'name': 'invoice.pdf',
|
|
483
|
+
'content': base64.b64encode(open('/path/to/invoice.pdf', 'rb').read()).decode()
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
'name': 'report.csv',
|
|
487
|
+
'content': base64.b64encode(open('/path/to/report.csv', 'rb').read()).decode()
|
|
488
|
+
}
|
|
489
|
+
]
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
# Response includes message_id and tracking_id
|
|
493
|
+
print(f"Message ID: {result['message_id']}")
|
|
494
|
+
print(f"Tracking ID: {result['tracking_id']}")
|
|
495
|
+
|
|
496
|
+
# Send to multiple recipients
|
|
497
|
+
result = client.email.send({
|
|
498
|
+
'to': ['user1@example.com', 'user2@example.com'],
|
|
499
|
+
'subject': 'Team update',
|
|
500
|
+
'html': '<p>Here is the latest update.</p>'
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
# Get SES send quota
|
|
504
|
+
quota = client.email.quota()
|
|
505
|
+
print(f"Remaining: {quota['remaining']}")
|
|
506
|
+
print(f"Max 24h: {quota['max_24_hour_send']}")
|
|
507
|
+
print(f"Utilization: {quota['utilization_percent']}%")
|
|
508
|
+
|
|
509
|
+
# Get verified emails
|
|
510
|
+
verified = client.email.verified_emails()
|
|
511
|
+
|
|
512
|
+
# Verify a new email address
|
|
513
|
+
result = client.email.verify_email('new@example.com')
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## Loyalty API
|
|
517
|
+
|
|
518
|
+
The Loyalty API uses HMAC SHA256 signature authentication for secure POS integration. The SDK handles signature calculation automatically.
|
|
519
|
+
|
|
520
|
+
### Authentication
|
|
521
|
+
|
|
522
|
+
```python
|
|
523
|
+
# Email API credentials (required)
|
|
524
|
+
client = kirimel.KiriMel(api_key='sk_test_xxx')
|
|
525
|
+
|
|
526
|
+
# Loyalty API credentials (optional - only if using loyalty features)
|
|
527
|
+
client = kirimel.KiriMel(
|
|
528
|
+
api_key='sk_test_xxx',
|
|
529
|
+
client_key='cli_test_xxx', # Or KIRIMEL_LOYALTY_CLIENT_KEY env var
|
|
530
|
+
client_secret='your_secret_here' # Or KIRIMEL_LOYALTY_CLIENT_SECRET env var
|
|
531
|
+
)
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Customers
|
|
535
|
+
|
|
536
|
+
```python
|
|
537
|
+
# Register a new customer
|
|
538
|
+
customer = client.loyalty_customers.register({
|
|
539
|
+
'phone': '+60123456789',
|
|
540
|
+
'name': 'John Doe',
|
|
541
|
+
'email': 'john@example.com',
|
|
542
|
+
'birth_date': '1990-05-15', # Optional
|
|
543
|
+
'qr_code': 'CUSTOMER_123' # Optional
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
# Look up customer by phone
|
|
547
|
+
customer = client.loyalty_customers.lookup({
|
|
548
|
+
'phone': '+60123456789'
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
# Get customer profile
|
|
552
|
+
profile = client.loyalty_customers.get(customer_id)
|
|
553
|
+
|
|
554
|
+
# Get customer transactions
|
|
555
|
+
transactions = client.loyalty_customers.transactions(customer_id)
|
|
556
|
+
|
|
557
|
+
# Manually adjust points
|
|
558
|
+
adjustment = client.loyalty_customers.adjust(customer_id, {
|
|
559
|
+
'points': 50,
|
|
560
|
+
'reference': 'MANUAL_ADJUST_001',
|
|
561
|
+
'description': 'Goodwill gesture',
|
|
562
|
+
'adjusted_by': 'Admin'
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
# Get customer tier
|
|
566
|
+
tier = client.loyalty_customers.tier(customer_id)
|
|
567
|
+
|
|
568
|
+
# List customers
|
|
569
|
+
customers = client.loyalty_customers.list({
|
|
570
|
+
'page': 1,
|
|
571
|
+
'per_page': 50,
|
|
572
|
+
'tier': 'gold'
|
|
573
|
+
})
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Points & Wallet
|
|
577
|
+
|
|
578
|
+
```python
|
|
579
|
+
# Award points
|
|
580
|
+
earn = client.loyalty_points.earn({
|
|
581
|
+
'customer_id': customer_id,
|
|
582
|
+
'points': 100,
|
|
583
|
+
'amount': 50.50,
|
|
584
|
+
'reference_id': 'ORDER_123',
|
|
585
|
+
'description': 'Purchase reward'
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
# Preview redemption (check before confirming)
|
|
589
|
+
preview = client.loyalty_points.preview_redeem({
|
|
590
|
+
'customer_id': customer_id,
|
|
591
|
+
'points_to_redeem': 100
|
|
592
|
+
})
|
|
593
|
+
# Returns: points_value, max_redeemable, amount_discount
|
|
594
|
+
|
|
595
|
+
# Confirm redemption
|
|
596
|
+
redeem = client.loyalty_points.commit_redeem({
|
|
597
|
+
'customer_id': customer_id,
|
|
598
|
+
'points_to_redeem': 100,
|
|
599
|
+
'reference_id': 'BILL_456'
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
# Reverse transaction (if needed)
|
|
603
|
+
reverse = client.loyalty_points.reverse({
|
|
604
|
+
'transaction_id': transaction_id,
|
|
605
|
+
'reason': 'Customer return',
|
|
606
|
+
'reference_id': 'RETURN_123'
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
# Get wallet balance
|
|
610
|
+
balance = client.loyalty_wallet.balance({
|
|
611
|
+
'customer_id': customer_id
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
# Recalculate balance from ledger
|
|
615
|
+
recalc = client.loyalty_wallet.recalculate({
|
|
616
|
+
'customer_id': customer_id
|
|
617
|
+
})
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Vouchers
|
|
621
|
+
|
|
622
|
+
```python
|
|
623
|
+
# Create voucher batch
|
|
624
|
+
batch = client.loyalty_vouchers.create_batch({
|
|
625
|
+
'name': 'Grand Opening Promo',
|
|
626
|
+
'type': 'PERCENT', # or 'FIXED'
|
|
627
|
+
'value': 10,
|
|
628
|
+
'quantity': 100,
|
|
629
|
+
'valid_from': '2024-06-01',
|
|
630
|
+
'valid_until': '2024-12-31',
|
|
631
|
+
'min_purchase': 50.00,
|
|
632
|
+
'max_discount': 25.00
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
# List voucher batches
|
|
636
|
+
batches = client.loyalty_vouchers.list_batches()
|
|
637
|
+
|
|
638
|
+
# Issue voucher to customer
|
|
639
|
+
issue = client.loyalty_vouchers.issue({
|
|
640
|
+
'voucher_batch_id': batch_id,
|
|
641
|
+
'customer_id': customer_id,
|
|
642
|
+
'delivered_via': 'email', # or 'sms'
|
|
643
|
+
'reference_id': 'PROMO_001'
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
# Redeem voucher
|
|
647
|
+
redeem = client.loyalty_vouchers.redeem({
|
|
648
|
+
'code': 'VOUCHER_A1B2C3D4E5F6',
|
|
649
|
+
'customer_id': customer_id,
|
|
650
|
+
'purchase_amount': 75.00,
|
|
651
|
+
'reference_id': 'ORDER_789'
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
# Get voucher details
|
|
655
|
+
voucher = client.loyalty_vouchers.get('VOUCHER_A1B2C3D4E5F6')
|
|
656
|
+
```
|
|
657
|
+
|
|
457
658
|
## Error Handling
|
|
458
659
|
|
|
459
660
|
```python
|
|
@@ -502,6 +703,6 @@ MIT License
|
|
|
502
703
|
|
|
503
704
|
## Support
|
|
504
705
|
|
|
505
|
-
- Documentation: https://
|
|
706
|
+
- Documentation: https://kirimel.com/api-docs
|
|
506
707
|
- GitHub: https://github.com/hualiglobal/kirimel-python-sdk
|
|
507
708
|
- Issues: https://github.com/hualiglobal/kirimel-python-sdk/issues
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# KiriMel Python SDK
|
|
2
2
|
|
|
3
|
-
Official Python SDK for KiriMel Email Marketing API.
|
|
3
|
+
Official Python SDK for KiriMel Email Marketing API & Loyalty API.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Email API**: Manage campaigns, subscribers, lists, templates, forms, workflows & more
|
|
8
|
+
- **Loyalty API**: Customer loyalty with points, vouchers, tiers, and wallet management
|
|
9
|
+
- **Unified Client**: Single SDK for both APIs with different authentication methods
|
|
10
|
+
- **Type Hints**: Full type annotations for Python 3.8+
|
|
11
|
+
- **Retry Logic**: Built-in exponential backoff for failed requests
|
|
4
12
|
|
|
5
13
|
## Installation
|
|
6
14
|
|
|
@@ -424,6 +432,199 @@ node_types = client.workflows.node_types()
|
|
|
424
432
|
data = client.workflows.get_data(id)
|
|
425
433
|
```
|
|
426
434
|
|
|
435
|
+
### Email (Transactional)
|
|
436
|
+
|
|
437
|
+
Send transactional emails with attachment support:
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
# Send transactional email
|
|
441
|
+
result = client.email.send({
|
|
442
|
+
'to': 'user@example.com',
|
|
443
|
+
'subject': 'Welcome to our service!',
|
|
444
|
+
'html': '<h1>Welcome!</h1><p>Thanks for signing up.</p>',
|
|
445
|
+
'text': 'Welcome! Thanks for signing up.',
|
|
446
|
+
'from_name': 'My App',
|
|
447
|
+
'reply_to': 'support@example.com',
|
|
448
|
+
'cc': 'cc@example.com',
|
|
449
|
+
'bcc': ['bcc1@example.com', 'bcc2@example.com'],
|
|
450
|
+
'attachments': [
|
|
451
|
+
{
|
|
452
|
+
'name': 'invoice.pdf',
|
|
453
|
+
'content': base64.b64encode(open('/path/to/invoice.pdf', 'rb').read()).decode()
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
'name': 'report.csv',
|
|
457
|
+
'content': base64.b64encode(open('/path/to/report.csv', 'rb').read()).decode()
|
|
458
|
+
}
|
|
459
|
+
]
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
# Response includes message_id and tracking_id
|
|
463
|
+
print(f"Message ID: {result['message_id']}")
|
|
464
|
+
print(f"Tracking ID: {result['tracking_id']}")
|
|
465
|
+
|
|
466
|
+
# Send to multiple recipients
|
|
467
|
+
result = client.email.send({
|
|
468
|
+
'to': ['user1@example.com', 'user2@example.com'],
|
|
469
|
+
'subject': 'Team update',
|
|
470
|
+
'html': '<p>Here is the latest update.</p>'
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
# Get SES send quota
|
|
474
|
+
quota = client.email.quota()
|
|
475
|
+
print(f"Remaining: {quota['remaining']}")
|
|
476
|
+
print(f"Max 24h: {quota['max_24_hour_send']}")
|
|
477
|
+
print(f"Utilization: {quota['utilization_percent']}%")
|
|
478
|
+
|
|
479
|
+
# Get verified emails
|
|
480
|
+
verified = client.email.verified_emails()
|
|
481
|
+
|
|
482
|
+
# Verify a new email address
|
|
483
|
+
result = client.email.verify_email('new@example.com')
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
## Loyalty API
|
|
487
|
+
|
|
488
|
+
The Loyalty API uses HMAC SHA256 signature authentication for secure POS integration. The SDK handles signature calculation automatically.
|
|
489
|
+
|
|
490
|
+
### Authentication
|
|
491
|
+
|
|
492
|
+
```python
|
|
493
|
+
# Email API credentials (required)
|
|
494
|
+
client = kirimel.KiriMel(api_key='sk_test_xxx')
|
|
495
|
+
|
|
496
|
+
# Loyalty API credentials (optional - only if using loyalty features)
|
|
497
|
+
client = kirimel.KiriMel(
|
|
498
|
+
api_key='sk_test_xxx',
|
|
499
|
+
client_key='cli_test_xxx', # Or KIRIMEL_LOYALTY_CLIENT_KEY env var
|
|
500
|
+
client_secret='your_secret_here' # Or KIRIMEL_LOYALTY_CLIENT_SECRET env var
|
|
501
|
+
)
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Customers
|
|
505
|
+
|
|
506
|
+
```python
|
|
507
|
+
# Register a new customer
|
|
508
|
+
customer = client.loyalty_customers.register({
|
|
509
|
+
'phone': '+60123456789',
|
|
510
|
+
'name': 'John Doe',
|
|
511
|
+
'email': 'john@example.com',
|
|
512
|
+
'birth_date': '1990-05-15', # Optional
|
|
513
|
+
'qr_code': 'CUSTOMER_123' # Optional
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
# Look up customer by phone
|
|
517
|
+
customer = client.loyalty_customers.lookup({
|
|
518
|
+
'phone': '+60123456789'
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
# Get customer profile
|
|
522
|
+
profile = client.loyalty_customers.get(customer_id)
|
|
523
|
+
|
|
524
|
+
# Get customer transactions
|
|
525
|
+
transactions = client.loyalty_customers.transactions(customer_id)
|
|
526
|
+
|
|
527
|
+
# Manually adjust points
|
|
528
|
+
adjustment = client.loyalty_customers.adjust(customer_id, {
|
|
529
|
+
'points': 50,
|
|
530
|
+
'reference': 'MANUAL_ADJUST_001',
|
|
531
|
+
'description': 'Goodwill gesture',
|
|
532
|
+
'adjusted_by': 'Admin'
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
# Get customer tier
|
|
536
|
+
tier = client.loyalty_customers.tier(customer_id)
|
|
537
|
+
|
|
538
|
+
# List customers
|
|
539
|
+
customers = client.loyalty_customers.list({
|
|
540
|
+
'page': 1,
|
|
541
|
+
'per_page': 50,
|
|
542
|
+
'tier': 'gold'
|
|
543
|
+
})
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Points & Wallet
|
|
547
|
+
|
|
548
|
+
```python
|
|
549
|
+
# Award points
|
|
550
|
+
earn = client.loyalty_points.earn({
|
|
551
|
+
'customer_id': customer_id,
|
|
552
|
+
'points': 100,
|
|
553
|
+
'amount': 50.50,
|
|
554
|
+
'reference_id': 'ORDER_123',
|
|
555
|
+
'description': 'Purchase reward'
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
# Preview redemption (check before confirming)
|
|
559
|
+
preview = client.loyalty_points.preview_redeem({
|
|
560
|
+
'customer_id': customer_id,
|
|
561
|
+
'points_to_redeem': 100
|
|
562
|
+
})
|
|
563
|
+
# Returns: points_value, max_redeemable, amount_discount
|
|
564
|
+
|
|
565
|
+
# Confirm redemption
|
|
566
|
+
redeem = client.loyalty_points.commit_redeem({
|
|
567
|
+
'customer_id': customer_id,
|
|
568
|
+
'points_to_redeem': 100,
|
|
569
|
+
'reference_id': 'BILL_456'
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
# Reverse transaction (if needed)
|
|
573
|
+
reverse = client.loyalty_points.reverse({
|
|
574
|
+
'transaction_id': transaction_id,
|
|
575
|
+
'reason': 'Customer return',
|
|
576
|
+
'reference_id': 'RETURN_123'
|
|
577
|
+
})
|
|
578
|
+
|
|
579
|
+
# Get wallet balance
|
|
580
|
+
balance = client.loyalty_wallet.balance({
|
|
581
|
+
'customer_id': customer_id
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
# Recalculate balance from ledger
|
|
585
|
+
recalc = client.loyalty_wallet.recalculate({
|
|
586
|
+
'customer_id': customer_id
|
|
587
|
+
})
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Vouchers
|
|
591
|
+
|
|
592
|
+
```python
|
|
593
|
+
# Create voucher batch
|
|
594
|
+
batch = client.loyalty_vouchers.create_batch({
|
|
595
|
+
'name': 'Grand Opening Promo',
|
|
596
|
+
'type': 'PERCENT', # or 'FIXED'
|
|
597
|
+
'value': 10,
|
|
598
|
+
'quantity': 100,
|
|
599
|
+
'valid_from': '2024-06-01',
|
|
600
|
+
'valid_until': '2024-12-31',
|
|
601
|
+
'min_purchase': 50.00,
|
|
602
|
+
'max_discount': 25.00
|
|
603
|
+
})
|
|
604
|
+
|
|
605
|
+
# List voucher batches
|
|
606
|
+
batches = client.loyalty_vouchers.list_batches()
|
|
607
|
+
|
|
608
|
+
# Issue voucher to customer
|
|
609
|
+
issue = client.loyalty_vouchers.issue({
|
|
610
|
+
'voucher_batch_id': batch_id,
|
|
611
|
+
'customer_id': customer_id,
|
|
612
|
+
'delivered_via': 'email', # or 'sms'
|
|
613
|
+
'reference_id': 'PROMO_001'
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
# Redeem voucher
|
|
617
|
+
redeem = client.loyalty_vouchers.redeem({
|
|
618
|
+
'code': 'VOUCHER_A1B2C3D4E5F6',
|
|
619
|
+
'customer_id': customer_id,
|
|
620
|
+
'purchase_amount': 75.00,
|
|
621
|
+
'reference_id': 'ORDER_789'
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
# Get voucher details
|
|
625
|
+
voucher = client.loyalty_vouchers.get('VOUCHER_A1B2C3D4E5F6')
|
|
626
|
+
```
|
|
627
|
+
|
|
427
628
|
## Error Handling
|
|
428
629
|
|
|
429
630
|
```python
|
|
@@ -472,6 +673,6 @@ MIT License
|
|
|
472
673
|
|
|
473
674
|
## Support
|
|
474
675
|
|
|
475
|
-
- Documentation: https://
|
|
676
|
+
- Documentation: https://kirimel.com/api-docs
|
|
476
677
|
- GitHub: https://github.com/hualiglobal/kirimel-python-sdk
|
|
477
678
|
- Issues: https://github.com/hualiglobal/kirimel-python-sdk/issues
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"""
|
|
2
2
|
KiriMel Python SDK Client
|
|
3
3
|
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
4
6
|
from typing import Optional
|
|
5
7
|
from .http_client import HttpClient
|
|
8
|
+
from .loyalty_http_client import LoyaltyHttpClient
|
|
6
9
|
from .resources import (
|
|
7
10
|
Campaigns,
|
|
8
11
|
Subscribers,
|
|
@@ -14,6 +17,13 @@ from .resources import (
|
|
|
14
17
|
LandingPages,
|
|
15
18
|
Workflows,
|
|
16
19
|
Webhooks,
|
|
20
|
+
Email,
|
|
21
|
+
)
|
|
22
|
+
from .resources.loyalty import (
|
|
23
|
+
Customers as LoyaltyCustomers,
|
|
24
|
+
Points as LoyaltyPoints,
|
|
25
|
+
Vouchers as LoyaltyVouchers,
|
|
26
|
+
Wallet as LoyaltyWallet,
|
|
17
27
|
)
|
|
18
28
|
|
|
19
29
|
|
|
@@ -33,6 +43,8 @@ class KiriMel:
|
|
|
33
43
|
base_url: str = "https://kirimel.com/api",
|
|
34
44
|
timeout: int = 30,
|
|
35
45
|
retries: int = 3,
|
|
46
|
+
client_key: Optional[str] = None,
|
|
47
|
+
client_secret: Optional[str] = None,
|
|
36
48
|
):
|
|
37
49
|
"""
|
|
38
50
|
Create a new API client
|
|
@@ -42,6 +54,8 @@ class KiriMel:
|
|
|
42
54
|
base_url: Base URL (default: https://kirimel.com/api)
|
|
43
55
|
timeout: Request timeout in seconds (default: 30)
|
|
44
56
|
retries: Number of retries (default: 3)
|
|
57
|
+
client_key: Loyalty API client key (or use KIRIMEL_LOYALTY_CLIENT_KEY env var)
|
|
58
|
+
client_secret: Loyalty API client secret (or use KIRIMEL_LOYALTY_CLIENT_SECRET env var)
|
|
45
59
|
"""
|
|
46
60
|
self._http_client = HttpClient(
|
|
47
61
|
api_key=api_key,
|
|
@@ -59,6 +73,32 @@ class KiriMel:
|
|
|
59
73
|
self._landing_pages: Optional[LandingPages] = None
|
|
60
74
|
self._workflows: Optional[Workflows] = None
|
|
61
75
|
self._webhooks: Optional[Webhooks] = None
|
|
76
|
+
self._email: Optional[Email] = None
|
|
77
|
+
|
|
78
|
+
# Loyalty API clients
|
|
79
|
+
self._loyalty_http_client: Optional[LoyaltyHttpClient] = None
|
|
80
|
+
self._loyalty_customers: Optional[LoyaltyCustomers] = None
|
|
81
|
+
self._loyalty_points: Optional[LoyaltyPoints] = None
|
|
82
|
+
self._loyalty_vouchers: Optional[LoyaltyVouchers] = None
|
|
83
|
+
self._loyalty_wallet: Optional[LoyaltyWallet] = None
|
|
84
|
+
|
|
85
|
+
# Store credentials for lazy initialization
|
|
86
|
+
self._loyalty_base_url = base_url.replace("/api", "")
|
|
87
|
+
self._loyalty_client_key = client_key or os.getenv("KIRIMEL_LOYALTY_CLIENT_KEY")
|
|
88
|
+
self._loyalty_client_secret = client_secret or os.getenv("KIRIMEL_LOYALTY_CLIENT_SECRET")
|
|
89
|
+
self._loyalty_timeout = timeout
|
|
90
|
+
self._loyalty_retries = retries
|
|
91
|
+
|
|
92
|
+
def _init_loyalty_client(self) -> None:
|
|
93
|
+
"""Initialize loyalty HTTP client (lazy initialization)"""
|
|
94
|
+
if self._loyalty_http_client is None:
|
|
95
|
+
self._loyalty_http_client = LoyaltyHttpClient(
|
|
96
|
+
client_key=self._loyalty_client_key,
|
|
97
|
+
client_secret=self._loyalty_client_secret,
|
|
98
|
+
base_url=self._loyalty_base_url,
|
|
99
|
+
timeout=self._loyalty_timeout,
|
|
100
|
+
retries=self._loyalty_retries,
|
|
101
|
+
)
|
|
62
102
|
|
|
63
103
|
@property
|
|
64
104
|
def campaigns(self) -> Campaigns:
|
|
@@ -129,3 +169,42 @@ class KiriMel:
|
|
|
129
169
|
if self._webhooks is None:
|
|
130
170
|
self._webhooks = Webhooks(self._http_client)
|
|
131
171
|
return self._webhooks
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def email(self) -> Email:
|
|
175
|
+
"""Get email resource client for transactional emails"""
|
|
176
|
+
if self._email is None:
|
|
177
|
+
self._email = Email(self._http_client)
|
|
178
|
+
return self._email
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def loyalty_customers(self) -> LoyaltyCustomers:
|
|
182
|
+
"""Get loyalty customers resource client"""
|
|
183
|
+
self._init_loyalty_client()
|
|
184
|
+
if self._loyalty_customers is None:
|
|
185
|
+
self._loyalty_customers = LoyaltyCustomers(self._loyalty_http_client)
|
|
186
|
+
return self._loyalty_customers
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def loyalty_points(self) -> LoyaltyPoints:
|
|
190
|
+
"""Get loyalty points resource client"""
|
|
191
|
+
self._init_loyalty_client()
|
|
192
|
+
if self._loyalty_points is None:
|
|
193
|
+
self._loyalty_points = LoyaltyPoints(self._loyalty_http_client)
|
|
194
|
+
return self._loyalty_points
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def loyalty_vouchers(self) -> LoyaltyVouchers:
|
|
198
|
+
"""Get loyalty vouchers resource client"""
|
|
199
|
+
self._init_loyalty_client()
|
|
200
|
+
if self._loyalty_vouchers is None:
|
|
201
|
+
self._loyalty_vouchers = LoyaltyVouchers(self._loyalty_http_client)
|
|
202
|
+
return self._loyalty_vouchers
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def loyalty_wallet(self) -> LoyaltyWallet:
|
|
206
|
+
"""Get loyalty wallet resource client"""
|
|
207
|
+
self._init_loyalty_client()
|
|
208
|
+
if self._loyalty_wallet is None:
|
|
209
|
+
self._loyalty_wallet = LoyaltyWallet(self._loyalty_http_client)
|
|
210
|
+
return self._loyalty_wallet
|
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
KiriMel SDK Exception Classes
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from typing import Optional, Dict, Any
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
class ApiException(Exception):
|
|
7
9
|
"""Base API exception"""
|
|
8
10
|
|
|
9
|
-
def __init__(
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
message: str,
|
|
14
|
+
status_code: Optional[int] = None,
|
|
15
|
+
errors: Optional[Dict[str, Any]] = None,
|
|
16
|
+
):
|
|
10
17
|
self.message = message
|
|
11
18
|
self.status_code = status_code
|
|
12
19
|
self.errors = errors
|
|
@@ -27,7 +34,13 @@ class RateLimitException(ApiException):
|
|
|
27
34
|
|
|
28
35
|
error_type = "rate_limit_error"
|
|
29
36
|
|
|
30
|
-
def __init__(
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
message: str,
|
|
40
|
+
status_code: Optional[int] = None,
|
|
41
|
+
errors: Optional[Dict[str, Any]] = None,
|
|
42
|
+
retry_after: Optional[int] = None,
|
|
43
|
+
):
|
|
31
44
|
super().__init__(message, status_code, errors)
|
|
32
45
|
self.retry_after = retry_after
|
|
33
46
|
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
"""
|
|
2
2
|
HTTP Client for KiriMel API
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import os
|
|
5
6
|
import time
|
|
6
7
|
import logging
|
|
7
|
-
from typing import Optional, Dict, Any, List
|
|
8
|
-
import requests
|
|
8
|
+
from typing import Optional, Dict, Any, List, cast
|
|
9
|
+
import requests # type: ignore
|
|
9
10
|
|
|
10
|
-
from .exceptions import
|
|
11
|
+
from .exceptions import (
|
|
12
|
+
ApiException,
|
|
13
|
+
AuthenticationException,
|
|
14
|
+
RateLimitException,
|
|
15
|
+
ValidationException,
|
|
16
|
+
)
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
class HttpClient:
|
|
@@ -53,6 +59,7 @@ class HttpClient:
|
|
|
53
59
|
url = f"{self.base_url}/{path.lstrip('/')}"
|
|
54
60
|
if params:
|
|
55
61
|
import urllib.parse
|
|
62
|
+
|
|
56
63
|
query_string = urllib.parse.urlencode(params)
|
|
57
64
|
url = f"{url}?{query_string}"
|
|
58
65
|
return url
|
|
@@ -88,7 +95,7 @@ class HttpClient:
|
|
|
88
95
|
if response.status_code >= 400:
|
|
89
96
|
self._handle_error(response, url, attempt)
|
|
90
97
|
|
|
91
|
-
return response.json()
|
|
98
|
+
return cast(Dict[str, Any], response.json())
|
|
92
99
|
|
|
93
100
|
def _build_headers(self) -> Dict[str, str]:
|
|
94
101
|
"""Build request headers"""
|