From 9c32efd30d0f77611285ccd3eeadef0453d8ea1b Mon Sep 17 00:00:00 2001 From: Joe Furfaro Date: Wed, 24 Jan 2024 23:25:57 -0800 Subject: [PATCH 1/2] handle full refunds via API --- lib/services/orders.js | 113 ++++++++++++++++++++++++++-------------- lib/services/tickets.js | 8 ++- 2 files changed, 79 insertions(+), 42 deletions(-) diff --git a/lib/services/orders.js b/lib/services/orders.js index 7eecec0..a2f853a 100644 --- a/lib/services/orders.js +++ b/lib/services/orders.js @@ -441,51 +441,84 @@ module.exports = { return order; }, - // Full refunds only - refundOrder() { - throw new OrdersServiceError('Not yet implemented', 'NOT_IMPLEMENTED'); - // const order = await run(r.table('orders').get(id)); + // Full order refund + async refundOrder(id) { + let order; + try { + [order] = await sql` + SELECT + o.status AS order_status, + t.id AS transaction_id, + t.type AS transaction_type, + t.processor_transaction_id, + processor, + t.parent_transaction_id + FROM orders AS o + LEFT JOIN transactions AS t + ON o.id = t.order_id + WHERE + o.id = ${id}; + `; + } catch(e) { + throw new OrdersServiceError('Could not query orders', 'UNKNOWN', e); + } - // if(!order) throw new OrdersServiceError('Order not found', 'NOT_FOUND'); + if (!order) throw new OrdersServiceError('Order not found', 'NOT_FOUND'); + if (order.orderStatus !== 'complete') throw new OrdersServiceError(`Cannot refund order with status: ${order.orderStatus}`, 'REFUND_NOT_ALLOWED'); - // let refundResponse, orderStatus; - // try { - // const {status: processorStatus} = await gateway.order.find(order.braintreeTransactionId); - - // if(['settled', 'settling'].includes(processorStatus)) { - // orderStatus = 'refunded'; - // refundResponse = await gateway.order.refund(order.braintreeTransactionId); - // } else { - // orderStatus = 'voided'; - // refundResponse = await gateway.order.void(order.braintreeTransactionId); - // } - // } catch(e) { - // throw new OrdersServiceError('Order not refunded', 'BRAINTREE_ERROR', e); - // } + const newTransaction = { + id: uuidV4(), + orderId: id, + processor: order.processor, + parentTransactionId: order.transactionId + }; - // // Sometimes the call is successful but the refund is not - // if(!refundResponse.success) throw new OrdersServiceError('Order not refunded', 'BRAINTREE_ERROR', refundResponse); + if(order.processor === 'braintree') { + let processorResponse; + try { + const {status: processorStatus} = await gateway.transaction.find(order.processorTransactionId); + + if(['settled', 'settling'].includes(processorStatus)) { + processorResponse = await gateway.transaction.refund(order.processorTransactionId); + newTransaction.type = 'refund'; + } else { + processorResponse = await gateway.transaction.void(order.processorTransactionId); + newTransaction.type = 'void'; + } + } catch(e) { + throw new OrdersServiceError('Order not refunded', 'BRAINTREE_ERROR', e); + } - // // Mark the order as refunded in our system, disable the guests and tickets - // try { - // // Synchronize this - // const updated = r.now(); - // await Promise.all([ - // run(r.table('orders').get(id).update({status: orderStatus, updatedBy: username, updated})), - // run(r.table('guests').filter({orderId: order.id}).update({status: 'archived', updatedBy: username, updated})), - // run(r.table('tickets') - // .getAll( - // r.args(r.table('guests').filter({orderId: order.id})('id').coerceTo('array')), - // {index: 'guestId'} - // ) - // .update({status: 'disabled', updatedBy: username, updated})) - // ]); - // } catch(e) { - // console.error(e); - // throw new OrdersServiceError('Order voiding failed', 'UNKNOWN'); - // } + // Sometimes the call is successful but the refund is not + if(!processorResponse.success) throw new OrdersServiceError('Order not refunded', 'BRAINTREE_ERROR', processorResponse); + + newTransaction.processorTransactionId = processorResponse.transaction.id; + newTransaction.processorCreatedAt = processorResponse.transaction.createdAt; + newTransaction.amount = Number(processorResponse.transaction.amount); + } else { + throw new OrdersServiceError('Order not refunded', 'INVALID_PROCESSOR'); + } - // return refundResponse; + // Mark the order as canceled in our system, archive the guests + try { + await Promise.all([ + sql` + INSERT INTO transactions ${sql(newTransaction)} + `, + sql` + UPDATE orders + SET status = 'canceled' + WHERE id = ${id} + `, + sql` + UPDATE guests + SET status = 'archived', updated = now() + WHERE order_id = ${id} + ` + ]); + } catch(e) { + throw new OrdersServiceError('Order voiding failed', 'UNKNOWN', e); + } }, transferOrderTickets() { diff --git a/lib/services/tickets.js b/lib/services/tickets.js index 85895f2..f068247 100644 --- a/lib/services/tickets.js +++ b/lib/services/tickets.js @@ -35,9 +35,11 @@ module.exports = { SELECT g.id, g.admission_tier, + e.id as event_id, e.name as event_name, e.date as event_date, - g.ticket_seed + g.ticket_seed, + g.status FROM guests as g LEFT JOIN events as e ON e.id = g.event_id @@ -57,8 +59,10 @@ module.exports = { tickets.push({ id: guest.id, admissionTier: guest.admissionTier, + eventId: guest.eventId, eventName: guest.eventName, - eventDate: guest.eventDate + eventDate: guest.eventDate, + status: guest.status // qrCode }); } From 8cc34760bf46b70c59dcc7b69f380ac3cbfa5e56 Mon Sep 17 00:00:00 2001 From: Joe Furfaro Date: Wed, 24 Jan 2024 23:41:26 -0800 Subject: [PATCH 2/2] add new role and lower permissions for some write operations --- lib/routes/orders.js | 2 +- lib/routes/promos.js | 4 ++-- lib/services/auth.js | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/routes/orders.js b/lib/routes/orders.js index aad9721..c76a334 100644 --- a/lib/routes/orders.js +++ b/lib/routes/orders.js @@ -119,7 +119,7 @@ ordersRouter }); ordersRouter - .post('/:id/transfers', authorizeUser, requiresPermission('admin'), async ctx => { + .post('/:id/transfers', authorizeUser, requiresPermission('write'), async ctx => { if(!ctx.request.body) ctx.throw(400); try { diff --git a/lib/routes/promos.js b/lib/routes/promos.js index 935732b..9c1a364 100644 --- a/lib/routes/promos.js +++ b/lib/routes/promos.js @@ -17,7 +17,7 @@ promosRouter ctx.throw(e); } }) - .post('/', authorizeUser, requiresPermission('admin'), async ctx => { + .post('/', authorizeUser, requiresPermission('write'), async ctx => { try { const promo = await createPromo({...ctx.request.body, createdBy: ctx.user.id}); @@ -59,7 +59,7 @@ promosRouter ctx.throw(e); } }) - .delete('/:id', authorizeUser, requiresPermission('admin'), async ctx => { + .delete('/:id', authorizeUser, requiresPermission('write'), async ctx => { try { const promo = await updatePromo(ctx.params.id, {updatedBy: ctx.user.id, status: 'disabled'}); diff --git a/lib/services/auth.js b/lib/services/auth.js index e4f4271..c54729c 100644 --- a/lib/services/auth.js +++ b/lib/services/auth.js @@ -215,6 +215,7 @@ module.exports = { 'root', 'god', 'admin', + 'write', 'doorman', 'read' ];