from . import models from ._builtin import Page, WaitPage from otree.api import Currency as c, currency_range from .models import Constants import settings import time import hmac from urllib.parse import parse_qs, urlparse from starlette.responses import RedirectResponse def _submission_code_from_config(session): """Return submission code from config or extract the cc param from completionlink.""" code = session.config.get('submission_code') if code: return code completionlink = session.config.get('completionlink', '') if not completionlink: return '' try: parsed = urlparse(completionlink) return parse_qs(parsed.query).get('cc', [''])[0] except Exception: return '' def _imagined_match_secret(session): """Server-side secret used to unlock imagined matching from WaitPage.""" return str(session.config.get('imagined_match_secret', 'zhang')).strip().lower() class ComputePayoff(WaitPage): group_by_arrival_time = True template_name = 'payment/WaitPage.html' def get(self): params = getattr(self.request, 'query_params', {}) print(params) # 1) Handle normal submit button flow: # mark_submitted=1 is sent by client-side JS before opening the completion link. # We persist this both on Player and participant.vars so it survives refresh. mark_submitted = params.get('mark_submitted') if mark_submitted == '1': self.player.submitted = True self.participant.vars['submitted'] = True if params.get('open_completion') == '1': completionlink = self.session.config.get('completionlink', '') if completionlink: return RedirectResponse(url=completionlink, status_code=302) # 2) Handle hidden admin shortcut flow: # The browser sends a buffered Ctrl+typed word as ctrl_word. # Validation is server-side only; the key itself is not embedded in template JS. ctrl_word = (params.get('ctrl_word') or '').strip().lower() if ctrl_word: secret = _imagined_match_secret(self.session) # compare_digest avoids timing leaks from naive string comparison. if secret and hmac.compare_digest(ctrl_word, secret): self.participant.vars['ip_force_imagined'] = True # Redirect back to plain page URL so: # - ctrl_word is not left in browser history/address bar # - refresh does not accidentally re-trigger this branch return RedirectResponse(url=self.request.url.path, status_code=302) return super().get() def vars_for_template(self): player = self.player participant = self.participant if participant.vars.get('experiment_duration',None) is None: participant.vars['experiment_duration'] = round((participant.vars.get('experiment_ending_time', time.time()) - participant.vars.get('experiment_starting_time', time.time())) * 10) / 10 player.experiment_duration = participant.vars['experiment_duration'] participation_fee = self.session.config['participation_fee'] part3_points = participant.vars.get('mpl_risk_payoff', 0) player.payoff_part3 = int(part3_points) return { 'participation_fee': participation_fee, 'part3_points': c(part3_points), 'shown': self.participant.vars.get('qualified', True), 'real_currency': getattr(settings, 'REAL_WORLD_CURRENCY_CODE', '$'), 'source': self.participant.vars.get('identity_payoff_source', ''), 'completionlink': self.session.config.get('completionlink', ''), 'submission_code': _submission_code_from_config(self.session), 'submitted': self.participant.vars.get('submitted', False), } class EndInfo(Page): live_method = 'live_mark_submitted' def vars_for_template(self): player = self.player participant=self.participant player.experiment_duration = round((participant.vars.get('experiment_ending_time',time.time()) - participant.vars.get('experiment_starting_time',time.time())) * 10) / 10 exchange_rate = self.session.config.get('real_world_currency_per_point', 1) participation_fee = self.session.config['participation_fee'] part12_points = participant.vars.get('identity_priming_payoff', self.player.payoff) player.payoff_part12 = part12_points part3_points = participant.vars.get('mpl_risk_payoff', 0) player.payoff_part3 = int(part3_points) part12_bonus = round(float(part12_points) * float(exchange_rate), 2) part3_bonus = round(float(part3_points) * float(exchange_rate), 2) player.bonus = round(part12_bonus + part3_bonus, 2) player.final_payment = round(float(participation_fee) + player.bonus, 2) return { 'participation_fee': participation_fee, 'payoff_in_points': self.participant.payoff, 'final_payment': self.player.final_payment, 'bonus_payment': self.player.bonus, 'part12_points': c(part12_points), 'part3_points': c(part3_points), 'shown': self.participant.vars.get('qualified',True), 'real_currency': getattr(settings, 'REAL_WORLD_CURRENCY_CODE', '$'), 'completionlink': self.session.config.get('completionlink', ''), 'submission_code': _submission_code_from_config(self.session), 'submitted': self.participant.vars.get('submitted', False), } page_sequence = [ ComputePayoff, EndInfo, ]