Moved stripe listener

This commit is contained in:
Keith Irwin 2023-03-25 14:31:27 -06:00
parent d772db6436
commit a17c5cf686
Signed by: ki9
GPG Key ID: DF773B3F4A88DA86
14 changed files with 189 additions and 135 deletions

View File

@ -1,7 +1,7 @@
# Ports
SITE_PORT="8080"
API_PORT="8081"
HOOK_PORT="8082"
STRIPE_LISTENER_PORT="8082"
# Domains
SITE_DOMAIN="https://www.example.com"

2
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,2 @@
{
}

View File

@ -276,7 +276,7 @@ class Cart { constructor() {
if (self.payment_method()==='USD')
window.location = `/shop/checkout/stripe/?order=${parsedRes.id}`
else if (self.payment_method()==='XMR')
window.location = `/shop/checkout/monero/?order=${parsedRes.id}`
window.location = `/shop/order/monero/?order=${parsedRes.id}`
else {
alert(`Invalid payment method: ${self.payment_method()}`)
self.isLoading(false)

View File

@ -1,121 +0,0 @@
---
layout: layouts/base.njk
title: Checkout
---
<style>
#qr_wrapper {
max-width: 80vh;
width: 80vw;
height: 80vh;
max-height: 80vw;
padding: calc(1.5vh + 1.5vw);
background-color: #FFFFFF;
margin: auto;
}
#qr_wrapper div {
width: 100%;
height: 100%;
}
</style>
<link rel="stylesheet" href="/assets/shop.css">
<h1><img src="/assets/img/monero.svg" style="width:1em;height:1em"> Monero Checkout</h1>
<table class="tal" style="width:100%"><thead>
<th>Item</th>
<th class="tar">Price</th>
<th class="tar">Qty</th>
<th class="tar">Sub</th>
</thead><tbody data-bind="foreach:items"><tr>
<td data-bind="text:name"></td>
<td class="tar" data-bind="text:price"></td>
<td class="tar" data-bind="text:qty" style="width:2rem"></td>
<td class="tar" data-bind="text:subtotal"></td>
</tr></tbody></table>
<hr>
<div class="flex" style="align-items:flex-start">
<p id="shipping-container">
<b>📦 <span class="ul">Shipping</span></b><br>
<span data-bind="text:shipname"></span>
<br><span data-bind="text:addr1"></span>
<br><span data-bind="text:addr2"></span>
<br><span data-bind="text:city"></span>,
<span data-bind="text:state"></span>
<span data-bind="text:zip"></span>
</p>
<p id="contact-container">
<b>📇 <span class="ul">Contact</ul></b><br>
<span data-bind="text:contactname"></span>
<br><span data-bind="text:phone"></span>
<br><span data-bind="text:email"></span>
</p>
<div id="totals-container"><table><tbody>
<tr>
<td><b>Subtotal:</b></td>
<td class="tar"><b data-bind="text:subtotal"></b></td>
</tr><tr>
<td>Tax:</td>
<td class="tar" data-bind="text:tax"></td>
</tr><tr>
<td>Shipping:</td>
<td class="tar" data-bind="text:shipping"></td>
</tr><tr style="border-top:.1vh solid">
<td>Total:</td>
<td class="tar" data-bind="text:total"></td>
</tr><tr>
<td><b>Monero:</b></td>
<td class="tar"><b data-bind="text:totalxmr_pretty"></b>
</td></tr>
</tbody></table></div>
</div>
<p data-bind="hidden:isPaid">To change order details, <a href="#" onclick="cancel()">cancel it and return to your cart</a>.</p>
<hr>
<h2>Payment status</h2>
<p>Your order is for <code data-bind="text:totalxmr_pretty"></code>.
<br><code data-bind="text:submitted_pretty"></code> have been submitted to the blockchain.
<br><code data-bind="text:unlocked_pretty"></code> have been unlocked.
</p>
<p data-bind="hidden:isPaid">Your order will ship when <span data-bind="text:totalxmr_pretty"></span> have been unlocked. (Transactions unlock after receiving 10 confirmations.)</p>
<p data-bind="visible:isPaid">Your payment has been confirmed. We will ship your order as soon as possible and send an email with the tracking info.</p>
<h3 data-bind="visible:transactions">Transactions</h3>
<table data-bind="visible:transactions">
<thead>
<th class="tal">Date</th>
<th class="tal">Time</th>
<th class="tal">Amt</th>
<th class="tal">Fee</th>
<th class="tal">Confs</th>
<th class="tal">Block</th>
<th class="tal">Stat</th>
</thead>
<tbody data-bind="foreach:transactions"><tr>
<td class="tal" data-bind="text:date"></td>
<td class="tal" data-bind="text:time"></td>
<td class="tal" data-bind="text:amount"></td>
<td class="tal" data-bind="text:fee"></td>
<td class="tal" data-bind="text:confirmations"></td>
<td class="tal" data-bind="text:height"></td>
<td class="tal" style="cursor:default">
<span data-bind="visible:locked" title="LOCKED: Wait for 10 confirmations for this transaction to unlock">⏲️</span>
<span data-bind="visible:double_spend_seen" title="DOUBLE SPENT! Double-spend has been detected! This transaction is invalid. Contact {{metadata.sales.email}} to resolve.">⛔</span>
<span data-bind="hidden:(locked||double_spend_seen)" title="CONFIRMED! This transaction is valid and has been confirmed by the blockchain.">✅</span>
</td>
</tr></tbody>
</table>
<p><i data-bind="text:checkingStatus"></i></p>
<hr>
<div data-bind="hidden:isPaid">
<h2>Monero payment</h2>
<p>Please send <code data-bind="text:unsubmitted"></code> XMR to this address: </p>
<pre><code data-bind="text:xmr_address"></code></pre>
<p>You can also click, tap, or scan this qr code:</p>
<a data-bind="attr:{href:xmr_uri}"><div id="qr_wrapper"><div id="xmr_qr"></div></div></a>
</div>
<script>
const API_DOMAIN = '{{env.API_DOMAIN}}'
const MONERO_CHECKOUT_POLL_SECS = '{{env.MONERO_CHECKOUT_POLL_SECS}}'
</script>
<script src="/assets/scripts/lib/qrcode.min.js" integrity="sha256-CxytvzTC2wdC92a5wqFc1DTkbS+uuQ8N2NgxGcuqoDc="></script>
<script src="/assets/scripts/lib/knockout-3.5.1.min.js" integrity="sha256-6JV7sYKlBHsHvqCkn9IrEWFLGrmsW4KG/LIln0hljnM="></script>
<script src="/assets/scripts/lib/socket-io_4-5-4.min.js" integrity="sha256-GKNqkn2sVGULGLkD+Ph3ghngLhOUblgdmz4eSZX3Q1s="></script>
<script src="/assets/scripts/checkout/monero.js" defer></script>

View File

@ -0,0 +1,121 @@
---
layout: layouts/base.njk
title: Checkout
---
<style>
#qr_wrapper {
max-width: 80vh;
width: 80vw;
height: 80vh;
max-height: 80vw;
padding: calc(1.5vh + 1.5vw);
background-color: #FFFFFF;
margin: auto;
}
#qr_wrapper div {
width: 100%;
height: 100%;
}
</style>
<link rel="stylesheet" href="/assets/shop.css">
<h1><img src="/assets/img/monero.svg" style="width:1em;height:1em"> Monero Checkout</h1>
<table class="tal" style="width:100%"><thead>
<th>Item</th>
<th class="tar">Price</th>
<th class="tar">Qty</th>
<th class="tar">Sub</th>
</thead><tbody data-bind="foreach:items"><tr>
<td data-bind="text:name"></td>
<td class="tar" data-bind="text:price"></td>
<td class="tar" data-bind="text:qty" style="width:2rem"></td>
<td class="tar" data-bind="text:subtotal"></td>
</tr></tbody></table>
<hr>
<div class="flex" style="align-items:flex-start">
<p id="shipping-container">
<b>📦 <span class="ul">Shipping</span></b><br>
<span data-bind="text:shipname"></span>
<br><span data-bind="text:addr1"></span>
<br><span data-bind="text:addr2"></span>
<br><span data-bind="text:city"></span>,
<span data-bind="text:state"></span>
<span data-bind="text:zip"></span>
</p>
<p id="contact-container">
<b>📇 <span class="ul">Contact</ul></b><br>
<span data-bind="text:contactname"></span>
<br><span data-bind="text:phone"></span>
<br><span data-bind="text:email"></span>
</p>
<div id="totals-container"><table><tbody>
<tr>
<td><b>Subtotal:</b></td>
<td class="tar"><b data-bind="text:subtotal"></b></td>
</tr><tr>
<td>Tax:</td>
<td class="tar" data-bind="text:tax"></td>
</tr><tr>
<td>Shipping:</td>
<td class="tar" data-bind="text:shipping"></td>
</tr><tr style="border-top:.1vh solid">
<td>Total:</td>
<td class="tar" data-bind="text:total"></td>
</tr><tr>
<td><b>Monero:</b></td>
<td class="tar"><b data-bind="text:totalxmr_pretty"></b>
</td></tr>
</tbody></table></div>
</div>
<p data-bind="hidden:isPaid">To change order details, <a href="#" onclick="cancel()">cancel it and return to your cart</a>.</p>
<hr>
<h2>Payment status</h2>
<p>Your order is for <code data-bind="text:totalxmr_pretty"></code>.
<br><code data-bind="text:submitted_pretty"></code> have been submitted to the blockchain.
<br><code data-bind="text:unlocked_pretty"></code> have been unlocked.
</p>
<p data-bind="hidden:isPaid">Your order will ship when <span data-bind="text:totalxmr_pretty"></span> have been unlocked. (Transactions unlock after receiving 10 confirmations.)</p>
<p data-bind="visible:isPaid">Your payment has been confirmed. We will ship your order as soon as possible and send an email with the tracking info.</p>
<h3 data-bind="visible:transactions">Transactions</h3>
<table data-bind="visible:transactions">
<thead>
<th class="tal">Date</th>
<th class="tal">Time</th>
<th class="tal">Amt</th>
<th class="tal">Fee</th>
<th class="tal">Confs</th>
<th class="tal">Block</th>
<th class="tal">Stat</th>
</thead>
<tbody data-bind="foreach:transactions"><tr>
<td class="tal" data-bind="text:date"></td>
<td class="tal" data-bind="text:time"></td>
<td class="tal" data-bind="text:amount"></td>
<td class="tal" data-bind="text:fee"></td>
<td class="tal" data-bind="text:confirmations"></td>
<td class="tal" data-bind="text:height"></td>
<td class="tal" style="cursor:default">
<span data-bind="visible:locked" title="LOCKED: Wait for 10 confirmations for this transaction to unlock">⏲️</span>
<span data-bind="visible:double_spend_seen" title="DOUBLE SPENT! Double-spend has been detected! This transaction is invalid. Contact {{metadata.sales.email}} to resolve.">⛔</span>
<span data-bind="hidden:(locked||double_spend_seen)" title="CONFIRMED! This transaction is valid and has been confirmed by the blockchain.">✅</span>
</td>
</tr></tbody>
</table>
<p><i data-bind="text:checkingStatus"></i></p>
<hr>
<div data-bind="hidden:isPaid">
<h2>Monero payment</h2>
<p>Please send <code data-bind="text:unsubmitted"></code> XMR to this address: </p>
<pre><code data-bind="text:xmr_address"></code></pre>
<p>You can also click, tap, or scan this qr code:</p>
<a data-bind="attr:{href:xmr_uri}"><div id="qr_wrapper"><div id="xmr_qr"></div></div></a>
</div>
<script>
const API_DOMAIN = '{{env.API_DOMAIN}}'
const MONERO_CHECKOUT_POLL_SECS = '{{env.MONERO_CHECKOUT_POLL_SECS}}'
</script>
<script src="/assets/scripts/lib/qrcode.min.js" integrity="sha256-CxytvzTC2wdC92a5wqFc1DTkbS+uuQ8N2NgxGcuqoDc="></script>
<script src="/assets/scripts/lib/knockout-3.5.1.min.js" integrity="sha256-6JV7sYKlBHsHvqCkn9IrEWFLGrmsW4KG/LIln0hljnM="></script>
<script src="/assets/scripts/lib/socket-io_4-5-4.min.js" integrity="sha256-GKNqkn2sVGULGLkD+Ph3ghngLhOUblgdmz4eSZX3Q1s="></script>
<script src="/assets/scripts/order/monero.js" defer></script>

View File

@ -64,14 +64,14 @@ services:
- "./api:/app/api:ro"
- "./lib:/app/lib:ro"
stripe-hook:
stripe-listener:
restart: unless-stopped
build: .
command: stripe-webhook
container_name: my_stripe-webhook
command: stripe-listener
container_name: my_stripe-listener
environment:
- STRIPE_SEC=${STRIPE_SEC}
- HOOK_PORT=${HOOK_PORT}
- STRIPE_LISTENER_PORT=${STRIPE_LISTENER_PORT}
- SALES_EMAIL=${SALES_EMAIL}
- MAIL_SERVER=${MAIL_SERVER}
- MAIL_USER=${MAIL_USER}
@ -81,7 +81,8 @@ services:
- "./_src:/app/_src"
- "./orders:/app/orders"
- "./sold:/app/sold"
- "./stripe-webhook:/app/stripe-webhook:ro"
- "./listeners:/app/listeners:ro"
- "./hooks:/app/hooks:ro"
- "./lib:/app/lib:ro"
moneropay:

51
listeners/monero.js Normal file
View File

@ -0,0 +1,51 @@
'use strict'
require('dotenv').config()
const fs = require('fs').promises
const express = require('express')
const app = express()
const ORDERS_DIR = `${__dirname}/../orders`
app.listen(process.env.MONERO_LISTENER_PORT)
.post('/', express.json(), async (req) => {
if (req.body.data.object.object==='charge') {
// Check if paid
if (!req.body.data.object.paid)
return console.log(`[${req.body.id}] Charge unpaid!`)
// Get order ID
const orderId = req.body.data.object.metadata.id
if (orderId == null)
return console.error(`[${req.body.id}] Charge has no metadata.id!`)
else
console.log(`[${req.body.id}] Charge paid for order ${orderId}`)
// Get order file
const orderFile = `${ORDERS_DIR}/${orderId}.json`
let order; try {
order = await JSON.parse(await fs.readFile(orderFile))
} catch (err) {
return console.error(`[${req.body.id}] Failed to retrieve order from ${orderFile}:\n${err}`)
}
// Save paidDate to order
try { order.paidDate = new Date()
fs.writeFile(orderFile, JSON.stringify(order,null,2))
} catch (err) {
console.error(`[${req.body.id}] Failed to write paidDate to ${orderFile}:\n${err}`)
}
// Email customer
require('../hooks/email-customer')(order)
// Ntfy sales team
require('../hooks/ntfy-sales')(order)
// Email sales team
require('../hooks/email-sales')(order)
// Remove single products from store
require('./remove-sid')(order)
}
})

View File

@ -3,14 +3,13 @@ require('dotenv').config()
const fs = require('fs').promises
const express = require('express')
const app = express()
const ORDERS_DIR = `${__dirname}/../orders`
// Start webhook forwarder so we don't need a public endpoint
require('../lib/run.js')(`stripe listen --api-key '${process.env.STRIPE_RES}' --forward-to 'http://localhost:${process.env.HOOK_PORT}/' --format 'JSON'`)
// Receive that webhook
app.listen(process.env.HOOK_PORT)
app.listen(process.env.STRIPE_LISTENER_PORT)
app.post('/', express.json(), async (req) => {
if (req.body.data.object.object==='charge') {
@ -42,16 +41,16 @@ app.post('/', express.json(), async (req) => {
}
// Email customer
require('./email-customer')(order)
require('../hooks/email-customer')(order)
// Ntfy sales team
require('./ntfy-sales')(order)
require('../hooks/ntfy-sales')(order)
// Email sales team
require('./email-sales')(order)
require('../hooks/email-sales')(order)
// Remove single products from store
require('./remove-sid')(order)
require('../hooks/remove-sid')(order)
}

View File

@ -11,7 +11,8 @@
"serve": "npx @11ty/eleventy --quiet --serve",
"json": "npx @11ty/eleventy --to=json",
"api": "node api/index.js",
"stripe-webhook": "node stripe-webhook/index.js"
"stripe-listener": "node listeners/stripe.js",
"monero-listener": "node listeners/monero.js"
},
"author": {
"name": "Keith Irwin",