Version: 1.0.0
Last updated: 2025-10-12
Table of contents
- Overview
- Features
- Tech stack & files
- Project structure
- Quick start (local)
- PayPal integration
- Client-side Buttons (SDK)
- Hosted buttons fallback
- Server-side webhook (optional)
- Recommended PayPal settings
- Web3 (MetaMask) integration
- Supported flows
- Security & network notes
- Example donation/purchase flow
- Client-side cart & checkout flow
- Carousel, accessibility & responsiveness
- Performance & progressive enhancement
- Security considerations
- Deployment
- Environment variables & configuration
- Testing & debugging
- Extending the project
- Add product management / admin
- Add ERC-20 token payments (USDC)
- Add currency conversion
- Troubleshooting
- License
- Acknowledgements
Cotopia Shop is a lightweight static storefront that demonstrates a small inventory of downloadable templates with PayPal-powered checkout and optional Web3 (MetaMask) wallet connectivity for crypto donations or direct payments. The project contains plain HTML, CSS and vanilla JavaScript (no frameworks), with progressive enhancement: PayPal Buttons are used when available, falling back to hosted PayPal forms embedded per-product.
This repository is intended for small digital goods sellers, templates, and creators who want a simple front-end shop that can be hosted on static hosting (Netlify, Vercel, GitHub Pages) while optionally wiring in server-side webhooks for order verification.
- Static site (HTML/CSS/JS) — no build tools required
- Product carousel with keyboard, touch, and pointer support
- PayPal client-side Buttons (via SDK) for immediate payments
- Fallback to hosted PayPal buttons/forms if SDK not loaded
- Optional server webhook pattern for PayPal order verification & inventory/order tracking
- MetaMask / Web3 wallet connect function for donations and on-chain payments
- Accessible navigation and keyboard-friendly carousel
- Local client-side cart support (localStorage) ready for extension
- Responsive layout, CSS variables, and reduced-motion support
- Plain HTML:
index.html(root page) - Styles:
styles.css - Client JS:
scripts.js - Optional server stub (Node/Express):
server/(webhook example) - PayPal SDK (client-side) via script tag (requires client-id)
- Web3 injected provider (MetaMask) via
window.ethereum
- index.html
- styles.css
- scripts.js
- README.md
- server/
- package.json
- index.js (Express webhook example)
- README.md (server instructions)
- Clone or copy files to your project directory.
- Edit
index.html:- Replace PayPal SDK client id placeholder with your production or sandbox client id:
- Example:
<script src="https://www.paypal.com/sdk/js?client-id=REPLACE_CLIENT_ID¤cy=USD" defer></script>
- Example:
- Replace PayPal SDK client id placeholder with your production or sandbox client id:
- (Optional) Serve locally with a static server. Examples:
- Python 3:
python -m http.server 8000then openhttp://localhost:8000 - Node:
npx serve .ornpx http-server .
- Python 3:
- Open the site in a browser and verify:
- PayPal Buttons render for each product (if SDK loaded).
- Connect MetaMask button prompts wallet connection (if MetaMask installed).
- Carousel is keyboard and pointer/touch operable.
This project supports two modes of PayPal integration:
- Client-side PayPal Buttons (recommended)
- Load PayPal SDK in
index.html:- For production:
https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID¤cy=USD&intent=capture - For sandbox/testing:
https://www.paypal.com/sdk/js?client-id=YOUR_SANDBOX_CLIENT_ID¤cy=USD&intent=capture
- For production:
- The
scripts.jscontainsinitPayPalButtons()which:- Locates
.productnodes - Reads product info from
data-*attributes and.product-price - Renders a PayPal button into each
.paypal-button-container - Creates an order with
actions.order.create()and captures ononApprove - Handles
onErrorand provides a graceful alert fallback
- Locates
Notes:
- Use your sandbox client id for testing. Transactions in sandbox will not move real funds.
- Keep
intent=capturefor immediate payments or useintent=authorizeif you want later capture. - Use
purchase_units[].reference_idorcustom_idfor internal product/SKU mapping.
- Hosted button / HTML form fallback
- Each product includes an embedded PayPal hosted button form (older PayPal flow).
- If the SDK is unavailable,
initPayPalButtons()will create a fallback "Buy with PayPal" button that submits the hosted form, opening PayPal in the configured target (often_top).
Server-side webhook (recommended for order verification)
- Relying on client-side capture only is convenient but not sufficient for secure fulfillment. Implement a server endpoint to:
- Receive PayPal Webhook events (PAYMENT.CAPTURE.COMPLETED, CHECKOUT.ORDER.APPROVED, etc.)
- Verify webhook signatures using PayPal's API (transmission verification)
- Retrieve order details with PayPal API (orders/v2/orders/{id} or payments API)
- Fulfill the order (send download link to buyer email, mark inventory, create order record)
- Example server stub is provided in
server/index.js:- Use
express.json()to parse PayPal webhook POSTs - Validate webhooks using PayPal verification endpoints or SDK (recommended)
- Use
Recommended PayPal settings
- Use REST API client credentials (client id + secret) to verify/call PayPal securely from the server (never put the secret in client-side code).
- Enable Webhooks in your PayPal developer dashboard; configure your server webhook URL and subscribe to relevant events:
- CHECKOUT.ORDER.APPROVED
- PAYMENT.CAPTURE.COMPLETED
- PAYMENT.CAPTURE.DENIED
- PAYMENT.AUTHORIZATION.CREATED (if using authorize)
- Use the sandbox environment for development and testing. Switch to live client-id and live webhook URL for production.
What is included
connectMetaMask()inindex.html(orscripts.js) detectswindow.ethereumand callseth_requestAccounts.- The project currently demonstrates wallet connectivity and a basic on-page confirmation alert.
Supported flows (examples you may implement)
- Donations (ETH): The simplest flow — user connects wallet and sends ETH to the shop's wallet address with a standard transaction.
- Direct purchase (ETH or ERC-20):
- Option A (simple): Accept ETH transfers to a recipient address, verify payment off-chain (server monitoring) and deliver product manually or via automated system.
- Option B (contract-based): Deploy a smart contract for purchase logic, add purchase functions for token payments, and call contract from client using Web3/Ethers. This is more secure and automatable but requires contract development, deployment, and auditing.
Security & network notes
- Never store private keys or sign transactions server-side unless using secure vaults and keys that are not publicly accessible.
- For production purchases on-chain:
- Use a server to observe on-chain payments (via an indexer, Alchemy, Infura, or a lightweight node) and verify that the transaction contains expected data/value.
- Confirm the correct network (mainnet vs testnet).
- For token payments (ERC-20), remember to require token approvals and verify token transfer receipts.
Example donation/purchase flow (ETH)
- Buyer clicks "Connect To Web 3 Wallet" and signs wallet connection.
- UI shows a "Donate" or "Pay" button with preset amount and recipient address.
- On click, call
ethereum.request({ method: 'eth_sendTransaction', params: [...] }). - After tx hash is returned, monitor transaction status and confirm receipt (server or client) before delivering product.
- The project includes product nodes with
data-*attributes. To build a cart:- Add "Add to cart" buttons that push product data into an in-memory array and persist to
localStorage. - Create a
/cartUI pane or modal summarizing items, quantity, and total. - Allow checkout via PayPal Buttons that use aggregated
purchase_units(PayPal supports multiple items in a single order). - For hosted forms per-product, server-side consolidation may be required (i.e., create an order server-side then redirect to PayPal).
- Add "Add to cart" buttons that push product data into an in-memory array and persist to
- Security: do not trust client-side totals for fulfillment. Recalculate totals on the server or verify order details via PayPal order ID after capture.
- Carousel supports:
- Keyboard navigation (ArrowLeft / ArrowRight)
- Touch and pointer dragging (pointer events + touch fallback)
- Dynamic responsiveness: visible slides adapt to viewport width
- Accessibility:
- Skip link for screen readers
- ARIA attributes for the carousel role/labels and products
- Focus styles using a CSS variable
--focus - Ensure color contrast: consider swapping the current bright blue/black combination to accessible colors (WCAG AA/AAA). See "Visual & accessibility notes" below.
- Images use
loading="lazy"attribute. - If JS fails or is blocked, the page still renders content and hosted PayPal forms still work.
- For better performance:
- Optimize images (WebP, properly scaled)
- Minify CSS/JS for production
- Use HTTP caching and serve assets from a CDN
- Defer non-critical third-party scripts where possible
- Never embed PayPal secret keys or server credentials in client files.
- Validate PayPal webhooks server-side with PayPal verification.
- Sanitize any input that will be persisted or used in emails/download links.
- Run integrated third-party scripts (PayPal, Web3 providers) from reputable sources and pin to latest stable versions where possible.
- Use HTTPS in production. Mixed content (HTTP + HTTPS) will break wallet and payment flows.
Static hosting
- This site can be deployed as static files:
- GitHub Pages — push files to
gh-pagesormainwith appropriate config - Netlify / Vercel — drag-and-drop or connect repository
- S3 + CloudFront — upload static files and configure distribution
- GitHub Pages — push files to
Server component (optional)
- If you use webhooks or server-side order verification, you will deploy the
server/component to any Node-capable hosting:- Heroku, Railway, Render, Fly, DigitalOcean App Platform, AWS Elastic Beanstalk
- Ensure your server has a public HTTPS URL for PayPal webhooks.
Client-side:
PAYPAL_CLIENT_ID— Replace inindex.htmlPayPal SDK script tag (do not store secret).PAYPAL_CURRENCY— USD by default; change if needed.
Server-side (example)
PAYPAL_CLIENT_ID— (optional) used for server REST callsPAYPAL_CLIENT_SECRET— used for server REST calls (NEVER in client)PAYPAL_WEBHOOK_ID— webhook ID for validationNODE_ENV— production/developmentPORT— server port
- PayPal sandbox:
- Create a PayPal developer account: https://developer.paypal.com
- Create sandbox accounts (buyer & merchant)
- Use the sandbox client id and sandbox test accounts to simulate purchases
- MetaMask:
- Use MetaMask with a testnet (Goerli / Sepolia) while developing
- Use faucets to fund test accounts
- Browser console:
- Watch for PayPal SDK logs and errors
- Check for CORS or mixed-content errors when webhook endpoints or assets are not served securely
Add product management / admin
- Add a small admin JSON file or a protected server endpoint to manage inventory, price and hosted PayPal button ids.
- Secure admin UI behind HTTP basic auth or a server-side auth system.
Add ERC-20 token payments (USDC)
- Use web3/ethers to request approvals and token transfer to recipient (or to a smart contract).
- Verify transfer receipts via a server or on-chain indexer.
Add currency conversion
- Use a 3rd-party rates API (e.g., exchangerate.host, Open Exchange Rates) on the server to compute local currency prices and pass values to PayPal.
- PayPal Buttons not rendering:
- Make sure you replaced
REPLACE_WITH_YOUR_CLIENT_IDwith a valid client id in the SDK script tag. - Check the browser console for SDK load errors and network requests.
- Confirm no duplicate PayPal SDK tags are present (duplicate tags cause conflicts).
- Make sure you replaced
- Hosted button not working:
- Inspect the embedded form
actionandhosted_button_idvalues. - If PayPal returns an error, open the form directly in a new tab to see PayPal diagnostics.
- Inspect the embedded form
- MetaMask connect fails:
- Ensure MetaMask extension is installed and the site is served over HTTPS (or localhost).
- Check for rejected requests if the user denies connection.
- Webhook events not being delivered:
- Ensure your server accepts POST requests and is reachable over HTTPS from PayPal.
- Verify webhook signatures with PayPal's verification endpoint and check your webhook dashboard for delivery logs.
- The provided default CSS uses a strong blue background and black/blue text which may not meet WCAG contrast ratios. Consider:
- Using a light background with dark text, or ensuring text color contrast ratio >= 4.5:1 for body copy.
- Making focus outlines more prominent for keyboard users.
- Ensuring interactive elements (buttons/links) have adequate hit area and visible focus state.
- Provide alt text for all product images (the HTML has alt attributes, review content).
A minimal Express example (see server/index.js) should:
- Accept
POST /webhook/paypal - Parse JSON body
- Verify webhook event signature with PayPal
- Call PayPal orders API to retrieve order details if needed
- Mark order as fulfilled in your database and send a fulfillment email
Example endpoints:
- POST /webhook/paypal
- POST /create-order (server-side create order if you prefer server-created orders)
- GET /orders/:id (order status)
Troubleshooting webhooks
- Use ngrok or similar to expose local dev server to PayPal while testing
- Check the PayPal developer dashboard "Webhook events" list for delivery status
- Log raw webhook bodies and PayPal verification responses for debugging
GNU v3.0 License — see LICENSE file
Attribution: PayPal SDK and Web3/MetaMask are third-party software with their own usage policies.
- PayPal developer docs: https://developer.paypal.com
- MetaMask / Ethereum docs: https://docs.metamask.io, https://ethereum.org
- Inspiration for styling and interactions derived from modern e-commerce micro-frontends and accessibility best-practices
If you need an enhanced implementation (server order processing, multi-currency, digital-download delivery automation, or smart-contract integration), provide:
- Preferred hosting for server webhook (Heroku, Railway, Vercel Serverless, etc.)
- PayPal client-id(s) (sandbox + live)
- Server language preference (Node/Express example included)
- Preferred crypto token / network (ETH mainnet/testnet, USDC, others)
End of README.