docs: add consolidated dev URL reference and migrate /shop to /storefront
Some checks failed
Some checks failed
- Add Development URL Quick Reference section to url-routing overview with all login URLs, entry points, and full examples - Replace /shop/ path segments with /storefront/ across 50 docs files - Update file references: shop_pages.py → storefront_pages.py, templates/shop/ → templates/storefront/, api/v1/shop/ → api/v1/storefront/ - Preserve domain references (orion.shop) and /store/ staff dashboard paths - Archive docs left unchanged (historical) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -215,7 +215,7 @@ COPY . .
|
||||
# Build Tailwind CSS
|
||||
RUN tailwindcss -i ./static/admin/css/tailwind.css -o ./static/admin/css/tailwind.output.css --minify \
|
||||
&& tailwindcss -i ./static/store/css/tailwind.css -o ./static/store/css/tailwind.output.css --minify \
|
||||
&& tailwindcss -i ./static/shop/css/tailwind.css -o ./static/shop/css/tailwind.output.css --minify \
|
||||
&& tailwindcss -i ./static/storefront/css/tailwind.css -o ./static/storefront/css/tailwind.output.css --minify \
|
||||
&& tailwindcss -i ./static/public/css/tailwind.css -o ./static/public/css/tailwind.output.css --minify
|
||||
|
||||
# Create non-root user
|
||||
|
||||
@@ -27,22 +27,22 @@ class StorePaymentConfig(Base, TimestampMixin):
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
store_id = Column(Integer, ForeignKey("stores.id"), nullable=False, unique=True)
|
||||
|
||||
|
||||
# Stripe Connect configuration
|
||||
stripe_account_id = Column(String(255)) # Stripe Connect account ID
|
||||
stripe_account_status = Column(String(50)) # pending, active, restricted, inactive
|
||||
stripe_onboarding_url = Column(Text) # Onboarding link for store
|
||||
stripe_dashboard_url = Column(Text) # Store's Stripe dashboard
|
||||
|
||||
|
||||
# Payment settings
|
||||
accepts_payments = Column(Boolean, default=False)
|
||||
currency = Column(String(3), default="EUR")
|
||||
platform_fee_percentage = Column(Numeric(5, 2), default=2.5) # Platform commission
|
||||
|
||||
|
||||
# Payout settings
|
||||
payout_schedule = Column(String(20), default="weekly") # daily, weekly, monthly
|
||||
minimum_payout = Column(Numeric(10, 2), default=20.00)
|
||||
|
||||
|
||||
# Relationships
|
||||
store = relationship("Store", back_populates="payment_config")
|
||||
|
||||
@@ -58,31 +58,31 @@ class Payment(Base, TimestampMixin):
|
||||
store_id = Column(Integer, ForeignKey("stores.id"), nullable=False)
|
||||
order_id = Column(Integer, ForeignKey("orders.id"), nullable=False)
|
||||
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False)
|
||||
|
||||
|
||||
# Stripe payment details
|
||||
stripe_payment_intent_id = Column(String(255), unique=True, index=True)
|
||||
stripe_charge_id = Column(String(255), index=True)
|
||||
stripe_transfer_id = Column(String(255)) # Transfer to store account
|
||||
|
||||
|
||||
# Payment amounts (in cents to avoid floating point issues)
|
||||
amount_total = Column(Integer, nullable=False) # Total customer payment
|
||||
amount_store = Column(Integer, nullable=False) # Amount to store
|
||||
amount_platform_fee = Column(Integer, nullable=False) # Platform commission
|
||||
currency = Column(String(3), default="EUR")
|
||||
|
||||
|
||||
# Payment status
|
||||
status = Column(String(50), nullable=False) # pending, succeeded, failed, refunded
|
||||
payment_method = Column(String(50)) # card, bank_transfer, etc.
|
||||
|
||||
|
||||
# Metadata
|
||||
stripe_metadata = Column(Text) # JSON string of Stripe metadata
|
||||
failure_reason = Column(Text)
|
||||
refund_reason = Column(Text)
|
||||
|
||||
|
||||
# Timestamps
|
||||
paid_at = Column(DateTime)
|
||||
refunded_at = Column(DateTime)
|
||||
|
||||
|
||||
# Relationships
|
||||
store = relationship("Store")
|
||||
order = relationship("Order", back_populates="payment")
|
||||
@@ -109,21 +109,21 @@ class PaymentMethod(Base, TimestampMixin):
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
store_id = Column(Integer, ForeignKey("stores.id"), nullable=False)
|
||||
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False)
|
||||
|
||||
|
||||
# Stripe payment method details
|
||||
stripe_payment_method_id = Column(String(255), nullable=False, index=True)
|
||||
payment_method_type = Column(String(50), nullable=False) # card, sepa_debit, etc.
|
||||
|
||||
|
||||
# Card details (if applicable)
|
||||
card_brand = Column(String(50)) # visa, mastercard, etc.
|
||||
card_last4 = Column(String(4))
|
||||
card_exp_month = Column(Integer)
|
||||
card_exp_year = Column(Integer)
|
||||
|
||||
|
||||
# Settings
|
||||
is_default = Column(Boolean, default=False)
|
||||
is_active = Column(Boolean, default=True)
|
||||
|
||||
|
||||
# Relationships
|
||||
store = relationship("Store")
|
||||
customer = relationship("Customer")
|
||||
@@ -138,15 +138,15 @@ class PaymentMethod(Base, TimestampMixin):
|
||||
# Update models/database/order.py
|
||||
class Order(Base, TimestampMixin):
|
||||
# ... existing fields ...
|
||||
|
||||
|
||||
# Payment integration
|
||||
payment_status = Column(String(50), default="pending") # pending, paid, failed, refunded
|
||||
payment_intent_id = Column(String(255)) # Stripe PaymentIntent ID
|
||||
total_amount_cents = Column(Integer, nullable=False) # Amount in cents
|
||||
|
||||
|
||||
# Relationships
|
||||
payment = relationship("Payment", back_populates="order", uselist=False)
|
||||
|
||||
|
||||
@property
|
||||
def total_amount_euros(self):
|
||||
"""Convert cents to euros for display."""
|
||||
@@ -185,25 +185,25 @@ class PaymentService:
|
||||
self.db = db
|
||||
|
||||
def create_payment_intent(
|
||||
self,
|
||||
store_id: int,
|
||||
order_id: int,
|
||||
self,
|
||||
store_id: int,
|
||||
order_id: int,
|
||||
amount_euros: Decimal,
|
||||
customer_email: str,
|
||||
metadata: Optional[Dict] = None
|
||||
) -> Dict:
|
||||
"""Create Stripe PaymentIntent for store order."""
|
||||
|
||||
|
||||
# Get store payment configuration
|
||||
payment_config = self.get_store_payment_config(store_id)
|
||||
if not payment_config.accepts_payments:
|
||||
raise PaymentNotConfiguredException(f"Store {store_id} not configured for payments")
|
||||
|
||||
|
||||
# Calculate amounts
|
||||
amount_cents = int(amount_euros * 100)
|
||||
platform_fee_cents = int(amount_cents * (payment_config.platform_fee_percentage / 100))
|
||||
store_amount_cents = amount_cents - platform_fee_cents
|
||||
|
||||
|
||||
try:
|
||||
# Create PaymentIntent with Stripe Connect
|
||||
payment_intent = stripe.PaymentIntent.create(
|
||||
@@ -222,7 +222,7 @@ class PaymentService:
|
||||
receipt_email=customer_email,
|
||||
description=f"Order payment for store {store_id}"
|
||||
)
|
||||
|
||||
|
||||
# Create payment record
|
||||
payment = Payment(
|
||||
store_id=store_id,
|
||||
@@ -236,17 +236,17 @@ class PaymentService:
|
||||
status='pending',
|
||||
stripe_metadata=json.dumps(payment_intent.metadata)
|
||||
)
|
||||
|
||||
|
||||
self.db.add(payment)
|
||||
|
||||
|
||||
# Update order
|
||||
order = self.db.query(Order).filter(Order.id == order_id).first()
|
||||
if order:
|
||||
order.payment_intent_id = payment_intent.id
|
||||
order.payment_status = 'pending'
|
||||
|
||||
|
||||
self.db.commit()
|
||||
|
||||
|
||||
return {
|
||||
'payment_intent_id': payment_intent.id,
|
||||
'client_secret': payment_intent.client_secret,
|
||||
@@ -255,58 +255,58 @@ class PaymentService:
|
||||
'platform_fee': platform_fee_cents / 100,
|
||||
'currency': payment_config.currency
|
||||
}
|
||||
|
||||
|
||||
except stripe.error.StripeError as e:
|
||||
logger.error(f"Stripe error creating PaymentIntent: {e}")
|
||||
raise PaymentProcessingException(f"Payment processing failed: {str(e)}")
|
||||
|
||||
def confirm_payment(self, payment_intent_id: str) -> Payment:
|
||||
"""Confirm payment and update records."""
|
||||
|
||||
|
||||
try:
|
||||
# Retrieve PaymentIntent from Stripe
|
||||
payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id)
|
||||
|
||||
|
||||
# Find payment record
|
||||
payment = self.db.query(Payment).filter(
|
||||
Payment.stripe_payment_intent_id == payment_intent_id
|
||||
).first()
|
||||
|
||||
|
||||
if not payment:
|
||||
raise PaymentNotFoundException(f"Payment not found for intent {payment_intent_id}")
|
||||
|
||||
|
||||
# Update payment status based on Stripe status
|
||||
if payment_intent.status == 'succeeded':
|
||||
payment.status = 'succeeded'
|
||||
payment.stripe_charge_id = payment_intent.charges.data[0].id if payment_intent.charges.data else None
|
||||
payment.paid_at = datetime.utcnow()
|
||||
|
||||
|
||||
# Update order status
|
||||
order = self.db.query(Order).filter(Order.id == payment.order_id).first()
|
||||
if order:
|
||||
order.payment_status = 'paid'
|
||||
order.status = 'processing' # Move order to processing
|
||||
|
||||
|
||||
elif payment_intent.status == 'payment_failed':
|
||||
payment.status = 'failed'
|
||||
payment.failure_reason = payment_intent.last_payment_error.message if payment_intent.last_payment_error else "Unknown error"
|
||||
|
||||
|
||||
# Update order status
|
||||
order = self.db.query(Order).filter(Order.id == payment.order_id).first()
|
||||
if order:
|
||||
order.payment_status = 'failed'
|
||||
|
||||
|
||||
self.db.commit()
|
||||
|
||||
|
||||
return payment
|
||||
|
||||
|
||||
except stripe.error.StripeError as e:
|
||||
logger.error(f"Stripe error confirming payment: {e}")
|
||||
raise PaymentProcessingException(f"Payment confirmation failed: {str(e)}")
|
||||
|
||||
def create_store_stripe_account(self, store_id: int, store_data: Dict) -> str:
|
||||
"""Create Stripe Connect account for store."""
|
||||
|
||||
|
||||
try:
|
||||
# Create Stripe Connect Express account
|
||||
account = stripe.Account.create(
|
||||
@@ -333,27 +333,27 @@ class PaymentService:
|
||||
'platform': 'multi_tenant_ecommerce'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# Update or create payment configuration
|
||||
payment_config = self.get_or_create_store_payment_config(store_id)
|
||||
payment_config.stripe_account_id = account.id
|
||||
payment_config.stripe_account_status = account.charges_enabled and account.payouts_enabled and 'active' or 'pending'
|
||||
|
||||
|
||||
self.db.commit()
|
||||
|
||||
|
||||
return account.id
|
||||
|
||||
|
||||
except stripe.error.StripeError as e:
|
||||
logger.error(f"Stripe error creating account: {e}")
|
||||
raise PaymentConfigurationException(f"Failed to create payment account: {str(e)}")
|
||||
|
||||
def create_onboarding_link(self, store_id: int) -> str:
|
||||
"""Create Stripe onboarding link for store."""
|
||||
|
||||
|
||||
payment_config = self.get_store_payment_config(store_id)
|
||||
if not payment_config.stripe_account_id:
|
||||
raise PaymentNotConfiguredException("Store does not have Stripe account")
|
||||
|
||||
|
||||
try:
|
||||
account_link = stripe.AccountLink.create(
|
||||
account=payment_config.stripe_account_id,
|
||||
@@ -361,13 +361,13 @@ class PaymentService:
|
||||
return_url=f"{settings.frontend_url}/store/admin/payments/success",
|
||||
type='account_onboarding',
|
||||
)
|
||||
|
||||
|
||||
# Update onboarding URL
|
||||
payment_config.stripe_onboarding_url = account_link.url
|
||||
self.db.commit()
|
||||
|
||||
|
||||
return account_link.url
|
||||
|
||||
|
||||
except stripe.error.StripeError as e:
|
||||
logger.error(f"Stripe error creating onboarding link: {e}")
|
||||
raise PaymentConfigurationException(f"Failed to create onboarding link: {str(e)}")
|
||||
@@ -377,28 +377,28 @@ class PaymentService:
|
||||
config = self.db.query(StorePaymentConfig).filter(
|
||||
StorePaymentConfig.store_id == store_id
|
||||
).first()
|
||||
|
||||
|
||||
if not config:
|
||||
raise PaymentNotConfiguredException(f"No payment configuration for store {store_id}")
|
||||
|
||||
|
||||
return config
|
||||
|
||||
def webhook_handler(self, event_type: str, event_data: Dict) -> None:
|
||||
"""Handle Stripe webhook events."""
|
||||
|
||||
|
||||
if event_type == 'payment_intent.succeeded':
|
||||
payment_intent_id = event_data['object']['id']
|
||||
self.confirm_payment(payment_intent_id)
|
||||
|
||||
|
||||
elif event_type == 'payment_intent.payment_failed':
|
||||
payment_intent_id = event_data['object']['id']
|
||||
self.confirm_payment(payment_intent_id)
|
||||
|
||||
|
||||
elif event_type == 'account.updated':
|
||||
# Update store account status
|
||||
account_id = event_data['object']['id']
|
||||
self.update_store_account_status(account_id, event_data['object'])
|
||||
|
||||
|
||||
# Add more webhook handlers as needed
|
||||
```
|
||||
|
||||
@@ -426,7 +426,7 @@ async def get_payment_config(
|
||||
):
|
||||
"""Get store payment configuration."""
|
||||
payment_service = PaymentService(db)
|
||||
|
||||
|
||||
try:
|
||||
config = payment_service.get_store_payment_config(store.id)
|
||||
return {
|
||||
@@ -454,17 +454,17 @@ async def setup_payments(
|
||||
):
|
||||
"""Set up Stripe payments for store."""
|
||||
payment_service = PaymentService(db)
|
||||
|
||||
|
||||
store_data = {
|
||||
"business_name": store.name,
|
||||
"business_email": store.business_email,
|
||||
"business_phone": store.business_phone,
|
||||
**setup_data
|
||||
}
|
||||
|
||||
|
||||
account_id = payment_service.create_store_stripe_account(store.id, store_data)
|
||||
onboarding_url = payment_service.create_onboarding_link(store.id)
|
||||
|
||||
|
||||
return {
|
||||
"stripe_account_id": account_id,
|
||||
"onboarding_url": onboarding_url,
|
||||
@@ -481,7 +481,7 @@ async def create_payment_intent(
|
||||
):
|
||||
"""Create payment intent for customer order."""
|
||||
payment_service = PaymentService(db)
|
||||
|
||||
|
||||
payment_intent = payment_service.create_payment_intent(
|
||||
store_id=store_id,
|
||||
order_id=payment_data['order_id'],
|
||||
@@ -489,7 +489,7 @@ async def create_payment_intent(
|
||||
customer_email=payment_data['customer_email'],
|
||||
metadata=payment_data.get('metadata', {})
|
||||
)
|
||||
|
||||
|
||||
return payment_intent
|
||||
|
||||
|
||||
@@ -500,10 +500,10 @@ async def stripe_webhook(
|
||||
):
|
||||
"""Handle Stripe webhook events."""
|
||||
import stripe
|
||||
|
||||
|
||||
payload = await request.body()
|
||||
sig_header = request.headers.get('stripe-signature')
|
||||
|
||||
|
||||
try:
|
||||
event = stripe.Webhook.construct_event(
|
||||
payload, sig_header, settings.stripe_webhook_secret
|
||||
@@ -512,10 +512,10 @@ async def stripe_webhook(
|
||||
raise HTTPException(status_code=400, detail="Invalid payload")
|
||||
except stripe.error.SignatureVerificationError:
|
||||
raise HTTPException(status_code=400, detail="Invalid signature")
|
||||
|
||||
|
||||
payment_service = PaymentService(db)
|
||||
payment_service.webhook_handler(event['type'], event['data'])
|
||||
|
||||
|
||||
return {"status": "success"}
|
||||
```
|
||||
|
||||
@@ -524,7 +524,7 @@ async def stripe_webhook(
|
||||
### Checkout Process
|
||||
|
||||
```javascript
|
||||
// frontend/js/shop/checkout.js
|
||||
// frontend/js/storefront/checkout.js
|
||||
class CheckoutManager {
|
||||
constructor(storeId) {
|
||||
this.storeId = storeId;
|
||||
@@ -562,7 +562,7 @@ class CheckoutManager {
|
||||
const { error } = await this.stripe.confirmPayment({
|
||||
elements: this.elements,
|
||||
confirmParams: {
|
||||
return_url: `${window.location.origin}/shop/order-confirmation`,
|
||||
return_url: `${window.location.origin}/storefront/order-confirmation`,
|
||||
receipt_email: orderData.customerEmail
|
||||
}
|
||||
});
|
||||
@@ -614,4 +614,4 @@ Webhook updates account status to 'active'
|
||||
Store can now accept payments
|
||||
```
|
||||
|
||||
This integration provides secure, compliant payment processing while maintaining store isolation and enabling proper revenue distribution between stores and the platform.
|
||||
This integration provides secure, compliant payment processing while maintaining store isolation and enabling proper revenue distribution between stores and the platform.
|
||||
|
||||
Reference in New Issue
Block a user