your-ai-workflow-firebase-os 1.2.60 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/index.d.ts +26 -0
- package/dist/server.cjs.js +25 -0
- package/dist/server.es.js +670 -0
- package/dist/vite.cjs.js +1 -1
- package/dist/vite.es.js +2 -2
- package/dist/your-ai-workflow-firebase-os.cjs.js +6 -6
- package/dist/your-ai-workflow-firebase-os.css +1 -1
- package/dist/your-ai-workflow-firebase-os.es.js +204 -174
- package/package.json +21 -2
- package/scripts/postinstall.js +24 -3
- package/server.template.ts +65 -0
- package/src/index.css +4 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Request, Router } from 'express';
|
|
2
|
+
import { default as Stripe } from 'stripe';
|
|
3
|
+
import { VerifiedUser } from './auth';
|
|
4
|
+
export interface ApiRoutesOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Called for every Stripe event that arrives at the webhook, after its
|
|
7
|
+
* signature is verified. This is where an app reacts to something that
|
|
8
|
+
* happened outside it — email a receipt, notify the owner of a refund.
|
|
9
|
+
* Reading payment state needs nothing here; the routes read Stripe live.
|
|
10
|
+
*/
|
|
11
|
+
onStripeEvent?: (event: Stripe.Event) => void | Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export interface AuthedRequest extends Request {
|
|
14
|
+
user?: VerifiedUser;
|
|
15
|
+
/** The caller's own ID token — Firestore reads are made as them, never as us. */
|
|
16
|
+
idToken?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function apiRoutes(options?: ApiRoutesOptions): Router;
|
|
19
|
+
export { projectId, verifyIdToken } from './auth';
|
|
20
|
+
export { runSelfTest, selfTestAllowed } from './selftest';
|
|
21
|
+
export { checkoutProblem, grantsAccess, returnUrls, stripeMode, MAX_ADHOC_AMOUNT, MIN_ADHOC_AMOUNT, } from './stripeClient';
|
|
22
|
+
export type { CheckoutRequest } from './stripeClient';
|
|
23
|
+
export type { Check, SelfTestResult } from './selftest';
|
|
24
|
+
export { TEMPLATES, renderTemplate, escapeHtml, fill, adminEmails } from './email';
|
|
25
|
+
export type { EmailTemplate, RenderedEmail } from './email';
|
|
26
|
+
export { readDocument, emailOnRecord, pathProblem, fromFields } from './firestore';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunk-DYteiM-1.cjs`);let t=require(`express`);t=e.i(t,1);let n=require(`node:crypto`),r=require(`stripe`);r=e.i(r,1);var i=`https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com`,a=60;function o(e,t,n,r=Math.floor(Date.now()/1e3)){return n?e.alg===`RS256`?e.kid?t.aud===n?t.iss===`https://securetoken.google.com/${n}`?t.sub?t.sub.length>128?`Token subject is not a valid uid.`:typeof t.exp!=`number`||t.exp<=r?`Token has expired.`:typeof t.iat!=`number`||t.iat>r+a?`Token was issued in the future.`:null:`Token has no subject.`:`Token has the wrong issuer.`:`Token was issued for a different Firebase project.`:`Token has no key id.`:`Token is not signed with RS256.`:`The server does not know its Firebase project id.`}function s(e){return Buffer.from(e.replace(/-/g,`+`).replace(/_/g,`/`),`base64`)}function c(e){let t=String(e||``).split(`.`);if(t.length!==3)return null;try{return{header:JSON.parse(s(t[0]).toString(`utf8`)),claims:JSON.parse(s(t[1]).toString(`utf8`)),signed:`${t[0]}.${t[1]}`,signature:s(t[2])}}catch{return null}}var l=null;async function u(){if(l&&l.expiresAt>Date.now())return l.keys;let e=await fetch(i);if(!e.ok)throw Error(`Could not fetch Google signing keys (${e.status}).`);let t=await e.json(),n=/max-age=(\d+)/.exec(e.headers.get(`cache-control`)||``);return l={keys:t,expiresAt:Date.now()+(n?Number(n[1]):3600)*1e3},t}async function d(e,t){let r=c(e);if(!r)throw Error(`That is not a valid sign-in token.`);let i=o(r.header,r.claims,t);if(i)throw Error(i);let a=(await u())[r.header.kid];if(!a)throw Error(`Token was signed with a key Google no longer publishes.`);let s=(0,n.createVerify)(`RSA-SHA256`);if(s.update(r.signed),s.end(),!s.verify(a,r.signature))throw Error(`Token signature does not check out.`);return{uid:r.claims.sub,email:r.claims.email||null,emailVerified:!!r.claims.email_verified}}function f(){return(process.env.VITE_FIREBASE_PROJECT_ID||process.env.FIREBASE_PROJECT_ID||``).trim()}var p=`https://firestore.googleapis.com/v1`;function m(e){return e==null?null:`stringValue`in e?e.stringValue:`integerValue`in e?Number(e.integerValue):`doubleValue`in e?e.doubleValue:`booleanValue`in e?e.booleanValue:`timestampValue`in e?e.timestampValue:`nullValue`in e?null:`mapValue`in e?h(e.mapValue?.fields||{}):`arrayValue`in e?(e.arrayValue?.values||[]).map(m):null}function h(e){let t={};for(let[n,r]of Object.entries(e||{}))t[n]=m(r);return t}function g(e,t){let n=`${e}/${t}`.split(`/`);if(n.length%2!=0)return`That is not a document path.`;for(let e of n)if(!e||e===`.`||e===`..`||!/^[A-Za-z0-9_.@~+-]+$/.test(e))return`That is not a document path.`;return null}async function _(e,t,n,r){let i=g(t,n);if(i)throw Error(i);let a=`${p}/projects/${e}/databases/(default)/documents/${t}/${n}`,o=await fetch(a,{headers:r?{Authorization:`Bearer ${r}`}:{}});if(o.status===404)return null;if(o.status===403)throw Error(`You do not have permission to read that record.`);if(!o.ok)throw Error(`Firestore refused the read (${o.status}).`);return h((await o.json()).fields||{})}var ee=[`email`,`Email`,`emailAddress`,`email_address`,`userEmail`,`contactEmail`];function v(e){for(let t of ee){let n=e[t];if(typeof n==`string`&&n.includes(`@`))return n.trim()}return null}var te=`https://api.resend.com/emails`;function y(){return(process.env.RESEND_API_KEY||``).trim()}function b(){return(process.env.EMAIL_FROM||``).trim()||`onboarding@resend.dev`}function x(){return(process.env.EMAIL_REPLY_TO||``).trim()||void 0}function S(){return(process.env.VITE_ADMIN_EMAILS||process.env.ADMIN_EMAILS||``).split(/[,\s]+/).map(e=>e.trim().toLowerCase()).filter(e=>e.includes(`@`))}var C={test:{audience:`admins`,subject:`Test email from {{appName}}`,body:`This is a test.
|
|
2
|
+
|
|
3
|
+
If you are reading it, {{appName}} can send email.
|
|
4
|
+
|
|
5
|
+
Sent {{now}}.`,description:`Proves the connection works. Goes to the admin addresses.`},new_submission:{audience:`admins`,subject:`New {{formName}} submission`,body:`Someone filled in {{formName}} on {{appName}}.
|
|
6
|
+
|
|
7
|
+
From: {{name}} ({{email}})
|
|
8
|
+
|
|
9
|
+
{{summary}}
|
|
10
|
+
|
|
11
|
+
Open the app to see the full entry.`,description:`Tells the owner a form was filled in.`},submission_received:{audience:`record`,subject:`We got your message`,body:`Hi {{name}},
|
|
12
|
+
|
|
13
|
+
Thanks for getting in touch — we have your message and will come back to you shortly.
|
|
14
|
+
|
|
15
|
+
— {{appName}}`,description:`Confirms to the sender that their form arrived.`},order_confirmed:{audience:`record`,subject:`Your order is confirmed`,body:`Hi {{name}},
|
|
16
|
+
|
|
17
|
+
Thank you — your payment of {{amount}} went through and your order is confirmed.
|
|
18
|
+
|
|
19
|
+
{{details}}
|
|
20
|
+
|
|
21
|
+
— {{appName}}`,description:`Sent to the buyer after a successful payment.`},new_sale:{audience:`admins`,subject:`You made a sale — {{amount}}`,body:`{{name}} ({{email}}) just paid {{amount}} on {{appName}}.
|
|
22
|
+
|
|
23
|
+
{{details}}`,description:`Tells the owner money came in.`}};function w(e){return e?Object.prototype.hasOwnProperty.call(C,e)?null:`There is no email template called "${e}". Available: ${Object.keys(C).join(`, `)}.`:`No template was named.`}function T(e){return String(e).replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`).replace(/'/g,`'`)}function E(e,t,n){return String(e).replace(/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g,(e,r)=>{let i=t[r];return i==null?``:n?T(String(i)):String(i)})}function D(e,t){let n=E(e.body,t,!1);return{subject:E(e.subject,t,!1).slice(0,200),text:n,html:`<div style="font-family:system-ui,-apple-system,Segoe UI,sans-serif;font-size:15px;line-height:1.6;color:#1e293b">`+E(e.body,t,!0).split(`
|
|
24
|
+
|
|
25
|
+
`).map(e=>`<p>${e.replace(/\n/g,`<br>`)}</p>`).join(``)+`</div>`}}async function O(e,t){let n=y();if(!n)return{id:null,error:`RESEND_API_KEY is not set.`};if(!e.length)return{id:null,error:`There is nobody to send this to.`};let r=await fetch(te,{method:`POST`,headers:{Authorization:`Bearer ${n}`,"Content-Type":`application/json`},body:JSON.stringify({from:b(),to:e,reply_to:x(),subject:t.subject,html:t.html,text:t.text})}),i=await r.json().catch(()=>({}));return r.ok?{id:i?.id||null,error:null}:{id:null,error:i?.message||`Resend refused the send (${r.status}).`}}var k=null;function A(){return(process.env.STRIPE_SECRET_KEY||``).trim()}function j(){let e=A();return e?e.startsWith(`sk_live_`)?`live`:`test`:null}function M(){let e=A();if(!e)throw Error(`STRIPE_SECRET_KEY is not set.`);return k||=new r.default(e),k}var N=Number(process.env.STRIPE_MAX_ADHOC_AMOUNT||1e4),P=.5;function F(e){let{priceId:t,amount:n,mode:r=`payment`,quantity:i=1}=e||{};if(!t&&n===void 0)return`This button does not say what is being sold.`;if(t&&n!==void 0)return`A payment has either a Stripe price or an amount, not both.`;if(r!==`payment`&&r!==`subscription`)return`A payment is either one-time or a subscription.`;if(!Number.isInteger(i)||i<1||i>999)return`Quantity must be a whole number between 1 and 999.`;if(n!==void 0){if(r===`subscription`)return`A subscription needs a price created in Stripe — a typed amount cannot recur.`;if(typeof n!=`number`||!Number.isFinite(n))return`That amount is not a number.`;if(n<.5)return`The smallest amount that can be charged is ${P}.`;if(n>N)return`That amount is above the ${N} limit for typed prices. Use a price created in Stripe.`;if(!e.productName||!String(e.productName).trim())return`A typed amount needs a name for what is being bought.`}return t&&!/^price_[A-Za-z0-9]+$/.test(t)?`That does not look like a Stripe price id.`:null}function I(e){return Math.round(e*100)}function L(e,t,n){let r=String(e||``).replace(/\/+$/,``),i=(e,t)=>{let n=String(e||t);return n.startsWith(`/`)?n:`/${n}`};return{success:`${r}${i(t,`/payment-complete`)}?session_id={CHECKOUT_SESSION_ID}`,cancel:`${r}${i(n,`/payment-cancelled`)}`}}function R(e){return e===`active`||e===`trialing`||e===`past_due`}var z=e=>!!(process.env[e]||``).trim();function B(e){let t=(process.env.SELFTEST_TOKEN||``).trim();return t?String(e||``)===t:!0}async function V(){let e=[{name:`Server`,status:`pass`,detail:`The backend is running and reachable.`}],t=f();if(!t)e.push({name:`Firebase project`,status:`fail`,detail:`VITE_FIREBASE_PROJECT_ID is not set, so the server cannot tell who is signed in.`});else{e.push({name:`Firebase project`,status:`pass`,detail:`Signing in against ${t}.`});try{let t=await u();e.push({name:`Sign-in verification`,status:`pass`,detail:`Google's signing keys are reachable (${Object.keys(t).length} in rotation).`})}catch(t){e.push({name:`Sign-in verification`,status:`fail`,detail:`Could not reach Google's signing keys: ${t.message}`})}}if(!A())e.push({name:`Stripe`,status:`warn`,detail:`STRIPE_SECRET_KEY is not set. Payments are switched off until it is.`});else{let t=j();try{let n=await M().accounts.retrieve();e.push({name:`Stripe`,status:`pass`,detail:`Connected in ${t} mode to ${n.business_profile?.name||n.id}.`}),t===`live`&&e.push({name:`Live mode`,status:`warn`,detail:`This app is charging real cards. Every payment made here is real money.`})}catch(t){e.push({name:`Stripe`,status:`fail`,detail:`The key is set but Stripe refused it: ${t.message}`})}e.push(z(`STRIPE_WEBHOOK_SECRET`)?{name:`Stripe webhook`,status:`pass`,detail:`Signed webhooks will be accepted.`}:{name:`Stripe webhook`,status:`warn`,detail:`STRIPE_WEBHOOK_SECRET is not set. Payments still confirm when the payer returns; refunds and cancellations made in the Stripe dashboard will not reach the app until it is.`})}return e.push(z(`RESEND_API_KEY`)?{name:`Email`,status:`pass`,detail:`RESEND_API_KEY is set.`}:{name:`Email`,status:`warn`,detail:`RESEND_API_KEY is not set. The app will not send its own email.`}),{ok:!e.some(e=>e.status===`fail`),service:`your-ai-workflow-firebase-os`,checks:e}}var H=(e,t,n,r={})=>e.status(t).json({success:!1,error:n,...r});function U(e,t){let n=t instanceof Error?t.message:`Something went wrong on the server.`,r=n.includes(`STRIPE_SECRET_KEY`)||n.includes(`project id`);return H(e,r?503:500,n,r?{requiresSetup:!0}:{})}async function W(e,t,n){let r=String(e.headers.authorization||``),i=r.startsWith(`Bearer `)?r.slice(7):``;if(!i)return H(t,401,`Please sign in first.`);try{e.user=await d(i,f()),e.idToken=i,n()}catch(e){return H(t,401,e.message)}}async function G(e,t){return!t.email||!t.emailVerified?null:(await e.customers.list({email:t.email,limit:1})).data[0]||null}async function K(e,t){let n=await G(e,t);return n?(n.metadata?.uid!==t.uid&&await e.customers.update(n.id,{metadata:{...n.metadata,uid:t.uid}}),n):e.customers.create({email:t.email||void 0,metadata:{uid:t.uid}})}function q(e){return e?.current_period_end??e?.items?.data?.[0]?.current_period_end??null}function J(e){let t=e.items?.data?.[0],n=t?.price;return{id:e.id,status:e.status,active:R(e.status),priceId:n?.id||null,productName:typeof n?.product==`object`&&n.product?.name||null,amount:n?.unit_amount==null?null:n.unit_amount/100,currency:n?.currency||null,interval:n?.recurring?.interval||null,quantity:t?.quantity??1,currentPeriodEnd:q(e),cancelAtPeriodEnd:!!e.cancel_at_period_end}}function ne(e){return{id:e.id,amount:e.amount/100,currency:e.currency,created:e.created,description:e.description||null,paid:e.paid&&e.status===`succeeded`,refunded:e.refunded,amountRefunded:e.amount_refunded/100,fullyRefunded:e.refunded&&e.amount_refunded>=e.amount}}var re=600*1e3,ie=Number(process.env.EMAIL_RATE_PER_CALLER||20),ae=Number(process.env.EMAIL_RATE_PER_HOUR||200),Y=new Map,X=[];function Z(e){let t=Date.now();for(;X.length&&t-X[0]>3600*1e3;)X.shift();if(X.length>=ae)return`This app has sent its hourly limit of email. Try again shortly.`;let n=(Y.get(e)||[]).filter(e=>t-e<re);return n.length>=ie?(Y.set(e,n),`Too many messages from here just now. Try again in a few minutes.`):(n.push(t),Y.set(e,n),X.push(t),Y.size>5e3&&Y.clear(),null)}var Q=e=>String(e.ip||e.socket?.remoteAddress||`unknown`),$=new Set;function oe(e){return $.has(e)?!0:($.add(e),$.size>500&&$.delete($.values().next().value),!1)}function se(e={}){let n=t.default.Router();return n.post(`/payments/webhook`,t.default.raw({type:`application/json`}),async(t,n)=>{let r=(process.env.STRIPE_WEBHOOK_SECRET||``).trim(),i=t.headers[`stripe-signature`];if(!r)return H(n,503,`STRIPE_WEBHOOK_SECRET is not set.`);if(!i)return H(n,400,`Missing stripe-signature header.`);let a;try{a=M().webhooks.constructEvent(t.body,i,r)}catch(e){return H(n,400,`Webhook signature check failed: ${e.message}`)}if(oe(a.id))return n.json({received:!0,duplicate:!0});try{await e.onStripeEvent?.(a)}catch(e){return $.delete(a.id),H(n,503,`Handler failed: ${e.message}`)}return n.json({received:!0})}),n.use(t.default.json()),n.get(`/_selftest`,async(e,t)=>{if(!B(e.query.token))return H(t,403,`This check needs its token.`);try{let e=await V();return t.status(e.ok?200:500).json(e)}catch(e){return U(t,e)}}),n.get(`/_selftest/checkout`,async(e,t)=>{if(!B(e.query.token))return H(t,403,`This check needs its token.`);if(j()!==`test`)return H(t,403,`The test checkout only runs against a test key (sk_test_).`);try{let n=e.headers.origin||`${e.protocol}://${e.get(`host`)}`,r=L(String(n),`/api/_selftest/paid`,`/api/_selftest/cancelled`),i=await M().checkout.sessions.create({mode:`payment`,line_items:[{quantity:1,price_data:{currency:`usd`,product_data:{name:`Self-test payment`},unit_amount:100}}],success_url:r.success,cancel_url:r.cancel,metadata:{selftest:`true`}});return t.redirect(303,i.url)}catch(e){return U(t,e)}}),n.get(`/_selftest/paid`,async(e,t)=>{let n=String(e.query.session_id||``);if(!n)return H(t,400,`No session id came back.`);try{let e=await M().checkout.sessions.retrieve(n),r=e.payment_status===`paid`;return t.status(r?200:402).json({ok:r,message:r?`The whole loop works: checkout created, card charged, payment confirmed by the server.`:`Stripe says this payment is ${e.payment_status}.`,amount:e.amount_total==null?null:e.amount_total/100,currency:e.currency,email:e.customer_details?.email||null})}catch(e){return U(t,e)}}),n.get(`/_selftest/cancelled`,(e,t)=>t.json({ok:!0,message:`Payment cancelled — nothing was charged.`})),n.get(`/_selftest/email`,async(e,t)=>{if(!B(e.query.token))return H(t,403,`This check needs its token.`);if(!y())return H(t,503,`RESEND_API_KEY is not set.`);let n=S();if(!n.length)return H(t,503,`VITE_ADMIN_EMAILS is empty, so there is no admin address to send to.`);let r=await O(n,D(C.test,{appName:process.env.APP_NAME||`your app`,now:new Date().toISOString()}));return t.status(r.error?502:200).json({ok:!r.error,sentTo:n,from:b(),id:r.id,error:r.error,message:r.error?`Resend refused the send.`:`Sent. If it does not arrive within a minute, check spam — an unverified domain often lands there.`})}),n.get(`/email/status`,(e,t)=>{t.json({configured:!!y(),from:b(),adminCount:S().length,templates:Object.entries(C).map(([e,t])=>({id:e,audience:t.audience,description:t.description}))})}),n.post(`/email/notify`,async(e,t)=>{let n=Z(Q(e));if(n)return H(t,429,n);let{template:r,data:i}=e.body||{},a=w(String(r||``));if(a)return H(t,400,a);let o=C[String(r)];if(o.audience!==`admins`)return H(t,400,`The "${r}" template is written to a customer, not to you.`);let s=S();if(!s.length)return H(t,503,`There is no admin address to send to.`);let c=await O(s,D(o,{appName:process.env.APP_NAME||`your app`,...i||{}}));return t.status(c.error?502:200).json({success:!c.error,id:c.id,error:c.error})}),n.post(`/email/send`,async(e,t)=>{let n=Z(Q(e));if(n)return H(t,429,n);let{template:r,collection:i,docId:a,data:o}=e.body||{},s=w(String(r||``));if(s)return H(t,400,s);if(!i||!a)return H(t,400,`Name the record this email is about.`);let c=C[String(r)];if(c.audience!==`record`)return H(t,400,`The "${r}" template is written to you, not to a customer. Use /email/notify.`);try{let n=String(e.headers.authorization||``),r=n.startsWith(`Bearer `)?n.slice(7):void 0,s=await _(f(),String(i),String(a),r);if(!s)return H(t,404,`That record does not exist.`);let l=v(s);if(!l)return H(t,422,`That record has no email address on it.`);let u=D(c,{appName:process.env.APP_NAME||`your app`,...s,...o||{}}),d=await O([l],u);return t.status(d.error?502:200).json({success:!d.error,id:d.id,error:d.error})}catch(e){return H(t,400,e.message)}}),n.get(`/payments/status`,(e,t)=>{let n=j();t.json({configured:!!A(),mode:n,webhookConfigured:!!(process.env.STRIPE_WEBHOOK_SECRET||``).trim(),message:n?`Stripe is connected in ${n} mode.`:`Add STRIPE_SECRET_KEY to start taking payments.`})}),n.post(`/payments/checkout`,async(e,t)=>{let n=e.body||{},r=F(n);if(r)return H(t,400,r);let i=n.origin||e.headers.origin;if(!i)return H(t,400,`The page did not say where to send the payer back to.`);try{let r=M(),a=null,o=String(e.headers.authorization||``);if(o.startsWith(`Bearer `))try{a=await d(o.slice(7),f())}catch{a=null}let s=a?await K(r,a):null,c=L(String(i),n.successPath,n.cancelPath),l=n.priceId?{price:n.priceId,quantity:n.quantity||1}:{quantity:n.quantity||1,price_data:{currency:n.currency||`usd`,product_data:{name:String(n.productName)},unit_amount:I(Number(n.amount))}},u=await r.checkout.sessions.create({mode:n.mode||`payment`,line_items:[l],success_url:c.success,cancel_url:c.cancel,customer:s?.id,allow_promotion_codes:!0,metadata:{...n.metadata||{},...a?{uid:a.uid}:{}}});return t.json({success:!0,sessionId:u.id,url:u.url})}catch(e){return U(t,e)}}),n.get(`/payments/session/:id`,async(e,t)=>{try{let n=await M().checkout.sessions.retrieve(String(e.params.id));return t.json({success:!0,paid:n.payment_status===`paid`,paymentStatus:n.payment_status,mode:n.mode,amountTotal:n.amount_total==null?null:n.amount_total/100,currency:n.currency,customerEmail:n.customer_details?.email||null,customerName:n.customer_details?.name||null,metadata:n.metadata||{}})}catch(e){return(e instanceof Error?e.message:`Unknown error`).includes(`STRIPE_SECRET_KEY`)?U(t,e):H(t,404,`That payment could not be found.`)}}),n.get(`/payments/me`,W,async(e,t)=>{try{let n=M(),r=await G(n,e.user);if(!r)return t.json({success:!0,customerId:null,active:!1,subscriptions:[],payments:[]});let[i,a]=await Promise.all([n.subscriptions.list({customer:r.id,status:`all`,limit:10,expand:[`data.items.data.price.product`]}),n.charges.list({customer:r.id,limit:20})]),o=i.data.map(J),s=a.data.map(ne);return t.json({success:!0,customerId:r.id,email:r.email,active:o.some(e=>e.active)||s.some(e=>e.paid&&!e.fullyRefunded),subscriptions:o,payments:s})}catch(e){return U(t,e)}}),n.post(`/payments/portal`,W,async(e,t)=>{try{let n=M(),r=await G(n,e.user);if(!r)return H(t,404,`There is nothing to manage on this account yet.`);let i=e.body?.origin||e.headers.origin;if(!i)return H(t,400,`The page did not say where to send them back to.`);let a=await n.billingPortal.sessions.create({customer:r.id,return_url:String(i).replace(/\/+$/,``)});return t.json({success:!0,url:a.url})}catch(e){return U(t,e)}}),n}exports.MAX_ADHOC_AMOUNT=N,exports.MIN_ADHOC_AMOUNT=P,exports.TEMPLATES=C,exports.adminEmails=S,exports.apiRoutes=se,exports.checkoutProblem=F,exports.emailOnRecord=v,exports.escapeHtml=T,exports.fill=E,exports.fromFields=h,exports.grantsAccess=R,exports.pathProblem=g,exports.projectId=f,exports.readDocument=_,exports.renderTemplate=D,exports.returnUrls=L,exports.runSelfTest=V,exports.selfTestAllowed=B,exports.stripeMode=j,exports.verifyIdToken=d;
|