diff --git a/src/components/PayNow.svelte b/src/components/PayNow.svelte index aec83d3..8d01f1a 100644 --- a/src/components/PayNow.svelte +++ b/src/components/PayNow.svelte @@ -14,7 +14,7 @@ function zap() { let z = new NDKZap({ndk:$ndk, zappedEvent:rocket, zappedUser: rocket.author}) - z.createZapRequest(1000, `Purchase of ${product.getMatchingTags("name")[0][1]} from ${rocket.dTag}`, [["e", product.id]]).then(invoice=>{ + z.createZapRequest(1000, `Purchase of ${product.getMatchingTags("name")[0][1]} from ${rocket.dTag}`, [["product", product.id]]).then(invoice=>{ if (invoice) { requestProvider().then((webln)=>{ webln.sendPayment(invoice).then((response)=>{ diff --git a/src/components/ProductPurchases.svelte b/src/components/ProductPurchases.svelte index 6e0c39e..5289880 100644 --- a/src/components/ProductPurchases.svelte +++ b/src/components/ProductPurchases.svelte @@ -1,7 +1,7 @@ @@ -105,29 +64,31 @@ - {#each $newZaps as [id, zapReceipt], _ (id)} - {console.log(getZapRequest(zapReceipt)?.rawEvent())}} class="bg-accent"> + {#each $purchases as [id, purchase], _ (id)} + { + console.log(purchase.ZapReceipt.rawEvent()); + }} + class="bg-accent" + > - {getZapRequest(zapReceipt)?.author.pubkey} - - - {getZapAmount(getZapRequest(zapReceipt)) / 1000}{purchase.Amount / 1000} + {unixToRelativeTime(purchase.ZapReceipt.created_at * 1000)} - {unixToRelativeTime(zapReceipt.created_at*1000)} {/each} diff --git a/src/lib/event_helpers/rockets.ts b/src/lib/event_helpers/rockets.ts index d6b7b1a..4bb874f 100644 --- a/src/lib/event_helpers/rockets.ts +++ b/src/lib/event_helpers/rockets.ts @@ -1,98 +1,129 @@ -import { NDKEvent, type NDKTag } from "@nostr-dev-kit/ndk"; -import type NDKSvelte from "@nostr-dev-kit/ndk-svelte"; - -export function getZapData(ndk:NDKSvelte, zap: NDKEvent, rocket:NDKEvent) { - let productPrice = 0; - let zapAmount = 0; - let productID: string | undefined = undefined; - let buyerPubkey: string | undefined = undefined; - let zapRequest: NDKEvent | undefined = undefined; - - let desc = zap.getMatchingTags('description'); - if (desc && desc.length == 1 && rocket) { - zapRequest = new NDKEvent(ndk, JSON.parse(desc[0][1])); - let zapRequestETags = zapRequest.getMatchingTags('e'); - - if (zapRequestETags && zapRequestETags.length > 0) { - for (let productIDfromZapRequest of zapRequestETags) { - if (productIDfromZapRequest.length > 1) { - let productsInRocket = getMapOfProductsFromRocket(rocket); - if (productsInRocket.size > 0) { - productID = productIDfromZapRequest[1]; - if (productID.length == 64) { - let productDataFromRocket = productsInRocket.get(productID); - if (productDataFromRocket) { - productPrice = productDataFromRocket.Price; - } - } - } - } - } - } - let amount = zapRequest.getMatchingTags('amount'); - if (amount && amount.length == 1) { - if (amount[0].length == 2) { - zapAmount = parseInt(amount[0][1], 10); - } - } - buyerPubkey = zapRequest.author.pubkey; - } - let success = false; - if (zapRequest && productID && buyerPubkey && productPrice && zapAmount) { - if (zapAmount >= productPrice && productID.length == 64 && buyerPubkey.length == 64) { - success = true; - return { - productPrice: productPrice, - zapAmount: zapAmount, - productID: productID, - buyerPubkey: buyerPubkey, - zapReceipt: zap.id - }; - } - } - if (!success) { - console.log('invalid product payment zap found:', zapRequest?.rawEvent()); - } -} +import { NDKEvent, type NDKTag } from '@nostr-dev-kit/ndk'; export class RocketProduct { - ID: string; - Price: number; - ValidAfter: number; //unix time - MaxPurchases: number; - Purchases: Map; - - constructor(tag: NDKTag) { - this.Purchases = new Map(); - this.ID = tag[1].split(':')[0]; - this.Price = parseInt(tag[1].split(':')[1], 10); - this.ValidAfter = parseInt(tag[1].split(':')[2], 10); - this.MaxPurchases = parseInt(tag[1].split(':')[3], 10); - let purchases = JSON.parse(tag[3]); - for (let p of purchases) { - let payment = new ProductPayment(p); - this.Purchases.set(payment.ZapID, payment); - } - } + ID: string; + Price: number; + ValidAfter: number; //unix time + MaxPurchases: number; + Purchases: Map; + + constructor(tag: NDKTag) { + this.Purchases = new Map(); + this.ID = tag[1].split(':')[0]; + this.Price = parseInt(tag[1].split(':')[1], 10); + this.ValidAfter = parseInt(tag[1].split(':')[2], 10); + this.MaxPurchases = parseInt(tag[1].split(':')[3], 10); + let purchases = JSON.parse(tag[3]); + for (let p of purchases) { + let payment = new ProductPayment(p); + this.Purchases.set(payment.ZapID, payment); + } + } } +//ProductPayment takes the payment string from a product tag on a rocket event export class ProductPayment { - ZapID: string; - BuyerPubkey: string; - WitnessedAt: number; - constructor(purchase: string) { - this.ZapID = purchase.split(':')[0]; - this.BuyerPubkey = purchase.split(':')[1]; - this.WitnessedAt = parseInt(purchase.split(':')[2], 10); - } + ZapID: string; + BuyerPubkey: string; + WitnessedAt: number; + constructor(purchase: string) { + this.ZapID = purchase.split(':')[0]; + this.BuyerPubkey = purchase.split(':')[1]; + this.WitnessedAt = parseInt(purchase.split(':')[2], 10); + } } export function getMapOfProductsFromRocket(rocket: NDKEvent): Map { - let productIDs = new Map(); - for (let product of rocket.getMatchingTags('product')) { - if (product.length > 1 && product[1].split(':') && product[1].split(':').length > 0) { - productIDs.set(product[1].split(':')[0], new RocketProduct(product)); - } - } - return productIDs; -} \ No newline at end of file + let productIDs = new Map(); + for (let product of rocket.getMatchingTags('product')) { + if (product.length > 1 && product[1].split(':') && product[1].split(':').length > 0) { + productIDs.set(product[1].split(':')[0], new RocketProduct(product)); + } + } + return productIDs; +} + +export class ZapPurchase { + Amount: number; + ProductID: string; + BuyerPubkey: string; + ZapReceipt: NDKEvent; + ZapRequest(): NDKEvent | undefined { + return getZapRequest(this.ZapReceipt); + } + IncludedInRocketState(rocket: NDKEvent): boolean { + let thisProduct = this.ProductFromRocket(rocket); + if (thisProduct) { + return thisProduct.Purchases.get(this.ZapReceipt.id) ? true : false; + } else { + return false; + } + } + ProductFromRocket(rocket: NDKEvent): RocketProduct | undefined { + let productsInRocket = getMapOfProductsFromRocket(rocket); + return productsInRocket.get(this.ProductID); + } + ValidAmount(rocket: NDKEvent): boolean { + if (this.Amount < 1) { + return false; + } + if (this.IncludedInRocketState(rocket)) { + return true; + } + let product = this.ProductFromRocket(rocket); + if (product && this.Amount >= product.Price) { + return true; + } + return false; + } + Valid(rocket: NDKEvent): boolean { + //todo: validate zapper pubkey is from a LSP specified in rocket + let valid = true; + if (!this.ValidAmount(rocket)) { + valid = false; + } + if (!this.ProductID) { + valid = false; + } + if (this.ProductID && this.ProductID.length != 64) { + valid = false; + } + if (this.BuyerPubkey.length != 64) { + valid = false; + } + return valid; + } + constructor(zapReceipt: NDKEvent) { + this.ZapReceipt = zapReceipt; + this.Amount = getZapAmount(this.ZapRequest()); + let zapRequest = this.ZapRequest(); + if (zapRequest) { + this.BuyerPubkey = zapRequest.pubkey; + let products = zapRequest.getMatchingTags('product'); + if (products.length == 1 && products[0] && products[0][1] && products[0][1].length == 64) { + this.ProductID = products[0][1]; + } + } + } +} + +function getZapRequest(zapReceipt: NDKEvent): NDKEvent | undefined { + let zapRequestEvent: NDKEvent | undefined = undefined; + let zapRequest = zapReceipt.getMatchingTags('description'); + if (zapRequest.length == 1) { + let zapRequestJSON = JSON.parse(zapRequest[0][1]); + if (zapRequestJSON) { + zapRequestEvent = new NDKEvent(zapReceipt.ndk, zapRequestJSON); + } + } + return zapRequestEvent; +} + +function getZapAmount(zapRequest?: NDKEvent): number { + let amount = 0; + let amountTag = zapRequest?.getMatchingTags('amount'); + if (amountTag?.length == 1) { + amount = parseInt(amountTag[0][1], 10); + } + return amount; +} diff --git a/src/routes/rockets/[ignition]/+page.svelte b/src/routes/rockets/[ignition]/+page.svelte index d13d47f..bfbd215 100644 --- a/src/routes/rockets/[ignition]/+page.svelte +++ b/src/routes/rockets/[ignition]/+page.svelte @@ -74,13 +74,7 @@ } } - class ZapPurchase { - Amount: number; - ProductID: string; - Buyer: string; - ZapReceiptID: string; - constructor(zapReceipt: NDKEvent) {} - } + //todo: check that this zap is not already included in the payment JSON for the product //todo: list purchases on the rocket page (from product tags, as well as zap receipts that aren't yet included). Deduct total products available if not 0.