diff --git a/lnbits/extensions/offlineshop/README.md b/lnbits/extensions/offlineshop/README.md
deleted file mode 100644
index 7b9c6c8d..00000000
--- a/lnbits/extensions/offlineshop/README.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# Offline Shop
-
-## Create QR codes for each product and display them on your store for receiving payments Offline
-
-[](https://youtu.be/_XAvM_LNsoo 'video tutorial offline shop')
-
-LNbits Offline Shop allows for merchants to receive Bitcoin payments while offline and without any electronic device.
-
-Merchant will create items and associate a QR code ([a LNURLp](https://github.com/lnbits/lnbits/blob/master/lnbits/extensions/lnurlp/README.md)) with a price. He can then print the QR codes and display them on their shop. When a customer chooses an item, scans the QR code, gets the description and price. After payment, the customer gets a confirmation code that the merchant can validate to be sure the payment was successful.
-
-Customers must use an LNURL pay capable wallet.
-
-[**Wallets supporting LNURL**](https://github.com/fiatjaf/awesome-lnurl#wallets)
-
-## Usage
-
-1. Entering the Offline shop extension you'll see an Items list, the Shop wallet and a Wordslist\
- 
-2. Begin by creating an item, click "ADD NEW ITEM"
- - set the item name and a small description
- - you can set an optional, preferably square image, that will show up on the customer wallet - _depending on wallet_
- - set the item price, if you choose a fiat currency the bitcoin conversion will happen at the time customer scans to pay\
- 
-3. After creating some products, click on "PRINT QR CODES"\
- 
-4. You'll see a QR code for each product in your LNbits Offline Shop with a title and price ready for printing\
- 
-5. Place the printed QR codes on your shop, or at the fair stall, or have them as a menu style laminated sheet
-6. Choose what type of confirmation do you want customers to report to merchant after a successful payment\
- 
-
- - Wordlist is the default option: after a successful payment the customer will receive a word from this list, **sequentially**. Starting in _albatross_ as customers pay for the items they will get the next word in the list until _zebra_, then it starts at the top again. The list can be changed, for example if you think A-Z is a big list to track, you can use _apple_, _banana_, _coconut_\
- 
- - TOTP (time-based one time password) can be used instead. If you use Google Authenticator just scan the presented QR with the app and after a successful payment the user will get the password that you can check with GA\
- 
- - Nothing, disables the need for confirmation of payment, click the "DISABLE CONFIRMATION CODES"
diff --git a/lnbits/extensions/offlineshop/__init__.py b/lnbits/extensions/offlineshop/__init__.py
deleted file mode 100644
index 72d1ae6b..00000000
--- a/lnbits/extensions/offlineshop/__init__.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from fastapi import APIRouter
-from fastapi.staticfiles import StaticFiles
-
-from lnbits.db import Database
-from lnbits.helpers import template_renderer
-
-db = Database("ext_offlineshop")
-
-offlineshop_static_files = [
- {
- "path": "/offlineshop/static",
- "app": StaticFiles(packages=[("lnbits", "extensions/offlineshop/static")]),
- "name": "offlineshop_static",
- }
-]
-
-offlineshop_ext: APIRouter = APIRouter(prefix="/offlineshop", tags=["Offlineshop"])
-
-
-def offlineshop_renderer():
- return template_renderer(["lnbits/extensions/offlineshop/templates"])
-
-
-from .lnurl import * # noqa: F401,F403
-from .views import * # noqa: F401,F403
-from .views_api import * # noqa: F401,F403
diff --git a/lnbits/extensions/offlineshop/config.json b/lnbits/extensions/offlineshop/config.json
deleted file mode 100644
index 94dcd478..00000000
--- a/lnbits/extensions/offlineshop/config.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "name": "OfflineShop",
- "short_description": "Receive payments for products offline!",
- "tile": "/offlineshop/static/image/offlineshop.png",
- "contributors": [
- "fiatjaf"
- ]
-}
diff --git a/lnbits/extensions/offlineshop/crud.py b/lnbits/extensions/offlineshop/crud.py
deleted file mode 100644
index 1fa63f3e..00000000
--- a/lnbits/extensions/offlineshop/crud.py
+++ /dev/null
@@ -1,117 +0,0 @@
-from typing import List, Optional
-
-from lnbits.db import SQLITE
-
-from . import db
-from .models import Item, Shop
-from .wordlists import animals
-
-
-async def create_shop(*, wallet_id: str) -> int:
- returning = "" if db.type == SQLITE else "RETURNING ID"
- method = db.execute if db.type == SQLITE else db.fetchone
-
- result = await (method)(
- f"""
- INSERT INTO offlineshop.shops (wallet, wordlist, method)
- VALUES (?, ?, 'wordlist')
- {returning}
- """,
- (wallet_id, "\n".join(animals)),
- )
- if db.type == SQLITE:
- return result._result_proxy.lastrowid
- else:
- return result[0] # type: ignore
-
-
-async def get_shop(id: int) -> Optional[Shop]:
- row = await db.fetchone("SELECT * FROM offlineshop.shops WHERE id = ?", (id,))
- return Shop(**row) if row else None
-
-
-async def get_or_create_shop_by_wallet(wallet: str) -> Optional[Shop]:
- row = await db.fetchone(
- "SELECT * FROM offlineshop.shops WHERE wallet = ?", (wallet,)
- )
-
- if not row:
- # create on the fly
- ls_id = await create_shop(wallet_id=wallet)
- return await get_shop(ls_id)
-
- return Shop(**row) if row else None
-
-
-async def set_method(shop: int, method: str, wordlist: str = "") -> Optional[Shop]:
- await db.execute(
- "UPDATE offlineshop.shops SET method = ?, wordlist = ? WHERE id = ?",
- (method, wordlist, shop),
- )
- return await get_shop(shop)
-
-
-async def add_item(
- shop: int,
- name: str,
- description: str,
- image: Optional[str],
- price: int,
- unit: str,
- fiat_base_multiplier: int,
-) -> int:
- result = await db.execute(
- """
- INSERT INTO offlineshop.items (shop, name, description, image, price, unit, fiat_base_multiplier)
- VALUES (?, ?, ?, ?, ?, ?, ?)
- """,
- (shop, name, description, image, price, unit, fiat_base_multiplier),
- )
- return result._result_proxy.lastrowid
-
-
-async def update_item(
- shop: int,
- item_id: int,
- name: str,
- description: str,
- image: Optional[str],
- price: int,
- unit: str,
- fiat_base_multiplier: int,
-) -> int:
- await db.execute(
- """
- UPDATE offlineshop.items SET
- name = ?,
- description = ?,
- image = ?,
- price = ?,
- unit = ?,
- fiat_base_multiplier = ?
- WHERE shop = ? AND id = ?
- """,
- (name, description, image, price, unit, fiat_base_multiplier, shop, item_id),
- )
- return item_id
-
-
-async def get_item(id: int) -> Optional[Item]:
- row = await db.fetchone(
- "SELECT * FROM offlineshop.items WHERE id = ? LIMIT 1", (id,)
- )
- return Item.from_row(row) if row else None
-
-
-async def get_items(shop: int) -> List[Item]:
- rows = await db.fetchall("SELECT * FROM offlineshop.items WHERE shop = ?", (shop,))
- return [Item.from_row(row) for row in rows]
-
-
-async def delete_item_from_shop(shop: int, item_id: int):
- await db.execute(
- """
- DELETE FROM offlineshop.items WHERE shop = ? AND id = ?
- """,
- (shop, item_id),
- )
diff --git a/lnbits/extensions/offlineshop/helpers.py b/lnbits/extensions/offlineshop/helpers.py
deleted file mode 100644
index 86a653aa..00000000
--- a/lnbits/extensions/offlineshop/helpers.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import base64
-import hmac
-import struct
-import time
-
-
-def hotp(key, counter, digits=6, digest="sha1"):
- key = base64.b32decode(key.upper() + "=" * ((8 - len(key)) % 8))
- counter = struct.pack(">Q", counter)
- mac = hmac.new(key, counter, digest).digest()
- offset = mac[-1] & 0x0F
- binary = struct.unpack(">L", mac[offset : offset + 4])[0] & 0x7FFFFFFF
- return str(binary)[-digits:].zfill(digits)
-
-
-def totp(key, time_step=30, digits=6, digest="sha1"):
- return hotp(key, int(time.time() / time_step), digits, digest)
diff --git a/lnbits/extensions/offlineshop/lnurl.py b/lnbits/extensions/offlineshop/lnurl.py
deleted file mode 100644
index ca4e6bac..00000000
--- a/lnbits/extensions/offlineshop/lnurl.py
+++ /dev/null
@@ -1,88 +0,0 @@
-from fastapi import Query
-from lnurl import LnurlErrorResponse, LnurlPayActionResponse, LnurlPayResponse
-from lnurl.models import ClearnetUrl, LightningInvoice, MilliSatoshi
-from starlette.requests import Request
-
-from lnbits.core.services import create_invoice
-from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
-
-from . import offlineshop_ext
-from .crud import get_item, get_shop
-
-
-@offlineshop_ext.get("/lnurl/{item_id}", name="offlineshop.lnurl_response")
-async def lnurl_response(req: Request, item_id: int = Query(...)) -> dict:
- item = await get_item(item_id)
- if not item:
- return {"status": "ERROR", "reason": "Item not found."}
-
- if not item.enabled:
- return {"status": "ERROR", "reason": "Item disabled."}
-
- price_msat = (
- await fiat_amount_as_satoshis(item.price, item.unit)
- if item.unit != "sat"
- else item.price
- ) * 1000
-
- resp = LnurlPayResponse(
- callback=ClearnetUrl(
- req.url_for("offlineshop.lnurl_callback", item_id=item.id), scheme="https"
- ),
- minSendable=MilliSatoshi(price_msat),
- maxSendable=MilliSatoshi(price_msat),
- metadata=await item.lnurlpay_metadata(),
- )
-
- return resp.dict()
-
-
-@offlineshop_ext.get("/lnurl/cb/{item_id}", name="offlineshop.lnurl_callback")
-async def lnurl_callback(request: Request, item_id: int):
- item = await get_item(item_id)
- if not item:
- return {"status": "ERROR", "reason": "Couldn't find item."}
-
- if item.unit == "sat":
- min = item.price * 1000
- max = item.price * 1000
- else:
- price = await fiat_amount_as_satoshis(item.price, item.unit)
- # allow some fluctuation (the fiat price may have changed between the calls)
- min = price * 995
- max = price * 1010
-
- amount_received = int(request.query_params.get("amount") or 0)
- if amount_received < min:
- return LnurlErrorResponse(
- reason=f"Amount {amount_received} is smaller than minimum {min}."
- ).dict()
- elif amount_received > max:
- return LnurlErrorResponse(
- reason=f"Amount {amount_received} is greater than maximum {max}."
- ).dict()
-
- shop = await get_shop(item.shop)
- assert shop
-
- try:
- payment_hash, payment_request = await create_invoice(
- wallet_id=shop.wallet,
- amount=int(amount_received / 1000),
- memo=item.name,
- unhashed_description=(await item.lnurlpay_metadata()).encode(),
- extra={"tag": "offlineshop", "item": item.id},
- )
- except Exception as exc:
- return LnurlErrorResponse(reason=str(exc)).dict()
-
- if shop.method:
- success_action = item.success_action(shop, payment_hash, request)
- assert success_action
- resp = LnurlPayActionResponse(
- pr=LightningInvoice(payment_request),
- successAction=success_action,
- routes=[],
- )
-
- return resp.dict()
diff --git a/lnbits/extensions/offlineshop/migrations.py b/lnbits/extensions/offlineshop/migrations.py
deleted file mode 100644
index 4e668668..00000000
--- a/lnbits/extensions/offlineshop/migrations.py
+++ /dev/null
@@ -1,39 +0,0 @@
-async def m001_initial(db):
- """
- Initial offlineshop tables.
- """
- await db.execute(
- f"""
- CREATE TABLE offlineshop.shops (
- id {db.serial_primary_key},
- wallet TEXT NOT NULL,
- method TEXT NOT NULL,
- wordlist TEXT
- );
- """
- )
-
- await db.execute(
- f"""
- CREATE TABLE offlineshop.items (
- shop INTEGER NOT NULL REFERENCES {db.references_schema}shops (id),
- id {db.serial_primary_key},
- name TEXT NOT NULL,
- description TEXT NOT NULL,
- image TEXT, -- image/png;base64,...
- enabled BOOLEAN NOT NULL DEFAULT true,
- price {db.big_int} NOT NULL,
- unit TEXT NOT NULL DEFAULT 'sat'
- );
- """
- )
-
-
-async def m002_fiat_base_multiplier(db):
- """
- Store the multiplier for fiat prices. We store the price in cents and
- remember to multiply by 100 when we use it to convert to Dollars.
- """
- await db.execute(
- "ALTER TABLE offlineshop.items ADD COLUMN fiat_base_multiplier INTEGER DEFAULT 1;"
- )
diff --git a/lnbits/extensions/offlineshop/models.py b/lnbits/extensions/offlineshop/models.py
deleted file mode 100644
index 01044cb0..00000000
--- a/lnbits/extensions/offlineshop/models.py
+++ /dev/null
@@ -1,138 +0,0 @@
-import base64
-import hashlib
-import json
-from collections import OrderedDict
-from sqlite3 import Row
-from typing import Dict, List, Optional
-
-from lnurl import encode as lnurl_encode
-from lnurl.models import ClearnetUrl, Max144Str, UrlAction
-from lnurl.types import LnurlPayMetadata
-from pydantic import BaseModel
-from starlette.requests import Request
-
-from .helpers import totp
-
-shop_counters: Dict = {}
-
-
-class ShopCounter:
- wordlist: List[str]
- fulfilled_payments: OrderedDict
- counter: int
-
- @classmethod
- def invoke(cls, shop: "Shop"):
- shop_counter = shop_counters.get(shop.id)
- if not shop_counter:
- shop_counter = cls(wordlist=shop.wordlist.split("\n"))
- shop_counters[shop.id] = shop_counter
- return shop_counter
-
- @classmethod
- def reset(cls, shop: "Shop"):
- shop_counter = cls.invoke(shop)
- shop_counter.counter = -1
- shop_counter.wordlist = shop.wordlist.split("\n")
-
- def __init__(self, wordlist: List[str]):
- self.wordlist = wordlist
- self.fulfilled_payments = OrderedDict()
- self.counter = -1
-
- def get_word(self, payment_hash):
- if payment_hash in self.fulfilled_payments:
- return self.fulfilled_payments[payment_hash]
-
- # get a new word
- self.counter += 1
- word = self.wordlist[self.counter % len(self.wordlist)]
- self.fulfilled_payments[payment_hash] = word
-
- # cleanup confirmation words cache
- to_remove = len(self.fulfilled_payments) - 23
- if to_remove > 0:
- for _ in range(to_remove):
- self.fulfilled_payments.popitem(False)
-
- return word
-
-
-class Shop(BaseModel):
- id: int
- wallet: str
- method: str
- wordlist: str
-
- @classmethod
- def from_row(cls, row: Row):
- return cls(**dict(row))
-
- @property
- def otp_key(self) -> str:
- return base64.b32encode(
- hashlib.sha256(
- ("otpkey" + str(self.id) + self.wallet).encode("ascii")
- ).digest()
- ).decode("ascii")
-
- def get_code(self, payment_hash: str) -> str:
- if self.method == "wordlist":
- sc = ShopCounter.invoke(self)
- return sc.get_word(payment_hash)
- elif self.method == "totp":
- return totp(self.otp_key)
- return ""
-
-
-class Item(BaseModel):
- shop: int
- id: int
- name: str
- description: str
- image: Optional[str]
- enabled: bool
- price: float
- unit: str
- fiat_base_multiplier: int
-
- @classmethod
- def from_row(cls, row: Row) -> "Item":
- data = dict(row)
- if data["unit"] != "sat" and data["fiat_base_multiplier"]:
- data["price"] /= data["fiat_base_multiplier"]
- return cls(**data)
-
- def lnurl(self, req: Request) -> str:
- return lnurl_encode(req.url_for("offlineshop.lnurl_response", item_id=self.id))
-
- def values(self, req: Request):
- values = self.dict()
- values["lnurl"] = lnurl_encode(
- req.url_for("offlineshop.lnurl_response", item_id=self.id)
- )
- return values
-
- async def lnurlpay_metadata(self) -> LnurlPayMetadata:
- metadata = [["text/plain", self.description]]
-
- if self.image:
- metadata.append(self.image.split(":")[1].split(","))
-
- return LnurlPayMetadata(json.dumps(metadata))
-
- def success_action(
- self, shop: Shop, payment_hash: str, req: Request
- ) -> Optional[UrlAction]:
- if not shop.wordlist:
- return None
-
- return UrlAction(
- url=ClearnetUrl(
- req.url_for("offlineshop.confirmation_code", p=payment_hash),
- scheme="https",
- ),
- description=Max144Str(
- "Open to get the confirmation code for your purchase."
- ),
- )
diff --git a/lnbits/extensions/offlineshop/static/image/offlineshop.png b/lnbits/extensions/offlineshop/static/image/offlineshop.png
deleted file mode 100644
index 24241d4f..00000000
Binary files a/lnbits/extensions/offlineshop/static/image/offlineshop.png and /dev/null differ
diff --git a/lnbits/extensions/offlineshop/static/js/index.js b/lnbits/extensions/offlineshop/static/js/index.js
deleted file mode 100644
index 7ade1bb9..00000000
--- a/lnbits/extensions/offlineshop/static/js/index.js
+++ /dev/null
@@ -1,230 +0,0 @@
-/* globals Quasar, Vue, _, VueQrcode, windowMixin, LNbits, LOCALE */
-
-Vue.component(VueQrcode.name, VueQrcode)
-
-const pica = window.pica()
-
-function imgSizeFit(img, maxWidth = 1024, maxHeight = 768) {
- let ratio = Math.min(
- 1,
- maxWidth / img.naturalWidth,
- maxHeight / img.naturalHeight
- )
- return {width: img.naturalWidth * ratio, height: img.naturalHeight * ratio}
-}
-
-const defaultItemData = {
- unit: 'sat'
-}
-
-new Vue({
- el: '#vue',
- mixins: [windowMixin],
- data() {
- return {
- selectedWallet: null,
- confirmationMethod: 'wordlist',
- wordlistTainted: false,
- offlineshop: {
- method: null,
- wordlist: [],
- items: []
- },
- itemDialog: {
- show: false,
- urlImg: true,
- data: {...defaultItemData},
- units: ['sat']
- }
- }
- },
- computed: {
- printItems() {
- return this.offlineshop.items.filter(({enabled}) => enabled)
- }
- },
- methods: {
- openNewDialog() {
- this.itemDialog.show = true
- this.itemDialog.data = {...defaultItemData}
- },
- openUpdateDialog(itemId) {
- this.itemDialog.show = true
- let item = this.offlineshop.items.find(item => item.id === itemId)
- if (item.image.startsWith('data:')) {
- this.itemDialog.urlImg = false
- }
- this.itemDialog.data = item
- },
- imageAdded(file) {
- let blobURL = URL.createObjectURL(file)
- let image = new Image()
- image.src = blobURL
- image.onload = async () => {
- let fit = imgSizeFit(image, 100, 100)
- let canvas = document.createElement('canvas')
- canvas.setAttribute('width', fit.width)
- canvas.setAttribute('height', fit.height)
- output = await pica.resize(image, canvas)
- this.itemDialog.data.image = output.toDataURL('image/jpeg', 0.4)
- this.itemDialog = {...this.itemDialog}
- }
- },
- imageCleared() {
- this.itemDialog.data.image = null
- this.itemDialog = {...this.itemDialog}
- },
- disabledAddItemButton() {
- return (
- !this.itemDialog.data.name ||
- this.itemDialog.data.name.length === 0 ||
- !this.itemDialog.data.price ||
- !this.itemDialog.data.description ||
- !this.itemDialog.data.unit ||
- this.itemDialog.data.unit.length === 0
- )
- },
- changedWallet(wallet) {
- this.selectedWallet = wallet
- this.loadShop()
- },
- loadShop() {
- LNbits.api
- .request(
- 'GET',
- '/offlineshop/api/v1/offlineshop',
- this.selectedWallet.inkey
- )
- .then(response => {
- this.offlineshop = response.data
- this.confirmationMethod = response.data.method
- this.wordlistTainted = false
- })
- .catch(err => {
- LNbits.utils.notifyApiError(err)
- })
- },
- async setMethod() {
- try {
- await LNbits.api.request(
- 'PUT',
- '/offlineshop/api/v1/offlineshop/method',
- this.selectedWallet.inkey,
- {method: this.confirmationMethod, wordlist: this.offlineshop.wordlist}
- )
- } catch (err) {
- LNbits.utils.notifyApiError(err)
- return
- }
-
- this.$q.notify({
- message:
- `Method set to ${this.confirmationMethod}.` +
- (this.confirmationMethod === 'wordlist' ? ' Counter reset.' : ''),
- timeout: 700
- })
- this.loadShop()
- },
- async sendItem() {
- let {id, name, image, description, price, unit} = this.itemDialog.data
- const data = {
- name,
- description,
- image,
- price,
- unit,
- fiat_base_multiplier: unit == 'sat' ? 1 : 100
- }
-
- try {
- if (id) {
- await LNbits.api.request(
- 'PUT',
- '/offlineshop/api/v1/offlineshop/items/' + id,
- this.selectedWallet.inkey,
- data
- )
- } else {
- await LNbits.api.request(
- 'POST',
- '/offlineshop/api/v1/offlineshop/items',
- this.selectedWallet.inkey,
- data
- )
- this.$q.notify({
- message: `Item '${this.itemDialog.data.name}' added.`,
- timeout: 700
- })
- }
- } catch (err) {
- LNbits.utils.notifyApiError(err)
- return
- }
-
- this.loadShop()
- this.itemDialog.show = false
- this.itemDialog.urlImg = true
- this.itemDialog.data = {...defaultItemData}
- },
- toggleItem(itemId) {
- let item = this.offlineshop.items.find(item => item.id === itemId)
- item.enabled = !item.enabled
-
- LNbits.api
- .request(
- 'PUT',
- '/offlineshop/api/v1/offlineshop/items/' + itemId,
- this.selectedWallet.inkey,
- item
- )
- .then(response => {
- this.$q.notify({
- message: `Item ${item.enabled ? 'enabled' : 'disabled'}.`,
- timeout: 700
- })
- this.offlineshop.items = this.offlineshop.items
- })
- .catch(err => {
- LNbits.utils.notifyApiError(err)
- })
- },
- deleteItem(itemId) {
- LNbits.utils
- .confirmDialog('Are you sure you want to delete this item?')
- .onOk(() => {
- LNbits.api
- .request(
- 'DELETE',
- '/offlineshop/api/v1/offlineshop/items/' + itemId,
- this.selectedWallet.inkey
- )
- .then(response => {
- this.$q.notify({
- message: `Item deleted.`,
- timeout: 700
- })
- this.offlineshop.items.splice(
- this.offlineshop.items.findIndex(item => item.id === itemId),
- 1
- )
- })
- .catch(err => {
- LNbits.utils.notifyApiError(err)
- })
- })
- }
- },
- created() {
- this.selectedWallet = this.g.user.wallets[0]
- this.loadShop()
-
- LNbits.api
- .request('GET', '/offlineshop/api/v1/currencies')
- .then(response => {
- this.itemDialog = {...this.itemDialog, units: ['sat', ...response.data]}
- })
- .catch(err => {
- LNbits.utils.notifyApiError(err)
- })
- }
-})
diff --git a/lnbits/extensions/offlineshop/templates/offlineshop/_api_docs.html b/lnbits/extensions/offlineshop/templates/offlineshop/_api_docs.html
deleted file mode 100644
index 0a4b9df8..00000000
--- a/lnbits/extensions/offlineshop/templates/offlineshop/_api_docs.html
+++ /dev/null
@@ -1,154 +0,0 @@
-
- The confirmation codes are words from a predefined sequential word list.
- Each new payment bumps the words sequence by 1. So you can check the
- confirmation codes manually by just looking at them.
-
- For example, if your wordlist is
- Powered by LNURL-pay.
-
- [apple, banana, coconut] the first purchase will be
- apple, the second banana and so on. When it
- gets to the end it starts from the beginning again.
- POST
- Headers
- {"X-Api-Key": <invoice_key>}
- Body (application/json)
- Returns 201 OK
- Curl example
- curl -X GET {{ request.base_url
- }}offlineshop/api/v1/offlineshop/items -H "Content-Type:
- application/json" -H "X-Api-Key: {{ user.wallets[0].inkey }}" -d
- '{"name": <string>, "description": <string>, "image":
- <data-uri string>, "price": <integer>, "unit": <"sat"
- or "USD">}'
-
- GET
- Headers
- {"X-Api-Key": <invoice_key>}
- Body (application/json)
-
- Returns 200 OK (application/json)
-
- {"id": <integer>, "wallet": <string>, "wordlist":
- <string>, "items": [{"id": <integer>, "name":
- <string>, "description": <string>, "image":
- <string>, "enabled": <boolean>, "price": <integer>,
- "unit": <string>, "lnurl": <string>}, ...]}<
- Curl example
- curl -X GET {{ request.base_url }}offlineshop/api/v1/offlineshop -H
- "X-Api-Key: {{ user.wallets[0].inkey }}"
-
- PUT
- Headers
- {"X-Api-Key": <invoice_key>}
- Body (application/json)
- Returns 200 OK
- Curl example
- curl -X GET {{ request.base_url
- }}offlineshop/api/v1/offlineshop/items/<item_id> -H
- "Content-Type: application/json" -H "X-Api-Key: {{
- user.wallets[0].inkey }}" -d '{"name": <string>, "description":
- <string>, "image": <data-uri string>, "price":
- <integer>, "unit": <"sat" or "USD">}'
-
- DELETE
- Headers
- {"X-Api-Key": <invoice_key>}
- Body (application/json)
- Returns 200 OK
- Curl example
- curl -X GET {{ request.base_url
- }}offlineshop/api/v1/offlineshop/items/<item_id> -H "X-Api-Key:
- {{ user.wallets[0].inkey }}"
-
-
- Setting this option disables the confirmation code message that - appears in the consumer wallet after a purchase is paid for. It's ok - if the consumer is to be trusted when they claim to have paid. -
- -