pronamic / wp-pay-core

Core components for the WordPress payment processing library. This library is used in the WordPress plugin Pronamic Pay: https://www.pronamicpay.com/, but also allows other plugin developers to set up a payment plugin.
https://www.wp-pay.org/
GNU General Public License v3.0
27 stars 3 forks source link

Issue in gateways with METHOD_HTML_FORM redirection method, if multidimensional is passed. #73

Closed knit-pay closed 2 years ago

knit-pay commented 2 years ago

The below code fails if we pass a multidimensional array to html_hidden_fields function.

https://github.com/pronamic/wp-pay-core/blob/ecb3e112d21b01d70cbea53f1caaf92303fc5b17/src/Util.php#L211-L213

In Some payment gateways, data is passed as multidimensional array. See example below https://razorpay.com/docs/payments/payment-gateway/web-integration/hosted/build-integration/#code-to-add-pay-button

Here field prefill[name] is creating an issue.

Before running the loop, if we will call below function, issue will be fixed.

$data = self::flatten_array($data);
    public static function flatten_array($array, $prefix = '', $parent = true) {
        $result = array();
        foreach($array as $key=>$value) {
            if(is_array($value)) {
                if($parent){
                    $result = $result + self::flatten_array($value, "{$key}", false);
                }else {
                    $result = $result + self::flatten_array($value, "{$prefix}[{$key}]", false);
                }
            } else if ($parent){
                $result["{$prefix}{$key}"] = $value;
            }
            else {
                $result["{$prefix}[{$key}]"] = $value;
            }
        }
        return $result;
    }

Do you have any better suggestion for the same?

remcotolsma commented 2 years ago

You want to pass in a an array like:

return [
    'key_id'       => 'YOUR_KEY_ID',
    'amount'       => '1001',
    'order_id'     => 'razorpay_order_id',
    'name'         => 'Acme Corp',
    'description'  => 'A Wild Sheep Chase',
    'image'        => 'https://cdn.razorpay.com/logos/BUVwvgaqVByGp2_large.jpg',
    'prefill' => [
        'name'    => 'Gaurav Kumar',
        'contact' => '9123456780',
        'email'   => 'gaurav.kumar@example.com',
    ],
    'notes'   => [
        'shipping address' => 'L-16, The Business Centre, 61 Wellfield Road, New Delhi - 110001',
    ],
    'callback_url' => 'https://example.com/payment-callback',
    'cancel_url'   => 'https://example.com/payment-cancel',
];

You can just build a flat array?

return [
    'key_id'                  => 'YOUR_KEY_ID',
    'amount'                  => '1001',
    'order_id'                => 'razorpay_order_id',
    'name'                    => 'Acme Corp',
    'description'             => 'A Wild Sheep Chase',
    'image'                   => 'https://cdn.razorpay.com/logos/BUVwvgaqVByGp2_large.jpg',
    'prefill[name]'           => 'Gaurav Kumar',
    'prefill[contact]'        => '9123456780',
    'prefill[email]'          => 'gaurav.kumar@example.com',
    'notes[shipping address]' => 'L-16, The Business Centre, 61 Wellfield Road, New Delhi - 110001',
    'callback_url'            => 'https://example.com/payment-callback',
    'cancel_url'              => 'https://example.com/payment-cancel',
];

We could implement support for multidimensional arrays, we don't really need this ourselves, so maybe this should remain gateway territory?

https://www.php.net/manual/en/function.http-build-query.php


Solution with RecursiveIteratorIterator? ```php 'YOUR_KEY_ID', 'amount' => '1001', 'order_id' => 'razorpay_order_id', 'name' => 'Acme Corp', 'description' => 'A Wild Sheep Chase', 'image' => 'https://cdn.razorpay.com/logos/BUVwvgaqVByGp2_large.jpg', 'prefill' => [ 'name' => 'Gaurav Kumar', 'contact' => '9123456780', 'email' => 'gaurav.kumar@example.com', ], 'notes' => [ 'shipping address' => 'L-16, The Business Centre, 61 Wellfield Road, New Delhi - 110001', ], 'callback_url' => 'https://example.com/payment-callback', 'cancel_url' => 'https://example.com/payment-cancel', 1 => 'test', ]; $iterator = new class( new RecursiveArrayIterator( $data ) ) extends RecursiveIteratorIterator { function key() { $key = $this->getSubIterator( 0 )->key(); for ( $i = 1; $i <= $this->getDepth(); $i++ ) { $key .= '[' . $this->getSubIterator( $i )->key() . ']'; } return $key; } }; foreach ( $iterator as $key => $item ) { var_dump( $key ); var_dump( $item ); echo PHP_EOL; } ```
knit-pay commented 2 years ago

You want to pass in a an array like:

Yes, I need to pass an array like the first one in post form.

You can just build a flat array?

Yes, need to pass a flat array like the one you provided. The first array is getting used for another purpose also. So, developing a second array directly is not possible. Either I will have to convert the normal array to a flat array within the get_output_fields function before returning. Using a function similar to the function given below or I will have to recreate the complete array. For the time being, I have already handled it within my gateway code to convert it within the get_output_fields function. It's just a suggestion that we can handle it within the wp-pay-core code itself as other gateways might also need similar functionality.

    public static function flatten_array($array, $prefix = '', $parent = true) {
        $result = array();
        foreach($array as $key=>$value) {
            if(is_array($value)) {
                if($parent){
                    $result = $result + self::flatten_array($value, "{$key}", false);
                }else {
                    $result = $result + self::flatten_array($value, "{$prefix}[{$key}]", false);
                }
            } else if ($parent){
                $result["{$prefix}{$key}"] = $value;
            }
            else {
                $result["{$prefix}[{$key}]"] = $value;
            }
        }
        return $result;
    }

https://www.php.net/manual/en/function.http-build-query.php

Yes, this function is good for creating query strings. add_query_arg or http_build_query can be used for sending parameters in GET requests. But I think we can't use it for POST form submission. Correct me if I am wrong.

Solution with RecursiveIteratorIterator?

Thanks for the suggestion, I will try to use this also.

What do you think, I should handle this within gateway or you can include it in wp-pay-core also?

remcotolsma commented 2 years ago

What do you think, I should handle this within gateway or you can include it in wp-pay-core also?

I think we could include this in the core library, it might be useful if this works like for example like the body argument in the wp_remote_post function. I think the WordPress HTTP library is using the http_build_query function for this. I will discuss this internally with @rvdsteege and double check if a RecursiveIteratorIterator solution is handy.

function flat( $data, $parent = '', $result = [] ) {
    foreach ( $data as $key => $item ) {
        if ( '' !== $parent ) {
            $key = $parent . '[' . $key . ']';
        }

        if ( is_array( $item ) ) {
            $result = flat( $item, $key, $result );
        } else {
            $result[ $key ] = $item;
        }
    }

    return $result;
}

var_dump( flat( $data ) );
remcotolsma commented 2 years ago

Naming helper function, perhaps like array_dot:

private static function array_square_bracket( $data, $parent = '', $result = [] ) {
    foreach ( $data as $key => $item ) {
        if ( '' !== $parent ) {
            $key = $parent . '[' . $key . ']';
        }

        if ( is_array( $item ) ) {
            $result = self::array_square_bracket( $item, $key, $result );
        } else {
            $result[ $key ] = $item;
        }
    }

    return $result;
}