In this script: import os
import json
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from jinja2 import Template, Environment, FileSystemLoader
import subprocess
import sys

# Define template contents for various files
BOT_PY_TEMPLATE = Template("""
{% raw %}
import logging
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
    Updater,
    CommandHandler,
    MessageHandler,
    Filters,
    CallbackContext,
    CallbackQueryHandler
)
from database import Session, User, Product, Cart, Order
from scraper import scrape_yaga_store
from payments import create_payfast_payment, create_yoco_payment
import json
import requests
import threading
import time

# Configure logging
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.INFO
)
logger = logging.getLogger(__name__)

# Initialize Rasa endpoint
RASA_ENDPOINT = '{{ rasa_endpoint }}'

def start(update: Update, context: CallbackContext):
    user = update.message.from_user
    session_db = Session()
    db_user = session_db.query(User).filter_by(telegram_id=str(user.id)).first()
    if not db_user:
        db_user = User(telegram_id=str(user.id))
        session_db.add(db_user)
        session_db.commit()
    session_db.close()
    update.message.reply_text(
        "Welcome to our store! How can I assist you today? Type /help to see available commands."
    )

def help_command(update: Update, context: CallbackContext):
    help_text = (
        "/start - Start interacting with the bot\n"
        "/products - View available products\n"
        "/cart - View your cart\n"
        "/checkout - Proceed to checkout\n"
        "/help - Show this help message"
    )
    update.message.reply_text(help_text)

def products(update: Update, context: CallbackContext):
    session_db = Session()
    products = session_db.query(Product).all()
    session_db.close()
    for product in products:
        keyboard = [
            [InlineKeyboardButton("Add to Cart", callback_data=f"add_{{ product.id }}")]
        ]
        reply_markup = InlineKeyboardMarkup(keyboard)
        update.message.reply_photo(
            photo=product.image_url,
            caption=f"*{{ product.name }}*\n{{ product.description }}\nPrice: R{{ product.price }}",
            parse_mode='Markdown',
            reply_markup=reply_markup
        )

def button(update: Update, context: CallbackContext):
    query = update.callback_query
    query.answer()
    data = query.data
    user = query.from_user
    session_db = Session()
    db_user = session_db.query(User).filter_by(telegram_id=str(user.id)).first()
    cart = session_db.query(Cart).filter_by(user_id=db_user.id).first()
    if not cart:
        cart = Cart(user_id=db_user.id, items=json.dumps({}))
        session_db.add(cart)
    cart_items = json.loads(cart.items)

    if data.startswith("add_"):
        product_id = int(data.split("_")[1])
        cart_items[str(product_id)] = cart_items.get(str(product_id), 0) + 1
        query.edit_message_text(text="Item added to your cart.")
    elif data.startswith("remove_"):
        product_id = int(data.split("_")[1])
        if str(product_id) in cart_items:
            del cart_items[str(product_id)]
            query.edit_message_text(text="Item removed from your cart.")
    elif data == "checkout":
        if not cart_items:
            query.edit_message_text(text="Your cart is empty.")
            session_db.close()
            return
        total = 0
        item_description = ""
        for pid, qty in cart_items.items():
            product = session_db.query(Product).filter_by(id=int(pid)).first()
            if product:
                total += product.price * qty
                item_description += f"{product.name} x{qty}, "
        payment_link = ""
        order = Order(
            user_id=db_user.id,
            items=json.dumps(cart_items),
            total=total,
            payment_status=False
        )
        session_db.add(order)
        session_db.commit()
        if "{{ payment_gateway }}" == "PayFast":
            payment_link = create_payfast_payment(order_id=str(order.id), amount=total, item_description=item_description)
        elif "{{ payment_gateway }}" == "Yoco":
            payment_link = create_yoco_payment(order_id=str(order.id), amount=total, item_description=item_description)
        query.edit_message_text(text=f"Please complete your payment here: {payment_link}")
    cart.items = json.dumps(cart_items)
    session_db.commit()
    session_db.close()

def view_cart(update: Update, context: CallbackContext):
    user = update.message.from_user
    session_db = Session()
    db_user = session_db.query(User).filter_by(telegram_id=str(user.id)).first()
    cart = session_db.query(Cart).filter_by(user_id=db_user.id).first()
    if not cart or not cart.items:
        update.message.reply_text("Your cart is empty.")
        session_db.close()
        return
    cart_items = json.loads(cart.items)
    message = "Your Cart:\n"
    total = 0
    for product_id, quantity in cart_items.items():
        product = session_db.query(Product).filter_by(id=int(product_id)).first()
        if product:
            message += f"{product.name} x{quantity} - R{product.price * quantity}\n"
            total += product.price * quantity
    message += f"\nTotal: R{total}"
    keyboard = [
        [InlineKeyboardButton("Checkout", callback_data="checkout")]
    ]
    reply_markup = InlineKeyboardMarkup(keyboard)
    update.message.reply_text(message, reply_markup=reply_markup)
    session_db.close()

def checkout(update: Update, context: CallbackContext):
    query = update.callback_query
    query.answer()
    user = query.from_user
    session_db = Session()
    db_user = session_db.query(User).filter_by(telegram_id=str(user.id)).first()
    cart = session_db.query(Cart).filter_by(user_id=db_user.id).first()
    if not cart or not cart.items:
        query.edit_message_text(text="Your cart is empty.")
        session_db.close()
        return
    cart_items = json.loads(cart.items)
    total = 0
    item_description = ""
    for product_id, quantity in cart_items.items():
        product = session_db.query(Product).filter_by(id=int(product_id)).first()
        if product:
            total += product.price * quantity
            item_description += f"{product.name} x{quantity}, "
    payment_link = ""
    order = Order(
        user_id=db_user.id,
        items=json.dumps(cart_items),
        total=total,
        payment_status=False
    )
    session_db.add(order)
    session_db.commit()
    if "{{ payment_gateway }}" == "PayFast":
        payment_link = create_payfast_payment(order_id=str(order.id), amount=total, item_description=item_description)
    elif "{{ payment_gateway }}" == "Yoco":
        payment_link = create_yoco_payment(order_id=str(order.id), amount=total, item_description=item_description)
    query.edit_message_text(text=f"Please complete your payment here: {payment_link}")
    session_db.close()

def handle_message(update: Update, context: CallbackContext):
    user_message = update.message.text
    # Send message to Rasa for NLP processing
    response = requests.post(RASA_ENDPOINT, json={"message": user_message})
    if response.status_code == 200:
        data = response.json()
        if data:
            bot_reply = data[0].get('text', "I'm not sure how to respond to that.")
            update.message.reply_text(bot_reply)
        else:
            update.message.reply_text("I'm not sure how to respond to that.")
    else:
        update.message.reply_text("Sorry, I'm having trouble understanding you right now.")

def broadcast_promotion(context: CallbackContext):
    session_db = Session()
    users = session_db.query(User).all()
    for user in users:
        try:
            context.bot.send_message(chat_id=user.telegram_id, text="Check out our latest discounts!")
        except Exception as e:
            logger.error(f"Error sending message to {user.telegram_id}: {e}")
    session_db.close()

def main():
    # Replace '{{ telegram_token }}' with your actual token
    updater = Updater("{{ telegram_token }}", use_context=True)
    dispatcher = updater.dispatcher

    # Handlers
    dispatcher.add_handler(CommandHandler("start", start))
    dispatcher.add_handler(CommandHandler("help", help_command))
    dispatcher.add_handler(CommandHandler("products", products))
    dispatcher.add_handler(CommandHandler("cart", view_cart))
    dispatcher.add_handler(CallbackQueryHandler(button))
    dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, handle_message))

    # Start the Bot
    updater.start_polling()

    # Start scraping periodically (e.g., every 6 hours)
    def periodic_scrape():
        while True:
            scrape_yaga_store()
            time.sleep(6 * 60 * 60)  # 6 hours

    scrape_thread = threading.Thread(target=periodic_scrape, daemon=True)
    scrape_thread.start()

    # Schedule a daily promotion at 9 AM
    from datetime import time as dt_time
    job_queue = updater.job_queue
    job_queue.run_daily(
        broadcast_promotion,
        time=dt_time(hour=9, minute=0, second=0)
    )

    updater.idle()

if __name__ == '__main__':
    main()
{% endraw %}
""")

DATABASE_PY_TEMPLATE = Template("""
from sqlalchemy import create_engine, Column, Integer, String, Float, Boolean, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship

Base = declarative_base()

class Product(Base):
    __tablename__ = 'products'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    description = Column(String)
    price = Column(Float)
    stock = Column(Integer)
    image_url = Column(String)

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    telegram_id = Column(String, unique=True)
    cart = relationship("Cart", back_populates="user")

class Cart(Base):
    __tablename__ = 'carts'
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'))
    items = Column(String)  # JSON string of product IDs and quantities
    user = relationship("User", back_populates="cart")

class Order(Base):
    __tablename__ = 'orders'
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'))
    items = Column(String)  # JSON string
    total = Column(Float)
    payment_status = Column(Boolean, default=False)

engine = create_engine('sqlite:///telegram_bot.db')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
""")

SCRAPER_PY_TEMPLATE = Template("""
import requests
from bs4 import BeautifulSoup
from database import Session, Product
import logging

logger = logging.getLogger(__name__)

def scrape_yaga_store():
    session = Session()
    url = '{{ yaga_store_url }}'  # Replace with your Yaga store URL
    try:
        response = requests.get(url)
        response.raise_for_status()
        soup = BeautifulSoup(response.text, 'html.parser')

        # Example scraping logic - adjust selectors based on your store's HTML structure
        products = soup.find_all('div', class_='product-item')
        for prod in products:
            name = prod.find('h2').text.strip()
            description = prod.find('p', class_='description').text.strip()
            price = float(prod.find('span', class_='price').text.strip().replace('$', ''))
            stock = int(prod.find('span', class_='stock').text.strip())
            image_url = prod.find('img')['src']

            existing_product = session.query(Product).filter_by(name=name).first()
            if not existing_product:
                product = Product(
                    name=name,
                    description=description,
                    price=price,
                    stock=stock,
                    image_url=image_url
                )
                session.add(product)
        session.commit()
    except Exception as e:
        logger.error(f"Error scraping Yaga store: {e}")
    finally:
        session.close()

if __name__ == "__main__":
    scrape_yaga_store()
""")

PAYMENTS_PY_TEMPLATE = Template("""
import requests

{% if payment_gateway == 'PayFast' %}
PAYFAST_MERCHANT_ID = '{{ payfast_merchant_id }}'
PAYFAST_RETURN_URL = '{{ payfast_return_url }}'
PAYFAST_NOTIFY_URL = '{{ payfast_notify_url }}'
PAYFAST_CANCEL_URL = '{{ payfast_cancel_url }}'
PAYFAST_SECRET_KEY = '{{ payfast_secret_key }}'  # Needed for validating notifications

def create_payfast_payment(order_id, amount, item_description):
    data = {
        'merchant_id': PAYFAST_MERCHANT_ID,
        'return_url': PAYFAST_RETURN_URL,
        'cancel_url': PAYFAST_CANCEL_URL,
        'notify_url': PAYFAST_NOTIFY_URL,
        'm_payment_id': order_id,
        'amount': amount,
        'item_name': item_description,
        'currency': 'ZAR'
    }
    # PayFast uses form-based POST; here we could generate the payment link
    payment_url = 'https://www.payfast.co.za/eng/process?' + '&'.join([f"{{k}}={{v}}" for k, v in data.items()])
    return payment_url

def validate_payfast_notification(params):
    # Implement validation logic based on PayFast's documentation
    pass

{% elif payment_gateway == 'Yoco' %}
YOCO_API_KEY = '{{ yoco_api_key }}'
YOCO_RETURN_URL = '{{ yoco_return_url }}'

def create_yoco_payment(order_id, amount, item_description):
    # Implement Yoco payment link creation
    payment_link = f"https://checkout.yoco.com/pay?client_id={{ YOCO_API_KEY }}&order_id={{ order_id }}&amount={{ amount }}&description={{ item_description }}"
    return payment_link

def validate_yoco_notification(params):
    # Implement validation logic based on Yoco's documentation
    pass

{% endif %}
""")

REQUIREMENTS_TXT = """
python-telegram-bot==13.14
beautifulsoup4
requests
sqlalchemy
jinja2
"""

PROCFILE_TEMPLATE = Template("""
worker: python bot.py
""")

BUILD_SCRIPT_TEMPLATE = Template("""
#!/bin/bash

# Install dependencies
pip install -r requirements.txt

# Run Rasa server (assuming Rasa is installed)
rasa run --enable-api --model models/
""")

README_TEMPLATE = Template("""
# Telegram Store Bot

This Telegram bot allows you to sell products, handle customer inquiries, process payments, and execute marketing strategies directly within Telegram.

## Features

- Product Listings
- Cart Management
- Payment Integration (PayFast/Yoco)
- Customer Inquiries with NLP
- Marketing Automation

## Setup Instructions

1. **Install Dependencies**

    ```bash
    pip install -r requirements.txt
    ```

2. **Run the Bot**

    ```bash
    python bot.py
    ```

3. **Run Rasa Server**

    Ensure Rasa is installed and run:

    ```bash
    rasa run --enable-api --model models/
    ```

## Deployment

For deploying the bot to Heroku or any other cloud service, follow the respective platform's deployment guide.

## License

MIT License
""")

# Function to create directories and files
def create_project(project_path, config):
    try:
        # Create project directory
        os.makedirs(project_path, exist_ok=True)

        # Create subdirectories
        subdirs = ['nlp']
        for subdir in subdirs:
            os.makedirs(os.path.join(project_path, subdir), exist_ok=True)

        # Create bot.py
        with open(os.path.join(project_path, 'bot.py'), 'w') as f:
            f.write(BOT_PY_TEMPLATE.render(
                telegram_token=config['telegram_token'],
                payment_gateway=config['payment_gateway'],
                rasa_endpoint=config['rasa_endpoint']
            ))

        # Create database.py
        with open(os.path.join(project_path, 'database.py'), 'w') as f:
            f.write(DATABASE_PY_TEMPLATE.render())

        # Create scraper.py
        with open(os.path.join(project_path, 'scraper.py'), 'w') as f:
            f.write(SCRAPER_PY_TEMPLATE.render(
                yaga_store_url=config['yaga_store_url']
            ))

        # Create payments.py
        with open(os.path.join(project_path, 'payments.py'), 'w') as f:
            f.write(PAYMENTS_PY_TEMPLATE.render(
                payment_gateway=config['payment_gateway'],
                payfast_merchant_id=config.get('payfast_merchant_id', ''),
                payfast_return_url=config.get('payfast_return_url', ''),
                payfast_notify_url=config.get('payfast_notify_url', ''),
                payfast_cancel_url=config.get('payfast_cancel_url', ''),
                payfast_secret_key=config.get('payfast_secret_key', ''),
                yoco_api_key=config.get('yoco_api_key', ''),
                yoco_return_url=config.get('yoco_return_url', '')
            ))

        # Create requirements.txt
        with open(os.path.join(project_path, 'requirements.txt'), 'w') as f:
            f.write(REQUIREMENTS_TXT.strip())

        # Create Procfile
        with open(os.path.join(project_path, 'Procfile'), 'w') as f:
            f.write(PROCFILE_TEMPLATE.render())

        # Create build.sh
        with open(os.path.join(project_path, 'build.sh'), 'w') as f:
            f.write(BUILD_SCRIPT_TEMPLATE.render())

        # Create README.md
        with open(os.path.join(project_path, 'README.md'), 'w') as f:
            f.write(README_TEMPLATE.render())

        # Create nlp configuration files (if necessary)
        nlp_dir = os.path.join(project_path, 'nlp')
        if not os.path.exists(os.path.join(nlp_dir, 'config.yml')):
            with open(os.path.join(nlp_dir, 'config.yml'), 'w') as f:
                f.write("# Rasa configuration file\n")
        if not os.path.exists(os.path.join(nlp_dir, 'domain.yml')):
            with open(os.path.join(nlp_dir, 'domain.yml'), 'w') as f:
                f.write("# Rasa domain file\n")
        if not os.path.exists(os.path.join(nlp_dir, 'nlu.yml')):
            with open(os.path.join(nlp_dir, 'nlu.yml'), 'w') as f:
                f.write("# Rasa NLU training data\n")
        if not os.path.exists(os.path.join(nlp_dir, 'stories.yml')):
            with open(os.path.join(nlp_dir, 'stories.yml'), 'w') as f:
                f.write("# Rasa stories\n")

        return True, "Project created successfully."
    except Exception as e:
        return False, str(e)

# Utility function to check if Rasa is installed
def is_rasa_installed():
    try:
        subprocess.run(['rasa', '--version'], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        return True
    except (subprocess.CalledProcessError, FileNotFoundError):
        return False

# GUI Setup
class SetupGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("Telegram Bot Setup Wizard")
        self.root.geometry("700x800")

        # Initialize variables
        self.telegram_path = "D:\\Desk\\Telegram\\Telegram.exe"  # Default path
        self.telegram_token = tk.StringVar()
        self.yaga_store_url = tk.StringVar()
        self.rasa_endpoint = tk.StringVar()
        self.payment_gateway = tk.StringVar(value="PayFast")
        self.payfast_merchant_id = tk.StringVar()
        self.payfast_return_url = tk.StringVar()
        self.payfast_notify_url = tk.StringVar()
        self.payfast_cancel_url = tk.StringVar()
        self.payfast_secret_key = tk.StringVar()
        self.yoco_api_key = tk.StringVar()
        self.yoco_return_url = tk.StringVar()
        self.project_name = tk.StringVar(value="telegram_bot")
        self.project_dir = tk.StringVar(value=os.path.join(os.getcwd(), "telegram_bot"))

        # Create a notebook
        self.notebook = ttk.Notebook(self.root)
        self.notebook.pack(pady=10, expand=True, fill='both')

        # Tabs
        self.tab_setup = ttk.Frame(self.notebook)
        self.notebook.add(self.tab_setup, text="Setup")

        # ------- Tab: Setup -------
        self.create_tab_setup()

        # ------- Generate Button -------
        self.generate_button = ttk.Button(self.root, text="Generate Project", command=self.generate_project)
        self.generate_button.pack(pady=20)

    def create_tab_setup(self):
        frame = self.tab_setup
        frame.pack(fill='both', expand=True, padx=20, pady=20)

        # Section: Telegram Bot Setup
        telegram_frame = ttk.LabelFrame(frame, text="1. Telegram Bot Setup")
        telegram_frame.pack(fill='x', pady=10)

        # Telegram Path
        ttk.Label(telegram_frame, text="Telegram Executable Path:").grid(row=0, column=0, sticky='w', padx=10, pady=5)
        self.telegram_path_entry = ttk.Entry(telegram_frame, textvariable=tk.StringVar(value=self.telegram_path), width=50)
        self.telegram_path_entry.grid(row=0, column=1, padx=10, pady=5)
        ttk.Button(telegram_frame, text="Browse", command=self.browse_telegram).grid(row=0, column=2, padx=10, pady=5)

        # Instructions to create bot via BotFather
        instructions = (
            "1. Open Telegram using the path above.\n"
            "2. Search for @BotFather and start a chat.\n"
            "3. Use the /newbot command to create a new bot.\n"
            "4. Follow the prompts to set a name and username for your bot.\n"
            "5. BotFather will provide you with a Bot Token. Please enter it below."
        )
        ttk.Label(telegram_frame, text="Follow these steps to create your bot via BotFather:").grid(row=1, column=0, columnspan=3, sticky='w', padx=10, pady=5)
        ttk.Label(telegram_frame, text=instructions, wraplength=600, justify='left').grid(row=2, column=0, columnspan=3, sticky='w', padx=10, pady=5)

        # Telegram Bot Token
        ttk.Label(telegram_frame, text="Telegram Bot Token:").grid(row=3, column=0, sticky='w', padx=10, pady=5)
        self.telegram_token_entry = ttk.Entry(telegram_frame, textvariable=self.telegram_token, width=50)
        self.telegram_token_entry.grid(row=3, column=1, padx=10, pady=5)

        ttk.Button(telegram_frame, text="Open Telegram", command=self.open_telegram).grid(row=3, column=2, padx=10, pady=5)

        # Section: Yaga Store Setup
        yaga_frame = ttk.LabelFrame(frame, text="2. Yaga Store Setup")
        yaga_frame.pack(fill='x', pady=10)

        ttk.Label(yaga_frame, text="Yaga Store URL:").grid(row=0, column=0, sticky='w', padx=10, pady=5)
        self.yaga_store_url_entry = ttk.Entry(yaga_frame, textvariable=self.yaga_store_url, width=50)
        self.yaga_store_url_entry.grid(row=0, column=1, padx=10, pady=5)

        # Section: Rasa Setup
        rasa_frame = ttk.LabelFrame(frame, text="3. Rasa NLP Setup")
        rasa_frame.pack(fill='x', pady=10)

        # Check if Rasa is installed
        self.rasa_installed = is_rasa_installed()
        rasa_status = "Installed" if self.rasa_installed else "Not Installed"

        ttk.Label(rasa_frame, text=f"Rasa Status: {rasa_status}").grid(row=0, column=0, columnspan=2, sticky='w', padx=10, pady=5)
        if not self.rasa_installed:
            ttk.Label(rasa_frame, text="Click below to install Rasa.").grid(row=1, column=0, columnspan=2, sticky='w', padx=10, pady=5)
            ttk.Button(rasa_frame, text="Install Rasa", command=self.install_rasa).grid(row=2, column=0, padx=10, pady=5, sticky='w')
        else:
            ttk.Label(rasa_frame, text="Enter Rasa Server Endpoint:").grid(row=3, column=0, sticky='w', padx=10, pady=5)
            self.rasa_endpoint_entry = ttk.Entry(rasa_frame, textvariable=self.rasa_endpoint, width=50)
            self.rasa_endpoint_entry.grid(row=3, column=1, padx=10, pady=5)
            ttk.Button(rasa_frame, text="Provide Rasa Endpoint", command=self.provide_rasa_endpoint).grid(row=4, column=0, padx=10, pady=5, sticky='w')

        # Section: Payment Gateway Setup
        payment_frame = ttk.LabelFrame(frame, text="4. Payment Gateway Setup")
        payment_frame.pack(fill='x', pady=10)

        # Payment Gateway Selection
        ttk.Label(payment_frame, text="Select Payment Gateway:").grid(row=0, column=0, sticky='w', padx=10, pady=5)
        self.payment_gateway.set("PayFast")
        gateways = ["PayFast", "Yoco"]
        self.gateway_dropdown = ttk.OptionMenu(payment_frame, self.payment_gateway, gateways[0], *gateways, command=self.toggle_payment_fields)
        self.gateway_dropdown.grid(row=0, column=1, padx=10, pady=5, sticky='w')

        # PayFast Fields
        self.payfast_frame = ttk.LabelFrame(payment_frame, text="PayFast Configuration")
        self.payfast_frame.grid(row=1, column=0, columnspan=2, sticky='ew', padx=10, pady=10)

        ttk.Label(self.payfast_frame, text="Merchant ID:").grid(row=0, column=0, sticky='w', padx=10, pady=5)
        self.payfast_merchant_id_entry = ttk.Entry(self.payfast_frame, textvariable=self.payfast_merchant_id, width=50)
        self.payfast_merchant_id_entry.grid(row=0, column=1, padx=10, pady=5)

        ttk.Label(self.payfast_frame, text="Return URL:").grid(row=1, column=0, sticky='w', padx=10, pady=5)
        self.payfast_return_url_entry = ttk.Entry(self.payfast_frame, textvariable=self.payfast_return_url, width=50)
        self.payfast_return_url_entry.grid(row=1, column=1, padx=10, pady=5)

        ttk.Label(self.payfast_frame, text="Notify URL:").grid(row=2, column=0, sticky='w', padx=10, pady=5)
        self.payfast_notify_url_entry = ttk.Entry(self.payfast_frame, textvariable=self.payfast_notify_url, width=50)
        self.payfast_notify_url_entry.grid(row=2, column=1, padx=10, pady=5)

        ttk.Label(self.payfast_frame, text="Cancel URL:").grid(row=3, column=0, sticky='w', padx=10, pady=5)
        self.payfast_cancel_url_entry = ttk.Entry(self.payfast_frame, textvariable=self.payfast_cancel_url, width=50)
        self.payfast_cancel_url_entry.grid(row=3, column=1, padx=10, pady=5)

        ttk.Label(self.payfast_frame, text="Secret Key:").grid(row=4, column=0, sticky='w', padx=10, pady=5)
        self.payfast_secret_key_entry = ttk.Entry(self.payfast_frame, textvariable=self.payfast_secret_key, width=50, show="*")
        self.payfast_secret_key_entry.grid(row=4, column=1, padx=10, pady=5)

        # Yoco Fields (Initially Hidden)
        self.yoco_frame = ttk.LabelFrame(payment_frame, text="Yoco Configuration")
        self.yoco_frame.grid(row=2, column=0, columnspan=2, sticky='ew', padx=10, pady=10)
        self.yoco_frame.grid_remove()

        ttk.Label(self.yoco_frame, text="API Key:").grid(row=0, column=0, sticky='w', padx=10, pady=5)
        self.yoco_api_key_entry = ttk.Entry(self.yoco_frame, textvariable=self.yoco_api_key, width=50)
        self.yoco_api_key_entry.grid(row=0, column=1, padx=10, pady=5)

        ttk.Label(self.yoco_frame, text="Return URL:").grid(row=1, column=0, sticky='w', padx=10, pady=5)
        self.yoco_return_url_entry = ttk.Entry(self.yoco_frame, textvariable=self.yoco_return_url, width=50)
        self.yoco_return_url_entry.grid(row=1, column=1, padx=10, pady=5)

        # Section: Project Setup
        project_frame = ttk.LabelFrame(frame, text="5. Project Setup")
        project_frame.pack(fill='x', pady=10)

        # Project Name
        ttk.Label(project_frame, text="Project Name:").grid(row=0, column=0, sticky='w', padx=10, pady=5)
        self.project_name_entry = ttk.Entry(project_frame, textvariable=self.project_name, width=50)
        self.project_name_entry.grid(row=0, column=1, padx=10, pady=5)

        # Project Directory
        ttk.Label(project_frame, text="Project Directory:").grid(row=1, column=0, sticky='w', padx=10, pady=5)
        self.project_dir_entry = ttk.Entry(project_frame, textvariable=self.project_dir, width=50)
        self.project_dir_entry.grid(row=1, column=1, padx=10, pady=5)
        ttk.Button(project_frame, text="Browse", command=self.browse_project_dir).grid(row=1, column=2, padx=10, pady=5)

    def browse_telegram(self):
        path = filedialog.askopenfilename(initialdir=os.path.dirname(self.telegram_path), title="Select Telegram Executable",
                                          filetypes=(("Executable Files", "*.exe"), ("All Files", "*.*")))
        if path:
            self.telegram_path_entry.delete(0, tk.END)
            self.telegram_path_entry.insert(0, path)
            self.telegram_path = path

    def open_telegram(self):
        path = self.telegram_path_entry.get()
        if os.path.exists(path):
            try:
                subprocess.Popen([path])
                messagebox.showinfo("Telegram", "Telegram has been opened. Please follow the instructions to create your bot.")
            except Exception as e:
                messagebox.showerror("Error", f"Failed to open Telegram: {e}")
        else:
            messagebox.showerror("Error", "Telegram executable not found. Please check the path.")

    def install_rasa(self):
        install = messagebox.askyesno("Install Rasa", "Rasa is not installed. Do you want to install it now? This may take a few minutes.")
        if install:
            try:
                subprocess.check_call([sys.executable, "-m", "pip", "install", "rasa"])
                messagebox.showinfo("Success", "Rasa has been installed successfully.")
                self.rasa_installed = True
                self.notebook.select(0)
                self.create_tab_setup()  # Refresh tabs
            except subprocess.CalledProcessError as e:
                messagebox.showerror("Error", f"Failed to install Rasa: {e}")
        else:
            messagebox.showwarning("Rasa Not Installed", "Rasa is required for NLP functionalities. Proceed with caution.")

    def provide_rasa_endpoint(self):
        if not self.rasa_installed:
            messagebox.showerror("Rasa Not Installed", "Please install Rasa first.")
            return
        # Prompt user to start Rasa server if not already running
        rasa_running = self.check_rasa_running()
        if not rasa_running:
            start_rasa = messagebox.askyesno("Start Rasa", "Rasa server is not running. Do you want to start it now?")
            if start_rasa:
                try:
                    subprocess.Popen(['rasa', 'run', '--enable-api', '--model', 'models/'])
                    messagebox.showinfo("Rasa Started", "Rasa server has been started.")
                except Exception as e:
                    messagebox.showerror("Error", f"Failed to start Rasa server: {e}")
                    return
        # Inform user to enter Rasa endpoint
        messagebox.showinfo("Rasa Endpoint", "Ensure Rasa server is running and obtain the endpoint URL (e.g., http://localhost:5005/webhooks/rest/webhook).")
    
    def check_rasa_running(self):
        try:
            import socket
            with socket.create_connection(("localhost", 5005), timeout=2):
                return True
        except (OSError, ImportError):
            return False

    def toggle_payment_fields(self, selected_gateway):
        if selected_gateway == "PayFast":
            self.yoco_frame.grid_remove()
            self.payfast_frame.grid()
        elif selected_gateway == "Yoco":
            self.payfast_frame.grid_remove()
            self.yoco_frame.grid()

    def browse_project_dir(self):
        directory = filedialog.askdirectory()
        if directory:
            self.project_dir_entry.delete(0, tk.END)
            self.project_dir_entry.insert(0, directory)
            self.project_dir.set(directory)

    def generate_project(self):
        # Collect all inputs
        telegram_token = self.telegram_token.get().strip()
        yaga_store_url = self.yaga_store_url.get().strip()
        rasa_endpoint = self.rasa_endpoint.get().strip()
        payment_gateway = self.payment_gateway.get()
        project_name = self.project_name.get().strip()
        project_dir = self.project_dir.get().strip()

        if not telegram_token or not yaga_store_url or not project_name or not project_dir:
            messagebox.showerror("Error", "Please fill in all required fields.")
            return

        if self.payment_gateway.get() == "PayFast":
            required_payfast = ['payfast_merchant_id', 'payfast_return_url', 'payfast_notify_url', 'payfast_cancel_url', 'payfast_secret_key']
            for field in required_payfast:
                if not getattr(self, field).get().strip():
                    messagebox.showerror("Error", f"Please fill in the PayFast field: {field.replace('_', ' ').title()}")
                    return
        elif self.payment_gateway.get() == "Yoco":
            required_yoco = ['yoco_api_key', 'yoco_return_url']
            for field in required_yoco:
                if not getattr(self, field).get().strip():
                    messagebox.showerror("Error", f"Please fill in the Yoco field: {field.replace('_', ' ').title()}")
                    return

        # Confirm project creation
        confirm = messagebox.askyesno("Confirm", f"Create project '{project_name}' at:\n{project_dir}?")
        if not confirm:
            return

        # Create project
        config = {
            'telegram_token': telegram_token,
            'yaga_store_url': yaga_store_url,
            'rasa_endpoint': rasa_endpoint,
            'payment_gateway': payment_gateway,
            'payfast_merchant_id': self.payfast_merchant_id.get().strip(),
            'payfast_return_url': self.payfast_return_url.get().strip(),
            'payfast_notify_url': self.payfast_notify_url.get().strip(),
            'payfast_cancel_url': self.payfast_cancel_url.get().strip(),
            'payfast_secret_key': self.payfast_secret_key.get().strip(),
            'yoco_api_key': self.yoco_api_key.get().strip(),
            'yoco_return_url': self.yoco_return_url.get().strip()
        }

        success, message = create_project(project_dir, config)
        if success:
            messagebox.showinfo("Success", message)
            # Optionally, open the project directory
            try:
                if os.name == 'nt':
                    os.startfile(project_dir)
                elif os.name == 'posix':
                    subprocess.Popen(['open', project_dir])
                else:
                    messagebox.showinfo("Info", "Please navigate to the project directory manually.")
            except Exception as e:
                messagebox.showerror("Error", f"Failed to open directory: {e}")
        else:
            messagebox.showerror("Error", f"Failed to create project:\n{message}")

# Main execution
if __name__ == "__main__":
    # Ensure Jinja2 is installed
    try:
        import jinja2
    except ImportError:
        response = messagebox.askyesno("Install Required Package", "Jinja2 is not installed. Do you want to install it now?")
        if response:
            subprocess.check_call([sys.executable, "-m", "pip", "install", "jinja2"])
        else:
            sys.exit("Jinja2 is required to run this script.")

    root = tk.Tk()
    app = SetupGUI(root)
    root.mainloop()

Please read the following and automate setting up the paygate using payfast. Prompt the user for any information that is required in order to streamline the process and facilitate setting things up: Logo
Preface
PayFast – Go Cashless!
API End Points
Get Access Token
Refresh Access Token
Issuer/Banks
Payment (Instrument) Types
Issuer/Banks By Issuer Instrument ID
Customer Account Validation
Initiate Transaction
Temporary Transaction Token
Tokenized Transaction
Add Permanent Payment Instrument
OTP for Recurring Transaction
Initiate Recurring Transaction
Fetch Permanent Instrument Token
Delete Instrument
Refund Transaction Request
Get Transaction Status
Error Codes
Hashed Parameters
3D Secure Transaction
PayFast Corporate Web
Preface
About
This document contains detailed explanation about how to integrate with PayFast API’s Based transactions functionality. This document also contains the details for online transactions.

Intended Audience
This document is for merchants, acquirers and developers who want to integrate with PayFast to perform a API’s based Transactions.

Integration scope
The merchant will implement all ecommerce functionality. PayFast’ service (Payfast) will be used only for payment processing.

PayFast – Go Cashless!
PayFast is focused to bring merchants on ecommerce platforms by offering a complete suite of solutions to all types of merchants from building a website, online store, mobile application, social media marketing to securing payment. PayFast will enable merchants to sell the products and services online, and receive their payment safely. PayFast

Key Features
Simple, secure and low risk
Offers easy integration and a broad application suite
Directly charge from the customer’s Bank account, Debit Card and Wallets
Guaranteed prompt payment to Merchants
Integrates easily with Mobile and web based application
Connects to a variety of Merchant organizations including retail stores, governments, etc.
How Payfast Works
PayFast has developed a flagship payment gateway solution called “PayFast” which is targeted towards merchants, schools and other corporate entities to accept payments digitally from customers by using multiple options such as Debit Cards and Account Numbers. It will be integrated directly with the client’s website to allow smooth transaction placements. How It Works

API Based Transaction
All retail stores can integrate with Payfast direct API’s for over the counter payments without redirection. After the payment process is completed, the merchant will get an intimation of the transaction status.

Integration Prerequisites
Merchants will be registered on PayFast prior to integration. After merchant sign up for PayFast account, following two unique values will be provided to merchant to operate: * MERCHANT_ID * SECURED_KEY These keys are used to get a one-time authentication token, which is used to authenticate payment requests to the “Payfast” payment gateway.

Getting Authentication Token
To initiate the transaction, the merchant needs to generate the Authentication Token from PayFast server. For fetching, the token developer needs to call the Authentication token API.

Let’s start with Scenarios
Scenario 1
Consider a typical ecommerce transaction where a customer enters card/account details and tries to pay via PayFast. In this scenario, a temporary transaction token (refer to this section) should be fetched (which will last for short period) and then a tokenized transaction API (refer to this section) will be called to initiate a payment request based on temporary token. In this process, an OTP will be sent to customer mobile number. This token cannot be used for recurring transaction.

Scenario 2
Consider a scenario where a mobile app wants its customer card information for monthly subscription. This is recurring model, where customer will store its instrument such as card details or account details for later use. It is recommended for merchant to get a temporary transaction (refer to this section) token against the card details and perform a small amount of transaction (refer to this section) (which will be refunded) to validate instrument details, based on temporary token. Again, an OTP will be sent to customer and once transaction gets success, merchant will add instruments details and will get the permanent transaction token (refer to this section) which could be used later on for recurring transaction API (refer to this section) and no OTP will be sent to customer.

Scenario 3
Consider a situation where merchant do not want a token against card/account details and want to perform a transaction with the instrument details on the fly. In this case, a simple transaction API (refer to section API Transaction) will serve the purpose. In this process, merchant would call "validate" (refer to section API Customer Account Validation) API to get the OTP for customer and it will be sent with other transaction parameter.

API End Points
This section contains the details of all APIs provided by PayFast. These APIs could be called by the merchants, acquirers and/or aggregators. These API’S are based on REST architecture and serve standard HTTP codes for the response payload.

Get Access Token
Following API will provide you the Authentication token, which will be used to call APIs. Merchant_id and Secured_key is mandatory to get the access token. This token will be sent on all the APIs with standard HTTP header ‘Authorization’.

curl -X POST \
  /token \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'merchant_id=<MERCHANT_ID>&grant_type=client_credentials&secured_key=<KEY>'
The above command following JSON structure:

{
  "token": "<token>",
  "refresh_token": "<refresh token>",
  "code": "",
  "message": null,
  "expiry": <no.ofseconds>
}
HTTP Request
POST /token

Post Parameters
Parameter	Required	Description
merchant_id	Yes	Merchant ID
secured_key	Yes	Secured Key
grant_type	Yes	Grant type ( client_credentials )
customer_ip	Yes	IP address of the end user
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version (optional)
Refresh Access Token
Any access token can be refreshed upon expiry. A refresh token is given along with original token.

curl -X POST \
  <BASE_URL>/refreshtoken \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/x-www-form-urlencoded' 
The above command returns JSON structured like this:

{
  "token": "<token>",
  "refresh_token": "<refresh token>",
  "code": "",
  "message": null,
  "expiry": <no.ofseconds>
}
HTTP Request
POST /refreshtoken

HTTP Header
Authorization: Bearer <token>

Post Parameters
Parameter	Required	Description
refresh_token	Yes	Refresh token
grant_type	Yes	Grant type ( refresh_token )
customer_ip	Yes	IP address of the end user
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version (optional)
Issuer/Banks
This API will provide the available list of issuer/bank.

curl -X GET \
  <BASE_URL>/list/banks \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/x-www-form-urlencoded' 
The above command returns following JSON structure:

{
  "banks": [
    {
      "bank_code": "xxxxxxxx",
      "name": "xxxxxxxxxxxxx",
      “recurring_allowed”: true/false,
      “otp_required”: true/false
    },
    ……..
  ]"code": null,
  "message": null
}
HTTP Request
GET /list/banks

HTTP Header
Authorization: Bearer <access token>

Query Parameter
Parameter	Required	Description
reserved_1	Conditional	Reserved Field 1
customer_ip	Yes	IP address of the end user
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version
Payment (Instrument) Types
This API will provide the Payment type (or account type, e.g. Account, Wallet or Debit Card) based on selected issuer/bank.

curl -X GET \
  '<BASE_URL>/list/instruments?bank_code=<bank code>' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/x-www-form-urlencoded' 
The above command returns following JSON structure:

{
  "bankInstruments": [
    {
      "id": "xxxx",
      "name": "xxxxxxx"
    },

  ],
  "code": "00",
  "message": null
}
HTTP Request
GET /list/instruments

HTTP Header
Authorization: Bearer <access token>

Query Parameter
Parameter	Required	Description
bank_code	Yes	Bank Code
customer_ip	Yes	IP address of the end user
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version (optional)
Issuer/Banks By Issuer Instrument ID
This API will provide the available list of issuer/bank based on instrument id.

curl -X GET \
  '<BASE_URL>/list/instrumentbanks?instrument_id=1' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/x-www-form-urlencoded' 
The above command returns following JSON structure:

{
  "banks": [
    {
      "bank_code": "xxxxxxxx",
      "name": "xxxxxxxxxxxxx",
      "recurring_allowed": true/false,
      "otp_required": true/false
    },

  ],
  "code": null,
  "message": null
}
HTTP Request
GET /list/instrumentbanks

HTTP Header
Authorization: Bearer <access token>

Query Parameter
Parameter	Required	Description
instrument_id	Yes	Instrument Id
customer_ip	Yes	IP address of the end user
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version (optional)
Customer Account Validation
This API will be used if you choose to send OTP to registered mobile number of the customer with that respective Issuer/Bank.

# Example using a bank account

curl --location --request POST '<BASE_URL>/customer/validate' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer <access token>' \
--data-urlencode 'basket_id=<basket_id>' \
--data-urlencode 'txnamt=<total amount>' \
--data-urlencode 'customer_mobile_no=<mobile number>' \
--data-urlencode 'customer_email_address=<email address>' \
--data-urlencode 'account_type_id=3' \
--data-urlencode 'bank_code=<bank code>' \
--data-urlencode 'cnic_number=<cnic number>' \
--data-urlencode 'account_number=<bank account number>' \
--data-urlencode 'order_date=<transaction date>' \
--data-urlencode 'otp_required=yes/no' \
--data-urlencode 'recurring_txn=yes/no'
The above command returns following JSON structure:

{
    "code": "",
    "message": "",
    "data_3ds_acsurl": "",
    "data_3ds_pareq": "",
    "data_3ds_html": "",
    "data_3ds_secureid": "",
    "data_3ds_gatewayrecommendation": "",
    "transaction_id": ""
}
HTTP Request
POST /customer/validate

HTTP Header
Authorization: Bearer <access token>

Post Parameter
Common Parameters
Parameter	Required	Description
basket_id	Yes	Merchant's Basket ID/Order ID/Cart ID/Reference ID
txnamt	Yes	Transaction total amount
order_date	Yes	Merchant's order date (format: YYYY-MM-DD HH:mm:ss)
customer_mobile_no	Yes	Customer's mobile number (format: 92-345XXXXXX)
customer_email_address	Yes	Customer's email address
account_type_id	Yes	Account type id / Payment instrument id
merCatCode	Yes	Merchant category code
subMerId	No	Sub merchant id (for Acquirer)
customer_ip	Yes	IP address of the end user
subMerName	No	Sub merchant name (for Acquirer)
subMerAbbr	No	Sub merchant abbreviation (for Acquirer)
secured_hash	Conditional	Secured hash value
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version (optional)
Card Payment Parameters: (UPI,Master,VISA and Paypak Cards)
Parameter	Required	Description
card_number	Yes	Card Number (without spaces/hyphens)
expiry_month	Yes	Card Expiry Month (e.g. 09 for September)
expiry_year	Yes	Card Expiry Year (e.g. 2021)
cvv	Yes	CVV Value
data_3ds_pagemode	For 3DS	Pagemode for 3DS auth. Required value SIMPLE
data_3ds_callback_url	For 3DS	Merchant's call back URL for 3DS auth.
(Refer to 3DS for 3DS verification process)

Bank Account Payment Parameters
Parameter	Required	Description
bank_code	Yes	Bank code
account_number	Yes	Bank account number
account_title	No	Account Title
cnic_number	Yes	Customer's CNIC number registered with Bank
Wallet Payment Parameters
Parameter	Required	Description
bank_code	Yes	Bank code
account_number	Yes	Bank account number
account_title	No	Account Title
cnic_number	Yes	Customer's CNIC number registered with Bank
Initiate Transaction
This API will initiate payment/transaction request without token.

# Example using bank account
curl --location --request POST '<BASE_URL>/transaction' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer <access token>' \
--data-urlencode 'basket_id=<basket_id>' \
--data-urlencode 'txnamt=<total a amount>' \
--data-urlencode 'customer_email_address=<email>' \
--data-urlencode 'account_type_id=3' \
--data-urlencode 'customer_mobile_no=<mobile>' \
--data-urlencode 'account_number=<account number>' \
--data-urlencode 'cnic_number=<cnic number>' \
--data-urlencode 'account_title=<account title>' \
--data-urlencode 'bank_code=<bank code>' \
--data-urlencode 'order_date=<transaction dat3>' \
--data-urlencode 'otp_required=yes/no' \
--data-urlencode 'recurring_txn=yes/no' \
--data-urlencode 'otp=<OTP>' \
--data-urlencode 'transaction_id=<transaction_id>'
HTTP Request
`POST /transaction

HTTP Header
Authorization: Bearer <access token>

The above command returns following JSON structure:

{
  "status_code": "",
  "status_msg": "",
  "rdv_message_key": "",
  "basket_id": "",
  "transaction_id": "<transaction id>",
  "code": ""
}
Common Parameters
Parameter	Required	Description
basket_id	Yes	Merchant's Basket ID/Order ID/Cart ID/Reference ID
txnamt	Yes	Transaction total amount
order_date	Yes	Merchant's order date (format: YYYY-MM-DD HH:mm:ss)
customer_mobile_no	Yes	Customer's mobile number (format: 92-345XXXXXX)
customer_email_address	Yes	Customer's email address
account_type_id	Yes	Account type id / Payment instrument id
merCatCode	Yes	Merchant category code
customer_ip	Yes	IP address of the end user
customer_ip	Yes	IP address of the end user
subMerId	No	Sub merchant id (for Acquirer)
subMerName	No	Sub merchant name (for Acquirer)
subMerAbbr	No	Sub merchant abbreviation (for Acquirer)
secured_hash	Conditional	Secured hash value
otp	Conditional	Not required when issuer managed OTP
transaction_id	Yes	received from customer validation response
eci	Yes	Received from customer validation response
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version (optional)
Card Payment Parameters: (UPI,Master,VISA and Paypak Cards)
Parameter	Required	Description
card_number	Yes	Card Number (without spaces/hyphens)
expiry_month	Yes	Card Expiry Month (e.g. 09 for September)
expiry_year	Yes	Card Expiry Year (e.g. 2021)
cvv	Yes	CVV Value
data_3ds_pagemode	For 3DS	Pagemode for 3DS auth. Required value SIMPLE
data_3ds_callback_url	For 3DS	Merchant's call back URL for 3DS auth.
(Refer to 3DS for 3DS verification process)

Bank Account Payment Parameters
Parameter	Required	Description
bank_code	Yes	Bank code
account_number	Yes	Bank account number
cnic_number	Yes	Customer's CNIC number registered with Bank
account_title	No	Account's title
Wallet Payment Parameters
Parameter	Required	Description
bank_code	Yes	Bank code
account_number	Yes	Bank account number
cnic_number	Yes	Customer's CNIC number registered with Bank
account_title	No	Account's title
Temporary Transaction Token
Get a temporary payment request token, against card, wallet or account details. This token can be used to send a tokenized transaction.

curl --location --request POST '<BASE_URL>/transaction/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer <access token>' \
--data-urlencode 'merchant_user_id=<merchant's user id>' \
--data-urlencode 'user_mobile_number=<user mobile number>' \
--data-urlencode 'basket_id=<basket_id>' \
--data-urlencode 'txnamt=<total amount>' \
--data-urlencode 'account_type=4' \
--data-urlencode 'bank_code=<bank code>' \
--data-urlencode 'cnic_number=<cnic_number>' \
--data-urlencode 'account_number=<account_number>' \
--data-urlencode 'account_title=<account title>'
HTTP Request
POST /transaction/token

HTTP Header
Authorization: Bearer <access token>

The above command returns following JSON structure:

{
  "status_code": "*****",
  "status_msg": "*******",
  "instrument_alias": "**********",
  "instrument_token": "********",
  "transaction_id": "<transaction id>",
  "otp_required": true/false,
  "eci": true/false,
  "data_3ds_acsurl": <3DSVerificationURL>,
  "data_3ds_pareq": <3DSRequestValue>,
  "data_3ds_html": <3DSRedirectionHTMLPage>,
  "data_3ds_secureid": <3DSecureID>,
  "data_3ds_gatewayrecommendation": <3DSRecommendation>
}
Common Parameters
Parameter	Required	Description
basket_id	Yes	Merchant's Basket ID/Order ID/Cart ID/Reference ID
txnamt	Yes	Transaction total amount
order_date	Yes	Merchant's order date (format: YYYY-MM-DD HH:mm:ss)
merchant_user_id	Yes	Merchant's customer/user id
user_mobile_number	Yes	Merchant's customer/user id
instrument_alias	No	Instrument alias
account_type	Yes	Account type id/instrument type id
secured_hash	Conditional	Secured hash value
customer_ip	Yes	IP address of the end user
merCatCode	Yes	Merchant category code
subMerId	No	Sub merchant id (for Acquirer)
subMerName	No	Sub merchant name (for Acquirer)
subMerAbbr	No	Sub merchant abbreviation (for Acquirer)
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version (optional)
Card Payment Parameters: (UPI,Master,VISA and Paypak Cards)
Parameter	Required	Description
card_number	Yes	Card Number (without spaces/hyphens)
expiry_month	Yes	Card Expiry Month (e.g. 09 for September)
expiry_year	Yes	Card Expiry Year (e.g. 2021)
cvv	Yes	CVV Value
data_3ds_pagemode	For 3DS	Pagemode for 3DS auth. Required value SIMPLE
data_3ds_callback_url	For 3DS	Merchant's call back URL for 3DS auth.
Bank Account Payment Parameters
Parameter	Required	Description
bank_code	Yes	Bank code
account_number	Yes	Bank account number
cnic_number	Yes	Customer's CNIC number registered with Bank
account_title	No	Account's title
Wallet Payment Parameters
Parameter	Required	Description
bank_code	Yes	Bank code
account_number	Yes	Bank account number
cnic_number	Yes	Customer's CNIC number registered with Bank
account_title	No	Account's title
Tokenized Transaction
Initiate a payment request based on “temporary” transaction token.

curl --location --request POST '<BASE_URL>/transaction/tokenized' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer <access token>' \
--data-urlencode 'instrument_token=<instrument token>' \
--data-urlencode 'transaction_id=<transaction_id>' \
--data-urlencode 'merchant_user_id=<merchant's user id>' \
--data-urlencode 'user_mobile_number=<user mobile number>' \
--data-urlencode 'basket_id=<basket_id>' \
--data-urlencode 'order_date=<transaction date>' \
--data-urlencode 'txndesc=<description>' \
--data-urlencode 'txnamt=<total amount>' \
--data-urlencode 'otp=<OTP>'
HTTP Request
POST /transaction/tokenized

HTTP Header
Authorization: Bearer <access token>

The above command returns following JSON structure:

{
  "status_code": "",
  "status_msg": "",
  "rdv_message_key": "",
  "basket_id": "",
  "transaction_id": "<transaction id>",
  "code": ""
}
Parameter	Required	Description
instrument_token	Yes	Instrument token
merchant_user_id	Yes	Merchant's customer/user id
user_mobile_number	Yes	Merchant's customer/user id
basket_id	Yes	Merchant's Basket ID/Order ID/Cart ID/Reference ID
txnamt	Yes	Transaction total amount
order_date	Yes	Merchant's order date (format: YYYY-MM-DD HH:mm:ss)
txndesc	Yes	Transaction description
otp	Conditional	Not required when issuer managed OTP
transaction_id	Yes	Received from get transaction token API
eci	Yes	Received from customer validation response
customer_ip	Yes	IP address of the end user
merCatCode	Yes	Merchant category code
subMerId	No	Sub merchant id (for Acquirer)
subMerName	No	Sub merchant name (for Acquirer)
subMerAbbr	No	Sub merchant abbreviation (for Acquirer)
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version
data_3ds_secureid	3DS	required-3DS Only
data_3ds_pares	3DS	required-3DS Only
secured_hash	Conditional	Secured hash
Add Permanent Payment Instrument
This API will allow merchants to add permanent payment instrument on PayFast. A token value will be returned to initiate the transaction for this instrument. (Currently this API is not available due to some bank/scheme level constrains).

curl --location --request POST '<BASE_URL>/user/instruments' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer <access token>' \
--data-urlencode 'merchant_user_id=<merchant's user id>' \
--data-urlencode 'user_mobile_number=<user mobile number>' \
--data-urlencode 'basket_id=<basket_id>' \
--data-urlencode 'txnamt=<total amount>' \
--data-urlencode 'account_type=4' \
--data-urlencode 'bank_code=<bank code>' \
--data-urlencode 'cnic_number=<cnic_number>' \
--data-urlencode 'account_number=<account_number>' \
--data-urlencode 'account_title=<account title>'
HTTP Request
POST /user/instruments

HTTP Header
Authorization: Bearer <access token>

The above command returns following JSON structure:

{
  "status_code": "*****",
  "status_msg": "*******",
  "instrument_alias": "**********",
  "instrument_token": "********"
}
Parameter	Required	Description
merchant_user_id	Yes	Merchant's customer/user id
user_mobile_number	Yes	Merchant's customer/user id
instrument_alias	No	Instrument alias
account_type	Yes	Account type id/Payment type id
transaction_id	Yes	Last successfull transaction id
customer_ip	Yes	IP address of the end user
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version
merCatCode	Yes	Merchant category code
subMerId	No	Sub merchant id (for Acquirer)
subMerName	No	Sub merchant name (for Acquirer)
subMerAbbr	No	Sub merchant abbreviation (for Acquirer)
data_3ds_secureid	3DS	required-3DS Only
secured_hash	Conditional	Secured hash value
Card Payment Parameters: (UPI,Master,VISA and Paypak Cards)
Parameter	Required	Description
card_number	Yes	Card Number (without spaces/hyphens)
expiry_month	Yes	Card Expiry Month (e.g. 09 for September)
expiry_year	Yes	Card Expiry Year (e.g. 2021)
cvv	Yes	CVV Value
Bank Account Payment Parameters
Parameter	Required	Description
bank_code	Yes	Bank code
account_number	Yes	Bank account number
cnic_number	Yes	Customer's CNIC number registered with Bank
account_title	No	Account's title
Wallet Payment Parameters
Parameter	Required	Description
bank_code	Yes	Bank code
account_number	Yes	Bank account number
cnic_number	Yes	Customer's CNIC number registered with Bank
account_title	No	Account's title
OTP for Recurring Transaction
This API will generate an OTP and send to customer, which could be used for sending recurring transaction. OTP will be sent by the bank to customer's mobile number registered with the bank account.

curl --location --request POST '<BASE_URL>/transaction/recurring/otp' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer <access token>' \
--data-urlencode 'instrument_token=<instrument token>' \
--data-urlencode 'merchant_user_id=<merchant's user id>' \
--data-urlencode 'user_mobile_number=<mobile>' \
--data-urlencode 'basket_id=<basket_id>' \
--data-urlencode 'txnamt=<Total amount>' \
--data-urlencode 'order_date=<transaction date>'
{
  "status_code": "",
  "status_msg": "",
  "rdv_message_key": "",
  "basket_id": "",
  "transaction_id": "<transaction id>",
  "code": "",
  “eci”: true/false,
  "data_3ds_acsurl": <3DSVerificationURL>,
  "data_3ds_pareq": <3DSRequestValue>,
  "data_3ds_html": <3DSRedirectionHTMLPage>,
  "data_3ds_secureid": <3DSecureID>,
  "data_3ds_gatewayrecommendation": <3DSRecommendation>
}
HTTP Request
POST /transaction/recurring/otp

HTTP Header
Authorization: Bearer <access token>

Parameter	Required	Description
instrument_token	Yes	Instrument token
merchant_user_id	Yes	Merchant's customer/user id
user_mobile_number	Yes	Merchant's customer/user id
basket_id	Yes	Merchant's Basket ID/Order ID/Cart ID/Reference ID
customer_ip	Yes	IP address of the end user
cvv	Yes	CVV (for “visa/master card” based transaction)
txnamt	Yes	Transaction total amount
order_date	Yes	Merchant's order date (format: YYYY-MM-DD HH:mm:ss)
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version
secured_hash	Conditional	Secured hash
Initiate Recurring Transaction
Initiate payment transaction based on permanent token. This API will serve recurring payment requests. (Currently this API is not available due to some bank/scheme level constrains).

curl --location --request POST '<BASE_URL>/transaction/recurring' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer <access token>' \
--data-urlencode 'instrument_token=<instrument token>' \
--data-urlencode 'transaction_id=<transaction_id>' \
--data-urlencode 'merchant_user_id=<merchant's user id>' \
--data-urlencode 'user_mobile_number=<user mobile number>' \
--data-urlencode 'basket_id=<basket_id>' \
--data-urlencode 'order_date=<transaction date>' \
--data-urlencode 'txndesc=<description>' \
--data-urlencode 'txnamt=<total amount>' \
--data-urlencode 'otp=<OTP>'
HTTP Request
POST /transaction/recurring

HTTP Header
Authorization: Bearer <access token>

The above command returns following JSON structure:

{
  "status_code": "",
  "status_msg": "",
  "rdv_message_key": "",
  "basket_id": "",
  "transaction_id": "<transaction id>",
  "code": ""
}
Parameter	Required	Description
instrument_token	Yes	Instrument token
merchant_user_id	Yes	Merchant's customer/user id
user_mobile_number	Yes	Merchant's customer/user id
basket_id	Yes	Merchant's Basket ID/Order ID/Cart ID/Reference ID
cvv	Yes	cvv (for “visa/master card” based transaction) (string)
txnamt	Yes	Transaction total amount
order_date	Yes	Merchant's order date (format: YYYY-MM-DD HH:mm:ss)
txndesc	Yes	Transaction description
otp	Conditional	Not required when issuer managed OTP
transaction_id	Yes	Received from get transaction token API
eci	Yes	Received from customer validation response
customer_ip	Yes	IP address of the end user
merCatCode	Yes	Merchant category code
subMerId	No	Sub merchant id (for Acquirer)
subMerName	No	Sub merchant name (for Acquirer)
subMerAbbr	No	Sub merchant abbreviation (for Acquirer)
customer_ip	Yes	IP address of the end user
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version
data_3ds_secureid	3DS	required-3DS Only
data_3ds_pares	3DS	required-3DS Only
secured_hash	Conditional	Secured hash
Fetch Permanent Instrument Token
This API will fetch a list of stored permanent instrument tokens against provided merchant's users information. (Currently this API is not available due to some bank/scheme level constrains).

var client = new RestClient("<BASE_URL>/user/instruments?merchant_user_id=<merchant's user id>
&user_mobile_number=<user mobile number>");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddHeader("Authorization", "Bearer <access token>");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
HTTP Request
GET /user/instruments

HTTP Header
Authorization: Bearer <access token>

[
  {
    "instrument_token": "**********",
    "account_type": "*",
    "description": "***",
    "instrument_alias": "**********"
  }
]
Parameter	Required	Description
merchant_user_id	Yes	Merchant's customer/user id
user_mobile_number	Yes	Merchant's customer/user id
customer_ip	Yes	IP address of the end user
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version
secured_hash	Conditional	Secured hash
Delete Instrument
This API will allow merchants to delete stored permanent instrument tokens.

curl --location --request DELETE '<BASE_URL>/user/instruments' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer <access token>' \
--data-urlencode 'merchant_user_id=<merchant user id>' \
--data-urlencode 'user_mobile_number=<user mobile number>' \
--data-urlencode 'instrument_alias=<instrument alias>' \
--data-urlencode 'instrument_token=<instrument_token>'
The above command returns following JSON structure:

{
  "code": Error/SuccessCode,
  "message": ResponseMessage,

}
HTTP Request
DELETE /user/instruments

HTTP Header
Authorization: Bearer <access token>

Parameter	Required	Description
merchant_user_id	Yes	Merchant's customer/user id
user_mobile_number	Yes	Merchant's customer/user id
instrument_token	Yes	Instrument token
instrument_alias	No	Instrument alias
customer_ip	Yes	IP address of the end user
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version
secured_hash	Conditional	Secured hash
Refund Transaction Request
This API will allow merchant to initiate the request for transaction refund in case of any dispute in the transaction.

curl --location --request POST '<BASE_URL>/transaction/refund/<transaction_id>' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer a68adea0cc508db888625bd9a4b9d850521219df68cbf33926c4bbff8c50c00f' \
--data-urlencode 'txnamt=<total amount>' \
--data-urlencode 'refund_reason=Refund'
HTTP Request
POST /transaction/refund/<transaction_id>

HTTP Header
Authorization: Bearer <access token>

The above command returns following JSON structure:

{
  "code": Error/SuccessCode,
  "message": ResponseMessage,

}
Parameter	Required	Description
transaction_id	Yes	Successfull transaction id to be refunded
txnamt	Yes	Original transaction amount
customer_ip	Yes	IP address of the end user
refund_reason	Yes	Reason of refund
reserved_1	Conditional	Reserved Field 1
reserved_2	Conditional	Reserved Field 2
reserved_3	Conditional	Reserved Field 3
api_version	No	API Version
Get Transaction Status
This API will provide transaction status with respect to the transaction id or the merchant's provided basket id/order id.

// Example code for getting transaction status
// using transaction id

curl --location --request GET '<BASE_URL>/transaction/<transaction_id>' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer <access token>'
The above command returns following JSON structure:

{
  "status_code": "",
  "status_msg": "",
  "rdv_message_key": "",
  "basket_id": "",
  "transaction_id": "<transaction id>",
  "code": ""
}
HTTP Request
Transaction status by transaction id
GET /transaction/<transaction_id>

HTTP Header
Authorization: Bearer <access token>

Parameter	Required	Description
api_version	No	API Version
Transaction status by basket id/order id (last)
GET /transaction/basket_id/<basket_id>

HTTP Header
Authorization: Bearer <access token>

Parameter	Required	Description
order_date	Yes	Transaction/Order Date
customer_ip	Yes	IP address of the end user
api_version	No	API Version
HTTP Header
Authorization: Bearer <access token>

Error Codes
CODES	DESCRIPTION
00	Processed OK
002	Time Out
001	Pending
97	Dear Customer, you have an insufficient Balance to proceed
106	Dear Customer, Your transaction Limit has been exceeded please contact your bank
3	You have entered an Inactive Account
14	Entered details are Incorrect
55	You have entered an Invalid OTP/PIN
54	Card Expired
13	You have entered an Invalid Amount
126	Dear Customer your provided Account details are Invalid
75	Maximum PIN Retries has been Exceeded
14	Dear Customer, You have entered an In-Active Card number
15	Dear Customer, You have entered an In-Active Card number
42	Dear Customer, You have entered an Invalid CNIC
423	Dear Customer, We are unable to process your request at the moment please try again later
41	Dear Customer, entered details are Mismatched
801	{0} is your PayFast OTP (One Time Password). Please do not share with anyone.
802	OTP could not be sent. Please try again later.
803	OTP has been sent to your email address.
804	OTP has been sent to your mobile number.
805	OTP Verified
806	OTP could not be verified.
807	Too many attempts. Please try again later in few minutes.
808	Passwords do not match
809	Invalid Password
810	Password could not be changed
811	Password changed successfully
812	Request could not be validated. Please try again.
813	Email address already registered
850	OTP not required because issuer manages OTP itself.
851	OTP required for permanent token.
79	Alternate Success response
9000	Rejected by FRMS
Hashed Parameters
API Reference	Parameters to be Hashed	Data
Temp. Token For Transaction	merchant_user_id	For Card :- merchant_user_id+ user_mobile_number+card_number+ expiry_month+expiry_year+cvv
user_mobile_number	For Account :- merchant_user_id+ user_mobile_number+account_number+ cnic_number
Card	card_number	
expiry_month	For Wallet: - merchant_user_id+ user_mobile_number+account_number+ cnic_number
expiry_year	
cvv	
Account	account_number	
cnic_number	
Wallet	account_number	
cnic_number	
Temp. Token Transaction	instrument_token	instrument_token + merchant_user_id + user_mobile_number + txnamt + otp
merchant_user_id	
user_mobile_number	
txnamt	
otp	
OTP for Recurring Transaction	instrument_token	instrument_token + merchant_user_id + user_mobile_number
merchant_user_id	
user_mobile_number	
Initiate Recurring Transaction	instrument_token	instrument_token + merchant_user_id + user_mobile_number + txnamt + basket_id
merchant_user_id	[+ otp]
user_mobile_number	
txnamt	
basket_id	
otp (if applicable)	
Customer Account Validation		
Common	basket_id	
txnamt	
Card	card_number	basket_id + txnamt + card_number+ expiry_month+expiry_year+cvv
expiry_month	
expiry_year	basket_id + txnamt + account_number + cnic_number
cvv	
Account	account_number	basket_id + txnamt + account_number + cnic_number
cnic_number	
Wallet	account_number	
cnic_number	
Inititate Transaction without Token		
Common	basket_id	
txnamt	
otp	
Card	card_number	basket_id + txnamt + card_number+ expiry_month+expiry_year+cvv+otp
expiry_month	
expiry_year	basket_id + txnamt + account_number + cnic_number+otp
cvv	
Account	account_number	basket_id + txnamt + account_number + cnic_number+otp
cnic_number	
Wallet	account_number	
cnic_number	
txnamt	
otp	
Add Permanent Instrument		
merchant_user_id	merchant_user_id+ user_mobile_number+card_number+ expiry_month+expiry_year+cvv
user_mobile_number	merchant_user_id+ user_mobile_number+account_number+ cnic_number
Card	card_number	
expiry_month	merchant_user_id+ user_mobile_number+account_number+ cnic_number
expiry_year	
cvv	
Account	account_number	
cnic_number	
Wallet	account_number	
cnic_number	
Fetch Permanent Instruments		
merchant_user_id	merchant_user_id+ user_mobile_number
user_mobile_number	
Delete Stored Instrument		
merchant_user_id	merchant_user_id + user_mobile_number + instrument_token
user_mobile_number	
instrument_token	
Note:

“+” symbol denotes simple string concatenation.

Hashed data will be received in “secured_hash” parameter in designated APIs

A key will be provided separately for the hash calculation.

Hash Method: HMAC (Keyed Hash)
Hash Algorithm: sha256
Hash Key: ASCII encoded string |
3D Secure Transaction
In case of Visa/Master card transaction, 3DS verification will be performed.

A. APIs Customer Account Validation, Get Temp. Transaction Token, OTP for Recurring Transaction, will accept an additional parameter data_3ds_pagemode, it has 1 value: SIMPLE.

i. In SIMPLE case, data_3ds_html, will contain a pre-formatted html page which can be used directly to redirect customer to verify OTP. You are not required to modify this value in any case. Any modification in this valaue may lead to unexpected results.

B. APIs Initiate Transaction without Token, Temp. Token Transaction, Initiate Recurring Transaction, will accept 2 more additional parameters for 3DS verification:

i. data_3ds_secureid: This will be returned in response of APIs Customer Account Validation, Get Temp. Transaction Token, OTP for Recurring Transaction (Customer Validation).

ii. data_3ds_pares: This value will be returned in call back URL which is called after 3DS verification, the parameter is returned paRes.

shellcsharpjavascriptphp