diff --git a/scripts/autoload/donation_service.gd b/scripts/autoload/donation_service.gd index c0b7308..c7c3fbd 100644 --- a/scripts/autoload/donation_service.gd +++ b/scripts/autoload/donation_service.gd @@ -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 = "" diff --git a/scripts/ui/donation_screen.gd b/scripts/ui/donation_screen.gd index 0b8a6c2..98c6e4d 100644 --- a/scripts/ui/donation_screen.gd +++ b/scripts/ui/donation_screen.gd @@ -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: diff --git a/web/donation-canceled.html b/web/donation-canceled.html index f011e45..3745740 100644 --- a/web/donation-canceled.html +++ b/web/donation-canceled.html @@ -15,5 +15,24 @@ -
RanaZe

Checkout canceled

No contribution was completed.

Return to RanaZe
+
RanaZe

Checkout canceled

No contribution was completed. Returning you to the app...

Return to RanaZe
+ diff --git a/web/donation-complete.html b/web/donation-complete.html index 6edc761..594d764 100644 --- a/web/donation-complete.html +++ b/web/donation-complete.html @@ -15,5 +15,24 @@ -
RanaZe

Thank you for supporting RanaZe

Your checkout is complete.

Stripe will send your receipt by email.

Return to RanaZe
+
RanaZe

Thank you for supporting RanaZe

Your checkout is complete.

Stripe will send your receipt by email. Returning you to the app...

Return to RanaZe
+