feat(billing): migrate frontend templates to feature provider system
Replace hardcoded subscription fields (orders_limit, products_limit,
team_members_limit) across 5 frontend pages with dynamic feature
provider APIs. Add admin convenience endpoint for store subscription
lookup. Remove legacy stubs (StoreSubscription, FeatureCode, Feature,
TIER_LIMITS, FeatureInfo, FeatureUpgradeInfo) and schema aliases.
Pages updated:
- Admin subscriptions: dynamic feature overrides editor
- Admin tiers: correct feature catalog/limits API URLs
- Store billing: usage metrics from /store/billing/usage
- Merchant subscription detail: tier.feature_limits rendering
- Admin store detail: new GET /admin/subscriptions/store/{id} endpoint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,10 +9,10 @@ import pytest
|
||||
from app.handlers.stripe_webhook import StripeWebhookHandler
|
||||
from app.modules.billing.models import (
|
||||
BillingHistory,
|
||||
MerchantSubscription,
|
||||
StripeWebhookEvent,
|
||||
SubscriptionStatus,
|
||||
SubscriptionTier,
|
||||
VendorSubscription,
|
||||
)
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ class TestStripeWebhookHandlerCheckout:
|
||||
|
||||
@patch("app.handlers.stripe_webhook.stripe.Subscription.retrieve")
|
||||
def test_handle_checkout_completed_success(
|
||||
self, mock_stripe_retrieve, db, test_vendor, test_subscription, mock_checkout_event
|
||||
self, mock_stripe_retrieve, db, test_store, test_subscription, mock_checkout_event
|
||||
):
|
||||
"""Test successful checkout completion."""
|
||||
# Mock Stripe subscription retrieve
|
||||
@@ -71,7 +71,7 @@ class TestStripeWebhookHandlerCheckout:
|
||||
mock_stripe_sub.trial_end = None
|
||||
mock_stripe_retrieve.return_value = mock_stripe_sub
|
||||
|
||||
mock_checkout_event.data.object.metadata = {"vendor_id": str(test_vendor.id)}
|
||||
mock_checkout_event.data.object.metadata = {"store_id": str(test_store.id)}
|
||||
|
||||
result = self.handler.handle_event(db, mock_checkout_event)
|
||||
|
||||
@@ -80,15 +80,15 @@ class TestStripeWebhookHandlerCheckout:
|
||||
assert test_subscription.stripe_customer_id == "cus_test123"
|
||||
assert test_subscription.status == SubscriptionStatus.ACTIVE
|
||||
|
||||
def test_handle_checkout_completed_no_vendor_id(self, db, mock_checkout_event):
|
||||
"""Test checkout with missing vendor_id is skipped."""
|
||||
def test_handle_checkout_completed_no_store_id(self, db, mock_checkout_event):
|
||||
"""Test checkout with missing store_id is skipped."""
|
||||
mock_checkout_event.data.object.metadata = {}
|
||||
|
||||
result = self.handler.handle_event(db, mock_checkout_event)
|
||||
|
||||
assert result["status"] == "processed"
|
||||
assert result["result"]["action"] == "skipped"
|
||||
assert result["result"]["reason"] == "no vendor_id"
|
||||
assert result["result"]["reason"] == "no store_id"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@@ -101,7 +101,7 @@ class TestStripeWebhookHandlerSubscription:
|
||||
self.handler = StripeWebhookHandler()
|
||||
|
||||
def test_handle_subscription_updated_status_change(
|
||||
self, db, test_vendor, test_active_subscription, mock_subscription_updated_event
|
||||
self, db, test_store, test_active_subscription, mock_subscription_updated_event
|
||||
):
|
||||
"""Test subscription update changes status."""
|
||||
result = self.handler.handle_event(db, mock_subscription_updated_event)
|
||||
@@ -109,7 +109,7 @@ class TestStripeWebhookHandlerSubscription:
|
||||
assert result["status"] == "processed"
|
||||
|
||||
def test_handle_subscription_deleted(
|
||||
self, db, test_vendor, test_active_subscription, mock_subscription_deleted_event
|
||||
self, db, test_store, test_active_subscription, mock_subscription_deleted_event
|
||||
):
|
||||
"""Test subscription deletion."""
|
||||
result = self.handler.handle_event(db, mock_subscription_deleted_event)
|
||||
@@ -129,7 +129,7 @@ class TestStripeWebhookHandlerInvoice:
|
||||
self.handler = StripeWebhookHandler()
|
||||
|
||||
def test_handle_invoice_paid_creates_billing_record(
|
||||
self, db, test_vendor, test_active_subscription, mock_invoice_paid_event
|
||||
self, db, test_store, test_active_subscription, mock_invoice_paid_event
|
||||
):
|
||||
"""Test invoice.paid creates billing history record."""
|
||||
result = self.handler.handle_event(db, mock_invoice_paid_event)
|
||||
@@ -139,7 +139,7 @@ class TestStripeWebhookHandlerInvoice:
|
||||
# Check billing record created
|
||||
record = (
|
||||
db.query(BillingHistory)
|
||||
.filter(BillingHistory.vendor_id == test_vendor.id)
|
||||
.filter(BillingHistory.store_id == test_store.id)
|
||||
.first()
|
||||
)
|
||||
assert record is not None
|
||||
@@ -147,7 +147,7 @@ class TestStripeWebhookHandlerInvoice:
|
||||
assert record.total_cents == 4900
|
||||
|
||||
def test_handle_invoice_paid_resets_counters(
|
||||
self, db, test_vendor, test_active_subscription, mock_invoice_paid_event
|
||||
self, db, test_store, test_active_subscription, mock_invoice_paid_event
|
||||
):
|
||||
"""Test invoice.paid resets order counters."""
|
||||
test_active_subscription.orders_this_period = 50
|
||||
@@ -159,7 +159,7 @@ class TestStripeWebhookHandlerInvoice:
|
||||
assert test_active_subscription.orders_this_period == 0
|
||||
|
||||
def test_handle_payment_failed_marks_past_due(
|
||||
self, db, test_vendor, test_active_subscription, mock_payment_failed_event
|
||||
self, db, test_store, test_active_subscription, mock_payment_failed_event
|
||||
):
|
||||
"""Test payment failure marks subscription as past due."""
|
||||
result = self.handler.handle_event(db, mock_payment_failed_event)
|
||||
@@ -248,7 +248,7 @@ def test_subscription_tier(db):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_subscription(db, test_vendor):
|
||||
def test_subscription(db, test_store):
|
||||
"""Create a basic subscription for testing."""
|
||||
# Create tier first if not exists
|
||||
tier = db.query(SubscriptionTier).filter(SubscriptionTier.code == "essential").first()
|
||||
@@ -264,8 +264,8 @@ def test_subscription(db, test_vendor):
|
||||
db.add(tier)
|
||||
db.commit()
|
||||
|
||||
subscription = VendorSubscription(
|
||||
vendor_id=test_vendor.id,
|
||||
subscription = MerchantSubscription(
|
||||
store_id=test_store.id,
|
||||
tier="essential",
|
||||
status=SubscriptionStatus.TRIAL,
|
||||
period_start=datetime.now(timezone.utc),
|
||||
@@ -278,7 +278,7 @@ def test_subscription(db, test_vendor):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_active_subscription(db, test_vendor):
|
||||
def test_active_subscription(db, test_store):
|
||||
"""Create an active subscription with Stripe IDs."""
|
||||
# Create tier first if not exists
|
||||
tier = db.query(SubscriptionTier).filter(SubscriptionTier.code == "essential").first()
|
||||
@@ -294,8 +294,8 @@ def test_active_subscription(db, test_vendor):
|
||||
db.add(tier)
|
||||
db.commit()
|
||||
|
||||
subscription = VendorSubscription(
|
||||
vendor_id=test_vendor.id,
|
||||
subscription = MerchantSubscription(
|
||||
store_id=test_store.id,
|
||||
tier="essential",
|
||||
status=SubscriptionStatus.ACTIVE,
|
||||
stripe_customer_id="cus_test123",
|
||||
|
||||
Reference in New Issue
Block a user