1- import * as functions from ' firebase-functions' ;
2- import * as admin from ' firebase-admin' ;
3- import * as cors from ' cors' ;
1+ import * as functions from " firebase-functions" ;
2+ import * as admin from " firebase-admin" ;
3+ import * as cors from " cors" ;
44
5- const corsHandler = cors ( { origin : true } ) ;
5+ // Configuration CORS
6+ const corsHandler = cors ( {
7+ origin : true ,
8+ credentials : true ,
9+ } ) ;
610
7- // Interface pour la configuration des plugins
8- interface PluginConfig {
9- pluginType : 'wordpress' | 'shopify' ;
10- domain : string ;
11- apiKey ?: string ;
11+ // Types pour les requêtes
12+ interface WordPressConfigRequest {
1213 campaignId : string ;
13- userId : string ;
14- settings : {
15- autoInject ?: boolean ;
16- trackingEnabled ?: boolean ;
17- commissionRate ?: number ;
18- } ;
14+ domain : string ;
1915}
2016
21- // Interface pour l'installation Shopify
22- interface ShopifyInstall {
23- shop : string ;
24- code : string ;
25- state : string ;
17+ interface ShopifyInstallRequest {
2618 campaignId : string ;
19+ shopDomain : string ;
2720}
2821
2922// API pour la configuration WordPress
3023export const wordpressConfig = functions . https . onRequest ( ( request , response ) => {
3124 return corsHandler ( request , response , async ( ) => {
32- if ( request . method !== 'POST' ) {
33- return response . status ( 405 ) . json ( { error : 'Method not allowed' } ) ;
25+ if ( request . method !== "POST" ) {
26+ response . status ( 405 ) . json ( { error : "Method not allowed" } ) ;
27+ return ;
3428 }
3529
3630 try {
37- const config : PluginConfig = request . body ;
38-
39- // Validation
40- if ( ! config . domain || ! config . campaignId || ! config . userId ) {
41- return response . status ( 400 ) . json ( { error : 'Missing required fields' } ) ;
42- }
31+ const { campaignId, domain } : WordPressConfigRequest = request . body ;
4332
44- // Vérifier que la campagne existe et appartient à l'utilisateur
45- const campaignDoc = await admin . firestore ( )
46- . collection ( 'campaigns' )
47- . doc ( config . campaignId )
48- . get ( ) ;
33+ if ( ! campaignId || ! domain ) {
34+ response . status ( 400 ) . json ( { error : "Missing required fields" } ) ;
35+ return ;
36+ }
4937
50- if ( ! campaignDoc . exists || campaignDoc . data ( ) ?. userId !== config . userId ) {
51- return response . status ( 403 ) . json ( { error : 'Campaign not found or access denied' } ) ;
38+ // Valider le domaine
39+ const domainRegex = / ^ [ a - z A - Z 0 - 9 ] [ a - z A - Z 0 - 9 - ] { 1 , 61 } [ a - z A - Z 0 - 9 ] ? \. [ a - z A - Z ] { 2 , } $ / ;
40+ if ( ! domainRegex . test ( domain ) ) {
41+ response . status ( 400 ) . json ( { error : "Invalid domain format" } ) ;
42+ return ;
5243 }
5344
54- // Stocker la configuration du plugin
55- const pluginDoc = await admin . firestore ( )
56- . collection ( 'plugin_configs' )
57- . add ( {
58- ...config ,
59- pluginType : 'wordpress' ,
60- createdAt : new Date ( ) ,
61- updatedAt : new Date ( ) ,
62- active : true
63- } ) ;
45+ // Générer le script de tracking WordPress
46+ const trackingScript = generateWordPressTrackingScript ( campaignId , domain ) ;
47+
48+ // Sauvegarder la configuration
49+ const db = admin . firestore ( ) ;
50+ const configData = {
51+ campaignId,
52+ domain,
53+ platform : "wordpress" ,
54+ trackingScript,
55+ installedAt : admin . firestore . FieldValue . serverTimestamp ( ) ,
56+ status : "active"
57+ } ;
6458
65- // Générer le script de tracking personnalisé
66- const trackingScript = generateWordPressTrackingScript ( config . campaignId , config . domain ) ;
59+ await db . collection ( "plugin_configurations" ) . doc ( campaignId ) . set ( configData ) ;
60+
61+ console . log ( "WordPress config generated:" , { campaignId, domain } ) ;
6762
6863 response . json ( {
6964 success : true ,
70- pluginId : pluginDoc . id ,
7165 trackingScript,
72- message : 'WordPress plugin configured successfully'
66+ instructions : [
67+ "1. Copiez le code PHP ci-dessous" ,
68+ "2. Ajoutez-le dans le fichier functions.php de votre thème WordPress" ,
69+ "3. Ou utilisez un plugin comme 'Code Snippets' pour l'ajouter" ,
70+ "4. Le tracking sera automatiquement activé sur toutes les pages"
71+ ]
7372 } ) ;
7473
7574 } catch ( error ) {
76- console . error ( ' WordPress config error:' , error ) ;
77- response . status ( 500 ) . json ( { error : ' Internal server error' } ) ;
75+ console . error ( " WordPress config error:" , error ) ;
76+ response . status ( 500 ) . json ( { error : " Internal server error" } ) ;
7877 }
7978 } ) ;
8079} ) ;
8180
8281// API pour l'installation Shopify (redirige vers le processus OAuth)
8382export const shopifyInstall = functions . https . onRequest ( ( request , response ) => {
8483 return corsHandler ( request , response , async ( ) => {
85- if ( request . method !== 'POST' ) {
86- return response . status ( 405 ) . json ( { error : 'Method not allowed' } ) ;
84+ if ( request . method !== "POST" ) {
85+ response . status ( 405 ) . json ( { error : "Method not allowed" } ) ;
86+ return ;
8787 }
8888
8989 try {
90- const installData : ShopifyInstall = request . body ;
91-
92- // Validation
93- if ( ! installData . shop || ! installData . campaignId ) {
94- return response . status ( 400 ) . json ( { error : 'Missing required fields' } ) ;
90+ const { campaignId , shopDomain } : ShopifyInstallRequest = request . body ;
91+
92+ if ( ! campaignId || ! shopDomain ) {
93+ response . status ( 400 ) . json ( { error : "Missing required fields" } ) ;
94+ return ;
9595 }
9696
97- // Génération de l'état OAuth sécurisé
98- const state = Buffer . from ( `${ installData . campaignId } :${ Date . now ( ) } ` ) . toString ( 'base64' ) ;
99-
100- // Rediriger vers le processus OAuth réel
97+ // Valider le domaine Shopify
98+ const shopName = shopDomain . replace ( ".myshopify.com" , "" ) ;
99+ if ( ! / ^ [ a - z A - Z 0 - 9 - ] + $ / . test ( shopName ) ) {
100+ response . status ( 400 ) . json ( { error : "Invalid Shopify domain" } ) ;
101+ return ;
102+ }
103+
104+ // Générer un state unique pour la sécurité OAuth
105+ const state = admin . firestore ( ) . collection ( "temp" ) . doc ( ) . id ;
106+
107+ // Sauvegarder temporairement les données de la requête
108+ const db = admin . firestore ( ) ;
109+ await db . collection ( "shopify_oauth_states" ) . doc ( state ) . set ( {
110+ campaignId,
111+ shopDomain,
112+ createdAt : admin . firestore . FieldValue . serverTimestamp ( ) ,
113+ expiresAt : new Date ( Date . now ( ) + 10 * 60 * 1000 ) // 10 minutes
114+ } ) ;
115+
116+ // Retourner les informations pour rediriger vers OAuth
101117 response . json ( {
102118 success : true ,
103- requiresOAuth : true ,
104- state,
105- message : 'Please complete OAuth authorization' ,
106- nextStep : 'oauth'
119+ redirectData : {
120+ shop : shopName ,
121+ campaignId,
122+ state
123+ } ,
124+ instructions : [
125+ "Utilisez ces données pour initier le processus OAuth Shopify" ,
126+ "Appelez l'endpoint shopifyAuthUrl avec ces paramètres"
127+ ]
107128 } ) ;
108129
109130 } catch ( error ) {
110- console . error ( ' Shopify install error:' , error ) ;
111- response . status ( 500 ) . json ( { error : ' Internal server error' } ) ;
131+ console . error ( " Shopify install error:" , error ) ;
132+ response . status ( 500 ) . json ( { error : " Internal server error" } ) ;
112133 }
113134 } ) ;
114135} ) ;
115136
116- // API pour générer les clés API des plugins
117- export const generatePluginApiKey = functions . https . onCall ( async ( data , context ) => {
118- if ( ! context . auth ) {
119- throw new functions . https . HttpsError ( 'unauthenticated' , 'User must be authenticated' ) ;
120- }
121-
122- const { campaignId } = data ;
123-
124- if ( ! campaignId ) {
125- throw new functions . https . HttpsError ( 'invalid-argument' , 'Campaign ID is required' ) ;
126- }
127-
128- // Vérifier que la campagne appartient à l'utilisateur
129- const campaignDoc = await admin . firestore ( )
130- . collection ( 'campaigns' )
131- . doc ( campaignId )
132- . get ( ) ;
133-
134- if ( ! campaignDoc . exists || campaignDoc . data ( ) ?. userId !== context . auth . uid ) {
135- throw new functions . https . HttpsError ( 'permission-denied' , 'Campaign not found or access denied' ) ;
136- }
137-
138- // Générer une clé API unique
139- const apiKey = 'rsp_' + Buffer . from ( campaignId + '_' + Date . now ( ) ) . toString ( 'base64' ) ;
140-
141- // Stocker la clé API
142- await admin . firestore ( )
143- . collection ( 'plugin_api_keys' )
144- . doc ( apiKey )
145- . set ( {
146- campaignId,
147- userId : context . auth . uid ,
148- createdAt : new Date ( ) ,
149- lastUsed : null ,
150- active : true
151- } ) ;
152-
153- return { apiKey } ;
137+ // API pour obtenir le statut d'une installation
138+ export const getInstallationStatus = functions . https . onRequest ( ( request , response ) => {
139+ return corsHandler ( request , response , async ( ) => {
140+ if ( request . method !== "GET" ) {
141+ response . status ( 405 ) . json ( { error : "Method not allowed" } ) ;
142+ return ;
143+ }
144+
145+ try {
146+ const campaignId = request . query . campaignId as string ;
147+
148+ if ( ! campaignId ) {
149+ response . status ( 400 ) . json ( { error : "Campaign ID required" } ) ;
150+ return ;
151+ }
152+
153+ const db = admin . firestore ( ) ;
154+
155+ // Vérifier les installations WordPress
156+ const wordpressDoc = await db . collection ( "plugin_configurations" ) . doc ( campaignId ) . get ( ) ;
157+
158+ // Vérifier les installations Shopify
159+ const shopifyDoc = await db . collection ( "shopify_installations" ) . doc ( campaignId ) . get ( ) ;
160+
161+ const installations = {
162+ wordpress : wordpressDoc . exists ? wordpressDoc . data ( ) : null ,
163+ shopify : shopifyDoc . exists ? shopifyDoc . data ( ) : null
164+ } ;
165+
166+ response . json ( {
167+ success : true ,
168+ installations,
169+ hasActiveInstallations : wordpressDoc . exists || shopifyDoc . exists
170+ } ) ;
171+
172+ } catch ( error ) {
173+ console . error ( "Get installation status error:" , error ) ;
174+ response . status ( 500 ) . json ( { error : "Internal server error" } ) ;
175+ }
176+ } ) ;
154177} ) ;
155178
156179// Fonction utilitaire pour générer le script WordPress
@@ -159,23 +182,82 @@ function generateWordPressTrackingScript(campaignId: string, _domain: string): s
159182// RefSpring Tracking Script for WordPress
160183function refspring_add_tracking_script() {
161184 $campaign_id = '${ campaignId } ';
162- $script_url = 'https://refspring.com/tracking.js';
163-
164- echo '<script data-campaign="' . $campaign_id . '" src="' . $script_url . '"></script>';
185+ ?>
186+ <script>
187+ (function() {
188+ // RefSpring Tracking Code
189+ window.refspring = window.refspring || {};
190+ window.refspring.campaignId = '<?php echo esc_js($campaign_id); ?>';
191+
192+ // Track page views
193+ function trackPageView() {
194+ const data = {
195+ campaignId: window.refspring.campaignId,
196+ page: window.location.pathname,
197+ referrer: document.referrer,
198+ timestamp: new Date().toISOString(),
199+ userAgent: navigator.userAgent
200+ };
201+
202+ // Send tracking data
203+ fetch('https://your-functions-url/trackPageView', {
204+ method: 'POST',
205+ headers: {
206+ 'Content-Type': 'application/json',
207+ },
208+ body: JSON.stringify(data)
209+ }).catch(console.error);
210+ }
211+
212+ // Track conversions
213+ function trackConversion(value, currency = 'USD') {
214+ const data = {
215+ campaignId: window.refspring.campaignId,
216+ value: value,
217+ currency: currency,
218+ page: window.location.pathname,
219+ timestamp: new Date().toISOString()
220+ };
221+
222+ fetch('https://your-functions-url/trackConversion', {
223+ method: 'POST',
224+ headers: {
225+ 'Content-Type': 'application/json',
226+ },
227+ body: JSON.stringify(data)
228+ }).catch(console.error);
229+ }
230+
231+ // Initialize tracking
232+ if (document.readyState === 'loading') {
233+ document.addEventListener('DOMContentLoaded', trackPageView);
234+ } else {
235+ trackPageView();
236+ }
237+
238+ // Make trackConversion available globally
239+ window.refspring.trackConversion = trackConversion;
240+ })();
241+ </script>
242+ <?php
165243}
166- add_action('wp_head', 'refspring_add_tracking_script');
167-
168- // Hook pour WooCommerce conversions
169- function refspring_woocommerce_conversion($order_id) {
170- $order = wc_get_order($order_id);
171- $total = $order->get_total();
172-
173- echo '<script>
174- if (window.RefSpring) {
175- window.RefSpring.trackConversion(' . $total . ');
244+ add_action('wp_footer', 'refspring_add_tracking_script');
245+
246+ // Hook into WooCommerce order completion (if WooCommerce is active)
247+ if (class_exists('WooCommerce')) {
248+ function refspring_track_woocommerce_conversion($order_id) {
249+ $order = wc_get_order($order_id);
250+ $total = $order->get_total();
251+ $currency = $order->get_currency();
252+ ?>
253+ <script>
254+ if (window.refspring && window.refspring.trackConversion) {
255+ window.refspring.trackConversion(<?php echo esc_js($total); ?>, '<?php echo esc_js($currency); ?>');
176256 }
177- </script>';
257+ </script>
258+ <?php
259+ }
260+ add_action('woocommerce_thankyou', 'refspring_track_woocommerce_conversion');
178261}
179- add_action('woocommerce_thankyou', 'refspring_woocommerce_conversion');
180262?>` ;
181263}
0 commit comments