Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bandcamp-ucashpay

A pay-what-you-want crypto tip + download tool for musicians (Bandcamp-style). Non-custodial.

Fans name their price, get sent to a pay.u.cash checkout to pay in cryptocurrency, and the download is revealed when they return. Payments land directly in the musician's own wallet. No platform custody, no payout wait, no middleman between the fan and the artist.

This is a small static page plus JavaScript. No build step, no backend required for the happy path. Drop it on any static host (GitHub Pages, Netlify, Cloudflare Pages, S3, nginx).

How it works

  1. The fan picks an amount (or 0 for a free download).
  2. app.js builds a hosted pay.u.cash checkout link with the amount, the track title, and an external_reference.
  3. The fan pays in crypto on pay.u.cash. pay.u.cash forwards the funds straight to the address you configured in your store.
  4. On return (?paid=<external_reference>), the download is revealed.

The store Cloud Token used to open the checkout is publishable: it only authorizes receiving funds into your wallet. It is not a private key and cannot move funds out. It is safe to ship in config.js in the browser.

Files

  • index.html - the Bandcamp-style pay-what-you-want form.
  • config.js - the only file an operator edits (Cloud Token, download URL, track metadata).
  • app.js - builds the pay.u.cash embed link and reveals the download on return.
  • package.json - static-site manifest and a one-command local server.

Usage

  1. Clone the repo:

    git clone https://github.com/UdotCASH/bandcamp-ucashpay.git
    cd bandcamp-ucashpay
  2. Edit config.js:

    window.BANDCAMP_UCASHPAY_CONFIG = {
      cloudToken: "st_your_store_cloud_token_here",
      downloadUrl: "./downloads/my-track.zip",
      trackTitle: "My Track",
      trackArtist: "My Name",
      minimumAmount: 0
    };
  3. Put your download file in place, e.g. ./downloads/my-track.zip.

  4. Serve the folder. Any static host works. For local testing:

    npm start
    # then open http://localhost:8080

    For GitHub Pages: push to main, then enable Pages on the repo settings (source: main / root). The page is fully static.

  5. Open the page, set a price, and complete a checkout to confirm the return flow reveals your download.

Set up your pay.u.cash account

  1. Sign up at pay.u.cash, then click the verification link in the email.
  2. Set receive addresses under Settings -> Addresses (raw address, ENS, Unstoppable Domains, or FIO).
  3. Create a store under Account -> Stores and copy its Store Cloud Token (use the store-level token, not the account-wide one).
  4. For fiat cards, connect your own Stripe under Settings -> Payment processors.

The pay.u.cash API used

Two surfaces are used. The browser uses the first; the second is for operators who want server-side tracking/verification.

Client-side hosted pay link (publishable Cloud Token, called straight from the browser, no server secret):

GET https://pay.u.cash/embed.php
  ?cloud=<store Cloud Token>
  &amount=<number>
  &currency=<USD|EUR|...>     (default USD)
  &title=<track title>
  &external_reference=<your id>
  &redirect=<return URL>

app.js builds exactly this URL and navigates to it.

Server-side tracked checkout (call from a server route; idempotent per external_reference):

POST https://pay.u.cash/payment/ajax.php
Content-Type: application/x-www-form-urlencoded

function=create-transaction
&amount=<number>
&currency_code=<USD|EUR|...>
&cryptocurrency_code=                         (empty string)
&external_reference=<your id>
&title=<track title>
&redirect=<return URL>
&cloud=<store Cloud Token>
&idempotent=1

Response JSON: { "success": true, "response": [paymentUrl, transactionId, ...] }. The payment URL is the array element that starts with http:// or https://.

Verifying payment server-side (recommended for production)

By default, the download is revealed purely on the return redirect. That is fine for a tip jar, but a determined fan could fake the return URL. For a hardened download gate, verify the transaction on your server before revealing the file:

  1. Open the checkout using the server-side create-transaction call above (idempotent per external_reference), store the transactionId against the track.
  2. After the fan returns, call pay.u.cash to confirm that transaction is paid (the idempotent create-transaction call with the same external_reference returns the live status).
  3. Only stream/serve the download file from your server when the status is paid.

A minimal Node example:

// server/verify.js (run on your own backend; never expose your account-wide key)
const params = new URLSearchParams({
  function: "create-transaction",
  amount: "5.00",
  currency_code: "USD",
  cryptocurrency_code: "",            // empty: let the fan pick the coin
  external_reference: req.body.ref,   // the id you minted for this checkout
  title: "My Track",
  redirect: "https://yoursite.band/thanks",
  cloud: process.env.STORE_CLOUD_TOKEN,
  idempotent: 1
});

const res = await fetch("https://pay.u.cash/payment/ajax.php", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: params.toString()
});
const json = await res.json();
// json.response[0] starts with http(s):// -> the payment URL
// check the transaction status, then stream the file if paid

Honest limitations

  • No automatic recurring payments. pay.u.cash is pay-per-checkout and non-custodial, so subscription-style billing is not supported. For ongoing support, fans simply pay what they want each time, or use a separate patron setup. This tool is intentionally a one-time tip-and-download flow.
  • Default reveal is trust-the-return. See "Verifying payment server-side" above to harden it.
  • The download file itself is static. Once a fan has the URL they can re-download it. For DRM-free music this is expected (Bandcamp works the same way). Put the file behind your server's verify route if you want a true gate.

Publish (optional)

This is a static site, not a registry package, so there is nothing to publish to npm. The npm start script just pulls in a zero-config static server on demand. If you want to publish it as an npm skeleton anyway:

npm login
npm publish --access public

License

MIT. See LICENSE.

About

A pay-what-you-want crypto tip + download tool for musicians (Bandcamp-style). Non-custodial.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages