texlab / controller /pricing_controller.py
syk101's picture
Upload 239 files
d05a9d0 verified
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/<plan_id>')
@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/<plan_id>', 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
})