waffy_sdk_flutter
waffy_sdk_flutter is a small Flutter SDK that embeds the Waffy checkout flow in a WebView.
You pass in a Waffy payment URL and one way to authenticate the customer (a payment ticket, a login hint or a user token), and the SDK:
- Appends the authentication value to the URL as a query parameter.
- Loads the checkout in a
WebView. - Listens for redirects that indicate payment success or failure.
- Exposes simple callbacks so your app can react to the result.
Features
- ✅ Simple
WaffyPaymentWebViewwidget to start a payment flow. - ✅ Supports payment tickets (recommended), login hints and the legacy user token.
- ✅ Notifies you when:
- The payment succeeds or fails.
- The payment flow completes with a
statusand optionalmessage. - The underlying
WebViewURL changes.
- ✅ Uses
webview_flutterunder the hood.
Getting started
1. Add dependency
flutter pub add waffy_sdk_flutter
Or add it manually to your app's pubspec.yaml:
dependencies:
waffy_sdk_flutter: ^0.6.0
2. Platform setup
This package uses webview_flutter.
Follow the platform-specific setup in its documentation if you have not already configured WebView for your Flutter app (e.g. iOS minimum version, Android WebView requirements).
Usage
Basic example
The main entry point is the WaffyPaymentWebView widget.
paymentUrl: The Waffy checkout URL you receive from your backend.ticket: A payment ticket your backend requested right before opening the checkout. See Authentication for the other options.onPaymentComplete: Called once when the flow finishes (success or failure).onPaymentSuccess: Convenience callback when the result is successful.onPaymentFailure: Convenience callback when the result is failed, with a message.onUrlChange: Called for every URL change inside theWebView.
import 'package:flutter/material.dart';
import 'package:waffy_sdk_flutter/waffy_sdk_flutter.dart';
class WaffyCheckoutPage extends StatelessWidget {
const WaffyCheckoutPage({
super.key,
required this.paymentUrl,
required this.ticket,
});
final String paymentUrl;
final String ticket;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Waffy Checkout'),
),
body: WaffyPaymentWebView.ticket(
paymentUrl: paymentUrl,
ticket: ticket,
onPaymentComplete: (status, message) {
switch (status) {
case WaffyPaymentStatus.success:
// Handle success (e.g. navigate, show success UI).
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Payment success: $message')),
);
break;
case WaffyPaymentStatus.failed:
// Handle failure (e.g. show error, allow retry).
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Payment failed: $message')),
);
break;
}
},
onUrlChange: (url) {
// Optional: inspect URL changes for analytics or debugging.
debugPrint('Waffy WebView URL changed: $url');
},
),
);
}
}
Authentication
Pass exactly one of ticket, hint or userToken. The SDK appends it to the payment URL for you.
| Parameter | Appended to the payment URL as | Use it when |
|---|---|---|
ticket |
ticket |
Recommended. You have a payment ticket for this customer and payment. |
hint |
login_hint |
You want the customer to sign in on the payment page, and you know their phone number. |
userToken |
userTokenUrl |
You already integrated with the legacy customer token. It keeps working unchanged. |
Use the named constructor for your option. Each one accepts only its own value, so passing more than one is a compile-time error:
// Payment ticket (recommended)
WaffyPaymentWebView.ticket(paymentUrl: paymentUrl, ticket: ticket);
// Login hint: the customer's phone number, e.g. '+9665XXXXXXXX'
WaffyPaymentWebView.hint(paymentUrl: paymentUrl, hint: customerPhone);
// Legacy user token
WaffyPaymentWebView.userToken(paymentUrl: paymentUrl, userToken: userToken);
The default WaffyPaymentWebView(...) constructor also accepts all three, for existing integrations.
Getting a payment ticket
Request the ticket from your backend, never from the app, because the request uses your client_secret. Then send the ticket to the app and pass it to the ticket parameter.
curl -X POST "$WAFFY_ID_URL/realms/waffy/protocol/openid-connect/token" \
-u "$CLIENT_ID:$CLIENT_SECRET" \
--data-urlencode "grant_type=urn:waffy:params:oauth:grant-type:customer-token-exchange" \
--data-urlencode "login_hint=$CUSTOMER_PHONE" \
--data-urlencode "scope=payment:$PAYMENT_REF"
# => { "ticket": "a1b2c3...", "expiresIn": 300 }
PAYMENT_REFis the contract's short ID: the last part of the payment URL path (.../external/6VzpzZlR?...gives6VzpzZlR).- A ticket expires after 5 minutes and works only once. Request a new one each time you open the payment page.
- If the customer hasn't approved your organization yet, the payment page asks them to sign in instead.
See the Waffy authentication guide for the full flow.
Invalid authentication
With the default constructor, passing none of the three parameters, or more than one:
- fails an
assertwhere the widget is created, in debug mode (and is an analyzer error forconstwidgets); - throws a
WaffyAuthErrorwhen the widget is first built, in every build mode. Empty strings count as not provided here.
Upgrading from 0.1.x
No code changes are needed. Apps that pass userToken get exactly the same payment URL as before. To move to tickets, replace userToken: ... with ticket: ....
How the payment result is detected
Internally, WaffyPaymentWebView:
- Takes your
paymentUrland appends the authentication query parameter (ticket,login_hintoruserTokenUrl). - Loads that URL in a
WebView. - Listens to URL changes:
- If the URL contains
status=SUCCESS, it:- Calls
onPaymentComplete(WaffyPaymentStatus.success, message). - Calls
onPaymentSuccess()if provided.
- Calls
- If the URL contains
status=FAILED, it:- Calls
onPaymentComplete(WaffyPaymentStatus.failed, message). - Calls
onPaymentFailure(message)if provided.
- Calls
messageis taken from themessagequery parameter of the URL, if present.
- If the URL contains
Your backend / Waffy checkout implementation is responsible for redirecting
to a URL with status=SUCCESS or status=FAILED (and optional message).
API reference
WaffyPaymentWebView
Constructor parameters:
paymentUrl(required): Base payment/checkout URL from your backend/Waffy.ticket,hintoruserToken(exactly one required): How the customer is authenticated on the payment page. See Authentication.ticket:String?– one-time payment ticket; added asticket.hint:String?– the customer's phone number; added aslogin_hint.userToken:String?– legacy customer token; added asuserTokenUrl.
onPaymentSuccess:void Function()?– called when the payment succeeds.onPaymentFailure:void Function(String message)?– called when the payment fails.onPaymentComplete:void Function(WaffyPaymentStatus status, String message)?– called once when the payment flow completes.onUrlChange:void Function(String? url)?– called on any URL change inside theWebView.webViewController: OptionalWebViewControllerif you want to provide your own.
WaffyPaymentStatus
enum WaffyPaymentStatus {
success,
failed,
}
WaffyAuthError
Thrown when none, or more than one, of ticket, hint and userToken is provided. message describes the problem.
Support
For integration help or to report an issue, contact the Waffy team via waffyapp.com.