Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ export const RESPONSE_MESSAGES = {
INVALID_EMAIL_400: { statusCode: 400, message: 'Invalid email.' },
USER_OUT_OF_EMAIL_CREDITS_400: { statusCode: 400, message: 'User out of email credits.' },
USER_OUT_OF_POST_CREDITS_400: { statusCode: 400, message: 'User out of post credits.' },
NO_INVOICE_FOUND_404: { statusCode: 404, message: 'No invoice found.' }
NO_INVOICE_FOUND_404: { statusCode: 404, message: 'No invoice found.' },
INVALID_FIELDS_FORMAT_400: { statusCode: 400, message: "'fields' must be a valid JSON array." },
INVALID_FIELD_STRUCTURE_400: { statusCode: 400, message: "Each field must have 'name' property as string." },
INVALID_AMOUNT_400: { statusCode: 400, message: "'amount' must be a valid positive number." }
}

export const SOCKET_MESSAGES = {
Expand Down
17 changes: 12 additions & 5 deletions pages/api/payments/paymentId/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Decimal } from '@prisma/client/runtime/library'
import { generatePaymentId } from 'services/clientPaymentService'
import { parseAddress, parseCreatePaymentIdPOSTRequest } from 'utils/validators'
import { RESPONSE_MESSAGES } from 'constants/index'
Expand All @@ -14,11 +13,10 @@ export default async (req: any, res: any): Promise<void> => {
await runMiddleware(req, res, cors)
if (req.method === 'POST') {
try {
const values = parseCreatePaymentIdPOSTRequest(req.body)
const address = parseAddress(values.address)
const amount = values.amount as Decimal | undefined
const { amount, fields, address } = parseCreatePaymentIdPOSTRequest(req.body)
const parsedAddress = parseAddress(address)

const paymentId = await generatePaymentId(address, amount)
const paymentId = await generatePaymentId(parsedAddress, amount, fields)

res.status(200).json({ paymentId })
} catch (error: any) {
Expand All @@ -29,6 +27,15 @@ export default async (req: any, res: any): Promise<void> => {
case RESPONSE_MESSAGES.INVALID_ADDRESS_400.message:
res.status(RESPONSE_MESSAGES.INVALID_ADDRESS_400.statusCode).json(RESPONSE_MESSAGES.INVALID_ADDRESS_400)
break
case RESPONSE_MESSAGES.INVALID_FIELDS_FORMAT_400.message:
res.status(RESPONSE_MESSAGES.INVALID_FIELDS_FORMAT_400.statusCode).json(RESPONSE_MESSAGES.INVALID_FIELDS_FORMAT_400)
break
case RESPONSE_MESSAGES.INVALID_FIELD_STRUCTURE_400.message:
res.status(RESPONSE_MESSAGES.INVALID_FIELD_STRUCTURE_400.statusCode).json(RESPONSE_MESSAGES.INVALID_FIELD_STRUCTURE_400)
break
case RESPONSE_MESSAGES.INVALID_AMOUNT_400.message:
res.status(RESPONSE_MESSAGES.INVALID_AMOUNT_400.statusCode).json(RESPONSE_MESSAGES.INVALID_AMOUNT_400)
break
default:
res.status(500).json({ statusCode: 500, message: error.message })
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE `ClientPayment` ADD COLUMN `fields` LONGTEXT NOT NULL DEFAULT '[]';
1 change: 1 addition & 0 deletions prisma-local/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ model ClientPayment {
addressString String
amount Decimal?
address Address @relation(fields: [addressString], references: [address])
fields String @db.LongText @default("[]")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
34 changes: 32 additions & 2 deletions services/clientPaymentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import { parseAddress } from 'utils/validators'
import { addressExists } from './addressService'
import moment from 'moment'

export const generatePaymentId = async (address: string, amount?: Prisma.Decimal): Promise<string> => {
export interface ClientPaymentField {
name: string
text?: string
type?: string
value?: string | boolean
}

export const generatePaymentId = async (address: string, amount?: Prisma.Decimal, fields?: ClientPaymentField[]): Promise<string> => {
const rawUUID = uuidv4()
const cleanUUID = rawUUID.replace(/-/g, '')
const status = 'PENDING' as ClientPaymentStatus
Expand All @@ -29,7 +36,8 @@ export const generatePaymentId = async (address: string, amount?: Prisma.Decimal
},
paymentId: cleanUUID,
status,
amount
amount,
fields: fields !== undefined ? JSON.stringify(fields) : '[]'
},
include: {
address: true
Expand Down Expand Up @@ -57,6 +65,28 @@ export const getClientPayment = async (paymentId: string): Promise<Prisma.Client
})
}

export const getClientPaymentFields = async (paymentId: string): Promise<ClientPaymentField[]> => {
const clientPayment = await prisma.clientPayment.findUnique({
where: { paymentId },
select: { fields: true }
})
if (clientPayment === null) {
return []
}
try {
return JSON.parse(clientPayment.fields) as ClientPaymentField[]
} catch {
return []
}
}

export const updateClientPaymentFields = async (paymentId: string, fields: ClientPaymentField[]): Promise<void> => {
await prisma.clientPayment.update({
where: { paymentId },
data: { fields: JSON.stringify(fields) }
})
}

export const cleanupExpiredClientPayments = async (): Promise<void> => {
const cutoff = moment.utc().subtract(CLIENT_PAYMENT_EXPIRATION_TIME, 'milliseconds').toDate()

Expand Down
69 changes: 67 additions & 2 deletions utils/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import crypto from 'crypto'
import { getUserPrivateKey } from '../services/userService'
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'
import moment from 'moment-timezone'
import { ClientPaymentField } from 'services/clientPaymentService'

/* The functions exported here should validate the data structure / syntax of an
* input by throwing an error in case something is different from the expected.
Expand Down Expand Up @@ -574,10 +575,70 @@ export interface CreateInvoicePOSTParameters {
export interface CreatePaymentIdPOSTParameters {
address?: string
amount?: string
fields?: string
}
export interface ClientPaymentFieldInput {
name?: string
text?: string
type?: string
value?: string | boolean
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

export interface CreatePaymentIdInput {
address: string
amount?: string
amount?: Prisma.Decimal
fields?: ClientPaymentField[]
}

export const parseClientPaymentFields = function (fieldsInput: string | undefined): ClientPaymentField[] | undefined {
if (fieldsInput === undefined || fieldsInput === '') {
return undefined
}

let parsedFields: unknown
try {
parsedFields = JSON.parse(fieldsInput)
} catch {
throw new Error(RESPONSE_MESSAGES.INVALID_FIELDS_FORMAT_400.message)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if (!Array.isArray(parsedFields)) {
throw new Error(RESPONSE_MESSAGES.INVALID_FIELDS_FORMAT_400.message)
}

for (const field of parsedFields) {
if (
typeof field !== 'object' ||
field === null ||
typeof field.name !== 'string' ||
field.name?.trim() === ''
) {
throw new Error(RESPONSE_MESSAGES.INVALID_FIELD_STRUCTURE_400.message)
}
if (field.type !== undefined && typeof field.type !== 'string') {
throw new Error(RESPONSE_MESSAGES.INVALID_FIELD_STRUCTURE_400.message)
}
if (field.value !== undefined && typeof field.value !== 'string' && typeof field.value !== 'boolean') {
throw new Error(RESPONSE_MESSAGES.INVALID_FIELD_STRUCTURE_400.message)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

return parsedFields as ClientPaymentField[]
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

export const parseAmount = function (amountInput: string | undefined): Prisma.Decimal | undefined {
if (amountInput === undefined || amountInput === '') {
return undefined
}

const trimmedAmount = amountInput.trim()
const numericAmount = Number(trimmedAmount)

if (isNaN(numericAmount) || numericAmount <= 0) {
throw new Error(RESPONSE_MESSAGES.INVALID_AMOUNT_400.message)
}

return new Prisma.Decimal(trimmedAmount)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export const parseCreatePaymentIdPOSTRequest = function (params: CreatePaymentIdPOSTParameters): CreatePaymentIdInput {
Expand All @@ -588,8 +649,12 @@ export const parseCreatePaymentIdPOSTRequest = function (params: CreatePaymentId
throw new Error(RESPONSE_MESSAGES.ADDRESS_NOT_PROVIDED_400.message)
}

const amount = parseAmount(params.amount)
const fields = parseClientPaymentFields(params.fields)

return {
address: params.address,
amount: params.amount === '' ? undefined : params.amount
amount,
fields
}
}