Moved stripe listener
This commit is contained in:
parent
d772db6436
commit
a17c5cf686
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
})
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue