pinelabs_web 1.0.0
pinelabs_web: ^1.0.0 copied to clipboard
Flutter plugin for fullscreen payment WebView flow with UPI intent handling and callback-based results.
example/lib/main.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:pinelabs_web/pinelabs_web.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Pine Payment SDK Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF005C4B)),
useMaterial3: true,
),
home: const PaymentDemoPage(),
);
}
}
class PaymentDemoPage extends StatefulWidget {
const PaymentDemoPage({super.key});
@override
State<PaymentDemoPage> createState() => _PaymentDemoPageState();
}
class _PaymentDemoPageState extends State<PaymentDemoPage> {
static const String _defaultCheckoutRedirectUrl =
'https://pluraluat.v2.pinepg.in/api/v3/checkout-bff/redirect/checkout?token=V3_VhNtSoX8fgDVJl4J9JXC%2FmNUkgV0YzvzJD56Kk1v9MExAc5yhXJzGyt2hdntTMK1';
PaymentResult? _lastResult;
String? _lastResultJson;
String? _error;
bool _isProcessing = false;
late final TextEditingController _customPaymentUrlController;
@override
void initState() {
super.initState();
_customPaymentUrlController = TextEditingController(
text: _defaultCheckoutRedirectUrl,
);
}
@override
void dispose() {
_customPaymentUrlController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Pine Payment SDK Example')),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.all(16),
children: <Widget>[
const Text(
'Merchant simulation: paste the checkout redirect URL from your backend, '
'tap Start Payment, and the SDK handles WebView + UPI intent switching.',
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text(
'Checkout Redirect URL',
style: TextStyle(fontWeight: FontWeight.w700),
),
const SizedBox(height: 8),
TextField(
controller: _customPaymentUrlController,
enabled: !_isProcessing,
maxLines: 3,
decoration: const InputDecoration(
labelText: 'Redirect URL from merchant backend',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 8),
FilledButton(
onPressed: _isProcessing
? null
: () {
_startCustomPaymentFromInput();
},
child: const Text('Start Payment'),
),
],
),
),
),
if (_isProcessing)
const Padding(
padding: EdgeInsets.only(top: 16),
child: Center(child: CircularProgressIndicator()),
),
const SizedBox(height: 24),
_buildResultCard(),
if (_error != null) ...<Widget>[
const SizedBox(height: 16),
Text(
_error!,
style: TextStyle(
color: Theme.of(context).colorScheme.error,
fontWeight: FontWeight.w600,
),
),
],
],
),
),
);
}
Future<void> _startCustomPaymentFromInput() async {
final String redirectUrl = _customPaymentUrlController.text.trim();
if (redirectUrl.isEmpty) {
setState(() {
_error = 'Please enter a checkout redirect URL.';
});
return;
}
setState(() {
_isProcessing = true;
_error = null;
});
try {
final PaymentResult result = await PinePaymentSdk.startPaymentFromRedirectUrl(
redirectUrl: redirectUrl,
appBarTitle: 'Complete Payment',
);
if (!mounted) {
return;
}
setState(() {
_lastResult = result;
_lastResultJson = const JsonEncoder.withIndent(' ').convert(result.toJson());
});
debugPrint('PinePaymentSdk result => ${result.toJson()}');
} catch (error) {
if (!mounted) {
return;
}
setState(() {
_error = 'Unable to start custom URL payment flow: $error';
});
debugPrint('PinePaymentSdk error => $error');
} finally {
if (mounted) {
setState(() {
_isProcessing = false;
});
}
}
}
Widget _buildResultCard() {
if (_lastResult == null) {
return const Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Text('No payment result yet.'),
),
);
}
final PaymentResult result = _lastResult!;
final Color statusColor = result.success ? Colors.green : Colors.red;
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Icon(
result.success ? Icons.check_circle : Icons.cancel,
color: statusColor,
),
const SizedBox(width: 8),
Text(
'Status: ${result.status}',
style: TextStyle(
color: statusColor,
fontWeight: FontWeight.w700,
),
),
],
),
const SizedBox(height: 12),
Text('Success: ${result.success}'),
Text('Final URL: ${result.finalUrl}'),
Text('Transaction ID: ${result.transactionId ?? '-'}'),
Text('Message: ${result.message ?? '-'}'),
if (_lastResultJson != null) ...<Widget>[
const SizedBox(height: 12),
const Text(
'Raw PaymentResult',
style: TextStyle(fontWeight: FontWeight.w700),
),
const SizedBox(height: 6),
SelectableText(_lastResultJson!),
],
],
),
),
);
}
}