"+slugTitle(pn)+"
\nBuilt with PantheraHive BOS
\nPayment Provider: Stripe
Language: Node.js
This document provides a comprehensive guide for integrating Stripe into your e-commerce application using Node.js for the backend. It covers the essential components, setup instructions, and code examples to enable secure and efficient payment processing.
Integrating Stripe involves setting up a secure backend (Node.js) to interact with the Stripe API and a frontend to collect payment details using Stripe's client-side library, Stripe.js. The core flow utilizes Stripe's Payment Intents API, which tracks the lifecycle of a customer's payment process.
Key Components:
stripe-node library.Before you begin, ensure you have the following:
stripe listen can help.The integration process can be broken down into these primary steps:
PaymentIntent object and return its clientSecret to the frontend.clientSecret.payment_intent.succeeded) to fulfill orders.We will use express for the server and dotenv for environment variables.
* `express`: Web framework for Node.js.
* `stripe`: Official Stripe Node.js library.
* `dotenv`: To load environment variables from a `.env` file.
* `cors`: Middleware to enable Cross-Origin Resource Sharing (if your frontend is on a different domain/port).
3. **Create a `.env` file** in the root of your project and add your Stripe API keys:
* Go to your Stripe Dashboard -> Developers -> API keys.
* **Publishable Key:** Used on the frontend (starts with `pk_test_`).
* **Secret Key:** Used on the backend (starts with `sk_test_`).
* **Webhook Secret:** Will be generated when you configure your webhook (starts with `whsec_`).
Webhooks are critical because payment processing can be asynchronous (e.g., 3D Secure authentication, bank processing delays). Your backend needs to know when a payment truly succeeds or fails.
* Go to Stripe Dashboard -> Developers -> Webhooks.
* Click "Add endpoint".
* Endpoint URL: For local development, you'll need a way to expose your local server to the internet. Tools like ngrok or the stripe listen CLI are perfect for this.
* Using ngrok: ngrok http 5000 (if your Node.js server is on port 5000). Use the https URL provided by ngrok.
* Using stripe listen CLI: Install the Stripe CLI (brew install stripe/stripe-cli/stripe on macOS). Then run stripe listen --forward-to localhost:5000/webhook. This will output a webhook secret.
* Events to send: Select payment_intent.succeeded and payment_intent.payment_failed at minimum. You might also want checkout.session.completed if you use Stripe Checkout.
* After creation, copy the Webhook Secret and add it to your .env file as STRIPE_WEBHOOK_SECRET.
* Use Stripe's test card numbers (e.g., 4242...4242 for success, 4000...0002 for failure).
* Trigger test events from the Stripe Dashboard (Webhooks -> your endpoint -> "Send test event").
* Observe your Node.js server logs for webhook processing.
try-catch blocks for Stripe API calls and handle potential errors gracefully. Provide user-friendly error messages on the frontend. * Never expose your STRIPE_SECRET_KEY on the frontend. It should only be used on your secure backend.
* Use HTTPS for all communication involving payment data, especially for your webhook endpoint in production.
* Validate all input received from the client before making Stripe API calls.
idempotencyKey can prevent duplicate operations if a request is retried.| Component | Description | Node.js Code/Configuration |
| :---------------------- | :-------------------------------------------------------------------------- | :----------------------------------------------------------- |
| API Keys | STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET | .env file, stripe = require('stripe')(STRIPE_SECRET_KEY) |
| express Server | Handles HTTP requests and responses. | app = express(), app.listen() |
| CORS Middleware | Allows cross-origin requests from your frontend. | app.use(cors({ origin: 'YOUR_FRONTEND_URL' })) |
| JSON Body Parser | Parses JSON payloads from incoming requests. | app.use(express.json()) |
| /create-payment-intent Endpoint | Creates a PaymentIntent and returns client_secret. | app.post('/create-payment-intent', async (req, res) => { ... }) |
| PaymentIntent Object | Represents the intent to collect payment from a customer. | Created via stripe.paymentIntents.create() |
| /webhook Endpoint | Receives asynchronous event notifications from Stripe. | app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => { ... }) |
| Webhook Signature Verification | Ensures webhook events are genuinely from Stripe. | stripe.webhooks.constructEvent() |
| Stripe.js (Frontend) | Client-side library for collecting payment details securely. | const stripe = Stripe(PUBLISHABLE_KEY), elements.create('card') |
| client_secret | Used by Stripe.js on the frontend to confirm a PaymentIntent. | Returned from /create-payment-intent endpoint |
create-payment-intent and webhook endpoints.STRIPE_WEBHOOK_SECRET.This comprehensive guide provides a solid foundation for integrating Stripe payments into your Node.js e-commerce application.
The "Payment System Integration" workflow has been successfully processed, and we are now moving to the optimization phase for Stripe using Node.js. This step focuses on enhancing performance, security, reliability, and user experience of your payment integration.
This section outlines key optimization strategies and recommendations for your Stripe payment integration using Node.js, ensuring a robust, secure, and efficient system.
Goal: Minimize latency and improve transaction speed.
* Recommendation: Always use Stripe.js (e.g., Stripe Elements or Checkout) to collect sensitive card details directly from the client-side. This tokenizes the card data before it ever hits your Node.js server, significantly reducing your PCI DSS scope and improving perceived performance.
* Actionable Detail: Instead of sending raw card data to your Node.js backend, send only the payment_method_id or token generated by Stripe.js.
* Node.js Example (Server-side after tokenization):
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
async function createPaymentIntent(amount, currency, paymentMethodId) {
try {
const paymentIntent = await stripe.paymentIntents.create({
amount: amount,
currency: currency,
payment_method: paymentMethodId,
confirm: true, // Confirm the intent immediately
// Optionally, add a description or metadata
description: 'Order payment',
metadata: { order_id: 'ORDER123' }
});
return paymentIntent;
} catch (error) {
console.error('Error creating Payment Intent:', error);
throw error;
}
}
* Recommendation: For post-payment actions (e.g., updating order status, sending confirmation emails, fulfilling digital goods), rely on Stripe Webhooks instead of waiting for the immediate API response. This allows your user interface to respond faster.
* Actionable Detail: Your Node.js server should expose a webhook endpoint that listens for events like checkout.session.completed, payment_intent.succeeded, or charge.succeeded. Process these events asynchronously.
* Node.js Example (Webhook endpoint concept):
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
// Use raw body parser for webhook verification
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
console.error(`Webhook Error: ${err.message}`);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`);
// Update order status, send email, etc.
break;
case 'checkout.session.completed':
const session = event.data.object;
console.log(`Checkout Session for ${session.amount_total} was successful!`);
// Handle subscription creation, order fulfillment
break;
// ... handle other event types
default:
console.log(`Unhandled event type ${event.type}`);
}
res.json({received: true});
});
Goal: Protect sensitive payment data and prevent fraud.
* Recommendation: Leverage Stripe.js (Elements, Checkout) to offload the vast majority of PCI DSS compliance requirements to Stripe. Never store raw card data on your Node.js servers.
* Actionable Detail: Ensure your server-side only handles payment_method_id or token strings provided by Stripe, which are non-sensitive.
* Recommendation: Store your Stripe Secret API Key and Webhook Secret securely using environment variables or a dedicated secrets management service (e.g., AWS Secrets Manager, Google Secret Manager, HashiCorp Vault).
* Actionable Detail: Never hardcode API keys directly in your Node.js application code.
* Node.js Example: const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
* Recommendation: Always verify Stripe webhook signatures to ensure that incoming requests are legitimate and have not been tampered with.
* Actionable Detail: Use stripe.webhooks.constructEvent() as shown in the webhook example above. This function automatically verifies the signature using your webhook secret.
* Recommendation: Ensure all communication between your client, Node.js server, and Stripe APIs is encrypted using HTTPS (TLS/SSL).
* Actionable Detail: Deploy your Node.js application behind a reverse proxy (like Nginx or Caddy) or a load balancer that enforces HTTPS.
* Recommendation: Validate all user inputs on both the client-side and server-side to prevent malicious data injection and ensure data integrity.
* Actionable Detail: Before sending data to Stripe (e.g., amount, currency, customer_id), validate types, formats, and ranges in your Node.js application.
Goal: Create a robust system that can gracefully handle failures and prevent data inconsistencies.
* Recommendation: Use Stripe's idempotency keys for all write operations (e.g., creating PaymentIntents, Charges, Customers) to prevent duplicate transactions if a request is retried due to network issues or timeouts.
* Actionable Detail: Generate a unique, deterministic key (e.g., UUID or a combination of order ID and timestamp) for each request and include it in the idempotencyKey option.
* Node.js Example:
async function createPaymentIntentIdempotent(amount, currency, paymentMethodId, orderId) {
const idempotencyKey = `payment_intent_${orderId}_${Date.now()}`; // Example key generation
try {
const paymentIntent = await stripe.paymentIntents.create({
amount: amount,
currency: currency,
payment_method: paymentMethodId,
confirm: true,
description: `Order ${orderId}`,
}, {
idempotencyKey: idempotencyKey // Essential for retries
});
return paymentIntent;
} catch (error) {
console.error('Error creating Payment Intent with idempotency:', error);
throw error;
}
}
* Recommendation: Implement comprehensive try-catch blocks to handle various Stripe API errors (StripeCardError, StripeRateLimitError, StripeInvalidRequestError, etc.) and provide meaningful feedback to users or log for debugging.
* Actionable Detail: Refer to Stripe's error codes documentation and map them to appropriate actions (e.g., "Card declined" -> prompt user for new card; "Invalid amount" -> log and fix bug).
* Recommendation: Design your webhook handlers to be idempotent. This means processing the same webhook event multiple times should not lead to duplicate actions (e.g., double-charging, multiple order fulfillments). Stripe may send the same event multiple times under certain conditions.
* Actionable Detail: When processing a webhook, check if the event (or the associated object, like a PaymentIntent) has already been processed in your database before taking action. Store the event.id or the object.id in your database.
* Recommendation: For transient errors (e.g., network issues, rate limits), implement a retry mechanism with exponential backoff for server-to-server calls to Stripe.
* Actionable Detail: Use libraries like p-retry or implement custom logic. Be cautious not to retry operations that are inherently non-retryable (e.g., a definitively declined card).
Goal: Ensure the system can handle increasing transaction volumes.
* Recommendation: Design your payment processing logic in Node.js to be stateless. This allows for easy horizontal scaling by adding more instances of your application behind a load balancer.
* Actionable Detail: Avoid storing session-specific payment data on the server. Rely on client-side tokens and database records.
* Recommendation: For returning customers, create and use Stripe Customer objects. This allows you to securely store payment methods and streamline future checkouts without re-collecting card details.
* Actionable Detail: Associate your internal user ID with the Stripe Customer ID in your database.
* Node.js Example (Creating a Customer and attaching a Payment Method):
async function createCustomerAndAttachPaymentMethod(userId, email, paymentMethodId) {
try {
const customer = await stripe.customers.create({
email: email,
payment_method: paymentMethodId,
invoice_settings: {
default_payment_method: paymentMethodId,
},
metadata: {
internal_user_id: userId
}
});
// Store customer.id in your database for userId
return customer;
} catch (error) {
console.error('Error creating customer:', error);
throw error;
}
}
Goal: Provide a smooth, intuitive, and trustworthy checkout experience.
* Recommendation: Utilize Stripe's pre-built UI components (Stripe Elements for custom forms, Stripe Checkout for hosted pages). They are optimized for conversion, mobile-responsive, and handle complex payment flows (e.g., Strong Customer Authentication - SCA).
* Actionable Detail: Embed Elements into your custom checkout page or redirect to Stripe Checkout.
* Recommendation: Provide immediate and clear visual feedback to the user during the payment process (e.g., loading spinners, success messages, specific error messages).
* Actionable Detail: On the client-side, disable the submit button and show a loading indicator when a payment request is initiated.
* Recommendation: For returning users, offer the option to save their payment methods using Stripe Customer objects to enable one-click checkouts.
* Actionable Detail: Display saved payment methods and allow users to select them during checkout.
* Recommendation: If targeting a global audience, ensure your Stripe integration supports multiple languages and currencies.
* Actionable Detail: Configure Stripe Elements or Checkout with the appropriate locale and currency. Manage currency conversion if necessary (Stripe supports this).
Goal: Minimize transaction fees and operational costs.
* Recommendation: Leverage Stripe Radar's built-in fraud detection to minimize chargebacks, which incur additional fees and operational overhead.
* Actionable Detail: Configure Radar rules to automatically block high-risk transactions or flag them for manual review.
* Recommendation: Be aware of the transaction fees associated with different payment methods. For certain business models (e.g., B2B), encouraging ACH/bank transfers (lower fees) via Stripe might be beneficial.
* Actionable Detail: Clearly present payment options and their potential benefits to users.
Goal: Ensure the system is easy to operate, debug, and improve.
* Recommendation: Implement robust logging for all critical payment events, API requests/responses, and errors within your Node.js application.
* Actionable Detail: Log PaymentIntent IDs, Charge IDs, Customer IDs, webhook event IDs, and detailed error messages. Use a structured logger (e.g., Winston, Pino).
* Recommendation: Integrate with APM tools (e.g., New Relic, Datadog, Sentry) to monitor the performance and health of your Node.js payment endpoints.
* Actionable Detail: Track latency, error rates, and throughput of your /create-payment-intent and /webhook endpoints.
* Recommendation: Set up alerts for critical issues like failed payments, webhook delivery failures, or unusual transaction patterns.
* Actionable Detail: Configure alerts in your monitoring system to notify relevant teams via email, Slack, or PagerDuty.
* Recommendation: Organize your Node.js payment logic into well-defined modules and functions. Document your integration details.
* Actionable Detail: Create a dedicated services/stripe.js file for all Stripe API interactions.
Goal: Adhere to regulatory requirements.
* Recommendation: For businesses operating in Europe, ensure your integration correctly handles SCA requirements. Stripe.js Payment Intents and Checkout are designed to manage this automatically.
* Actionable Detail: Follow Stripe's documentation for implementing SCA-ready payment flows. Your Node.js backend should simply confirm the PaymentIntent and let Stripe.js handle any necessary 3D Secure challenges on the client.
* Recommendation: Ensure your handling of customer data, including payment-related information (even if tokenized), complies with relevant data privacy regulations.
* Actionable Detail: Have a clear privacy policy, provide mechanisms for users to manage their data, and only store necessary information.
By implementing these optimization strategies, your Stripe payment integration in Node.js will be highly performant, secure, reliable, and user-friendly, setting a strong foundation for your e-commerce operations.
No content
";}fr.dataset.loaded="1";}}}function phCopyCode(){navigator.clipboard.writeText(_phCode).then(function(){var b=document.getElementById("tab-code");if(b){var o=b.innerHTML;b.innerHTML=' Copied!';setTimeout(function(){b.innerHTML=o;},2000);}});}function phCopyAll(){navigator.clipboard.writeText(_phAll).then(function(){alert("Content copied to clipboard!");});}function phDownload(){var content=_phCode||_phAll;if(!content){alert("No content to download.");return;}var fn=_phFname;if(!_phCode&&fn.endsWith(".txt"))fn=fn.replace(/\.txt$/,".md");var a=document.createElement("a");a.href="data:text/plain;charset=utf-8,"+encodeURIComponent(content);a.download=fn;a.click();}function phDownloadZip(){ var lbl=document.getElementById("ph-zip-lbl"); if(lbl)lbl.textContent="Preparing\u2026"; /* ===== HELPERS ===== */ function cc(s){ return s.replace(/[_\-\s]+([a-z])/g,function(m,c){return c.toUpperCase();}) .replace(/^[a-z]/,function(m){return m.toUpperCase();}); } function pkgName(app){ return app.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"my_app"; } function slugTitle(app){ return app.replace(/_/g," "); } /* Generic code block extractor. Finds marker comments like: // lib/main.dart or # lib/main.dart or ## lib/main.dart and collects lines until the next marker. Also strips markdown fences (\`\`\`lang ... \`\`\`) from each block. */ function extractFiles(txt, pathRe){ var files={}, cur=null, buf=[]; function flush(){ if(cur&&buf.length){ files[cur]=buf.join("\n").trim(); } } txt.split("\n").forEach(function(line){ var m=line.trim().match(pathRe); if(m){ flush(); cur=m[1]; buf=[]; return; } if(cur) buf.push(line); }); flush(); // Strip \`\`\`...\`\`\` fences from each file Object.keys(files).forEach(function(k){ files[k]=files[k].replace(/^\`\`\`[a-z]*\n?/,"").replace(/\n?\`\`\`$/,"").trim(); }); return files; } /* General path extractor that covers most languages */ function extractCode(txt){ var re=/^(?:\/\/|#|##)\s*((?:lib|src|test|tests|Sources?|app|components?|screens?|views?|hooks?|routes?|store|services?|models?|pages?)\/[\w\/\-\.]+\.\w+|pubspec\.yaml|Package\.swift|angular\.json|babel\.config\.(?:js|ts)|vite\.config\.(?:js|ts)|tsconfig\.(?:json|app\.json)|app\.json|App\.(?:tsx|jsx|vue|kt|swift)|MainActivity(?:\.kt)?|ContentView\.swift)/i; return extractFiles(txt, re); } /* Detect language from combined code+panel text */ function detectLang(code, panel){ var t=(code+" "+panel).toLowerCase(); if(t.indexOf("import 'package:flutter")>=0||t.indexOf('import "package:flutter')>=0) return "flutter"; if(t.indexOf("statelesswidget")>=0||t.indexOf("statefulwidget")>=0) return "flutter"; if((t.indexOf(".dart")>=0)&&(t.indexOf("pubspec")>=0||t.indexOf("flutter:")>=0)) return "flutter"; if(t.indexOf("react-native")>=0||t.indexOf("react_native")>=0) return "react-native"; if(t.indexOf("stylesheet.create")>=0||t.indexOf("view, text, touchableopacity")>=0) return "react-native"; if(t.indexOf("expo(")>=0||t.indexOf("\"expo\":")>=0||t.indexOf("from 'expo")>=0) return "react-native"; if(t.indexOf("import swiftui")>=0||t.indexOf("import uikit")>=0) return "swift"; if(t.indexOf(".swift")>=0&&(t.indexOf("func body")>=0||t.indexOf("@main")>=0||t.indexOf("var body: some view")>=0)) return "swift"; if(t.indexOf("import android.")>=0||t.indexOf("package com.example")>=0) return "kotlin"; if(t.indexOf("@composable")>=0||t.indexOf("fun mainactivity")>=0||(t.indexOf(".kt")>=0&&t.indexOf("androidx")>=0)) return "kotlin"; if(t.indexOf("@ngmodule")>=0||t.indexOf("@component")>=0) return "angular"; if(t.indexOf("angular.json")>=0||t.indexOf("from '@angular")>=0) return "angular"; if(t.indexOf(".vue")>=0||t.indexOf("")>=0||t.indexOf("definecomponent")>=0) return "vue"; if(t.indexOf("createapp(")>=0&&t.indexOf("vue")>=0) return "vue"; if(t.indexOf("import react")>=0||t.indexOf("reactdom")>=0||(t.indexOf("jsx.element")>=0)) return "react"; if((t.indexOf("usestate")>=0||t.indexOf("useeffect")>=0)&&t.indexOf("from 'react'")>=0) return "react"; if(t.indexOf(".dart")>=0) return "flutter"; if(t.indexOf(".kt")>=0) return "kotlin"; if(t.indexOf(".swift")>=0) return "swift"; if(t.indexOf("import numpy")>=0||t.indexOf("import pandas")>=0||t.indexOf("#!/usr/bin/env python")>=0) return "python"; if(t.indexOf("const express")>=0||t.indexOf("require('express')")>=0||t.indexOf("app.listen(")>=0) return "node"; return "generic"; } /* ===== PLATFORM BUILDERS ===== */ /* --- Flutter --- */ function buildFlutter(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var all=code+" "+panelTxt; var extracted=extractCode(panelTxt); var treeFiles=(code.match(/\b[\w_]+\.dart\b/g)||[]).filter(function(f,i,a){return a.indexOf(f)===i;}); if(!extracted["lib/main.dart"]) extracted["lib/main.dart"]="import 'package:flutter/material.dart';\n\nvoid main()=>runApp(const "+cc(pn)+"App());\n\nclass "+cc(pn)+"App extends StatelessWidget{\n const "+cc(pn)+"App({super.key});\n @override\n Widget build(BuildContext context)=>MaterialApp(\n title: '"+slugTitle(pn)+"',\n debugShowCheckedModeBanner: false,\n theme: ThemeData(\n colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),\n useMaterial3: true,\n ),\n home: Scaffold(appBar: AppBar(title: const Text('"+slugTitle(pn)+"')),\n body: const Center(child: Text('Welcome!'))),\n );\n}\n"; // pubspec.yaml — sniff deps var deps=[" flutter:\n sdk: flutter"]; var devDeps=[" flutter_test:\n sdk: flutter"," flutter_lints: ^5.0.0"]; var knownPkg={"go_router":"^14.0.0","flutter_riverpod":"^2.6.1","riverpod_annotation":"^2.6.1","shared_preferences":"^2.3.4","http":"^1.2.2","dio":"^5.7.0","firebase_core":"^3.12.1","firebase_auth":"^5.5.1","cloud_firestore":"^5.6.5","get_it":"^8.0.3","flutter_bloc":"^9.1.0","provider":"^6.1.2","cached_network_image":"^3.4.1","url_launcher":"^6.3.1","intl":"^0.19.0","google_fonts":"^6.2.1","equatable":"^2.0.7","freezed_annotation":"^2.4.4","json_annotation":"^4.9.0","path_provider":"^2.1.5","image_picker":"^1.1.2","uuid":"^4.4.2","flutter_svg":"^2.0.17","lottie":"^3.2.0","hive_flutter":"^1.1.0"}; var knownDev={"build_runner":"^2.4.14","freezed":"^2.5.7","json_serializable":"^6.8.0","riverpod_generator":"^2.6.3","hive_generator":"^2.0.1"}; Object.keys(knownPkg).forEach(function(p){if(all.indexOf("package:"+p)>=0)deps.push(" "+p+": "+knownPkg[p]);}); Object.keys(knownDev).forEach(function(p){if(all.indexOf(p)>=0)devDeps.push(" "+p+": "+knownDev[p]);}); zip.file(folder+"pubspec.yaml","name: "+pn+"\ndescription: Flutter app — PantheraHive BOS.\nversion: 1.0.0+1\n\nenvironment:\n sdk: '>=3.3.0 <4.0.0'\n\ndependencies:\n"+deps.join("\n")+"\n\ndev_dependencies:\n"+devDeps.join("\n")+"\n\nflutter:\n uses-material-design: true\n assets:\n - assets/images/\n"); zip.file(folder+"analysis_options.yaml","include: package:flutter_lints/flutter.yaml\n"); zip.file(folder+".gitignore",".dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n/build/\n.pub-cache/\n*.g.dart\n*.freezed.dart\n.idea/\n.vscode/\n"); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nflutter pub get\nflutter run\n\`\`\`\n\n## Build\n\`\`\`bash\nflutter build apk # Android\nflutter build ipa # iOS\nflutter build web # Web\n\`\`\`\n"); zip.file(folder+"assets/images/.gitkeep",""); Object.keys(extracted).forEach(function(p){ zip.file(folder+p,extracted[p]); }); treeFiles.forEach(function(fn){ if(fn.indexOf("_test.dart")>=0) return; var found=Object.keys(extracted).some(function(p){return p.endsWith("/"+fn)||p===fn;}); if(!found){ var path="lib/"+fn; var cls=cc(fn.replace(".dart","")); var isScr=fn.indexOf("screen")>=0||fn.indexOf("page")>=0||fn.indexOf("view")>=0; var stub=isScr?"import 'package:flutter/material.dart';\n\nclass "+cls+" extends StatelessWidget{\n const "+cls+"({super.key});\n @override\n Widget build(BuildContext ctx)=>Scaffold(\n appBar: AppBar(title: const Text('"+fn.replace(/_/g," ").replace(".dart","")+"')),\n body: const Center(child: Text('"+cls+" — TODO')),\n );\n}\n":"// TODO: implement\n\nclass "+cls+"{\n // "+fn+"\n}\n"; zip.file(folder+path,stub); } }); } /* --- React Native (Expo) --- */ function buildReactNative(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var extracted=extractCode(panelTxt); var allT=code+" "+panelTxt; var usesTS=allT.indexOf(".tsx")>=0||allT.indexOf(": React.")>=0||allT.indexOf("interface ")>=0; var ext=usesTS?"tsx":"jsx"; zip.file(folder+"package.json",'{\n "name": "'+pn+'",\n "version": "1.0.0",\n "main": "expo-router/entry",\n "scripts": {\n "start": "expo start",\n "android": "expo run:android",\n "ios": "expo run:ios",\n "web": "expo start --web"\n },\n "dependencies": {\n "expo": "~52.0.0",\n "expo-router": "~4.0.0",\n "expo-status-bar": "~2.0.1",\n "expo-font": "~13.0.1",\n "react": "18.3.1",\n "react-native": "0.76.7",\n "react-native-safe-area-context": "4.12.0",\n "react-native-screens": "~4.3.0",\n "@react-navigation/native": "^7.0.14"\n },\n "devDependencies": {\n "@babel/core": "^7.25.0",\n "typescript": "~5.3.3",\n "@types/react": "~18.3.12"\n }\n}\n'); zip.file(folder+"app.json",'{\n "expo": {\n "name": "'+slugTitle(pn)+'",\n "slug": "'+pn+'",\n "version": "1.0.0",\n "orientation": "portrait",\n "scheme": "'+pn+'",\n "platforms": ["ios","android","web"],\n "icon": "./assets/icon.png",\n "splash": {"image": "./assets/splash.png","resizeMode":"contain","backgroundColor":"#ffffff"},\n "ios": {"supportsTablet": true},\n "android": {"package": "com.example.'+pn+'"},\n "newArchEnabled": true\n }\n}\n'); zip.file(folder+"tsconfig.json",'{\n "extends": "expo/tsconfig.base",\n "compilerOptions": {\n "strict": true,\n "paths": {"@/*": ["./src/*"]}\n }\n}\n'); zip.file(folder+"babel.config.js","module.exports=function(api){\n api.cache(true);\n return {presets:['babel-preset-expo']};\n};\n"); var hasApp=Object.keys(extracted).some(function(k){return k.toLowerCase().indexOf("app.")>=0;}); if(!hasApp) zip.file(folder+"App."+ext,"import React from 'react';\nimport {View,Text,StyleSheet,StatusBar,SafeAreaView} from 'react-native';\n\nexport default function App(){\n return(\nBuilt with PantheraHive BOS
\n"); h+="
"+hc+"