-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: This allows users to fund their accounts
- Loading branch information
1 parent
5585f03
commit 04194e8
Showing
22 changed files
with
866 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Knex } from 'knex'; | ||
|
||
export async function up(knex: Knex): Promise<void> { | ||
return await knex.schema.createTable('payments', (table) => { | ||
table.increments('id'); | ||
table.enum('type', ['fund', 'withdraw']); | ||
table.string('reference', 255).nullable(); | ||
table.string('channel', 50).nullable(); | ||
table.double('amount').nullable().unsigned(); | ||
table.integer('transactionId').nullable().unsigned(); | ||
table.timestamps(true, true, true); | ||
table.integer('customer').references('id').inTable('users').unsigned(); | ||
table.string('walletAddress').references('address').inTable('wallets'); | ||
}); | ||
} | ||
|
||
export async function down(knex: Knex): Promise<void> { | ||
return await knex.schema.dropTable('payments'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { registerAs } from '@nestjs/config'; | ||
|
||
export default registerAs('payments', () => ({ | ||
email: process.env.PAYMENTS_EMAIL, | ||
paystackSecretKey: process.env.PAYSTACK_SECRET_KEY, | ||
paystackPublicKey: process.env.PAYSTACK_PUBLIC_KEY | ||
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import { PaymentsController } from './payments.controller'; | ||
|
||
describe('PaymentsController', () => { | ||
let controller: PaymentsController; | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
controllers: [PaymentsController], | ||
}).compile(); | ||
|
||
controller = module.get<PaymentsController>(PaymentsController); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(controller).toBeDefined(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import { | ||
BadRequestException, | ||
Body, | ||
Controller, | ||
Get, | ||
HttpCode, | ||
HttpStatus, | ||
Post, | ||
Query, | ||
Redirect, | ||
Res | ||
} from '@nestjs/common'; | ||
|
||
import { | ||
ApiBadRequestResponse, | ||
ApiOkResponse, | ||
ApiOperation, | ||
ApiTags | ||
} from '@nestjs/swagger'; | ||
import * as express from 'express'; | ||
import { Auth } from 'src/common/decorators/http.decorator'; | ||
import { CurrentUser } from 'src/common/decorators/user.decorator'; | ||
import { ErrorResponseDTO } from 'src/common/dtos/response.dto'; | ||
import { CreateTransactionDto } from 'src/transactions/transactions.dto'; | ||
import { Roles, UserFindDto } from 'src/users/users.dto'; | ||
import { WalletsService } from 'src/wallets/wallets.service'; | ||
import { InitializePaymentDto, PaymentDto } from './payments.dto'; | ||
import { PaymentsService } from './payments.service'; | ||
|
||
@ApiTags('payments') | ||
@Controller('payments') | ||
export class PaymentsController { | ||
constructor( | ||
private paymentsService: PaymentsService, | ||
private walletsService: WalletsService | ||
) {} | ||
|
||
@Post('/') | ||
@HttpCode(HttpStatus.OK) | ||
@ApiOperation({ | ||
summary: 'Endpoint for for funding wallet and withdraw from wallet' | ||
}) | ||
@ApiOkResponse({ | ||
description: 'Registration is successful', | ||
type: InitializePaymentDto | ||
}) | ||
@ApiBadRequestResponse({ | ||
description: 'Credentials is invalid', | ||
type: ErrorResponseDTO | ||
}) | ||
@Auth([Roles.user]) | ||
async initialize( | ||
@Body() initializePaymentDto: InitializePaymentDto, | ||
@CurrentUser() auth: { id: number; email: string } | ||
) { | ||
const data = { | ||
...initializePaymentDto, | ||
user: { | ||
id: auth.id, | ||
email: auth.email | ||
} | ||
}; | ||
|
||
const wallet = await this.walletsService.findOne({ | ||
owner: auth.id, | ||
address: data.walletAddress | ||
}); | ||
|
||
if (!wallet) { | ||
throw new BadRequestException('Wallet not found for this user'); | ||
} | ||
|
||
const result = await this.paymentsService.initialize(data); | ||
|
||
if (!result.status) { | ||
throw new BadRequestException(result.message); | ||
} | ||
|
||
// redirection to the payment url did not work | ||
|
||
return { | ||
data: { | ||
message: | ||
'copy and paste the authorization_url in your browser to make your payment', | ||
...result.data | ||
} | ||
}; | ||
} | ||
|
||
@Get('/callback-url') | ||
@HttpCode(HttpStatus.OK) | ||
@ApiOperation({ | ||
summary: | ||
'Endpoint for payments response as it relates with paystack callback url' | ||
}) | ||
@ApiOkResponse({ | ||
description: ' success', | ||
type: class Force { | ||
data: any; | ||
} | ||
}) | ||
async callbackUrl(@Query() query) { | ||
const result = await this.paymentsService.verify(query.reference); | ||
|
||
const data = { | ||
reference: result.reference, | ||
transactionId: result.id, | ||
channel: result.channel, | ||
amount: result.amount | ||
}; | ||
|
||
const payment = await this.paymentsService.findOne({ | ||
reference: data.reference | ||
}); | ||
|
||
if (payment?.reference && payment.amount) { | ||
return { | ||
data: { | ||
message: | ||
'This payment was successfully and has been processed earlier', | ||
payment | ||
} | ||
}; | ||
} | ||
|
||
const updatePayment = await this.paymentsService.update( | ||
{ reference: result.reference }, | ||
data | ||
); | ||
|
||
const updateWallet = await this.walletsService.fund( | ||
{ | ||
owner: updatePayment.customer, | ||
address: updatePayment.walletAddress | ||
}, | ||
{ | ||
balance: result.amount / 100 // This is to convert back from to naira amount | ||
} | ||
); | ||
|
||
return { | ||
data: { | ||
message: 'payment was success', | ||
payment: updatePayment, | ||
wallet: updateWallet | ||
} | ||
}; | ||
} | ||
|
||
@Get('/') | ||
@HttpCode(HttpStatus.OK) | ||
@ApiOperation({ | ||
summary: 'Endpoint for listing user payments (funding and withrawal)' | ||
}) | ||
@ApiOkResponse({ | ||
description: ' success', | ||
type: [PaymentDto] | ||
}) | ||
@Auth([Roles.user]) | ||
async myPayments(@CurrentUser() auth: Partial<UserFindDto>) { | ||
const payments = await this.paymentsService.find({ | ||
customer: auth.id | ||
}); | ||
|
||
if (!payments?.length) { | ||
return { | ||
message: 'No payments found' | ||
}; | ||
} | ||
return { data: payments }; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { ApiProperty } from '@nestjs/swagger'; | ||
import { IsEmail, IsNotEmpty, IsNumber, IsString } from 'class-validator'; | ||
import { TransactionTypes } from 'src/transactions/transactions.dto'; | ||
|
||
export enum PaymentTypeDto { | ||
fund = 'fund', | ||
withdraw = 'withdraw' | ||
} | ||
|
||
export class InitializePaymentDto { | ||
@ApiProperty() | ||
@IsString() | ||
@IsNotEmpty() | ||
walletAddress: string; | ||
|
||
@ApiProperty() | ||
@IsNotEmpty() | ||
@IsNumber() | ||
amount: number; | ||
|
||
@ApiProperty({ enum: PaymentTypeDto, default: PaymentTypeDto.fund }) | ||
@IsString() | ||
@IsNotEmpty() | ||
type: PaymentTypeDto; | ||
} | ||
|
||
export class PaymentDto extends InitializePaymentDto { | ||
@ApiProperty() | ||
@IsString() | ||
reference: string; | ||
|
||
@ApiProperty() | ||
@IsString() | ||
walletAddress: string; | ||
|
||
@ApiProperty() | ||
@IsNumber() | ||
customer: number; | ||
|
||
@ApiProperty() | ||
@IsNumber() | ||
id?: number; | ||
|
||
@ApiProperty() | ||
@IsString() | ||
channel: string; | ||
|
||
@ApiProperty() | ||
@IsString() | ||
transactionId: string; | ||
} | ||
|
||
export class PaymentFindingDto { | ||
@ApiProperty() | ||
@IsNumber() | ||
id?: number; | ||
|
||
@ApiProperty() | ||
@IsString() | ||
reference?: string; | ||
|
||
@ApiProperty() | ||
@IsNumber() | ||
customer?: number; | ||
|
||
@ApiProperty() | ||
@IsString() | ||
walletAddress?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { PaymentsService } from './payments.service'; | ||
import { PaymentsController } from './payments.controller'; | ||
import { WalletsModule } from 'src/wallets/wallets.module'; | ||
|
||
@Module({ | ||
imports: [WalletsModule], | ||
providers: [PaymentsService], | ||
controllers: [PaymentsController], | ||
exports: [PaymentsService] | ||
}) | ||
export class PaymentsModule {} |
Oops, something went wrong.