Uploaded image for project: 'Blesta Core'
  1. Blesta Core
  2. CORE-4986

PayPal Checkout: Duplicate transaction

    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
              ];
          }
      

        Activity

        jonathan Jonathan Reissmueller created issue -
        jonathan Jonathan Reissmueller made changes -
        Field Original Value New Value
        Rank Ranked higher
        jonathan Jonathan Reissmueller made changes -
        Sprint 5.10.0 Sprint 1 [ 188 ]
        jonathan Jonathan Reissmueller made changes -
        Rank Ranked higher
        jonathan Jonathan Reissmueller made changes -
        Sprint 5.10.0 Sprint 1 [ 188 ] 5.9.0 Sprint 4 [ 187 ]
        jonathan Jonathan Reissmueller made changes -
        Rank Ranked higher
        abdy Abdy Franco made changes -
        Assignee Abdy Franco [ abdy ]
        abdy Abdy Franco made changes -
        Status Open [ 1 ] In Progress [ 3 ]
        abdy Abdy Franco made changes -
        Remaining Estimate 0 minutes [ 0 ]
        Time Spent 1 hour, 22 minutes [ 4920 ]
        Worklog Id 16640 [ 16640 ]
        abdy Abdy Franco made changes -
        Status In Progress [ 3 ] In Review [ 5 ]
        Resolution Fixed [ 1 ]
        jonathan Jonathan Reissmueller made changes -
        Status In Review [ 5 ] Closed [ 6 ]

          People

          • Assignee:
            abdy Abdy Franco
            Reporter:
            jonathan Jonathan Reissmueller
          • Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:
              Fix Release Date:
              15/Dec/23

              Time Tracking

              Estimated:
              Original Estimate - Not Specified
              Not Specified
              Remaining:
              Remaining Estimate - 0 minutes
              0m
              Logged:
              Time Spent - 1 hour, 22 minutes
              1h 22m

                Agile