Keep Stripe checkout in a popup

This commit is contained in:
hz
2026-07-20 16:42:17 -05:00
parent d142da2f78
commit 2dd4fa6420
4 changed files with 75 additions and 4 deletions
+34 -1
View File
@@ -5,6 +5,7 @@ const DEFAULT_URL := "https://s3q.ranaze.com/api/donations"
const REQUEST_TIMEOUT_SECONDS := 30.0
const POLL_INTERVAL_SECONDS := 3.0
const POLL_LIMIT := 200
const CHECKOUT_STORAGE_KEY := "ranaze_stripe_checkout_url"
signal checkout_started(donation_id: String, checkout_url: String)
signal donation_status_changed(status: String, donation: Dictionary)
@@ -19,6 +20,7 @@ var active_donation_id := ""
var poll_count := 0
var checkout_pending := false
var status_pending := false
var checkout_popup_opened := false
func _ready() -> void:
@@ -46,6 +48,9 @@ func begin_checkout(amount_cents: int) -> void:
if not AuthState.can_send_authenticated_request():
donation_failed.emit("Log in before making a contribution.")
return
if OS.has_feature("web") and not _prepare_browser_window():
donation_failed.emit("Stripe checkout needs a popup. Allow popups for this site, then try again.")
return
checkout_pending = true
request_token = AuthState.access_token
var headers := PackedStringArray([
@@ -57,6 +62,7 @@ func begin_checkout(amount_cents: int) -> void:
JSON.stringify({"amountCents": amount_cents}))
if error != OK:
checkout_pending = false
_close_browser_window()
donation_failed.emit("The secure checkout request could not be started.")
@@ -80,15 +86,18 @@ func _on_checkout_completed(result: int, response_code: int, _headers: PackedStr
if response_code == 401:
AuthState.handle_unauthorized(request_token)
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
_close_browser_window()
donation_failed.emit(_failure_message("Checkout could not be started.", result, response_code, body))
return
var parsed: Variant = JSON.parse_string(body.get_string_from_utf8())
if typeof(parsed) != TYPE_DICTIONARY:
_close_browser_window()
donation_failed.emit("The donation service returned an invalid response.")
return
var donation_id := str(parsed.get("donationId", ""))
var checkout_url := str(parsed.get("checkoutUrl", ""))
if donation_id.is_empty() or not checkout_url.begins_with("https://"):
_close_browser_window()
donation_failed.emit("The donation service returned an invalid checkout link.")
return
active_donation_id = donation_id
@@ -129,15 +138,38 @@ func _on_status_completed(result: int, response_code: int, _headers: PackedStrin
donation_status_changed.emit(status, parsed)
if status in ["paid", "failed", "expired"]:
stop_polling()
_close_browser_window()
func _open_checkout_url(url: String) -> void:
if OS.has_feature("web"):
JavaScriptBridge.eval("window.location.assign(%s);" % JSON.stringify(url), true)
JavaScriptBridge.eval(
"var ranazeStripeUrl = %s; window.localStorage.setItem('%s', ranazeStripeUrl); if (window.ranazeStripeWindow && !window.ranazeStripeWindow.closed) { try { window.ranazeStripeWindow.location.replace(ranazeStripeUrl); } catch (_) {} window.ranazeStripeWindow.focus(); } else { var ranazeStripePopup = window.open(ranazeStripeUrl, 'ranaze_stripe'); if (ranazeStripePopup) { window.ranazeStripeWindow = ranazeStripePopup; ranazeStripePopup.focus(); } else { window.location.assign(ranazeStripeUrl); } }" % [JSON.stringify(url), CHECKOUT_STORAGE_KEY],
true
)
else:
OS.shell_open(url)
func _prepare_browser_window() -> bool:
var popup_result: Variant = JavaScriptBridge.eval(
"window.localStorage.removeItem('%s'); var ranazeStripeBridge = new URL('/external-redirect.html?key=%s&provider=Stripe', window.location.origin).href; window.ranazeStripeWindow = window.open(ranazeStripeBridge, 'ranaze_stripe', 'popup=yes,width=620,height=760,resizable=yes,scrollbars=yes'); if (window.ranazeStripeWindow) { window.ranazeStripeWindow.blur(); window.focus(); } window.ranazeStripeWindow !== null;" % [CHECKOUT_STORAGE_KEY, CHECKOUT_STORAGE_KEY],
true
)
checkout_popup_opened = bool(popup_result)
return checkout_popup_opened
func _close_browser_window() -> void:
if not OS.has_feature("web") or not checkout_popup_opened:
return
JavaScriptBridge.eval(
"window.localStorage.removeItem('%s'); if (window.ranazeStripeWindow && !window.ranazeStripeWindow.closed) { window.ranazeStripeWindow.close(); } window.ranazeStripeWindow = null; window.focus();" % CHECKOUT_STORAGE_KEY,
true
)
checkout_popup_opened = false
func _failure_message(prefix: String, result: int, response_code: int, body: PackedByteArray) -> String:
if result != HTTPRequest.RESULT_SUCCESS and response_code == 0:
return "%s Check your connection and try again." % prefix
@@ -152,4 +184,5 @@ func _failure_message(prefix: String, result: int, response_code: int, body: Pac
func _on_auth_changed() -> void:
if not AuthState.is_logged_in:
stop_polling()
_close_browser_window()
active_donation_id = ""
+1 -1
View File
@@ -223,7 +223,7 @@ func _on_donate_pressed() -> void:
func _on_checkout_started(_donation_id: String, _checkout_url: String) -> void:
donate_button.disabled = false
_set_validation_state("Checkout opened in your browser. This page will confirm when Stripe completes the payment.")
_set_validation_state("Complete payment in the Stripe window. This page will confirm when the payment finishes.")
func _on_donation_status_changed(status: String, _donation: Dictionary) -> void:
+20 -1
View File
@@ -15,5 +15,24 @@
</style>
</head>
<script>try{document.documentElement.dataset.theme=localStorage.getItem('medmind-color-scheme')||''}catch(e){}</script>
<body><main><img src="/ranaze-loader-icon-v1.png" alt="RanaZe" width="320" height="270"><h1>Checkout canceled</h1><p>No contribution was completed.</p><a href="/">Return to RanaZe</a></main></body>
<body><main><img src="/ranaze-loader-icon-v1.png" alt="RanaZe" width="320" height="270"><h1>Checkout canceled</h1><p>No contribution was completed. Returning you to the app...</p><a id="return-to-app" href="/">Return to RanaZe</a></main>
<script>
(() => {
const returnToApp = () => {
if (window.opener && !window.opener.closed) {
window.opener.focus();
window.close();
return;
}
window.location.assign('/');
};
document.getElementById('return-to-app').addEventListener('click', (event) => {
if (window.opener && !window.opener.closed) {
event.preventDefault();
returnToApp();
}
});
window.setTimeout(returnToApp, 1200);
})();
</script></body>
</html>
+20 -1
View File
@@ -15,5 +15,24 @@
</style>
</head>
<script>try{document.documentElement.dataset.theme=localStorage.getItem('medmind-color-scheme')||''}catch(e){}</script>
<body><main><img src="/ranaze-loader-icon-v1.png" alt="RanaZe" width="320" height="270"><h1>Thank you for supporting RanaZe</h1><p class="status">Your checkout is complete.</p><p>Stripe will send your receipt by email.</p><a href="/">Return to RanaZe</a></main></body>
<body><main><img src="/ranaze-loader-icon-v1.png" alt="RanaZe" width="320" height="270"><h1>Thank you for supporting RanaZe</h1><p class="status">Your checkout is complete.</p><p>Stripe will send your receipt by email. Returning you to the app...</p><a id="return-to-app" href="/">Return to RanaZe</a></main>
<script>
(() => {
const returnToApp = () => {
if (window.opener && !window.opener.closed) {
window.opener.focus();
window.close();
return;
}
window.location.assign('/');
};
document.getElementById('return-to-app').addEventListener('click', (event) => {
if (window.opener && !window.opener.closed) {
event.preventDefault();
returnToApp();
}
});
window.setTimeout(returnToApp, 1800);
})();
</script></body>
</html>