00.99.48

parent 13b80367
......@@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.99.47] - 2026-04-21
## [0.99.48] - 2026-04-21
### Added
- **Unified Wallet System**
......@@ -29,7 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Clarified token access patterns (global vs user tokens)
### Changed
- Updated all version numbers to 0.99.47
- Updated all version numbers to 0.99.48
- Added wallet package to pyproject.toml
## [0.99.26] - 2026-04-16
......
......@@ -41,8 +41,8 @@ python -m build
```
This creates:
- `dist/aisbf-0.99.47.tar.gz` - Source distribution
- `dist/aisbf-0.99.47-py3-none-any.whl` - Wheel distribution
- `dist/aisbf-0.99.48.tar.gz` - Source distribution
- `dist/aisbf-0.99.48-py3-none-any.whl` - Wheel distribution
## Testing the Package
......@@ -50,7 +50,7 @@ This creates:
```bash
# Install from the built wheel
pip install dist/aisbf-0.99.47-py3-none-any.whl
pip install dist/aisbf-0.99.48-py3-none-any.whl
# Test the installation
aisbf status
......
......@@ -54,7 +54,7 @@ from .auth.qwen import QwenOAuth2
from .handlers import RequestHandler, RotationHandler, AutoselectHandler
from .utils import count_messages_tokens, split_messages_into_chunks, get_max_request_tokens_for_model
__version__ = "0.99.47"
__version__ = "0.99.48"
__all__ = [
# Config
"config",
......
......@@ -4334,8 +4334,18 @@ def DatabaseManager__run_config_migrations(self, cursor, auto_increment, timesta
logger.info("✅ Migration: Populated username, is_active and role for existing users")
else:
# MySQL migrations would go here
pass
# MySQL: check and add any missing columns
for col_name, col_def, _ in required_columns:
try:
cursor.execute("""
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = %s
""", (col_name,))
if not cursor.fetchone():
cursor.execute(f'ALTER TABLE users ADD COLUMN {col_name} {col_def}')
logger.info(f"✅ Migration: Added {col_name} column to users")
except Exception as col_e:
logger.warning(f"Migration check for users.{col_name}: {col_e}")
except Exception as e:
logger.warning(f"Migration check for users table columns: {e}")
......
......@@ -355,22 +355,36 @@ class PayPalPaymentHandler:
return {'status': 'error', 'message': str(e)}
async def _verify_webhook_signature(self, payload: dict, headers: dict) -> bool:
"""
Verify PayPal webhook signature
For production, implement proper signature verification:
https://developer.paypal.com/api/rest/webhooks/rest/#verify-webhook-signature
"""
# For now, basic verification - in production, verify the signature properly
if not self.webhook_secret:
logger.warning("PayPal webhook_secret not configured - skipping signature verification")
"""Verify PayPal webhook signature via PayPal's verify-webhook-signature API."""
webhook_id = self.webhook_secret # stored as 'webhook_secret' in admin settings
if not webhook_id:
logger.warning("PayPal webhook_id not configured - skipping signature verification")
return True
# TODO: Implement proper webhook signature verification
# This requires calling PayPal's verify-webhook-signature endpoint
# with the webhook_id, transmission_id, transmission_sig, etc.
return True
try:
access_token = await self.get_access_token()
body = {
"auth_algo": headers.get("paypal-auth-algo", ""),
"cert_url": headers.get("paypal-cert-url", ""),
"transmission_id": headers.get("paypal-transmission-id", ""),
"transmission_sig": headers.get("paypal-transmission-sig", ""),
"transmission_time": headers.get("paypal-transmission-time", ""),
"webhook_id": webhook_id,
"webhook_event": payload,
}
response = await self.http_client.post(
f"{self.base_url}/v1/notifications/verify-webhook-signature",
headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"},
json=body,
)
result = response.json()
verified = result.get("verification_status") == "SUCCESS"
if not verified:
logger.warning(f"PayPal webhook verification failed: {result}")
return verified
except Exception as e:
logger.error(f"PayPal webhook signature verification error: {e}")
return False
async def _handle_order_completed(self, resource: dict):
"""Handle completed order (Vault v3)"""
......
......@@ -1363,6 +1363,7 @@ async def startup_event():
payment_service = PaymentService(db_manager, payment_config)
await payment_service.initialize()
app.state.payment_service = payment_service
logger.info("Payment service started")
except Exception as e:
......@@ -3976,6 +3977,8 @@ async def dashboard_index(request: Request):
if request.session.get('role') == 'admin':
# Admin dashboard
db = DatabaseRegistry.get_config_database()
users_count = len(db.get_users())
return templates.TemplateResponse(
request=request,
name="dashboard/index.html",
......@@ -3986,7 +3989,8 @@ async def dashboard_index(request: Request):
"providers_count": len(config.providers) if config else 0,
"rotations_count": len(config.rotations) if config else 0,
"autoselect_count": len(config.autoselect) if config else 0,
"server_config": server_config or {}
"server_config": server_config or {},
"users_count": users_count,
}
)
else:
......
......@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "aisbf"
version = "0.99.47"
version = "0.99.48"
description = "AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations"
readme = "README.md"
license = "GPL-3.0-or-later"
......
......@@ -49,7 +49,7 @@ class InstallCommand(_install):
setup(
name="aisbf",
version="0.99.47",
version="0.99.48",
author="AISBF Contributors",
author_email="stefy@nexlab.net",
description="AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations",
......
......@@ -283,13 +283,27 @@ document.addEventListener('DOMContentLoaded', function () {
})
.then(r => r.json())
.then(data => {
if (data.type === 'stripe' && data.checkout_url) {
window.location.href = data.checkout_url;
} else if (data.type === 'paypal' && data.approval_url) {
window.location.href = data.approval_url;
} else {
this.disabled = false;
this.innerHTML = orig;
let url = null;
if (data.type === 'stripe' && data.checkout_url) url = data.checkout_url;
else if (data.type === 'paypal' && data.approval_url) url = data.approval_url;
if (url) {
const popup = window.open(url, 'payment_popup', 'width=600,height=700,scrollbars=yes,resizable=yes');
if (!popup) {
// Blocked by browser — open in new tab
window.open(url, '_blank');
return;
}
// Poll for popup close then reload wallet page
const timer = setInterval(() => {
if (popup.closed) {
clearInterval(timer);
window.location.reload();
}
}, 800);
} else {
showAlert(data.error || 'Failed to initiate checkout.', 'Error', '❌', 'danger');
}
})
......@@ -435,7 +449,8 @@ function openCryptoModal(name) {
document.getElementById('cryptoModal').classList.add('active');
// Fetch a per-user deposit address from the server
const amount = getAmount() || 15;
const customVal = parseFloat(document.getElementById('custom-amount').value);
const amount = customVal || selectedAmount || 15;
fetch('/dashboard/wallet/topup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment