I'am trying to create a payment for an order via the rest post action at /entity/commerce_payment: POST

My message body:

{
  "type": [
	{
      "value": "payment_manual"
	}
  ],
  "payment_gateway": [
    {
      "target_id": "invoice",
      "target_type": "commerce_payment_gateway"
    }
  ],
  "amount": [
	{
	  "number": "9.990000",
          "currency_code": "EUR"
	}
  ],
  "order_id": [
	{
	  "target_id": 6,
	  "target_type": "commerce_order"
	}
  ]
}

I'am getting a 500 with following error in the log:

Uncaught PHP Exception Drupal\\Core\\Entity\\EntityStorageException: "Missing "payment_gateway" property when creating a payment." at .../modules/contrib/commerce/modules/payment/src/PaymentStorage.php line 41

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

daveiano created an issue. See original summary.

czigor’s picture

I can reproduce the issue the following way.
1. Enable the restui module.
2. Enable POST for payment entities on the Rest UI.
3. Run the following in devel/php:

$serialized_entity = json_encode([

'type' => [['value' => 'payment_default']],
 'payment_gateway' => [
    [
      "target_id" => 'manual',
      "target_type"=> "commerce_payment_gateway",
  ]],
  "amount" => [
	[
	  "number" => "9.990000",
          "currency_code"=> "USD",
	],
  ],
  "order_id" =>  [
	[
	  "target_id"=> 5,
	  "target_type"=> "commerce_order",
	],
  ],
]);

$response = \Drupal::httpClient()
  ->post('http://drupal8.loc/entity/commerce_payment?_format=json', [
    'body' => $serialized_entity,
    'headers' => [
      'Content-Type' => 'application/json',
    ],
  ]);

The result is a WSOD with a

[Mon Sep 10 15:36:40.811605 2018] [fcgid:warn] [pid 8802] [client 127.0.0.1:59404] mod_fcgid: stderr: Uncaught PHP Exception Drupal\\Core\\Entity\\EntityStorageException: "Missing "payment_gateway" property when creating a payment." at /modules/contrib/commerce/modules/payment/src/PaymentStorage.php line 41

in my logs.

czigor’s picture

Status: Active » Needs review
FileSize
689 bytes

This is caused by the following pattern used in Drupal\commerce_payment\PaymentStorage::doCreate():

protected function doCreate(array $values) {
  if (empty($values['payment_gateway'])) {
    throw new EntityStorageException('Missing "payment_gateway" property when creating a payment.');
  }

When creating the payment via REST however, $values only contains the payment bundle. The stack is:

modules/contrib/commerce/modules/payment/src/PaymentStorage.php.Drupal\commerce_payment\PaymentStorage->doCreate(): lineno 41	
core/lib/Drupal/Core/Entity/EntityStorageBase.php.Drupal\commerce_payment\PaymentStorage->create(): lineno 184	
core/modules/serialization/src/Normalizer/EntityNormalizer.php.Drupal\serialization\Normalizer\ContentEntityNormalizer->denormalize(): lineno 56	
vendor/symfony/serializer/Serializer.php.Symfony\Component\Serializer\Serializer->denormalize(): lineno 182	

Where denormalize() takes care of passing only the bundle to doCreate().

Core never uses the above validation pattern. Since Drupal\commerce_payment\Entity::baseFieldDefinitions() sets the payment gateway field required anyway I wonder if we could get rid of these lines altogether. With the attached patch the below POST request creates a payment:


$serialized_entity = json_encode([
  'type' => [['value' => 'payment_default']],
  'payment_gateway' => [
    [
      "target_id" => 'manual',
      "target_type"=> "commerce_payment_gateway",
    ]
  ],
  'payment_gateway_mode' => [['value' => 'test']],
  "amount" => [
    [
      'number' => "9.990000",
      'currency_code'=> "USD",
    ],
  ],
  'order_id' =>  [[
    "target_id"=> 5,
    "target_type"=> "commerce_order",
  ]],
]);

$response = \Drupal::httpClient()
  ->post('http://drupal8.loc/entity/commerce_payment?_format=json', [
    'body' => $serialized_entity,
    'headers' => [
      'Content-Type' => 'application/json',
    ],
  ]);
mglaman’s picture

Core never uses the above validation pattern. Since Drupal\commerce_payment\Entity::baseFieldDefinitions() sets the payment gateway field required anyway I wonder if we could get rid of these lines altogether. With the attached patch the below POST request creates a payment:

That is because core has very loose validations on the entity. I believe REST/JSON API invoke entity validations, but outside of that scope nothing is guaranteed. You can save entities programmatically without populating required fields and not receive an error.

czigor’s picture

@mglaman

You can save entities programmatically without populating required fields and not receive an error.

With the patch above applied I cannot create a payment with this code:

use Drupal\commerce_payment\Entity\Payment;
use Drupal\commerce_price\Price;

$values = [
'type' => 'payment_default',
'payment_gateway_mode' => 'test',
'order_id' => ['target_id' => 5],
  "amount" => 	new Price("9.990000", "USD"),
];
Payment::create($values)->save();

I get a WSOD with this log entry:

[Tue Sep 11 14:52:35.236777 2018] [fcgid:warn] [pid 25289] [client 127.0.0.1:51440] mod_fcgid: stderr: Uncaught PHP Exception Drupal\\Core\\Entity\\EntityStorageException: "Required payment field "payment_gateway" is empty." at /core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php line 829, referer: http://drupal8.loc/devel/php

Seems to me that core is doing validation for required base fields. Am I missing something?

bojanz’s picture

Not core. That's our code:

  public function preSave(EntityStorageInterface $storage) {
    parent::preSave($storage);

    $payment_gateway = $this->getPaymentGateway();
    if (!$payment_gateway) {
      throw new EntityMalformedException(sprintf('Required payment field "payment_gateway" is empty.'));
    }

PaymentStorage::doCreate() requires the payment gateway because that's used to determine the "type".
I guess we could make payment_gateway on create optional if type was specified, but the DX is less ideal than specifying just the gateway.

mglaman’s picture

Status: Needs review » Needs work
Issue tags: -payment rest +Needs tests

This was also tackled the wrong way. We need a test in the payment module which enables the rest endpoint and replicates the failure.

czigor’s picture

I'm working on adding REST tests for commerce_payment the way core does it (see e.g. core/modules/node/tests/src/Functional/Rest). That will cover all drupal core rest methods, formats and authentications.

czigor’s picture

czigor’s picture

Status: Needs work » Needs review
czigor’s picture

The tests adhere to the current situation of PATCHing commerce_payment entities is not supported at all.

Status: Needs review » Needs work
czigor’s picture

Status: Needs work » Needs review
FileSize
26.44 KB

Adding a base_path() to the order_id url.

Status: Needs review » Needs work

The last submitted patch, 13: commerce-2988625-payment_creation_via_rest-13.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

czigor’s picture

Status: Needs work » Needs review
FileSize
26.44 KB

Remove slash.

Status: Needs review » Needs work

The last submitted patch, 15: commerce-2988625-payment_creation_via_rest-15.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

czigor’s picture

suresh.gju’s picture

The patch resolve the error.

However if the payment gateway is some onsite creditcard payment, it is not processing the actual payment gateway (even the payment method ID is provided), it just create an entity record.

rifas-ali-pbi’s picture

Version: 8.x-2.8 » 8.x-2.12
rifas-ali-pbi’s picture

Version: 8.x-2.12 » 8.x-2.x-dev
rifas-ali-pbi’s picture

same isssue for me when i am using jsonapi. but i am using 2.12 commerce. So i cant use this patch right?

csg’s picture

Status: Needs review » Needs work

The patch in #17 was working great before but with 2.12 it's not working anymore. It still can be applied, but the error persists.

zserno’s picture

I could successfully create commerce_payment entities after applying patch #17, but not commerce_payment_method entities via POST to /entity/commerce_payment_method.

Applying the same fix to PaymentMethodStorage.php file seems to be working.

czigor’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 23: commerce-2988625-payment_creation_via_rest-23.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

bojanz’s picture

Title: Create payment with rest - Missing "payment_gateway" property » Cannot create payments and payment methods via REST
Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
2.13 KB

Expanding title, since both commerce_payment and commerce_payment_method entity types are affected.

We don't need to enforce payment_gateway presence in doCreate() since we already do it in the entity preSave() methods.

I don't think we want to commit REST tests at this point. I want to wait until we have an actual handcrafted REST API (probably living in commerce_rest), the one that core provides is just a clunky stopgap.

Rerolled patch provided.

bojanz’s picture

Status: Needs review » Fixed

Committed. Thanks, everyone!

  • bojanz committed 89901b0 on 8.x-2.x authored by czigor
    Issue #2988625 by czigor, zserno, bojanz: Cannot create payments and...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.