@@ -10,21 +10,15 @@ import {
1010 type PaymentExecutionIdParam ,
1111 paymentExecutionIdParamSchema ,
1212} from "../schemas/payment-execution.schema.js" ;
13+ import { verifyOneTimePayment } from "../services/solana.service.js" ;
1314
14- /**
15- * Create a payment execution record for a one-time payment
16- *
17- * This endpoint is called by the frontend AFTER the user has successfully
18- * made a payment via their wallet. It records the transaction in the database.
19- */
2015export const createPaymentExecution = async (
2116 request : FastifyRequest < { Body : CreatePaymentExecutionBody } > ,
2217 reply : FastifyReply
2318) => {
2419 try {
2520 const validatedData = createPaymentExecutionSchema . parse ( request . body ) ;
2621
27- // Verify the plan exists (no authentication needed - public endpoint)
2822 const plan = await prisma . plan . findUnique ( {
2923 where : { id : validatedData . planId } ,
3024 include : { planTokens : true , receiver : true } ,
@@ -38,7 +32,6 @@ export const createPaymentExecution = async (
3832 } ) ;
3933 }
4034
41- // Verify this is NOT a recurring plan
4235 if ( plan . isRecurring ) {
4336 return reply . code ( 400 ) . send ( {
4437 statusCode : 400 ,
@@ -48,7 +41,6 @@ export const createPaymentExecution = async (
4841 } ) ;
4942 }
5043
51- // Verify the token is supported by the plan
5244 const planToken = plan . planTokens . find (
5345 ( t ) => t . tokenMint === validatedData . tokenMint
5446 ) ;
@@ -61,7 +53,6 @@ export const createPaymentExecution = async (
6153 } ) ;
6254 }
6355
64- // Check if this transaction signature already exists (prevent duplicates)
6556 const existingPayment = await prisma . paymentExecution . findFirst ( {
6657 where : { txSignature : validatedData . txSignature } ,
6758 } ) ;
@@ -76,26 +67,27 @@ export const createPaymentExecution = async (
7667 } ) ;
7768 }
7869
79- // TODO: Verify the transaction on-chain
80- // 1. Fetch transaction from Solana using txSignature
81- // 2. Verify transaction is confirmed
82- // 3. Verify amount matches
83- // 4. Verify recipient is the plan's receiver
84- // 5. Verify token mint matches
85- // For now, we'll trust the frontend and mark as SUCCESS
70+ const verification = await verifyOneTimePayment ( {
71+ txSignature : validatedData . txSignature ,
72+ expectedTokenMint : validatedData . tokenMint ,
73+ expectedAmount : validatedData . amount ,
74+ expectedReceiverWallet : plan . receiver . walletAddress ,
75+ tokenDecimals : planToken . tokenDecimals ,
76+ } ) ;
77+
78+ const status = verification . valid ? "SUCCESS" : "FAILED" ;
8679
87- // Create the payment execution record
8880 const paymentExecution = await prisma . paymentExecution . create ( {
8981 data : {
9082 planId : validatedData . planId ,
91- subscriptionId : null , // One-time payment, no subscription
83+ subscriptionId : null ,
9284 txSignature : validatedData . txSignature ,
9385 executedBy : validatedData . executedBy ?? null ,
94- status : "SUCCESS" , // TODO: Verify on-chain before marking as SUCCESS
86+ status,
9587 executedAt : new Date ( ) ,
9688 tokenMint : validatedData . tokenMint ,
9789 amount : validatedData . amount ,
98- errorMessage : null ,
90+ errorMessage : verification . valid ? null : verification . reason ?? null ,
9991 } ,
10092 include : {
10193 plan : {
@@ -107,12 +99,25 @@ export const createPaymentExecution = async (
10799 } ,
108100 } ) ;
109101
102+ if ( ! verification . valid ) {
103+ request . log . warn (
104+ `Payment verification failed for plan ${ plan . id } (tx: ${ validatedData . txSignature } ): ${ verification . reason } `
105+ ) ;
106+
107+ return reply . code ( 400 ) . send ( {
108+ statusCode : 400 ,
109+ error : "Bad Request" ,
110+ message : `Transaction verification failed: ${ verification . reason } ` ,
111+ paymentExecutionId : paymentExecution . id ,
112+ } ) ;
113+ }
114+
110115 request . log . info (
111- `Payment execution created : ${ paymentExecution . id } for plan ${ plan . id } (tx: ${ validatedData . txSignature } )`
116+ `Payment verified and recorded : ${ paymentExecution . id } for plan ${ plan . id } (tx: ${ validatedData . txSignature } )`
112117 ) ;
113118
114119 return reply . code ( 201 ) . send ( {
115- message : "Payment execution recorded successfully" ,
120+ message : "Payment verified and recorded successfully" ,
116121 paymentExecution,
117122 } ) ;
118123 } catch ( error ) {
@@ -134,9 +139,6 @@ export const createPaymentExecution = async (
134139 }
135140} ;
136141
137- /**
138- * Get all payment executions for the authenticated receiver
139- */
140142export const getPaymentExecutions = async (
141143 request : FastifyRequest < { Querystring : GetPaymentExecutionsQuery } > ,
142144 reply : FastifyReply
@@ -145,29 +147,24 @@ export const getPaymentExecutions = async (
145147 const validatedQuery = getPaymentExecutionsQuerySchema . parse ( request . query ) ;
146148 const userId = request . user . userId ;
147149
148- // Build query filters
149150 const where : Prisma . PaymentExecutionWhereInput = {
150151 plan : {
151- receiverId : userId , // Only show payments for this receiver's plans
152+ receiverId : userId ,
152153 } ,
153154 } ;
154155
155- // Status filter
156156 if ( validatedQuery . status !== "all" ) {
157157 where . status = validatedQuery . status ;
158158 }
159159
160- // Plan filter
161160 if ( validatedQuery . planId ) {
162161 where . planId = validatedQuery . planId ;
163162 }
164163
165- // Token filter
166164 if ( validatedQuery . tokenMint ) {
167165 where . tokenMint = validatedQuery . tokenMint ;
168166 }
169167
170- // Date range filter
171168 if ( validatedQuery . dateFrom || validatedQuery . dateTo ) {
172169 where . executedAt = { } ;
173170 if ( validatedQuery . dateFrom ) {
@@ -178,7 +175,6 @@ export const getPaymentExecutions = async (
178175 }
179176 }
180177
181- // Search by transaction signature
182178 if ( validatedQuery . search ) {
183179 where . txSignature = {
184180 contains : validatedQuery . search ,
@@ -235,9 +231,6 @@ export const getPaymentExecutions = async (
235231 }
236232} ;
237233
238- /**
239- * Get a specific payment execution by ID
240- */
241234export const getPaymentExecution = async (
242235 request : FastifyRequest < { Params : PaymentExecutionIdParam } > ,
243236 reply : FastifyReply
@@ -271,7 +264,6 @@ export const getPaymentExecution = async (
271264 } ) ;
272265 }
273266
274- // Verify the payment belongs to this receiver
275267 if ( paymentExecution . plan ?. receiverId !== userId ) {
276268 return reply . code ( 403 ) . send ( {
277269 statusCode : 403 ,
0 commit comments