Aller au contenu principal

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

PlanPrixFonctionnalites
Free0Qualite SD, 1 ecran, publicites
Basic4,99/moisQualite HD, 2 ecrans, sans publicite
Premium9,99/moisQualite FHD, 4 ecrans, telechargements
Ultimate14,99/moisQualite 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

MethodeEndpointDescriptionAuth
GET/subscriptions/plansLister les plans disponiblesNon
GET/subscriptions/currentObtenir l'abonnement actuelOui
POST/subscriptions/subscribeS'abonner a un planOui
POST/subscriptions/change-planChanger de planOui
POST/subscriptions/cancelAnnuler l'abonnementOui
POST/subscriptions/reactivateReactiver un abonnement annuleOui
GET/payments/historyHistorique des transactionsOui
POST/payments/refundDemander un remboursementOui

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);
});
});

Documentation associee