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.
Overview
Stripe integrations typically follow this architecture:
- Client (JS or mobile) requests a payment intent from your server
- Client submits the payment intent to Stripe

To implement roundups with Stripe, we will add:
- Client can update a payment intent
- Server receives a webhook from Stripe after payment

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 git@github.com:get-change/roundup-and-donate-with-stripe-skeleton.git
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
:
CHANGE_PUBLIC_KEY=<replace-with-your-public-key>
CHANGE_SECRET_KEY=<replace-with-your-secret-key>
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
).
STRIPE_PUBLISHABLE_KEY=<replace-with-your-publishable-key>
STRIPE_SECRET_KEY=<replace-with-your-secret-key>
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 -->
<label>
<input type="checkbox">
Round up and donate to <a href="https://watsi.org/" target="_blank">Watsi 501(c)3</a>
</label>
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 */
document.querySelector('input[type="checkbox"]')
.addEventListener("change", function(evt) {
handleCheckboxEvent(stripeData.id, evt.target.checked);
});
And update the payment intent when the event is fired:
/** TODO: Add handleCheckboxEvent here */
var handleCheckboxEvent = function(id, isDonating) {
changeLoadingState(true);
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) {
changeLoadingState(false);
updateTotal(data.amount);
});
};
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(request.body.read)
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']
}.to_json
end
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
return
end
event_type = event['type']
data = event['data']
data_object = data['object']
# Next: Make donation with Change
end
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 = HTTParty.post("https://api.getchange.io/api/v1/donations",
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"
else
puts '😶 Received a payment -- customer did not donate.'
end
end
content_type 'application/json'
{
status: 'success'
}.to_json
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
:
STRIPE_WEBHOOK_SECRET=<replace-with-your-webhook-secret>
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.