# paynex (Dart) + Flutter guide

Two halves:

1. **Server side (this package, `lib/paynex.dart`)** — signs requests with your API key.
   Runs in your Dart backend (shelf / Serverpod / Cloud Functions). Add to `pubspec.yaml`:
   ```yaml
   dependencies:
     paynex:
       path: ../paynex-dart   # or copy lib/paynex.dart into your project
   ```
2. **Flutter app** — needs **no key**. It asks your backend for `checkout_url`, opens it in an
   in-app browser, and handles the deep link back. Snippet below.

## Server

```dart
final px = Paynex(baseUrl: 'https://pay.example.com', keyId: env['PAYNEX_KEY_ID']!, secret: env['PAYNEX_SECRET']!);

// POST /api/pay  → { checkout_url, payment_id }
final p = await px.createPayment(
  amount: order.total, merchantRef: order.id, description: order.title,
  customer: {'name': order.name, 'email': order.email, 'phone': order.phone},
  returnUrl: 'myapp://paynex/return', cancelUrl: 'myapp://paynex/cancel',   // register "myapp" in store → Domains → Mobile app URL schemes
  idempotencyKey: '${order.id}-${order.total}');

// POST /paynex/webhook
final ev = Paynex.verifyWebhook(webhookSecret, request.headers['x-pg-signature'], rawBodyBytes);
if (ev.event == 'payment.paid') {
  final full = await px.getPayment(ev.data['id'] as String);   // source of truth
  if (full.paid) await markOrderPaid(full.merchantRef, full.id); // idempotent by full.id
}
```

## Flutter app (no key in the app)

```yaml
dependencies:
  url_launcher: ^6.3.0     # opens checkout in Chrome Custom Tabs / SFSafariViewController
  app_links: ^6.3.2        # receives myapp://paynex/return?pg_ref=…
```

```dart
// 1. ask your backend, then open the hosted checkout in an in-app browser (not a WebView)
final r = await http.post(Uri.parse('$backend/api/pay'), body: jsonEncode({'orderId': order.id}));
final checkoutUrl = jsonDecode(r.body)['checkout_url'] as String;
await launchUrl(Uri.parse(checkoutUrl), mode: LaunchMode.inAppBrowserView);

// 2. deep link back: myapp://paynex/return?pg_ref=<payment id>&pg_status=paid&pg_ts=…&pg_sig=…
AppLinks().uriLinkStream.listen((uri) async {
  if (uri.host == 'paynex' && uri.path == '/return') {
    final paymentId = uri.queryParameters['pg_ref'];
    // show "confirming…" then ask YOUR backend (which calls GET /v1/payments/{id})
    final s = await http.get(Uri.parse('$backend/api/pay/$paymentId'));
    if (jsonDecode(s.body)['status'] == 'paid') showSuccess();
  } else if (uri.host == 'paynex' && uri.path == '/cancel') {
    showCancelled();
  }
});
```

Register the scheme: Android `AndroidManifest.xml` intent-filter with `<data android:scheme="myapp" android:host="paynex"/>`;
iOS `Info.plist` → `CFBundleURLTypes` → `CFBundleURLSchemes: [myapp]`. Prefer Universal Links / App Links
(`https://yourdomain.com/pay/return`) in production — then `return_url` is a normal https URL on a store domain.

The app may also poll `GET {checkout_url}/status` (public JSON: `id, status, amount, currency, merchant_ref, paid_at`)
to update its own UI while the browser is open. That is for UX only; the order is marked paid by your backend via the webhook.
