wiki-plugin-shoppe 0.0.42 → 0.0.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -1
- package/package.json +1 -1
- package/server/server.js +67 -70
- package/server/templates/generic-recover-stripe.html +2 -2
package/CLAUDE.md
CHANGED
package/package.json
CHANGED
package/server/server.js
CHANGED
|
@@ -8,6 +8,15 @@ const multer = require('multer');
|
|
|
8
8
|
const FormData = require('form-data');
|
|
9
9
|
const AdmZip = require('adm-zip');
|
|
10
10
|
const sessionless = require('sessionless-node');
|
|
11
|
+
const { secp256k1: _secp256k1 } = require('ethereum-cryptography/secp256k1');
|
|
12
|
+
const { keccak256: _keccak256 } = require('ethereum-cryptography/keccak.js');
|
|
13
|
+
const { utf8ToBytes: _utf8ToBytes } = require('ethereum-cryptography/utils.js');
|
|
14
|
+
|
|
15
|
+
// Race-safe signing: bypasses the shared sessionless.getKeys singleton.
|
|
16
|
+
function signMessage(message, privateKey) {
|
|
17
|
+
const hash = _keccak256(_utf8ToBytes(message));
|
|
18
|
+
return _secp256k1.sign(hash, privateKey).toCompactHex();
|
|
19
|
+
}
|
|
11
20
|
|
|
12
21
|
// Stripe is used directly for Terminal (card-present) payments.
|
|
13
22
|
// Set STRIPE_SECRET_KEY in the environment. If absent, Terminal endpoints return 503.
|
|
@@ -75,10 +84,24 @@ function getSanoraUrl() {
|
|
|
75
84
|
}
|
|
76
85
|
|
|
77
86
|
function getAddieUrl() {
|
|
78
|
-
|
|
87
|
+
const sanora = getSanoraUrl();
|
|
88
|
+
try {
|
|
89
|
+
const url = new URL(sanora);
|
|
90
|
+
// Only derive from origin when sanora is a wiki proxy URL (has a path component).
|
|
91
|
+
// A bare host:port URL (e.g. http://localhost:7243) means Addie is on its own port.
|
|
92
|
+
if (url.pathname && url.pathname !== '/') {
|
|
93
|
+
return url.origin + '/plugin/allyabase/addie';
|
|
94
|
+
}
|
|
95
|
+
} catch { /* fall through */ }
|
|
79
96
|
return `http://localhost:${process.env.ADDIE_PORT || 3005}`;
|
|
80
97
|
}
|
|
81
98
|
|
|
99
|
+
// Returns the public-facing Sanora URL (via wiki proxy) for browser-visible resource URLs.
|
|
100
|
+
// Always constructed from the current request so it matches the host the browser is using.
|
|
101
|
+
function getSanoraPublicUrl(req) {
|
|
102
|
+
return `${reqProto(req)}://${req.get('host')}/plugin/allyabase/sanora`;
|
|
103
|
+
}
|
|
104
|
+
|
|
82
105
|
function getLucilleUrl() {
|
|
83
106
|
const config = loadConfig();
|
|
84
107
|
if (config.lucilleUrl) return config.lucilleUrl.replace(/\/$/, '');
|
|
@@ -111,9 +134,8 @@ async function getOrCreateAffiliateAddieUser(shopperePublicKey) {
|
|
|
111
134
|
if (affiliates[shopperePublicKey]) return affiliates[shopperePublicKey];
|
|
112
135
|
|
|
113
136
|
const addieKeys = await sessionless.generateKeys(() => {}, () => null);
|
|
114
|
-
sessionless.getKeys = () => addieKeys;
|
|
115
137
|
const timestamp = Date.now().toString();
|
|
116
|
-
const signature =
|
|
138
|
+
const signature = signMessage(timestamp + addieKeys.pubKey, addieKeys.privateKey);
|
|
117
139
|
|
|
118
140
|
const resp = await fetch(`${getAddieUrl()}/user/create`, {
|
|
119
141
|
method: 'PUT',
|
|
@@ -142,10 +164,9 @@ async function getOrCreateBuyerAddieUser(recoveryKey, productId) {
|
|
|
142
164
|
if (buyers[buyerKey]) return buyers[buyerKey];
|
|
143
165
|
|
|
144
166
|
const addieKeys = await sessionless.generateKeys(() => {}, () => null);
|
|
145
|
-
sessionless.getKeys = () => addieKeys;
|
|
146
167
|
const timestamp = Date.now().toString();
|
|
147
168
|
const message = timestamp + addieKeys.pubKey;
|
|
148
|
-
const signature =
|
|
169
|
+
const signature = signMessage(message, addieKeys.privateKey);
|
|
149
170
|
|
|
150
171
|
const resp = await fetch(`${getAddieUrl()}/user/create`, {
|
|
151
172
|
method: 'PUT',
|
|
@@ -183,10 +204,9 @@ async function getOrCreateBuyerAddieUserByPubKey(pubKey, productId) {
|
|
|
183
204
|
if (buyers[buyerKey]) return buyers[buyerKey];
|
|
184
205
|
|
|
185
206
|
const addieKeys = await sessionless.generateKeys(() => {}, () => null);
|
|
186
|
-
sessionless.getKeys = () => addieKeys;
|
|
187
207
|
const timestamp = Date.now().toString();
|
|
188
208
|
const message = timestamp + addieKeys.pubKey;
|
|
189
|
-
const signature =
|
|
209
|
+
const signature = signMessage(message, addieKeys.privateKey);
|
|
190
210
|
|
|
191
211
|
const resp = await fetch(`${getAddieUrl()}/user/create`, {
|
|
192
212
|
method: 'PUT',
|
|
@@ -208,9 +228,8 @@ async function getOrCreateBuyerAddieUserByPubKey(pubKey, productId) {
|
|
|
208
228
|
async function hasPurchasedByPubKey(tenant, pubKey, productId) {
|
|
209
229
|
const orderKey = crypto.createHash('sha256').update(pubKey + productId).digest('hex');
|
|
210
230
|
const sanoraUrl = getSanoraUrl();
|
|
211
|
-
sessionless.getKeys = () => tenant.keys;
|
|
212
231
|
const timestamp = Date.now().toString();
|
|
213
|
-
const signature =
|
|
232
|
+
const signature = signMessage(timestamp + tenant.uuid, tenant.keys.privateKey);
|
|
214
233
|
try {
|
|
215
234
|
const resp = await fetch(
|
|
216
235
|
`${sanoraUrl}/user/${tenant.uuid}/orders/${encodeURIComponent(productId)}` +
|
|
@@ -479,10 +498,9 @@ function generateEmojicode(tenants) {
|
|
|
479
498
|
|
|
480
499
|
async function addieCreateUser() {
|
|
481
500
|
const addieKeys = await sessionless.generateKeys(() => {}, () => null);
|
|
482
|
-
sessionless.getKeys = () => addieKeys;
|
|
483
501
|
const timestamp = Date.now().toString();
|
|
484
502
|
const message = timestamp + addieKeys.pubKey;
|
|
485
|
-
const signature =
|
|
503
|
+
const signature = signMessage(message, addieKeys.privateKey);
|
|
486
504
|
|
|
487
505
|
const resp = await fetch(`${getAddieUrl()}/user/create`, {
|
|
488
506
|
method: 'PUT',
|
|
@@ -501,10 +519,9 @@ async function registerTenant(name) {
|
|
|
501
519
|
|
|
502
520
|
// Create a dedicated Sanora user for this tenant
|
|
503
521
|
const keys = await sessionless.generateKeys(() => {}, () => null);
|
|
504
|
-
sessionless.getKeys = () => keys;
|
|
505
522
|
const timestamp = Date.now().toString();
|
|
506
523
|
const message = timestamp + keys.pubKey;
|
|
507
|
-
const signature =
|
|
524
|
+
const signature = signMessage(message, keys.privateKey);
|
|
508
525
|
|
|
509
526
|
const resp = await fetch(`${getSanoraUrl()}/user/create`, {
|
|
510
527
|
method: 'PUT',
|
|
@@ -605,8 +622,7 @@ async function sanoraEnsureUser(tenant) {
|
|
|
605
622
|
const { keys } = tenant;
|
|
606
623
|
const timestamp = Date.now().toString();
|
|
607
624
|
const message = timestamp + keys.pubKey;
|
|
608
|
-
|
|
609
|
-
const signature = await sessionless.sign(message);
|
|
625
|
+
const signature = signMessage(message, keys.privateKey);
|
|
610
626
|
|
|
611
627
|
const resp = await fetch(`${getSanoraUrl()}/user/create`, {
|
|
612
628
|
method: 'PUT',
|
|
@@ -656,8 +672,7 @@ async function sanoraCreateProduct(tenant, title, category, description, price,
|
|
|
656
672
|
const safePrice = price || 0;
|
|
657
673
|
const message = timestamp + uuid + title + (description || '') + safePrice;
|
|
658
674
|
|
|
659
|
-
|
|
660
|
-
const signature = await sessionless.sign(message);
|
|
675
|
+
const signature = signMessage(message, keys.privateKey);
|
|
661
676
|
|
|
662
677
|
const resp = await fetchWithRetry(
|
|
663
678
|
`${getSanoraUrl()}/user/${uuid}/product/${encodeURIComponent(title)}`,
|
|
@@ -707,9 +722,8 @@ async function sanoraCreateProductResilient(tenant, title, category, description
|
|
|
707
722
|
async function sanoraUploadArtifact(tenant, title, fileBuffer, filename, artifactType) {
|
|
708
723
|
const { uuid, keys } = tenant;
|
|
709
724
|
const timestamp = Date.now().toString();
|
|
710
|
-
sessionless.getKeys = () => keys;
|
|
711
725
|
const message = timestamp + uuid + title;
|
|
712
|
-
const signature =
|
|
726
|
+
const signature = signMessage(message, keys.privateKey);
|
|
713
727
|
|
|
714
728
|
const form = new FormData();
|
|
715
729
|
form.append('artifact', fileBuffer, { filename, contentType: getMimeType(filename) });
|
|
@@ -741,9 +755,8 @@ async function sanoraUploadArtifact(tenant, title, fileBuffer, filename, artifac
|
|
|
741
755
|
async function sanoraUploadImage(tenant, title, imageBuffer, filename) {
|
|
742
756
|
const { uuid, keys } = tenant;
|
|
743
757
|
const timestamp = Date.now().toString();
|
|
744
|
-
sessionless.getKeys = () => keys;
|
|
745
758
|
const message = timestamp + uuid + title;
|
|
746
|
-
const signature =
|
|
759
|
+
const signature = signMessage(message, keys.privateKey);
|
|
747
760
|
|
|
748
761
|
const form = new FormData();
|
|
749
762
|
form.append('image', imageBuffer, { filename, contentType: getMimeType(filename) });
|
|
@@ -778,8 +791,7 @@ async function sanoraDeleteProduct(tenant, title) {
|
|
|
778
791
|
const timestamp = Date.now().toString();
|
|
779
792
|
const message = timestamp + uuid + title;
|
|
780
793
|
|
|
781
|
-
|
|
782
|
-
const signature = await sessionless.sign(message);
|
|
794
|
+
const signature = signMessage(message, keys.privateKey);
|
|
783
795
|
|
|
784
796
|
await fetch(
|
|
785
797
|
`${getSanoraUrl()}/user/${uuid}/product/${encodeURIComponent(title)}?timestamp=${timestamp}&signature=${encodeURIComponent(signature)}`,
|
|
@@ -792,10 +804,9 @@ async function sanoraDeleteProduct(tenant, title) {
|
|
|
792
804
|
async function lucilleCreateUser(lucilleUrl) {
|
|
793
805
|
const url = lucilleUrl || getLucilleUrl();
|
|
794
806
|
const keys = await sessionless.generateKeys(() => {}, () => null);
|
|
795
|
-
sessionless.getKeys = () => keys;
|
|
796
807
|
const timestamp = Date.now().toString();
|
|
797
808
|
const message = timestamp + keys.pubKey;
|
|
798
|
-
const signature =
|
|
809
|
+
const signature = signMessage(message, keys.privateKey);
|
|
799
810
|
|
|
800
811
|
const resp = await fetch(`${url}/user/create`, {
|
|
801
812
|
method: 'PUT',
|
|
@@ -824,8 +835,7 @@ async function lucilleRegisterVideo(tenant, title, description, tags, lucilleUrl
|
|
|
824
835
|
const { lucilleKeys } = tenant;
|
|
825
836
|
if (!lucilleKeys) throw new Error('Tenant has no Lucille user — re-register to enable video uploads');
|
|
826
837
|
const timestamp = Date.now().toString();
|
|
827
|
-
|
|
828
|
-
const signature = await sessionless.sign(timestamp + lucilleKeys.pubKey);
|
|
838
|
+
const signature = signMessage(timestamp + lucilleKeys.pubKey, lucilleKeys.privateKey);
|
|
829
839
|
|
|
830
840
|
const resp = await fetch(
|
|
831
841
|
`${url}/user/${lucilleKeys.uuid}/video/${encodeURIComponent(title)}`,
|
|
@@ -846,8 +856,7 @@ async function lucilleUploadVideo(tenant, title, fileBuffer, filename, lucilleUr
|
|
|
846
856
|
const { lucilleKeys } = tenant;
|
|
847
857
|
if (!lucilleKeys) throw new Error('Tenant has no Lucille user');
|
|
848
858
|
const timestamp = Date.now().toString();
|
|
849
|
-
|
|
850
|
-
const signature = await sessionless.sign(timestamp + lucilleKeys.pubKey);
|
|
859
|
+
const signature = signMessage(timestamp + lucilleKeys.pubKey, lucilleKeys.privateKey);
|
|
851
860
|
|
|
852
861
|
const form = new FormData();
|
|
853
862
|
form.append('video', fileBuffer, { filename, contentType: getMimeType(filename) });
|
|
@@ -1506,7 +1515,7 @@ async function processArchive(zipPath, onProgress = () => {}) {
|
|
|
1506
1515
|
// PORTFOLIO PAGE GENERATION
|
|
1507
1516
|
// ============================================================
|
|
1508
1517
|
|
|
1509
|
-
async function getShoppeGoods(tenant) {
|
|
1518
|
+
async function getShoppeGoods(tenant, imageBaseUrl) {
|
|
1510
1519
|
let products = {};
|
|
1511
1520
|
try {
|
|
1512
1521
|
const resp = await fetch(`${getSanoraUrl()}/products/${tenant.uuid}`, { timeout: 15000 });
|
|
@@ -1561,7 +1570,7 @@ async function getShoppeGoods(tenant) {
|
|
|
1561
1570
|
description: product.description || '',
|
|
1562
1571
|
price: product.price || 0,
|
|
1563
1572
|
shipping: product.shipping || 0,
|
|
1564
|
-
image: product.image ? `${getSanoraUrl()}/images/${product.image}` : null,
|
|
1573
|
+
image: product.image ? `${imageBaseUrl || getSanoraUrl()}/images/${product.image}` : null,
|
|
1565
1574
|
url: resolvedUrl,
|
|
1566
1575
|
...(isPost && { category: product.category, tags: product.tags || '' }),
|
|
1567
1576
|
...(lucillePlayerUrl && { lucillePlayerUrl }),
|
|
@@ -1616,9 +1625,8 @@ async function getAppointmentSchedule(tenant, product) {
|
|
|
1616
1625
|
async function getBookedSlots(tenant, productId) {
|
|
1617
1626
|
const sanoraUrl = getSanoraUrl();
|
|
1618
1627
|
const tenantKeys = tenant.keys;
|
|
1619
|
-
sessionless.getKeys = () => tenantKeys;
|
|
1620
1628
|
const timestamp = Date.now().toString();
|
|
1621
|
-
const signature =
|
|
1629
|
+
const signature = signMessage(timestamp + tenant.uuid, tenantKeys.privateKey);
|
|
1622
1630
|
const resp = await fetch(
|
|
1623
1631
|
`${sanoraUrl}/user/${tenant.uuid}/orders/${encodeURIComponent(productId)}?timestamp=${timestamp}&signature=${encodeURIComponent(signature)}`
|
|
1624
1632
|
);
|
|
@@ -1695,9 +1703,8 @@ async function getSubscriptionStatus(tenant, productId, recoveryKey) {
|
|
|
1695
1703
|
const orderKey = crypto.createHash('sha256').update(recoveryKey + productId).digest('hex');
|
|
1696
1704
|
const sanoraUrl = getSanoraUrl();
|
|
1697
1705
|
const tenantKeys = tenant.keys;
|
|
1698
|
-
sessionless.getKeys = () => tenantKeys;
|
|
1699
1706
|
const timestamp = Date.now().toString();
|
|
1700
|
-
const signature =
|
|
1707
|
+
const signature = signMessage(timestamp + tenant.uuid, tenantKeys.privateKey);
|
|
1701
1708
|
try {
|
|
1702
1709
|
const resp = await fetch(
|
|
1703
1710
|
`${sanoraUrl}/user/${tenant.uuid}/orders/${encodeURIComponent(productId)}?timestamp=${timestamp}&signature=${encodeURIComponent(signature)}`
|
|
@@ -1748,12 +1755,11 @@ async function getAllOrders(tenant) {
|
|
|
1748
1755
|
console.warn(`[shoppe] getAllOrders: Sanora unreachable — ${err.message}`);
|
|
1749
1756
|
}
|
|
1750
1757
|
|
|
1751
|
-
sessionless.getKeys = () => tenant.keys;
|
|
1752
1758
|
|
|
1753
1759
|
const results = [];
|
|
1754
1760
|
for (const [title, product] of Object.entries(products)) {
|
|
1755
1761
|
const timestamp = Date.now().toString();
|
|
1756
|
-
const signature =
|
|
1762
|
+
const signature = signMessage(timestamp + tenant.uuid, tenant.keys.privateKey);
|
|
1757
1763
|
try {
|
|
1758
1764
|
const resp = await fetch(
|
|
1759
1765
|
`${sanoraUrl}/user/${tenant.uuid}/orders/${encodeURIComponent(product.productId)}` +
|
|
@@ -3215,7 +3221,7 @@ async function startServer(params) {
|
|
|
3215
3221
|
const product = products[title] || Object.values(products).find(p => p.title === title);
|
|
3216
3222
|
if (!product) return res.status(404).send('<h1>Product not found</h1>');
|
|
3217
3223
|
|
|
3218
|
-
const imageUrl = product.image ? `${
|
|
3224
|
+
const imageUrl = product.image ? `${sanoraUrl}/images/${product.image}` : '';
|
|
3219
3225
|
const ebookUrl = `${wikiOrigin}/plugin/shoppe/${tenant.uuid}/download/${encodeURIComponent(title)}`;
|
|
3220
3226
|
const shoppeUrl = `${wikiOrigin}/plugin/shoppe/${tenant.uuid}`;
|
|
3221
3227
|
const payees = tenant.addieKeys
|
|
@@ -3286,7 +3292,7 @@ async function startServer(params) {
|
|
|
3286
3292
|
const schedule = await getAppointmentSchedule(tenant, product);
|
|
3287
3293
|
const wikiOrigin = `${reqProto(req)}://${req.get('host')}`;
|
|
3288
3294
|
const shoppeUrl = `${wikiOrigin}/plugin/shoppe/${tenant.uuid}`;
|
|
3289
|
-
const imageUrl = product.image ? `${
|
|
3295
|
+
const imageUrl = product.image ? `${getSanoraPublicUrl(req)}/images/${product.image}` : '';
|
|
3290
3296
|
|
|
3291
3297
|
const price = product.price || 0;
|
|
3292
3298
|
const html = fillTemplate(APPOINTMENT_BOOKING_TMPL, {
|
|
@@ -3354,7 +3360,7 @@ async function startServer(params) {
|
|
|
3354
3360
|
const tierInfo = await getTierInfo(tenant, product);
|
|
3355
3361
|
const wikiOrigin = `${reqProto(req)}://${req.get('host')}`;
|
|
3356
3362
|
const shoppeUrl = `${wikiOrigin}/plugin/shoppe/${tenant.uuid}`;
|
|
3357
|
-
const imageUrl = product.image ? `${
|
|
3363
|
+
const imageUrl = product.image ? `${getSanoraPublicUrl(req)}/images/${product.image}` : '';
|
|
3358
3364
|
const benefits = tierInfo && tierInfo.benefits
|
|
3359
3365
|
? tierInfo.benefits.map(b => `<li>${escHtml(b)}</li>`).join('')
|
|
3360
3366
|
: '';
|
|
@@ -3426,10 +3432,9 @@ async function startServer(params) {
|
|
|
3426
3432
|
}
|
|
3427
3433
|
|
|
3428
3434
|
const addieKeys = { pubKey: tenant.addieKeys.pubKey, privateKey: tenant.addieKeys.privateKey };
|
|
3429
|
-
sessionless.getKeys = () => addieKeys;
|
|
3430
3435
|
const timestamp = Date.now().toString();
|
|
3431
3436
|
const message = timestamp + tenant.addieKeys.uuid;
|
|
3432
|
-
const signature =
|
|
3437
|
+
const signature = signMessage(message, addieKeys.privateKey);
|
|
3433
3438
|
|
|
3434
3439
|
const wikiOrigin = `${reqProto(req)}://${req.get('host')}`;
|
|
3435
3440
|
const returnUrl = `${wikiOrigin}/plugin/shoppe/${tenant.uuid}/payouts/return`;
|
|
@@ -3536,7 +3541,7 @@ async function startServer(params) {
|
|
|
3536
3541
|
productId: product.productId,
|
|
3537
3542
|
description: product.description || '',
|
|
3538
3543
|
price: product.price || 0,
|
|
3539
|
-
image: product.image ? `${
|
|
3544
|
+
image: product.image ? `${getSanoraPublicUrl(req)}/images/${product.image}` : null,
|
|
3540
3545
|
benefits: tierInfo ? (tierInfo.benefits || []) : [],
|
|
3541
3546
|
renewalDays: tierInfo ? (tierInfo.renewalDays || 30) : 30,
|
|
3542
3547
|
active: status.active,
|
|
@@ -3673,9 +3678,8 @@ async function startServer(params) {
|
|
|
3673
3678
|
: tenant.addieKeys ? [{ pubKey: tenant.addieKeys.pubKey, amount }] : [];
|
|
3674
3679
|
}
|
|
3675
3680
|
const buyerKeys = { pubKey: buyer.pubKey, privateKey: buyer.privateKey };
|
|
3676
|
-
sessionless.getKeys = () => buyerKeys;
|
|
3677
3681
|
const intentTimestamp = Date.now().toString();
|
|
3678
|
-
const intentSignature =
|
|
3682
|
+
const intentSignature = signMessage(intentTimestamp + buyer.uuid + amount + 'USD', buyerKeys.privateKey);
|
|
3679
3683
|
const intentResp = await fetch(`${getAddieUrl()}/user/${buyer.uuid}/processor/stripe/intent`, {
|
|
3680
3684
|
method: 'POST',
|
|
3681
3685
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -3728,9 +3732,8 @@ async function startServer(params) {
|
|
|
3728
3732
|
// Shoppere subscription: orderKey = sha256(pubKey + productId)
|
|
3729
3733
|
const orderKey = crypto.createHash('sha256').update(pubKey + productId).digest('hex');
|
|
3730
3734
|
const tenantKeys = tenant.keys;
|
|
3731
|
-
sessionless.getKeys = () => tenantKeys;
|
|
3732
3735
|
const ts = Date.now().toString();
|
|
3733
|
-
const sig =
|
|
3736
|
+
const sig = signMessage(ts + tenant.uuid, tenantKeys.privateKey);
|
|
3734
3737
|
const order = { orderKey, pubKey, paidAt: Date.now(), title, productId, renewalDays: renewalDays || 30, status: 'active' };
|
|
3735
3738
|
await fetch(`${sanoraUrlInternal}/user/${tenant.uuid}/orders`, {
|
|
3736
3739
|
method: 'PUT',
|
|
@@ -3745,9 +3748,8 @@ async function startServer(params) {
|
|
|
3745
3748
|
// Shoppere appointment: record booking with pubKey credential
|
|
3746
3749
|
const orderKey = crypto.createHash('sha256').update(pubKey + productId).digest('hex');
|
|
3747
3750
|
const tenantKeys = tenant.keys;
|
|
3748
|
-
sessionless.getKeys = () => tenantKeys;
|
|
3749
3751
|
const bookingTimestamp = Date.now().toString();
|
|
3750
|
-
const bookingSignature =
|
|
3752
|
+
const bookingSignature = signMessage(bookingTimestamp + tenant.uuid, tenantKeys.privateKey);
|
|
3751
3753
|
const order = {
|
|
3752
3754
|
orderKey,
|
|
3753
3755
|
pubKey,
|
|
@@ -3770,9 +3772,8 @@ async function startServer(params) {
|
|
|
3770
3772
|
// Shoppere digital product: record purchase with pubKey as credential
|
|
3771
3773
|
const orderKey = crypto.createHash('sha256').update(pubKey + productId).digest('hex');
|
|
3772
3774
|
const tenantKeys = tenant.keys;
|
|
3773
|
-
sessionless.getKeys = () => tenantKeys;
|
|
3774
3775
|
const ts = Date.now().toString();
|
|
3775
|
-
const sig =
|
|
3776
|
+
const sig = signMessage(ts + tenant.uuid, tenantKeys.privateKey);
|
|
3776
3777
|
const order = { orderKey, pubKey, paidAt: Date.now(), title, productId, status: 'purchased' };
|
|
3777
3778
|
await fetch(`${sanoraUrlInternal}/user/${tenant.uuid}/orders`, {
|
|
3778
3779
|
method: 'PUT',
|
|
@@ -3798,9 +3799,8 @@ async function startServer(params) {
|
|
|
3798
3799
|
// The recovery key itself is never stored; orderKey = sha256(recoveryKey + productId).
|
|
3799
3800
|
const orderKey = crypto.createHash('sha256').update(recoveryKey + productId).digest('hex');
|
|
3800
3801
|
const tenantKeys = tenant.keys;
|
|
3801
|
-
sessionless.getKeys = () => tenantKeys;
|
|
3802
3802
|
const ts = Date.now().toString();
|
|
3803
|
-
const sig =
|
|
3803
|
+
const sig = signMessage(ts + tenant.uuid, tenantKeys.privateKey);
|
|
3804
3804
|
const order = { orderKey, paidAt: Date.now(), title, productId, renewalDays: renewalDays || 30, status: 'active' };
|
|
3805
3805
|
await fetch(`${sanoraUrlInternal}/user/${tenant.uuid}/orders`, {
|
|
3806
3806
|
method: 'PUT',
|
|
@@ -3819,9 +3819,8 @@ async function startServer(params) {
|
|
|
3819
3819
|
|
|
3820
3820
|
// Record the booking in Sanora (contact info flows through the server, never direct from browser)
|
|
3821
3821
|
const tenantKeys = tenant.keys;
|
|
3822
|
-
sessionless.getKeys = () => tenantKeys;
|
|
3823
3822
|
const bookingTimestamp = Date.now().toString();
|
|
3824
|
-
const bookingSignature =
|
|
3823
|
+
const bookingSignature = signMessage(bookingTimestamp + tenant.uuid, tenantKeys.privateKey);
|
|
3825
3824
|
const order = {
|
|
3826
3825
|
productId,
|
|
3827
3826
|
title,
|
|
@@ -3851,9 +3850,8 @@ async function startServer(params) {
|
|
|
3851
3850
|
// Physical product — record order in Sanora signed by the tenant.
|
|
3852
3851
|
// The shippingAddress is collected here (post-payment) and sent once, server-side.
|
|
3853
3852
|
const tenantKeys = tenant.keys;
|
|
3854
|
-
sessionless.getKeys = () => tenantKeys;
|
|
3855
3853
|
const orderTimestamp = Date.now().toString();
|
|
3856
|
-
const orderSignature =
|
|
3854
|
+
const orderSignature = signMessage(orderTimestamp + tenant.uuid, tenantKeys.privateKey);
|
|
3857
3855
|
const order = {
|
|
3858
3856
|
productId,
|
|
3859
3857
|
title,
|
|
@@ -3972,8 +3970,7 @@ async function startServer(params) {
|
|
|
3972
3970
|
// Record order in Sanora (fire-and-forget)
|
|
3973
3971
|
if (productId && tenant.keys) {
|
|
3974
3972
|
const ts = Date.now().toString();
|
|
3975
|
-
|
|
3976
|
-
const orderSig = await sessionless.sign(ts + tenant.uuid).catch(() => null);
|
|
3973
|
+
const orderSig = signMessage(ts + tenant.uuid).catch(() => null, tenant.keys.privateKey);
|
|
3977
3974
|
if (orderSig) {
|
|
3978
3975
|
const order = {
|
|
3979
3976
|
productId,
|
|
@@ -4053,14 +4050,15 @@ async function startServer(params) {
|
|
|
4053
4050
|
if (!purchased) return res.status(403).send('<h1>No purchase found for this key</h1>');
|
|
4054
4051
|
}
|
|
4055
4052
|
|
|
4056
|
-
const
|
|
4053
|
+
const sanoraPublicUrl = getSanoraPublicUrl(req);
|
|
4054
|
+
const imageUrl = product.image ? `${sanoraPublicUrl}/images/${product.image}` : '';
|
|
4057
4055
|
|
|
4058
4056
|
// Map artifact UUIDs to download paths by extension
|
|
4059
4057
|
let epubPath = '', pdfPath = '', mobiPath = '';
|
|
4060
4058
|
(product.artifacts || []).forEach(artifact => {
|
|
4061
|
-
if (artifact.includes('epub')) epubPath = `${
|
|
4062
|
-
if (artifact.includes('pdf')) pdfPath = `${
|
|
4063
|
-
if (artifact.includes('mobi')) mobiPath = `${
|
|
4059
|
+
if (artifact.includes('epub')) epubPath = `${sanoraPublicUrl}/artifacts/${artifact}`;
|
|
4060
|
+
if (artifact.includes('pdf')) pdfPath = `${sanoraPublicUrl}/artifacts/${artifact}`;
|
|
4061
|
+
if (artifact.includes('mobi')) mobiPath = `${sanoraPublicUrl}/artifacts/${artifact}`;
|
|
4064
4062
|
});
|
|
4065
4063
|
|
|
4066
4064
|
const html = fillTemplate(EBOOK_DOWNLOAD_TMPL, {
|
|
@@ -4106,7 +4104,7 @@ async function startServer(params) {
|
|
|
4106
4104
|
const fm = parseFrontMatter(mdContent);
|
|
4107
4105
|
const postTitle = fm.title || title;
|
|
4108
4106
|
const postDate = fm.date || '';
|
|
4109
|
-
const imageUrl = product.image ? `${
|
|
4107
|
+
const imageUrl = product.image ? `${getSanoraPublicUrl(req)}/images/${product.image}` : null;
|
|
4110
4108
|
|
|
4111
4109
|
res.set('Content-Type', 'text/html');
|
|
4112
4110
|
res.send(generatePostHTML(tenant, postTitle, postDate, imageUrl, fm.body || mdContent));
|
|
@@ -4135,8 +4133,7 @@ async function startServer(params) {
|
|
|
4135
4133
|
const { uuid: lucilleUuid, pubKey, privateKey } = tenant.lucilleKeys;
|
|
4136
4134
|
|
|
4137
4135
|
const timestamp = Date.now().toString();
|
|
4138
|
-
|
|
4139
|
-
const signature = await sessionless.sign(timestamp + pubKey);
|
|
4136
|
+
const signature = signMessage(timestamp + pubKey, privateKey);
|
|
4140
4137
|
|
|
4141
4138
|
const uploadUrl = `${lucilleBase}/user/${lucilleUuid}/video/${encodeURIComponent(title)}/file`;
|
|
4142
4139
|
res.json({ uploadUrl, timestamp, signature });
|
|
@@ -4182,7 +4179,7 @@ async function startServer(params) {
|
|
|
4182
4179
|
productId: product.productId,
|
|
4183
4180
|
title: product.title,
|
|
4184
4181
|
category: product.category,
|
|
4185
|
-
image: product.image ? `${
|
|
4182
|
+
image: product.image ? `${getSanoraPublicUrl(req)}/images/${product.image}` : null,
|
|
4186
4183
|
price: product.price,
|
|
4187
4184
|
paidAt: match.paidAt,
|
|
4188
4185
|
status: match.status,
|
|
@@ -4208,7 +4205,7 @@ async function startServer(params) {
|
|
|
4208
4205
|
try {
|
|
4209
4206
|
const tenant = getTenantByIdentifier(req.params.identifier);
|
|
4210
4207
|
if (!tenant) return res.status(404).json({ error: 'Shoppe not found' });
|
|
4211
|
-
const goods = await getShoppeGoods(tenant);
|
|
4208
|
+
const goods = await getShoppeGoods(tenant, getSanoraPublicUrl(req));
|
|
4212
4209
|
const cat = req.query.category;
|
|
4213
4210
|
res.json({ success: true, goods: (cat && goods[cat]) ? goods[cat] : goods });
|
|
4214
4211
|
} catch (err) {
|
|
@@ -4230,7 +4227,7 @@ async function startServer(params) {
|
|
|
4230
4227
|
const tracks = [];
|
|
4231
4228
|
for (const [key, product] of Object.entries(products)) {
|
|
4232
4229
|
if (product.category !== 'music') continue;
|
|
4233
|
-
const cover = product.image ? `${
|
|
4230
|
+
const cover = product.image ? `${getSanoraPublicUrl(req)}/images/${product.image}` : null;
|
|
4234
4231
|
const artifacts = product.artifacts || [];
|
|
4235
4232
|
if (artifacts.length > 1) {
|
|
4236
4233
|
albums.push({
|
|
@@ -4264,7 +4261,7 @@ async function startServer(params) {
|
|
|
4264
4261
|
try {
|
|
4265
4262
|
const tenant = getTenantByIdentifier(req.params.identifier);
|
|
4266
4263
|
if (!tenant) return res.status(404).send('<h1>Shoppe not found</h1>');
|
|
4267
|
-
const goods = await getShoppeGoods(tenant);
|
|
4264
|
+
const goods = await getShoppeGoods(tenant, getSanoraPublicUrl(req));
|
|
4268
4265
|
|
|
4269
4266
|
// Check if the request carries a valid owner signature — if so, embed auth
|
|
4270
4267
|
// params in the page so the upload button can authenticate with upload-info.
|
|
@@ -193,7 +193,7 @@
|
|
|
193
193
|
return p;
|
|
194
194
|
}).filter(p => p.pubKey);
|
|
195
195
|
}
|
|
196
|
-
|
|
196
|
+
var URL_PAYEES = parseUrlPayees();
|
|
197
197
|
</script>
|
|
198
198
|
|
|
199
199
|
<!-- ── Shoppere (pubKey) path ────────────────────────────────────────────── -->
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
const _buyerPubKey = '{{buyerPubKey}}' || new URLSearchParams(window.location.search).get('pubKey') || '';
|
|
203
203
|
const _buyerTimestamp = '{{buyerTimestamp}}' || new URLSearchParams(window.location.search).get('timestamp') || '';
|
|
204
204
|
const _buyerSignature = '{{buyerSignature}}' || new URLSearchParams(window.location.search).get('signature') || '';
|
|
205
|
-
|
|
205
|
+
var IS_SHOPPERE = !!(_buyerPubKey && _buyerTimestamp && _buyerSignature);
|
|
206
206
|
// Affiliate (NFC proximity charge / referral link) param — present when buyer arrived via a referred link
|
|
207
207
|
const _affiliatePubKey = new URLSearchParams(window.location.search).get('affiliatePubKey') || '';
|
|
208
208
|
const IS_AFFILIATE_CHARGE = !!_affiliatePubKey;
|