Design Spec: Customer to User Subscription (#956)
Date: 2025-04-13
Status: Approved
Type: Feature (Backend + Frontend)
1. Overview
Goal: Enable recurring subscriptions from Customers to Users (sellers) through Stripe Connect using Destination Charges.
Architecture Decisions:
- Build Backend and Frontend together in one feature branch
- “Subscription Content” as new service type (Type::SUBSCRIPTION = 3) in existing service creation flow
- Stripe Connect with Destination Charges (Platform owns subscription)
- Frontend follows メニューフォーム pattern (existing CalendarService/EventSeminarService structure)
2. Backend Architecture
2.1 New Trait: StripeConnectSubscription.php
Location: yoyacoo_be/common/src/Base/Traits/StripeConnectSubscription.php
Extends existing StripeConnectPayment patterns for subscription handling.
Key Method:
public function createConnectSubscription($customerId, $priceId, $destinationAccountId, $feePercent) {
return Subscription::create([
'customer' => $customerId,
'items' => [['price' => $priceId]],
'transfer_data' => [
'destination' => $destinationAccountId,
],
'application_fee_percent' => $feePercent,
'payment_behavior' => 'default_incomplete',
'expand' => ['latest_invoice.payment_intent'],
], $this->stripeConnectOptions());
}
2.2 Database Schema
New Table: order_subscriptions
| Column | Type | Description |
|---|---|---|
id |
BigInt | Primary Key |
order_id |
BigInt | Foreign key to original order |
user_id |
BigInt | User (seller) receiving funds |
customer_id |
BigInt | Customer (buyer) paying |
stripe_subscription_id |
String | Stripe Subscription ID (sub_…) |
stripe_price_id |
String | Stripe Price ID (price_…) |
status |
String | active, trialing, past_due, canceled |
current_period_end |
DateTime | Next payment due date |
created_at |
Timestamp | |
updated_at |
Timestamp |
Migration Location: yoyacoo_be/common/database/migrations/
2.3 Webhook Handlers
File: yoyacoo_be/admin/app/Domains/Subscription/Controllers/ConnectWebhookController.php
Extend existing ConnectWebhookController to handle:
| Event | Handler | Action |
|---|---|---|
invoice.paid |
handleInvoicePaid() |
Update current_period_end, log recurring payment |
customer.subscription.deleted |
handleSubscriptionDeleted() |
Mark subscription as canceled |
invoice.payment_failed |
handleInvoicePaymentFailed() |
Notify customer/user |
2.4 Service Integration
Type::SUBSCRIPTION = 3already exists inServicemodel- Order flow extended to support subscription type
OrderServiceupdated to callcreateConnectSubscriptionfor subscription orders
3. Frontend Architecture (Supplier App)
3.1 Component Structure
Location: yoyacoo_fe/supplier/src/components/organisms/services/content/services/SubscriptionService/
SubscriptionService/
└── SubscriptionForm.tsx
Follows existing メニューフォーム pattern (CalendarService/EventSeminarService structure).
3.2 Form Structure
5 collapsible sections matching the wireframe:
Basic Settings
- Service Name (required, max 100 chars)
- Description (optional, max 500 chars)
- Availability Status (Continuous/Scheduled/Stopped)
Subscription Settings
- Billing Start Date (date picker)
- Payment Cycle (Monthly from App Date / Monthly from 1st of Month)
- Payment Method (Credit Card Only - read-only)
Menu & Options
- Menu Name (required, max 50 chars)
- Price (JPY, required)
- Capacity Limit (optional)
- Contract Period (1mo/3mo/6mo/1yr/Ongoing)
- Free Trial Period (None/7days/14days/30days)
- Menu Description (optional)
- Selection Method (Direct/Application Form)
Post-Purchase
- Download URL (optional)
- Video URL (optional)
- Terms of Use (Display/External Link)
Contact Information
- Name (required)
- Email (required)
- Phone (optional)
3.3 State Management
Jotai Atoms extended for subscription:
serviceFormAtom- includes subscription-specific fields- Follows existing atom/operations/selectors pattern
3.4 API Integration
Service API methods:
serviceApi.postSubscription() // POST /api/subscriptions
serviceApi.getSubscription() // GET /api/subscriptions/{id}
serviceApi.deleteSubscription() // DELETE /api/subscriptions/{id}
All calls use wrap() utility for consistent error handling.
3.5 Validation
Yup schemas following existing pattern:
subscriptionBasicSettingsSchemasubscriptionMenuSettingsSchemasubscriptionPostPurchaseSchemasubscriptionContactSchema
4. Data Flow
1. User creates Subscription Content service
↓
2. Customer views and subscribes to service
↓
3. Order created with subscription type
↓
4. Backend calls Stripe API:
Stripe::subscriptions()->create([
'customer' => $customerId,
'items' => [['price' => $priceId]],
'transfer_data' => ['destination' => $userStripeConnectId],
'application_fee_percent' => $feePercent,
])
↓
5. Subscription saved to order_subscriptions table
↓
6. Webhooks update status on invoice.paid, subscription.deleted
5. API Endpoints
Backend (Laravel)
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/subscriptions |
Create subscription |
| GET | /api/subscriptions/{id} |
Get subscription status |
| DELETE | /api/subscriptions/{id} |
Cancel subscription |
Frontend (Service API)
// In supplier/src/apis/service/serviceApi.ts
const serviceApi = {
postSubscription: wrap(ServiceApi, ServiceApi.prototype.postSubscription),
getSubscription: wrap(ServiceApi, ServiceApi.prototype.getSubscription),
deleteSubscription: wrap(ServiceApi, ServiceApi.prototype.deleteSubscription),
};
6. Error Handling
Frontend
- Form validation with Yup (on blur + on submit)
- API errors handled via
ApiValidationErrorandApiResponseError - Toast notifications for success/error states
Backend
- Stripe webhook signature verification
- Idempotent webhook handlers
- Logging for all payment events
7. Testing
Backend
- Unit tests for
calcFeeAmountlogic - Integration tests with Stripe CLI:
stripe trigger invoice.paid
Frontend
- Playwright E2E tests for subscription flow
- Form validation tests
8. File Changes Summary
Backend (yoyacoo_be/common)
- New:
src/Base/Traits/StripeConnectSubscription.php - New: Migration for
order_subscriptionstable - Modified:
OrderServiceto handle subscription type - Modified:
ConnectWebhookControllerfor subscription events
Frontend (yoyacoo_fe/supplier)
- New:
src/components/organisms/services/content/services/SubscriptionService/SubscriptionForm.tsx - New: Validation schemas for subscription
- Modified:
serviceApi.tswith subscription endpoints - Modified:
serviceFormAtomfor subscription state
9. Out of Scope (Phase 1)
- User dashboard to view active subscribers
- Customer subscription management UI (cancel/pause)
- Proration handling for upgrades/downgrades
- Direct Charges option (future enhancement)