<?php
/**
 * MachForm
 * 
 * © 2007–2025 Appnitro Software. All rights reserved.
 * 
 * This source code is proprietary and may not be copied, modified,
 * or distributed in any form without express written permission from Appnitro Software.
 * 
 * License information: https://www.machform.com/license-agreement/
 */

	require('config.php');
	require('lib/db-session-handler.php');
	require('includes/init-form.php');
	require('includes/db-core.php');
	require('includes/language.php');

	require('includes/helper-functions.php');
	require('includes/filter-functions.php');
	require('includes/post-functions.php');
	require('includes/entry-functions.php');
	require('lib/dompdf/autoload.inc.php');
	require('lib/google-api-client/vendor/autoload.php');
	require('lib/phpmailer/vendor/autoload.php');
	require('lib/HttpClient.class.php');
	require('lib/stripe/init.php');
	
    $use_debug_mode = false; //set to 'true' to display verbose log mode into error log file
	
	class PaymentExistException extends Exception {}
	class UnsupportedEventException extends Exception {}

    //read the raw POST body
    $payload    = @file_get_contents("php://input");
    $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
    $event      = null;
	$error_message = '';

    try{

        $dbh            = mf_connect_db();
		$mf_settings    = mf_get_settings($dbh);
        $payload_json   = json_decode($payload);

		//we currently only handle payment_intent.succeeded event
		//if we receive any other event, we need to stop the process
		if($payload_json->type != 'payment_intent.succeeded'){
			throw new UnsupportedEventException("Webhook event type: " . $payload_json->type. " - ignoring this event.");
		}else{
			if($use_debug_mode){
				error_log("Webhook event type: " . $payload_json->type);
				error_log("Webhook content: " . $payload);
			}
		}

        //parsed Form ID is untrusted, so we need to validate it later down below
        //we need to get the form id upfront to retrieve the webhook secret from ap_forms table
        $parsed_form_id = (int) $payload_json->data->object->metadata->{'Form ID'}; 

        //get form properties data
		$query 	= "select 		
                        payment_enable_merchant,
                        payment_merchant_type,
                        payment_currency,
                        payment_ask_billing,
                        payment_ask_shipping,
                        payment_stripe_live_secret_key,
                        payment_stripe_test_secret_key,
                        payment_stripe_enable_test_mode,
                        payment_stripe_live_webhook_secret,
						payment_stripe_test_webhook_secret,
                        payment_enable_recurring,
                        logic_success_enable,
                        form_redirect_enable,
                        form_redirect  
                    from 
                        ".MF_TABLE_PREFIX."forms 
                where 
                        form_id=? and form_active=1";
        $params = array($parsed_form_id);

        $sth = mf_do_query($query,$params,$dbh);
        $row = mf_do_fetch_result($sth);

        if(!empty($row)){	
            $payment_enable_merchant 	= (int) $row['payment_enable_merchant'];
            $payment_merchant_type		= $row['payment_merchant_type'];
            $payment_currency 	   		= strtolower($row['payment_currency']);
            $payment_ask_billing 	 	= (int) $row['payment_ask_billing'];
            $payment_ask_shipping 	 	= (int) $row['payment_ask_shipping'];
            $payment_enable_recurring 	= (int) $row['payment_enable_recurring'];	
            $logic_success_enable 	 	= (int) $row['logic_success_enable'];	
            $form_redirect_enable 	 	= (int) $row['form_redirect_enable'];
            $form_redirect 	   		 	= trim($row['form_redirect'] ?? '');
            $payment_stripe_enable_test_mode 	= (int) $row['payment_stripe_enable_test_mode'];
            $payment_stripe_live_secret_key	 	= trim($row['payment_stripe_live_secret_key'] ?? '');
			$payment_stripe_live_webhook_secret	= trim($row['payment_stripe_live_webhook_secret'] ?? '');
            $payment_stripe_test_secret_key	 	= trim($row['payment_stripe_test_secret_key'] ?? '');
            $payment_stripe_test_webhook_secret	= trim($row['payment_stripe_test_webhook_secret'] ?? '');
        }

		//initialize Stripe
		if(!empty($payment_stripe_enable_test_mode)){
			$stripe_secret_key = $payment_stripe_test_secret_key;
			$stripe_webhook_secret = $payment_stripe_test_webhook_secret;
		}else{
			$stripe_secret_key = $payment_stripe_live_secret_key;
			$stripe_webhook_secret = $payment_stripe_live_webhook_secret;
		}

		$stripe = new \Stripe\StripeClient([
			'api_key' => $stripe_secret_key
		]);

		//validate the payload signature
        $event = \Stripe\Webhook::constructEvent($payload, $sig_header, $stripe_webhook_secret);

		//get the form id and record id from the payload
		$form_id 		   = (int) $event->data->object->metadata->{'Form ID'};
		$payment_record_id = (int) $event->data->object->metadata->{'Entry Number'};

		//if the form id from the payload is different from the form id in the database
		//then we need to stop the process
		if($form_id != $parsed_form_id){
			$error_message = "Form ID mismatch. Parsed Form ID: " . $parsed_form_id . ", Database Form ID: " . $form_id;
			throw new Exception($error_message);
		}

		//validate the request, make sure stripe is really enabled for this form
		if(empty($payment_enable_merchant) || $payment_merchant_type != 'stripe'){
			throw new Exception("Stripe is not enabled for this form.");
		}

		//check ap_form_payments table, make sure there is no record for this entry id yet
		$query = "SELECT COUNT(*) as total FROM `".MF_TABLE_PREFIX."form_payments` WHERE form_id = ? AND record_id = ? AND payment_id IS NOT NULL";
		$params = array($form_id,$payment_record_id);
		$sth = mf_do_query($query,$params,$dbh);
		$row = mf_do_fetch_result($sth);
		if($row['total'] > 0){
			throw new PaymentExistException("Payment record already exists for this entry id.");
		}

		//validate the payment intent id and client secret
		$payment_intent_id = $event->data->object->id;
		$payment_intent_client_secret = $event->data->object->client_secret;
		
		if(empty($payment_intent_id) || empty($payment_intent_client_secret)){
			throw new Exception("Payment intent id or client secret is missing.");
		}

		$payment_intent = $stripe->paymentIntents->retrieve($payment_intent_id);
		if($payment_intent->client_secret != $payment_intent_client_secret){
			throw new Exception("Invalid payment intent client secret.");
		}

		//check if the provided form_id and payment record id is correct
		if($payment_intent->metadata['Form ID'] != $form_id || $payment_intent->metadata['Entry Number'] != $payment_record_id){
			throw new Exception("Invalid form id or payment record id.");
		}

		if($event->type == 'payment_intent.succeeded'){
			if($use_debug_mode){
				error_log("Processing payment_intent.succeeded event");
			}

			//handle successful payment intent
			$payment_amount = $payment_intent->amount_received / 100; //convert to dollar amount
			$payment_id = $payment_intent->id;
			$payment_currency = $payment_intent->currency;

			//if there is billing name, update the customer name
			if(!empty($payment_intent->payment_method)){
				$paymentMethod = $stripe->paymentMethods->retrieve($payment_intent->payment_method);
				if(!empty($paymentMethod->billing_details->name)){
					$payment_fullname = $paymentMethod->billing_details->name;
					$stripe->customers->update($payment_intent->customer, [
						'name' => $payment_fullname
					]);
				}
			}

			//get billing details, if any
			if(!empty($paymentMethod->billing_details->address)){
				$billing_street = $paymentMethod->billing_details->address->line1."\n".$paymentMethod->billing_details->address->line2;
				$billing_city = $paymentMethod->billing_details->address->city;
				$billing_state = $paymentMethod->billing_details->address->state;
				$billing_zipcode = $paymentMethod->billing_details->address->postal_code;
				$billing_country = $paymentMethod->billing_details->address->country;
			}

			//get shipping details, if any
			if(!empty($payment_intent->shipping)){
				$shipping_street = $payment_intent->shipping->address->line1."\n".$payment_intent->shipping->address->line2;
				$shipping_city = $payment_intent->shipping->address->city;
				$shipping_state = $payment_intent->shipping->address->state;
				$shipping_zipcode = $payment_intent->shipping->address->postal_code;
				$shipping_country = $payment_intent->shipping->address->country;
			}

			//make sure to delete empty record from ap_form_payments table related with current entry_id
			//empty record is possible when the user manually changed the payment status previously
			$query = "DELETE FROM `".MF_TABLE_PREFIX."form_payments` WHERE form_id = ? AND record_id = ? and payment_id IS NULL";
			$params = array($form_id,$payment_record_id);
			mf_do_query($query,$params,$dbh);

			//insert into ap_form_payments table
			$query = "INSERT INTO `".MF_TABLE_PREFIX."form_payments`(
									`form_id`, 
									`record_id`, 
									`payment_id`, 
									`date_created`, 
									`payment_date`, 
									`payment_status`, 
									`payment_fullname`, 
									`payment_amount`, 
									`payment_currency`, 
									`payment_test_mode`,
									`payment_merchant_type`, 
									`status`, 
									`billing_street`, 
									`billing_city`, 
									`billing_state`, 
									`billing_zipcode`, 
									`billing_country`, 
									`same_shipping_address`, 
									`shipping_street`, 
									`shipping_city`, 
									`shipping_state`, 
									`shipping_zipcode`, 
									`shipping_country`) 
							VALUES (
									:form_id, 
									:record_id, 
									:payment_id, 
									:date_created, 
									:payment_date, 
									:payment_status, 
									:payment_fullname, 
									:payment_amount, 
									:payment_currency, 
									:payment_test_mode,
									:payment_merchant_type, 
									:status, 
									:billing_street, 
									:billing_city, 
									:billing_state, 
									:billing_zipcode, 
									:billing_country, 
									:same_shipping_address, 
									:shipping_street, 
									:shipping_city, 
									:shipping_state, 
									:shipping_zipcode, 
									:shipping_country)";		

			$params = array();
			$params[':form_id'] 		  	= $form_id;
			$params[':record_id'] 			= $payment_record_id;
			$params[':payment_id'] 			= $payment_id;
			$params[':date_created'] 		= date("Y-m-d H:i:s");
			$params[':payment_date'] 		= date("Y-m-d H:i:s");
			$params[':payment_status'] 		= 'paid';
			$params[':payment_fullname']  	= $payment_fullname;
			$params[':payment_amount'] 	  	= $payment_amount;
			$params[':payment_currency']  	= $payment_currency;
			$params[':payment_test_mode'] 	= $payment_stripe_enable_test_mode;
			$params[':payment_merchant_type'] = 'stripe';
			$params[':status'] 			  	= 1;

			$params[':billing_street'] 		= $billing_street;
			$params[':billing_city']		= $billing_city;
			$params[':billing_state'] 		= $billing_state;
			$params[':billing_zipcode'] 	= $billing_zipcode;
			$params[':billing_country'] 	= $billing_country;

			$params[':same_shipping_address'] = 0;

			$params[':shipping_street'] 	= $shipping_street;
			$params[':shipping_city'] 		= $shipping_city;
			$params[':shipping_state'] 		= $shipping_state;
			$params[':shipping_zipcode'] 	= $shipping_zipcode;
			$params[':shipping_country'] 	= $shipping_country;
						
			mf_do_query($query,$params,$dbh);

			if($use_debug_mode){
				error_log("Completed inserting payment record into ap_form_payments table");
			}

			//process any delayed notifications
			mf_process_delayed_notifications($dbh,$form_id,$payment_record_id);

			if($use_debug_mode){
				error_log("Completed processing delayed notifications");
			}

		}else{
			//we currently only handle payment_intent.succeeded event
			//if we receive any other event, we need to stop the process
			$error_message = "Unknown event type: " . $event->type;

			throw new Exception($error_message);
		}

    }catch(\UnexpectedValueException $e) {
        //invalid payload
        $error_message = "Error parsing payload: " . $e->getMessage();

        http_response_code(400);
    } catch(\Stripe\Exception\SignatureVerificationException $e) {
        //invalid signature
        $error_message = "Error verifying webhook signature: " . $e->getMessage();

        http_response_code(200);
    }catch(PaymentExistException $e){
		$error_message = "Exception: ".$e->getMessage();

		http_response_code(200);
	}catch(UnsupportedEventException $e){
		$error_message = "Exception: ".$e->getMessage();

		http_response_code(200);
	}catch(Exception $e){
		$error_message = "Exception: ".$e->getMessage();

		http_response_code(400);
	}catch(Throwable $e){
		$error_message = "Error: ".$e->getMessage()." in ".basename($e->getFile())." on line ".$e->getLine();
		
		http_response_code(400);
	}finally{
		//log error message
        if(!empty($error_message)){
            error_log($error_message);
        }

		if($use_debug_mode){
			error_log("Webhook process completed.");
		}
    }