TutorBot integrates with Stripe for secure online payments, handling lesson bookings, trial sessions, and urgent appointments. The system creates payment links, processes webhooks, and manages payment status in Chatwoot conversations.
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β TutorBot β β Stripe β β Chatwoot β
β β β β β β
β - Payment βββββΆβ - Checkout βββββΆβ - Status β
β - Links β β - Sessions β β - Updates β
β - Webhooks ββββββ - Webhooks ββββββ - Labels β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
create_payment_link() (Line 2033)def create_payment_link(segment, minutes, order_id, conversation_id, student_name, service, audience, program):
"""Create Stripe payment link"""
try:
# Determine price based on segment and duration
if segment == "urgent":
price_id = WEEKEND_PRICE_ID_60 if minutes == 60 else WEEKEND_PRICE_ID_90
else:
price_id = STANDARD_PRICE_ID_60 if minutes == 60 else STANDARD_PRICE_ID_90
# Create Stripe checkout session
session = stripe.checkout.Session.create(
payment_method_types=['card', 'ideal'],
line_items=[{
'price': price_id,
'quantity': 1,
}],
mode='payment',
success_url=f"https://crm.stephenadei.nl/conversations/{conversation_id}",
cancel_url=f"https://crm.stephenadei.nl/conversations/{conversation_id}",
metadata={
'order_id': order_id,
'conversation_id': conversation_id,
'student_name': student_name,
'service': service,
'audience': audience,
'program': program,
'minutes': minutes
}
)
return session.url
except Exception as e:
print(f"β Error creating payment link: {e}")
return None
create_payment_request() (Line 4918)def create_payment_request(cid, contact_id, lang):
"""Create payment request"""
# Get contact and conversation attributes
contact_attrs = get_contact_attrs(contact_id)
conv_attrs = get_conv_attrs(cid)
# Generate order ID
order_id = f"order_{int(datetime.now().timestamp())}"
# Create payment link
payment_link = create_payment_link(
segment=contact_attrs.get("segment", "standard"),
minutes=conv_attrs.get("lesson_minutes", 60),
order_id=order_id,
conversation_id=cid,
student_name=contact_attrs.get("name", "Student"),
service=conv_attrs.get("service", "tutoring"),
audience=conv_attrs.get("audience", "student"),
program=conv_attrs.get("program", "general")
)
if payment_link:
# Update conversation attributes
set_conv_attrs(cid, {
"order_id": order_id,
"payment_status": "pending",
"payment_link": payment_link
})
# Add payment labels
add_conv_labels(cid, ["payment:pending"])
# Send payment link
send_text_with_duplicate_check(cid, t("payment_link", lang, link=payment_link))
return True
return False
stripe_webhook() (Line 4950)@app.post("/webhook/payments")
def stripe_webhook():
"""Handle Stripe webhook events"""
payload = request.get_data()
signature = request.headers.get('Stripe-Signature')
if not verify_stripe_webhook(payload, signature):
return "Unauthorized", 401
event = stripe.Webhook.construct_event(
payload, signature, STRIPE_WEBHOOK_SECRET
)
event_type = event['type']
if event_type == "checkout.session.completed":
handle_payment_success(event)
elif event_type == "payment_intent.succeeded":
handle_payment_success(event)
return "OK", 200
handle_payment_success() (Line 4969)def handle_payment_success(event):
"""Handle successful payment"""
try:
# Extract metadata
metadata = event['data']['object']['metadata']
conversation_id = int(metadata.get('conversation_id'))
order_id = metadata.get('order_id')
student_name = metadata.get('student_name', 'Student')
# Update conversation attributes
set_conv_attrs(conversation_id, {
"payment_status": "paid",
"payment_date": datetime.now().isoformat(),
"has_paid_lesson": True
})
# Update contact attributes
contact_id = get_contact_id_from_conversation(conversation_id)
if contact_id:
set_contact_attrs(contact_id, {
"has_paid_lesson": True,
"customer_since": datetime.now().isoformat()
})
# Update conversation labels
remove_conv_labels(conversation_id, ["payment:pending"])
add_conv_labels(conversation_id, ["payment:paid", "status:booked"])
# Add payment note
payment_note = f"β
Payment received for {student_name} - Order: {order_id}"
send_text_with_duplicate_check(conversation_id, payment_note)
print(f"β
Payment processed: {order_id} for conversation {conversation_id}")
except Exception as e:
print(f"β Error processing payment success: {e}")
verify_stripe_webhook() (Line 2049)def verify_stripe_webhook(payload, signature):
"""Verify Stripe webhook signature"""
if not STRIPE_WEBHOOK_SECRET:
print("β οΈ No Stripe webhook secret configured")
return False
try:
event = stripe.Webhook.construct_event(
payload,
signature,
STRIPE_WEBHOOK_SECRET
)
return True
except Exception as e:
print(f"β Stripe webhook verification failed: {e}")
return False
# Payment-related attributes
{
"order_id": "order_1234567890",
"payment_status": "pending|paid|failed",
"payment_date": "2025-01-15T10:30:00",
"payment_link": "https://checkout.stripe.com/...",
"lesson_minutes": 60,
"service": "tutoring",
"audience": "student",
"program": "general"
}
# Customer-related attributes
{
"has_paid_lesson": True,
"customer_since": "2025-01-15T10:30:00",
"segment": "standard|urgent|premium"
}
# Payment status labels
["payment:pending"] # Payment link sent
["payment:paid"] # Payment received
["status:booked"] # Lesson confirmed
trial_lessondefinitiefurgentweekend# Stripe Configuration
STRIPE_WEBHOOK_SECRET="whsec_your_webhook_secret"
STANDARD_PRICE_ID_60="price_standard_60min"
STANDARD_PRICE_ID_90="price_standard_90min"
WEEKEND_PRICE_ID_60="price_weekend_60min"
WEEKEND_PRICE_ID_90="price_weekend_90min"
# Standard Rates (Weekdays)
STANDARD_PRICE_ID_60 = "price_1ABC123..." # β¬60/hour
STANDARD_PRICE_ID_90 = "price_1DEF456..." # β¬90/1.5hour
# Weekend Rates
WEEKEND_PRICE_ID_60 = "price_1GHI789..." # β¬75/hour
WEEKEND_PRICE_ID_90 = "price_1JKL012..." # β¬112.50/1.5hour
Payment Link Creation Failure
try:
payment_link = create_payment_link(...)
if not payment_link:
send_text_with_duplicate_check(cid, "β Payment system temporarily unavailable")
except Exception as e:
print(f"β Payment link creation failed: {e}")
Webhook Verification Failure
if not verify_stripe_webhook(payload, signature):
print("β Invalid webhook signature")
return "Unauthorized", 401
Payment Processing Error
try:
handle_payment_success(event)
except Exception as e:
print(f"β Payment processing failed: {e}")
# Send notification to admin
Failed Payment Links
Webhook Failures
Payment Status Mismatch
π‘ Tip: Always test payment flows thoroughly in Stripe test mode before going live. Monitor webhook delivery and payment processing in production.