Roundup and Donate with Stripe

Looking to add Change to a Stripe integration? Change makes it easy to drop donations into any existing payments solution, including Stripe. In this guide, we’ll add the ability for customers to roundup their purchase with a donation.

If you want to jump straight to the whole solution instead, see the solution repo.


Stripe integrations typically follow this architecture:

  1. Client (JS or mobile) requests a payment intent from your server
  2. Client submits the payment intent to Stripe
pre-change integration server architecture

To implement roundups with Stripe, we will add:

  1. Client can update a payment intent
  2. Server receives a webhook from Stripe after payment
pre-change integration server architecture

Let’s start with allowing the customer to add a donation to their cart.

Setting up

To follow along, clone this skeleton repo. This guide is in JavasScript and Ruby, but Change works with any environment.

git clone

Copy the .env.example file into a file named .env in the server folder:

cp .env.example server/.env

You will need a Change account in order to run the demo. Sign up to receive a set of test API keys.

Once you sign up, add your API keys to server/.env:


You will also need a Stripe account in order to run the demo. If you don’t have an account, sign up here. Once you set up your account, go to the Stripe developer dashboard to find your API keys.

Make sure you use your test keys (they start with pk_test and sk_test).


Now we’re ready to start the server. To start the server, run:

cd server
bundle install
ruby server.rb

You can verify that everything is running by running a test transaction.

Visit your browser at localhost:4242. Enter the card number 4242 4242 4242 4242 with any CVV and future date. Click “Pay”. You should see a successful result.

Now we’re ready to implement roundup and donate!

Update payment intents

First, add a checkbox to the cart page to give customers the option to roundup their purchase with a donation.

<!-- TODO: Add checkbox here -->
<input type="checkbox">
Round up and donate to <a href="" target="_blank">Watsi 501(c)3</a>

When the customer checks the box, their amount due will change. So, we need to update their payment intent.

Listen for the checkbox event:

/** TODO: Add event listener here */
.addEventListener("change", function(evt) {

And update the payment intent when the event is fired:

/** TODO: Add handleCheckboxEvent here */
var handleCheckboxEvent = function(id, isDonating) {

const orderData = {
isDonating: isDonating,
id: id
fetch("/update-payment-intent", {
method: "POST",
headers: {
"Content-Type": "application/json"
body: JSON.stringify(orderData)
.then(function(response) {
return response.json();
.then(function(data) {

Now, the payment intent is updated whenever the customer changes their donation preference.

But wait, we haven’t implemented the /update-payment-intent endpoint yet. Let’s do that now.

# TODO: Add /update-payment-intent endpoint here
post '/update-payment-intent' do
content_type 'application/json'
data = JSON.parse(

payment_intent = Stripe::PaymentIntent.retrieve(data['id'])

# Add metadata to track the amount being donated
metadata = payment_intent['metadata'].to_hash.merge(donationAmount: data['isDonating'] ? 46 : 0)

# Update PaymentIntent with new amount
updated_payment_intent = Stripe::PaymentIntent.update(data['id'],
amount: calculate_order_amount(data['isDonating']),
metadata: metadata)

# Send new amount to client
amount: updated_payment_intent['amount']

Submit donation

Now we’re ready to submit the donation using Change.

First, add a /webhook endpoint with some standard Stripe webhook boilerplate for verification and errors:

# TODO: Add /webhook endpoint here
post '/webhook' do
event, data = verify_webhook
if event == nil
status 400

event_type = event['type']
data = event['data']
data_object = data['object']

# Next: Make donation with Change

Then, make a donation with Change.

# Next: Make donation with Change
if event_type != 'payment_intent.succeeded'
if data_object['metadata']['donationAmount']
# Make a donation with using Change
authentication = { username: ENV['CHANGE_PUBLIC_KEY'], password: ENV['CHANGE_SECRET_KEY'] }
response ="",
body: {
amount: data_object['metadata']['donationAmount'],
nonprofit_id: 'n_IfEoPCaPqVsFAUI5xl0CBUOx',
funds_collected: true,
headers: headers,
basic_auth: authentication
puts "✅ Response from Change: #{response.body}"
puts "😀 Customer donated #{data_object['metadata']['donationAmount']} cents"
puts '😶 Received a payment -- customer did not donate.'

content_type 'application/json'
status: 'success'

Now let’s see it work!

Stripe makes it simple to test their webhooks using the Stripe CLI. Get the Stripe CLI using Homebrew (for other installation methods, see this Stripe doc).

brew install stripe/stripe-cli/stripe

Login to the Stripe CLI:

stripe login

Tell Stripe to forward webhooks to the server we’re about to run:

stripe listen --forward-to localhost:4242/webhook

It should print your webhook secret. Grab that secret and put it in server/.env:


Now restart your server.

# Stop your server, then re-run:
ruby server.rb

Your server is now receiving all test webhooks from Stripe! If you have other apps using your test credentials, you may see those webhooks hitting your app. They won’t trigger the donation flow.

Visit your browser at localhost:4242. Enter the card number 4242 4242 4242 4242 with any CVV and future date. Click “Round up and donate”, then click “Pay”.

You should see a successful test donation response in your server output!

Visit your Change dashboard and click “View test data” to see your donation.

Made with ❤ in San Francisco