@@ -23,7 +23,9 @@ import {
2323 DatabaseFetchError ,
2424 DatabaseInsertError ,
2525 InternalServerError ,
26+ NotFoundError ,
2627 UnauthenticatedError ,
28+ UnauthorizedError ,
2729 ValidationError ,
2830} from "common/errors/index.js" ;
2931import { Modules } from "common/modules.js" ;
@@ -39,6 +41,7 @@ import stripe, { Stripe } from "stripe";
3941import rawbody from "fastify-raw-body" ;
4042import { AvailableSQSFunctions , SQSPayload } from "common/types/sqsMessage.js" ;
4143import { SendMessageCommand , SQSClient } from "@aws-sdk/client-sqs" ;
44+ import { z } from "zod" ;
4245
4346const stripeRoutes : FastifyPluginAsync = async ( fastify , _options ) => {
4447 await fastify . register ( rawbody , {
@@ -177,6 +180,112 @@ const stripeRoutes: FastifyPluginAsync = async (fastify, _options) => {
177180 reply . status ( 201 ) . send ( { id : linkId , link : url } ) ;
178181 } ,
179182 ) ;
183+ fastify . withTypeProvider < FastifyZodOpenApiTypeProvider > ( ) . delete (
184+ "/paymentLinks/:linkId" ,
185+ {
186+ schema : withRoles (
187+ [ AppRoles . STRIPE_LINK_CREATOR ] ,
188+ withTags ( [ "Stripe" ] , {
189+ summary : "Deactivate a Stripe payment link." ,
190+ params : z . object ( {
191+ linkId : z . string ( ) . min ( 1 ) . openapi ( {
192+ description : "Payment Link ID" ,
193+ example : "plink_abc123" ,
194+ } ) ,
195+ } ) ,
196+ } ) ,
197+ ) ,
198+ onRequest : fastify . authorizeFromSchema ,
199+ } ,
200+ async ( request , reply ) => {
201+ if ( ! request . username ) {
202+ throw new UnauthenticatedError ( { message : "No username found" } ) ;
203+ }
204+ const { linkId } = request . params ;
205+ const response = await fastify . dynamoClient . send (
206+ new QueryCommand ( {
207+ TableName : genericConfig . StripeLinksDynamoTableName ,
208+ IndexName : "LinkIdIndex" ,
209+ KeyConditionExpression : "linkId = :linkId" ,
210+ ExpressionAttributeValues : {
211+ ":linkId" : { S : linkId } ,
212+ } ,
213+ } ) ,
214+ ) ;
215+ if ( ! response ) {
216+ throw new DatabaseFetchError ( {
217+ message : "Could not check for payment link in table." ,
218+ } ) ;
219+ }
220+ if ( ! response . Items || response . Items ?. length !== 1 ) {
221+ throw new NotFoundError ( { endpointName : request . url } ) ;
222+ }
223+ const unmarshalledEntry = unmarshall ( response . Items [ 0 ] ) as {
224+ userId : string ;
225+ invoiceId : string ;
226+ amount ?: number ;
227+ priceId ?: string ;
228+ productId ?: string ;
229+ } ;
230+ if (
231+ unmarshalledEntry . userId !== request . username &&
232+ ! request . userRoles ?. has ( AppRoles . BYPASS_OBJECT_LEVEL_AUTH )
233+ ) {
234+ throw new UnauthorizedError ( {
235+ message : "Not authorized to deactivate this payment link." ,
236+ } ) ;
237+ }
238+ const logStatement = buildAuditLogTransactPut ( {
239+ entry : {
240+ module : Modules . STRIPE ,
241+ actor : request . username ,
242+ target : `Link ${ linkId } | Invoice ${ unmarshalledEntry . invoiceId } ` ,
243+ message : "Deactivated Stripe payment link" ,
244+ } ,
245+ } ) ;
246+ const dynamoCommand = new TransactWriteItemsCommand ( {
247+ TransactItems : [
248+ logStatement ,
249+ {
250+ Update : {
251+ TableName : genericConfig . StripeLinksDynamoTableName ,
252+ Key : {
253+ userId : { S : unmarshalledEntry . userId } ,
254+ linkId : { S : linkId } ,
255+ } ,
256+ UpdateExpression : "SET active = :new_val" ,
257+ ConditionExpression : "active = :old_val" ,
258+ ExpressionAttributeValues : {
259+ ":new_val" : { BOOL : false } ,
260+ ":old_val" : { BOOL : true } ,
261+ } ,
262+ } ,
263+ } ,
264+ ] ,
265+ } ) ;
266+ const secretApiConfig =
267+ ( await getSecretValue (
268+ fastify . secretsManagerClient ,
269+ genericConfig . ConfigSecretName ,
270+ ) ) || { } ;
271+ if ( unmarshalledEntry . productId ) {
272+ request . log . debug (
273+ `Deactivating Stripe product ${ unmarshalledEntry . productId } ` ,
274+ ) ;
275+ await deactivateStripeProduct ( {
276+ stripeApiKey : secretApiConfig . stripe_secret_key as string ,
277+ productId : unmarshalledEntry . productId ,
278+ } ) ;
279+ }
280+ request . log . debug ( `Deactivating Stripe link ${ linkId } ` ) ;
281+ await deactivateStripeLink ( {
282+ stripeApiKey : secretApiConfig . stripe_secret_key as string ,
283+ linkId,
284+ } ) ;
285+ await fastify . dynamoClient . send ( dynamoCommand ) ;
286+ return reply . status ( 201 ) . send ( ) ;
287+ } ,
288+ ) ;
180289 fastify . post (
181290 "/webhook" ,
182291 {
0 commit comments