more package declarations
This commit is contained in:
389
docs/MOBILE_PUSH_INTEGRATION.md
Normal file
389
docs/MOBILE_PUSH_INTEGRATION.md
Normal file
@@ -0,0 +1,389 @@
|
||||
# Push Notifications - Mobile App Quick Start
|
||||
|
||||
This guide helps you integrate push notifications into the ShieldAI React Native mobile app.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting, ensure:
|
||||
- ✅ Backend push notification infrastructure is deployed
|
||||
- ✅ Firebase project is set up (for Android)
|
||||
- ✅ Apple Developer account is active (for iOS)
|
||||
- ✅ Environment variables are configured on the backend
|
||||
|
||||
## Step 1: Install Dependencies
|
||||
|
||||
```bash
|
||||
npm install @react-native-firebase/app @react-native-firebase/messaging
|
||||
```
|
||||
|
||||
## Step 2: Android Setup
|
||||
|
||||
### 2.1 Add Firebase to Android Project
|
||||
|
||||
1. Go to [Firebase Console](https://console.firebase.google.com)
|
||||
2. Select your ShieldAI project
|
||||
3. Add Android app with package name: `com.shieldai.mobile`
|
||||
4. Download `google-services.json`
|
||||
5. Place it in `android/app/google-services.json`
|
||||
|
||||
### 2.2 Update Build Configuration
|
||||
|
||||
In `android/build.gradle`:
|
||||
```gradle
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath 'com.google.gms:google-services:4.4.0'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In `android/app/build.gradle`:
|
||||
```gradle
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
```
|
||||
|
||||
### 2.3 Request Permissions
|
||||
|
||||
In `AndroidManifest.xml`:
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
```
|
||||
|
||||
## Step 3: iOS Setup
|
||||
|
||||
### 3.1 Enable Push Notifications
|
||||
|
||||
1. Open Xcode project
|
||||
2. Select your app target
|
||||
3. Go to "Signing & Capabilities"
|
||||
4. Click "+ Capability"
|
||||
5. Add "Push Notifications"
|
||||
6. Add "Background Modes" and check "Remote notifications"
|
||||
|
||||
### 3.2 Configure Firebase
|
||||
|
||||
1. Download `GoogleService-Info.plist` from Firebase Console
|
||||
2. Add it to your Xcode project (drag to project folder)
|
||||
3. Ensure "Copy items if needed" is checked
|
||||
|
||||
### 3.3 Add Import in AppDelegate.swift
|
||||
|
||||
```swift
|
||||
import FirebaseMessaging
|
||||
import UserNotifications
|
||||
|
||||
@main
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
|
||||
|
||||
func application(_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
FirebaseApp.configure()
|
||||
Messaging.messaging().delegate = self
|
||||
return true
|
||||
}
|
||||
|
||||
// Request permission
|
||||
func requestAuthorization() {
|
||||
UNUserNotificationCenter.current().delegate = self
|
||||
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
|
||||
UNUserNotificationCenter.current().requestAuthorization(
|
||||
options: authOptions,
|
||||
completionHandler: { granted, error in
|
||||
if granted {
|
||||
DispatchQueue.main.async {
|
||||
UIApplication.shared.registerForRemoteNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Handle token refresh
|
||||
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String) {
|
||||
print("FCM token refreshed: \(fcmToken)")
|
||||
// Send new token to backend
|
||||
registerDeviceToken(fcmToken)
|
||||
}
|
||||
|
||||
// Handle foreground notifications
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter,
|
||||
willPresent notification: UNNotification,
|
||||
withCompletionHandler completionHandler:
|
||||
@escaping (UNNotificationPresentationOptions) -> Void) {
|
||||
let userInfo = notification.request.content.userInfo
|
||||
print("Foreground notification: \(userInfo)")
|
||||
completionHandler([.banner, .sound, .badge])
|
||||
}
|
||||
|
||||
// Handle notification tap
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter,
|
||||
didReceive response: UNNotificationResponse,
|
||||
withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||
let userInfo = response.notification.request.content.userInfo
|
||||
print("Notification tapped: \(userInfo)")
|
||||
completionHandler()
|
||||
}
|
||||
}
|
||||
|
||||
extension AppDelegate: MessagingDelegate {
|
||||
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
|
||||
print("FCM token received: \(fcmToken ?? "none")")
|
||||
if let token = fcmToken {
|
||||
registerDeviceToken(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to register with backend
|
||||
func registerDeviceToken(_ token: String) {
|
||||
// Call your API to register the device
|
||||
// POST /api/v1/devices/register
|
||||
// Body: { userId, platform: "ios", token, deviceType: "mobile" }
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4: React Native Integration
|
||||
|
||||
### 4.1 Create Notification Service
|
||||
|
||||
Create `src/services/NotificationService.ts`:
|
||||
|
||||
```typescript
|
||||
import messaging from '@react-native-firebase/messaging';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import axios from 'axios';
|
||||
|
||||
const API_BASE_URL = 'https://api.shieldai.com/api/v1';
|
||||
|
||||
export class NotificationService {
|
||||
private static instance: NotificationService;
|
||||
private userId: string | null = null;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static getInstance(): NotificationService {
|
||||
if (!NotificationService.instance) {
|
||||
NotificationService.instance = new NotificationService();
|
||||
}
|
||||
return NotificationService.instance;
|
||||
}
|
||||
|
||||
setUserId(userId: string) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
async requestPermission(): Promise<boolean> {
|
||||
try {
|
||||
const authStatus = await messaging().requestPermission();
|
||||
const enabled =
|
||||
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
|
||||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
|
||||
|
||||
if (enabled) {
|
||||
console.log('Push notification permission granted');
|
||||
await this.registerDevice();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Failed to request permission:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async registerDevice(): Promise<void> {
|
||||
if (!this.userId) {
|
||||
console.warn('User ID not set, cannot register device');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const token = await messaging().getToken();
|
||||
const platform = Platform.OS === 'ios' ? 'ios' : 'android';
|
||||
|
||||
const response = await axios.post(`${API_BASE_URL}/devices/register`, {
|
||||
userId: this.userId,
|
||||
platform,
|
||||
token,
|
||||
deviceType: 'mobile',
|
||||
appName: 'ShieldAI Mobile',
|
||||
appVersion: '1.0.0',
|
||||
});
|
||||
|
||||
console.log('Device registered:', response.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to register device:', error);
|
||||
}
|
||||
}
|
||||
|
||||
setupListeners() {
|
||||
// Foreground messages
|
||||
messaging().onMessage(async (remoteMessage) => {
|
||||
console.log('Foreground message received:', remoteMessage);
|
||||
// Show local notification
|
||||
this.showLocalNotification(remoteMessage);
|
||||
});
|
||||
|
||||
// Background messages
|
||||
messaging().setBackgroundMessageHandler(async (remoteMessage) => {
|
||||
console.log('Background message received:', remoteMessage);
|
||||
});
|
||||
|
||||
// Notification opened
|
||||
messaging().onNotificationOpenedApp((remoteMessage) => {
|
||||
console.log('Notification opened app:', remoteMessage);
|
||||
// Navigate to relevant screen
|
||||
this.handleNotificationTap(remoteMessage);
|
||||
});
|
||||
|
||||
// Check if app was opened from notification
|
||||
messaging()
|
||||
.getInitialNotification()
|
||||
.then((remoteMessage) => {
|
||||
if (remoteMessage) {
|
||||
console.log('App opened from notification:', remoteMessage);
|
||||
this.handleNotificationTap(remoteMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private showLocalNotification(message: any) {
|
||||
// Use react-native-push-notification or similar
|
||||
console.log('Show notification:', message.notification?.title);
|
||||
}
|
||||
|
||||
private handleNotificationTap(message: any) {
|
||||
// Navigate to relevant screen based on notification data
|
||||
const { alertId, type } = message.data || {};
|
||||
if (alertId) {
|
||||
// Navigate to alert detail
|
||||
console.log('Navigate to alert:', alertId);
|
||||
}
|
||||
}
|
||||
|
||||
async deregisterDevice(): Promise<void> {
|
||||
if (!this.userId) return;
|
||||
|
||||
try {
|
||||
const token = await messaging().getToken();
|
||||
await axios.post(`${API_BASE_URL}/devices/deregister`, {
|
||||
token,
|
||||
userId: this.userId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to deregister device:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Initialize in App Component
|
||||
|
||||
```typescript
|
||||
// App.tsx
|
||||
import React, { useEffect } from 'react';
|
||||
import { NotificationService } from './src/services/NotificationService';
|
||||
|
||||
const notificationService = NotificationService.getInstance();
|
||||
|
||||
function App() {
|
||||
useEffect(() => {
|
||||
// Initialize push notifications
|
||||
notificationService.setupListeners();
|
||||
}, []);
|
||||
|
||||
// When user logs in
|
||||
const handleLogin = async (userId: string) => {
|
||||
notificationService.setUserId(userId);
|
||||
await notificationService.requestPermission();
|
||||
};
|
||||
|
||||
// When user logs out
|
||||
const handleLogout = async () => {
|
||||
await notificationService.deregisterDevice();
|
||||
notificationService.setUserId('');
|
||||
};
|
||||
|
||||
return (
|
||||
// Your app components
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Step 5: Testing
|
||||
|
||||
### Android Emulator
|
||||
|
||||
1. Start Android emulator with Google Play Services
|
||||
2. Install and run app
|
||||
3. Trigger a test notification from backend
|
||||
4. Check notification appears
|
||||
|
||||
### iOS Device (Required)
|
||||
|
||||
iOS Simulator does not support push notifications. Use a real device.
|
||||
|
||||
### Backend Test
|
||||
|
||||
```bash
|
||||
# Test notification API
|
||||
curl -X POST https://api.shieldai.com/api/v1/notifications/send \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"userId": "user-uuid",
|
||||
"channel": "push",
|
||||
"subject": "Test Alert",
|
||||
"body": "This is a test notification from ShieldAI",
|
||||
"priority": "high",
|
||||
"metadata": {
|
||||
"alertId": "alert-uuid",
|
||||
"type": "darkweb_exposure"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Android Issues
|
||||
|
||||
**Problem**: Token not received
|
||||
- Check `google-services.json` is in correct location
|
||||
- Verify Firebase project ID matches
|
||||
- Check internet connection on emulator
|
||||
|
||||
**Problem**: Notifications not showing
|
||||
- Ensure notification permissions granted
|
||||
- Check battery optimization settings
|
||||
- Verify notification channel is created
|
||||
|
||||
### iOS Issues
|
||||
|
||||
**Problem**: Token not received
|
||||
- Verify Push Notifications capability is enabled
|
||||
- Check APNs key configuration in backend
|
||||
- Ensure device is not in development mode if using production certs
|
||||
|
||||
**Problem**: Notifications not showing
|
||||
- Check notification permissions granted
|
||||
- Verify APNs configuration on backend
|
||||
- Ensure proper certificate/key is used
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ✅ Set up Firebase project
|
||||
2. ✅ Configure APNs on Apple Developer Portal
|
||||
3. ✅ Implement notification handling in app
|
||||
4. ✅ Test on Android device/emulator
|
||||
5. ✅ Test on iOS device
|
||||
6. ✅ Integrate with DarkWatch and SpamShield alerts
|
||||
7. ✅ Add notification preferences UI
|
||||
8. ✅ Implement deep linking from notifications
|
||||
|
||||
## Resources
|
||||
|
||||
- [Firebase Cloud Messaging Docs](https://firebase.google.com/docs/cloud-messaging)
|
||||
- [React Native Firebase Messaging](https://rnfirebase.io/messaging/usage)
|
||||
- [Apple Push Notification Service](https://developer.apple.com/documentation/usernotifications)
|
||||
462
docs/PUSH_NOTIFICATIONS_SETUP.md
Normal file
462
docs/PUSH_NOTIFICATIONS_SETUP.md
Normal file
@@ -0,0 +1,462 @@
|
||||
# Push Notifications Setup Guide (FCM & APNs)
|
||||
|
||||
## Overview
|
||||
|
||||
This guide covers setting up Firebase Cloud Messaging (FCM) for Android and Apple Push Notification service (APNs) for iOS push notifications in the ShieldAI mobile app.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
||||
│ Mobile │────▶│ API Gateway │────▶│ Notification │
|
||||
│ App │ │ /devices/* │ │ Service │
|
||||
└─────────────┘ └──────────────────┘ └────────┬────────┘
|
||||
│
|
||||
┌──────────────────────────────┼──────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────────────┐ ┌─────────────────┐ ┌──────────────────┐
|
||||
│ Firebase │ │ Apple Push │ │ Redis (Rate │
|
||||
│ Cloud │ │ Notification │ │ Limiting) │
|
||||
│ Messaging │ │ Service │ │ │
|
||||
│ (Android) │ │ (iOS) │ │ │
|
||||
└────────────────┘ └─────────────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Add the following to your `.env` file:
|
||||
|
||||
```bash
|
||||
# Firebase Cloud Messaging (FCM)
|
||||
FCM_PROJECT_ID=your-firebase-project-id
|
||||
FCM_CLIENT_EMAIL=service-account@your-project.iam.gserviceaccount.com
|
||||
FCM_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
|
||||
|
||||
# Apple Push Notification (APNs)
|
||||
APNS_KEY_ID=your-key-id
|
||||
APNS_TEAM_ID=your-team-id
|
||||
APNS_BUNDLE_ID=com.yourcompany.shieldai
|
||||
APNS_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
|
||||
|
||||
# Redis (for rate limiting)
|
||||
REDIS_URL=redis://localhost:6379
|
||||
|
||||
# Rate Limits
|
||||
PUSH_RATE_LIMIT=100
|
||||
RATE_LIMIT_WINDOW_SECONDS=60
|
||||
```
|
||||
|
||||
## Firebase Cloud Messaging (FCM) Setup
|
||||
|
||||
### 1. Create Firebase Project
|
||||
|
||||
1. Go to [Firebase Console](https://console.firebase.google.com/)
|
||||
2. Click **Add project**
|
||||
3. Enter project name: `shieldai-production` (or your preferred name)
|
||||
4. Disable Google Analytics (optional)
|
||||
5. Click **Create project**
|
||||
|
||||
### 2. Enable Cloud Messaging
|
||||
|
||||
1. In Firebase Console, go to **Project settings**
|
||||
2. Scroll down to **Your apps** section
|
||||
3. Click the **Web** icon `</>` to add a web app
|
||||
4. Register app nickname: `ShieldAI API`
|
||||
5. **Do not** check "Also set up for Firebase Hosting"
|
||||
6. Click **Register app**
|
||||
7. Copy the `firebaseConfig` for later use in mobile app
|
||||
|
||||
### 3. Generate Service Account Key
|
||||
|
||||
1. Go to **Project settings** → **Service accounts**
|
||||
2. Click **Generate new private key**
|
||||
3. Save the downloaded JSON file securely
|
||||
4. Extract the following values:
|
||||
- `project_id`
|
||||
- `client_email`
|
||||
- `private_key`
|
||||
|
||||
### 4. Configure FCM in ShieldAI
|
||||
|
||||
Create a file `packages/shared-notifications/.fcm-config.json` (gitignored):
|
||||
|
||||
```json
|
||||
{
|
||||
"projectId": "your-firebase-project-id",
|
||||
"clientEmail": "service-account@your-project.iam.gserviceaccount.com",
|
||||
"privateKey": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
|
||||
}
|
||||
```
|
||||
|
||||
Set environment variables:
|
||||
|
||||
```bash
|
||||
export FCM_PROJECT_ID="your-firebase-project-id"
|
||||
export FCM_CLIENT_EMAIL="service-account@your-project.iam.gserviceaccount.com"
|
||||
export FCM_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
|
||||
```
|
||||
|
||||
### 5. Install Firebase Admin SDK
|
||||
|
||||
```bash
|
||||
cd packages/shared-notifications
|
||||
npm install firebase-admin
|
||||
```
|
||||
|
||||
## Apple Push Notification (APNs) Setup
|
||||
|
||||
### 1. Create App ID
|
||||
|
||||
1. Go to [Apple Developer Portal](https://developer.apple.com/account/resources/identifiers/list)
|
||||
2. Click **+** to create new identifier
|
||||
3. Select **App IDs** → **App**
|
||||
4. Enter description: `ShieldAI Mobile`
|
||||
5. Enter Bundle ID: `com.yourcompany.shieldai`
|
||||
6. Enable **Push Notifications** capability
|
||||
7. Click **Continue** → **Register**
|
||||
|
||||
### 2. Create APNs Key
|
||||
|
||||
1. Go to **Certificates, IDs & Profiles** → **Keys**
|
||||
2. Click **+** to create new key
|
||||
3. Enter key name: `ShieldAI APNs Key`
|
||||
4. Enable **Apple Push Notification service (APNs)**
|
||||
5. Click **Continue** → **Register**
|
||||
6. **Download the key** (`.p8` file) - you can only download once!
|
||||
7. Note the **Key ID** displayed
|
||||
|
||||
### 3. Configure APNs in ShieldAI
|
||||
|
||||
Convert the `.p8` file to PEM format:
|
||||
|
||||
```bash
|
||||
# Convert .p8 to PEM
|
||||
openssl pkcs8 -topk8 -nocrypt -in AuthKey_XXXXXX.p8 -out apns_key.pem
|
||||
|
||||
# Copy the PEM content
|
||||
cat apns_key.pem
|
||||
```
|
||||
|
||||
Set environment variables:
|
||||
|
||||
```bash
|
||||
export APNS_KEY_ID="XXXXXX"
|
||||
export APNS_TEAM_ID="YYYYYY"
|
||||
export APNS_BUNDLE_ID="com.yourcompany.shieldai"
|
||||
export APNS_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
|
||||
```
|
||||
|
||||
### 4. Configure in Xcode
|
||||
|
||||
In your iOS app's `Info.plist`:
|
||||
|
||||
```xml
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
Enable capabilities in Xcode:
|
||||
- **Push Notifications**
|
||||
- **Background Modes** → **Remote notifications**
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Device Registration
|
||||
|
||||
#### Register Device
|
||||
```http
|
||||
POST /api/v1/devices/register
|
||||
Authorization: Bearer <JWT_TOKEN>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"platform": "android",
|
||||
"fcmToken": "eXwR9...",
|
||||
"appVersion": "1.0.0",
|
||||
"osVersion": "Android 13"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"device": {
|
||||
"deviceId": "dev_1234567890_abc123",
|
||||
"platform": "android",
|
||||
"registeredAt": "2026-05-14T13:00:00.000Z"
|
||||
},
|
||||
"message": "Device registered successfully"
|
||||
}
|
||||
```
|
||||
|
||||
#### Update Device Tokens
|
||||
```http
|
||||
PUT /api/v1/devices/:deviceId/tokens
|
||||
Authorization: Bearer <JWT_TOKEN>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"fcmToken": "new-token-here",
|
||||
"apnsToken": "new-token-here"
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Registered Devices
|
||||
```http
|
||||
GET /api/v1/devices
|
||||
Authorization: Bearer <JWT_TOKEN>
|
||||
```
|
||||
|
||||
#### Deregister Device
|
||||
```http
|
||||
DELETE /api/v1/devices/:deviceId
|
||||
Authorization: Bearer <JWT_TOKEN>
|
||||
```
|
||||
|
||||
## Mobile App Integration
|
||||
|
||||
### Android (React Native)
|
||||
|
||||
```javascript
|
||||
import messaging from '@react-native-firebase/messaging';
|
||||
import { API } from '@shieldai/api-client';
|
||||
|
||||
// Request permission
|
||||
async function requestPushPermission() {
|
||||
const authStatus = await messaging().requestPermission();
|
||||
const enabled =
|
||||
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
|
||||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
|
||||
|
||||
if (enabled) {
|
||||
console.log('Push permission granted:', authStatus);
|
||||
}
|
||||
}
|
||||
|
||||
// Get FCM token
|
||||
async function getFCMToken() {
|
||||
const token = await messaging().getToken();
|
||||
return token;
|
||||
}
|
||||
|
||||
// Register device with API
|
||||
async function registerDevice() {
|
||||
try {
|
||||
const fcmToken = await getFCMToken();
|
||||
|
||||
const response = await API.post('/devices/register', {
|
||||
platform: 'android',
|
||||
fcmToken,
|
||||
appVersion: '1.0.0',
|
||||
osVersion: Platform.OS + ' ' + Platform.Version,
|
||||
});
|
||||
|
||||
console.log('Device registered:', response.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to register device:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for token refresh
|
||||
messaging().onTokenRefresh(async (newToken) => {
|
||||
await API.put('/devices/tokens', {
|
||||
fcmToken: newToken,
|
||||
});
|
||||
});
|
||||
|
||||
// Handle foreground messages
|
||||
messaging().onMessage(async (remoteMessage) => {
|
||||
console.log('Foreground message received:', remoteMessage);
|
||||
// Show local notification
|
||||
});
|
||||
|
||||
// Handle background messages
|
||||
messaging().setBackgroundMessageHandler(async remoteMessage => {
|
||||
console.log('Background message received:', remoteMessage);
|
||||
});
|
||||
```
|
||||
|
||||
### iOS (React Native)
|
||||
|
||||
```javascript
|
||||
import messaging from '@react-native-firebase/messaging';
|
||||
import { API } from '@shieldai/api-client';
|
||||
|
||||
// Request permission
|
||||
async function requestPushPermission() {
|
||||
const authStatus = await messaging().requestPermission();
|
||||
const enabled =
|
||||
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
|
||||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
|
||||
|
||||
if (enabled) {
|
||||
console.log('Push permission granted:', authStatus);
|
||||
}
|
||||
}
|
||||
|
||||
// Get APNs token
|
||||
async function getAPNSToken() {
|
||||
const token = await messaging().getToken();
|
||||
return token;
|
||||
}
|
||||
|
||||
// Register device with API
|
||||
async function registerDevice() {
|
||||
try {
|
||||
const apnsToken = await getAPNSToken();
|
||||
|
||||
const response = await API.post('/devices/register', {
|
||||
platform: 'ios',
|
||||
apnsToken,
|
||||
appVersion: '1.0.0',
|
||||
osVersion: Platform.OS + ' ' + Platform.Version,
|
||||
});
|
||||
|
||||
console.log('Device registered:', response.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to register device:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle foreground messages
|
||||
messaging().onMessage(async (remoteMessage => {
|
||||
console.log('Foreground message received:', remoteMessage);
|
||||
// Show local notification
|
||||
}));
|
||||
```
|
||||
|
||||
## Testing Push Notifications
|
||||
|
||||
### Test with Firebase Console
|
||||
|
||||
1. Go to [Firebase Console](https://console.firebase.google.com/) → **Cloud Messaging**
|
||||
2. Click **Send your first message**
|
||||
3. Enter notification title and body
|
||||
4. Choose test device or all users
|
||||
5. Click **Send test message**
|
||||
|
||||
### Test with API
|
||||
|
||||
```bash
|
||||
# Send test push notification
|
||||
curl -X POST https://your-api.com/api/v1/notifications/send \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"userId": "user-123",
|
||||
"channel": "push",
|
||||
"title": "Test Notification",
|
||||
"body": "This is a test push notification",
|
||||
"data": {
|
||||
"type": "test",
|
||||
"action": "open_app"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Test with cURL (Direct FCM)
|
||||
|
||||
```bash
|
||||
curl -X POST https://fcm.googleapis.com/v1/projects/your-project-id/messages:send \
|
||||
-H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"message": {
|
||||
"token": "FCM_TOKEN_HERE",
|
||||
"notification": {
|
||||
"title": "Test Title",
|
||||
"body": "Test Body"
|
||||
},
|
||||
"data": {
|
||||
"type": "test"
|
||||
}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## Production Checklist
|
||||
|
||||
### FCM Configuration
|
||||
- [ ] Firebase project created
|
||||
- [ ] Service account key generated and stored securely
|
||||
- [ ] FCM environment variables configured
|
||||
- [ ] Firebase Admin SDK initialized
|
||||
- [ ] Test notifications sent successfully
|
||||
|
||||
### APNs Configuration
|
||||
- [ ] App ID created with push capability
|
||||
- [ ] APNs key generated and downloaded
|
||||
- [ ] Key converted to PEM format
|
||||
- [ ] APNs environment variables configured
|
||||
- [ ] Xcode capabilities enabled
|
||||
- [ ] Test notifications sent from simulator
|
||||
|
||||
### API Configuration
|
||||
- [ ] Device registration endpoints working
|
||||
- [ ] Token update endpoints working
|
||||
- [ ] Rate limiting configured
|
||||
- [ ] Error handling implemented
|
||||
- [ ] Logging for push notifications
|
||||
|
||||
### Mobile App Configuration
|
||||
- [ ] Push permissions requested
|
||||
- [ ] Token retrieval implemented
|
||||
- [ ] Device registration on app start
|
||||
- [ ] Token refresh handling
|
||||
- [ ] Foreground message handling
|
||||
- [ ] Background message handling
|
||||
- [ ] Notification display implemented
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### FCM Token Not Received
|
||||
- Check Firebase configuration in mobile app
|
||||
- Verify Google Services (Android) / GoogleService-Info.plist (iOS)
|
||||
- Ensure push permissions granted
|
||||
|
||||
### APNs Token Not Received
|
||||
- Verify App ID configuration in Apple Developer
|
||||
- Check APNs key configuration
|
||||
- Ensure background modes enabled in Xcode
|
||||
- Test on actual device (not simulator)
|
||||
|
||||
### Notifications Not Delivering
|
||||
- Check device registration status
|
||||
- Verify tokens are valid
|
||||
- Check rate limiting status
|
||||
- Review server logs for errors
|
||||
|
||||
### Token Refresh Issues
|
||||
- Listen for `onTokenRefresh` events
|
||||
- Update tokens via API immediately
|
||||
- Handle network errors gracefully
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Never expose service account keys** in client-side code
|
||||
2. **Always validate** device ownership on server
|
||||
3. **Use HTTPS** for all API calls
|
||||
4. **Implement rate limiting** to prevent abuse
|
||||
5. **Store tokens securely** using secure storage libraries
|
||||
6. **Deregister devices** on user logout
|
||||
|
||||
## Monitoring
|
||||
|
||||
Monitor the following metrics:
|
||||
- Push notification delivery rate
|
||||
- Token refresh frequency
|
||||
- Device registration failures
|
||||
- Rate limit hits
|
||||
- Notification open rate
|
||||
|
||||
## Support
|
||||
|
||||
- Firebase Support: https://firebase.google.com/support
|
||||
- Apple Developer Support: https://developer.apple.com/contact/
|
||||
- FCM Documentation: https://firebase.google.com/docs/cloud-messaging
|
||||
- APNs Documentation: https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server
|
||||
353
docs/STRIPE_INTEGRATION.md
Normal file
353
docs/STRIPE_INTEGRATION.md
Normal file
@@ -0,0 +1,353 @@
|
||||
# Stripe Integration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide covers the Stripe live API integration for ShieldAI subscription management. The implementation includes webhook handlers, subscription management endpoints, and tier-based feature gating.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Add the following to your `.env` file:
|
||||
|
||||
```bash
|
||||
# Stripe Configuration
|
||||
STRIPE_API_KEY=sk_live_...
|
||||
STRIPE_WEBHOOK_SECRET=whsec_...
|
||||
STRIPE_FREE_TIER_PRICE_ID=price_...
|
||||
STRIPE_BASIC_TIER_PRICE_ID=price_...
|
||||
STRIPE_PLUS_TIER_PRICE_ID=price_...
|
||||
STRIPE_PREMIUM_TIER_PRICE_ID=price_...
|
||||
```
|
||||
|
||||
## Getting Stripe Live API Keys
|
||||
|
||||
### 1. Get Live API Key
|
||||
|
||||
1. Go to [Stripe Dashboard](https://dashboard.stripe.com/)
|
||||
2. Navigate to **Developers** → **API keys**
|
||||
3. Toggle **Live mode** (top right corner)
|
||||
4. Copy the **Secret key** (starts with `sk_live_`)
|
||||
|
||||
### 2. Get Webhook Signing Secret
|
||||
|
||||
1. In Stripe Dashboard, go to **Developers** → **Webhooks**
|
||||
2. Click **Add endpoint**
|
||||
3. Add your webhook URL: `https://your-api-domain.com/api/v1/billing/webhooks/stripe`
|
||||
4. Select these events to listen for:
|
||||
- `customer.subscription.created`
|
||||
- `customer.subscription.updated`
|
||||
- `customer.subscription.deleted`
|
||||
- `invoice.payment_succeeded`
|
||||
- `invoice.payment_failed`
|
||||
5. Click **Add endpoint**
|
||||
6. Copy the **Signing secret** (starts with `whsec_`)
|
||||
|
||||
### 3. Create Price IDs for Subscription Tiers
|
||||
|
||||
1. Go to **Products** in Stripe Dashboard
|
||||
2. Create products for each tier:
|
||||
- Free Tier ( $0/month)
|
||||
- Basic Tier ($9.99/month)
|
||||
- Plus Tier ($19.99/month)
|
||||
- Premium Tier ($49.99/month)
|
||||
3. For each product, create a recurring price
|
||||
4. Copy the Price IDs (start with `price_`)
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Subscription Management
|
||||
|
||||
#### Get Current Subscription
|
||||
```http
|
||||
GET /api/v1/billing/subscription
|
||||
Authorization: Bearer <JWT_TOKEN>
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"subscription": {
|
||||
"id": "sub_123",
|
||||
"status": "active",
|
||||
"currentPeriodStart": "2026-05-01T00:00:00.000Z",
|
||||
"currentPeriodEnd": "2026-06-01T00:00:00.000Z",
|
||||
"cancelAtPeriodEnd": false
|
||||
},
|
||||
"customer": {
|
||||
"id": "cus_123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Create Subscription
|
||||
```http
|
||||
POST /api/v1/billing/subscription/create
|
||||
Authorization: Bearer <JWT_TOKEN>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"tier": "basic",
|
||||
"customerId": "cus_123"
|
||||
}
|
||||
```
|
||||
|
||||
#### Update Subscription Tier
|
||||
```http
|
||||
PUT /api/v1/billing/subscription/:subscriptionId/tier
|
||||
Authorization: Bearer <JWT_TOKEN>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"tier": "plus"
|
||||
}
|
||||
```
|
||||
|
||||
#### Cancel Subscription
|
||||
```http
|
||||
DELETE /api/v1/billing/subscription/:subscriptionId
|
||||
Authorization: Bearer <JWT_TOKEN>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"cancelAtPeriodEnd": true
|
||||
}
|
||||
```
|
||||
|
||||
#### Get User Tier
|
||||
```http
|
||||
GET /api/v1/billing/user/tier
|
||||
Authorization: Bearer <JWT_TOKEN>
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"tier": "basic",
|
||||
"limits": {
|
||||
"callMinutesLimit": 500,
|
||||
"smsCountLimit": 2000,
|
||||
"darkWebScans": 12
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Create Customer Portal Session
|
||||
```http
|
||||
POST /api/v1/billing/customer/portal
|
||||
Authorization: Bearer <JWT_TOKEN>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"customerId": "cus_123",
|
||||
"returnUrl": "https://yourapp.com/billing"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"url": "https://billing.stripe.com/p/login/...",
|
||||
"expiresAt": "2026-05-14T14:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Invoice History
|
||||
```http
|
||||
GET /api/v1/billing/invoices?customerId=cus_123
|
||||
Authorization: Bearer <JWT_TOKEN>
|
||||
```
|
||||
|
||||
### Webhook Handler
|
||||
|
||||
#### Stripe Webhook
|
||||
```http
|
||||
POST /api/v1/billing/webhooks/stripe
|
||||
Stripe-Signature: <SIGNATURE>
|
||||
Content-Type: application/json
|
||||
|
||||
<Raw Stripe Event Body>
|
||||
```
|
||||
|
||||
## Webhook Events Handled
|
||||
|
||||
### customer.subscription.created
|
||||
Triggered when a new subscription is created.
|
||||
|
||||
### customer.subscription.updated
|
||||
Triggered when subscription details change (tier upgrade/downgrade, payment method update).
|
||||
|
||||
### customer.subscription.deleted
|
||||
Triggered when a subscription is cancelled.
|
||||
|
||||
### invoice.payment_succeeded
|
||||
Triggered when a payment is successfully processed.
|
||||
|
||||
### invoice.payment_failed
|
||||
Triggered when a payment fails.
|
||||
|
||||
## Testing
|
||||
|
||||
### Test with Stripe CLI
|
||||
|
||||
1. Install [Stripe CLI](https://stripe.com/docs/stripe-cli)
|
||||
2. Login: `stripe login`
|
||||
3. Forward webhooks: `stripe listen --forward-to localhost:3000/api/v1/billing/webhooks/stripe`
|
||||
|
||||
### Test Events
|
||||
|
||||
```bash
|
||||
# Trigger a subscription created event
|
||||
stripe trigger customer.subscription.created
|
||||
|
||||
# Trigger a payment succeeded event
|
||||
stripe trigger invoice.payment_succeeded
|
||||
```
|
||||
|
||||
## Mobile App Integration
|
||||
|
||||
### React Native Example
|
||||
|
||||
```javascript
|
||||
import { API } from '@shieldai/api-client';
|
||||
|
||||
// Get current subscription
|
||||
const getSubscription = async () => {
|
||||
try {
|
||||
const response = await API.get('/billing/subscription');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch subscription:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Create subscription
|
||||
const createSubscription = async (tier, customerId) => {
|
||||
try {
|
||||
const response = await API.post('/billing/subscription/create', {
|
||||
tier,
|
||||
customerId,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to create subscription:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Upgrade subscription
|
||||
const upgradeSubscription = async (subscriptionId, newTier) => {
|
||||
try {
|
||||
const response = await API.put(
|
||||
`/billing/subscription/${subscriptionId}/tier`,
|
||||
{ tier: newTier }
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to upgrade subscription:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Cancel subscription
|
||||
const cancelSubscription = async (subscriptionId) => {
|
||||
try {
|
||||
const response = await API.delete(
|
||||
`/billing/subscription/${subscriptionId}`,
|
||||
{ data: { cancelAtPeriodEnd: true } }
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to cancel subscription:', error);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Feature Gating
|
||||
|
||||
Use the middleware to protect routes based on subscription tier:
|
||||
|
||||
```typescript
|
||||
import { requireTier } from '@shieldai/shared-billing';
|
||||
import { SubscriptionTier } from '@shieldai/shared-billing';
|
||||
|
||||
// Require minimum tier
|
||||
fastify.get(
|
||||
'/premium-feature',
|
||||
{
|
||||
preHandler: requireTier([SubscriptionTier.BASIC, SubscriptionTier.PLUS, SubscriptionTier.PREMIUM])
|
||||
},
|
||||
async (request, reply) => {
|
||||
// Only accessible to BASIC tier and above
|
||||
}
|
||||
);
|
||||
|
||||
// Require specific tier
|
||||
fastify.get(
|
||||
'/exclusive-feature',
|
||||
{
|
||||
preHandler: requireTier([SubscriptionTier.PREMIUM])
|
||||
},
|
||||
async (request, reply) => {
|
||||
// Only accessible to PREMIUM tier
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Deployment Checklist
|
||||
|
||||
- [ ] Set `STRIPE_API_KEY` to live key (not test key)
|
||||
- [ ] Set `STRIPE_WEBHOOK_SECRET` to live webhook secret
|
||||
- [ ] Configure webhook endpoint in Stripe Dashboard
|
||||
- [ ] Verify webhook events are being received
|
||||
- [ ] Test subscription creation flow
|
||||
- [ ] Test tier upgrade/downgrade flow
|
||||
- [ ] Test cancellation flow
|
||||
- [ ] Verify feature gating works correctly
|
||||
- [ ] Monitor Stripe dashboard for errors
|
||||
- [ ] Set up alerts for failed payments
|
||||
|
||||
## Production Considerations
|
||||
|
||||
### Security
|
||||
|
||||
1. **Never expose secret keys** in client-side code
|
||||
2. **Always verify webhook signatures** on the server
|
||||
3. **Use HTTPS** for all API endpoints in production
|
||||
4. **Implement rate limiting** on webhook endpoints
|
||||
|
||||
### Error Handling
|
||||
|
||||
1. **Idempotency**: Webhook events may be delivered multiple times
|
||||
2. **Retry logic**: Stripe will retry failed webhook deliveries
|
||||
3. **Logging**: Log all webhook events for debugging
|
||||
4. **Alerts**: Set up alerts for payment failures
|
||||
|
||||
### Compliance
|
||||
|
||||
1. **PCI DSS**: Use Stripe Elements for payment collection
|
||||
2. **GDPR**: Handle customer data according to regulations
|
||||
3. **Tax**: Consider tax calculation for different regions
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Webhook Signature Verification Fails
|
||||
|
||||
- Ensure `STRIPE_WEBHOOK_SECRET` is correctly set
|
||||
- Verify the webhook URL matches what's configured in Stripe
|
||||
- Check that raw body is being captured (not parsed JSON)
|
||||
|
||||
### Subscription Creation Fails
|
||||
|
||||
- Verify `STRIPE_API_KEY` is valid
|
||||
- Check that price IDs exist and are active
|
||||
- Ensure customer ID is valid
|
||||
|
||||
### Tier Not Updating
|
||||
|
||||
- Verify the new tier's price ID exists
|
||||
- Check for active subscriptions on the customer
|
||||
- Review Stripe dashboard for error messages
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
- Stripe Dashboard: https://dashboard.stripe.com/
|
||||
- Stripe Docs: https://stripe.com/docs
|
||||
- Stripe Support: https://support.stripe.com/
|
||||
Reference in New Issue
Block a user