You are here

class CheckoutSdk in Commerce PayPal 8

Provides a replacement of the PayPal SDK.

Hierarchy

Expanded class hierarchy of CheckoutSdk

1 string reference to 'CheckoutSdk'
commerce_paypal.services.yml in ./commerce_paypal.services.yml
commerce_paypal.services.yml
1 service uses CheckoutSdk
commerce_paypal.checkout_sdk in ./commerce_paypal.services.yml
Drupal\commerce_paypal\CheckoutSdk

File

src/CheckoutSdk.php, line 20

Namespace

Drupal\commerce_paypal
View source
class CheckoutSdk implements CheckoutSdkInterface {

  /**
   * The HTTP client.
   *
   * @var \GuzzleHttp\Client
   */
  protected $client;

  /**
   * The adjustment transformer.
   *
   * @var \Drupal\commerce_order\AdjustmentTransformerInterface
   */
  protected $adjustmentTransformer;

  /**
   * The event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $eventDispatcher;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The time.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  protected $time;

  /**
   * The payment gateway plugin configuration.
   *
   * @var array
   */
  protected $config;

  /**
   * Constructs a new CheckoutSdk object.
   *
   * @param \GuzzleHttp\ClientInterface $client
   *   The client.
   * @param \Drupal\commerce_order\AdjustmentTransformerInterface $adjustment_transformer
   *   The adjustment transformer.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
   *   The event dispatcher.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   The time.
   * @param array $config
   *   The payment gateway plugin configuration array.
   */
  public function __construct(ClientInterface $client, AdjustmentTransformerInterface $adjustment_transformer, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler, TimeInterface $time, array $config) {
    $this->client = $client;
    $this->adjustmentTransformer = $adjustment_transformer;
    $this->eventDispatcher = $event_dispatcher;
    $this->moduleHandler = $module_handler;
    $this->time = $time;
    $this->config = $config;
  }

  /**
   * {@inheritdoc}
   */
  public function getAccessToken() {
    return $this->client
      ->post('/v1/oauth2/token', [
      'auth' => [
        $this->config['client_id'],
        $this->config['secret'],
      ],
      'form_params' => [
        'grant_type' => 'client_credentials',
      ],
    ]);
  }

  /**
   * {@inheritdoc}
   */
  public function getClientToken() {
    return $this->client
      ->post('/v1/identity/generate-token', [
      'headers' => [
        'Content-Type' => 'application/json',
      ],
    ]);
  }

  /**
   * {@inheritdoc}
   */
  public function createOrder(OrderInterface $order, AddressInterface $billing_address = NULL) {
    $params = $this
      ->prepareOrderRequest($order, $billing_address);
    $event = new CheckoutOrderRequestEvent($order, $params);
    $this->eventDispatcher
      ->dispatch(PayPalEvents::CHECKOUT_CREATE_ORDER_REQUEST, $event);
    return $this->client
      ->post('/v2/checkout/orders', [
      'json' => $event
        ->getRequestBody(),
    ]);
  }

  /**
   * {@inheritdoc}
   */
  public function getOrder($remote_id) {
    return $this->client
      ->get(sprintf('/v2/checkout/orders/%s', $remote_id));
  }

  /**
   * {@inheritdoc}
   */
  public function updateOrder($remote_id, OrderInterface $order) {
    $params = $this
      ->prepareOrderRequest($order);
    $update_params = [
      [
        'op' => 'replace',
        'path' => "/purchase_units/@reference_id=='default'",
        'value' => $params['purchase_units'][0],
      ],
    ];
    $event = new CheckoutOrderRequestEvent($order, $update_params);
    $this->eventDispatcher
      ->dispatch(PayPalEvents::CHECKOUT_UPDATE_ORDER_REQUEST, $event);
    return $this->client
      ->patch(sprintf('/v2/checkout/orders/%s', $remote_id), [
      'json' => $event
        ->getRequestBody(),
    ]);
  }

  /**
   * {@inheritdoc}
   */
  public function authorizeOrder($remote_id) {
    $headers = [
      'Content-Type' => 'application/json',
    ];
    return $this->client
      ->post(sprintf('/v2/checkout/orders/%s/authorize', $remote_id), [
      'headers' => $headers,
    ]);
  }

  /**
   * {@inheritdoc}
   */
  public function captureOrder($remote_id) {
    $headers = [
      'Content-Type' => 'application/json',
    ];
    return $this->client
      ->post(sprintf('/v2/checkout/orders/%s/capture', $remote_id), [
      'headers' => $headers,
    ]);
  }

  /**
   * {@inheritdoc}
   */
  public function capturePayment($authorization_id, array $parameters = []) {
    $options = [
      'headers' => [
        'Content-Type' => 'application/json',
      ],
    ];
    if ($parameters) {
      $options['json'] = $parameters;
    }
    return $this->client
      ->post(sprintf('/v2/payments/authorizations/%s/capture', $authorization_id), $options);
  }

  /**
   * {@inheritdoc}
   */
  public function reAuthorizePayment($authorization_id, array $parameters = []) {
    $options = [
      'headers' => [
        'Content-Type' => 'application/json',
      ],
    ];
    if ($parameters) {
      $options['json'] = $parameters;
    }
    return $this->client
      ->post(sprintf('/v2/payments/authorizations/%s/reauthorize', $authorization_id), $options);
  }

  /**
   * {@inheritdoc}
   */
  public function refundPayment($capture_id, array $parameters = []) {
    $options = [
      'headers' => [
        'Content-Type' => 'application/json',
      ],
    ];
    if ($parameters) {
      $options['json'] = $parameters;
    }
    return $this->client
      ->post(sprintf('/v2/payments/captures/%s/refund', $capture_id), $options);
  }

  /**
   * {@inheritdoc}
   */
  public function voidPayment($authorization_id, array $parameters = []) {
    $options = [
      'headers' => [
        'Content-Type' => 'application/json',
      ],
    ];
    return $this->client
      ->post(sprintf('/v2/payments/authorizations/%s/void', $authorization_id), $options);
  }

  /**
   * Prepare the order request parameters.
   *
   * @param \Drupal\commerce_order\Entity\OrderInterface $order
   *   The order.
   * @param \Drupal\address\AddressInterface $billing_address
   *   (optional) A billing address to pass to PayPal as the payer information.
   *   This is used in checkout to pass the entered address that is not yet
   *   submitted and associated to the order.
   *
   * @return array
   *   An array suitable for use in the create|update order API calls.
   */
  protected function prepareOrderRequest(OrderInterface $order, AddressInterface $billing_address = NULL) {
    $items = [];
    $item_total = NULL;
    $tax_total = NULL;
    foreach ($order
      ->getItems() as $order_item) {
      $item_total = $item_total ? $item_total
        ->add($order_item
        ->getTotalPrice()) : $order_item
        ->getTotalPrice();
      $item = [
        'name' => mb_substr($order_item
          ->getTitle(), 0, 127),
        'unit_amount' => [
          'currency_code' => $order_item
            ->getUnitPrice()
            ->getCurrencyCode(),
          'value' => Calculator::trim($order_item
            ->getUnitPrice()
            ->getNumber()),
        ],
        'quantity' => intval($order_item
          ->getQuantity()),
      ];
      $purchased_entity = $order_item
        ->getPurchasedEntity();
      if ($purchased_entity instanceof ProductVariationInterface) {
        $item['sku'] = mb_substr($purchased_entity
          ->getSku(), 0, 127);
      }
      $items[] = $item;
    }

    // Now, pass adjustments that are not "supported" by PayPal such as fees
    // and "custom" adjustments.
    // We could pass fees under "handling", but we can't make that assumption.
    $adjustments = $order
      ->collectAdjustments();
    $adjustments = $this->adjustmentTransformer
      ->processAdjustments($adjustments);
    foreach ($adjustments as $adjustment) {

      // Skip included adjustments and the adjustment types we're handling
      // below such as "shipping" and "tax".
      if ($adjustment
        ->isIncluded() || in_array($adjustment
        ->getType(), [
        'tax',
        'shipping',
        'promotion',
      ])) {
        continue;
      }
      $item_total = $item_total ? $item_total
        ->add($adjustment
        ->getAmount()) : $adjustment
        ->getAmount();
      $items[] = [
        'name' => mb_substr($adjustment
          ->getLabel(), 0, 127),
        'unit_amount' => [
          'currency_code' => $adjustment
            ->getAmount()
            ->getCurrencyCode(),
          'value' => Calculator::trim($adjustment
            ->getAmount()
            ->getNumber()),
        ],
        'quantity' => 1,
      ];
    }
    $breakdown = [
      'item_total' => [
        'currency_code' => $item_total
          ->getCurrencyCode(),
        'value' => Calculator::trim($item_total
          ->getNumber()),
      ],
    ];
    $tax_total = $this
      ->getAdjustmentsTotal($adjustments, [
      'tax',
    ]);
    if (!empty($tax_total)) {
      $breakdown['tax_total'] = [
        'currency_code' => $tax_total
          ->getCurrencyCode(),
        'value' => Calculator::trim($tax_total
          ->getNumber()),
      ];
    }
    $shipping_total = $this
      ->getAdjustmentsTotal($adjustments, [
      'shipping',
    ]);
    if (!empty($shipping_total)) {
      $breakdown['shipping'] = [
        'currency_code' => $shipping_total
          ->getCurrencyCode(),
        'value' => Calculator::trim($shipping_total
          ->getNumber()),
      ];
    }
    $promotion_total = $this
      ->getAdjustmentsTotal($adjustments, [
      'promotion',
    ]);
    if (!empty($promotion_total)) {
      $breakdown['discount'] = [
        'currency_code' => $promotion_total
          ->getCurrencyCode(),
        'value' => Calculator::trim($promotion_total
          ->multiply(-1)
          ->getNumber()),
      ];
    }
    $payer = [];
    if (!empty($order
      ->getEmail())) {
      $payer['email_address'] = $order
        ->getEmail();
    }
    $profiles = $order
      ->collectProfiles();
    if (!empty($billing_address)) {
      $payer += static::formatAddress($billing_address);
    }
    elseif (isset($profiles['billing'])) {

      /** @var \Drupal\address\AddressInterface $address */
      $address = $profiles['billing']->address
        ->first();
      if (!empty($address)) {
        $payer += static::formatAddress($address);
      }
    }
    $params = [
      'intent' => strtoupper($this->config['intent']),
      'purchase_units' => [
        [
          'reference_id' => 'default',
          'custom_id' => $order
            ->id(),
          'invoice_id' => $order
            ->id() . '-' . $this->time
            ->getRequestTime(),
          'amount' => [
            'currency_code' => $order
              ->getTotalPrice()
              ->getCurrencyCode(),
            'value' => Calculator::trim($order
              ->getTotalPrice()
              ->getNumber()),
            'breakdown' => $breakdown,
          ],
          'items' => $items,
        ],
      ],
      'application_context' => [
        'brand_name' => mb_substr($order
          ->getStore()
          ->label(), 0, 127),
      ],
    ];
    $shipping_address = [];
    if (isset($profiles['shipping'])) {

      /** @var \Drupal\address\AddressInterface $address */
      $address = $profiles['shipping']->address
        ->first();
      if (!empty($address)) {
        $shipping_address = static::formatAddress($address, 'shipping');
      }
    }
    $shipping_preference = $this->config['shipping_preference'];

    // The shipping module isn't enabled, override the shipping preference
    // configured.
    if (!$this->moduleHandler
      ->moduleExists('commerce_shipping')) {
      $shipping_preference = 'no_shipping';
    }
    else {

      // If no shipping address was already collected, override the shipping
      // preference to "GET_FROM_FILE" so that the shipping address is collected
      // on the PayPal site.
      if ($shipping_preference == 'set_provided_address' && !$shipping_address) {
        $shipping_preference = 'get_from_file';
      }
    }

    // No need to pass a shipping_address if the shipping address collection
    // is configured to "no_shipping".
    if ($shipping_address && $shipping_preference !== 'no_shipping') {
      $params['purchase_units'][0]['shipping'] = $shipping_address;
    }
    $params['application_context']['shipping_preference'] = strtoupper($shipping_preference);
    if ($payer) {
      $params['payer'] = $payer;
    }
    return $params;
  }

  /**
   * Get the total for the given adjustments.
   *
   * @param \Drupal\commerce_order\Adjustment[] $adjustments
   *   The adjustments.
   * @param string[] $adjustment_types
   *   The adjustment types to include in the calculation.
   *   Examples: fee, promotion, tax. Defaults to all adjustment types.
   *
   * @return \Drupal\commerce_price\Price|null
   *   The adjustments total, or NULL if no matching adjustments were found.
   */
  protected function getAdjustmentsTotal(array $adjustments, array $adjustment_types = []) {
    $adjustments_total = NULL;
    $matching_adjustments = [];
    foreach ($adjustments as $adjustment) {
      if ($adjustment_types && !in_array($adjustment
        ->getType(), $adjustment_types)) {
        continue;
      }
      if ($adjustment
        ->isIncluded()) {
        continue;
      }
      $matching_adjustments[] = $adjustment;
    }
    if ($matching_adjustments) {
      $matching_adjustments = $this->adjustmentTransformer
        ->processAdjustments($matching_adjustments);
      foreach ($matching_adjustments as $adjustment) {
        $adjustments_total = $adjustments_total ? $adjustments_total
          ->add($adjustment
          ->getAmount()) : $adjustment
          ->getAmount();
      }
    }
    return $adjustments_total;
  }

  /**
   * Formats the given address into a format expected by PayPal.
   *
   * @param \Drupal\address\AddressInterface $address
   *   The address to format.
   * @param string $type
   *   The address type ("billing"|"shipping").
   *
   * @return array
   *   The formatted address.
   */
  public static function formatAddress(AddressInterface $address, $type = 'billing') {
    $return = [
      'address' => [
        'address_line_1' => $address
          ->getAddressLine1(),
        'address_line_2' => $address
          ->getAddressLine2(),
        'admin_area_2' => mb_substr($address
          ->getLocality(), 0, 120),
        'admin_area_1' => $address
          ->getAdministrativeArea(),
        'postal_code' => mb_substr($address
          ->getPostalCode(), 0, 60),
        'country_code' => $address
          ->getCountryCode(),
      ],
    ];
    if ($type == 'billing') {
      $return['name'] = [
        'given_name' => $address
          ->getGivenName(),
        'surname' => $address
          ->getFamilyName(),
      ];
    }
    elseif ($type == 'shipping') {
      $return['name'] = [
        'full_name' => mb_substr($address
          ->getGivenName() . ' ' . $address
          ->getFamilyName(), 0, 300),
      ];
    }
    return $return;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
CheckoutSdk::$adjustmentTransformer protected property The adjustment transformer.
CheckoutSdk::$client protected property The HTTP client.
CheckoutSdk::$config protected property The payment gateway plugin configuration.
CheckoutSdk::$eventDispatcher protected property The event dispatcher.
CheckoutSdk::$moduleHandler protected property The module handler.
CheckoutSdk::$time protected property The time.
CheckoutSdk::authorizeOrder public function Authorize payment for order. Overrides CheckoutSdkInterface::authorizeOrder
CheckoutSdk::captureOrder public function Capture payment for order. Overrides CheckoutSdkInterface::captureOrder
CheckoutSdk::capturePayment public function Captures an authorized payment, by ID. Overrides CheckoutSdkInterface::capturePayment
CheckoutSdk::createOrder public function Creates an order in PayPal. Overrides CheckoutSdkInterface::createOrder
CheckoutSdk::formatAddress public static function Formats the given address into a format expected by PayPal.
CheckoutSdk::getAccessToken public function Gets an access token. Overrides CheckoutSdkInterface::getAccessToken
CheckoutSdk::getAdjustmentsTotal protected function Get the total for the given adjustments.
CheckoutSdk::getClientToken public function Gets a client token. Overrides CheckoutSdkInterface::getClientToken
CheckoutSdk::getOrder public function Get an existing order from PayPal. Overrides CheckoutSdkInterface::getOrder
CheckoutSdk::prepareOrderRequest protected function Prepare the order request parameters.
CheckoutSdk::reAuthorizePayment public function Reauthorizes an authorized PayPal account payment, by ID. Overrides CheckoutSdkInterface::reAuthorizePayment
CheckoutSdk::refundPayment public function Refunds a captured payment, by ID. Overrides CheckoutSdkInterface::refundPayment
CheckoutSdk::updateOrder public function Updates an existing PayPal order. Overrides CheckoutSdkInterface::updateOrder
CheckoutSdk::voidPayment public function Voids, or cancels, an authorized payment, by ID. Overrides CheckoutSdkInterface::voidPayment
CheckoutSdk::__construct public function Constructs a new CheckoutSdk object.