forked from Kieran/void.cat
Optional payments
This commit is contained in:
parent
af62bd74eb
commit
150579c509
@ -100,7 +100,7 @@ public class DownloadController : Controller
|
||||
}
|
||||
|
||||
// check payment order
|
||||
if (meta.Payment != default && meta.Payment.Service != PaymentServices.None)
|
||||
if (meta.Payment != default && meta.Payment.Service != PaymentServices.None && meta.Payment.Required)
|
||||
{
|
||||
var orderId = Request.Headers.GetHeader("V-OrderId") ?? Request.Query["orderId"];
|
||||
if (!await IsOrderPaid(orderId))
|
||||
|
@ -15,7 +15,7 @@ namespace VoidCat.Controllers
|
||||
{
|
||||
private readonly FileStoreFactory _storage;
|
||||
private readonly IFileMetadataStore _metadata;
|
||||
private readonly IPaymentStore _payment;
|
||||
private readonly IPaymentStore _paymentStore;
|
||||
private readonly IPaymentFactory _paymentFactory;
|
||||
private readonly FileInfoManager _fileInfo;
|
||||
private readonly IUserUploadsStore _userUploads;
|
||||
@ -29,7 +29,7 @@ namespace VoidCat.Controllers
|
||||
{
|
||||
_storage = storage;
|
||||
_metadata = metadata;
|
||||
_payment = payment;
|
||||
_paymentStore = payment;
|
||||
_paymentFactory = paymentFactory;
|
||||
_fileInfo = fileInfo;
|
||||
_userUploads = userUploads;
|
||||
@ -214,7 +214,7 @@ namespace VoidCat.Controllers
|
||||
{
|
||||
var gid = id.FromBase58Guid();
|
||||
var file = await _fileInfo.Get(gid);
|
||||
var config = await _payment.Get(gid);
|
||||
var config = await _paymentStore.Get(gid);
|
||||
|
||||
var provider = await _paymentFactory.CreateProvider(config!.Service);
|
||||
return await provider.CreateOrder(file!.Payment!);
|
||||
@ -231,7 +231,7 @@ namespace VoidCat.Controllers
|
||||
public async ValueTask<PaymentOrder?> GetOrderStatus([FromRoute] string id, [FromRoute] Guid order)
|
||||
{
|
||||
var gid = id.FromBase58Guid();
|
||||
var config = await _payment.Get(gid);
|
||||
var config = await _paymentStore.Get(gid);
|
||||
|
||||
var provider = await _paymentFactory.CreateProvider(config!.Service);
|
||||
return await provider.GetOrderStatus(order);
|
||||
@ -254,18 +254,19 @@ namespace VoidCat.Controllers
|
||||
|
||||
if (req.Strike != default)
|
||||
{
|
||||
await _payment.Add(gid, new StrikePaymentConfig()
|
||||
await _paymentStore.Add(gid, new StrikePaymentConfig()
|
||||
{
|
||||
Service = PaymentServices.Strike,
|
||||
Handle = req.Strike.Handle,
|
||||
Cost = req.Strike.Cost
|
||||
Cost = req.Strike.Cost,
|
||||
Required = req.Required
|
||||
});
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// if none set, delete config
|
||||
await _payment.Delete(gid);
|
||||
await _paymentStore.Delete(gid);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@ -341,5 +342,7 @@ namespace VoidCat.Controllers
|
||||
public Guid EditSecret { get; init; }
|
||||
|
||||
public StrikePaymentConfig? Strike { get; init; }
|
||||
|
||||
public bool Required { get; init; }
|
||||
}
|
||||
}
|
||||
|
20
VoidCat/Services/Migrations/Database/04-OptionalPayments.cs
Normal file
20
VoidCat/Services/Migrations/Database/04-OptionalPayments.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using FluentMigrator;
|
||||
|
||||
namespace VoidCat.Services.Migrations.Database;
|
||||
|
||||
[Migration(20220908_1602)]
|
||||
public class OptionalPayments : Migration{
|
||||
public override void Up()
|
||||
{
|
||||
Create.Column("Required")
|
||||
.OnTable("Payment")
|
||||
.AsBoolean()
|
||||
.WithDefaultValue(true);
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
Delete.Column("Required")
|
||||
.FromTable("Payment");
|
||||
}
|
||||
}
|
@ -18,11 +18,11 @@ public sealed class PostgresPaymentStore : IPaymentStore
|
||||
public async ValueTask<PaymentConfig?> Get(Guid id)
|
||||
{
|
||||
await using var conn = await _connection.Get();
|
||||
var svc = await conn.QuerySingleOrDefaultAsync<DtoPaymentConfig>(
|
||||
var dto = await conn.QuerySingleOrDefaultAsync<DtoPaymentConfig>(
|
||||
@"select * from ""Payment"" where ""File"" = :file", new {file = id});
|
||||
if (svc != default)
|
||||
if (dto != default)
|
||||
{
|
||||
switch (svc.Service)
|
||||
switch (dto.Service)
|
||||
{
|
||||
case PaymentServices.Strike:
|
||||
{
|
||||
@ -31,10 +31,11 @@ public sealed class PostgresPaymentStore : IPaymentStore
|
||||
@"select ""Handle"" from ""PaymentStrike"" where ""File"" = :file", new {file = id});
|
||||
return new StrikePaymentConfig
|
||||
{
|
||||
Cost = new(svc.Amount, svc.Currency),
|
||||
File = svc.File,
|
||||
Cost = new(dto.Amount, dto.Currency),
|
||||
File = dto.File,
|
||||
Handle = handle,
|
||||
Service = PaymentServices.Strike
|
||||
Service = PaymentServices.Strike,
|
||||
Required = dto.Required
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -55,14 +56,15 @@ public sealed class PostgresPaymentStore : IPaymentStore
|
||||
await using var conn = await _connection.Get();
|
||||
await using var txn = await conn.BeginTransactionAsync();
|
||||
await conn.ExecuteAsync(
|
||||
@"insert into ""Payment""(""File"", ""Service"", ""Amount"", ""Currency"") values(:file, :service, :amount, :currency)
|
||||
on conflict(""File"") do update set ""Service"" = :service, ""Amount"" = :amount, ""Currency"" = :currency",
|
||||
@"insert into ""Payment""(""File"", ""Service"", ""Amount"", ""Currency"", ""Required"") values(:file, :service, :amount, :currency, :required)
|
||||
on conflict(""File"") do update set ""Service"" = :service, ""Amount"" = :amount, ""Currency"" = :currency, ""Required"" = :required",
|
||||
new
|
||||
{
|
||||
file = id,
|
||||
service = (int)obj.Service,
|
||||
amount = obj.Cost.Amount,
|
||||
currency = obj.Cost.Currency
|
||||
currency = obj.Cost.Currency,
|
||||
required = obj.Required
|
||||
});
|
||||
|
||||
if (obj is StrikePaymentConfig sc)
|
||||
|
@ -7,6 +7,7 @@ import "./FileEdit.css";
|
||||
import {useSelector} from "react-redux";
|
||||
import {VoidButton} from "./VoidButton";
|
||||
import moment from "moment";
|
||||
import {PaymentServices} from "./Const";
|
||||
|
||||
export function FileEdit(props) {
|
||||
const {Api} = useApi();
|
||||
@ -42,10 +43,10 @@ export function FileEdit(props) {
|
||||
|
||||
function renderPaymentConfig() {
|
||||
switch (payment) {
|
||||
case 0: {
|
||||
case PaymentServices.None: {
|
||||
return <NoPaymentConfig privateFile={privateFile} onSaveConfig={saveConfig}/>;
|
||||
}
|
||||
case 1: {
|
||||
case PaymentServices.Strike: {
|
||||
return <StrikePaymentConfig file={file} privateFile={privateFile} onSaveConfig={saveConfig}/>
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ export function FilePayment(props) {
|
||||
}
|
||||
|
||||
if (!order) {
|
||||
if (pw.required) {
|
||||
return (
|
||||
<div className="payment">
|
||||
<h3>
|
||||
@ -42,6 +43,11 @@ export function FilePayment(props) {
|
||||
<VoidButton onClick={fetchOrder}>Pay</VoidButton>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<VoidButton onClick={fetchOrder}>Tip {FormatCurrency(pw.cost.amount, pw.cost.currency)}</VoidButton>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
switch (pw.service) {
|
||||
case PaymentServices.Strike: {
|
||||
|
@ -26,13 +26,24 @@ export function FilePreview() {
|
||||
}
|
||||
}
|
||||
|
||||
function renderTypes() {
|
||||
function canAccessFile() {
|
||||
if (info?.payment?.required === true && !order) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderPayment() {
|
||||
if (info.payment && info.payment.service !== 0) {
|
||||
if (!order) {
|
||||
return <FilePayment file={info} onPaid={loadInfo}/>;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderPreview() {
|
||||
if (info.metadata) {
|
||||
switch (info.metadata.mimeType) {
|
||||
case "image/avif":
|
||||
@ -109,7 +120,7 @@ export function FilePreview() {
|
||||
}
|
||||
|
||||
function renderVirusWarning() {
|
||||
if(info.virusScan && info.virusScan.isVirus === true) {
|
||||
if (info.virusScan && info.virusScan.isVirus === true) {
|
||||
let scanResult = info.virusScan;
|
||||
return (
|
||||
<div className="virus-warning">
|
||||
@ -149,7 +160,8 @@ export function FilePreview() {
|
||||
<Fragment>
|
||||
<Helmet>
|
||||
<title>void.cat - {info.metadata?.name ?? info.id}</title>
|
||||
{info.metadata?.description ? <meta name="description" content={info.metadata?.description}/> : null}
|
||||
{info.metadata?.description ?
|
||||
<meta name="description" content={info.metadata?.description}/> : null}
|
||||
{renderOpenGraphTags()}
|
||||
</Helmet>
|
||||
{renderVirusWarning()}
|
||||
@ -158,10 +170,13 @@ export function FilePreview() {
|
||||
{info.uploader ? <InlineProfile profile={info.uploader}/> : null}
|
||||
</div>
|
||||
<div>
|
||||
<a className="btn" href={link} download={info.metadata?.name ?? info.id}>Download</a>
|
||||
{canAccessFile() ?
|
||||
<a className="btn" href={link}
|
||||
download={info.metadata?.name ?? info.id}>Download</a> : null}
|
||||
</div>
|
||||
</div>
|
||||
{renderTypes()}
|
||||
{renderPayment()}
|
||||
{canAccessFile() ? renderPreview() : null}
|
||||
<div className="file-stats">
|
||||
<div>
|
||||
<FeatherIcon icon="download-cloud"/>
|
||||
|
@ -13,6 +13,7 @@ export function StrikePaymentConfig(props) {
|
||||
const [username, setUsername] = useState(payment?.handle ?? "hrf");
|
||||
const [currency, setCurrency] = useState(payment?.cost.currency ?? PaymentCurrencies.USD);
|
||||
const [price, setPrice] = useState(payment?.cost.amount ?? 1);
|
||||
const [required, setRequired] = useState(payment?.required);
|
||||
const [saveStatus, setSaveStatus] = useState();
|
||||
|
||||
async function saveStrikeConfig(e) {
|
||||
@ -24,7 +25,8 @@ export function StrikePaymentConfig(props) {
|
||||
currency: currency,
|
||||
amount: price
|
||||
}
|
||||
}
|
||||
},
|
||||
required
|
||||
};
|
||||
|
||||
if (typeof onSaveConfig === "function") {
|
||||
@ -53,6 +55,8 @@ export function StrikePaymentConfig(props) {
|
||||
</dd>
|
||||
<dt>Price:</dt>
|
||||
<dd><input type="number" value={price} onChange={(e) => setPrice(parseFloat(e.target.value))}/></dd>
|
||||
<dt>Required:</dt>
|
||||
<dd><input type="checkbox" checked={required} onChange={(e) => setRequired(e.target.checked)}/></dd>
|
||||
</dl>
|
||||
<VoidButton onClick={saveStrikeConfig}>Save</VoidButton>
|
||||
{saveStatus ? <FeatherIcon icon="check-circle"/> : null}
|
||||
|
@ -60,7 +60,7 @@ a:hover {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="number"], input[type="password"], input[type="datetime-local"], select {
|
||||
input[type="text"], input[type="number"], input[type="password"], input[type="datetime-local"], input[type="checkbox"], select {
|
||||
display: inline-block;
|
||||
line-height: 1.1;
|
||||
border-radius: 10px;
|
||||
@ -69,6 +69,11 @@ input[type="text"], input[type="number"], input[type="password"], input[type="da
|
||||
border: 0;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
word-break: keep-all;
|
||||
|
Loading…
Reference in New Issue
Block a user