Skip to content

Commit 23019ac

Browse files
Enable refunds and fulfillment of purchases (#532)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Admin endpoints to refund orders and to fulfill specific line items. * Store purchases are queued for asynchronous processing; queue message ID returned. * New background handler consumes queued store purchases and triggers post-purchase flows (including emails). * **Improvements** * Sales emails adapt to identity verification (simpler email when verified; attachment used otherwise). * Webhook/payment flows include richer context and support idempotent refunds and per-line-item fulfillment. * **Tests** * Added unit tests for fulfillment flows, validation, and error cases. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 9ad397e commit 23019ac

File tree

13 files changed

+854
-45
lines changed

13 files changed

+854
-45
lines changed

src/api/functions/ses.ts

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,22 @@ ${encodedAttachment}
146146
export function generateSalesEmail(
147147
payload: SQSPayload<AvailableSQSFunctions.SendSaleEmail>["payload"],
148148
senderEmail: string,
149-
imageBuffer: ArrayBufferLike,
149+
imageBuffer?: ArrayBufferLike,
150150
): SendRawEmailCommand {
151-
const encodedImage = encode(imageBuffer as ArrayBuffer);
152151
const boundary = "----BoundaryForEmail";
152+
const subject = `Your purchase has been confirmed!`;
153153

154-
const subject = `Your ${payload.type === "merch" ? "order" : "ticket"} has been confirmed!`;
154+
// Format items list
155+
const itemsList = payload.itemsPurchased
156+
.map((item) => {
157+
const variant = item.variantName ? ` (${item.variantName})` : "";
158+
return `${item.quantity}x ${item.itemName}${variant}`;
159+
})
160+
.join(", ");
161+
162+
const verificationInstructions = payload.isVerifiedIdentity
163+
? "Show your Illinois iCard or Illinois App QR code to our staff to verify your purchase at pickup."
164+
: "Show the attached QR code to our staff to verify your purchase at pickup.";
155165

156166
const emailTemplate = `
157167
<!doctype html>
@@ -218,8 +228,8 @@ export function generateSalesEmail(
218228
<div class="wrap">
219229
<h2 style="text-align: center;">${subject}</h2>
220230
<p>
221-
Thank you for your purchase of ${payload.quantity} ${payload.itemName} ${payload.size ? `(size ${payload.size})` : ""}.
222-
${payload.type === "merch" ? "When picking up your order" : "When attending the event"}, show the attached QR code to our staff to verify your purchase.
231+
Thank you for your purchase of ${itemsList}.
232+
${verificationInstructions}
223233
</p>
224234
${payload.customText ? `<p>${payload.customText}</p>` : ""}
225235
<p>
@@ -231,18 +241,40 @@ export function generateSalesEmail(
231241
</div>
232242
<div class="footer">
233243
<p>
234-
<a href="https://acm.illinois.edu">ACM @ UIUC Homepage</a>
244+
<a href="https://www.acm.illinois.edu?utm_source=store_email">ACM @ UIUC Homepage</a>
235245
<a href="mailto:[email protected]">Email ACM @ UIUC</a>
236246
</p>
237247
</div>
238248
</body>
239249
</html>
240250
`;
241251

242-
const rawEmail = `
252+
// Build email based on whether we need to attach a QR code
253+
let rawEmail: string;
254+
255+
if (payload.isVerifiedIdentity) {
256+
// Simple email without attachment
257+
rawEmail = `
258+
MIME-Version: 1.0
259+
Content-Type: text/html; charset="UTF-8"
260+
From: ACM @ UIUC Store <${senderEmail}>
261+
To: ${payload.email}
262+
Subject: Your ACM @ UIUC Purchase
263+
264+
${emailTemplate}`.trim();
265+
} else {
266+
// Email with QR code attachment
267+
if (!imageBuffer) {
268+
throw new Error(
269+
"imageBuffer is required when isVerifiedIdentity is false",
270+
);
271+
}
272+
const encodedImage = encode(imageBuffer as ArrayBuffer);
273+
274+
rawEmail = `
243275
MIME-Version: 1.0
244276
Content-Type: multipart/mixed; boundary="${boundary}"
245-
From: ACM @ UIUC <${senderEmail}>
277+
From: ACM @ UIUC Store <${senderEmail}>
246278
To: ${payload.email}
247279
Subject: Your ACM @ UIUC Purchase
248280
@@ -254,10 +286,11 @@ ${emailTemplate}
254286
--${boundary}
255287
Content-Type: image/png
256288
Content-Transfer-Encoding: base64
257-
Content-Disposition: attachment; filename="${payload.itemName}.png"
289+
Content-Disposition: attachment; filename="purchase-qr.png"
258290
259291
${encodedImage}
260292
--${boundary}--`.trim();
293+
}
261294

262295
return new SendRawEmailCommand({
263296
RawMessage: {

0 commit comments

Comments
 (0)