Details
-
Type:
Bug
-
Status: Closed
-
Priority:
Major
-
Resolution: Fixed
-
Affects Version/s: None
-
Fix Version/s: 5.9.0-b1
-
Component/s: None
-
Labels:None
Description
PayPal checkout seems to create two separate transactions, one pending and one accepted.
To reproduce just make a payment with the webhooks configure.
Possible solution (replace text with language definitions)
Alright, in components/gateways/nonmerchant/paypal_checkout/paypal_checkout.php around line 240 replace the whole validate() section with
/**
* Validates the incoming POST/GET response from the gateway to ensure it is
* legitimate and can be trusted.
*
* @param array $get The GET data for this request
* @param array $post The POST data for this request
* @return array An array of transaction data, sets any errors using Input if the data fails to validate
* - client_id The ID of the client that attempted the payment
* - amount The amount of the payment
* - currency The currency of the payment
* - invoices An array of invoices and the amount the payment should be applied to (if any) including:
* - id The ID of the invoice to apply to
* - amount The amount to apply to the invoice
* - status The status of the transaction (approved, declined, void, pending, reconciled, refunded, returned)
* - reference_id The reference ID for gateway-only use with this transaction (optional)
* - transaction_id The ID returned by the gateway to identify this transaction
* - parent_transaction_id The ID returned by the gateway to identify this
* transaction's original transaction (in the case of refunds)
*/
public function validate(array $get, array $post)
{
// Initialize API
$api = $this->getApi($this->meta['client_id'], $this->meta['client_secret'], $this->meta['sandbox']);
$payments = new PaypalCheckoutPayments($api);
// Fetch webhook payload
$payload = file_get_contents('php://input');
$webhook = json_decode($payload);
// Discard all webhook events, except when the order is completed or approved
$events = ['CHECKOUT.ORDER.APPROVED', 'PAYMENT.CAPTURE.COMPLETED'];
if (!in_array($webhook->event_type ?? '', $events)) {
$this->Input->setErrors(['unsupported_event' => ['unsupported_event' => 'Unsupported event type.']]);
return;
}
$this->log('validate', json_encode($webhook), 'input', !empty($webhook));
// Capture payment
if ($webhook->event_type == 'CHECKOUT.ORDER.APPROVED') {
$orders = new PaypalCheckoutOrders($api);
$response = $orders->capture(['id' => $webhook->resource->id]);
$this->log('capture', json_encode($response->response()), 'output', empty($response->errors()));
// Output errors
if (($errors = $response->errors())) {
$this->Input->setErrors($errors);
return;
}
return [
'client_id' => $webhook->resource->purchase_units[0]->custom_id ?? null,
'amount' => $webhook->resource->purchase_units[0]->amount->value ?? null,
'currency' => $webhook->resource->purchase_units[0]->amount->currency_code ?? null,
'invoices' => $this->unserializeInvoices($webhook->resource->purchase_units[0]->reference_id ?? ''),
'status' => 'pending',
'reference_id' => null,
'transaction_id' => $webhook->resource->id ?? null,
'parent_transaction_id' => null
];
}
// Set the payment
$payment = $webhook->resource ?? (object) [];
// Fetch the transaction
$order_response = (object) [];
$order = (object) [];
$transaction = (object) [];
if (isset($payment->supplementary_data->related_ids->order_id)) {
$orders = new PaypalCheckoutOrders($api);
$order_response = $orders->get(['id' => $payment->supplementary_data->related_ids->order_id]) ?? (object) [];
$order = $order_response->response();
$transaction = $order->purchase_units[0] ?? (object) [];
}
$this->log('validate', json_encode($transaction), 'output', !empty($transaction));
if (empty($transaction)) {
$this->Input->setErrors(['missing_transaction' => ['missing_transaction' => 'No transaction found.']]);
return;
}
// Set status
$status = 'error';
$success = false;
switch ($payment->status ?? 'ERROR') {
case 'COMPLETED':
$status = 'approved';
$success = true;
break;
case 'APPROVED':
$status = 'pending';
$success = true;
break;
case 'VOIDED':
$status = 'void';
$success = true;
break;
}
if (!$success) {
return;
}
// Output errors
if (($errors = $order_response->errors())) {
$this->Input->setErrors($errors);
return;
}
return [
'client_id' => $transaction->custom_id ?? null,
'amount' => $payment->amount->value ?? null,
'currency' => $payment->amount->currency_code ?? null,
'invoices' => $this->unserializeInvoices($transaction->reference_id ?? ''),
'status' => $status,
'reference_id' => $payment->id ?? null,
'transaction_id' => $order->id ?? null,
'parent_transaction_id' => null
];
}