unified-video-framework 1.4.21 → 1.4.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/core/dist/BasePlayer.js +1 -1
- package/packages/core/dist/interfaces.d.ts +48 -0
- package/packages/core/dist/interfaces.d.ts.map +1 -1
- package/packages/core/src/interfaces.ts +50 -0
- package/packages/web/dist/SecureVideoPlayer.js +1 -1
- package/packages/web/dist/index.js +3 -3
- package/packages/web/dist/paywall/PaymentGatewayAdapter.d.ts +72 -0
- package/packages/web/dist/paywall/PaymentGatewayAdapter.d.ts.map +1 -0
- package/packages/web/dist/paywall/PaymentGatewayAdapter.js +206 -0
- package/packages/web/dist/paywall/PaymentGatewayAdapter.js.map +1 -0
- package/packages/web/dist/paywall/PaywallController.d.ts +3 -0
- package/packages/web/dist/paywall/PaywallController.d.ts.map +1 -1
- package/packages/web/dist/paywall/PaywallController.js +158 -189
- package/packages/web/dist/paywall/PaywallController.js.map +1 -1
- package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
- package/packages/web/dist/react/WebPlayerView.js +4 -10
- package/packages/web/dist/react/WebPlayerView.js.map +1 -1
- package/packages/web/src/paywall/PaymentGatewayAdapter.ts +346 -0
- package/packages/web/src/paywall/PaywallController.ts +215 -206
- package/packages/web/src/react/WebPlayerView.tsx +1 -10
- package/packages/ios/README.md +0 -84
- package/packages/web/dist/paywall/EmailAuthController.d.ts +0 -60
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { PaywallConfig } from '@unified-video/core';
|
|
2
2
|
import { EmailAuthController, EmailAuthControllerOptions } from './EmailAuthController';
|
|
3
|
+
import {
|
|
4
|
+
PaymentGatewayManager,
|
|
5
|
+
PaymentGatewayAdapter,
|
|
6
|
+
CashfreePaymentGateway,
|
|
7
|
+
GenericPaymentGateway,
|
|
8
|
+
PaymentRequest,
|
|
9
|
+
PaymentResponse
|
|
10
|
+
} from './PaymentGatewayAdapter';
|
|
3
11
|
|
|
4
12
|
export type PaywallControllerOptions = {
|
|
5
13
|
getOverlayContainer: () => HTMLElement | null;
|
|
@@ -17,11 +25,16 @@ export class PaywallController {
|
|
|
17
25
|
private emailAuth: EmailAuthController | null = null;
|
|
18
26
|
private authenticatedUserId: string | null = null;
|
|
19
27
|
private sessionToken: string | null = null;
|
|
28
|
+
private paymentManager: PaymentGatewayManager = new PaymentGatewayManager();
|
|
29
|
+
private currentPaymentData: { gateway: string; orderId?: string; sessionId?: string } | null = null;
|
|
20
30
|
|
|
21
31
|
constructor(config: PaywallConfig | null, opts: PaywallControllerOptions) {
|
|
22
32
|
this.config = config;
|
|
23
33
|
this.opts = opts;
|
|
24
34
|
|
|
35
|
+
// Initialize payment gateway manager with default gateways
|
|
36
|
+
this.initializePaymentGateways();
|
|
37
|
+
|
|
25
38
|
// Initialize EmailAuthController if email auth is enabled
|
|
26
39
|
this.initializeEmailAuth();
|
|
27
40
|
|
|
@@ -34,29 +47,11 @@ export class PaywallController {
|
|
|
34
47
|
}
|
|
35
48
|
|
|
36
49
|
updateConfig(config: PaywallConfig | null) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (hadWorkingEmailAuth && newConfigLacksEmailAuth) {
|
|
43
|
-
console.log('[PaywallController] Preserving email auth instance during config update');
|
|
44
|
-
// Only update non-email auth related config, keep the email auth part
|
|
45
|
-
this.config = {
|
|
46
|
-
...config,
|
|
47
|
-
emailAuth: this.config?.emailAuth // Preserve existing email auth config
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
if (this.emailAuth) {
|
|
51
|
-
this.emailAuth.updateConfig(this.config);
|
|
52
|
-
}
|
|
53
|
-
} else {
|
|
54
|
-
// Normal config update
|
|
55
|
-
this.config = config;
|
|
56
|
-
this.initializeEmailAuth();
|
|
57
|
-
if (this.emailAuth) {
|
|
58
|
-
this.emailAuth.updateConfig(config);
|
|
59
|
-
}
|
|
50
|
+
this.config = config;
|
|
51
|
+
this.initializePaymentGateways();
|
|
52
|
+
this.initializeEmailAuth();
|
|
53
|
+
if (this.emailAuth) {
|
|
54
|
+
this.emailAuth.updateConfig(config);
|
|
60
55
|
}
|
|
61
56
|
}
|
|
62
57
|
|
|
@@ -74,34 +69,21 @@ export class PaywallController {
|
|
|
74
69
|
// Check authentication first if email auth is enabled
|
|
75
70
|
if (this.config.emailAuth?.enabled) {
|
|
76
71
|
console.log('[PaywallController] Email auth is enabled, checking authentication');
|
|
72
|
+
const isAuthenticated = this.emailAuth?.isAuthenticated();
|
|
73
|
+
console.log('[PaywallController] User authenticated:', isAuthenticated);
|
|
77
74
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// If still no instance after initialization, show error
|
|
85
|
-
if (!this.emailAuth) {
|
|
86
|
-
console.error('[PaywallController] Failed to initialize email auth, proceeding to payment overlay');
|
|
87
|
-
// Continue to payment overlay as fallback
|
|
75
|
+
if (!isAuthenticated) {
|
|
76
|
+
console.log('[PaywallController] User not authenticated, opening email auth modal');
|
|
77
|
+
// Show email authentication modal first
|
|
78
|
+
this.emailAuth?.openAuthModal();
|
|
79
|
+
return;
|
|
88
80
|
} else {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
this.emailAuth.openAuthModal();
|
|
96
|
-
return;
|
|
97
|
-
} else {
|
|
98
|
-
console.log('[PaywallController] User already authenticated, proceeding to payment overlay');
|
|
99
|
-
// Update userId for authenticated user
|
|
100
|
-
this.authenticatedUserId = this.emailAuth.getAuthenticatedUserId() || this.config.userId || null;
|
|
101
|
-
// Update config with authenticated userId for API calls
|
|
102
|
-
if (this.authenticatedUserId && this.config) {
|
|
103
|
-
this.config.userId = this.authenticatedUserId;
|
|
104
|
-
}
|
|
81
|
+
console.log('[PaywallController] User already authenticated, proceeding to payment overlay');
|
|
82
|
+
// Update userId for authenticated user
|
|
83
|
+
this.authenticatedUserId = this.emailAuth?.getAuthenticatedUserId() || this.config.userId || null;
|
|
84
|
+
// Update config with authenticated userId for API calls
|
|
85
|
+
if (this.authenticatedUserId && this.config) {
|
|
86
|
+
this.config.userId = this.authenticatedUserId;
|
|
105
87
|
}
|
|
106
88
|
}
|
|
107
89
|
}
|
|
@@ -109,47 +91,16 @@ export class PaywallController {
|
|
|
109
91
|
// Show payment overlay
|
|
110
92
|
console.log('[PaywallController] Showing payment overlay');
|
|
111
93
|
const root = this.ensureOverlay();
|
|
112
|
-
if (!root)
|
|
113
|
-
console.log('[PaywallController] Failed to create overlay');
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Show overlay with proper animation
|
|
94
|
+
if (!root) return;
|
|
118
95
|
root.style.display = 'flex';
|
|
119
96
|
root.classList.add('active');
|
|
120
|
-
|
|
121
|
-
// Force reflow then fade in with animation
|
|
122
|
-
void root.offsetWidth;
|
|
123
|
-
root.style.opacity = '1';
|
|
124
|
-
|
|
125
|
-
// Also animate the modal inside
|
|
126
|
-
const modal = root.querySelector('.uvf-paywall-modal') as HTMLElement;
|
|
127
|
-
if (modal) {
|
|
128
|
-
modal.style.transform = 'translateY(0)';
|
|
129
|
-
modal.style.opacity = '1';
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
console.log('[PaywallController] Payment overlay displayed successfully');
|
|
133
97
|
this.opts.onShow?.();
|
|
134
98
|
}
|
|
135
99
|
|
|
136
100
|
closeOverlay() {
|
|
137
101
|
if (this.overlayEl) {
|
|
138
|
-
|
|
139
|
-
this.overlayEl.style.
|
|
140
|
-
const modal = this.overlayEl.querySelector('.uvf-paywall-modal') as HTMLElement;
|
|
141
|
-
if (modal) {
|
|
142
|
-
modal.style.transform = 'translateY(20px)';
|
|
143
|
-
modal.style.opacity = '0';
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Hide after animation
|
|
147
|
-
setTimeout(() => {
|
|
148
|
-
if (this.overlayEl) {
|
|
149
|
-
this.overlayEl.classList.remove('active');
|
|
150
|
-
this.overlayEl.style.display = 'none';
|
|
151
|
-
}
|
|
152
|
-
}, 300); // Match the CSS transition duration
|
|
102
|
+
this.overlayEl.classList.remove('active');
|
|
103
|
+
this.overlayEl.style.display = 'none';
|
|
153
104
|
}
|
|
154
105
|
this.opts.onClose?.();
|
|
155
106
|
}
|
|
@@ -162,38 +113,11 @@ export class PaywallController {
|
|
|
162
113
|
ov.className = 'uvf-paywall-overlay';
|
|
163
114
|
ov.setAttribute('role', 'dialog');
|
|
164
115
|
ov.setAttribute('aria-modal', 'true');
|
|
165
|
-
ov.style.cssText =
|
|
166
|
-
position: absolute;
|
|
167
|
-
inset: 0;
|
|
168
|
-
background: rgba(0, 0, 0, 0.95);
|
|
169
|
-
z-index: 2147483647;
|
|
170
|
-
display: none;
|
|
171
|
-
align-items: center;
|
|
172
|
-
justify-content: center;
|
|
173
|
-
opacity: 0;
|
|
174
|
-
transition: opacity 0.3s ease;
|
|
175
|
-
`;
|
|
116
|
+
ov.style.cssText = 'position:absolute;inset:0;background:rgba(0,0,0,0.85);z-index:2147483000;display:none;align-items:center;justify-content:center;';
|
|
176
117
|
|
|
177
118
|
const modal = document.createElement('div');
|
|
178
119
|
modal.className = 'uvf-paywall-modal';
|
|
179
|
-
modal.style.cssText =
|
|
180
|
-
width: 90vw;
|
|
181
|
-
height: 85vh;
|
|
182
|
-
max-width: 1000px;
|
|
183
|
-
max-height: 700px;
|
|
184
|
-
background: #0f0f10;
|
|
185
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
186
|
-
border-radius: 16px;
|
|
187
|
-
display: flex;
|
|
188
|
-
flex-direction: column;
|
|
189
|
-
overflow: hidden;
|
|
190
|
-
box-shadow:
|
|
191
|
-
0 20px 60px rgba(0, 0, 0, 0.7),
|
|
192
|
-
0 0 0 1px rgba(255, 255, 255, 0.1);
|
|
193
|
-
transform: translateY(20px);
|
|
194
|
-
opacity: 0;
|
|
195
|
-
transition: transform 0.3s ease, opacity 0.3s ease;
|
|
196
|
-
`;
|
|
120
|
+
modal.style.cssText = 'width:80vw;height:80vh;max-width:1100px;max-height:800px;background:#0f0f10;border:1px solid rgba(255,255,255,0.15);border-radius:12px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,0.7)';
|
|
197
121
|
|
|
198
122
|
const header = document.createElement('div');
|
|
199
123
|
header.style.cssText = 'display:flex;gap:16px;align-items:center;padding:16px 20px;border-bottom:1px solid rgba(255,255,255,0.1)';
|
|
@@ -247,68 +171,104 @@ export class PaywallController {
|
|
|
247
171
|
const wrap = document.createElement('div');
|
|
248
172
|
wrap.style.cssText = 'display:flex;gap:12px;flex-wrap:wrap;justify-content:center;';
|
|
249
173
|
|
|
250
|
-
|
|
174
|
+
// Get available payment gateways from manager
|
|
175
|
+
const availableGateways = this.paymentManager.getGatewayNames();
|
|
176
|
+
const configuredGateways = this.config.gateways || availableGateways;
|
|
177
|
+
|
|
178
|
+
for (const gatewayName of configuredGateways) {
|
|
179
|
+
const gateway = this.paymentManager.getGateway(gatewayName);
|
|
180
|
+
if (!gateway) continue;
|
|
181
|
+
|
|
251
182
|
const btn = document.createElement('button');
|
|
252
|
-
btn.textContent =
|
|
183
|
+
btn.textContent = gateway.getDisplayName();
|
|
253
184
|
btn.style.cssText = 'background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);border-radius:8px;padding:12px 16px;cursor:pointer;min-width:120px;';
|
|
254
|
-
btn.addEventListener('click', () => this.openGateway(
|
|
185
|
+
btn.addEventListener('click', () => this.openGateway(gatewayName));
|
|
255
186
|
wrap.appendChild(btn);
|
|
256
187
|
}
|
|
257
188
|
this.gatewayStepEl!.appendChild(title);
|
|
258
189
|
this.gatewayStepEl!.appendChild(wrap);
|
|
259
190
|
}
|
|
260
191
|
|
|
261
|
-
private async openGateway(
|
|
192
|
+
private async openGateway(gatewayName: string) {
|
|
262
193
|
try {
|
|
263
194
|
if (!this.config) return;
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const
|
|
195
|
+
|
|
196
|
+
console.log(`[PaywallController] Opening gateway: ${gatewayName}`);
|
|
197
|
+
|
|
198
|
+
// Get authenticated user email and video slug
|
|
199
|
+
const userEmail = this.emailAuth?.getAuthenticatedUserId() || this.authenticatedUserId;
|
|
200
|
+
const videoSlug = (this.config as any)?.metadata?.slug || this.config?.videoSlug;
|
|
201
|
+
|
|
202
|
+
if (!userEmail) {
|
|
203
|
+
console.error('[PaywallController] User not authenticated - email required for payment');
|
|
204
|
+
alert('Please complete authentication first');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!videoSlug) {
|
|
209
|
+
console.error('[PaywallController] Missing video slug for payment');
|
|
210
|
+
alert('Video information missing. Please refresh and try again.');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
269
213
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
this.popup = window.open(data.url, 'uvfCheckout', `popup=1,width=${w},height=${h},left=${left},top=${top}`);
|
|
283
|
-
this.startPolling();
|
|
214
|
+
// Prepare payment request using email as userId and slug as videoId
|
|
215
|
+
const paymentRequest: PaymentRequest = {
|
|
216
|
+
userId: userEmail, // Use email as user identifier
|
|
217
|
+
videoId: videoSlug, // Use slug as video identifier
|
|
218
|
+
amount: this.config.pricing?.amount || 100,
|
|
219
|
+
currency: this.config.pricing?.currency || 'INR',
|
|
220
|
+
successUrl: `${window.location.origin}${window.location.pathname}?payment=success&popup=1`,
|
|
221
|
+
cancelUrl: `${window.location.origin}${window.location.pathname}?payment=cancel&popup=1`,
|
|
222
|
+
metadata: {
|
|
223
|
+
videoSlug: videoSlug,
|
|
224
|
+
userEmail: userEmail,
|
|
225
|
+
title: this.config.pricing?.title || 'Video Access'
|
|
284
226
|
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
console.log('[PaywallController] Payment request:', paymentRequest);
|
|
230
|
+
|
|
231
|
+
// Create payment using gateway manager
|
|
232
|
+
const paymentResponse = await this.paymentManager.createPayment(gatewayName, paymentRequest);
|
|
233
|
+
|
|
234
|
+
console.log('[PaywallController] Payment response:', paymentResponse);
|
|
235
|
+
|
|
236
|
+
if (!paymentResponse.success) {
|
|
237
|
+
console.error('[PaywallController] Payment creation failed:', paymentResponse.error);
|
|
238
|
+
alert(paymentResponse.message || 'Payment creation failed');
|
|
285
239
|
return;
|
|
286
240
|
}
|
|
287
241
|
|
|
288
|
-
if (
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
let pre: Window | null = null;
|
|
292
|
-
try { pre = window.open('', 'uvfCheckout', features); } catch(_) { pre = null; }
|
|
293
|
-
const res = await fetch(`${apiBase}/api/rentals/cashfree/order`, {
|
|
294
|
-
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
295
|
-
body: JSON.stringify({ userId, videoId, returnUrl: window.location.origin + window.location.pathname })
|
|
296
|
-
});
|
|
297
|
-
const data = await res.json();
|
|
298
|
-
if (data?.paymentLink && data?.orderId) {
|
|
299
|
-
try { this.popup && !this.popup.closed && this.popup.close(); } catch (_) {}
|
|
300
|
-
this.popup = pre && !pre.closed ? pre : window.open('', 'uvfCheckout', features);
|
|
301
|
-
try { if (this.popup) this.popup.location.href = data.paymentLink; } catch(_) {}
|
|
302
|
-
(window as any)._uvf_cfOrderId = data.orderId;
|
|
303
|
-
this.startPolling();
|
|
304
|
-
} else {
|
|
305
|
-
// Close the pre-opened popup if we didn't get a link
|
|
306
|
-
try { pre && !pre.closed && pre.close(); } catch(_) {}
|
|
307
|
-
}
|
|
242
|
+
if (!paymentResponse.paymentUrl) {
|
|
243
|
+
console.error('[PaywallController] No payment URL received');
|
|
244
|
+
alert('Payment URL not available');
|
|
308
245
|
return;
|
|
309
246
|
}
|
|
310
|
-
|
|
311
|
-
//
|
|
247
|
+
|
|
248
|
+
// Store payment data for verification
|
|
249
|
+
this.currentPaymentData = {
|
|
250
|
+
gateway: gatewayName,
|
|
251
|
+
orderId: paymentResponse.orderId,
|
|
252
|
+
sessionId: paymentResponse.sessionId
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// Open payment popup
|
|
256
|
+
const w = Math.min(window.screen.width - 100, this.config.popup?.width || 1000);
|
|
257
|
+
const h = Math.min(window.screen.height - 100, this.config.popup?.height || 800);
|
|
258
|
+
const left = Math.max(0, Math.round((window.screen.width - w) / 2));
|
|
259
|
+
const top = Math.max(0, Math.round((window.screen.height - h) / 2));
|
|
260
|
+
const features = `popup=1,width=${w},height=${h},left=${left},top=${top}`;
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
this.popup && !this.popup.closed && this.popup.close();
|
|
264
|
+
} catch (_) {}
|
|
265
|
+
|
|
266
|
+
this.popup = window.open(paymentResponse.paymentUrl, 'uvfCheckout', features);
|
|
267
|
+
this.startPolling();
|
|
268
|
+
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error('[PaywallController] Payment gateway error:', error);
|
|
271
|
+
alert('Payment system error. Please try again.');
|
|
312
272
|
}
|
|
313
273
|
}
|
|
314
274
|
|
|
@@ -330,49 +290,120 @@ export class PaywallController {
|
|
|
330
290
|
if (!d || d.type !== 'uvfCheckout') return;
|
|
331
291
|
try { if (this.popup && !this.popup.closed) this.popup.close(); } catch (_) {}
|
|
332
292
|
this.popup = null;
|
|
293
|
+
|
|
333
294
|
if (d.status === 'cancel') {
|
|
334
295
|
this.showGateways();
|
|
335
296
|
return;
|
|
336
297
|
}
|
|
298
|
+
|
|
337
299
|
if (d.status === 'success') {
|
|
338
300
|
try {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
301
|
+
// Get current user and video info
|
|
302
|
+
const userEmail = this.emailAuth?.getAuthenticatedUserId() || this.authenticatedUserId;
|
|
303
|
+
const videoSlug = (this.config as any)?.metadata?.slug || this.config?.videoSlug;
|
|
304
|
+
|
|
305
|
+
// Verify payment using the appropriate gateway
|
|
306
|
+
if (this.currentPaymentData && this.config && userEmail && videoSlug) {
|
|
307
|
+
const verificationRequest = {
|
|
308
|
+
orderId: this.currentPaymentData.orderId || d.orderId,
|
|
309
|
+
sessionId: this.currentPaymentData.sessionId || d.sessionId,
|
|
310
|
+
userId: userEmail, // Use email
|
|
311
|
+
videoId: videoSlug, // Use slug
|
|
312
|
+
customData: {
|
|
313
|
+
email: userEmail,
|
|
314
|
+
slug: videoSlug,
|
|
315
|
+
...d
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
console.log('[PaywallController] Verifying payment:', verificationRequest);
|
|
320
|
+
|
|
321
|
+
const verificationResult = await this.paymentManager.verifyPayment(
|
|
322
|
+
this.currentPaymentData.gateway,
|
|
323
|
+
verificationRequest
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
console.log('[PaywallController] Payment verification result:', verificationResult);
|
|
327
|
+
|
|
328
|
+
if (verificationResult.success && verificationResult.verified) {
|
|
329
|
+
console.log('[PaywallController] Payment verified successfully - unlocking video');
|
|
330
|
+
this.closeOverlay();
|
|
331
|
+
this.opts.onResume();
|
|
332
|
+
} else {
|
|
333
|
+
console.error('[PaywallController] Payment verification failed:', verificationResult.error);
|
|
334
|
+
alert('Payment verification failed. Please contact support.');
|
|
335
|
+
this.showGateways();
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
console.log('[PaywallController] Payment successful - assuming verification via return URL');
|
|
339
|
+
// For Cashfree, payment success via return URL usually means payment is complete
|
|
340
|
+
this.closeOverlay();
|
|
341
|
+
this.opts.onResume();
|
|
347
342
|
}
|
|
348
|
-
} catch (
|
|
349
|
-
|
|
350
|
-
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.error('[PaywallController] Payment verification error:', error);
|
|
345
|
+
console.log('[PaywallController] Proceeding with video unlock despite verification error');
|
|
346
|
+
this.closeOverlay();
|
|
347
|
+
this.opts.onResume(); // Allow playback anyway since payment was marked as successful
|
|
348
|
+
}
|
|
351
349
|
}
|
|
352
350
|
};
|
|
353
351
|
|
|
354
352
|
|
|
353
|
+
/**
|
|
354
|
+
* Initialize payment gateways based on configuration
|
|
355
|
+
*/
|
|
356
|
+
private initializePaymentGateways() {
|
|
357
|
+
if (!this.config?.apiBase) return;
|
|
358
|
+
|
|
359
|
+
console.log('[PaywallController] Initializing payment gateways');
|
|
360
|
+
|
|
361
|
+
// Clear existing gateways
|
|
362
|
+
this.paymentManager = new PaymentGatewayManager();
|
|
363
|
+
|
|
364
|
+
// Register Cashfree gateway with your specific API endpoint
|
|
365
|
+
const cashfreeGateway = new CashfreePaymentGateway({
|
|
366
|
+
name: 'cashfree',
|
|
367
|
+
displayName: 'Cashfree',
|
|
368
|
+
apiBase: this.config.apiBase,
|
|
369
|
+
endpoints: {
|
|
370
|
+
createPayment: '/Front-End/cashfree/ppv-payment'
|
|
371
|
+
},
|
|
372
|
+
headers: {
|
|
373
|
+
'Accept': 'application/json'
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
this.paymentManager.registerGateway(cashfreeGateway);
|
|
378
|
+
|
|
379
|
+
// Register Stripe gateway (if needed)
|
|
380
|
+
if (this.config.gateways?.includes('stripe')) {
|
|
381
|
+
const stripeGateway = new GenericPaymentGateway({
|
|
382
|
+
name: 'stripe',
|
|
383
|
+
displayName: 'Stripe',
|
|
384
|
+
apiBase: this.config.apiBase,
|
|
385
|
+
endpoints: {
|
|
386
|
+
createPayment: '/api/rentals/stripe/checkout-session',
|
|
387
|
+
verifyPayment: '/api/rentals/stripe/confirm'
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
this.paymentManager.registerGateway(stripeGateway);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
console.log('[PaywallController] Payment gateways initialized:', this.paymentManager.getGatewayNames());
|
|
395
|
+
}
|
|
396
|
+
|
|
355
397
|
/**
|
|
356
398
|
* Initialize EmailAuthController if email authentication is enabled
|
|
357
399
|
*/
|
|
358
400
|
private initializeEmailAuth() {
|
|
359
401
|
console.log('[PaywallController] initializeEmailAuth called');
|
|
360
402
|
console.log('[PaywallController] email auth config:', this.config?.emailAuth);
|
|
361
|
-
console.log('[PaywallController] config enabled:', this.config?.enabled);
|
|
362
403
|
|
|
363
|
-
// If paywall is disabled entirely, clean up everything
|
|
364
|
-
if (!this.config?.enabled) {
|
|
365
|
-
console.log('[PaywallController] Paywall completely disabled, cleaning up email auth');
|
|
366
|
-
if (this.emailAuth) {
|
|
367
|
-
this.emailAuth.destroy();
|
|
368
|
-
this.emailAuth = null;
|
|
369
|
-
}
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// If email auth specifically is disabled, clean up only email auth
|
|
374
404
|
if (!this.config?.emailAuth?.enabled) {
|
|
375
405
|
console.log('[PaywallController] Email auth disabled, cleaning up existing instance');
|
|
406
|
+
// Clean up existing EmailAuth if disabled
|
|
376
407
|
if (this.emailAuth) {
|
|
377
408
|
this.emailAuth.destroy();
|
|
378
409
|
this.emailAuth = null;
|
|
@@ -467,33 +498,11 @@ export class PaywallController {
|
|
|
467
498
|
* Open payment overlay directly (bypassing auth check)
|
|
468
499
|
*/
|
|
469
500
|
private openPaymentOverlay() {
|
|
470
|
-
console.log('[PaywallController] Opening payment overlay');
|
|
471
501
|
const root = this.ensureOverlay();
|
|
472
|
-
if (!root)
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
try {
|
|
478
|
-
root.style.display = 'flex';
|
|
479
|
-
root.classList.add('active');
|
|
480
|
-
|
|
481
|
-
// Force reflow then fade in with animation
|
|
482
|
-
void root.offsetWidth;
|
|
483
|
-
root.style.opacity = '1';
|
|
484
|
-
|
|
485
|
-
// Also animate the modal inside
|
|
486
|
-
const modal = root.querySelector('.uvf-paywall-modal') as HTMLElement;
|
|
487
|
-
if (modal) {
|
|
488
|
-
modal.style.transform = 'translateY(0)';
|
|
489
|
-
modal.style.opacity = '1';
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
this.opts.onShow?.();
|
|
493
|
-
console.log('[PaywallController] Payment overlay shown');
|
|
494
|
-
} catch (err) {
|
|
495
|
-
console.error('[PaywallController] Error showing overlay:', err);
|
|
496
|
-
}
|
|
502
|
+
if (!root) return;
|
|
503
|
+
root.style.display = 'flex';
|
|
504
|
+
root.classList.add('active');
|
|
505
|
+
this.opts.onShow?.();
|
|
497
506
|
}
|
|
498
507
|
|
|
499
508
|
/**
|
|
@@ -405,16 +405,7 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
405
405
|
useEffect(() => {
|
|
406
406
|
const p = playerRef.current as any;
|
|
407
407
|
if (p && typeof p.setPaywallConfig === 'function' && props.paywall) {
|
|
408
|
-
|
|
409
|
-
const paywall = props.paywall as any;
|
|
410
|
-
if (paywall.enabled && (paywall.apiBase || paywall.userId || paywall.videoId)) {
|
|
411
|
-
try {
|
|
412
|
-
console.log('[WebPlayerView] Updating paywall config:', paywall);
|
|
413
|
-
p.setPaywallConfig(paywall);
|
|
414
|
-
} catch(err) {
|
|
415
|
-
console.warn('[WebPlayerView] Failed to update paywall config:', err);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
408
|
+
try { p.setPaywallConfig(props.paywall as any); } catch(_) {}
|
|
418
409
|
}
|
|
419
410
|
}, [JSON.stringify(props.paywall)]);
|
|
420
411
|
|
package/packages/ios/README.md
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# UnifiedVideoPlayer (iOS SDK)
|
|
2
|
-
|
|
3
|
-
A unified iOS video player SDK with:
|
|
4
|
-
- HLS playback (AVPlayer)
|
|
5
|
-
- FairPlay DRM (SPC/CKC)
|
|
6
|
-
- Subtitles/audio track selection (AVMediaSelection)
|
|
7
|
-
- Picture-in-Picture (AVPictureInPictureController)
|
|
8
|
-
- AirPlay (AVRoutePickerView)
|
|
9
|
-
- Remote Command Center + Now Playing (lock screen controls)
|
|
10
|
-
- Background audio (AVAudioSession)
|
|
11
|
-
|
|
12
|
-
## Installation
|
|
13
|
-
|
|
14
|
-
### CocoaPods
|
|
15
|
-
1) Ensure this SDK is in a public Git repository. Update the podspec `s.source` to point at that repo and tag.
|
|
16
|
-
2) In your Podfile:
|
|
17
|
-
```ruby
|
|
18
|
-
platform :ios, '13.0'
|
|
19
|
-
use_frameworks!
|
|
20
|
-
|
|
21
|
-
target 'YourApp' do
|
|
22
|
-
pod 'UnifiedVideoPlayer', :git => 'https://github.com/yourcompany/unified-video-ios.git', :tag => '1.0.0'
|
|
23
|
-
end
|
|
24
|
-
```
|
|
25
|
-
3) Run:
|
|
26
|
-
```bash
|
|
27
|
-
pod install
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Swift Package Manager
|
|
31
|
-
Add the package at the repository URL or use Add Local Package pointing to `packages/ios`.
|
|
32
|
-
|
|
33
|
-
## Quick Start
|
|
34
|
-
```swift
|
|
35
|
-
import UnifiedVideoPlayer
|
|
36
|
-
|
|
37
|
-
let containerView = UIView(frame: .zero)
|
|
38
|
-
let player = UnifiedVideoPlayer()
|
|
39
|
-
let config = PlayerConfiguration()
|
|
40
|
-
config.autoPlay = true
|
|
41
|
-
|
|
42
|
-
player.initialize(container: containerView, configuration: config)
|
|
43
|
-
|
|
44
|
-
let source = MediaSource(url: "https://example.com/stream.m3u8")
|
|
45
|
-
source.metadata = ["title": "Demo Stream"]
|
|
46
|
-
player.onReady = { print("ready") }
|
|
47
|
-
player.onQualityChange = { br in print("bitrate: \(br)") }
|
|
48
|
-
|
|
49
|
-
player.load(source: source)
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### FairPlay DRM
|
|
53
|
-
```swift
|
|
54
|
-
let drm = DRMConfiguration(type: "fairplay", licenseUrl: "https://license.example.com/fps")
|
|
55
|
-
drm.certificateUrl = "https://license.example.com/cert"
|
|
56
|
-
drm.headers = ["X-Tenant-ID": "default"]
|
|
57
|
-
|
|
58
|
-
let source = MediaSource(url: "https://cdn.example.com/protected/playlist.m3u8")
|
|
59
|
-
source.drm = drm
|
|
60
|
-
player.load(source: source)
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Tracks
|
|
64
|
-
```swift
|
|
65
|
-
let audios = player.audioTracks() // [String]
|
|
66
|
-
let subs = player.subtitleTracks() // [String]
|
|
67
|
-
player.selectAudioTrack(index: 0)
|
|
68
|
-
player.selectSubtitleTrack(index: -1) // off
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### PiP & AirPlay
|
|
72
|
-
```swift
|
|
73
|
-
player.startPictureInPicture()
|
|
74
|
-
player.stopPictureInPicture()
|
|
75
|
-
let airPlay = player.makeAirPlayPickerView()
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Capabilities
|
|
79
|
-
- Enable Background Modes > Audio
|
|
80
|
-
- For DRM endpoints, configure ATS exceptions if necessary.
|
|
81
|
-
|
|
82
|
-
## License
|
|
83
|
-
MIT
|
|
84
|
-
|