Skip to content

Commit 02541da

Browse files
committed
support ACH direct debit in invoice settlement
1 parent 613f2f7 commit 02541da

File tree

2 files changed

+127
-72
lines changed

2 files changed

+127
-72
lines changed

src/api/functions/stripe.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export const createStripeLink = async ({
5757
quantity: 1,
5858
},
5959
],
60+
payment_method_types: ["card", "us_bank_account"],
6061
});
6162
return {
6263
url: paymentLink.url,

src/api/routes/stripe.ts

Lines changed: 126 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ const stripeRoutes: FastifyPluginAsync = async (fastify, _options) => {
333333
});
334334
}
335335
switch (event.type) {
336+
case "checkout.session.async_payment_succeeded":
336337
case "checkout.session.completed":
337338
if (event.data.object.payment_link) {
338339
const eventId = event.id;
@@ -391,93 +392,146 @@ const stripeRoutes: FastifyPluginAsync = async (fastify, _options) => {
391392
.formatToParts(paymentAmount / 100)
392393
.map((val) => val.value)
393394
.join("");
394-
request.log.info(
395-
`Registered payment of ${withCurrency} by ${name} (${email}) for payment link ${paymentLinkId} invoice ID ${unmarshalledEntry.invoiceId}). Invoice was paid ${paidInFull ? "in full." : "partially."}`,
396-
);
395+
397396
// Notify link owner of payment
398397
let queueId;
399-
if (unmarshalledEntry.userId.includes("@")) {
398+
if (event.data.object.payment_status === "unpaid") {
400399
request.log.info(
401-
`Sending email to ${unmarshalledEntry.userId}...`,
400+
`Pending payment of ${withCurrency} by ${name} (${email}) for payment link ${paymentLinkId} invoice ID ${unmarshalledEntry.invoiceId}). Invoice was tentatively paid ${paidInFull ? "in full." : "partially."}`,
402401
);
403-
const sqsPayload: SQSPayload<AvailableSQSFunctions.EmailNotifications> =
404-
{
405-
function: AvailableSQSFunctions.EmailNotifications,
406-
metadata: {
407-
initiator: eventId,
408-
reqId: request.id,
409-
},
410-
payload: {
411-
to: [unmarshalledEntry.userId],
412-
subject: `Payment Recieved for Invoice ${unmarshalledEntry.invoiceId}`,
413-
content: `ACM @ UIUC has received ${paidInFull ? "full" : "partial"} payment for Invoice ${unmarshalledEntry.invoiceId} (${withCurrency} paid by ${name}, ${email}).\n\nPlease contact Officer Board with any questions.`,
414-
callToActionButton: {
415-
name: "View Your Stripe Links",
416-
url: `${fastify.environmentConfig.UserFacingUrl}/stripe`,
402+
if (unmarshalledEntry.userId.includes("@")) {
403+
request.log.info(
404+
`Sending email to ${unmarshalledEntry.userId}...`,
405+
);
406+
const sqsPayload: SQSPayload<AvailableSQSFunctions.EmailNotifications> =
407+
{
408+
function: AvailableSQSFunctions.EmailNotifications,
409+
metadata: {
410+
initiator: eventId,
411+
reqId: request.id,
417412
},
418-
},
419-
};
420-
if (!fastify.sqsClient) {
421-
fastify.sqsClient = new SQSClient({
422-
region: genericConfig.AwsRegion,
423-
});
413+
payload: {
414+
to: [unmarshalledEntry.userId],
415+
subject: `Payment Pending for Invoice ${unmarshalledEntry.invoiceId}`,
416+
content: `
417+
ACM @ UIUC has received intent of ${paidInFull ? "full" : "partial"} payment for Invoice ${unmarshalledEntry.invoiceId} (${withCurrency} paid by ${name}, ${email}).
418+
419+
The payee has used a payment method which does not settle funds immediately. Therefore, ACM @ UIUC is still waiting for funds to settle and no services should be performed until the funds settle.
420+
421+
Please contact Officer Board with any questions.
422+
`,
423+
callToActionButton: {
424+
name: "View Your Stripe Links",
425+
url: `${fastify.environmentConfig.UserFacingUrl}/stripe`,
426+
},
427+
},
428+
};
429+
if (!fastify.sqsClient) {
430+
fastify.sqsClient = new SQSClient({
431+
region: genericConfig.AwsRegion,
432+
});
433+
}
434+
const result = await fastify.sqsClient.send(
435+
new SendMessageCommand({
436+
QueueUrl: fastify.environmentConfig.SqsQueueUrl,
437+
MessageBody: JSON.stringify(sqsPayload),
438+
}),
439+
);
440+
queueId = result.MessageId || "";
424441
}
425-
const result = await fastify.sqsClient.send(
426-
new SendMessageCommand({
427-
QueueUrl: fastify.environmentConfig.SqsQueueUrl,
428-
MessageBody: JSON.stringify(sqsPayload),
429-
}),
442+
} else {
443+
request.log.info(
444+
`Registered payment of ${withCurrency} by ${name} (${email}) for payment link ${paymentLinkId} invoice ID ${unmarshalledEntry.invoiceId}). Invoice was paid ${paidInFull ? "in full." : "partially."}`,
430445
);
431-
queueId = result.MessageId || "";
432-
}
433-
// If full payment is done, disable the link
434-
if (paidInFull) {
435-
request.log.debug("Paid in full, disabling link.");
436-
const logStatement = buildAuditLogTransactPut({
437-
entry: {
438-
module: Modules.STRIPE,
439-
actor: eventId,
440-
target: `Link ${paymentLinkId} | Invoice ${unmarshalledEntry.invoiceId}`,
441-
message:
442-
"Disabled Stripe payment link as payment was made in full.",
443-
},
444-
});
445-
const dynamoCommand = new TransactWriteItemsCommand({
446-
TransactItems: [
447-
...(logStatement ? [logStatement] : []),
446+
if (unmarshalledEntry.userId.includes("@")) {
447+
request.log.info(
448+
`Sending email to ${unmarshalledEntry.userId}...`,
449+
);
450+
const sqsPayload: SQSPayload<AvailableSQSFunctions.EmailNotifications> =
448451
{
449-
Update: {
450-
TableName: genericConfig.StripeLinksDynamoTableName,
451-
Key: {
452-
userId: { S: unmarshalledEntry.userId },
453-
linkId: { S: paymentLinkId },
454-
},
455-
UpdateExpression: "SET active = :new_val",
456-
ConditionExpression: "active = :old_val",
457-
ExpressionAttributeValues: {
458-
":new_val": { BOOL: false },
459-
":old_val": { BOOL: true },
452+
function: AvailableSQSFunctions.EmailNotifications,
453+
metadata: {
454+
initiator: eventId,
455+
reqId: request.id,
456+
},
457+
payload: {
458+
to: [unmarshalledEntry.userId],
459+
subject: `Payment Recieved for Invoice ${unmarshalledEntry.invoiceId}`,
460+
content: `
461+
ACM @ UIUC has received ${paidInFull ? "full" : "partial"} payment for Invoice ${unmarshalledEntry.invoiceId} (${withCurrency} paid by ${name}, ${email}).
462+
463+
This invoice should now be considered settled.
464+
465+
Please contact Officer Board with any questions.`,
466+
callToActionButton: {
467+
name: "View Your Stripe Links",
468+
url: `${fastify.environmentConfig.UserFacingUrl}/stripe`,
460469
},
461470
},
462-
},
463-
],
464-
});
465-
if (unmarshalledEntry.productId) {
466-
request.log.debug(
467-
`Deactivating Stripe product ${unmarshalledEntry.productId}`,
471+
};
472+
if (!fastify.sqsClient) {
473+
fastify.sqsClient = new SQSClient({
474+
region: genericConfig.AwsRegion,
475+
});
476+
}
477+
const result = await fastify.sqsClient.send(
478+
new SendMessageCommand({
479+
QueueUrl: fastify.environmentConfig.SqsQueueUrl,
480+
MessageBody: JSON.stringify(sqsPayload),
481+
}),
468482
);
469-
await deactivateStripeProduct({
483+
queueId = result.MessageId || "";
484+
}
485+
// If full payment is done, disable the link
486+
if (paidInFull) {
487+
request.log.debug("Paid in full, disabling link.");
488+
const logStatement = buildAuditLogTransactPut({
489+
entry: {
490+
module: Modules.STRIPE,
491+
actor: eventId,
492+
target: `Link ${paymentLinkId} | Invoice ${unmarshalledEntry.invoiceId}`,
493+
message:
494+
"Disabled Stripe payment link as payment was made in full.",
495+
},
496+
});
497+
const dynamoCommand = new TransactWriteItemsCommand({
498+
TransactItems: [
499+
...(logStatement ? [logStatement] : []),
500+
{
501+
Update: {
502+
TableName: genericConfig.StripeLinksDynamoTableName,
503+
Key: {
504+
userId: { S: unmarshalledEntry.userId },
505+
linkId: { S: paymentLinkId },
506+
},
507+
UpdateExpression: "SET active = :new_val",
508+
ConditionExpression: "active = :old_val",
509+
ExpressionAttributeValues: {
510+
":new_val": { BOOL: false },
511+
":old_val": { BOOL: true },
512+
},
513+
},
514+
},
515+
],
516+
});
517+
if (unmarshalledEntry.productId) {
518+
request.log.debug(
519+
`Deactivating Stripe product ${unmarshalledEntry.productId}`,
520+
);
521+
await deactivateStripeProduct({
522+
stripeApiKey: secretApiConfig.stripe_secret_key as string,
523+
productId: unmarshalledEntry.productId,
524+
});
525+
}
526+
request.log.debug(`Deactivating Stripe link ${paymentLinkId}`);
527+
await deactivateStripeLink({
470528
stripeApiKey: secretApiConfig.stripe_secret_key as string,
471-
productId: unmarshalledEntry.productId,
529+
linkId: paymentLinkId,
472530
});
531+
await fastify.dynamoClient.send(dynamoCommand);
473532
}
474-
request.log.debug(`Deactivating Stripe link ${paymentLinkId}`);
475-
await deactivateStripeLink({
476-
stripeApiKey: secretApiConfig.stripe_secret_key as string,
477-
linkId: paymentLinkId,
478-
});
479-
await fastify.dynamoClient.send(dynamoCommand);
480533
}
534+
481535
return reply.status(200).send({
482536
handled: true,
483537
requestId: request.id,

0 commit comments

Comments
 (0)