Add billing-contact config option

This commit is contained in:
binwiederhier 2023-02-28 14:38:31 -05:00
parent f862341997
commit fe3a225f8f
10 changed files with 51 additions and 7 deletions

View file

@ -84,6 +84,7 @@ var flagsServe = append(
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"behind_proxy", "P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting)"}), altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"behind_proxy", "P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "stripe-secret-key", Aliases: []string{"stripe_secret_key"}, EnvVars: []string{"NTFY_STRIPE_SECRET_KEY"}, Value: "", Usage: "key used for the Stripe API communication, this enables payments"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "stripe-secret-key", Aliases: []string{"stripe_secret_key"}, EnvVars: []string{"NTFY_STRIPE_SECRET_KEY"}, Value: "", Usage: "key used for the Stripe API communication, this enables payments"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "stripe-webhook-key", Aliases: []string{"stripe_webhook_key"}, EnvVars: []string{"NTFY_STRIPE_WEBHOOK_KEY"}, Value: "", Usage: "key required to validate the authenticity of incoming webhooks from Stripe"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "stripe-webhook-key", Aliases: []string{"stripe_webhook_key"}, EnvVars: []string{"NTFY_STRIPE_WEBHOOK_KEY"}, Value: "", Usage: "key required to validate the authenticity of incoming webhooks from Stripe"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "billing-contact", Aliases: []string{"billing_contact"}, EnvVars: []string{"NTFY_BILLING_CONTACT"}, Value: "", Usage: "e-mail or website to display in upgrade dialog (only if payments are enabled)"}),
) )
var cmdServe = &cli.Command{ var cmdServe = &cli.Command{
@ -159,6 +160,7 @@ func execServe(c *cli.Context) error {
behindProxy := c.Bool("behind-proxy") behindProxy := c.Bool("behind-proxy")
stripeSecretKey := c.String("stripe-secret-key") stripeSecretKey := c.String("stripe-secret-key")
stripeWebhookKey := c.String("stripe-webhook-key") stripeWebhookKey := c.String("stripe-webhook-key")
billingContact := c.String("billing-contact")
// Check values // Check values
if firebaseKeyFile != "" && !util.FileExists(firebaseKeyFile) { if firebaseKeyFile != "" && !util.FileExists(firebaseKeyFile) {
@ -305,6 +307,7 @@ func execServe(c *cli.Context) error {
conf.BehindProxy = behindProxy conf.BehindProxy = behindProxy
conf.StripeSecretKey = stripeSecretKey conf.StripeSecretKey = stripeSecretKey
conf.StripeWebhookKey = stripeWebhookKey conf.StripeWebhookKey = stripeWebhookKey
conf.BillingContact = billingContact
conf.EnableWeb = enableWeb conf.EnableWeb = enableWeb
conf.EnableSignup = enableSignup conf.EnableSignup = enableSignup
conf.EnableLogin = enableLogin conf.EnableLogin = enableLogin

View file

@ -839,6 +839,8 @@ config options:
enables payments in the ntfy web app (e.g. Upgrade dialog). See [API keys](https://dashboard.stripe.com/apikeys). enables payments in the ntfy web app (e.g. Upgrade dialog). See [API keys](https://dashboard.stripe.com/apikeys).
* `stripe-webhook-key` is the key required to validate the authenticity of incoming webhooks from Stripe. * `stripe-webhook-key` is the key required to validate the authenticity of incoming webhooks from Stripe.
Webhooks are essential to keep the local database in sync with the payment provider. See [Webhooks](https://dashboard.stripe.com/webhooks). Webhooks are essential to keep the local database in sync with the payment provider. See [Webhooks](https://dashboard.stripe.com/webhooks).
* `billing-contact` is an email address or website displayed in the "Upgrade tier" dialog to let people reach
out with billing questions. If unset, nothing will be displayed.
In addition to setting these two options, you also need to define a [Stripe webhook](https://dashboard.stripe.com/webhooks) In addition to setting these two options, you also need to define a [Stripe webhook](https://dashboard.stripe.com/webhooks)
for the `customer.subscription.updated` and `customer.subscription.deleted` event, which points for the `customer.subscription.updated` and `customer.subscription.deleted` event, which points
@ -849,6 +851,7 @@ Here's an example:
``` yaml ``` yaml
stripe-secret-key: "sk_test_ZmhzZGtmbGhkc2tqZmhzYcO2a2hmbGtnaHNkbGtnaGRsc2hnbG" stripe-secret-key: "sk_test_ZmhzZGtmbGhkc2tqZmhzYcO2a2hmbGtnaHNkbGtnaGRsc2hnbG"
stripe-webhook-key: "whsec_ZnNkZnNIRExBSFNES0hBRFNmaHNka2ZsaGR" stripe-webhook-key: "whsec_ZnNkZnNIRExBSFNES0hBRFNmaHNka2ZsaGR"
billing-contact: "phil@example.com"
``` ```
## Rate limiting ## Rate limiting
@ -1194,6 +1197,7 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
| `enable-reservations` | `NTFY_ENABLE_RESERVATIONS` | *boolean* (`true` or `false`) | `false` | Allows users to reserve topics (if their tier allows it) | | `enable-reservations` | `NTFY_ENABLE_RESERVATIONS` | *boolean* (`true` or `false`) | `false` | Allows users to reserve topics (if their tier allows it) |
| `stripe-secret-key` | `NTFY_STRIPE_SECRET_KEY` | *string* | - | Payments: Key used for the Stripe API communication, this enables payments | | `stripe-secret-key` | `NTFY_STRIPE_SECRET_KEY` | *string* | - | Payments: Key used for the Stripe API communication, this enables payments |
| `stripe-webhook-key` | `NTFY_STRIPE_WEBHOOK_KEY` | *string* | - | Payments: Key required to validate the authenticity of incoming webhooks from Stripe | | `stripe-webhook-key` | `NTFY_STRIPE_WEBHOOK_KEY` | *string* | - | Payments: Key required to validate the authenticity of incoming webhooks from Stripe |
| `billing-contact` | `NTFY_BILLING_CONTACT` | *email address* or *website* | - | Payments: Email or website displayed in Upgrade dialog as a billing contact |
The format for a *duration* is: `<number>(smh)`, e.g. 30s, 20m or 1h. The format for a *duration* is: `<number>(smh)`, e.g. 30s, 20m or 1h.
The format for a *size* is: `<number>(GMK)`, e.g. 1G, 200M or 4000k. The format for a *size* is: `<number>(GMK)`, e.g. 1G, 200M or 4000k.
@ -1277,6 +1281,7 @@ OPTIONS:
--behind-proxy, --behind_proxy, -P if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting) (default: false) [$NTFY_BEHIND_PROXY] --behind-proxy, --behind_proxy, -P if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting) (default: false) [$NTFY_BEHIND_PROXY]
--stripe-secret-key value, --stripe_secret_key value key used for the Stripe API communication, this enables payments [$NTFY_STRIPE_SECRET_KEY] --stripe-secret-key value, --stripe_secret_key value key used for the Stripe API communication, this enables payments [$NTFY_STRIPE_SECRET_KEY]
--stripe-webhook-key value, --stripe_webhook_key value key required to validate the authenticity of incoming webhooks from Stripe [$NTFY_STRIPE_WEBHOOK_KEY] --stripe-webhook-key value, --stripe_webhook_key value key required to validate the authenticity of incoming webhooks from Stripe [$NTFY_STRIPE_WEBHOOK_KEY]
--billing-contact value, --billing_contact value e-mail or website to display in upgrade dialog (only if payments are enabled) [$NTFY_BILLING_CONTACT]
--help, -h show help (default: false) --help, -h show help (default: false)
``` ```

View file

@ -21,6 +21,7 @@ are no closed-source features. So if you'd like to run your own server, you can!
* Upgrade dialog: Disable submit button for free tier (no ticket) * Upgrade dialog: Disable submit button for free tier (no ticket)
* Allow multiple `log-level-overrides` on the same field (no ticket) * Allow multiple `log-level-overrides` on the same field (no ticket)
* Actually remove `ntfy publish --env-topic` flag (as per [deprecations](deprecations.md), no ticket) * Actually remove `ntfy publish --env-topic` flag (as per [deprecations](deprecations.md), no ticket)
* Added `billing-contact` config option (no ticket)
## ntfy server v2.1.0 ## ntfy server v2.1.0
Released February 25, 2023 Released February 25, 2023

View file

@ -128,6 +128,7 @@ type Config struct {
StripeSecretKey string StripeSecretKey string
StripeWebhookKey string StripeWebhookKey string
StripePriceCacheDuration time.Duration StripePriceCacheDuration time.Duration
BillingContact string
EnableWeb bool EnableWeb bool
EnableSignup bool // Enable creation of accounts via API and UI EnableSignup bool // Enable creation of accounts via API and UI
EnableLogin bool EnableLogin bool

View file

@ -485,6 +485,7 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi
EnableSignup: s.config.EnableSignup, EnableSignup: s.config.EnableSignup,
EnablePayments: s.config.StripeSecretKey != "", EnablePayments: s.config.StripeSecretKey != "",
EnableReservations: s.config.EnableReservations, EnableReservations: s.config.EnableReservations,
BillingContact: s.config.BillingContact,
DisallowedTopics: s.config.DisallowedTopics, DisallowedTopics: s.config.DisallowedTopics,
} }
b, err := json.MarshalIndent(response, "", " ") b, err := json.MarshalIndent(response, "", " ")

View file

@ -240,9 +240,12 @@
# enables payments in the ntfy web app (e.g. Upgrade dialog). See https://dashboard.stripe.com/apikeys. # enables payments in the ntfy web app (e.g. Upgrade dialog). See https://dashboard.stripe.com/apikeys.
# - stripe-webhook-key is the key required to validate the authenticity of incoming webhooks from Stripe. # - stripe-webhook-key is the key required to validate the authenticity of incoming webhooks from Stripe.
# Webhooks are essential up keep the local database in sync with the payment provider. See https://dashboard.stripe.com/webhooks. # Webhooks are essential up keep the local database in sync with the payment provider. See https://dashboard.stripe.com/webhooks.
# - billing-contact is an email address or website displayed in the "Upgrade tier" dialog to let people reach
# out with billing questions. If unset, nothing will be displayed.
# #
# stripe-secret-key: # stripe-secret-key:
# stripe-webhook-key: # stripe-webhook-key:
# billing-contact:
# Logging options # Logging options
# #

View file

@ -341,6 +341,7 @@ type apiConfigResponse struct {
EnableSignup bool `json:"enable_signup"` EnableSignup bool `json:"enable_signup"`
EnablePayments bool `json:"enable_payments"` EnablePayments bool `json:"enable_payments"`
EnableReservations bool `json:"enable_reservations"` EnableReservations bool `json:"enable_reservations"`
BillingContact string `json:"billing_contact"`
DisallowedTopics []string `json:"disallowed_topics"` DisallowedTopics []string `json:"disallowed_topics"`
} }

View file

@ -6,11 +6,12 @@
// During web development, you may change values here for rapid testing. // During web development, you may change values here for rapid testing.
var config = { var config = {
base_url: "https://127.0.0.1", // to test against a different server base_url: window.location.origin, // Change to test against a different server
app_root: "/app", app_root: "/app",
enable_login: true, enable_login: true,
enable_signup: true, enable_signup: true,
enable_payments: true, enable_payments: true,
enable_reservations: true, enable_reservations: true,
billing_contact: "",
disallowed_topics: ["docs", "static", "file", "app", "account", "settings", "signup", "login", "v1"] disallowed_topics: ["docs", "static", "file", "app", "account", "settings", "signup", "login", "v1"]
}; };

View file

@ -236,6 +236,8 @@
"account_upgrade_dialog_tier_price_billed_yearly": "{{price}} billed annually. Save {{save}}.", "account_upgrade_dialog_tier_price_billed_yearly": "{{price}} billed annually. Save {{save}}.",
"account_upgrade_dialog_tier_selected_label": "Selected", "account_upgrade_dialog_tier_selected_label": "Selected",
"account_upgrade_dialog_tier_current_label": "Current", "account_upgrade_dialog_tier_current_label": "Current",
"account_upgrade_dialog_billing_contact_email": "For billing questions, please <Link>contact us</Link> directly.",
"account_upgrade_dialog_billing_contact_website": "For billing questions, please refer to our <Link>website</Link>.",
"account_upgrade_dialog_button_cancel": "Cancel", "account_upgrade_dialog_button_cancel": "Cancel",
"account_upgrade_dialog_button_redirect_signup": "Sign up now", "account_upgrade_dialog_button_redirect_signup": "Sign up now",
"account_upgrade_dialog_button_pay_now": "Pay now and subscribe", "account_upgrade_dialog_button_pay_now": "Pay now and subscribe",

View file

@ -3,9 +3,8 @@ import {useContext, useEffect, useState} from 'react';
import Dialog from '@mui/material/Dialog'; import Dialog from '@mui/material/Dialog';
import DialogContent from '@mui/material/DialogContent'; import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle'; import DialogTitle from '@mui/material/DialogTitle';
import {Alert, Badge, CardActionArea, CardContent, Chip, ListItem, Stack, Switch, useMediaQuery} from "@mui/material"; import {Alert, CardActionArea, CardContent, Chip, Link, ListItem, Switch, useMediaQuery} from "@mui/material";
import theme from "./theme"; import theme from "./theme";
import DialogFooter from "./DialogFooter";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import accountApi, {SubscriptionInterval} from "../app/AccountApi"; import accountApi, {SubscriptionInterval} from "../app/AccountApi";
import session from "../app/Session"; import session from "../app/Session";
@ -22,6 +21,8 @@ import ListItemText from "@mui/material/ListItemText";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import {NavLink} from "react-router-dom"; import {NavLink} from "react-router-dom";
import {UnauthorizedError} from "../app/errors"; import {UnauthorizedError} from "../app/errors";
import DialogContentText from "@mui/material/DialogContentText";
import DialogActions from "@mui/material/DialogActions";
const UpgradeDialog = (props) => { const UpgradeDialog = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -204,10 +205,35 @@ const UpgradeDialog = (props) => {
</Alert> </Alert>
} }
</DialogContent> </DialogContent>
<DialogFooter status={error}> <Box sx={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
paddingLeft: '24px',
paddingBottom: '8px',
}}>
<DialogContentText
component="div"
aria-live="polite"
sx={{
margin: '0px',
paddingTop: '12px',
paddingBottom: '4px'
}}
>
{config.billing_contact.indexOf('@') !== -1 &&
<><Trans i18nKey="account_upgrade_dialog_billing_contact_email" components={{ Link: <Link href={`mailto:${config.billing_contact}`}/> }}/>{" "}</>
}
{config.billing_contact.match(`^http?s://`) &&
<><Trans i18nKey="account_upgrade_dialog_billing_contact_website" components={{ Link: <Link href={config.billing_contact} target="_blank"/> }}/>{" "}</>
}
{error}
</DialogContentText>
<DialogActions sx={{paddingRight: 2}}>
<Button onClick={props.onCancel}>{t("account_upgrade_dialog_button_cancel")}</Button> <Button onClick={props.onCancel}>{t("account_upgrade_dialog_button_cancel")}</Button>
<Button onClick={handleSubmit} disabled={!submitAction}>{submitButtonLabel}</Button> <Button onClick={handleSubmit} disabled={!submitAction}>{submitButtonLabel}</Button>
</DialogFooter> </DialogActions>
</Box>
</Dialog> </Dialog>
); );
}; };