forked from ComputerTech/aprhodite
Fix #3: Remove client-exploitable payment endpoint
- Payment endpoint no longer uses @_require_auth (not client-callable) - Identifies user from webhook payload user_id instead of client JWT - Removed hardcoded payment secret from chat.js - Client now shows placeholder message directing to admin - Webhook secret + user_id must come from payment provider server
This commit is contained in:
parent
8da91ebf70
commit
be3503b31b
16
routes.py
16
routes.py
|
|
@ -291,12 +291,12 @@ def ai_message():
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
@api.route("/payment/success", methods=["POST"])
|
@api.route("/payment/success", methods=["POST"])
|
||||||
@_require_auth
|
|
||||||
def payment_success():
|
def payment_success():
|
||||||
"""
|
"""
|
||||||
Validate a payment webhook and flip user.has_ai_access.
|
Server-side payment webhook – NOT callable by clients.
|
||||||
|
|
||||||
Expected body: { "secret": "<PAYMENT_SECRET>" }
|
Validates the webhook secret and unlocks AI access for the user
|
||||||
|
identified by the 'user_id' field in the JSON body.
|
||||||
|
|
||||||
For Stripe production: replace the secret comparison with
|
For Stripe production: replace the secret comparison with
|
||||||
stripe.Webhook.construct_event() using the raw request body and
|
stripe.Webhook.construct_event() using the raw request body and
|
||||||
|
|
@ -312,7 +312,15 @@ def payment_success():
|
||||||
):
|
):
|
||||||
return jsonify({"error": "Invalid or missing payment secret"}), 403
|
return jsonify({"error": "Invalid or missing payment secret"}), 403
|
||||||
|
|
||||||
user = g.current_user
|
# Identify the user from the webhook payload (NOT from client auth)
|
||||||
|
user_id = data.get("user_id")
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({"error": "Missing user_id in webhook payload"}), 400
|
||||||
|
|
||||||
|
user = db.session.get(User, user_id)
|
||||||
|
if not user:
|
||||||
|
return jsonify({"error": "User not found"}), 404
|
||||||
|
|
||||||
if not user.has_ai_access:
|
if not user.has_ai_access:
|
||||||
user.has_ai_access = True
|
user.has_ai_access = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
|
||||||
|
|
@ -546,30 +546,10 @@ $("tab-lobby").onclick = () => switchTab("lobby");
|
||||||
|
|
||||||
$("close-paywall").onclick = () => paywallModal.classList.add("hidden");
|
$("close-paywall").onclick = () => paywallModal.classList.add("hidden");
|
||||||
$("unlock-btn").onclick = async () => {
|
$("unlock-btn").onclick = async () => {
|
||||||
// Generate dummy secret for the stub endpoint
|
// In production, this redirects to a real payment gateway (Stripe Checkout).
|
||||||
// In production, this would redirect to a real payment gateway (Stripe)
|
// The server-side webhook will unlock AI access after payment confirmation.
|
||||||
const secret = "change-me-payment-webhook-secret";
|
// For now, show a placeholder message.
|
||||||
const token = localStorage.getItem("sexychat_token");
|
alert("Payment integration coming soon. Contact the administrator to unlock Violet.");
|
||||||
|
|
||||||
try {
|
|
||||||
const resp = await fetch("/api/payment/success", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Authorization": `Bearer ${token}`
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ secret })
|
|
||||||
});
|
|
||||||
const res = await resp.json();
|
|
||||||
if (res.status === "ok") {
|
|
||||||
// socket event should handle UI unlock, but we can optimistically update
|
|
||||||
state.hasAiAccess = true;
|
|
||||||
updateVioletBadge();
|
|
||||||
paywallModal.classList.add("hidden");
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
alert("Payment simulation failed.");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
logoutBtn.onclick = () => {
|
logoutBtn.onclick = () => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue