Best way to refresh a child React component when props don’t change

I have a Dashboard React component (parent) which contains N widget React components (children).

Each child has a widgetId as the input which uses to call the back-end to get the data it needs to render the widget.

The dashboard has a date parameter (synced and stored in the back-end database) which the user can change. When this changes, I need to re-render all the child widgets as their data will change.

But from a child perspective, their widgetId remains the same (even though on the DB side, date has changed).

How can I tell those child components to re-render themselves without passing the date directly to them?

The reason I prefer not to pass the date parameter to each child is that in the code there are many more parameters that can cause a refresh and passing all of those items to each child component makes my code messy.

WHY I can’t use await inside normal function, but I can use it at top level?

What is the point of this design? It makes no sense to me.

If we are allowed to use await on the top level, we should also be able to use await inside a normal function. Yes I know it will make the function blocking, which is exactly the desired behaviour.

function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

// works
print("bef");
await sleep(1000); // blocking
print("aft");
async function asyncsleep(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

// works
print("bef");
await asyncsleep(1000); // blocking
print("aft");

How to resolve duplicate country code and automatic formatting issues with ngx-intl-tel-input in Angular?

I’m using the ngx-intl-tel-input library in my Angular project to handle phone number input fields. I’ve created a reusable component for the phone number field, and I am passing a FormControl from the parent component to it.

Here is my code:

component.ts

import { Component, Input } from '@angular/core';
import { FormControl } from '@angular/forms';
import { CountryISO, PhoneNumberFormat, SearchCountryField } from 'ngx-intl-tel-input';

@Component({
  selector: 'app-new-phone-no-field-v1',
  templateUrl: './new-phone-no-field-v1.component.html',
  styleUrl: './new-phone-no-field-v1.component.css'
})
export class NewPhoneNoFieldV1Component {
  @Input() control!: FormControl;
  @Input() separateDialCode: boolean = false;
  @Input() maxLength: number = 15;
  @Input() phoneValidation: boolean = true;

  preferredCountries: CountryISO[] = [
    CountryISO.UnitedStates,
    CountryISO.UnitedKingdom,
    CountryISO.India,
    CountryISO.Australia,
    CountryISO.Philippines,
    CountryISO.Thailand,
    CountryISO.SouthAfrica,
    CountryISO.Panama,
    CountryISO.Mexico,
    CountryISO.Indonesia,
    CountryISO.Canada,
    CountryISO.DominicanRepublic,
  ];
  searchCountryField: SearchCountryField[] = [
    SearchCountryField.Iso2,
    SearchCountryField.Name
  ];
  phoneNumberFormat: PhoneNumberFormat = PhoneNumberFormat.National;
  selectedCountryISO: CountryISO = CountryISO.UnitedStates;
}

Component.html

<div class="intl-tel-input-wrapper">
    <ngx-intl-tel-input
      [cssClass]="'custom'"
      [onlyCountries]="preferredCountries"
      [enableAutoCountrySelect]="true"
      [enablePlaceholder]="true"
      [searchCountryFlag]="true"
      [searchCountryField]="searchCountryField"
      [selectFirstCountry]="false"
      [selectedCountryISO]="selectedCountryISO"
      [maxLength]="maxLength"
      [phoneValidation]="phoneValidation"
      [separateDialCode]="true"
      [numberFormat]="phoneNumberFormat"
      name="phone"
      [formControl]="control">
    </ngx-intl-tel-input>
    <div *ngIf="control.touched && control.invalid" class="error-messages">
      <div *ngIf="control.errors?.required">Phone number is required.</div>
      <div *ngIf="!control.errors?.validatePhoneNumber?.valid">Invalid phone number.</div>
    </div>
  </div>
  

Parent.component.html

<app-new-phone-no-field-v1 
  [control]="$any(userForm.get('phone'))"
  [separateDialCode]="true">
</app-new-phone-no-field-v1>

<div class="error error-msg">
  <div *ngIf="f.phone.errors?.required">
    Phone Number is required.
  </div>
  <div *ngIf="f.phone.errors?.pattern">
    Phone Number is invalid.
  </div>
</div>

API Data:
The API returns phone numbers in the format: +919876543210. I’m patching this data to the form using:

this.userForm.patchValue({ phone: data.phone });

Issues:

  • On the UI, the country code appears twice: once in the dial code
    dropdown and again in the phone number field. Example: +91 appears in
    the dropdown and +919876543210 appears in the input field.

  • The phone number is not formatted automatically based on the selected
    country. Example: When I select the US as the country, it doesn’t
    format the number as (201)-555-1234.

Attempts:

  • I’ve tried setting the value property of the FormControl explicitly.
  • Ensured ngx-intl-tel-input configuration matches the documentation.

How can I fix these issues so that:

  • The country code does not appear twice.
  • The phone number is automatically formatted based on the selected country?
    Any guidance would be appreciated!

Image1

Image2

What are the differences between a TreeWalker and a NodeIterator?

Both a TreeWalker object and a NodeIterator object can be used to traverse a DOM tree starting from a given node. The APIs to use them, and even the APIs to create them, createTreeWalker and createNodeIterator look virtually identical. Both seem about equally old and neither seems to be deprecated in favour of the other.

JavaScript is hardly a stranger to duplicative, redundant APIs, but this one seems a pretty bizarre instance. Are there any meaningful differences between them, or is it just a historical accident that both exist?

Jest partial mocking from the same file

I am getting crazy on mocking with Jest. I feel that I have a fairly simple case that I cannot get to work.

I have a service extensions.service with a bunch of functions:

async function getSplitCost() {
  // Check if feature is enabled for the customer
  const isEnabled = await isExtensionFeatureEnabled().catch(err => {
    throw new Error(err);
  });

  return {
    split_cost: [],
    feature_enabled: isEnabled
  }
}

async function isExtensionFeatureEnabled(): Promise<boolean> {
  console.log('Function is executed');
  let serviceStatus = await servicesService.getServiceStatus('service1').catch( err => {
    throw new Error(err.message);
  });
  
  return serviceStatus;
}

export default {
  getSplitCost,
  isExtensionFeatureEnabled,
};

I now want to create a test case that should validate the function getSpitCost and mock the response from isExtensionFeatureEnabled without running the function. Example that I have tried below:

jest.mock('../services/extensions.service', () => ({
  __esModule: true,
  ...jest.requireActual('../services/extensions.service'),
  isExtensionFeatureEnabled: jest.fn().mockResolvedValue(false),
}));
import extensionsService from "../services/extensions.service";


describe("getSpitCost", () => {
  it("should return empty split cost when extension feature is disabled", async () => {
    const result = await extensionsService.getSplitCost();

    expect(result).toEqual({
      split_cost: [],
      feature_enabled: false
    });
  });
});

As I am not calling the function isExtensionFeatureEnabled directly I cannot use spyOn. However, even though I mock the response to false the actual function is being executed. The extension service contains a lot of more functions that are being tested in the same test file, so I cannot mock everything. Also, I cannot split up the two functions to separate files.

How should I handle this?

GSAP SplitText Breaking Words Instead of Lines

I am using GSAP’s SplitText utility to animate text by splitting it into lines, but instead of splitting by lines, it is splitting by words. My goal is to animate the h2 text line-by-line, but it seems like SplitText is not detecting the lines properly.

Here is my JavaScript code:

function animateTextF() {
  document.querySelectorAll(".animate-title-body").forEach(parentElement => {
    const h2Element = parentElement.querySelector(".h2-slide");
    const pElements = [...parentElement.querySelectorAll(".fade-in-p")];
    const btnElement = parentElement.querySelector(".button-1") || parentElement.querySelector(".button-2");

    const splitText = new SplitText(h2Element, {
      type: "lines" // Expecting this to split the text into lines
    });

    const timeline = gsap.timeline({
      paused: true
    });

    if (pElements.length > 0 && btnElement) {
      timeline
        .fromTo(splitText.lines, {
          opacity: 0,
          y: '100%'
        }, {
          opacity: 1,
          y: 0,
          duration: 0.7,
          ease: "expo.out",
          stagger: 0.2
        })
        .fromTo(pElements, {
          opacity: 0
        }, {
          opacity: 1,
          duration: 2,
          ease: "sine.in",
          stagger: 0.2
        })
        .fromTo(btnElement, {
          opacity: 0
        }, {
          opacity: 1,
          duration: 1,
          ease: "sine.in"
        }, 0);
    } else if (pElements.length > 0) {
      timeline
        .fromTo(splitText.lines, {
          opacity: 0,
          y: '100%'
        }, {
          opacity: 1,
          y: 0,
          duration: 0.7,
          ease: "expo.out",
          stagger: 0.2
        })
        .fromTo(pElements, {
          opacity: 0
        }, {
          opacity: 1,
          duration: 2,
          ease: "sine.in",
          stagger: 0.2
        });
    } else {
      timeline
        .fromTo(splitText.lines, {
          opacity: 0,
          y: '100%'
        }, {
          opacity: 1,
          y: 0,
          duration: 1,
          ease: "expo.out",
          stagger: 0.2
        });
    }

    ScrollTrigger.create({
      trigger: parentElement,
      start: "top 75%",
      end: "bottom top",
      scrub: true,
      invalidateOnRefresh: true,
      onEnter: () => timeline.play(),
      onEnterBack: () => timeline.play(),
      onLeave: () => timeline.reverse(),
      onLeaveBack: () => timeline.reverse(),
    });
  });
}
animateTextF();
<div class="animate-title-body">
  <h2 class="oversized">
    aurora
  </h2>
  <h3 class="h2-slide">
    A naturally occurring phenomenon crafted into a premium layer of protection.
  </h3>
  <p class="fade-in-p">
    Charged with cosmic greens and blues, the Aurora's carbon shell is left looking raw and rich as you flash over the horizon in a wavelength of colour usually blind to the naked eye.
  </p>
  <a href="{{store direct_url='eox-aurora'}}">
    <div class="button-2">
      <div class="p2">SHOP NOW</div>
    </div>
  </a>
</div>

HTML audio element currentTime property too high on mobile

I am developing a website with VoIP. I am setting the srcObj property of an audio element with the MediaStream of the user at the other end of the line (using PeerJS). I am also showing the time since the start of the call by displaying the currentTime property of the audio element every second.

This works fine on desktop browsers, but on mobile browsers (specifically Chrome on iOS), the currentTime property is way too high. Like it is equal to several hours when the call has just started.

Has anyone also experienced this or knows why this happens?

How to get image metadata into useChat within Svelte?

I am writing a ChatBot using Svelte and am using the useChat function to interface with my backend to talk to an LLM.

<script lang="ts">
    import ChatList from '$lib/components/ChatList.svelte';
    import PromptForm from '$lib/components/PromptForm.svelte';
    import { useChat, type Message } from 'ai/svelte';
    import { API_BASE_URL } from '$lib/config';

    export let id: string | undefined;
    export let initialMessages: Message[] | undefined;

    const { messages, append, isLoading, input } = useChat({
        initialMessages,
        id,
        api: `${API_BASE_URL}/api/chat`,
    });

    let tempUserInput = '';

    async function handleFinished() {
        await append({ id, content: tempUserInput, role: 'user' });
        tempUserInput = '';
    }
</script>

<div class="flex flex-col min-h-screen bg-background">
    <div class="flex-1 overflow-y-auto pt-6">
        <ChatList {messages} />
    </div>

    <div class="fixed inset-x-0 bottom-0 bg-metallic-gradient">
        <div class="mx-auto sm:max-w-2xl sm:px-4 flex gap-2">
            <div
                class="space-y-4 border-t bg-background px-4 py-2 shadow-subtle sm:rounded-t-xl sm:border md:py-4 flex gap-2 items-center grow"
            >
                <PromptForm on:submit={handleFinished} {input} {isLoading} />
            </div>
        </div>
    </div>
</div>

This is working fine for just chats, but sometimes I want to be able to get back images also from the LLM. According to the documentation, I think I can use experimental_attachments for this.

But I am not sure what to return from my backend to get the information about additional attachments into this experimental_attachments field.

Right now, I am returning a string, and that automatically seem to go inside the message.content field.

I tried returning a json instead that looks like this –

{
    'content': self.settings['dev_test_string'],
    'role': 'assistant',
    'experimental_attachments': {
        'content_type': 'img',
        'url': 'some_string'
    }
}

But this parses the whole of the return json into the message.content field. What should I return from the backend so that I have more control over what goes into the message object?

ReactJS strange state behaviour

I have two components: legacy parent class component, let’s call it RowComponent, and child ModalComponent. State of those modal is located in parent component:

this.state = {
  modalIsOpen: false,
};

When I click on button it executes function which changes modalIsOpen state to true and modal is popped up. So far so good.

showRequestModal() {
  this.setState({ modalIsOpen: true });
}

Inside my child component I have isOpen state which is property that relies on modalIsOpen

<Modal
  width={600}
  destroyOnClose
  open={isOpen}
  onCancel={hideModalHandler}
/>

hideModalHandler is function that passed as property like this:

hideModalHandler={this.hideRequestModal}

That’s how my hideRequestModal looks like (it bind properly):

hideRequestModal() {
  console.log('Executing hideRequestModal');
  this.setState({ modalIsOpen: false }, () => {
    console.log('callback - modalIsOpen:', this.state.modalIsOpen);
  });
}

The real magic (or what I would say my lack of knowledge) starts here. When I try to close my modal from child component I can see text Executing hidRequestModal without changing the state (which I see from the callback). But the most bizarre thing is when I click ESC button on modal it closes (and state is updating). So my question is what the hell is going on and how to close modal on click also, not by clicking Escape key. So I suppose there is some conflicts in events or something like that. I consider rewriting parent component to be function component and maybe it will solve the issue but I don’t know. Appreciate any help

Uncaught TypeError: Cannot set properties of undefined (setting ‘_DT_CellIndex’) for binding values

I have below html for which I am getting error as

Uncaught TypeError: Cannot set properties of undefined (setting ‘_DT_CellIndex’)

function dispalySignOffSheetFTTX(ReportType, Month, DataList) {
var _reportType = (ReportType == 'ALL') ? "PAN INDIA" : ReportType;
var _month = Month;
var table = $('#grdCicleDatatable');
$(table).empty();
var thead = "";
var datalist = JSON.parse(DataList);
if (ReportType == 'ALL') {        
    thead = "<thead>< tr ><th rowspan='2' class='text-left'>Maintenance Zone</th><th colspan='3'>FTTX</th><th colspan='3'>Grand Total</th></tr><tr><th>UG</th><th>Aerial</th><th>MDU</th><th>UG</th><th>Aerial</th><th>MDU</th></tr></thead >";
}

var tbody = "<tbody>";
table.append(thead);
table.append(tbody);
if (datalist != null && datalist.length > 0) {
    var grandTotalUG = 0;
    var grandTotalAR = 0;
    var grandTotalMDU = 0;
    $.each(datalist, function (key, val) {

        val.NE_LENGTH = val.NE_LENGTH == null ? 0 : parseFloat(val.NE_LENGTH);
        val.UG_LENGTH = val.UG_LENGTH == null ? 0 : parseFloat(val.UG_LENGTH);
        val.AR_LENGTH = val.AR_LENGTH == null ? 0 : parseFloat(val.AR_LENGTH);
        val.MDU_LENGTH = val.MDU_LENGTH == null ? 0 : parseFloat(val.MDU_LENGTH);

        grandTotalUG = val.UG_LENGTH;
        grandTotalUG = grandTotalUG.toFixed(3);
        grandTotalAR = val.AR_LENGTH;
        grandTotalAR = grandTotalAR.toFixed(3);
        grandTotalMDU = val.MDU_LENGTH;
        grandTotalMDU = grandTotalMDU.toFixed(3);

        var tr = "<tr id='" + val.ITEM + "'><td> " + val.ITEM + "</td><td class='text-center'> " + val.UG_LENGTH + "</td><td class='text-center'> " + val.AR_LENGTH + "</td><td class='text-center'> " + val.MDU_LENGTH + "</td><td class='text-center'> " + grandTotalUG + "</td><td class='text-center'> " + grandTotalAR + "</td><td class='text-center'> " + grandTotalMDU + "</td></tr>";

        table.append(tr);
    });

    table.append('</tbody>');
}

//var footer = "<tfoot><tr><th colspan='1' style='text-align:center'><b>Total:</b></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th></tr></tfoot>";

var footer = "<tfoot><tr><th colspan='1' style='text-align:center'><b>Total:</b></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th></tr></tfoot>";

table.append(footer);

oTable = $(table).dataTable({
    dom: 'tp',
    "dom": 'tp<"bottom"B><"clear">',
    "searching": false,
     responsive: true,
    "autoWidth": true,
    "bDestroy": true,
    "pageLength": 6,
    paging: false,
    "columnDefs": [
        { "width": "38.4%", "targets": 0 },
        { "width": "7.7%", "targets": 1 },
        { "width": "7.7%", "targets": 2 },
        { "width": "7.7%", "targets": 3 },
        { "width": "7.7%", "targets": 4 },
        { "width": "7.7%", "targets": 5 },
        { "width": "7.7%", "targets": 6 },
        { "width": "7.7%", "targets": 7 }

    ],
    buttons: [            
        {
            "extend": "excelHtml5", "text": "Export to Excel", "filename": _reportType + "_Fttx_SignOffSheet_" + _month,
            title: 'Sign Of Sheet of ' + _reportType + ' Circle for ' + _month + ' Month',
            messageBottom: '',
            exportOptions: {
                columns: ':visible',
                format: {
                    header: function (data, columnindex, trDOM, node) {                            
                        return GetColumnPrefixFTTX(columnindex) + data;
                    }
                }
            }
        }
    ],
    "footerCallback": function (row, data, start, end, display) {
        var api = this.api(), data;

        // converting to interger to find total
        var intVal = function (i) {
            return typeof i === 'string' ?
                i.replace(/[$,]/g, '') * 1 :
                typeof i === 'number' ?
                i : 0;
        };

        // computing column Total of the complete result 
        var FTTXUGTotal = api
            .column(1)
            .data()
            .reduce(function (a, b) {
                return intVal(a) + intVal(b);
            }, 0).toFixed(2);

        var FTTXARTotal = api
            .column(2)
            .data()
            .reduce(function (a, b) {
                return intVal(a) + intVal(b);
            }, 0).toFixed(2);

        var FTTXMDUTotal = api
            .column(3)
            .data()
            .reduce(function (a, b) {
                return intVal(a) + intVal(b);
            }, 0).toFixed(2);

        var TotFTTXUGTotal = api
            .column(4)
            .data()
            .reduce(function (a, b) {
                return intVal(a) + intVal(b);
            }, 0).toFixed(2);

        var TotFTTXARTotal = api
            .column(5)
            .data()
            .reduce(function (a, b) {
                return intVal(a) + intVal(b);
            }, 0).toFixed(2);

        var TotFTTXMDUTotal = api
            .column(6)
            .data()
            .reduce(function (a, b) {
                return intVal(a) + intVal(b);
            }, 0).toFixed(2);

        // Update footer by showing the total with the reference of the column index 
        $(api.column(0).footer()).html('Total');
        $(api.column(1).footer()).html(FTTXUGTotal);
        $(api.column(2).footer()).html(FTTXARTotal);
        $(api.column(3).footer()).html(FTTXMDUTotal);
        $(api.column(4).footer()).html(TotFTTXUGTotal);
        $(api.column(5).footer()).html(TotFTTXARTotal);
        $(api.column(6).footer()).html(TotFTTXMDUTotal);
    },
    initComplete: function () {
        var btns = $('.dt-button');
        btns.addClass('btn btn-danger button');
        btns.removeClass('dt-button');


    }

});

if (CurrentGroupName == UserGrouop.NHQPMO) {
    $('.buttons-pdf').css("display", "none");
} else {
    $('.buttons-excel').css("display", "none");

}

}

Facing issues in integrating payment gateway in php Laravel

I am a novice in php and am facing a issue while integrating Razor Pay in my website.

It gives me this array when asked for payment gateway page. This array is probably from the dd($input)

array:2 [▼ // Modules/Gateways/Http/Controllers/RazorPayController.php:77
  "_token" => "A String with random data"
  "payment_id" => "A String with random data"
]

In the url, if I change /payments? with /pay?, it starts working.

Sweetalert2 not showing in the code, but show in the console

i’m using sweetalert2 for my code, it not works when i use in my code. So, i test the script on the inspect element console on my app. And it works. Anyone can help?

$query = mysqli_query($koneksi,"SELECT `name` FROM `tb_bengkel` WHERE `name` = '$name'");
if(mysqli_num_rows($query) == true)
{
"<script>
var Toast = Swal.mixin({
    toast: false,
    position: 'center',
    showConfirmButton: false,
    timer: 2000
  });
Toast.fire({
    icon: 'error',
    title: 'Name has been used!'
  })
  </script>";
}

hitpay payment integration not able to implement in PHP

I am trying to implement the HitPay payment gateway.

I can successfully get the response from the payment request.
I am also able to complete the payment.
However, after the payment is made, I need to store the transaction details such as: Transaction ID, Transaction Status, and other relevant details.
For some reason, this part is not working as expected.
I have tried many approaches, but I am unable to identify the issue.

Here is the documentation.

    <!DOCTYPE html>
   <html lang="en">
  <head>
    <meta charset="utf-8">
    <title>HitPay Checkout Drop-in Test Page</title>
  </head>
  

    <body>    
        <script src="https://hit-pay.com/hitpay.js"></script>
        <script>
          // Callback for successful payment
          function onSuccess(data) {
            console.log('onSuccess called:', data);
    
            // Verify if valid data is received
            if (!data) {
              console.error('No data received in onSuccess');
              const el = document.createElement('p');
              el.innerHTML = 'Payment Success, but no data received.';
              document.body.appendChild(el);
              return;
            }
    
            // Display a success message
            const el = document.createElement('p');
            el.innerHTML = 'Payment Success';
            document.body.appendChild(el);
    
            // Send payment details to backend for storage
            fetch('store_payment_details.php', {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
              },
              body: JSON.stringify(data), // Sending received data
            })
              .then((response) => response.json())
              .then((response) => {
                console.log('Payment details stored successfully:', response);
              })
              .catch((error) => {
                console.error('Error storing payment details:', error);
              });
          }
    
          // Callback when payment is closed
          function onClose(data) {
            console.log('onClose called:', data);
            const el = document.createElement('p');
            el.innerHTML = 'Payment Closed';
            document.body.appendChild(el);
          }
    
          // Callback for errors
          function onError(error) {
            console.error('onError called:', error);
            const el = document.createElement('p');
            el.innerHTML = 'Error: ' + (error.message || error);
            document.body.appendChild(el);
          }
    
          // Function to initiate payment
          function onClick() {
            const carttotalValue = 1; // Dynamic total value (e.g., 100 for testing)
            const paymentData = {
              amount: carttotalValue, // Ensure amount matches cart total
              currency: 'SGD',
              email: '[email protected]',
              phone: '1111',
              order_id: '123',
            };
    
            // Make AJAX request to backend to create the payment request
            fetch('hitpay_payment_request.php', {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
              },
              body: JSON.stringify(paymentData), // Send dynamic data
            })
              .then((response) => response.json())
              .then((response) => {
                const data = response.data;
                console.log("data=",data);
                if (data && data.id) {
                  // Initialize HitPay only once
                  if (!window.HitPay.inited) {
                    window.HitPay.init(
                      'https://securecheckout.hit-pay.com/payment-request/@curvv-tech-private-limited',
                      {
                        domain: 'hit-pay.com',
                        apiDomain: 'hit-pay.com',
                      },
                      {
                        onClose: onClose,
                        onSuccess: onSuccess,
                        onError: onError,
                      }
                    );
                  }
    
                  // Trigger the payment gateway toggle with the provided payment_url
                  window.HitPay.toggle({
                    paymentRequest: data.id, // Dynamic payment ID
                    amount: carttotalValue,
                    name: 'Anbu',
                    phone: '93500239',
                  });
                } else {
                  // Handle error if no payment URL is received
                  console.error('Error: Payment URL not received');
                }
              })
              .catch((error) => {
                console.error('AJAX Error:', error);
              });
          }
        </script>
    
        <button onclick="onClick()">Pay</button>
      </body>
    </html>

hitpay_payment_request.php:

 <?php
//hitpay_payment_request.php
header("Content-Type: application/json");
header("X-Content-Type-Options: nosniff");

$data = json_decode(file_get_contents('php://input'), true);
if (json_last_error() !== JSON_ERROR_NONE) {
    http_response_code(400);
    echo json_encode(["error" => "Invalid JSON payload"]);
    exit;
}

// Sanitize input
$amount = filter_var($data['amount'] ?? 0, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
$currency = filter_var($data['currency'] ?? 'SGD', FILTER_SANITIZE_STRING);
$email = filter_var($data['email'] ?? '[email protected]', FILTER_VALIDATE_EMAIL);
$phone = filter_var($data['phone'] ?? '00000000', FILTER_SANITIZE_STRING);
$order_id = filter_var($data['order_id'] ?? 'ORD123456', FILTER_SANITIZE_STRING);

$requestData = [
    "amount" => $amount,
    "currency" => $currency,
    "email" => $email,
    "phone" => $phone,
    "reference_number" => $order_id,
    "redirect_url" => "https://domain.sg/hit-pay/hitpay_payment_success.php",
    "webhook" => "https://domain.sg/hit-pay/hitpay_payment_details.php",
];

$apiKey = 'my_api_key';
$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => "https://api.hit-pay.com/v1/payment-requests",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => "POST",
    CURLOPT_POSTFIELDS => json_encode($requestData),
    CURLOPT_HTTPHEADER => [
        "Content-Type: application/json",
        "X-BUSINESS-API-KEY: $apiKey"
    ],
]);

$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);

if ($err) {
    error_log("cURL Error: $err");
    http_response_code(500);
    echo json_encode(["error" => "cURL Error: $err"]);
} else {
    $responseData = json_decode($response, true);
    error_log("API Response: " . $response);
    if (isset($responseData['id'])) {
        echo json_encode(["data" => $responseData]);
    } else {
        http_response_code(500);
        echo json_encode(["error" => "Invalid response from HitPay API"]);
    }
}
?>

response getting:

    {
    "data": {
        "id": "9de0c639-c986-4743-a1ae-70b998068536",
        "name": null,
        "email": "[email protected]",
        "phone": "1111",
        "amount": "1.00",
        "currency": "SGD",
        "is_currency_editable": false,
        "status": "pending",
        "purpose": null,
        "reference_number": "123",
        "payment_methods": [
            "card",
            "paynow_online"
        ],
        "url": "https://securecheckout.hit-pay.com/payment-request/@curvv-tech-private-limited/9de0c639-c986-4743-a1ae-70b998068536/checkout",
        "redirect_url": "https://domain.sg/hit-pay/hitpay_payment_success.php",
        "webhook": "https://domain.sg/hit-pay/hitpay_payment_details.php",
        "send_sms": false,
        "send_email": false,
        "sms_status": "pending",
        "email_status": "pending",
        "allow_repeated_payments": false,
        "expiry_date": null,
        "address": null,
        "line_items": null,
        "executor_id": null,
        "created_at": "2025-01-03T14:53:59",
        "updated_at": "2025-01-03T14:53:59",
        "staff_id": null,
        "business_location_id": null
    }
}

hitpay_payment_details.php:

<?php
// hitpay_payment_deatils.php
header("Content-Type: application/json");
header("X-Content-Type-Options: nosniff");

// Include database connection
include '../rest_api/config.php';

$data = json_decode(file_get_contents("php://input"), true);

if (isset($data['payment_id'], $data['order_id'], $data['payment_status'], $data['payment_gross'], $data['currency_code'], $data['datetime'])) {
    $payment_id = $data['payment_id'];
    $order_id = $data['order_id'];
    $payment_status = $data['payment_status'];
    $payment_gross = $data['payment_gross'];
    $currency_code = $data['currency_code'];
    $datetime = $data['datetime'];

    $stmt = $conn->prepare("INSERT INTO payments (payment_id, item_number, txn_id, payment_gross, currency_code, payment_status, datetime, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, 1)");
    $stmt->bind_param("sssssss", $payment_id, $order_id, $payment_id, $payment_gross, $currency_code, $payment_status, $datetime);

    if ($stmt->execute()) {
        echo json_encode(["status" => "success", "message" => "Payment details inserted successfully."]);
    } else {
        echo json_encode(["status" => "error", "message" => "Failed to insert payment details."]);
    }

    $stmt->close();
} else {
    echo json_encode(["status" => "error", "message" => "Invalid input data."]);
}
?>

hitpay_payment_success.php

<?php
//hitpay_payment_success
// Database connection
include '../rest_api/config.php';

// Get query parameters
$order_id = $_GET['order_id'] ?? null; // Retrieve order_id
$paymentRequestId = $_GET['payment_request_id'] ?? null;
$status = $_GET['status'] ?? null;
$userIp = $_SERVER['REMOTE_ADDR'];

if ($paymentRequestId && $status && $order_id) {
    // Insert into database
    $stmt = $conn->prepare("INSERT INTO payment_redirect_logs (order_id, payment_request_id, status, user_ip) VALUES (?, ?, ?, ?)");
    $stmt->bind_param("ssss", $order_id, $paymentRequestId, $status, $userIp);
    $stmt->execute();
    $stmt->close();

    echo $status === 'completed' ? "<h1>Payment Successful</h1>" : "<h1>Payment Failed</h1>";
} else {
    echo "<h1>Invalid Request</h1>";
}
?>

Php based parser script for WhatsApp webhook json [closed]

I have a working WhatsApp Webhook json dumped into a text file.

Rather than writing from scratch, is there some kind of readymade PHP based script that would parse that json to a readable format (combine messages from same number along with displaying downloaded media, something like WhatsApp web gives).

Only display received messages, no sending required.

Any lead would helpful.

Thanks.

Execute the same javascript function from different onclick events

I have the following javascript code that plays a video when I click the “play” button in HTML.

How can I play different videos (multiple play buttons in HTML) without duplicating the JavaScript code?

$('#video-icon').on('click', function(e) {
  e.preventDefault();
  $('.video-popup').css('display', 'flex');
  $('.iframe-src').slideDown();
});

$('.video-popup').on('click', function(e) {
  var $target = e.target.nodeName;
  var video_src = $(this).find('iframe').attr('src');
  if ($target != 'IFRAME') {
    $('.video-popup').fadeOut();
    $('.iframe-src').slideUp();
    $('.video-popup iframe').attr('src', " ");
    $('.video-popup iframe').attr('src', video_src);
  }
});
<a class="video-section prelative text-center white" role="button" id="video-icon" aria-hidden="true">
  Play
  <div class="video-popup">
    <div class="video-src">
      <div class="iframe-src">
        <iframe src="the video link" allowfullscreen></iframe>
      </div>
    </div>
  </div>
</a>