remove paywall (#1518)

This commit is contained in:
dni ⚡ 2023-02-17 15:18:39 +01:00 committed by GitHub
parent dd14d2dc54
commit 967f46f82a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 0 additions and 972 deletions

View File

@ -1,22 +0,0 @@
# Paywall
## Hide content behind a paywall, a user has to pay some amount to access your hidden content
A Paywall is a way of restricting to content via a purchase or paid subscription. For example to read a determined blog post, or to continue reading further, to access a downloads area, etc...
## Usage
1. Create a paywall by clicking "NEW PAYWALL"\
![create new paywall](https://i.imgur.com/q0ZIekC.png)
2. Fill the options for your PAYWALL
- select the wallet
- set the link that will be unlocked after a successful payment
- give your paywall a _Title_
- an optional small description
- and set an amount a user must pay to access the hidden content. Note this is the minimum amount, a user can over pay if they wish
- if _Remember payments_ is checked, a returning paying user won't have to pay again for the same content.\
![paywall config](https://i.imgur.com/CBW48F6.png)
3. You can then use your paywall link to secure your awesome content\
![paywall link](https://i.imgur.com/hDQmCDf.png)
4. When a user wants to access your hidden content, he can use the minimum amount or increase and click the "_Check icon_" to generate an invoice, user will then be redirected to the content page\
![user paywall view](https://i.imgur.com/3pLywkZ.png)

View File

@ -1,25 +0,0 @@
from fastapi import APIRouter
from starlette.staticfiles import StaticFiles
from lnbits.db import Database
from lnbits.helpers import template_renderer
db = Database("ext_paywall")
paywall_ext: APIRouter = APIRouter(prefix="/paywall", tags=["Paywall"])
paywall_static_files = [
{
"path": "/paywall/static",
"app": StaticFiles(directory="lnbits/extensions/paywall/static"),
"name": "paywall_static",
}
]
def paywall_renderer():
return template_renderer(["lnbits/extensions/paywall/templates"])
from .views import * # noqa: F401,F403
from .views_api import * # noqa: F401,F403

View File

@ -1,6 +0,0 @@
{
"name": "Paywall",
"short_description": "Create paywalls for content",
"tile": "/paywall/static/image/paywall.png",
"contributors": ["eillarra"]
}

View File

@ -1,52 +0,0 @@
from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash
from . import db
from .models import CreatePaywall, Paywall
async def create_paywall(wallet_id: str, data: CreatePaywall) -> Paywall:
paywall_id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO paywall.paywalls (id, wallet, url, memo, description, amount, remembers)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
paywall_id,
wallet_id,
data.url,
data.memo,
data.description,
data.amount,
int(data.remembers),
),
)
paywall = await get_paywall(paywall_id)
assert paywall, "Newly created paywall couldn't be retrieved"
return paywall
async def get_paywall(paywall_id: str) -> Optional[Paywall]:
row = await db.fetchone(
"SELECT * FROM paywall.paywalls WHERE id = ?", (paywall_id,)
)
return Paywall.from_row(row) if row else None
async def get_paywalls(wallet_ids: Union[str, List[str]]) -> List[Paywall]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(
f"SELECT * FROM paywall.paywalls WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Paywall.from_row(row) for row in rows]
async def delete_paywall(paywall_id: str) -> None:
await db.execute("DELETE FROM paywall.paywalls WHERE id = ?", (paywall_id,))

View File

@ -1,63 +0,0 @@
async def m001_initial(db):
"""
Initial paywalls table.
"""
await db.execute(
f"""
CREATE TABLE paywall.paywalls (
id TEXT PRIMARY KEY,
wallet TEXT NOT NULL,
secret TEXT NOT NULL,
url TEXT NOT NULL,
memo TEXT NOT NULL,
amount {db.big_int} NOT NULL,
time TIMESTAMP NOT NULL DEFAULT """
+ db.timestamp_now
+ """
);
"""
)
async def m002_redux(db):
"""
Creates an improved paywalls table and migrates the existing data.
"""
await db.execute("ALTER TABLE paywall.paywalls RENAME TO paywalls_old")
await db.execute(
f"""
CREATE TABLE paywall.paywalls (
id TEXT PRIMARY KEY,
wallet TEXT NOT NULL,
url TEXT NOT NULL,
memo TEXT NOT NULL,
description TEXT NULL,
amount {db.big_int} DEFAULT 0,
time TIMESTAMP NOT NULL DEFAULT """
+ db.timestamp_now
+ """,
remembers INTEGER DEFAULT 0,
extras TEXT NULL
);
"""
)
for row in [
list(row) for row in await db.fetchall("SELECT * FROM paywall.paywalls_old")
]:
await db.execute(
"""
INSERT INTO paywall.paywalls (
id,
wallet,
url,
memo,
amount,
time
)
VALUES (?, ?, ?, ?, ?, ?)
""",
(row[0], row[1], row[3], row[4], row[5], row[6]),
)
await db.execute("DROP TABLE paywall.paywalls_old")

View File

@ -1,41 +0,0 @@
import json
from sqlite3 import Row
from typing import Optional
from fastapi import Query
from pydantic import BaseModel
class CreatePaywall(BaseModel):
url: str = Query(...)
memo: str = Query(...)
description: str = Query(None)
amount: int = Query(..., ge=0)
remembers: bool = Query(...)
class CreatePaywallInvoice(BaseModel):
amount: int = Query(..., ge=1)
class CheckPaywallInvoice(BaseModel):
payment_hash: str = Query(...)
class Paywall(BaseModel):
id: str
wallet: str
url: str
memo: str
description: Optional[str]
amount: int
time: int
remembers: bool
extras: Optional[dict]
@classmethod
def from_row(cls, row: Row) -> "Paywall":
data = dict(row)
data["remembers"] = bool(data["remembers"])
data["extras"] = json.loads(data["extras"]) if data["extras"] else None
return cls(**data)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,148 +0,0 @@
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/paywall"></q-btn>
<q-expansion-item group="api" dense expand-separator label="List paywalls">
<q-card>
<q-card-section>
<code><span class="text-blue">GET</span> /paywall/api/v1/paywalls</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>[&lt;paywall_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.base_url }}paywall/api/v1/paywalls -H
"X-Api-Key: {{ user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Create a paywall">
<q-card>
<q-card-section>
<code
><span class="text-green">POST</span> /paywall/api/v1/paywalls</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code
>{"amount": &lt;integer&gt;, "description": &lt;string&gt;, "memo":
&lt;string&gt;, "remembers": &lt;boolean&gt;, "url":
&lt;string&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code
>{"amount": &lt;integer&gt;, "description": &lt;string&gt;, "id":
&lt;string&gt;, "memo": &lt;string&gt;, "remembers": &lt;boolean&gt;,
"time": &lt;int&gt;, "url": &lt;string&gt;, "wallet":
&lt;string&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X POST {{ request.base_url }}paywall/api/v1/paywalls -d
'{"url": &lt;string&gt;, "memo": &lt;string&gt;, "description":
&lt;string&gt;, "amount": &lt;integer&gt;, "remembers":
&lt;boolean&gt;}' -H "Content-type: application/json" -H "X-Api-Key:
{{ user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Create an invoice (public)"
>
<q-card>
<q-card-section>
<code
><span class="text-green">POST</span>
/paywall/api/v1/paywalls/&lt;paywall_id&gt;/invoice</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code>{"amount": &lt;integer&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code
>{"payment_hash": &lt;string&gt;, "payment_request":
&lt;string&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X POST {{ request.base_url
}}paywall/api/v1/paywalls/&lt;paywall_id&gt;/invoice -d '{"amount":
&lt;integer&gt;}' -H "Content-type: application/json"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Check invoice status (public)"
>
<q-card>
<q-card-section>
<code
><span class="text-green">POST</span>
/paywall/api/v1/paywalls/&lt;paywall_id&gt;/check_invoice</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code>{"payment_hash": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>{"paid": false}</code><br />
<code
>{"paid": true, "url": &lt;string&gt;, "remembers":
&lt;boolean&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X POST {{ request.base_url
}}paywall/api/v1/paywalls/&lt;paywall_id&gt;/check_invoice -d
'{"payment_hash": &lt;string&gt;}' -H "Content-type: application/json"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Delete a paywall"
class="q-pb-md"
>
<q-card>
<q-card-section>
<code
><span class="text-pink">DELETE</span>
/paywall/api/v1/paywalls/&lt;paywall_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Returns 204 NO CONTENT</h5>
<code></code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X DELETE {{ request.base_url
}}paywall/api/v1/paywalls/&lt;paywall_id&gt; -H "X-Api-Key: {{
user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
</q-expansion-item>

View File

@ -1,168 +0,0 @@
{% extends "public.html" %} {% block page %}
<div class="row q-col-gutter-md justify-center">
<div class="col-12 col-sm-8 col-md-5 col-lg-4">
<q-card class="q-pa-lg">
<q-card-section class="q-pa-none">
<h5 class="text-subtitle1 q-mt-none q-mb-sm">{{ paywall.memo }}</h5>
{% if paywall.description %}
<p>{{ paywall.description }}</p>
{% endif %}
<div v-if="!this.redirectUrl" class="q-mt-lg">
<q-form v-if="">
<q-input
filled
v-model.number="userAmount"
type="number"
:min="paywallAmount"
suffix="sat"
label="Choose an amount *"
:hint="'Minimum ' + paywallAmount + ' sat'"
>
</q-input>
<div class="row q-mt-lg">
<q-btn
unelevated
color="primary"
:disabled="userAmount < paywallAmount || paymentReq"
@click="createInvoice"
>Send</q-btn
>
</div>
</q-form>
<div v-if="paymentReq" class="q-mt-lg">
<a class="text-secondary" :href="'lightning:' + paymentReq">
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode
:value="'lightning:' + paymentReq.toUpperCase()"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
</a>
<div class="row q-mt-lg">
<q-btn outline color="grey" @click="copyText(paymentReq)"
>Copy invoice</q-btn
>
<q-btn @click="cancelPayment" flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</div>
</div>
<div v-else>
<q-separator class="q-my-lg"></q-separator>
<p>
You can access the URL behind this paywall:<br />
<strong>{% raw %}{{ redirectUrl }}{% endraw %}</strong>
</p>
<div class="row q-mt-lg">
<q-btn outline color="grey" type="a" :href="redirectUrl"
>Open URL</q-btn
>
</div>
</div>
</q-card-section>
</q-card>
</div>
</div>
{% endblock %} {% block scripts %}
<script>
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function () {
return {
userAmount: '{{ paywall.amount }}',
paywallAmount: '{{ paywall.amount }}',
paymentReq: null,
redirectUrl: null,
paymentDialog: {
dismissMsg: null,
checker: null
}
}
},
computed: {
amount: function () {
return this.paywallAmount > this.userAmount
? this.paywallAmount
: this.userAmount
}
},
methods: {
cancelPayment: function () {
this.paymentReq = null
clearInterval(this.paymentDialog.checker)
if (this.paymentDialog.dismissMsg) {
this.paymentDialog.dismissMsg()
}
},
createInvoice: function () {
var self = this
LNbits.api
.request(
'POST',
'/paywall/api/v1/paywalls/invoice/{{ paywall.id }}',
'filler',
{
amount: self.amount
}
)
.then(function (response) {
if (response.data) {
self.paymentReq = response.data.payment_request.toUpperCase()
self.paymentDialog.dismissMsg = self.$q.notify({
timeout: 0,
message: 'Waiting for payment...'
})
self.paymentDialog.checker = setInterval(function () {
LNbits.api
.request(
'POST',
'/paywall/api/v1/paywalls/check_invoice/{{ paywall.id }}',
'filler',
{payment_hash: response.data.payment_hash}
)
.then(function (response) {
if (response.data) {
if (response.data.paid) {
self.cancelPayment()
self.redirectUrl = response.data.url
if (response.data.remembers) {
self.$q.localStorage.set(
'lnbits.paywall.{{ paywall.id }}',
response.data.url
)
}
self.$q.notify({
type: 'positive',
message: 'Payment received!',
icon: null
})
}
}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
}, 2000)
}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
}
},
created: function () {
var url = this.$q.localStorage.getItem('lnbits.paywall.{{ paywall.id }}')
if (url) {
this.redirectUrl = url
}
}
})
</script>
{% endblock %}

View File

@ -1,312 +0,0 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<div class="row q-col-gutter-md">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn unelevated color="primary" @click="formDialog.show = true"
>New paywall</q-btn
>
</q-card-section>
</q-card>
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Paywalls</h5>
</div>
<div class="col-auto">
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn>
</div>
</div>
<q-table
dense
flat
:data="paywalls"
row-key="id"
:columns="paywallsTable.columns"
:pagination.sync="paywallsTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
<q-th auto-width></q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn
unelevated
dense
size="xs"
icon="launch"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="props.row.displayUrl"
target="_blank"
></q-btn>
<q-btn
unelevated
dense
size="xs"
icon="link"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="props.row.url"
target="_blank"
></q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
<q-td auto-width>
<q-btn
flat
dense
size="xs"
@click="deletePaywall(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
</div>
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">
{{SITE_TITLE}} paywall extension
</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list> {% include "paywall/_api_docs.html" %} </q-list>
</q-card-section>
</q-card>
</div>
<q-dialog v-model="formDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="createPaywall" class="q-gutter-md">
<q-select
filled
dense
emit-value
v-model="formDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
>
</q-select>
<q-input
filled
dense
v-model.trim="formDialog.data.url"
type="url"
label="Redirect URL *"
></q-input>
<q-input
filled
dense
v-model.trim="formDialog.data.memo"
label="Title *"
placeholder="LNbits paywall"
></q-input>
<q-input
filled
dense
autogrow
v-model.trim="formDialog.data.description"
label="Description"
></q-input>
<q-input
filled
dense
v-model.number="formDialog.data.amount"
type="number"
label="Amount (sat) *"
hint="This is the minimum amount users can pay/donate."
></q-input>
<q-list>
<q-item tag="label" class="rounded-borders">
<q-item-section avatar>
<q-checkbox
v-model="formDialog.data.remembers"
color="primary"
></q-checkbox>
</q-item-section>
<q-item-section>
<q-item-label>Remember payments</q-item-label>
<q-item-label caption
>A succesful payment will be registered in the browser's
storage, so the user doesn't need to pay again to access the
URL.</q-item-label
>
</q-item-section>
</q-item>
</q-list>
<div class="row q-mt-lg">
<q-btn
unelevated
color="primary"
:disable="formDialog.data.amount == null || formDialog.data.amount < 0 || formDialog.data.url == null || formDialog.data.memo == null"
type="submit"
>Create paywall</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
</div>
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script>
var mapPaywall = function (obj) {
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
obj.displayUrl = ['/paywall/', obj.id].join('')
return obj
}
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function () {
return {
paywalls: [],
paywallsTable: {
columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'},
{name: 'memo', align: 'left', label: 'Memo', field: 'memo'},
{
name: 'amount',
align: 'right',
label: 'Amount (sat)',
field: 'fsat',
sortable: true,
sort: function (a, b, rowA, rowB) {
return rowA.amount - rowB.amount
}
},
{
name: 'remembers',
align: 'left',
label: 'Remember',
field: 'remembers'
},
{
name: 'date',
align: 'left',
label: 'Date',
field: 'date',
sortable: true
}
],
pagination: {
rowsPerPage: 10
}
},
formDialog: {
show: false,
data: {
remembers: false
}
}
}
},
methods: {
getPaywalls: function () {
var self = this
LNbits.api
.request(
'GET',
'/paywall/api/v1/paywalls?all_wallets=true',
this.g.user.wallets[0].inkey
)
.then(function (response) {
self.paywalls = response.data.map(function (obj) {
return mapPaywall(obj)
})
})
},
createPaywall: function () {
var data = {
url: this.formDialog.data.url,
memo: this.formDialog.data.memo,
amount: this.formDialog.data.amount,
description: this.formDialog.data.description,
remembers: this.formDialog.data.remembers
}
var self = this
LNbits.api
.request(
'POST',
'/paywall/api/v1/paywalls',
_.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet})
.inkey,
data
)
.then(function (response) {
self.paywalls.push(mapPaywall(response.data))
self.formDialog.show = false
self.formDialog.data = {
remembers: false
}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deletePaywall: function (paywallId) {
var self = this
var paywall = _.findWhere(this.paywalls, {id: paywallId})
LNbits.utils
.confirmDialog('Are you sure you want to delete this paywall link?')
.onOk(function () {
LNbits.api
.request(
'DELETE',
'/paywall/api/v1/paywalls/' + paywallId,
_.findWhere(self.g.user.wallets, {id: paywall.wallet}).inkey
)
.then(function (response) {
self.paywalls = _.reject(self.paywalls, function (obj) {
return obj.id == paywallId
})
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
exportCSV: function () {
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls)
}
},
created: function () {
if (this.g.user.wallets.length) {
this.getPaywalls()
}
}
})
</script>
{% endblock %}

View File

@ -1,30 +0,0 @@
from http import HTTPStatus
from fastapi import Depends
from starlette.exceptions import HTTPException
from starlette.requests import Request
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
from . import paywall_ext, paywall_renderer
from .crud import get_paywall
@paywall_ext.get("/")
async def index(request: Request, user: User = Depends(check_user_exists)):
return paywall_renderer().TemplateResponse(
"paywall/index.html", {"request": request, "user": user.dict()}
)
@paywall_ext.get("/{paywall_id}")
async def display(request: Request, paywall_id):
paywall = await get_paywall(paywall_id)
if not paywall:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Paywall does not exist."
)
return paywall_renderer().TemplateResponse(
"paywall/display.html", {"request": request, "paywall": paywall}
)

View File

@ -1,105 +0,0 @@
from http import HTTPStatus
from fastapi import Depends, Query
from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user, get_wallet
from lnbits.core.services import check_transaction_status, create_invoice
from lnbits.decorators import WalletTypeInfo, get_key_type
from . import paywall_ext
from .crud import create_paywall, delete_paywall, get_paywall, get_paywalls
from .models import CheckPaywallInvoice, CreatePaywall, CreatePaywallInvoice
@paywall_ext.get("/api/v1/paywalls")
async def api_paywalls(
wallet: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False)
):
wallet_ids = [wallet.wallet.id]
if all_wallets:
user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [paywall.dict() for paywall in await get_paywalls(wallet_ids)]
@paywall_ext.post("/api/v1/paywalls")
async def api_paywall_create(
data: CreatePaywall, wallet: WalletTypeInfo = Depends(get_key_type)
):
paywall = await create_paywall(wallet_id=wallet.wallet.id, data=data)
return paywall.dict()
@paywall_ext.delete("/api/v1/paywalls/{paywall_id}")
async def api_paywall_delete(
paywall_id, wallet: WalletTypeInfo = Depends(get_key_type)
):
paywall = await get_paywall(paywall_id)
if not paywall:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Paywall does not exist."
)
if paywall.wallet != wallet.wallet.id:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not your paywall."
)
await delete_paywall(paywall_id)
return "", HTTPStatus.NO_CONTENT
@paywall_ext.post("/api/v1/paywalls/invoice/{paywall_id}")
async def api_paywall_create_invoice(
data: CreatePaywallInvoice, paywall_id: str = Query(None)
):
paywall = await get_paywall(paywall_id)
assert paywall
if data.amount < paywall.amount:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Minimum amount is {paywall.amount} sat.",
)
try:
amount = data.amount if data.amount > paywall.amount else paywall.amount
payment_hash, payment_request = await create_invoice(
wallet_id=paywall.wallet,
amount=amount,
memo=f"{paywall.memo}",
extra={"tag": "paywall"},
)
except Exception as e:
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
return {"payment_hash": payment_hash, "payment_request": payment_request}
@paywall_ext.post("/api/v1/paywalls/check_invoice/{paywall_id}")
async def api_paywal_check_invoice(
data: CheckPaywallInvoice, paywall_id: str = Query(None)
):
paywall = await get_paywall(paywall_id)
payment_hash = data.payment_hash
if not paywall:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Paywall does not exist."
)
try:
status = await check_transaction_status(paywall.wallet, payment_hash)
is_paid = not status.pending
except Exception:
return {"paid": False}
if is_paid:
wallet = await get_wallet(paywall.wallet)
assert wallet
payment = await wallet.get_payment(payment_hash)
assert payment
await payment.set_pending(False)
return {"paid": True, "url": paywall.url, "remembers": paywall.remembers}
return {"paid": False}