evaldoc 1.0.0
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/.env.staging.example +19 -0
- package/.firebaserc +6 -0
- package/.github/workflows/auto-merge-to-main.yml +28 -0
- package/.github/workflows/ci.yml +27 -0
- package/.github/workflows/npxhub-publish.yml +170 -0
- package/.prettierignore +4 -0
- package/.prettierrc.json +7 -0
- package/README.md +142 -0
- package/SRS.md +448 -0
- package/apphosting.staging.yaml +48 -0
- package/apphosting.yaml +50 -0
- package/bin/evaldoc.cjs +105 -0
- package/cors.json +8 -0
- package/docs/ARCHITECTURE.md +769 -0
- package/docs/ARCHITECTURE_SUMMARY.md +323 -0
- package/docs/DEPLOYMENT.md +86 -0
- package/docs/DEPLOYMENT_STEPS.md +373 -0
- package/docs/DEPLOY_FIRESTORE_RULES.md +239 -0
- package/docs/DEPLOY_STORAGE_RULES.md +82 -0
- package/docs/DOCTOR_TUTORIAL.md +467 -0
- package/docs/FIREBASE_STORAGE_CORS_FIX.md +131 -0
- package/docs/FIXES_APPLIED.md +128 -0
- package/docs/IMPLEMENTATION.md +498 -0
- package/docs/IMPLEMENTATION_COMPLETE.md +317 -0
- package/docs/LLM-SETUP.md +216 -0
- package/docs/MANUAL_TEST_GUIDE.md +327 -0
- package/docs/PATIENT_GUIDE.md +451 -0
- package/docs/PLATFORM_OVERVIEW.md +194 -0
- package/docs/QUICKSTART.md +252 -0
- package/docs/QUICK_REFERENCE.md +222 -0
- package/docs/READY_TO_TEST.md +383 -0
- package/docs/RUN.md +237 -0
- package/docs/START_TESTING.md +130 -0
- package/docs/STRIPE_CONNECT_SETUP.md +454 -0
- package/docs/SYSTEM_READY.md +345 -0
- package/docs/THREADS_IMPLEMENTATION_COMPLETE.md +431 -0
- package/docs/THREAD_IMPLEMENTATION_GUIDE.md +345 -0
- package/docs/firebasefiles.md +21 -0
- package/docs/homepage.md +1208 -0
- package/docs/payments.md +159 -0
- package/eslint.config.js +76 -0
- package/firebase.json +27 -0
- package/firestore.indexes.json +88 -0
- package/firestore.rules +125 -0
- package/functions/package-lock.json +2884 -0
- package/functions/package.json +20 -0
- package/functions/src/analytics.js +101 -0
- package/functions/src/audit.js +28 -0
- package/functions/src/auth.js +19 -0
- package/functions/src/drafts.js +128 -0
- package/functions/src/feedback.js +164 -0
- package/functions/src/firebase-init.js +29 -0
- package/functions/src/i18n.js +134 -0
- package/functions/src/limits.js +27 -0
- package/functions/src/llm.js +271 -0
- package/functions/src/ner.js +162 -0
- package/functions/src/notifications.js +273 -0
- package/functions/src/orgs.js +127 -0
- package/functions/src/patient-api.js +545 -0
- package/functions/src/payment.js +830 -0
- package/functions/src/safety.js +188 -0
- package/functions/src/stripe-config.js +33 -0
- package/functions/src/topics.js +94 -0
- package/functions/src/triage.js +191 -0
- package/functions/src/utils.js +28 -0
- package/functions/src/validate.js +177 -0
- package/functions/test-firebase.js +36 -0
- package/jest.config.js +18 -0
- package/package.json +52 -0
- package/public/404.html +93 -0
- package/public/app/analytics.html +336 -0
- package/public/app/analytics.js +177 -0
- package/public/app/app.css +1400 -0
- package/public/app/app.js +1754 -0
- package/public/app/config.js +14 -0
- package/public/app/inbox-threads.js +843 -0
- package/public/app/inbox.html +598 -0
- package/public/app/inbox.js +397 -0
- package/public/app/index.html +612 -0
- package/public/assets/logo.jpg +0 -0
- package/public/chat.html +137 -0
- package/public/css/chat.css +870 -0
- package/public/css/landing-enhance.css +298 -0
- package/public/css/styles.css +1198 -0
- package/public/favicon.svg +5 -0
- package/public/find-doctor.html +220 -0
- package/public/index.html +399 -0
- package/public/js/chat-threads.js +513 -0
- package/public/js/chat.js +832 -0
- package/public/js/search.js +41 -0
- package/public/privacy.html +101 -0
- package/public/robots.txt +6 -0
- package/public/site.webmanifest +12 -0
- package/public/terms.html +110 -0
- package/scripts/create-test-provider.js +55 -0
- package/scripts/deploy-staging.sh +51 -0
- package/scripts/fix-provider.js +39 -0
- package/scripts/quick-test.sh +37 -0
- package/scripts/test-chat.sh +54 -0
- package/scripts/test-multi-turn.js +137 -0
- package/scripts/test-ner-simple.js +28 -0
- package/scripts/test-ner.js +60 -0
- package/scripts/test-stripe-connect.js +304 -0
- package/scripts/test-webhook-secret.sh +24 -0
- package/server.js +3864 -0
- package/storage.rules +22 -0
- package/test-api.json +1 -0
- package/tests/emulator/README.md +38 -0
- package/tests/emulator/counters.emulator.test.js +81 -0
- package/tests/integration/api-account-selfserve.test.js +312 -0
- package/tests/integration/api-admin-digest.test.js +202 -0
- package/tests/integration/api-chat-satisfaction.test.js +259 -0
- package/tests/integration/api-doctor-drafts.test.js +236 -0
- package/tests/integration/api-doctor-inbox.test.js +401 -0
- package/tests/integration/api-org.test.js +328 -0
- package/tests/integration/api-payment.test.js +664 -0
- package/tests/integration/api-provider-settings.test.js +475 -0
- package/tests/integration/api-qr.test.js +121 -0
- package/tests/integration/api-security.test.js +241 -0
- package/tests/integration/api-stripe-search.test.js +303 -0
- package/tests/integration/api-webhook-deletion.test.js +231 -0
- package/tests/mocks/firebase-admin.js +220 -0
- package/tests/unit/analytics.test.js +151 -0
- package/tests/unit/audit-bugs.test.js +203 -0
- package/tests/unit/chat-ui-logic.test.js +283 -0
- package/tests/unit/drafts.test.js +104 -0
- package/tests/unit/feedback.test.js +148 -0
- package/tests/unit/i18n.test.js +229 -0
- package/tests/unit/llm-retry.test.js +122 -0
- package/tests/unit/notifications.test.js +155 -0
- package/tests/unit/orgs.test.js +328 -0
- package/tests/unit/payment.test.js +889 -0
- package/tests/unit/safety-edge-cases.test.js +509 -0
- package/tests/unit/safety-srs-compliance.test.js +252 -0
- package/tests/unit/safety.test.js +291 -0
- package/tests/unit/server-helpers.test.js +774 -0
- package/tests/unit/topics-limits.test.js +158 -0
- package/tests/unit/triage.test.js +152 -0
- package/tests/unit/validate.test.js +408 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Simple test to verify NER is working with standard entities
|
|
2
|
+
require('dotenv').config();
|
|
3
|
+
const { HfInference } = require('@huggingface/inference');
|
|
4
|
+
|
|
5
|
+
async function quickTest() {
|
|
6
|
+
const client = new HfInference(process.env.HUGGINGFACE_API_KEY);
|
|
7
|
+
|
|
8
|
+
const testText = "John Smith from New York has diabetes and takes insulin.";
|
|
9
|
+
|
|
10
|
+
console.log('Testing:', testText);
|
|
11
|
+
console.log('\n');
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const result = await client.tokenClassification({
|
|
15
|
+
model: 'dslim/bert-base-NER',
|
|
16
|
+
inputs: testText
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
console.log('✓ NER Working! Detected entities:');
|
|
20
|
+
result.forEach(entity => {
|
|
21
|
+
console.log(` - "${entity.word}" [${entity.entity_group || entity.entity}] (${(entity.score * 100).toFixed(1)}%)`);
|
|
22
|
+
});
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error('Error:', error.message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
quickTest();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Test script for NER integration
|
|
2
|
+
require('dotenv').config();
|
|
3
|
+
const { analyzeMedicalSafety } = require('./functions/src/ner');
|
|
4
|
+
|
|
5
|
+
async function testNER() {
|
|
6
|
+
console.log('🧪 Testing Medical NER Integration\n');
|
|
7
|
+
|
|
8
|
+
const testCases = [
|
|
9
|
+
{
|
|
10
|
+
name: 'Urgent Symptoms',
|
|
11
|
+
text: 'I have severe chest pain and difficulty breathing'
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'Disease Mention',
|
|
15
|
+
text: 'I was diagnosed with diabetes and hypertension'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Medication Query',
|
|
19
|
+
text: 'I am taking metformin and lisinopril for my conditions'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'General Question',
|
|
23
|
+
text: 'What are the symptoms of the flu?'
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
for (const testCase of testCases) {
|
|
28
|
+
console.log(`\n📋 Test: ${testCase.name}`);
|
|
29
|
+
console.log(` Input: "${testCase.text}"`);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const result = await analyzeMedicalSafety(testCase.text);
|
|
33
|
+
|
|
34
|
+
console.log(` Entities Found: ${result.entities.length}`);
|
|
35
|
+
if (result.entities.length > 0) {
|
|
36
|
+
result.entities.forEach(entity => {
|
|
37
|
+
console.log(` - "${entity.text}" [${entity.type}] (confidence: ${(entity.confidence * 100).toFixed(1)}%)`);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(` Analysis:`);
|
|
42
|
+
console.log(` Has Symptoms: ${result.analysis.hasSymptoms}`);
|
|
43
|
+
console.log(` Has Diseases: ${result.analysis.hasDiseases}`);
|
|
44
|
+
console.log(` Has Medications: ${result.analysis.hasMedications}`);
|
|
45
|
+
console.log(` Should Escalate: ${result.shouldEscalate}`);
|
|
46
|
+
console.log(` Requires Disclaimer: ${result.requiresDisclaimer}`);
|
|
47
|
+
|
|
48
|
+
if (result.analysis.urgentSymptoms.length > 0) {
|
|
49
|
+
console.log(` ⚠️ URGENT: ${result.analysis.urgentSymptoms.join(', ')}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(` ❌ Error: ${error.message}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log('\n✅ NER Testing Complete\n');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
testNER().catch(console.error);
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test Script for Stripe Connect Payment Flow
|
|
5
|
+
* Tests: Provider creation → Connect setup → Pricing → Patient subscription
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const admin = require('firebase-admin');
|
|
9
|
+
const fetch = require('node-fetch');
|
|
10
|
+
|
|
11
|
+
// Load environment variables
|
|
12
|
+
require('dotenv').config();
|
|
13
|
+
|
|
14
|
+
const BASE_URL = 'http://localhost:54112';
|
|
15
|
+
|
|
16
|
+
// Initialize Firebase Admin
|
|
17
|
+
if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
18
|
+
admin.initializeApp({
|
|
19
|
+
credential: admin.credential.cert(require(process.env.GOOGLE_APPLICATION_CREDENTIALS)),
|
|
20
|
+
projectId: process.env.PROJECT_ID || 'evaldocplatform'
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const db = admin.firestore();
|
|
25
|
+
|
|
26
|
+
// ANSI color codes for pretty output
|
|
27
|
+
const colors = {
|
|
28
|
+
reset: '\x1b[0m',
|
|
29
|
+
green: '\x1b[32m',
|
|
30
|
+
red: '\x1b[31m',
|
|
31
|
+
yellow: '\x1b[33m',
|
|
32
|
+
blue: '\x1b[34m',
|
|
33
|
+
cyan: '\x1b[36m'
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function log(message, color = 'reset') {
|
|
37
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function step(number, title) {
|
|
41
|
+
console.log('\n' + '='.repeat(60));
|
|
42
|
+
log(`STEP ${number}: ${title}`, 'cyan');
|
|
43
|
+
console.log('='.repeat(60));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function testFlow() {
|
|
47
|
+
try {
|
|
48
|
+
log('\n🧪 Starting Stripe Connect Payment Flow Test\n', 'blue');
|
|
49
|
+
|
|
50
|
+
// Step 1: Create test user in Firebase Auth
|
|
51
|
+
step(1, 'Create Test Provider User');
|
|
52
|
+
|
|
53
|
+
const testEmail = `test-provider-${Date.now()}@evaldoc.test`;
|
|
54
|
+
const testPassword = 'TestPassword123!';
|
|
55
|
+
|
|
56
|
+
log(`Creating user: ${testEmail}`, 'yellow');
|
|
57
|
+
|
|
58
|
+
let userRecord;
|
|
59
|
+
try {
|
|
60
|
+
userRecord = await admin.auth().createUser({
|
|
61
|
+
email: testEmail,
|
|
62
|
+
password: testPassword,
|
|
63
|
+
displayName: 'Dr. Test Provider'
|
|
64
|
+
});
|
|
65
|
+
log(`✓ User created: ${userRecord.uid}`, 'green');
|
|
66
|
+
} catch (error) {
|
|
67
|
+
log(`✗ Error creating user: ${error.message}`, 'red');
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Step 2: Create provider document
|
|
72
|
+
step(2, 'Create Provider Document');
|
|
73
|
+
|
|
74
|
+
const providerData = {
|
|
75
|
+
handle: `test-provider-${Date.now()}`,
|
|
76
|
+
displayName: 'Dr. Test Provider',
|
|
77
|
+
specialty: 'Test Medicine',
|
|
78
|
+
country: 'US',
|
|
79
|
+
email: testEmail,
|
|
80
|
+
status: 'published',
|
|
81
|
+
welcomeMessage: 'Welcome to my test practice!',
|
|
82
|
+
topics: ['medication_info_safety', 'general_health_info'],
|
|
83
|
+
brandPrimaryColor: '#0e1a14',
|
|
84
|
+
brandSecondaryColor: '#4fb3a4',
|
|
85
|
+
currency: 'usd',
|
|
86
|
+
planMonthlyLimit: 1000,
|
|
87
|
+
createdAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
88
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp()
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
log(`Creating provider: ${providerData.handle}`, 'yellow');
|
|
92
|
+
|
|
93
|
+
await db.collection('providers').doc(userRecord.uid).set(providerData);
|
|
94
|
+
log(`✓ Provider created with handle: ${providerData.handle}`, 'green');
|
|
95
|
+
|
|
96
|
+
// Step 3: Get Firebase ID token (simulate login)
|
|
97
|
+
step(3, 'Get Authentication Token');
|
|
98
|
+
|
|
99
|
+
log('Getting custom token...', 'yellow');
|
|
100
|
+
const customToken = await admin.auth().createCustomToken(userRecord.uid);
|
|
101
|
+
log(`✓ Custom token generated`, 'green');
|
|
102
|
+
|
|
103
|
+
// Step 4: Create Stripe Connect Account
|
|
104
|
+
step(4, 'Create Stripe Connect Account (Programmatic)');
|
|
105
|
+
|
|
106
|
+
log('Calling /api/stripe/create-connect-account...', 'yellow');
|
|
107
|
+
|
|
108
|
+
const connectResponse = await fetch(`${BASE_URL}/api/stripe/create-connect-account`, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
'Authorization': `Bearer ${customToken}`,
|
|
112
|
+
'Content-Type': 'application/json'
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (!connectResponse.ok) {
|
|
117
|
+
const error = await connectResponse.text();
|
|
118
|
+
log(`✗ Error: ${error}`, 'red');
|
|
119
|
+
throw new Error(`Connect account creation failed: ${error}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const connectResult = await connectResponse.json();
|
|
123
|
+
log(`✓ Connect account created: ${connectResult.accountId}`, 'green');
|
|
124
|
+
log(` Status: ${connectResult.onboardingStatus}`, 'yellow');
|
|
125
|
+
|
|
126
|
+
// Step 5: Get onboarding link
|
|
127
|
+
step(5, 'Generate Onboarding Link');
|
|
128
|
+
|
|
129
|
+
log('Calling /api/stripe/create-account-link...', 'yellow');
|
|
130
|
+
|
|
131
|
+
const linkResponse = await fetch(`${BASE_URL}/api/stripe/create-account-link`, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: {
|
|
134
|
+
'Authorization': `Bearer ${customToken}`,
|
|
135
|
+
'Content-Type': 'application/json'
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (!linkResponse.ok) {
|
|
140
|
+
const error = await linkResponse.text();
|
|
141
|
+
log(`✗ Error: ${error}`, 'red');
|
|
142
|
+
throw new Error(`Account link creation failed: ${error}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const linkResult = await linkResponse.json();
|
|
146
|
+
log(`✓ Onboarding link generated`, 'green');
|
|
147
|
+
log(` URL: ${linkResult.url}`, 'cyan');
|
|
148
|
+
log(` Expires: ${new Date(linkResult.expiresAt * 1000).toLocaleString()}`, 'yellow');
|
|
149
|
+
|
|
150
|
+
log('\n⚠️ MANUAL STEP REQUIRED:', 'yellow');
|
|
151
|
+
log(' The provider would now visit this URL to complete Stripe onboarding.', 'yellow');
|
|
152
|
+
log(' In test mode, you can use test business details.', 'yellow');
|
|
153
|
+
log(' For this automated test, we\'ll simulate the account being approved.', 'yellow');
|
|
154
|
+
|
|
155
|
+
// Step 6: Simulate account approval (in real flow, this comes from Stripe webhook)
|
|
156
|
+
step(6, 'Simulate Stripe Account Approval');
|
|
157
|
+
|
|
158
|
+
log('Updating provider to simulate approved account...', 'yellow');
|
|
159
|
+
await db.collection('providers').doc(userRecord.uid).update({
|
|
160
|
+
chargesEnabled: true,
|
|
161
|
+
payoutsEnabled: true,
|
|
162
|
+
stripeOnboardingStatus: 'COMPLETE'
|
|
163
|
+
});
|
|
164
|
+
log(`✓ Provider account marked as approved`, 'green');
|
|
165
|
+
|
|
166
|
+
// Step 7: Set pricing
|
|
167
|
+
step(7, 'Set Subscription Pricing');
|
|
168
|
+
|
|
169
|
+
const pricingData = {
|
|
170
|
+
currency: 'usd',
|
|
171
|
+
prices: {
|
|
172
|
+
day: 5.00,
|
|
173
|
+
week: 20.00,
|
|
174
|
+
month: 50.00
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
log(`Setting pricing: Day=$${pricingData.prices.day}, Week=$${pricingData.prices.week}, Month=$${pricingData.prices.month}`, 'yellow');
|
|
179
|
+
|
|
180
|
+
const pricingResponse = await fetch(`${BASE_URL}/api/stripe/set-pricing`, {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
headers: {
|
|
183
|
+
'Authorization': `Bearer ${customToken}`,
|
|
184
|
+
'Content-Type': 'application/json'
|
|
185
|
+
},
|
|
186
|
+
body: JSON.stringify(pricingData)
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (!pricingResponse.ok) {
|
|
190
|
+
const error = await pricingResponse.text();
|
|
191
|
+
log(`✗ Error: ${error}`, 'red');
|
|
192
|
+
throw new Error(`Pricing setup failed: ${error}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const pricingResult = await pricingResponse.json();
|
|
196
|
+
log(`✓ Pricing configured`, 'green');
|
|
197
|
+
log(` Product ID: ${pricingResult.productId}`, 'yellow');
|
|
198
|
+
log(` Price IDs:`, 'yellow');
|
|
199
|
+
Object.entries(pricingResult.priceIds).forEach(([interval, priceId]) => {
|
|
200
|
+
log(` ${interval}: ${priceId}`, 'cyan');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Step 8: Patient creates checkout session
|
|
204
|
+
step(8, 'Patient Creates Checkout Session');
|
|
205
|
+
|
|
206
|
+
const patientSessionId = `test-patient-${Date.now()}`;
|
|
207
|
+
|
|
208
|
+
log(`Patient session: ${patientSessionId}`, 'yellow');
|
|
209
|
+
log(`Subscribing to: ${providerData.handle} (monthly plan)`, 'yellow');
|
|
210
|
+
|
|
211
|
+
const checkoutResponse = await fetch(`${BASE_URL}/api/payment/create-checkout-session`, {
|
|
212
|
+
method: 'POST',
|
|
213
|
+
headers: {
|
|
214
|
+
'Content-Type': 'application/json'
|
|
215
|
+
},
|
|
216
|
+
body: JSON.stringify({
|
|
217
|
+
handle: providerData.handle,
|
|
218
|
+
sessionId: patientSessionId,
|
|
219
|
+
interval: 'month'
|
|
220
|
+
})
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (!checkoutResponse.ok) {
|
|
224
|
+
const error = await checkoutResponse.text();
|
|
225
|
+
log(`✗ Error: ${error}`, 'red');
|
|
226
|
+
throw new Error(`Checkout session creation failed: ${error}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const checkoutResult = await checkoutResponse.json();
|
|
230
|
+
log(`✓ Checkout session created`, 'green');
|
|
231
|
+
log(` Session ID: ${checkoutResult.sessionId}`, 'yellow');
|
|
232
|
+
log(` Checkout URL: ${checkoutResult.url}`, 'cyan');
|
|
233
|
+
|
|
234
|
+
log('\n⚠️ MANUAL STEP REQUIRED:', 'yellow');
|
|
235
|
+
log(' The patient would now visit this URL to complete payment.', 'yellow');
|
|
236
|
+
log(' In test mode, use card: 4242 4242 4242 4242', 'yellow');
|
|
237
|
+
log(' After payment, Stripe webhooks would grant access.', 'yellow');
|
|
238
|
+
|
|
239
|
+
// Step 9: Check access (before payment)
|
|
240
|
+
step(9, 'Check Patient Access (Before Payment)');
|
|
241
|
+
|
|
242
|
+
const accessCheckResponse = await fetch(`${BASE_URL}/api/payment/check-access`, {
|
|
243
|
+
method: 'POST',
|
|
244
|
+
headers: {
|
|
245
|
+
'Content-Type': 'application/json'
|
|
246
|
+
},
|
|
247
|
+
body: JSON.stringify({
|
|
248
|
+
handle: providerData.handle,
|
|
249
|
+
sessionId: patientSessionId
|
|
250
|
+
})
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const accessResult = await accessCheckResponse.json();
|
|
254
|
+
log(`Access status: ${accessResult.hasAccess ? 'GRANTED' : 'DENIED'}`, accessResult.hasAccess ? 'green' : 'yellow');
|
|
255
|
+
log(`Access type: ${accessResult.type || 'none'}`, 'yellow');
|
|
256
|
+
|
|
257
|
+
// Summary
|
|
258
|
+
step('✓', 'Test Summary');
|
|
259
|
+
|
|
260
|
+
log('\n✅ All backend steps completed successfully!\n', 'green');
|
|
261
|
+
|
|
262
|
+
log('What was tested:', 'cyan');
|
|
263
|
+
log(' ✓ Provider user creation', 'green');
|
|
264
|
+
log(' ✓ Provider document creation', 'green');
|
|
265
|
+
log(' ✓ Stripe Connect account creation (programmatic)', 'green');
|
|
266
|
+
log(' ✓ Onboarding link generation', 'green');
|
|
267
|
+
log(' ✓ Account approval simulation', 'green');
|
|
268
|
+
log(' ✓ Subscription pricing setup', 'green');
|
|
269
|
+
log(' ✓ Checkout session creation', 'green');
|
|
270
|
+
log(' ✓ Access control check', 'green');
|
|
271
|
+
|
|
272
|
+
log('\nManual steps needed to complete flow:', 'yellow');
|
|
273
|
+
log(' 1. Visit onboarding URL to complete Stripe Connect setup', 'yellow');
|
|
274
|
+
log(' 2. Visit checkout URL and pay with test card (4242 4242 4242 4242)', 'yellow');
|
|
275
|
+
log(' 3. Set up webhook endpoint to receive Stripe events', 'yellow');
|
|
276
|
+
|
|
277
|
+
log('\nTest data created:', 'cyan');
|
|
278
|
+
log(` Provider UID: ${userRecord.uid}`, 'cyan');
|
|
279
|
+
log(` Provider Handle: ${providerData.handle}`, 'cyan');
|
|
280
|
+
log(` Provider Email: ${testEmail}`, 'cyan');
|
|
281
|
+
log(` Stripe Account: ${connectResult.accountId}`, 'cyan');
|
|
282
|
+
log(` Patient Session: ${patientSessionId}`, 'cyan');
|
|
283
|
+
|
|
284
|
+
log('\nCleanup (if needed):', 'yellow');
|
|
285
|
+
log(` firebase auth:users:delete ${userRecord.uid}`, 'yellow');
|
|
286
|
+
log(` Delete provider document: providers/${userRecord.uid}`, 'yellow');
|
|
287
|
+
|
|
288
|
+
} catch (error) {
|
|
289
|
+
log(`\n✗ Test failed: ${error.message}`, 'red');
|
|
290
|
+
console.error(error);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Run the test
|
|
296
|
+
testFlow()
|
|
297
|
+
.then(() => {
|
|
298
|
+
log('\n✓ Test completed successfully!\n', 'green');
|
|
299
|
+
process.exit(0);
|
|
300
|
+
})
|
|
301
|
+
.catch(error => {
|
|
302
|
+
log(`\n✗ Test failed: ${error.message}\n`, 'red');
|
|
303
|
+
process.exit(1);
|
|
304
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Test webhook secret is loaded
|
|
4
|
+
echo "🔍 Checking Stripe Webhook Secret..."
|
|
5
|
+
echo ""
|
|
6
|
+
|
|
7
|
+
if grep -q "STRIPE_WEBHOOK_SECRET=whsec_" .env; then
|
|
8
|
+
echo "✅ Webhook secret found in .env"
|
|
9
|
+
SECRET=$(grep "STRIPE_WEBHOOK_SECRET" .env | cut -d'=' -f2)
|
|
10
|
+
if [ ! -z "$SECRET" ] && [ "$SECRET" != "whsec_" ]; then
|
|
11
|
+
echo "✅ Secret is configured: ${SECRET:0:20}..."
|
|
12
|
+
echo ""
|
|
13
|
+
echo "🎉 Ready to receive webhooks!"
|
|
14
|
+
echo ""
|
|
15
|
+
echo "Next steps:"
|
|
16
|
+
echo " 1. Go to provider dashboard: http://localhost:54112/app"
|
|
17
|
+
echo " 2. Test payment flow using browser console"
|
|
18
|
+
echo " 3. See MANUAL_TEST_GUIDE.md for commands"
|
|
19
|
+
else
|
|
20
|
+
echo "⚠️ Secret is empty"
|
|
21
|
+
fi
|
|
22
|
+
else
|
|
23
|
+
echo "❌ Webhook secret not found"
|
|
24
|
+
fi
|