fix subscription from orange
This commit is contained in:
parent
6ea3ece796
commit
0af15e26fc
@ -24,19 +24,13 @@ export interface RefundResponse{
|
||||
|
||||
}
|
||||
|
||||
export interface SubscriptionParams{
|
||||
|
||||
}
|
||||
export interface SubscriptionResponse{
|
||||
|
||||
}
|
||||
export interface IOperatorAdapter {
|
||||
initializeAuth(params: AuthInitParams): Promise<AuthInitResponse>;
|
||||
validateAuth(params: AuthValidateParams): Promise<AuthValidateResponse>;
|
||||
charge(params: ChargeParams): Promise<ChargeResponse>;
|
||||
refund(params: RefundParams): Promise<RefundResponse>;
|
||||
sendSms(params: SmsParams): Promise<SmsResponse>;
|
||||
createSubscription?(
|
||||
createSubscription(
|
||||
params: SubscriptionParams,
|
||||
): Promise<SubscriptionResponse>;
|
||||
cancelSubscription?(subscriptionId: string): Promise<void>;
|
||||
@ -73,3 +67,24 @@ export interface ChargeResponse {
|
||||
resourceURL: string;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
|
||||
export interface SubscriptionParams {
|
||||
merchantId: any;
|
||||
periodicity: any;
|
||||
userToken: string;
|
||||
userAlias: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
description: string;
|
||||
productId: string;
|
||||
}
|
||||
|
||||
export interface SubscriptionResponse {
|
||||
subscriptionId: string;
|
||||
status: 'SUCCESS' | 'FAILED' | 'PENDING';
|
||||
operatorReference: string;
|
||||
amount: number;
|
||||
resourceURL: string;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@ import {
|
||||
AuthInitResponse,
|
||||
ChargeParams,
|
||||
ChargeResponse,
|
||||
SubscriptionParams,
|
||||
SubscriptionResponse,
|
||||
} from './operator.adapter.interface';
|
||||
import { OrangeTransformer } from '../transformers/orange.transformer';
|
||||
import {
|
||||
@ -161,6 +163,117 @@ export class OrangeAdapter implements IOperatorAdapter {
|
||||
};
|
||||
}
|
||||
|
||||
async createSubscription(
|
||||
params: SubscriptionParams,
|
||||
): Promise<SubscriptionResponse> {
|
||||
this.logger.debug(
|
||||
`[orange adapter createSubscription]: ${JSON.stringify(params, null, 2)}`,
|
||||
);
|
||||
const hubRequest = {
|
||||
note: {
|
||||
"text": "partner data"
|
||||
},
|
||||
relatedPublicKey: {
|
||||
"id": "PDKSUB-200-KzIxNnh4eHh4eC1TRU4tMTc1ODY1MjI5MjMwMg==",
|
||||
"name": "ISE2"
|
||||
},
|
||||
relatedParty: [
|
||||
{
|
||||
"id": "{{serviceId)}}",
|
||||
"name": " DIGITALAFRIQUETELECOM ",
|
||||
"role": "partner"
|
||||
},
|
||||
{
|
||||
"id": `${params.merchantId}`,
|
||||
"name": "{{onBehalfOf)}}",
|
||||
"role": "retailer"
|
||||
}
|
||||
],
|
||||
orderItem: {
|
||||
"action": "add",
|
||||
"state": "Completed",
|
||||
"product": {
|
||||
"id": `${params.productId}}`,
|
||||
"href": "antifraudId",
|
||||
"productCharacteristic": [
|
||||
{
|
||||
"name": "taxAmount",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": "amount",
|
||||
"value": `${params.amount}`
|
||||
},
|
||||
{
|
||||
"name": "currency",//ISO 4217 see Annexes
|
||||
"value": `${params.currency}`
|
||||
},
|
||||
{
|
||||
"name": "periodicity",//86400 (daily), 604800 (weekly), 0 (monthly) only those values will be accepted
|
||||
"value": `${params.periodicity}`
|
||||
},
|
||||
{
|
||||
"name": "startDate",//YYYY-MM-DD
|
||||
"value": "2021-08-16"
|
||||
},
|
||||
{
|
||||
"name": "country",
|
||||
"value": "COD"
|
||||
},
|
||||
{
|
||||
"name": "language",//ISO 639-1 see Annexes
|
||||
"value": "fr"
|
||||
},
|
||||
{
|
||||
"name": "mode",
|
||||
"value": "hybrid"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
const token = await this.getAccessToken();
|
||||
|
||||
this.logger.debug(
|
||||
`[requesting subscription to]: ${this.config.baseUrl}/payment/mea/v1/digipay_sub/productOrder`,
|
||||
);
|
||||
|
||||
this.logger.debug(`[requesting token]: ${token}`);
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.post(
|
||||
`${this.config.baseUrl}/payment/mea/v1/digipay_sub/productOrder`,
|
||||
hubRequest,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'X-Orange-ISE2': params.userToken,
|
||||
'X-Orange-MCO': 'orange',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
this.logger.debug(
|
||||
`[response from orange subscription]: ${JSON.stringify(response.data, null, 2)}`,
|
||||
);
|
||||
|
||||
return this.transformer.transformSubscriptionResponse(response.data);
|
||||
}
|
||||
|
||||
async cancelSubscription(subscriptionId: string): Promise<void> {
|
||||
this.logger.debug(
|
||||
`[orange adapter cancelSubscription]: ${subscriptionId}`,
|
||||
);
|
||||
|
||||
// Implémentation de l'annulation d'abonnement
|
||||
// Cela dépend de l'API Orange - à adapter selon la documentation
|
||||
throw new Error('Cancel subscription not implemented for Orange');
|
||||
}
|
||||
|
||||
async charge(params: ChargeParams): Promise<ChargeResponse> {
|
||||
this.logger.debug(
|
||||
`[orange adapter charge ]: ${JSON.stringify(params, null, 2)}`,
|
||||
@ -221,6 +334,8 @@ export class OrangeAdapter implements IOperatorAdapter {
|
||||
throw new Error('Refund not implemented for Orange');
|
||||
}
|
||||
|
||||
|
||||
|
||||
async sendSms(params: any): Promise<any> {
|
||||
const smsRequest = {
|
||||
outboundSMSMessageRequest: {
|
||||
|
||||
@ -20,8 +20,24 @@ export class OrangeTransformer {
|
||||
};
|
||||
}
|
||||
|
||||
private mapStatus(orangeStatus: string): string {
|
||||
|
||||
transformSubscriptionResponse(orangeResponse: any): any {
|
||||
return {
|
||||
subscriptionId: orangeResponse.id,
|
||||
status: this.mapStatus(
|
||||
orangeResponse.state,
|
||||
),
|
||||
operatorReference: orangeResponse.amountTransaction?.serverReferenceCode,
|
||||
amount: parseFloat(
|
||||
orangeResponse.amountTransaction?.paymentAmount?.totalAmountCharged,
|
||||
),
|
||||
createdAt: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
private mapStatus(orangeStatus: string): string {//todo make exaustifs
|
||||
const statusMap = {
|
||||
Completed: 'SUCCESS',
|
||||
Charged: 'SUCCESS',
|
||||
Failed: 'FAILED',
|
||||
Pending: 'PENDING',
|
||||
|
||||
@ -9,6 +9,14 @@ import { PaymentType, TransactionStatus } from 'generated/prisma';
|
||||
@Injectable()
|
||||
export class PaymentsService {
|
||||
private readonly logger = new Logger(PaymentsService.name);
|
||||
|
||||
|
||||
constructor(
|
||||
private readonly operatorsService: OperatorsService,
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
handleWebhook(arg0: {
|
||||
partnerId: any;
|
||||
event: any;
|
||||
@ -76,11 +84,7 @@ export class PaymentsService {
|
||||
processPayment(paymentId: any): any {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
constructor(
|
||||
private readonly operatorsService: OperatorsService,
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
|
||||
async createCharge(chargeDto: ChargeDto) {
|
||||
/* Récupérer les informations de l'utilisateur
|
||||
|
||||
@ -30,12 +30,14 @@ export class SubscriptionsController {
|
||||
@ApiOperation({ summary: 'Create a new subscription' })
|
||||
async create(
|
||||
@Headers('X-Merchant-ID') merchantId: string,
|
||||
@Headers('X-COUNTRY') country: string,
|
||||
@Headers('X-OPERATOR') operator: string,
|
||||
@Request() req, @Body() dto: CreateSubscriptionDto) {
|
||||
this.logger.log('Merchant ID from header:'+ merchantId);
|
||||
this.logger.debug(
|
||||
`[request to hub ]: ${JSON.stringify(dto, null, 2)}`,
|
||||
)
|
||||
return this.subscriptionsService.create(merchantId, dto);
|
||||
return this.subscriptionsService.create(merchantId, dto,country,operator);
|
||||
}
|
||||
|
||||
@Get('/')
|
||||
|
||||
@ -8,6 +8,7 @@ import { SubscriptionProcessor } from './processors/subscription.processor';
|
||||
import { PrismaService } from '../../shared/services/prisma.service';
|
||||
import { PaymentsModule } from '../payments/payments.module';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { OperatorsModule } from '../operators/operators.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -18,6 +19,7 @@ import { PaymentsModule } from '../payments/payments.module';
|
||||
BullModule.registerQueue({
|
||||
name: 'billing',
|
||||
}),
|
||||
OperatorsModule
|
||||
// PaymentsModule,
|
||||
],
|
||||
controllers: [SubscriptionsController],
|
||||
|
||||
@ -1,15 +1,26 @@
|
||||
import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { Injectable, BadRequestException, NotFoundException, Logger } from '@nestjs/common';
|
||||
import { InjectQueue } from '@nestjs/bull';
|
||||
import bull from 'bull';
|
||||
import { PrismaService } from '../../shared/services/prisma.service';
|
||||
import { PaymentsService } from '../payments/payments.service';
|
||||
import { CreateSubscriptionDto, UpdateSubscriptionDto } from './dto/subscription.dto';
|
||||
import { Subscription } from 'generated/prisma';
|
||||
import { OperatorsService } from '../operators/operators.service';
|
||||
//import { SubscriptionStatus } from '@prisma/client';
|
||||
//import { SubscriptionStatus, Prisma } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class SubscriptionsService {
|
||||
private readonly logger = new Logger(SubscriptionsService.name);
|
||||
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly operatorsService: OperatorsService,
|
||||
@InjectQueue('subscriptions') private subscriptionQueue: bull.Queue,
|
||||
@InjectQueue('billing') private billingQueue: bull.Queue,
|
||||
) {}
|
||||
|
||||
async get(id: number):Promise<any> {
|
||||
const service = await this.prisma.subscription.findUnique({
|
||||
where: { id },
|
||||
@ -50,13 +61,9 @@ export class SubscriptionsService {
|
||||
getInvoices(id: string, partnerId: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@InjectQueue('subscriptions') private subscriptionQueue: bull.Queue,
|
||||
@InjectQueue('billing') private billingQueue: bull.Queue,
|
||||
) {}
|
||||
|
||||
async create(partnerId: string, dto: CreateSubscriptionDto) {
|
||||
|
||||
async create(partnerId: string, dto: CreateSubscriptionDto, country:string,operator:string) {
|
||||
/* todo Vérifier l'utilisateur
|
||||
|
||||
const user = await this.prisma.user.findFirst({
|
||||
@ -82,6 +89,8 @@ export class SubscriptionsService {
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// Vérifier s'il n'y a pas déjà une subscription active
|
||||
const existingSubscription = await this.prisma.subscription.findFirst({
|
||||
where: {
|
||||
@ -96,12 +105,36 @@ export class SubscriptionsService {
|
||||
throw new BadRequestException('User already has an active subscription for this plan');
|
||||
}
|
||||
|
||||
const adapter = this.operatorsService.getAdapter(
|
||||
operator,
|
||||
country,
|
||||
|
||||
);
|
||||
|
||||
const subscriptionParams = {
|
||||
userToken: dto.userToken,
|
||||
userAlias: dto.userToken, //todo make alias in contrat
|
||||
amount: 200,//plan.amount,todo
|
||||
currency: 'XOF',//plan.currency,todo
|
||||
description: 'dto.description',//plan.description,todo
|
||||
productId: dto.planId +'',
|
||||
merchantId: partnerId,
|
||||
periodicity: '86400', // todo 86400 (daily), 604800 (weekly), 0 (monthly) only those values will be accepted
|
||||
};
|
||||
|
||||
const result = await adapter.createSubscription(subscriptionParams);
|
||||
|
||||
this.logger.debug(
|
||||
`result from adapter ${JSON.stringify(result, null, 2)} for subscription creation`,
|
||||
);
|
||||
|
||||
|
||||
|
||||
// Créer la subscription
|
||||
const subscription = await this.prisma.subscription.create({
|
||||
data: {
|
||||
customerId: 1, //user.id, todo
|
||||
externalReference: result.subscriptionId,
|
||||
merchantPartnerId: 4,// todo , parseInt(partnerId),
|
||||
token: dto.userToken,
|
||||
planId: dto.planId,
|
||||
@ -109,7 +142,11 @@ export class SubscriptionsService {
|
||||
periodicity: "Daily",
|
||||
amount: 20,
|
||||
currency: "XOF",
|
||||
status: 'ACTIVE',
|
||||
status: 'ACTIVE',//todo mapping result.status 'SUCCESS' ? 'ACTIVE' : 'PENDING',
|
||||
//currentPeriodStart: new Date(),
|
||||
//currentPeriodEnd: new Date(), // todo À ajuster selon la périodicité
|
||||
// nextBillingDate: new Date(), // todo À ajuster selon la périodicité
|
||||
//renewalCount: 0,
|
||||
startDate: new Date(),
|
||||
failureCount: 0,
|
||||
nextPaymentDate: new Date(), // todo À ajuster selon la périodicité
|
||||
|
||||
Loading…
Reference in New Issue
Block a user