DEV Community

Cover image for Monetize your Dart apps with Web Monetization
Tomás Arias
Tomás Arias

Posted on • Updated on

Monetize your Dart apps with Web Monetization

Dart and the related logo are trademarks of Google LLC. I am not endorsed by or affiliated with Google LLC.

What I built

Monetization is a Dart package for implementing and controlling Web Monetization on your web app. It exposes a straightforward API that resembles and extends the functionality of the Web Monetization's JavaScript API.

Submission Category: Foundational Technology

Demo

Link to Code

GitHub logo KNawm / monetization

💸 A wrapper around the Web Monetization API to monetize apps.

monetization

A wrapper around the Web Monetization API

 

Offer extra content and features for users who stream micro-payments — including premium features, additional content, or digital goods.

Usage

A simple usage example that initializes the monetization to a specific payment pointer:

import 'package:monetization/monetization.dart';

main() {
  var monetization = Monetization('\$pay.tomasarias.me');
}
Enter fullscreen mode Exit fullscreen mode

You can subscribe to Web Monetization events using a Stream:

monetization.onPending.listen((event) {
  // Prepare to serve the monetized content
});

monetization.onStart.listen((event) {
  // Show monetized content
});

monetization.onProgress.listen((event) {
  // Do something on each micro-payment
});

monetization.onStop.listen((event) {
  // Hide monetized content
});
Enter fullscreen mode Exit fullscreen mode

You can also check if a user is paying without subscribing to the streams:

Future<bool> isPaying() async {
  // Prefer custom logic over this
  await Future.delayed(const Duration(seconds: 3
Enter fullscreen mode Exit fullscreen mode

How I built it

For this project, I spent most of the time researching Web Monetization and it's eco-system, analyzing its strengths and where it lacked most.
The provided JavaScript API was the first stop as it's the fundamental part to implement Web Monetization, I wanted to have all of the core functionality as familiar as possible. With this in mind, I began to implement the shared API between the two:

In JavaScript, you can check if a user supports Web Monetization checking if document.monetization === undefined but because in Dart there is no use in exposing the document.monetization object directly, we can check everything using the state getter.

/// Returns the monetization state provided by the browser.
///
/// **`undefined`**:
/// Monetization is not supported for this user.
///
/// **`pending`**:
/// Streaming has been initiated, yet first non zero packet is
/// "pending". It will normally transition from this `state` to `started`,
/// yet not always.
///
/// **`started`**:
/// Streaming has received a non zero packet and is still active.
///
/// **`stopped`**:
/// Streaming is inactive. This could mean a variety of things:
/// - May not have started yet
/// - May be paused (potentially will be resumed)
/// - Has finished completely (and awaits another request)
/// - The payment request was denied by user intervention
String get state => _state ?? 'undefined';
Enter fullscreen mode Exit fullscreen mode

In JavaScript, you can listen to the monetizationpending | monetizationstart | monetizationstop | monetizationprogress events, in Dart there are corresponding streams of those events that you can subscribe.

/// Stream that tracks 'monetizationpending' events.
///
/// This event fires when Web Monetization is enabled.
Stream<Map> onPending;

/// Stream that tracks 'monetizationstart' events.
///
/// This event fires when Web Monetization has started actively paying.
Stream<Map> onStart;

/// Stream that tracks 'monetizationstop' events.
///
/// This event fires when Web Monetization has stopped.
Stream<Map> onStop;

/// Stream that tracks 'monetizationprogress' events.
///
/// This event fires when Web Monetization has streamed a payment.
Stream<Map> onProgress;
Enter fullscreen mode Exit fullscreen mode

But all of this functionality only works if you have a payment pointer defined in a <meta> tag in the HTML. This can be achieved initializing a Monetization object:

import 'package:monetization/monetization.dart';

main() {
  var monetization = Monetization('\$pay.tomasarias.me');
}
Enter fullscreen mode Exit fullscreen mode

And that's it, all the set up is done by the package, no need to edit the HTML or do Dart-JS interoperability. This is especially useful on Flutter Web where DOM manipulation or calling JavaScript is very rare.

This is where we began extending beyond the JavaScript API, for example, we can get information about the monetization status:

monetization.isMonetized; // Returns if the user supports monetization
monetization.isPaying;    // Returns if the user is streaming payments
monetization.pointer;     // Returns the current payment pointer
Enter fullscreen mode Exit fullscreen mode

Or even get the revenue paid to the current payment pointer:

monetization.getTotal(); // 884389
monetization.getTotal(formatted: true); // 0.000884389
monetization.assetCode;  // 'XRP'
monetization.assetScale; // 9
Enter fullscreen mode Exit fullscreen mode

Another useful thing we could do is programatically enable or disable the monetization:

var monetization = Monetization('\$pay.tomasarias.me'); // Monetization enabled on initialization
monetization.disable(); // Stops the monetization
monetization.enable();  // Start the monetization again with the same pointer
Enter fullscreen mode Exit fullscreen mode

If you want to share the revenue across different payment pointers, probabilistic revenue sharing is also supported:

final pointers = {
  'pay.tomasarias.me/usd': 0.5,
  'pay.tomasarias.me/xrp': 0.2,
  'pay.tomasarias.me/ars': 0.3
};

var monetization = Monetization.probabilistic(pointers);
Enter fullscreen mode Exit fullscreen mode

This will result in a 50% chance of choosing the first pointer, a 20% chance of choosing the second, and a 30% chance of choosing the third one.

One of the last features we are going to explore is the ability to verify the payment stream and anonymizing the payment pointer.

Verifying the payments is complex and not very straightforward at the moment (at least until STREAM Receipts are fully adopted). Thankfully there is a service that can do that for us, Vanilla keeps your information more secure and ensures users don’t cheat their way around the payment mechanism. To learn more about Vanilla check out this article.

To use Vanilla with Monetization we can initialize it like this:

// Vanilla API Credentials
final clientId = 'Your Client ID';
final clientSecret = 'Your Client Secret';

var monetization = Monetization.vanilla(clientId, clientSecret);
Enter fullscreen mode Exit fullscreen mode

Now you can check if the payment stream is valid in different ways:

// With Vanilla, this will return true if the user is paying and Vanilla generates a proof of payment.
monetization.isPaying;
// With Vanilla, this will return the current payment rate per second, if the monetization is stopped this will be 0.
monetization.getVanillaRate()
// With Vanilla, this will return the total amount received from the current requestId.
monetization.getVanillaTotal();
Enter fullscreen mode Exit fullscreen mode

This gives us much more confidence serving monetized content, ensuring only legit monetized users can access your content.

And last but not least, a debug mode to see all the events in real-time:

Debug mode

Thanks to the Coil team for their sample projects on Glitch, they were great inspiration for the features.

And a special thanks to Norbert Durcansky for sharing his vision and all of the Cinnamon team for crafting Vanilla.

That would be all! 🙌 Thanks for reading and please feel free to communicate your thoughts, suggestions, or corrections.

Top comments (1)

Collapse
 
moyinshabi profile image
Moyin Shabi

Great article. How can i apply this to a project please?

Some comments may only be visible to logged-in visitors. Sign in to view all comments.