feat: nowpayments

This commit is contained in:
Nevo David 2025-01-14 21:41:14 +07:00
parent 5be9d276a3
commit f3579cfcaf
8 changed files with 151 additions and 8 deletions

View File

@ -27,6 +27,7 @@ import { PublicController } from '@gitroom/backend/api/routes/public.controller'
import { RootController } from '@gitroom/backend/api/routes/root.controller';
import { TrackService } from '@gitroom/nestjs-libraries/track/track.service';
import { ShortLinkService } from '@gitroom/nestjs-libraries/short-linking/short.link.service';
import { Nowpayments } from '@gitroom/nestjs-libraries/crypto/nowpayments';
const authenticatedController = [
UsersController,
@ -65,6 +66,7 @@ const authenticatedController = [
IntegrationManager,
TrackService,
ShortLinkService,
Nowpayments,
],
get exports() {
return [...this.imports, ...this.providers];

View File

@ -8,6 +8,7 @@ import { ApiTags } from '@nestjs/swagger';
import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request';
import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';
import { Request } from 'express';
import { Nowpayments } from '@gitroom/nestjs-libraries/crypto/nowpayments';
@ApiTags('Billing')
@Controller('/billing')
@ -15,7 +16,8 @@ export class BillingController {
constructor(
private _subscriptionService: SubscriptionService,
private _stripeService: StripeService,
private _notificationService: NotificationService
private _notificationService: NotificationService,
private _nowpayments: Nowpayments
) {}
@Get('/check/:id')
@ -24,10 +26,7 @@ export class BillingController {
@Param('id') body: string
) {
return {
status: await this._stripeService.checkSubscription(
org.id,
body
),
status: await this._stripeService.checkSubscription(org.id, body),
};
}
@ -39,7 +38,13 @@ export class BillingController {
@Req() req: Request
) {
const uniqueId = req?.cookies?.track;
return this._stripeService.subscribe(uniqueId, org.id, user.id, body, org.allowTrial);
return this._stripeService.subscribe(
uniqueId,
org.id,
user.id,
body,
org.allowTrial
);
}
@Get('/portal')
@ -106,4 +111,9 @@ export class BillingController {
body.subscription
);
}
@Get('/crypto')
async crypto(@GetOrgFromRequest() org: Organization) {
return this._nowpayments.createPaymentPage(org.id);
}
}

View File

@ -10,6 +10,7 @@ import { Request, Response } from 'express';
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
import { getCookieUrlFromDomain } from '@gitroom/helpers/subdomain/subdomain.management';
import { AgentGraphInsertService } from '@gitroom/nestjs-libraries/agent/agent.graph.insert.service';
import { Nowpayments } from '@gitroom/nestjs-libraries/crypto/nowpayments';
@ApiTags('Public')
@Controller('/public')
@ -18,7 +19,8 @@ export class PublicController {
private _agenciesService: AgenciesService,
private _trackService: TrackService,
private _agentGraphInsertService: AgentGraphInsertService,
private _postsService: PostsService
private _postsService: PostsService,
private _nowpayments: Nowpayments
) {}
@Post('/agent')
async createAgent(@Body() body: { text: string; apiKey: string }) {
@ -120,4 +122,13 @@ export class PublicController {
track: uniqueId,
});
}
@Post('/crypto/:path')
async cryptoPost(
@Body() body: any,
@Param('path') path: string
) {
console.log('cryptoPost', body, path);
return this._nowpayments.processPayment(path, body);
}
}

View File

@ -26,6 +26,7 @@ import { useUtmUrl } from '@gitroom/helpers/utils/utm.saver';
import { useTolt } from '@gitroom/frontend/components/layout/tolt.script';
import { useTrack } from '@gitroom/react/helpers/use.track';
import { TrackEnum } from '@gitroom/nestjs-libraries/user/track.enum';
import { PurchaseCrypto } from '@gitroom/frontend/components/billing/purchase.crypto';
export interface Tiers {
month: Array<{
@ -502,6 +503,7 @@ export const MainBillingComponent: FC<{
</div>
))}
</div>
<PurchaseCrypto />
{!!subscription?.id && (
<div className="flex justify-center mt-[20px] gap-[10px]">
<Button onClick={updatePayment}>Update Payment Method</Button>

View File

@ -0,0 +1,24 @@
import { FC, useCallback, useState } from 'react';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import { Button } from '@gitroom/react/form/button';
export const PurchaseCrypto: FC = () => {
const fetch = useFetch();
const [loading, setLoading] = useState(false);
const load = useCallback(async () => {
setLoading(true);
const data = await (await fetch('/billing/crypto')).json();
window.location.href = data.invoice_url;
}, []);
return (
<div
className="flex-1 bg-sixth items-center border border-customColor6 rounded-[4px] p-[24px] gap-[16px] flex [@media(max-width:1024px)]:items-center"
>
<div>Purchase a Life-time PRO account with SOL ($199)</div>
<div>
<Button loading={loading} onClick={load}>Purchase now</Button>
</div>
</div>
);
};

View File

@ -79,6 +79,9 @@ export const TopMenu: FC = () => {
if (f.requireBilling && !billingEnabled) {
return false;
}
if (f.name === 'Billing' && user?.isLifetime) {
return false;
}
if (f.role) {
return f.role.includes(user?.role!);
}

View File

@ -0,0 +1,78 @@
import { Injectable } from '@nestjs/common';
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
import { AuthService } from '@gitroom/helpers/auth/auth.service';
import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';
export interface ProcessPayment {
payment_id: number;
payment_status: string;
pay_address: string;
price_amount: number;
price_currency: string;
pay_amount: number;
actually_paid: number;
pay_currency: string;
order_id: string;
order_description: string;
purchase_id: string;
created_at: string;
updated_at: string;
outcome_amount: number;
outcome_currency: string;
}
@Injectable()
export class Nowpayments {
constructor(private _subscriptionService: SubscriptionService) {}
async processPayment(path: string, body: ProcessPayment) {
const decrypt = AuthService.verifyJWT(path) as any;
if (!decrypt || !decrypt.order_id) {
return;
}
if (
decrypt.payment_status !== 'confirmed' &&
decrypt.payment_status !== 'finished'
) {
return;
}
const [org, make] = body.order_id.split('_');
await this._subscriptionService.lifeTime(org, make, 'PRO');
return body;
}
async createPaymentPage(orgId: string) {
const onlyId = makeId(5);
const make = orgId + '_' + onlyId;
const signRequest = AuthService.signJWT({ order_id: make });
const { id, invoice_url } = await (
await fetch('https://api.nowpayments.io/v1/invoice', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NOWPAYMENTS_API_KEY!,
},
body: JSON.stringify({
price_amount: process.env.NOWPAYMENTS_AMOUNT,
price_currency: 'USD',
order_id: make,
pay_currency: 'SOL',
order_description: 'Lifetime deal account for Postiz',
ipn_callback_url:
process.env.NEXT_PUBLIC_BACKEND_URL +
`/public/crypto/${signRequest}`,
success_url: process.env.FRONTEND_URL + `/launches?check=${onlyId}`,
cancel_url: process.env.FRONTEND_URL,
}),
})
).json();
return {
id,
invoice_url,
};
}
}

View File

@ -13,7 +13,7 @@ export class SubscriptionService {
constructor(
private readonly _subscriptionRepository: SubscriptionRepository,
private readonly _integrationService: IntegrationService,
private readonly _organizationService: OrganizationService,
private readonly _organizationService: OrganizationService
) {}
getSubscriptionByOrganizationId(organizationId: string) {
@ -188,6 +188,19 @@ export class SubscriptionService {
};
}
async lifeTime(orgId: string, identifier: string, subscription: any) {
return this.createOrUpdateSubscription(
identifier,
identifier,
pricing[subscription].channel!,
subscription,
'YEARLY',
null,
identifier,
orgId
);
}
async addSubscription(orgId: string, userId: string, subscription: any) {
await this._subscriptionRepository.setCustomerId(orgId, orgId);
return this.createOrUpdateSubscription(