Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewtanner91 committed Apr 16, 2024
0 parents commit ad4da05
Show file tree
Hide file tree
Showing 7 changed files with 1,131 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

.DS_Store
/node_modules
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
_These examples are not production-ready and are to be used as a starting point for those looking to automate the ability to keep Stripe and Moesif in sync as users top-up their balances in Stripe. If used in production, proper security mechanisms for authentication and authorization should be added_

## Background
With Moesif's new Credit Consumption feature, Moesif is now able to independently track consumption usage and burn down a pre-paid balance. For instance, if a user adds $10 in API usage credits, Moesif can keep track of their remaining balance in real-time, helping to more accurately enforce access to the API if no credits remain and many other use cases.

With this feature, users will still want to process payments and balances in a 3rd party billing platform, like Stripe. This requires additional logic to ensure that any credits purchased through Stripe, or another billing provider, are then applied to the balance within Moesif. If a user adds $10 in Stripe, that same amount should be added to their available balance within Moesif as well.

This can be done in multiple ways and this is the reasoning behind the examples in this project. Within the project, you will see 3 potential ways that such mechanisms can be implemented with Stripe. These include a webhook, API, and combined approach. Below is some detail on how each of the solutions works, how to customize them, and how to run them.

## The Applications

### Webhook-only App
With this approach, whenever a user's balance changes in Stripe, a corresponding debit or credit will be added to the Moesif balance. Using the Stripe webhook, when a change is sent, the record will be created and sent to Moesif via the Moesif Credit Consumption API.

This approach works well for applications that already have an existing top-up flow built and allows for minimal changes. This approach is automated and allows both balances to stay in sync between the two systems.

The webhook application in this example (`webhook-app.js`) will receive a message from Stripe when a user has added funds to their Stripe account. To start this example, you can use:

`node webhook-app.js`

For this application to work you must deploy the webhook somewhere (for example, for testing you could use ngrok or deploy on your existing API infrastructure). It needs to be a publicly accessible webhook. Once deployed, add the webhook to Stripe by going to the __Developers__ menu. Then go to __Webhooks > Add endpoints__.

On the next screen, you'll add your endpoint URL in the __Endpoint URL__ field, such as `https://mycompany.com/webhook`. Then, you'll need to also click the __Select events__ button, selecting the following events for the webhook:

- payment_intent.succeeded

Once configured, click __Add endpoint__.

To test this, go through your top-up logic and ensure that the webhook fires and successfully creates the credit/debit entry in Moesif (you will be able to see the debugger output in your APIs console as well).

### API-only App
If you are building or have an existing top-up flow, you can also do the same logic as above by using a direct API call to Moesif's Credit Consumption endpoints. In this example, there is an API app that allows users to call a /top-up endpoint with their Stripe customer ID and the amount they would like to credit to their account.

The endpoint itself then creates a Stripe __PaymentIntent__ that will then add the amount to the user's Stripe account. At the same time, this endpoint will also go and create a corresponding transaction on the Moesif side, adding the amount to the available balance in Moesif.

The endpoint can be deployed independently or on your existing API infrastructure, however, this __endpoint requires users to have a credit card on file__ and will create a Stripe Payment Intent for the amount they have requested to be charged against that card. To run the API, use the following command:

`node webhook-api-app.js`

### Webhook + API App
For users who want to use the best of both solutions, this example allows users to create an endpoint to initiate the top-up transaction and then use the webhook to apply that credit on the Moesif side once the PaymentIntent has succeeded on the Stripe side.

The webhook will require the same configuration as the initial webhook above, the same goes for the API. Currently, in this solution, both the /top-up REST API endpoint and the webhook run in the same node project, however, both of these can be split apart as needed.

The node app will need to be hosted publicly, but to test functionality locally, you can use the following command:

`node webhook-api-app.js`

## Configuring the .env
Depending on the project, the .env file that the projects use require the following values:

``` conf
STRIPE_API_KEY="sk_test_51KgE2......"
STRIPE_ENDPOINT_SECRET="whsec_teSTkeY......"
MOESIF_API_URL="https://api.moesif.com/~/billing/reports/balance_transactions"
MOESIF_MANAGEMENT_TOKEN="Bearer eyMoeSIfkEY......"
```

Here is how to get the values for each of these configuration items:

__STRIPE_API_KEY__
The Stripe API we are using requires an API key. The API keys can be provisioned in a few ways, the [Stripe docs](https://docs.stripe.com/keys) cover this well.

__STRIPE_ENDPOINT_SECRET__
The Webhook project requires a Webhook Secret. This can be retrieved in the Stripe UI through the Webhooks screen. Here are the [Stripe docs](https://docs.stripe.com/webhooks#endpoint-secrets) for more particulars.

__MOESIF_API_URL__
This can be left as is, pointing the the credit consumption endpoint available through the Moesif Management API.

__MOESIF_MANAGEMENT_TOKEN__
A Moesif Management Token must be provisioned in order to use the Management API. This can be done using the Moesif UI to provision a [Management API Token](https://www.moesif.com/docs/api#management-api). This will require the following scopes for the token:

- create:billing_meters
- create:billing_reports
83 changes: 83 additions & 0 deletions api-app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
const express = require('express');
const fetch = require('node-fetch');
const { v4: uuidv4 } = require('uuid');
require('dotenv').config();

const stripe = require('stripe')(process.env.STRIPE_API_KEY);

const MOESIF_MANAGEMENT_TOKEN = process.env.MOESIF_MANAGEMENT_TOKEN;
const app = express();
app.use(express.json());

app.post(`/top-up`, async (req, res) => {
const { amount, customer_id } = req.body;

const paymentMethods = await stripe.paymentMethods.list({
customer: customer_id,
type: 'card',
limit: 1
});

console.log(paymentMethods);
if(paymentMethods.data.length === 0) {
return res.status(400).json({ error: 'No payment method found for this customer. The customer needs to have a payment method added to their Stripe account.' });
}

const paymentID = paymentMethods.data[0].id;

const paymentIntent = await stripe.paymentIntents.create({
amount: amount,
currency: 'usd',
customer: customer_id,
payment_method: paymentID,
off_session: true,
payment_method_types: ['card'],
confirm: true,
error_on_requires_action: true,
});

const subscriptions = await stripe.subscriptions.list({
customer: customer_id,
});

const subscriptionId = subscriptions.data[0].id;
console.log(subscriptionId);

const url = `${process.env.MOESIF_API_URL}`;
const transactionType = "credit";
const transactionAmount= amount;

const body = {
"company_id": customer_id,
"amount": transactionAmount,
"type": transactionType,
"subscription_id": subscriptionId,
"transaction_id": uuidv4().toString(),
"description": "top up from API, post Stripe top-up event"
};

console.log('Creating balance transaction:', body);

try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': MOESIF_MANAGEMENT_TOKEN
},
body: JSON.stringify(body)
});

if (response.ok) {
console.log('Balance transaction created successfully');
} else {
console.error('Failed to create balance transaction!', response.status, response.statusText, await response.json());
}
} catch (error) {
console.error('An error occurred while creating balance transaction:', error);
}

res.json({ client_secret: paymentIntent.client_secret });
});

app.listen(4242, () => console.log('Running on port 4242'));
Loading

0 comments on commit ad4da05

Please sign in to comment.