Module Payments
Module de gestion des abonnements et paiements pour la MyTelevision API.
Statut
Ce module est en cours de developpement.
Vue d'ensemble
Le module Payments gere :
- Plans d'abonnement (Free, Basic, Premium, Ultimate)
- Traitement des paiements (Stripe, PayPal, Apple Pay, Google Pay)
- Historique des transactions
- Generation de factures
- Remboursements
- Webhooks des fournisseurs de paiement
Architecture
Plans d'Abonnement
| Plan | Prix | Fonctionnalites |
|---|---|---|
| Free | 0 | Qualite SD, 1 ecran, publicites |
| Basic | 4,99/mois | Qualite HD, 2 ecrans, sans publicite |
| Premium | 9,99/mois | Qualite FHD, 4 ecrans, telechargements |
| Ultimate | 14,99/mois | Qualite 4K, 6 ecrans, toutes fonctionnalites |
Modeles de Donnees
model Subscription {
id String @id @default(uuid())
accountId String
account Account @relation(...)
planId String
plan SubscriptionPlan @relation(...)
status SubscriptionStatus
currentPeriodStart DateTime
currentPeriodEnd DateTime
cancelAtPeriodEnd Boolean @default(false)
canceledAt DateTime?
// Donnees fournisseur
stripeSubscriptionId String? @unique
paypalSubscriptionId String? @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model SubscriptionPlan {
id String @id @default(uuid())
name String
description String?
priceMonthly Decimal @db.Decimal(10, 2)
priceYearly Decimal? @db.Decimal(10, 2)
maxScreens Int @default(1)
maxQuality VideoQuality
hasAds Boolean @default(true)
hasDownloads Boolean @default(false)
isActive Boolean @default(true)
subscriptions Subscription[]
}
model PaymentTransaction {
id String @id @default(uuid())
accountId String
subscriptionId String?
amount Decimal @db.Decimal(10, 2)
currency String @default("EUR")
status PaymentStatus
paymentMethod PaymentMethod
providerTransactionId String?
failureReason String?
createdAt DateTime @default(now())
processedAt DateTime?
}
enum SubscriptionStatus {
ACTIVE
PAST_DUE
CANCELED
UNPAID
TRIALING
}
enum PaymentStatus {
PENDING
PROCESSING
SUCCEEDED
FAILED
REFUNDED
CANCELED
}
enum PaymentMethod {
CARD
PAYPAL
APPLE_PAY
GOOGLE_PAY
BANK_TRANSFER
}
Composants Principaux
SubscriptionService
@Injectable()
export class SubscriptionService {
async getPlans(): Promise<SubscriptionPlan[]>;
async subscribe(
accountId: string,
planId: string,
paymentMethod: PaymentMethod,
): Promise<Subscription>;
async changePlan(
subscriptionId: string,
newPlanId: string,
): Promise<Subscription>;
async cancel(
subscriptionId: string,
immediately?: boolean,
): Promise<Subscription>;
async reactivate(subscriptionId: string): Promise<Subscription>;
async getCurrentSubscription(accountId: string): Promise<Subscription | null>;
}
PaymentService
@Injectable()
export class PaymentService {
async processPayment(dto: ProcessPaymentDto): Promise<PaymentTransaction>;
async refund(
transactionId: string,
amount?: number,
): Promise<PaymentTransaction>;
async getTransactionHistory(
accountId: string,
pagination: PaginationDto,
): Promise<PaginatedResponse<PaymentTransaction>>;
async handleWebhook(
provider: string,
payload: any,
signature: string,
): Promise<void>;
}
Flux de Paiement
Gestion des Webhooks
@Controller('webhooks')
export class WebhookController {
@Post('stripe')
async handleStripeWebhook(
@Body() payload: any,
@Headers('stripe-signature') signature: string,
) {
const event = this.stripeService.verifyWebhook(payload, signature);
switch (event.type) {
case 'payment_intent.succeeded':
await this.paymentService.handlePaymentSuccess(event.data.object);
break;
case 'payment_intent.payment_failed':
await this.paymentService.handlePaymentFailure(event.data.object);
break;
case 'customer.subscription.updated':
await this.subscriptionService.handleSubscriptionUpdate(
event.data.object,
);
break;
case 'customer.subscription.deleted':
await this.subscriptionService.handleSubscriptionCanceled(
event.data.object,
);
break;
}
}
}
Endpoints API
| Methode | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /subscriptions/plans | Lister les plans disponibles | Non |
| GET | /subscriptions/current | Obtenir l'abonnement actuel | Oui |
| POST | /subscriptions/subscribe | S'abonner a un plan | Oui |
| POST | /subscriptions/change-plan | Changer de plan | Oui |
| POST | /subscriptions/cancel | Annuler l'abonnement | Oui |
| POST | /subscriptions/reactivate | Reactiver un abonnement annule | Oui |
| GET | /payments/history | Historique des transactions | Oui |
| POST | /payments/refund | Demander un remboursement | Oui |
Metriques Prometheus
mytelevision_subscriptions_total{plan, status}
mytelevision_subscription_created_total{plan_type}
mytelevision_subscription_canceled_total{reason}
mytelevision_payment_transactions_total{status, payment_method}
mytelevision_payment_amount_total{status}
mytelevision_mrr_total
mytelevision_churn_rate
Variables d'Environnement
# Stripe
STRIPE_SECRET_KEY=sk_live_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
STRIPE_PUBLISHABLE_KEY=pk_live_xxx
# PayPal
PAYPAL_CLIENT_ID=xxx
PAYPAL_CLIENT_SECRET=xxx
PAYPAL_WEBHOOK_ID=xxx
# Apple Pay
APPLE_PAY_MERCHANT_ID=merchant.com.mytv
APPLE_PAY_CERTIFICATE_PATH=/path/to/cert.pem
Tests
describe('SubscriptionService', () => {
it('should create subscription', async () => {
const subscription = await subscriptionService.subscribe(
'account-id',
'premium-plan-id',
PaymentMethod.CARD,
);
expect(subscription.status).toBe(SubscriptionStatus.ACTIVE);
});
it('should cancel at period end', async () => {
const subscription = await subscriptionService.cancel('sub-id', false);
expect(subscription.cancelAtPeriodEnd).toBe(true);
expect(subscription.status).toBe(SubscriptionStatus.ACTIVE);
});
});