1import{j as e}from"./index-w8uym0Jr.js";import"./vendor-react-B5kyjNin.js";import"./vendor-auth-3IJ4rw9Z.js";import"./vendor-ui-zVkK_Prl.js";import"./vendor-payment-Cx_eeV9O.js";import"./vendor-framer-CGrNjTbd.js";function t(n){const r={code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...n.components};return e.jsxs(e.Fragment,{children:[e.jsx(r.h1,{children:"Payment Integration"}),` 2`,e.jsx(r.p,{children:"This document outlines the payment processing system in ClassAct, which uses PayPal integration to handle ticket purchases."}),` 3`,e.jsx(r.h2,{children:"Overview"}),` 4`,e.jsx(r.p,{children:"ClassAct implements PayPal as the primary payment processor for ticket purchases. The integration uses the official React PayPal JS library to provide a secure and seamless payment experience."}),` 5`,e.jsx(r.h2,{children:"Configuration"}),` 6`,e.jsx(r.h3,{children:"Environment Variables"}),` 7`,e.jsxs(r.p,{children:["Payment integration requires the following environment variable to be set in your ",e.jsx(r.code,{children:".env"})," file:"]}),` 8`,e.jsx("div",{className:"command-example",children:e.jsx("div",{className:"output-line",children:e.jsx("pre",{children:"VITE_PAYPAL_CLIENT_ID=your_paypal_client_id"})})}),` 9`,e.jsxs("div",{className:"note-block",children:[e.jsx("div",{className:"note-icon",children:"â ï¸"}),e.jsxs("div",{className:"note-content",children:[e.jsx("p",{children:e.jsx("strong",{children:"Security Note:"})}),e.jsx("p",{children:"Never hardcode PayPal credentials in your application code. Always use environment variables to store sensitive information."}),e.jsx("p",{children:"For development, you can obtain a sandbox client ID from the PayPal Developer Dashboard."})]})]}),` 10`,e.jsx(r.p,{children:"For local development, use PayPal's sandbox environment to avoid real transactions. The sandbox allows you to test the entire payment flow without processing actual payments."}),` 11`,e.jsx(r.h3,{children:"PayPal Provider Setup"}),` 12`,e.jsx(r.p,{children:"The application uses the PayPalScriptProvider to configure the PayPal SDK:"}),` 13`,e.jsx(r.pre,{children:e.jsx(r.code,{className:"language-tsx",children:`/**\r 14 * @fileoverview PayPal configuration for ClassAct\r 15 * \r 16 * Features:\r 17 * - PayPal script provider setup\r 18 * - Currency and client ID configuration\r 19 * \r 20 * @requires @paypal/react-paypal-js\r 21 * @requires react\r 22 */\r 23\r 24import { PayPalScriptProvider } from '@paypal/react-paypal-js';\r 25import React from 'react';\r 26\r 27// Initial configuration for PayPal\r 28const paypalInitialOptions = {\r 29 'client-id': import.meta.env.VITE_PAYPAL_CLIENT_ID,\r 30 currency: 'USD',\r 31 intent: 'capture',\r 32 'data-client-token': 'abc123xyz==',\r 33 // Set to 'sandbox' during development, 'production' for live payments\r 34};\r 35\r 36export const PayPalProvider = ({ children }) => {\r 37 return (\r 38 <PayPalScriptProvider options={paypalInitialOptions}>\r 39 {children}\r 40 </PayPalScriptProvider>\r 41 );\r 42}; 43`})}),` 44`,e.jsx(r.h2,{children:"Component Implementation"}),` 45`,e.jsx(r.h3,{children:"PayPal Buttons Component"}),` 46`,e.jsxs(r.p,{children:["The ",e.jsx(r.code,{children:"PayPalButtons"})," component handles the payment UI and transaction logic. Implement it as follows:"]}),` 47`,e.jsx(r.pre,{children:e.jsx(r.code,{className:"language-tsx",children:`/**\r 48 * @fileoverview Checkout component with PayPal integration\r 49 * \r 50 * Features:\r 51 * - PayPal button rendering\r 52 * - Payment processing\r 53 * - Transaction confirmation\r 54 * \r 55 * @requires react\r 56 * @requires @paypal/react-paypal-js\r 57 * @requires ../utils/logger\r 58 */\r 59\r 60import React, { useState, useEffect } from 'react';\r 61import { PayPalButtons, usePayPalScriptReducer } from '@paypal/react-paypal-js';\r 62import { createLogger } from '../utils/logger';\r 63import { useNavigate, useParams } from 'react-router-dom';\r 64import { useAuth } from '../context/AuthContext';\r 65import { getEventById } from '../services/eventService';\r 66import { createTicket } from '../services/ticketService';\r 67import styles from './CheckoutPreview.module.scss';\r 68import toast from 'react-hot-toast';\r 69\r 70const logger = createLogger('CheckoutPreview', true);\r 71\r 72const CheckoutPreview = () => {\r 73 const [{ isPending }] = usePayPalScriptReducer();\r 74 const [event, setEvent] = useState(null);\r 75 const [loading, setLoading] = useState(true);\r 76 const [error, setError] = useState(null);\r 77 const { eventId } = useParams();\r 78 const { user } = useAuth();\r 79 const navigate = useNavigate();\r 80\r 81 // Load event details\r 82 useEffect(() => {\r 83 const loadEvent = async () => {\r
84 try {\r 85 setLoading(true);\r 86 const eventData = await getEventById(eventId);\r 87 setEvent(eventData);\r 88 } catch (err) {\r 89 logger.error('Failed to load event:', err);\r 90 setError('Failed to load event details. Please try again.');\r 91 toast.error('Failed to load event details');\r 92 } finally {\r 93 setLoading(false);\r 94 }\r 95 };\r 96\r 97 loadEvent();\r 98 }, [eventId]);\r 99\r 100 // Handle payment completion\r 101 const handlePaymentSuccess = async (details) => {\r 102 try {\r 103 logger.log('Payment completed successfully:', details);\r 104 \r 105 // Create ticket after successful payment\r 106 const ticketData = {\r 107 event_id: eventId,\r 108 user_id: user.id,\r 109 purchase_id: details.id\r 110 };\r 111 \r 112 const ticket = await createTicket(ticketData);\r 113 \r 114 // Show success notification\r 115 toast.success('Payment successful! Your ticket has been issued.');\r 116 \r 117 // Redirect to ticket view\r 118 navigate(\`/tickets/\${ticket.id}\`);\r 119 } catch (err) {\r 120 logger.error('Error processing ticket after payment:', err);\r 121 setError('Payment was successful but we had trouble creating your ticket. Please contact support.');\r 122 toast.error('Error processing ticket');\r 123 }\r 124 };\r 125\r 126 return (\r 127 <div className={styles.checkoutContainer}>\r 128 {loading ? (\r 129 <div className={styles.loading}>Loading event details...</div>\r 130 ) : error ? (\r 131 <div className={styles.error}>{error}</div>\r 132 ) : (\r 133 <div className={styles.checkoutCard}>\r 134 <h1>Checkout</h1>\r 135 \r 136 <div className={styles.eventDetails}>\r 137 <h2>{event.title}</h2>\r 138 <p className={styles.eventDate}>{new Date(event.date).toLocaleDateString()}</p>\r 139 <p className={styles.eventLocation}>{event.location}</p>\r 140 </div>\r 141 \r 142 <div className={styles.priceDetails}>\r 143 <span className={styles.priceLabel}>Ticket Price:</span>\r 144 <span className={styles.priceValue}>\${event.price.toFixed(2)}</span>\r 145 </div>\r 146 \r 147 <div className={styles.paymentContainer}>\r 148 {isPending ? (\r 149 <div className={styles.loading}>Loading PayPal buttons...</div>\r 150 ) : (\r 151 <PayPalButtons\r 152 style={{ layout: 'vertical' }}\r 153 createOrder={(data, actions) => {\r 154 return actions.order.create({\r 155 purchase_units: [\r 156 {\r 157 description: \`Ticket for \${event.title}\`,\r 158 amount: {\r 159 currency_code: 'USD',\r 160 value: event.price.toString(),\r 161 },\r 162 },\r 163 ],\r 164 });\r 165 }}\r 166 onApprove={(data, actions) => {\r 167 return actions.order.capture().then(handlePaymentSuccess);\r 168 }}\r 169 onError={(err) => {\r 170 logger.error('PayPal error:', err);\r 171 setError('Payment failed. Please try again.');\r 172 toast.error('Payment failed');\r 173 }}\r 174 />\r 175 )}\r 176 </div>\r 177 </div>\r 178 )}\r 179 </div>\r 180 );\r 181};\r 182\r 183export default CheckoutPreview; 184`})}),` 185`,e.jsx(r.h2,{children:"Payment Flow"}),` 186`,e.jsx(r.p,{children:"The payment process follows these steps:"}),` 187`,e.jsxs(r.ol,{children:[` 188`,e.jsxs(r.li,{children:[e.jsx(r.strong,{children:"Event Selection"}),': User selects an event and clicks "Buy Ticket"']}),` 189`,e.jsxs(r.li,{children:[e.jsx(r.strong,{children:"Checkout Page"}),": User is taken to the checkout page showing event details and price"]}),` 190`,e.jsxs(r.li,{children:[e.jsx(r.strong,{children:"Payment Processing"}),": User completes payment using PayPal"]}),` 191`,e.jsxs(r.li,{children:[e.jsx(r.strong,{children:"Transaction Verification"}),": Application validates the payment with PayPal"]}),` 192`,e.jsxs(r.li,{children:[e.jsx(r.strong,{children:"Ticket Creation"}),": Upon successful payment, a ticket is created"]}),` 193`,e.jsxs(r.li,{children:[e.jsx(r.strong,{children:"Confirmation"}),": User receives confirmation and is redirected to view their ticket"]}),` 194`]}),` 195`,e.jsx(r.h2,{children:"Payment Service"}),` 196`,e.jsx(r.p,{children:"The payment service handles payment verification and tracking:"}),` 197`,e.jsx(r.pre,{children:e.jsx(r.code,{className:"language-typescript",children:`/**\r 198 * @fileoverview Payment service for ClassAct\r 199 * \r 200 * Features:\r 201 * - Payment verification\r 202 * - Transaction recording\r 203 * - Payment history\r 204 * \r 205 * @requires ../services/supabaseClient\r 206 * @requires ../utils/logger\r 207 */\r 208\r 209import { supabase } from '../services/supabaseClient';\r 210import { createLogger } from '../utils/logger';\r 211\r 212const logger = createLogger('PaymentService', true);\r 213\r 214// Record payment transaction\r 215export const recordPayment = async (paymentData) => {\r 216 try {\r 217 logger.log('Recording payment transaction:', paymentData);\r 218 \r 219 const { data, error } = await supabase\r 220 .from('payments')\r 221 .insert({\r 222 user_id: paymentData.userId,\r 223 event_id: paymentData.eventId,\r 224 amount: paymentData.amount,\r 225 transaction_id: paymentData.transactionId,\r 226 provider: 'paypal',\r 227 status: paymentData.status,\r 228 payment_details: paymentData.details\r 229 })\r 230 .select();\r 231 \r 232 if (error) {\r 233 logger.error('Error recording payment:', error);\r 234 throw error;\r 235 }\r 236 \r 237 return data[0];\r 238 } catch (error) {\r 239 logger.error('Unexpected error recording payment:', error);\r 240 throw error;\r 241 }\r 242};\r 243\r 244// Get payment history for a user\r 245export const getUserPayments = async (userId) => {\r 246 try {\r 247 logger.log('Fetching payment history for user:', userId);\r 248 \r 249 const { data, error } = await supabase\r 250 .from('payments')\r 251 .select(\`\r 252 *,\r 253 events (title, date)\r 254 \`)\r 255 .eq('user_id', userId)\r 256 .order('created_at', { ascending: false });\r 257 \r 258 if (error) {\r 259 logger.error('Error fetching payment history:', error);\r 260 throw error;\r 261 }\r 262 \r 263 return data;\r 264 } catch (error) {\r 265 logger.error('Unexpected error fetching payment history:', error);\r 266 throw error;\r 267 }\r 268};\r 269\r 270// Verify payment status\r 271export const verifyPayment = async (transactionId) => {\r 272 try {\r 273 logger.log('Verifying payment status for transaction:', transactionId);\r 274 \r 275 // In a real implementation, you would make an API call to PayPal\r 276 // to verify the transaction status\r 277 \r 278 const { data, error } = await supabase\r 279 .from('payments')\r 280 .select('*')\r 281 .eq('transaction_id', transactionId)\r 282 .single();\r 283 \r 284 if (error) {\r 285 logger.error('Error verifying payment:', error);\r 286 throw error;\r 287 }\r 288 \r 289 return {\r 290 verified: data.status === 'completed',\r 291 data\r 292 };\r 293 } catch (error) {\r 294 logger.error('Unexpected error verifying payment:', error);\r 295 throw error;\r 296 }\r 297}; 298`})}),` 299`,e.jsx(r.h2,{children:"Handling Payment Failures"}),` 300`,e.jsx(r.p,{children:"The application implements robust error handling for payment failures:"}),` 301`,e.jsx(r.pre,{children:e.jsx(r.code,{className:"language-tsx",children:`// Inside checkout component\r 302const handlePaymentError = (error) => {\r 303 logger.error('Payment failed:', error);\r 304 \r 305 // Show error notification to user\r 306 toast.error('Payment failed. Please try again.');\r 307 \r 308 // Log detailed error information\r 309 logger.error('Payment error details:', {\r 310 eventId,\r 311 userId: user.id,\r 312 errorMessage: error.message,\r 313 errorDetails: error\r 314 });\r 315 \r
316 // Update UI to show error state\r 317 setStatus('error');\r 318 setErrorMessage('We encountered an issue processing your payment. Please try again or use a different payment method.');\r 319}; 320`})}),` 321`,e.jsx(r.h2,{children:"Webhook Integration"}),` 322`,e.jsx(r.p,{children:"For production deployments, ClassAct implements PayPal webhooks to handle asynchronous payment events:"}),` 323`,e.jsx(r.pre,{children:e.jsx(r.code,{className:"language-typescript",children:`// Webhook handler (server-side implementation)\r 324export const handlePayPalWebhook = async (req, res) => {\r 325 try {\r 326 const { event_type, resource } = req.body;\r 327 \r 328 // Verify webhook signature (implementation details omitted)\r 329 \r 330 logger.log('Received PayPal webhook:', event_type);\r 331 \r 332 // Handle different event types\r 333 switch (event_type) {\r 334 case 'PAYMENT.CAPTURE.COMPLETED':\r 335 // Payment was successful\r 336 await handlePaymentSuccess(resource);\r 337 break;\r 338 \r 339 case 'PAYMENT.CAPTURE.DENIED':\r 340 // Payment was denied\r 341 await handlePaymentDenied(resource);\r 342 break;\r 343 \r 344 case 'PAYMENT.CAPTURE.REFUNDED':\r 345 // Payment was refunded\r 346 await handlePaymentRefund(resource);\r 347 break;\r 348 \r 349 default:\r 350 logger.log('Unhandled webhook event type:', event_type);\r 351 }\r 352 \r 353 res.status(200).send('Webhook received');\r 354 } catch (error) {\r 355 logger.error('Error handling PayPal webhook:', error);\r 356 res.status(400).send('Webhook Error');\r 357 }\r 358}; 359`})}),` 360`,e.jsx(r.h2,{children:"Testing PayPal Integration"}),` 361`,e.jsx(r.p,{children:"For development and testing, PayPal provides sandbox credentials:"}),` 362`,e.jsxs(r.ol,{children:[` 363`,e.jsx(r.li,{children:"Create a PayPal Developer account at developer.paypal.com"}),` 364`,e.jsx(r.li,{children:"Create sandbox business and personal accounts"}),` 365`,e.jsx(r.li,{children:"Use sandbox client ID in development environment"}),` 366`,e.jsx(r.li,{children:"Test transactions using the sandbox personal account"}),` 367`]}),` 368`,e.jsx(r.p,{children:"Testing can be done using these PayPal Sandbox test accounts:"}),` 369`,e.jsx(r.pre,{children:e.jsx(r.code,{children:`Sandbox Business Account: [email protected] / password123\r 370Sandbox Personal Account: [email protected] / password123 371`})}),` 372`,e.jsx(r.h2,{children:"Security Considerations"}),` 373`,e.jsxs(r.ol,{children:[` 374`,e.jsxs(r.li,{children:[` 375`,e.jsxs(r.p,{children:[e.jsx(r.strong,{children:"Client-side Security"}),":"]}),` 376`,e.jsxs(r.ul,{children:[` 377`,e.jsx(r.li,{children:"Never store sensitive payment information"}),` 378`,e.jsx(r.li,{children:"Use PayPal SDK for all payment operations"}),` 379`,e.jsx(r.li,{children:"Validate transaction IDs server-side"}),` 380`]}),` 381`]}),` 382`,e.jsxs(r.li,{children:[` 383`,e.jsxs(r.p,{children:[e.jsx(r.strong,{children:"Server-side Verification"}),":"]}),` 384`,e.jsxs(r.ul,{children:[` 385`,e.jsx(r.li,{children:"Always verify payment status before creating tickets"}),` 386`,e.jsx(r.li,{children:"Implement idempotency to prevent duplicate transactions"}),` 387`,e.jsx(r.li,{children:"Store payment records for audit purposes"}),` 388`]}),` 389`]}),` 390`,e.jsxs(r.li,{children:[` 391`,e.jsxs(r.p,{children:[e.jsx(r.strong,{children:"Error Handling"}),":"]}),` 392`,e.jsxs(r.ul,{children:[` 393`,e.jsx(r.li,{children:"Provide clear error messages to users"}),` 394`,e.jsx(r.li,{children:"Log detailed error information for troubleshooting"}),` 395`,e.jsx(r.li,{children:"Implement recovery mechanisms for interrupted payments"}),` 396`]}),` 397`]}),` 398`]}),` 399`,e.jsx(r.h2,{children:"Best Practices"}),` 400`,e.jsxs(r.ol,{children:[` 401`,e.jsx(r.li,{children:"Always use the PayPal sandbox for development and testing"}),` 402`,e.jsx(r.li,{children:"Implement proper error handling and logging"}),` 403`,e.jsx(r.li,{children:"Store transaction IDs with ticket records for reference"}),` 404`,e.jsx(r.li,{children:"Provide clear payment status updates to users"}),` 405`,e.jsx(r.li,{children:"Include contact information for payment issues"}),` 406`]})]})}function d(n={}){const{wrapper:r}=n.components||{};return r?e.jsx(r,{...n,children:e.jsx(t,{...n})}):t(n)}export{d as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.