initial commit

This commit is contained in:
equippedcoding-master
2025-09-17 09:37:06 -05:00
parent 86108ca47e
commit e2c98790b2
55389 changed files with 6206730 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>Account</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="css/base.css" rel="stylesheet" />
<script src="https://js.stripe.com/v3/"></script>
<script src="account.js" defer></script>
</head>
<body>
<main>
<h1>Account</h1>
<a href="prices.html">Add a subscription</a>
<a href="register.html">Restart demo</a>
<h2>Subscriptions</h2>
<div id="subscriptions">
<!-- see account.js to see how this div is populated -->
</div>
</main>
</body>
</html>

View File

@@ -0,0 +1,38 @@
document.addEventListener('DOMContentLoaded', async () => {
// Fetch the list of subscriptions for this customer.
const {subscriptions} = await fetch('/subscriptions').then((r) => r.json());
// Construct and display each subscription, its status, last4 of the card
// used, and the current period end.
const subscriptionsDiv = document.querySelector('#subscriptions');
console.log(subscriptions);
subscriptionsDiv.innerHTML = subscriptions.data.map((subscription) => {
console.log(subscription);
let last4 = subscription.default_payment_method?.card?.last4 || '';
return `
<hr>
<h4>
<a href="https://dashboard.stripe.com/test/subscriptions/${subscription.id}">
${subscription.id}
</a>
</h4>
<p>
Status: ${subscription.status}
</p>
<p>
Card last4: ${last4}
</p>
<small>If the last4 is blank, ensure webhooks are being handled. The default payment method is set in the webhook handler.</small>
<p>
Current period end: ${new Date(subscription.current_period_end * 1000)}
</p>
<a href="change-payment-method.html?subscription=${subscription.id}"> Update payment method </a><br />
<a href="change-plan.html?subscription=${subscription.id}"> Change plan </a><br />
<a href="cancel.html?subscription=${subscription.id}"> Cancel </a><br />
`;
}).join('<br />');
});

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>Cancel</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="css/base.css" rel="stylesheet" />
<script src="https://js.stripe.com/v3/"></script>
<script src="cancel.js" defer></script>
</head>
<body>
<main>
<h1>Cancel</h1>
<button id="cancel-btn">Cancel</button>
<div id="messages"></div>
</main>
</body>
</html>

View File

@@ -0,0 +1,41 @@
document.addEventListener('DOMContentLoaded', async () => {
// Fetch the ID of the subscription from the query string
// params.
const params = new URLSearchParams(window.location.search);
const subscriptionId = params.get('subscription');
// When the cancel button is clicked, send an AJAX request
// to our server to cancel the subscription.
const cancelBtn = document.querySelector('#cancel-btn');
cancelBtn.addEventListener('click', async (e) => {
e.preventDefault();
setMessage("Cancelling subscription...");
const {subscription} = await fetch('/cancel-subscription', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
subscriptionId
}),
})
.then((response) => response.json())
// Display the status of the subscription after attempting to
// cancel.
setMessage(`Subscription status: ${subscription.status}`);
setMessage(`Redirecting back to account in 7s.`);
// Redirect to the account page.
setTimeout(() => {
window.location.href = "account.html";
}, 7 * 1000);
});
const setMessage = (message) => {
const messagesDiv = document.querySelector('#messages');
messagesDiv.innerHTML += "<br>" + message;
}
});

View File

@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<title>Change plan</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="css/base.css" rel="stylesheet" />
<script src="https://js.stripe.com/v3/"></script>
<script src="account.js" defer></script>
</head>
<body>
<main>
<h1>Change plan</h1>
Current, new
<div class="price-list">
<div>
<h3>Basic</h3>
<p>
$5.00 / month
</p>
<a href="subscribe.html?price=basic">
Select
</a>
</div>
<div>
<h3>Premium</h3>
<p>
$15.00 / month
</p>
<a href="subscribe.html?price=premium">
Select
</a>
</div>
</div>
</main>
</body>
</html>

View File

@@ -0,0 +1,69 @@
@import url('https://fonts.googleapis.com/css?family=Raleway&display=swap');
:root {
--gray-offset: rgba(0, 0, 0, 0.03);
--gray-border: rgba(0, 0, 0, 0.15);
--gray-light: rgba(0, 0, 0, 0.4);
--gray-mid: rgba(0, 0, 0, 0.7);
--gray-dark: rgba(0, 0, 0, 0.9);
--body-color: var(--gray-mid);
--headline-color: var(--gray-dark);
--accent-color: #ed5f74;
--radius: 6px;
}
body {
padding: 20px;
font-family: 'Raleway';
display: flex;
justify-content: center;
font-size: 1.2em;
}
form > * {
margin: 10px 0;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}
button {
background-color: #ed5f74;
}
button {
background: var(--accent-color);
border-radius: var(--radius);
color: white;
border: 0;
padding: 12px 16px;
margin-top: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
display: block;
}
button:hover {
filter: contrast(115%);
}
button:active {
transform: translateY(0px) scale(0.98);
filter: brightness(0.9);
}
button:disabled {
opacity: 0.5;
cursor: none;
}
/* prices.html */
.price-list {
display: flex;
}
.price-list > div {
flex-grow: 3;
margin: 0 10px;
border: black solid 1px;
padding: 20px;
}

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Start a Subscription</title>
<link href="css/base.css" rel="stylesheet" />
</head>
<body>
<main>
<p>Welcome to the Stripe sample for starting a new fixed price subscription.</p>
<p><a href="register.html">Start Demo</a></p>
</main>
</body>
</html>

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>Subscription prices</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="css/base.css" rel="stylesheet" />
<script src="./prices.js" defer></script>
</head>
<body>
<main>
<h1>Select a plan</h1>
<div id="price-list" class="price-list">
loading...
</div>
</main>
</body>
</html>

View File

@@ -0,0 +1,55 @@
// Fetch price data.
const pricesDiv = document.querySelector('#price-list');
fetch('/config')
.then((response) => response.json())
.then((data) => {
pricesDiv.innerHTML = '';
if(!data.prices) {
pricesDiv.innerHTML = `
<h3>No prices found</h3>
<p>This sample requires two prices, one with the lookup_key sample_basic and another with the lookup_key sample_premium</p>
<p>You can create these through the API or with the Stripe CLI using the provided seed.json fixture file with: <code>stripe fixtures seed.json</code>
`
}
data.prices.forEach((price) => {
pricesDiv.innerHTML += `
<div>
<span>
${price.unit_amount / 100} /
${price.currency} /
${price.recurring.interval}
</span>
<button onclick="createSubscription('${price.id}')">Select</button>
</div>
`;
});
})
.catch((error) => {
console.error('Error:', error);
});
const createSubscription = (priceId) => {
return fetch('/create-subscription', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
priceId: priceId,
}),
})
.then((response) => response.json())
.then((data) => {
window.sessionStorage.setItem('subscriptionId', data.subscriptionId);
window.sessionStorage.setItem('clientSecret', data.clientSecret);
window.location.href = '/subscribe.html';
})
.catch((error) => {
console.error('Error:', error);
});
}

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<title>Register</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="css/base.css" rel="stylesheet" />
<script src="https://js.stripe.com/v3/"></script>
<script src="/register.js" defer></script>
</head>
<body>
<main>
<h1>Sample Photo Service</h1>
<img src="https://picsum.photos/280/320?random=1" width="140" height="160" alt="stock image" />
<p>
Unlimited photo hosting, and more. Cancel anytime.
</p>
<form id="signup-form">
<label>
Email
<input id="email" type="text" placeholder="Email address" value="test@example.com" required />
</label>
<button type="submit">
Register
</button>
</form>
</main>
</body>
</html>

View File

@@ -0,0 +1,31 @@
document.addEventListener('DOMContentLoaded', async () => {
const signupForm = document.querySelector('#signup-form');
if (signupForm) {
signupForm.addEventListener('submit', async (e) => {
e.preventDefault();
// Grab reference to the emailInput. The email address
// entered will be passed to the server and used to create
// a customer. Email addresses do NOT uniquely identify
// customers in Stripe.
const emailInput = document.querySelector('#email');
// Create a customer. This will also set a cookie on the server
// to simulate having a logged in user.
const {customer} = await fetch('/create-customer', {
method: 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: emailInput.value,
}),
}).then(r => r.json());
// Redirect to the pricing page.
window.location.href = '/prices.html';
});
} else {
alert("No sign up form with ID `signup-form` found on the page.");
}
});

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<title>Subscribe</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="css/base.css" rel="stylesheet" />
<script src="https://js.stripe.com/v3/"></script>
<script src="subscribe.js" defer></script>
</head>
<body>
<main>
<h1>Subscribe</h1>
<p>
Try the successful test card: <span>4242424242424242</span>.
</p>
<p>
Try the test card that requires SCA: <span>4000002500003155</span>.
</p>
<p>
Use any <i>future</i> expiry date, CVC, and 5 digit postal code.
</p>
<hr />
<form id="subscribe-form">
<label>
Full name
<input type="text" id="name" value="Jenny Rosen" />
</label>
<div id="card-element">
<!-- the card element will be mounted here -->
</div>
<button type="submit">
Subscribe
</button>
<div id="messages"></div>
</form>
</main>
</body>
</html>

View File

@@ -0,0 +1,62 @@
// helper method for displaying a status message.
const setMessage = (message) => {
const messageDiv = document.querySelector('#messages');
messageDiv.innerHTML += "<br>" + message;
}
// Fetch public key and initialize Stripe.
let stripe, cardElement;
fetch('/config')
.then((resp) => resp.json())
.then((resp) => {
stripe = Stripe(resp.publishableKey);
const elements = stripe.elements();
cardElement = elements.create('card');
cardElement.mount('#card-element');
});
// Extract the client secret query string argument. This is
// required to confirm the payment intent from the front-end.
const subscriptionId = window.sessionStorage.getItem('subscriptionId');
const clientSecret = window.sessionStorage.getItem('clientSecret');
// This sample only supports a Subscription with payment
// upfront. If you offer a trial on your subscription, then
// instead of confirming the subscription's latest_invoice's
// payment_intent. You'll use stripe.confirmCardSetup to confirm
// the subscription's pending_setup_intent.
// See https://stripe.com/docs/billing/subscriptions/trials
// Payment info collection and confirmation
// When the submit button is pressed, attempt to confirm the payment intent
// with the information input into the card element form.
// - handle payment errors by displaying an alert. The customer can update
// the payment information and try again
// - Stripe Elements automatically handles next actions like 3DSecure that are required for SCA
// - Complete the subscription flow when the payment succeeds
const form = document.querySelector('#subscribe-form');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const nameInput = document.getElementById('name');
// Create payment method and confirm payment intent.
stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: nameInput.value,
},
}
}).then((result) => {
if(result.error) {
setMessage(`Payment failed: ${result.error.message}`);
} else {
// Redirect the customer to their account page
setMessage('Success! Redirecting to your account.');
window.location.href = '/account.html';
}
});
});