为什么在POST超全局上使用'php:// input' - Stripe SCA示例

Stripe are soon to roll out their use of Strong Customer Authentication for payments with their platform. There's a fairly substantial section in their documentation about it.

https://stripe.com/docs/payments/payment-intents/quickstart#manual-confirmation-flow

The process has the following flow:

enter image description here

The vanilla PHP implementation is like so:

<?php
  # vendor using composer
  require_once('vendor/autoload.php');

  \Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));

  header('Content-Type: application/json');

  # retrieve json from POST body
  $json_str = file_get_contents('php://input');
  $json_obj = json_decode($json_str);

  $intent = null;
  try {
    if (isset($json_obj->payment_method_id)) {
      # Create the PaymentIntent
      $intent = \Stripe\PaymentIntent::create([
        'payment_method' => $json_obj->payment_method_id,
        'amount' => 1099,
        'currency' => 'gbp',
        'confirmation_method' => 'manual',
        'confirm' => true,
      ]);
    }
    if (isset($json_obj->payment_intent_id)) {
      $intent = \Stripe\PaymentIntent::retrieve(
        $json_obj->payment_intent_id
      );
      $intent->confirm();
    }
    generatePaymentResponse($intent);
  } catch (\Stripe\Error\Base $e) {
    # Display error on client
    echo json_encode([
      'error' => $e->getMessage()
    ]);
  }

  function generatePaymentResponse($intent) {
    # Note that if your API version is before 2019-02-11, 'requires_action'
    # appears as 'requires_source_action'.
    if ($intent->status == 'requires_action' &&
        $intent->next_action->type == 'use_stripe_sdk') {
      # Tell the client to handle the action
      echo json_encode([
        'requires_action' => true,
        'payment_intent_client_secret' => $intent->client_secret
      ]);
    } else if ($intent->status == 'succeeded') {
      # The payment didn’t need any additional actions and completed!
      # Handle post-payment fulfillment
      echo json_encode([
        "success" => true
      ]);
    } else {
      # Invalid status
      http_response_code(500);
      echo json_encode(['error' => 'Invalid PaymentIntent status']);
    }
  }
?>

The necessary JavaScript for its use with Stripe Elements looks like this:

var cardholderName = document.getElementById('cardholder-name');
var cardButton = document.getElementById('card-button');

cardButton.addEventListener('click', function(ev) {
  stripe.createPaymentMethod('card', cardElement, {
    billing_details: {name: cardholderName.value}
  }).then(function(result) {
    if (result.error) {
      // Show error in payment form
    } else {
      // Otherwise send paymentMethod.id to your server (see Step 2)
      fetch('/ajax/confirm_payment', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ payment_method_id: result.paymentMethod.id })
      }).then(function(result) {
        // Handle server response (see Step 3)
        result.json().then(function(json) {
          handleServerResponse(json);
        })
      });
    }
  });
});

function handleServerResponse(response) {
  if (response.error) {
    // Show error from server on payment form
  } else if (response.requires_action) {
    // Use Stripe.js to handle required card action
    stripe.handleCardAction(
      response.payment_intent_client_secret
    ).then(function(result) {
      if (result.error) {
        // Show error in payment form
      } else {
        // The card action has been handled
        // The PaymentIntent can be confirmed again on the server
        fetch('/ajax/confirm_payment', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ payment_intent_id: result.paymentIntent.id })
        }).then(function(confirmResult) {
          return confirmResult.json();
        }).then(handleServerResponse);
      }
    });
  } else {
    // Show success message
  }
}

In my own project I'm using Laravel which is entirely based on the MVC architecture and it fairly nice to you when it comes to most things.

I have tried to refactor a little but I have a question.

Why would you use this line $json_str = file_get_contents('php://input'); over just trying to grab the posted variables from the Request object used in Laravel?

I also read the following article from the PHP Manual:

https://www.php.net/manual/en/wrappers.php.php

To be perfectly honest I've been away from procedural PHP so this has confused me to no end.

Why use 'php://input' over the POST superglobal - Stripe SCA example

The body is encoded as JSON. You can tell because the next line explicitly decodes it.

PHP doesn't understand application/json requests. It will only populate $_POST if the data is encoding using the application/x-www-form-urlencoded or multipart/form-data formats.

Why would you use this line $json_str = file_get_contents('php://input'); over just trying to grab the posted variables from the Request object used in Laravel?

If you were using Laravel, there's no reason to do that.

Since there is no sign of anything Laravel in the example you gave, it is presumably not written with the intention of introducing a dependency on Laravel.