Sumathi Iyengar
Sumathi Iyengar

Reputation: 88

Countdown timer not updating in tkinter

I am trying to crate a payment application using python and tkinter. There is a "main" program which gets the details like card number, cvv etc. from the user and sends it to another program which is the payment gateway.

Thus is not a real payment gateway. It simply cross-checks the card number, cvv etc. with a database.

I have not include the main program in my code, because I can assure you that I have checked if the values have been successfully transferred from the main program, and it does.

The below program is a little long, sorry, but I will try to focus on the problem.

Here's the "payment gateway":

import smtplib, ssl
from tkinter import*
from tkinter import messagebox
import mysql.connector as mc
from random import randint
import time
global root

def visa_card_payment(cardnumber, cvv, name, expiry, premium, product):
    global root  

mycon=mc.connect(host="localhost",user="root",passwd="1234",database="visa",charset='utf8')
cursor=mycon.cursor()

todo1="select registered_email from all_data where card_no=%s and cvv='%s' and expiry='%s'"%(cardnumber,cvv,expiry)

cursor.execute(todo1)
data=cursor.fetchall()

if cursor.rowcount==1:
    
    cursor.execute(todo1)
    data=cursor.fetchone()
    print(data[0])
    emailid=data[0]
    
    def countdown(t):

        global root


        def payu(entered_otp):
            global root
            global otp
            global status
            global attempts

            if otp==entered_otp:

                status="true"

                root.destroy()
                root=Tk()
                root.title("Payment Successful")
                root.geometry("200x200")

                succ_label=Label("Payment Successful")
                succ_label.pack()

            elif otp!=entered_otp:
                if attempts!=3:
                    messagebox.showwarning(root,"Incorrect OTP. You have %s tries left."%(3-attempts))
                    attempts=+1

                elif attempts==3:
                    messagebox.showwarning(root,"Authentication Failed")
                    root.destroy()

        global root                    
        global attempts
        global status
        global otp

        attempts=1
        otp=randint(99999,1000000)
        status="false"

        port = 465
        smtp_server = "smtp.gmail.com"
        sender_email = "[email protected]"
        receiver_email =str(emailid)
        password = "visaandezpay2003"
        message = """\Subject:

        To %s,
        Dear Visa Cardholder,
        Your One Time Password (OTP) for payment to Tirth Insurance 4 Life towards payment of '%s' for policy '%s' is: %s
        Your OTP will be active for 5 minutes.
        Please do not share this OTP with anyone else."""%(name,premium,product,otp)

        context = ssl.create_default_context()
        with smtplib.SMTP_SSL(smtp_server, port, context=context) as server:
            server.login(sender_email, password)
            server.sendmail(sender_email, receiver_email, message)
        
        root=Tk()
        root.title("EzPay Payment Gateway")
        root.geometry("200x200")
        otp_label=Label(root,text="Enter OTP:")
        otp_entry=Entry(root,width=30,borderwidth=3)
        otp_entry.insert(0,0)
        countdown_label1=Label(root,text="OTP active for:")
        countdown_label2=Label(root,text="05:00")
        submit_button=Button(root,text="Submit",command=lambda:payu(int(otp_entry.get())))

        otp_label.grid(row=0,column=0)
        otp_entry.grid(row=0,column=1)
        countdown_label1.grid(row=1,column=0)
        countdown_label2.grid(row=1,column=1)
        submit_button.grid(row=2,column=1)
        
        root.mainloop()
        
        while t:
            
            mins, secs = divmod(t, 60)
            timeformat='{:02d}:{:02d}'.format(mins, secs)
            countdown_label2.grid_forget()
            countdown_label2=Label(root,text=timeformat)
            countdown_label2.grid(row=1,column=1)
            time.sleep(1)
            t=-1
        if status=="true":
            pass

        elif status=="false":
            
            otp=1000000
            messagebox.showwarning(root,"Authentication Failed")
            root.destroy()
        
    
    countdown(300)

The main program imports this program and runs the visa_card_payment function and also provides the required values.

Starting off, the program runs a query, sees if the rowcount is 1, gets the email ID of the desired, and enters the countdown(t) with t=300

Then it generates a random otp, sends an email for that, and creates a new window wherein asks the user to enter the otp. This is where the trouble begins.

I have a countdown_label2 which initially has 5:00 has text. Then I have a while(t) function to countdown for 5 minutes.

Each passing second it has to forget the location of previous countdown_label2, create a new label with text one second less than that and place it back again.

It will run the program for 5 minutes, within which if the correct otp is entered and submitted, then it will show it is successful, otherwise it will repeat for two more attempts, and that too within the countdown

Email is received successfully, but the countdown_label2 doesn't update, it remains at 5:00.

I thought the problem was where I had placed root.mainloop(), but where I have placed it in the above code only works, otherwise the otp screen doesn't appear.

Here's the otp screen:

otp screen

If I click submit, a new window appears, with the given root.title(), but nothing appears in it (I have a succ_label, but that doesn't appear).

If I close the screen then this is what appears:

error

I am really sorry for wasting your time in reading so much for a little task, but all help would be appreciated.

Thanks!

PS: Everything under def visa_card_payment function has to be indented.

Upvotes: 0

Views: 214

Answers (1)

scotty3785
scotty3785

Reputation: 7006

There are some fundamental Tkinter rules being broken here.

  1. Never have more than one tkinter Tk root method (they won't co-exist nicely together)
  2. Never use infinite while loops (they prevent the gui from updating)

To solve 1 use Toplevel instead of Tk for any child windows

Solving 2 is a bit more complex

The while loop containing your countdown won't be executed since root.mainloop() will continue to run until the GUI closes.

You should consider placing your countdown timer inside a separate function which is called once every second by the tkinter .after method.

When the button is pressed you call something like this to call the function after one second.

root.after(1000, myCountdownFunction)

then your function would look something like

def myCountDownFunction():
    mins, secs = divmod(t, 60)
    timeformat='{:02d}:{:02d}'.format(mins, secs)
    countdown_label2.grid_forget()
    countdown_label2=Label(root,text=timeformat)
    countdown_label2.grid(row=1,column=1)
    t=-1
    root.after(1000, MyCountdownFunction) # Calls this function again in 1 second

This question is very broad and I doubt that any answer will be able to totally solve your problem without a total re-write of the code.

Upvotes: 1

Related Questions