add: coin purchases + donator badge
This commit is contained in:
parent
fd529d3847
commit
44f9edd67e
21 changed files with 345 additions and 38 deletions
|
@ -35,7 +35,7 @@
|
|||
justify-content: right;
|
||||
}
|
||||
|
||||
.justify-start {
|
||||
.justify_start {
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
|
||||
|
|
|
@ -169,21 +169,20 @@
|
|||
("id" "littleweb")
|
||||
(div
|
||||
("class" "inner flex flex_col gap_2")
|
||||
|
||||
(a
|
||||
("class" "button w_full lowered justify-start")
|
||||
("class" "button w_full lowered justify_start")
|
||||
("href" "/net")
|
||||
(icon (text "globe"))
|
||||
(str (text "littleweb:label.browser")))
|
||||
|
||||
(a
|
||||
("class" "button w_full lowered justify-start")
|
||||
("class" "button w_full lowered justify_start")
|
||||
("href" "/services")
|
||||
(icon (text "panel-top"))
|
||||
(str (text "littleweb:label.my_services")))
|
||||
|
||||
(a
|
||||
("class" "button w_full lowered justify-start")
|
||||
("class" "button w_full lowered justify_start")
|
||||
("href" "/domains")
|
||||
(icon (text "panel-top"))
|
||||
(str (text "littleweb:label.my_domains")))
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
(text "{%- endif %}"))
|
||||
(text "{% if can_manage_channels -%}")
|
||||
(a
|
||||
("class" "button w_full justify-start lowered")
|
||||
("class" "button w_full justify_start lowered")
|
||||
("href" "/community/{{ selected_community }}/manage#/channels")
|
||||
(text "{{ icon \"plus\" }}")
|
||||
(span
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
(div
|
||||
("class" "flex flex_row gap_1")
|
||||
(a
|
||||
("class" "w_full justify-start button {% if selected_channel == channel.id -%}lowered{% else %}camo{%- endif %}")
|
||||
("class" "w_full justify_start button {% if selected_channel == channel.id -%}lowered{% else %}camo{%- endif %}")
|
||||
("href" "/chats/{{ selected_community }}/{{ channel.id }}")
|
||||
("data-turbo" "{{ selected_community == '0' }}")
|
||||
(text "{{ icon \"rss\" }}")
|
||||
|
|
|
@ -1863,14 +1863,14 @@
|
|||
|
||||
; option a
|
||||
(button
|
||||
("class" "hover_left_bar raised justify-start w_full poll_option")
|
||||
("class" "hover_left_bar raised justify_start w_full poll_option")
|
||||
("onclick" "trigger('me::vote', ['{{ post.id }}', 'A'])")
|
||||
(icon (text "tally-1"))
|
||||
(text "{{ poll[0].option_a }}"))
|
||||
|
||||
; option b
|
||||
(button
|
||||
("class" "hover_left_bar raised justify-start w_full poll_option")
|
||||
("class" "hover_left_bar raised justify_start w_full poll_option")
|
||||
("onclick" "trigger('me::vote', ['{{ post.id }}', 'B'])")
|
||||
(icon (text "tally-2"))
|
||||
(text "{{ poll[0].option_b }}"))
|
||||
|
@ -1878,7 +1878,7 @@
|
|||
; option c
|
||||
(text "{% if poll[0].option_c -%}")
|
||||
(button
|
||||
("class" "hover_left_bar raised justify-start w_full poll_option")
|
||||
("class" "hover_left_bar raised justify_start w_full poll_option")
|
||||
("onclick" "trigger('me::vote', ['{{ post.id }}', 'C'])")
|
||||
(icon (text "tally-3"))
|
||||
(text "{{ poll[0].option_c }}"))
|
||||
|
@ -1887,7 +1887,7 @@
|
|||
; option d
|
||||
(text "{% if poll[0].option_d -%}")
|
||||
(button
|
||||
("class" "hover_left_bar raised justify-start w_full poll_option")
|
||||
("class" "hover_left_bar raised justify_start w_full poll_option")
|
||||
("onclick" "trigger('me::vote', ['{{ post.id }}', 'D'])")
|
||||
(icon (text "tally-4"))
|
||||
(text "{{ poll[0].option_d }}"))
|
||||
|
@ -2181,7 +2181,7 @@
|
|||
("class" "flex flex_row gap_1")
|
||||
(a
|
||||
("href" "/journals/{{ journal.id }}/0?view={{ view_mode }}")
|
||||
("class" "button justify-start lowered w_full")
|
||||
("class" "button justify_start lowered w_full")
|
||||
(icon (text "notebook"))
|
||||
(text "{{ journal.title }}"))
|
||||
|
||||
|
@ -2207,7 +2207,7 @@
|
|||
(div
|
||||
("class" "flex flex_row gap_1")
|
||||
(button
|
||||
("class" "justify-start lowered w_full")
|
||||
("class" "justify_start lowered w_full")
|
||||
(icon (text "arrow-down"))
|
||||
(text "{{ journal.title }}"))
|
||||
|
||||
|
@ -2257,7 +2257,7 @@
|
|||
; create note
|
||||
(text "{% if user and user.id == journal.owner -%}")
|
||||
(button
|
||||
("class" "lowered justify-start w_full")
|
||||
("class" "lowered justify_start w_full")
|
||||
("onclick" "create_note()")
|
||||
(icon (text "plus"))
|
||||
(str (text "journals:action.create_note")))
|
||||
|
@ -2271,7 +2271,7 @@
|
|||
(text "{% macro notes_list_dir_listing(dir, dirs, notes, owner, journal, view_mode=false) -%}")
|
||||
(details
|
||||
(summary
|
||||
("class" "button w_full justify-start raised w_full")
|
||||
("class" "button w_full justify_start raised w_full")
|
||||
(icon (text "folder"))
|
||||
(text "{{ dir[2] }}"))
|
||||
|
||||
|
@ -2299,7 +2299,7 @@
|
|||
("ui_ident" "{% if selected_note == note.id -%} active_note {%- else -%} inactive_note {%- endif %}")
|
||||
(a
|
||||
("href" "{% if owner -%} /@{{ owner.username }}/{{ journal.title }}/{{ note.title }} {%- else -%} /journals/{{ journal.id }}/{{ note.id }} {%- endif %}")
|
||||
("class" "button justify-start w_full {% if selected_note == note.id -%} lowered {%- else -%} raised {%- endif %}")
|
||||
("class" "button justify_start w_full {% if selected_note == note.id -%} lowered {%- else -%} raised {%- endif %}")
|
||||
(icon (text "file-text"))
|
||||
(text "{{ note.title }}"))
|
||||
|
||||
|
@ -2380,7 +2380,7 @@
|
|||
(div
|
||||
("class" "flex flex_row gap_1")
|
||||
(button
|
||||
("class" "justify-start lowered w_full")
|
||||
("class" "justify_start lowered w_full")
|
||||
(icon (text "folder-open"))
|
||||
(text "{{ dir[2] }}"))
|
||||
|
||||
|
@ -2423,7 +2423,7 @@
|
|||
(text "{% macro note_mover_dirs_listing(dir, dirs) -%}")
|
||||
(button
|
||||
("onclick" "move_note_dir(window.NOTE_MOVER_NOTE_ID, '{{ dir[0] }}'); document.getElementById('note_mover_dialog').close()")
|
||||
("class" "justify-start lowered w_full")
|
||||
("class" "justify_start lowered w_full")
|
||||
(icon (text "folder-open"))
|
||||
(text "{{ dir[2] }}"))
|
||||
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
(span (str (text "general:link.wallet")))))
|
||||
(div
|
||||
("class" "card lowered flex flex_col gap_4")
|
||||
(a
|
||||
(button
|
||||
("class" "card button raised")
|
||||
("href" "/wallet/buy")
|
||||
("onclick" "document.getElementById('buy_dialog').showModal()")
|
||||
(b (text "Coin balance"))
|
||||
(h3
|
||||
("class" "flex gap_2 items_center")
|
||||
|
@ -63,4 +63,54 @@
|
|||
(icon (text "external-link")))
|
||||
(text "{%- endif %}")))
|
||||
(text "{%- endfor %}")))))))
|
||||
|
||||
(dialog
|
||||
("id" "buy_dialog")
|
||||
(div
|
||||
("class" "inner flex flex_col gap_2")
|
||||
(p (text "All coin purchases are one-time and will not recur."))
|
||||
(p (text "If you do not receive your coins within a minute of purchase, please contact support."))
|
||||
|
||||
(button
|
||||
("class" "lowered w_full justify_start")
|
||||
("onclick" "checkout('Coins100')")
|
||||
(text "100 coins ({{ config.stripe.price_texts.coins_100 }})"))
|
||||
|
||||
(button
|
||||
("class" "w_full justify_start")
|
||||
("onclick" "checkout('Coins400')")
|
||||
(text "400 coins ({{ config.stripe.price_texts.coins_400 }})"))
|
||||
|
||||
(hr ("class" "margin"))
|
||||
(div
|
||||
("class" "flex gap_2 justify_between")
|
||||
(div null?)
|
||||
(button
|
||||
("class" "lowered red")
|
||||
("type" "button")
|
||||
("onclick", "document.getElementById('buy_dialog').close()")
|
||||
(icon (text "x"))
|
||||
(str (text "dialog:action.cancel"))))))
|
||||
|
||||
(script
|
||||
(text "globalThis.checkout = (product) => {
|
||||
document.getElementById('buy_dialog').close();
|
||||
fetch(\"/api/v1/service_hooks/stripe/checkout\", {
|
||||
method: \"POST\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
product,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [res.ok ? \"success\" : \"error\", res.message]);
|
||||
|
||||
if (res.ok) {
|
||||
window.location.href = res.payload;
|
||||
}
|
||||
});
|
||||
}"))
|
||||
(text "{% endblock %}")
|
||||
|
|
|
@ -121,7 +121,7 @@
|
|||
(div
|
||||
("class" "flex flex_col gap_2 w_full")
|
||||
(button
|
||||
("class" "lowered justify-start w_full")
|
||||
("class" "lowered justify_start w_full")
|
||||
("onclick" "create_journal()")
|
||||
(icon (text "plus"))
|
||||
(str (text "journals:action.create_journal")))
|
||||
|
@ -207,7 +207,7 @@
|
|||
(details
|
||||
("class" "w_full")
|
||||
(summary
|
||||
("class" "button lowered w_full justify-start")
|
||||
("class" "button lowered w_full justify_start")
|
||||
(icon (text "settings"))
|
||||
(str (text "general:action.manage")))
|
||||
|
||||
|
@ -261,7 +261,7 @@
|
|||
(details
|
||||
("class" "w_full")
|
||||
(summary
|
||||
("class" "button lowered w_full justify-start")
|
||||
("class" "button lowered w_full justify_start")
|
||||
(icon (text "folders"))
|
||||
(str (text "journals:label.directories")))
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
("id" "username")
|
||||
("class" "username flex items_center gap_2 flex_wrap w_full")
|
||||
(span
|
||||
("class" "name shorter")
|
||||
("class" "name")
|
||||
(text "{{ components::username(user=profile) }}"))
|
||||
(text "{% if profile.is_verified -%}")
|
||||
(span
|
||||
|
@ -84,6 +84,12 @@
|
|||
("style" "color: var(--color-primary);")
|
||||
("class" "flex items_center")
|
||||
(text "{{ icon \"id-card-lanyard\" }}"))
|
||||
(text "{%- endif %} {% if profile.checkouts|length > 0 -%}")
|
||||
(span
|
||||
("title" "Donator")
|
||||
("style" "color: var(--color-primary);")
|
||||
("class" "flex items_center")
|
||||
(text "{{ icon \"hand-heart\" }}"))
|
||||
(text "{%- endif %} {% if profile.permissions|has_staff_badge -%}")
|
||||
(span
|
||||
("title" "Staff")
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
("class" "card w_full flex flex_col gap_2")
|
||||
("ui_ident" "io_data_load")
|
||||
; pinned
|
||||
(text "{% if pinned|length > 0 -%}")
|
||||
(text "{% if pinned and pinned|length > 0 -%}")
|
||||
(text "{% for post in pinned %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self, poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %}")
|
||||
(div ("class" "squig"))
|
||||
(text "{%- endif %}")
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
("class" "card w_full flex flex_col gap_2")
|
||||
("ui_ident" "io_data_load")
|
||||
; pinned
|
||||
(text "{% if pinned|length > 0 -%}")
|
||||
(text "{% if pinned and pinned|length > 0 -%}")
|
||||
(text "{% for post in pinned %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self, poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %}")
|
||||
(div ("class" "squig"))
|
||||
(text "{%- endif %}")
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
(span (text "Select a stack to add this user to:"))
|
||||
(text "{% for stack in stacks %}")
|
||||
(button
|
||||
("class" "justify-start lowered w_full")
|
||||
("class" "justify_start lowered w_full")
|
||||
("onclick" "choose_stack('{{ stack.id }}')")
|
||||
(icon (text "layers"))
|
||||
(text "{{ stack.name }}"))
|
||||
|
|
|
@ -726,7 +726,7 @@
|
|||
element.innerHTML = "";
|
||||
for (const token of Object.entries($.LOGIN_ACCOUNT_TOKENS)) {
|
||||
element.innerHTML += `<div class="flex gap_2 flex_row">
|
||||
<button class="lowered w_full justify-start" onclick="trigger('me::login', ['${token[0]}'])">
|
||||
<button class="lowered w_full justify_start" onclick="trigger('me::login', ['${token[0]}'])">
|
||||
<img
|
||||
title="${token[0]}'s avatar"
|
||||
src="/api/v1/auth/user/${token[0]}/avatar?selector_type=username"
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
use std::time::Duration;
|
||||
use axum::{http::HeaderMap, response::IntoResponse, Extension, Json};
|
||||
use std::{str::FromStr, time::Duration};
|
||||
use axum::{
|
||||
extract::Query,
|
||||
http::HeaderMap,
|
||||
response::{IntoResponse, Redirect},
|
||||
Extension, Json,
|
||||
};
|
||||
use tetratto_core::model::{
|
||||
auth::{Notification, User},
|
||||
economy::{CoinTransfer, CoinTransferMethod},
|
||||
moderation::AuditLogEntry,
|
||||
permissions::{FinePermission, SecondaryPermission},
|
||||
ApiReturn, Error,
|
||||
};
|
||||
use stripe::{EventObject, EventType};
|
||||
use crate::State;
|
||||
use crate::{get_user_from_token, State, cookie::CookieJar};
|
||||
use serde::Deserialize;
|
||||
|
||||
pub async fn stripe_webhook(
|
||||
Extension(data): Extension<State>,
|
||||
|
@ -131,7 +138,7 @@ pub async fn stripe_webhook(
|
|||
if let Err(e) = data
|
||||
.create_audit_log_entry(AuditLogEntry::new(
|
||||
0,
|
||||
format!("invoice tier update failed: stripe {customer_id}"),
|
||||
format!("invoice user update failed: stripe {customer_id}"),
|
||||
))
|
||||
.await
|
||||
{
|
||||
|
@ -469,3 +476,202 @@ pub async fn stripe_webhook(
|
|||
payload: (),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub enum ProductIDAlias {
|
||||
Coins100,
|
||||
Coins400,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateCheckoutSessionProps {
|
||||
pub product: ProductIDAlias,
|
||||
}
|
||||
|
||||
pub async fn create_stupid_fucking_checkout_session(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Json(props): Json<CreateCheckoutSessionProps>,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await);
|
||||
let user = match get_user_from_token!(jar, data.0) {
|
||||
Some(ua) => ua,
|
||||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
let stripe_cnf = match data.0.0.0.stripe {
|
||||
Some(ref c) => c,
|
||||
None => return Json(Error::MiscError("Disabled".to_string()).into()),
|
||||
};
|
||||
|
||||
let stripe_client = match data.3 {
|
||||
Some(ref x) => x,
|
||||
None => return Json(Error::MiscError("Disabled".to_string()).into()),
|
||||
};
|
||||
|
||||
let session = match stripe::CheckoutSession::create(
|
||||
&stripe_client,
|
||||
stripe::CreateCheckoutSession {
|
||||
customer_creation: if user.stripe_id.is_empty() {
|
||||
Some(stripe::CheckoutSessionCustomerCreation::Always)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
customer: if user.stripe_id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(stripe::CustomerId::from_str(&user.stripe_id).unwrap())
|
||||
},
|
||||
line_items: Some(vec![stripe::CreateCheckoutSessionLineItems {
|
||||
quantity: Some(1),
|
||||
adjustable_quantity: Some(
|
||||
stripe::CreateCheckoutSessionLineItemsAdjustableQuantity {
|
||||
enabled: false,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
price: Some(match props.product {
|
||||
ProductIDAlias::Coins100 => stripe_cnf.price_ids.coins_100.clone(),
|
||||
ProductIDAlias::Coins400 => stripe_cnf.price_ids.coins_400.clone(),
|
||||
}),
|
||||
..Default::default()
|
||||
}]),
|
||||
client_reference_id: Some(&user.id.to_string()),
|
||||
mode: Some(stripe::CheckoutSessionMode::Payment),
|
||||
ui_mode: Some(stripe::CheckoutSessionUiMode::Hosted),
|
||||
success_url: Some(&format!(
|
||||
"{}/api/v1/service_hooks/stripe/checkout/success?session_id={{CHECKOUT_SESSION_ID}}",
|
||||
data.0.0.0.host
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(e) => return Json(Error::MiscError(e.to_string()).into()),
|
||||
};
|
||||
|
||||
Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Success".to_string(),
|
||||
payload: session.url.unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CheckoutSessionSuccessProps {
|
||||
pub session_id: String,
|
||||
}
|
||||
|
||||
/// By this point, we can assume the customer has properly paid.
|
||||
///
|
||||
/// This endpoint will just read the purchase, apply the purchase, and then redirect home.
|
||||
pub async fn handle_stupid_fucking_checkout_success_session(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Query(props): Query<CheckoutSessionSuccessProps>,
|
||||
) -> std::result::Result<impl IntoResponse, Json<ApiReturn<()>>> {
|
||||
let data = &(data.read().await);
|
||||
let mut user = match get_user_from_token!(jar, data.0) {
|
||||
Some(ua) => ua,
|
||||
None => return Err(Json(Error::NotAllowed.into())),
|
||||
};
|
||||
|
||||
if user.checkouts.contains(&props.session_id) {
|
||||
return Err(Json(
|
||||
Error::MiscError("You can only do this once".to_string()).into(),
|
||||
));
|
||||
}
|
||||
|
||||
let stripe_cnf = match data.0.0.0.stripe {
|
||||
Some(ref c) => c,
|
||||
None => return Err(Json(Error::MiscError("Disabled".to_string()).into())),
|
||||
};
|
||||
|
||||
let stripe_client = match data.3 {
|
||||
Some(ref x) => x,
|
||||
None => return Err(Json(Error::MiscError("Disabled".to_string()).into())),
|
||||
};
|
||||
|
||||
let session = match stripe::CheckoutSession::retrieve(
|
||||
&stripe_client,
|
||||
&match stripe::CheckoutSessionId::from_str(&props.session_id) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return Err(Json(
|
||||
Error::MiscError("Invalid session ID".to_string()).into(),
|
||||
));
|
||||
}
|
||||
},
|
||||
&[&"line_items"],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(e) => return Err(Json(Error::MiscError(e.to_string()).into())),
|
||||
};
|
||||
|
||||
let price_id = session
|
||||
.line_items
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.data
|
||||
.get(0)
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.price
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.id
|
||||
.to_string();
|
||||
|
||||
if price_id == stripe_cnf.price_ids.coins_100 {
|
||||
if let Err(e) = data
|
||||
.0
|
||||
.create_transfer(
|
||||
&mut CoinTransfer::new(
|
||||
data.0.0.0.system_user,
|
||||
user.id,
|
||||
100,
|
||||
CoinTransferMethod::Transfer,
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Err(Json(e.into()));
|
||||
}
|
||||
} else if price_id == stripe_cnf.price_ids.coins_400 {
|
||||
if let Err(e) = data
|
||||
.0
|
||||
.create_transfer(
|
||||
&mut CoinTransfer::new(
|
||||
data.0.0.0.system_user,
|
||||
user.id,
|
||||
400,
|
||||
CoinTransferMethod::Transfer,
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Err(Json(e.into()));
|
||||
}
|
||||
} else {
|
||||
tracing::error!(
|
||||
"received an invalid stripe price id, please check config.stripe.price_ids"
|
||||
);
|
||||
|
||||
return Err(Json(
|
||||
Error::MiscError("Unknown price ID".to_string()).into(),
|
||||
));
|
||||
}
|
||||
|
||||
user.checkouts.push(props.session_id);
|
||||
if let Err(e) = data.0.update_user_checkouts(user.id, user.checkouts).await {
|
||||
return Err(Json(e.into()));
|
||||
}
|
||||
|
||||
Ok(Redirect::to("/wallet"))
|
||||
}
|
||||
|
|
|
@ -563,6 +563,14 @@ pub fn routes() -> Router {
|
|||
"/service_hooks/stripe",
|
||||
post(auth::connections::stripe::stripe_webhook),
|
||||
)
|
||||
.route(
|
||||
"/service_hooks/stripe/checkout",
|
||||
post(auth::connections::stripe::create_stupid_fucking_checkout_session),
|
||||
)
|
||||
.route(
|
||||
"/service_hooks/stripe/checkout/success",
|
||||
get(auth::connections::stripe::handle_stupid_fucking_checkout_success_session),
|
||||
)
|
||||
// channels
|
||||
.route("/channels", post(channels::channels::create_request))
|
||||
.route(
|
||||
|
|
|
@ -152,7 +152,7 @@ pub async fn update_price_request(
|
|||
if req.price < 25 {
|
||||
return Json(
|
||||
Error::MiscError(
|
||||
"Price is too low, please a price of use 25 coins or more".to_string(),
|
||||
"Price is too low, please use a price of 25 coins or more".to_string(),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue