import os from flask import Blueprint, render_template, request, jsonify, redirect, url_for, session, current_app from flask_login import login_required, current_user import requests import hashlib import hmac import json from datetime import datetime, timedelta import uuid from models.subscription import Subscription pricing_bp = Blueprint('pricing', __name__) # SSLCommerce configuration SSLCOMMERCE_STORE_ID = os.environ.get('SSLCOMMERCE_STORE_ID', 'your_store_id_here') SSLCOMMERCE_STORE_PASS = os.environ.get('SSLCOMMERCE_STORE_PASS', 'your_store_password_here') SSLCOMMERCE_API_URL = os.environ.get('SSLCOMMERCE_API_URL', 'https://sandbox.sslcommerz.com/gwprocess/v4/api.php') SSLCOMMERCE_VALIDATION_URL = os.environ.get('SSLCOMMERCE_VALIDATION_URL', 'https://sandbox.sslcommerz.com/validator/api/validationserverAPI.php') # Pricing plans PRICING_PLANS = { 'basic': { 'id': 'basic', 'name': 'Basic Plan', 'price': 0.00, # Free plan 'description': 'Perfect for individuals getting started', 'features': [ 'Convert up to 100 images per month', 'Basic math equation recognition', 'Email support', 'Standard processing speed' ], 'is_free': True }, 'pro': { 'id': 'pro', 'name': 'Professional Plan', 'price': 24.99, 'description': 'Ideal for professionals and researchers', 'features': [ 'Unlimited image conversions', 'Advanced math equation recognition', 'Handwritten table detection', 'Priority processing speed', '24/7 customer support', 'Early access to new features' ], 'is_free': False }, 'enterprise': { 'id': 'enterprise', 'name': 'Enterprise Plan', 'price': 49.99, 'description': 'For teams and organizations', 'features': [ 'Everything in Professional Plan', 'Team collaboration tools', 'Custom API access', 'Dedicated account manager', 'SLA guarantee', 'On-premise deployment option' ], 'is_free': False } } @pricing_bp.route('/pricing') @login_required def pricing(): """Display pricing plans""" return render_template('pricing.html', plans=PRICING_PLANS) @pricing_bp.route('/checkout/') @login_required def checkout(plan_id): """Display checkout page for a plan""" if plan_id not in PRICING_PLANS: return jsonify({'error': 'Invalid plan selected'}), 400 plan = PRICING_PLANS[plan_id] # If it's a free plan, activate it immediately if plan.get('is_free', False): # Calculate subscription dates (1 month from now) start_date = datetime.now() end_date = start_date + timedelta(days=30) # Update user's plan in database subscription = Subscription.create_or_update( user_id=current_user.id, plan_id=plan_id, start_date=start_date.isoformat(), end_date=end_date.isoformat(), is_active=True ) # Update current user object current_user.subscription = subscription.__dict__ if subscription else {} return render_template('payment_success.html', plan=plan) # For paid plans, generate unique transaction ID tran_id = str(uuid.uuid4()) # Store transaction info in session session['transaction'] = { 'tran_id': tran_id, 'plan_id': plan_id, 'user_id': current_user.id, 'amount': plan['price'], 'timestamp': datetime.now().isoformat() } return render_template('checkout.html', plan=plan, transaction=session['transaction']) @pricing_bp.route('/process-payment/', methods=['POST']) @login_required def process_payment(plan_id): """Process payment for a plan based on selected payment method""" if plan_id not in PRICING_PLANS: return jsonify({'error': 'Invalid plan selected'}), 400 plan = PRICING_PLANS[plan_id] payment_method = request.form.get('payment_method', 'sslcommerce') # If it's a free plan, activate it immediately if plan.get('is_free', False): # Calculate subscription dates (1 month from now) start_date = datetime.now() end_date = start_date + timedelta(days=30) # Update user's plan in database subscription = Subscription.create_or_update( user_id=current_user.id, plan_id=plan_id, start_date=start_date.isoformat(), end_date=end_date.isoformat(), is_active=True ) # Update current user object current_user.subscription = subscription.__dict__ if subscription else {} return render_template('payment_success.html', plan=plan) # Generate unique transaction ID tran_id = str(uuid.uuid4()) # Store transaction info in session session['transaction'] = { 'tran_id': tran_id, 'plan_id': plan_id, 'user_id': current_user.id, 'amount': plan['price'], 'timestamp': datetime.now().isoformat(), 'payment_method': payment_method } if payment_method == 'sslcommerce': # Process with SSLCommerce return process_sslcommerce_payment(plan, tran_id) else: # For other payment methods, return instructions return process_alternative_payment(plan, tran_id, payment_method) def process_sslcommerce_payment(plan, tran_id): """Process payment through SSLCommerce""" # Prepare data for SSLCommerce post_data = { 'store_id': SSLCOMMERCE_STORE_ID, 'store_passwd': SSLCOMMERCE_STORE_PASS, 'total_amount': plan['price'], 'currency': 'USD', 'tran_id': tran_id, 'success_url': url_for('pricing.payment_success', _external=True), 'fail_url': url_for('pricing.payment_failed', _external=True), 'cancel_url': url_for('pricing.payment_cancelled', _external=True), 'ipn_url': url_for('pricing.payment_ipn', _external=True), 'cus_name': current_user.name, 'cus_email': current_user.email, 'cus_add1': 'Dhaka', 'cus_add2': 'Dhaka', 'cus_city': 'Dhaka', 'cus_state': 'Dhaka', 'cus_postcode': '1000', 'cus_country': 'Bangladesh', 'cus_phone': '01711111111', 'cus_fax': '01711111111', 'ship_name': current_user.name, 'ship_add1': 'Dhaka', 'ship_add2': 'Dhaka', 'ship_city': 'Dhaka', 'ship_state': 'Dhaka', 'ship_postcode': '1000', 'ship_country': 'Bangladesh', 'product_name': plan['name'], 'product_category': 'Software', 'product_profile': 'non-physical-goods', 'multi_card_name': '', 'value_a': plan['id'], 'value_b': current_user.id, 'value_c': '', 'value_d': '' } try: # Make request to SSLCommerce response = requests.post(SSLCOMMERCE_API_URL, data=post_data) response_data = response.json() if response_data.get('status') == 'SUCCESS': # Redirect to payment gateway return redirect(response_data['GatewayPageURL']) else: return jsonify({'error': 'Payment gateway error', 'details': response_data}), 500 except Exception as e: return jsonify({'error': 'Failed to initiate payment', 'details': str(e)}), 500 def process_alternative_payment(plan, tran_id, payment_method): """Process payment through alternative methods (Nagad, Rocket, Bank)""" # For alternative payment methods, we return instructions # In a real implementation, you would integrate with their APIs or send notifications instructions = { 'nagad': { 'title': 'Nagad Payment Instructions', 'steps': [ 'Dial *167# from your Nagad registered mobile number', 'Select "Send Money"', 'Enter the merchant number: 017XXXXXXXX', f'Enter the amount: {plan["price"]} BDT', f'Enter reference: {tran_id}', 'Confirm the transaction with your Nagad PIN', 'You will receive a confirmation SMS' ], 'confirmation': 'After completing the payment, please click the confirmation button below.' }, 'rocket': { 'title': 'Rocket Payment Instructions', 'steps': [ 'Dial *322# from your Rocket registered mobile number', 'Select "Send Money"', 'Enter the merchant number: 017XXXXXXXX', f'Enter the amount: {plan["price"]} BDT', f'Enter reference: {tran_id}', 'Confirm the transaction with your Rocket PIN', 'You will receive a confirmation SMS' ], 'confirmation': 'After completing the payment, please click the confirmation button below.' }, 'bank': { 'title': 'Bank Transfer Instructions', 'steps': [ f'Transfer {plan["price"]} BDT to the following bank account:', 'Bank: ABC Bank Ltd.', 'Account Name: TexLab Services', 'Account Number: 1234567890', 'Routing Number: 123456789', f'Include this reference in the transfer description: {tran_id}', 'Email the transaction receipt to payments@texlab.com' ], 'confirmation': 'After completing the payment, please click the confirmation button below.' } } instruction_data = instructions.get(payment_method, {}) return render_template('manual_payment.html', instructions=instruction_data, plan=plan, tran_id=tran_id, method=payment_method, plan_id=plan['id']) @pricing_bp.route('/payment/manual-confirm') @login_required def manual_payment_confirm(): """Confirm manual payment (Nagad, Rocket, Bank)""" payment_method = request.args.get('method') plan_id = request.args.get('plan_id') if not payment_method or not plan_id: return redirect(url_for('pricing.pricing')) if 'transaction' not in session: return redirect(url_for('pricing.pricing')) transaction = session['transaction'] # For demo purposes, we'll assume the payment is successful # In a real implementation, you would verify the payment with the payment provider # Calculate subscription dates (1 month from now) start_date = datetime.now() end_date = start_date + timedelta(days=30) # Update user's plan in database subscription = Subscription.create_or_update( user_id=transaction['user_id'], plan_id=transaction['plan_id'], start_date=start_date.isoformat(), end_date=end_date.isoformat(), is_active=True ) # Update current user object current_user.subscription = subscription.__dict__ if subscription else {} # Clear transaction from session session.pop('transaction', None) return render_template('payment_success.html', plan=PRICING_PLANS[transaction['plan_id']]) @pricing_bp.route('/payment/success') @login_required def payment_success(): """Handle successful payment""" # Verify the payment with SSLCommerce if 'transaction' not in session: return redirect(url_for('pricing.pricing')) transaction = session['transaction'] # Get parameters from SSLCommerce params = request.args.to_dict() # Verify payment with SSLCommerce if verify_payment(params): # Calculate subscription dates (1 month from now) start_date = datetime.now() end_date = start_date + timedelta(days=30) # Update user's plan in database subscription = Subscription.create_or_update( user_id=transaction['user_id'], plan_id=transaction['plan_id'], start_date=start_date.isoformat(), end_date=end_date.isoformat(), is_active=True ) # Update current user object current_user.subscription = subscription.__dict__ if subscription else {} # Clear transaction from session session.pop('transaction', None) return render_template('payment_success.html', plan=PRICING_PLANS[transaction['plan_id']]) else: return redirect(url_for('pricing.payment_failed')) @pricing_bp.route('/payment/failed') @login_required def payment_failed(): """Handle failed payment""" session.pop('transaction', None) return render_template('payment_failed.html') @pricing_bp.route('/payment/cancelled') @login_required def payment_cancelled(): """Handle cancelled payment""" session.pop('transaction', None) return render_template('payment_cancelled.html') @pricing_bp.route('/payment/ipn', methods=['POST']) def payment_ipn(): """Handle IPN (Instant Payment Notification) from SSLCommerce""" # This endpoint should handle payment notifications from SSLCommerce # In a production environment, you would update your database here data = request.form.to_dict() # Verify the payment notification if verify_payment(data): # Update database with payment status # This is where you would update your user's subscription status pass return jsonify({'status': 'success'}) def verify_payment(params): """Verify payment with SSLCommerce""" if 'val_id' not in params: return False validation_url = f"{SSLCOMMERCE_VALIDATION_URL}?val_id={params['val_id']}&store_id={SSLCOMMERCE_STORE_ID}&store_passwd={SSLCOMMERCE_STORE_PASS}&v=1&format=json" try: response = requests.get(validation_url) data = response.json() if data.get('status') == 'VALID' or data.get('status') == 'VALIDATED': return True else: return False except Exception: return False def update_user_plan(user_id, plan_id): """Update user's subscription plan in database""" # Calculate subscription dates (1 month from now) start_date = datetime.now() end_date = start_date + timedelta(days=30) # Update user's plan in database subscription = Subscription.create_or_update( user_id=user_id, plan_id=plan_id, start_date=start_date.isoformat(), end_date=end_date.isoformat(), is_active=True ) return subscription @pricing_bp.route('/current-plan') @login_required def current_plan(): """Get current user's plan""" subscription = Subscription.get_by_user_id(current_user.id) if subscription and subscription.is_active: return jsonify({ 'plan': subscription.plan_id, 'expires': subscription.end_date }) else: return jsonify({ 'plan': 'basic', # Default to basic plan 'expires': None })