class ExpressCheckout in Commerce PayPal 8
Provides the Paypal Express Checkout payment gateway.
Plugin annotation
@CommercePaymentGateway(
id = "paypal_express_checkout",
label = @Translation("PayPal - Express Checkout [LEGACY-DEPRECATED]"),
display_label = @Translation("PayPal"),
forms = {
"offsite-payment" = "Drupal\commerce_paypal\PluginForm\ExpressCheckoutForm",
},
payment_method_types = {"credit_card"},
credit_card_types = {
"amex", "dinersclub", "discover", "jcb", "maestro", "mastercard", "visa", "unionpay"
},
requires_billing_information = FALSE,
)
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
- class \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\PaymentGatewayBase implements PaymentGatewayInterface, ContainerFactoryPluginInterface uses PluginWithFormsTrait
- class \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayBase implements OffsitePaymentGatewayInterface
- class \Drupal\commerce_paypal\Plugin\Commerce\PaymentGateway\ExpressCheckout implements ExpressCheckoutInterface
- class \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayBase implements OffsitePaymentGatewayInterface
- class \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\PaymentGatewayBase implements PaymentGatewayInterface, ContainerFactoryPluginInterface uses PluginWithFormsTrait
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of ExpressCheckout
File
- src/
Plugin/ Commerce/ PaymentGateway/ ExpressCheckout.php, line 34
Namespace
Drupal\commerce_paypal\Plugin\Commerce\PaymentGatewayView source
class ExpressCheckout extends OffsitePaymentGatewayBase implements ExpressCheckoutInterface {
// Shipping address collection options.
const SHIPPING_ASK_ALWAYS = 'shipping_ask_always';
const SHIPPING_ASK_NOT_PRESENT = 'shipping_ask_not_present';
const SHIPPING_SKIP = 'shipping_skip';
/**
* The logger.
*
* @var \Drupal\Core\Logger\LoggerChannelInterface
*/
protected $logger;
/**
* The HTTP client.
*
* @var \GuzzleHttp\Client
*/
protected $httpClient;
/**
* The price rounder.
*
* @var \Drupal\commerce_price\RounderInterface
*/
protected $rounder;
/**
* The time.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;
/**
* The IPN handler.
*
* @var \Drupal\commerce_paypal\IPNHandlerInterface
*/
protected $ipnHandler;
/**
* Module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->logger = $container
->get('logger.channel.commerce_paypal');
$instance->httpClient = $container
->get('http_client');
$instance->rounder = $container
->get('commerce_price.rounder');
$instance->ipnHandler = $container
->get('commerce_paypal.ipn_handler');
$instance->moduleHandler = $container
->get('module_handler');
$instance->eventDispatcher = $container
->get('event_dispatcher');
return $instance;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'api_username' => '',
'api_password' => '',
'shipping_prompt' => self::SHIPPING_SKIP,
'signature' => '',
'solution_type' => 'Mark',
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['api_username'] = [
'#type' => 'textfield',
'#title' => $this
->t('API Username'),
'#default_value' => $this->configuration['api_username'],
'#required' => TRUE,
];
$form['api_password'] = [
'#type' => 'textfield',
'#title' => $this
->t('API Password'),
'#default_value' => $this->configuration['api_password'],
'#required' => TRUE,
];
$form['signature'] = [
'#type' => 'textfield',
'#title' => $this
->t('Signature'),
'#default_value' => $this->configuration['signature'],
'#required' => TRUE,
];
$form['solution_type'] = [
'#type' => 'radios',
'#title' => $this
->t('Type of checkout flow'),
'#description' => $this
->t('Express Checkout Account Optional (ECAO) where PayPal accounts are not required for payment may not be available in all markets.'),
'#options' => [
'Mark' => $this
->t('Require a PayPal account (this is the standard configuration).'),
'SoleLogin' => $this
->t('Allow PayPal AND credit card payments, defaulting to the PayPal form.'),
'SoleBilling' => $this
->t('Allow PayPal AND credit card payments, defaulting to the credit card form.'),
],
'#default_value' => $this->configuration['solution_type'],
];
$form['shipping_prompt'] = [
'#type' => 'radios',
'#title' => $this
->t('Shipping address collection'),
'#description' => $this
->t('Express Checkout will only request a shipping address if the Shipping module is enabled to store the address in the order.'),
'#options' => [
self::SHIPPING_SKIP => $this
->t('Do not ask for a shipping address at PayPal.'),
],
'#default_value' => $this->configuration['shipping_prompt'],
];
if ($this->moduleHandler
->moduleExists('commerce_shipping')) {
$form['shipping_prompt']['#options'] += [
self::SHIPPING_ASK_NOT_PRESENT => $this
->t('Ask for a shipping address at PayPal if the order does not have one yet.'),
self::SHIPPING_ASK_ALWAYS => $this
->t('Ask for a shipping address at PayPal even if the order already has one.'),
];
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::validateConfigurationForm($form, $form_state);
if (!$form_state
->getErrors() && $form_state
->isSubmitted()) {
$values = $form_state
->getValue($form['#parents']);
$this->configuration['api_username'] = $values['api_username'];
$this->configuration['api_password'] = $values['api_password'];
$this->configuration['signature'] = $values['signature'];
$this->configuration['solution_type'] = $values['solution_type'];
$this->configuration['mode'] = $values['mode'];
$response = $this
->doRequest([
'METHOD' => 'GetBalance',
]);
if ($response['ACK'] != 'Success') {
$form_state
->setError($form, $this
->t('Invalid API credentials.'));
}
}
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
if (!$form_state
->getErrors()) {
$values = $form_state
->getValue($form['#parents']);
$this->configuration['api_username'] = $values['api_username'];
$this->configuration['api_password'] = $values['api_password'];
$this->configuration['signature'] = $values['signature'];
$this->configuration['solution_type'] = $values['solution_type'];
$this->configuration['shipping_prompt'] = $values['shipping_prompt'];
}
}
/**
* {@inheritdoc}
*/
public function onReturn(OrderInterface $order, Request $request) {
$order_express_checkout_data = $order
->getData('paypal_express_checkout');
if (empty($order_express_checkout_data['token'])) {
throw new PaymentGatewayException('Token data missing for this PayPal Express Checkout transaction.');
}
// GetExpressCheckoutDetails API Operation (NVP).
// Shows information about an Express Checkout transaction.
$paypal_response = $this
->getExpressCheckoutDetails($order);
// If the request failed, exit now with a failure message.
if ($paypal_response['ACK'] == 'Failure') {
throw new PaymentGatewayException($paypal_response['PAYMENTREQUESTINFO_0_LONGMESSAGE'], $paypal_response['PAYMENTREQUESTINFO_n_ERRORCODE']);
}
// Set the Payer ID used to finalize payment.
$order_express_checkout_data['payerid'] = $paypal_response['PAYERID'];
// Note: There is no need to save the order here, because it will be
// saved by the Commerce PaymentController after onReturn() completes.
$order
->setData('paypal_express_checkout', $order_express_checkout_data);
// If the user is anonymous, add their PayPal e-mail to the order.
if (empty($order->mail)) {
$order
->setEmail($paypal_response['EMAIL']);
}
// DoExpressCheckoutPayment API Operation (NVP).
// Completes an Express Checkout transaction.
$paypal_response = $this
->doExpressCheckoutDetails($order);
// Nothing to do for failures for now - no payment saved.
if (isset($paypal_response['PAYMENTINFO_0_PAYMENTSTATUS']) && $paypal_response['PAYMENTINFO_0_PAYMENTSTATUS'] == 'Failed') {
throw new PaymentGatewayException($paypal_response['PAYMENTINFO_0_LONGMESSAGE'], $paypal_response['PAYMENTINFO_0_ERRORCODE']);
}
if ($paypal_response['ACK'] == 'Failure') {
// When a buyer's funding source fails, the DoExpressCheckoutPayment and
// DoAuthorization call, a 10486 error is returned.
// @link https://developer.paypal.com/docs/classic/express-checkout/ht_ec_fundingfailure10486/
if (isset($paypal_response['L_ERRORCODE0']) && $paypal_response['L_ERRORCODE0'] == "10486") {
$message = $paypal_response['L_LONGMESSAGE0'];
throw new PaymentGatewayException("{$message} Express Checkout payment failed due to a bad funding source; it is possible that the transaction exceeded the buyer's card limit.", $paypal_response['L_ERRORCODE0']);
}
throw new PaymentGatewayException($paypal_response['L_LONGMESSAGE0'], $paypal_response['L_ERRORCODE0']);
}
$payment_storage = $this->entityTypeManager
->getStorage('commerce_payment');
$payment = $payment_storage
->create([
'state' => 'authorization',
'amount' => $order
->getTotalPrice(),
'payment_gateway' => $this->entityId,
'order_id' => $order
->id(),
'remote_id' => $paypal_response['PAYMENTINFO_0_TRANSACTIONID'],
'remote_state' => $paypal_response['PAYMENTINFO_0_PAYMENTSTATUS'],
]);
$status_mapping = $this
->getStatusMapping();
if (isset($status_mapping[$paypal_response['PAYMENTINFO_0_PAYMENTSTATUS']])) {
$payment
->setState($status_mapping[$paypal_response['PAYMENTINFO_0_PAYMENTSTATUS']]);
}
$payment
->save();
}
/**
* {@inheritdoc}
*/
public function capturePayment(PaymentInterface $payment, Price $amount = NULL) {
$this
->assertPaymentState($payment, [
'authorization',
]);
// If not specified, capture the entire amount.
$amount = $amount ?: $payment
->getAmount();
$amount = $this->rounder
->round($amount);
// GetExpressCheckoutDetails API Operation (NVP).
// Shows information about an Express Checkout transaction.
$paypal_response = $this
->doCapture($payment, $amount
->getNumber());
if ($paypal_response['ACK'] == 'Failure') {
$message = $paypal_response['L_LONGMESSAGE0'];
throw new PaymentGatewayException($message, $paypal_response['L_ERRORCODE0']);
}
$payment
->setState('completed');
$payment
->setAmount($amount);
// Update the remote id for the captured transaction.
$payment
->setRemoteId($paypal_response['TRANSACTIONID']);
$payment
->save();
}
/**
* {@inheritdoc}
*/
public function voidPayment(PaymentInterface $payment) {
$this
->assertPaymentState($payment, [
'authorization',
]);
// GetExpressCheckoutDetails API Operation (NVP).
// Shows information about an Express Checkout transaction.
$paypal_response = $this
->doVoid($payment);
if ($paypal_response['ACK'] == 'Failure') {
$message = $paypal_response['L_LONGMESSAGE0'];
throw new PaymentGatewayException($message, $paypal_response['L_ERRORCODE0']);
}
$payment
->setState('authorization_voided');
$payment
->save();
}
/**
* {@inheritdoc}
*/
public function refundPayment(PaymentInterface $payment, Price $amount = NULL) {
$this
->assertPaymentState($payment, [
'completed',
'partially_refunded',
]);
// If not specified, refund the entire amount.
$amount = $amount ?: $payment
->getAmount();
$this
->assertRefundAmount($payment, $amount);
$amount = $this->rounder
->round($amount);
$extra['amount'] = $amount
->getNumber();
// Check if the Refund is partial or full.
$old_refunded_amount = $payment
->getRefundedAmount();
$new_refunded_amount = $old_refunded_amount
->add($amount);
if ($new_refunded_amount
->lessThan($payment
->getAmount())) {
$payment
->setState('partially_refunded');
$extra['refund_type'] = 'Partial';
}
else {
$payment
->setState('refunded');
if ($amount
->lessThan($payment
->getAmount())) {
$extra['refund_type'] = 'Partial';
}
else {
$extra['refund_type'] = 'Full';
}
}
// RefundTransaction API Operation (NVP).
// Refund (full or partial) an Express Checkout transaction.
$paypal_response = $this
->doRefundTransaction($payment, $extra);
if ($paypal_response['ACK'] == 'Failure') {
$message = $paypal_response['L_LONGMESSAGE0'];
throw new PaymentGatewayException($message, $paypal_response['L_ERRORCODE0']);
}
$payment
->setRefundedAmount($new_refunded_amount);
$payment
->save();
}
/**
* {@inheritdoc}
*/
public function onNotify(Request $request) {
// Get IPN request data and basic processing for the IPN request.
$ipn_data = $this->ipnHandler
->process($request);
// Do not perform any processing on EC transactions here that do not have
// transaction IDs, indicating they are non-payment IPNs such as those used
// for subscription signup requests.
if (empty($ipn_data['txn_id'])) {
$this->logger
->alert('The IPN request does not have a transaction id. Ignored.');
return FALSE;
}
// Exit when we don't get a payment status we recognize.
if (!in_array($ipn_data['payment_status'], [
'Voided',
'Pending',
'Completed',
'Refunded',
])) {
throw new BadRequestHttpException('Invalid payment status');
}
$payment_storage = $this->entityTypeManager
->getStorage('commerce_payment');
$amount = new Price($ipn_data['mc_gross'], $ipn_data['mc_currency']);
// If this is a prior authorization capture IPN...
if (in_array($ipn_data['payment_status'], [
'Voided',
'Pending',
'Completed',
]) && !empty($ipn_data['auth_id'])) {
// Ensure we can load the existing corresponding transaction.
$payment = $payment_storage
->loadByRemoteId($ipn_data['auth_id']);
// If not, bail now because authorization transactions should be created
// by the Express Checkout API request itself.
if (!$payment) {
$this->logger
->warning('IPN for Order @order_number ignored: authorization transaction already created.', [
'@order_number' => $ipn_data['invoice'],
]);
return FALSE;
}
$payment
->setAmount($amount);
$payment
->setState($this
->getStatusMapping($ipn_data['payment_status']));
// Update the remote id.
$payment
->setRemoteId($ipn_data['txn_id']);
}
elseif ($ipn_data['payment_status'] == 'Refunded') {
// Get the corresponding parent transaction and refund it.
$payment = $payment_storage
->loadByRemoteId($ipn_data['txn_id']);
if (!$payment) {
$this->logger
->warning('IPN for Order @order_number ignored: the transaction to be refunded does not exist.', [
'@order_number' => $ipn_data['invoice'],
]);
return FALSE;
}
elseif ($payment
->getState() == 'refunded') {
$this->logger
->warning('IPN for Order @order_number ignored: the transaction is already refunded.', [
'@order_number' => $ipn_data['invoice'],
]);
return FALSE;
}
$amount = new Price((string) $ipn_data['mc_gross'], $ipn_data['mc_currency']);
// Check if the Refund is partial or full.
$old_refunded_amount = $payment
->getRefundedAmount();
$new_refunded_amount = $old_refunded_amount
->add($amount);
if ($new_refunded_amount
->lessThan($payment
->getAmount())) {
$payment
->setState('partially_refunded');
}
else {
$payment
->setState('refunded');
}
$payment
->setRefundedAmount($new_refunded_amount);
}
if (isset($payment)) {
$payment
->setRemoteState($ipn_data['payment_status']);
$payment
->save();
}
}
/**
* {@inheritdoc}
*/
public function getRedirectUrl() {
if ($this
->getMode() == 'test') {
return 'https://www.sandbox.paypal.com/checkoutnow';
}
else {
return 'https://www.paypal.com/checkoutnow';
}
}
/**
* {@inheritdoc}
*/
public function getApiUrl() {
if ($this
->getMode() == 'test') {
return 'https://api-3t.sandbox.paypal.com/nvp';
}
else {
return 'https://api-3t.paypal.com/nvp';
}
}
/**
* {@inheritdoc}
*/
public function setExpressCheckout(PaymentInterface $payment, array $extra) {
$order = $payment
->getOrder();
$amount = $this->rounder
->round($payment
->getAmount());
$configuration = $this
->getConfiguration();
if ($extra['capture']) {
$payment_action = 'Sale';
}
else {
$payment_action = 'Authorization';
}
// Build a name-value pair array for this transaction.
$nvp_data = [
'METHOD' => 'SetExpressCheckout',
// Default the Express Checkout landing page to the Mark solution.
'SOLUTIONTYPE' => 'Mark',
'LANDINGPAGE' => 'Login',
// Disable entering notes in PayPal, we don't have any way to accommodate
// them right now.
'ALLOWNOTE' => '0',
'PAYMENTREQUEST_0_PAYMENTACTION' => $payment_action,
'PAYMENTREQUEST_0_AMT' => $amount
->getNumber(),
'PAYMENTREQUEST_0_CURRENCYCODE' => $amount
->getCurrencyCode(),
'PAYMENTREQUEST_0_INVNUM' => $order
->id() . '-' . $this->time
->getCurrentTime(),
// Set the return and cancel URLs.
'RETURNURL' => $extra['return_url'],
'CANCELURL' => $extra['cancel_url'],
];
// Check if there is a reference transaction, and also see if a billing
// agreement was supplied.
if (!empty($configuration['reference_transactions']) && !empty($configuration['ba_desc'])) {
$nvp_data['BILLINGTYPE'] = 'MerchantInitiatedBillingSingleAgreement';
$nvp_data['L_BILLINGTYPE0'] = 'MerchantInitiatedBillingSingleAgreement';
$nvp_data['L_BILLINGAGREEMENTDESCRIPTION0'] = $configuration['ba_desc'];
}
// If Express Checkout Account Optional is enabled...
if ($configuration['solution_type'] != 'Mark') {
// Update the solution type and landing page parameters accordingly.
$nvp_data['SOLUTIONTYPE'] = 'Sole';
if ($configuration['solution_type'] == 'SoleBilling') {
$nvp_data['LANDINGPAGE'] = 'Billing';
}
}
// Add itemized information to the API request.
$nvp_data += $this
->itemizeOrder($order, $amount
->getCurrencyCode());
// If the shipping module is not enabled, or if
// "Shipping address collection" is configured to not send the address to
// PayPal, set the NOSHIPPING parameter to 1.
if ($configuration['shipping_prompt'] == self::SHIPPING_SKIP || !$this->moduleHandler
->moduleExists('commerce_shipping')) {
$nvp_data['NOSHIPPING'] = '1';
}
else {
// Check if the order references shipments.
if ($order
->hasField('shipments') && !$order
->get('shipments')
->isEmpty()) {
// Gather the shipping profiles and only send shipping information if
// there's only one shipping profile referenced by the shipments.
$shipping_profiles = [];
// Loop over the shipments to collect shipping profiles.
foreach ($order
->get('shipments')
->referencedEntities() as $shipment) {
if ($shipment
->get('shipping_profile')
->isEmpty()) {
continue;
}
$shipping_profile = $shipment
->getShippingProfile();
$shipping_profiles[$shipping_profile
->id()] = $shipping_profile;
}
// Don't send the shipping profile if we found more than one.
if ($shipping_profiles && count($shipping_profiles) === 1) {
$shipping_profile = reset($shipping_profiles);
/** @var \Drupal\address\AddressInterface $address */
$address = $shipping_profile->address
->first();
$name = $address
->getGivenName() . ' ' . $address
->getFamilyName();
$shipping_info = [
'PAYMENTREQUEST_0_SHIPTONAME' => substr($name, 0, 32),
'PAYMENTREQUEST_0_SHIPTOSTREET' => substr($address
->getAddressLine1(), 0, 100),
'PAYMENTREQUEST_0_SHIPTOSTREET2' => substr($address
->getAddressLine2(), 0, 100),
'PAYMENTREQUEST_0_SHIPTOCITY' => substr($address
->getLocality(), 0, 40),
'PAYMENTREQUEST_0_SHIPTOSTATE' => substr($address
->getAdministrativeArea(), 0, 40),
'PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE' => $address
->getCountryCode(),
'PAYMENTREQUEST_0_SHIPTOZIP' => substr($address
->getPostalCode(), 0, 20),
];
// Filter out empty values.
$nvp_data += array_filter($shipping_info);
// Do not prompt for an Address at Paypal.
if ($configuration['shipping_prompt'] != self::SHIPPING_ASK_ALWAYS) {
$nvp_data += [
'NOSHIPPING' => '1',
'ADDROVERRIDE' => '1',
];
}
else {
$nvp_data += [
'NOSHIPPING' => '0',
'ADDROVERRIDE' => '0',
];
}
}
else {
$nvp_data['NOSHIPPING'] = '0';
}
}
}
// Send the order's email if not empty.
if (!empty($order
->getEmail())) {
$nvp_data['PAYMENTREQUEST_0_EMAIL'] = $order
->getEmail();
}
// Make the PayPal NVP API request.
return $this
->doRequest($nvp_data, $order);
}
/**
* Returns a name-value pair array of information to the API request.
*
* @param \Drupal\commerce_order\Entity\OrderInterface $order
* The order entity.
* @param string $currency_code
* The currency code.
*
* @return array
* A name-value pair array.
*/
protected function itemizeOrder(OrderInterface $order, $currency_code) {
$nvp_data = [];
$n = 0;
// Calculate the items total.
$items_total = new Price('0', $currency_code);
// Add order item data.
foreach ($order
->getItems() as $item) {
$item_amount = $this->rounder
->round($item
->getUnitPrice());
$nvp_data += [
'L_PAYMENTREQUEST_0_NAME' . $n => $item
->getTitle(),
'L_PAYMENTREQUEST_0_AMT' . $n => $item_amount
->getNumber(),
'L_PAYMENTREQUEST_0_QTY' . $n => $item
->getQuantity(),
];
$items_total = $items_total
->add($item
->getTotalPrice());
$n++;
}
// Initialize Shipping|Tax prices, they need to be sent
// separately to PayPal.
$shipping_amount = new Price('0', $currency_code);
$tax_amount = new Price('0', $currency_code);
// Collect the adjustments.
$adjustments = [];
foreach ($order
->collectAdjustments() as $adjustment) {
// Skip included adjustments.
if ($adjustment
->isIncluded()) {
continue;
}
// Tax & Shipping adjustments need to be handled separately.
if ($adjustment
->getType() == 'shipping') {
$shipping_amount = $shipping_amount
->add($adjustment
->getAmount());
}
elseif ($adjustment
->getType() == 'tax') {
$tax_amount = $tax_amount
->add($adjustment
->getAmount());
}
else {
// Collect other adjustments.
$type = $adjustment
->getType();
$source_id = $adjustment
->getSourceId();
if (empty($source_id)) {
// Adjustments without a source ID are always shown standalone.
$key = count($adjustments);
}
else {
// Adjustments with the same type and source ID are combined.
$key = $type . '_' . $source_id;
}
if (empty($adjustments[$key])) {
$adjustments[$key] = [
'type' => $type,
'label' => (string) $adjustment
->getLabel(),
'total' => $adjustment
->getAmount(),
];
}
else {
$adjustments[$key]['total'] = $adjustments[$key]['total']
->add($adjustment
->getAmount());
}
}
}
foreach ($adjustments as $adjustment) {
$adjustment_amount = $this->rounder
->round($adjustment['total']);
$nvp_data += [
'L_PAYMENTREQUEST_0_NAME' . $n => $adjustment['label'],
'L_PAYMENTREQUEST_0_AMT' . $n => $adjustment_amount
->getNumber(),
'L_PAYMENTREQUEST_0_QTY' . $n => 1,
];
// Add the adjustment to the items total.
$items_total = $items_total
->add($adjustment['total']);
$n++;
}
// Send the items total.
$items_total = $this->rounder
->round($items_total);
$nvp_data['PAYMENTREQUEST_0_ITEMAMT'] = $items_total
->getNumber();
// Send the shipping amount separately.
if (!$shipping_amount
->isZero()) {
$shipping_amount = $this->rounder
->round($shipping_amount);
$nvp_data['PAYMENTREQUEST_0_SHIPPINGAMT'] = $shipping_amount
->getNumber();
}
// Send the tax amount.
if (!$tax_amount
->isZero()) {
$tax_amount = $this->rounder
->round($tax_amount);
$nvp_data['PAYMENTREQUEST_0_TAXAMT'] = $tax_amount
->getNumber();
}
return $nvp_data;
}
/**
* {@inheritdoc}
*/
public function getExpressCheckoutDetails(OrderInterface $order) {
// Get the Express Checkout order token.
$order_express_checkout_data = $order
->getData('paypal_express_checkout');
// Build a name-value pair array to obtain buyer information from PayPal.
$nvp_data = [
'METHOD' => 'GetExpressCheckoutDetails',
'TOKEN' => $order_express_checkout_data['token'],
];
// Make the PayPal NVP API request.
return $this
->doRequest($nvp_data, $order);
}
/**
* {@inheritdoc}
*/
public function doExpressCheckoutDetails(OrderInterface $order) {
// Build NVP data for PayPal API request.
$order_express_checkout_data = $order
->getData('paypal_express_checkout');
$amount = $this->rounder
->round($order
->getTotalPrice());
if ($order_express_checkout_data['capture']) {
$payment_action = 'Sale';
}
else {
$payment_action = 'Authorization';
}
$nvp_data = [
'METHOD' => 'DoExpressCheckoutPayment',
'TOKEN' => $order_express_checkout_data['token'],
'PAYMENTREQUEST_0_AMT' => $amount
->getNumber(),
'PAYMENTREQUEST_0_CURRENCYCODE' => $amount
->getCurrencyCode(),
'PAYMENTREQUEST_0_INVNUM' => $order
->getOrderNumber(),
'PAYERID' => $order_express_checkout_data['payerid'],
'PAYMENTREQUEST_0_PAYMENTACTION' => $payment_action,
];
// Make the PayPal NVP API request.
return $this
->doRequest($nvp_data, $order);
}
/**
* {@inheritdoc}
*/
public function doCapture(PaymentInterface $payment, $amount) {
$order = $payment
->getOrder();
// Build a name-value pair array for this transaction.
$nvp_data = [
'METHOD' => 'DoCapture',
'AUTHORIZATIONID' => $payment
->getRemoteId(),
'AMT' => $amount,
'CURRENCYCODE' => $payment
->getAmount()
->getCurrencyCode(),
'INVNUM' => $order
->getOrderNumber(),
'COMPLETETYPE' => 'Complete',
];
// Make the PayPal NVP API request.
return $this
->doRequest($nvp_data, $payment
->getOrder());
}
/**
* {@inheritdoc}
*/
public function doVoid(PaymentInterface $payment) {
// Build a name-value pair array for this transaction.
$nvp_data = [
'METHOD' => 'DoVoid',
'AUTHORIZATIONID' => $payment
->getRemoteId(),
];
// Make the PayPal NVP API request.
return $this
->doRequest($nvp_data, $payment
->getOrder());
}
/**
* {@inheritdoc}
*/
public function doRefundTransaction(PaymentInterface $payment, array $extra) {
// Build a name-value pair array for this transaction.
$nvp_data = [
'METHOD' => 'RefundTransaction',
'TRANSACTIONID' => $payment
->getRemoteId(),
'REFUNDTYPE' => $extra['refund_type'],
'AMT' => $extra['amount'],
'CURRENCYCODE' => $payment
->getAmount()
->getCurrencyCode(),
];
// Make the PayPal NVP API request.
return $this
->doRequest($nvp_data, $payment
->getOrder());
}
/**
* {@inheritdoc}
*/
public function doRequest(array $nvp_data, OrderInterface $order = NULL) {
// Add the default name-value pairs to the array.
$configuration = $this
->getConfiguration();
$nvp_data += [
// API credentials.
'USER' => $configuration['api_username'],
'PWD' => $configuration['api_password'],
'SIGNATURE' => $configuration['signature'],
'VERSION' => '124.0',
];
// Allow modules to alter the NVP request.
$event = new ExpressCheckoutRequestEvent($nvp_data, $order);
$this->eventDispatcher
->dispatch(PayPalEvents::EXPRESS_CHECKOUT_REQUEST, $event);
$nvp_data = $event
->getNvpData();
// Make PayPal request.
$request = $this->httpClient
->post($this
->getApiUrl(), [
'form_params' => $nvp_data,
])
->getBody()
->getContents();
parse_str(html_entity_decode($request), $paypal_response);
return $paypal_response;
}
/**
* Returns a mapping of PayPal payment statuses to payment states.
*
* @param string $status
* (optional) The PayPal payment status.
*
* @return array|string
* An array containing the PayPal remote statuses as well as their
* corresponding states. if $status is specified, the corresponding state
* is returned.
*/
protected function getStatusMapping($status = NULL) {
$mapping = [
'Voided' => 'authorization_voided',
'Pending' => 'authorization',
'Processed' => 'completed',
'Completed' => 'completed',
'Refunded' => 'refunded',
'Partially-Refunded' => 'partially_refunded',
'Expired' => 'authorization_expired',
];
// If a status was passed, return its corresponding payment state.
if (isset($status) && isset($mapping[$status])) {
return $mapping[$status];
}
return $mapping;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
ExpressCheckout:: |
protected | property | The event dispatcher. | |
ExpressCheckout:: |
protected | property | The HTTP client. | |
ExpressCheckout:: |
protected | property | The IPN handler. | |
ExpressCheckout:: |
protected | property | The logger. | |
ExpressCheckout:: |
protected | property | Module handler service. | |
ExpressCheckout:: |
protected | property | The price rounder. | |
ExpressCheckout:: |
protected | property |
The time. Overrides PaymentGatewayBase:: |
|
ExpressCheckout:: |
public | function |
Form constructor. Overrides PaymentGatewayBase:: |
|
ExpressCheckout:: |
public | function |
Captures the given authorized payment. Overrides SupportsAuthorizationsInterface:: |
|
ExpressCheckout:: |
public static | function |
Creates an instance of the plugin. Overrides PaymentGatewayBase:: |
|
ExpressCheckout:: |
public | function |
Gets default configuration for this plugin. Overrides PaymentGatewayBase:: |
|
ExpressCheckout:: |
public | function |
DoCapture API Operation (NVP) request. Overrides ExpressCheckoutInterface:: |
|
ExpressCheckout:: |
public | function |
GetExpressCheckoutDetails API Operation (NVP) request. Overrides ExpressCheckoutInterface:: |
|
ExpressCheckout:: |
public | function |
RefundTransaction API Operation (NVP) request. Overrides ExpressCheckoutInterface:: |
|
ExpressCheckout:: |
public | function |
Performs a PayPal Express Checkout NVP API request. Overrides ExpressCheckoutInterface:: |
|
ExpressCheckout:: |
public | function |
DoVoid API Operation (NVP) request. Overrides ExpressCheckoutInterface:: |
|
ExpressCheckout:: |
public | function |
Gets the API URL. Overrides ExpressCheckoutInterface:: |
|
ExpressCheckout:: |
public | function |
GetExpressCheckoutDetails API Operation (NVP) request. Overrides ExpressCheckoutInterface:: |
|
ExpressCheckout:: |
public | function |
Gets the redirect URL. Overrides ExpressCheckoutInterface:: |
|
ExpressCheckout:: |
protected | function | Returns a mapping of PayPal payment statuses to payment states. | |
ExpressCheckout:: |
protected | function | Returns a name-value pair array of information to the API request. | |
ExpressCheckout:: |
public | function |
Processes the notification request. Overrides OffsitePaymentGatewayBase:: |
|
ExpressCheckout:: |
public | function |
Processes the "return" request. Overrides OffsitePaymentGatewayBase:: |
|
ExpressCheckout:: |
public | function |
Refunds the given payment. Overrides SupportsRefundsInterface:: |
|
ExpressCheckout:: |
public | function |
SetExpressCheckout API Operation (NVP) request. Overrides ExpressCheckoutInterface:: |
|
ExpressCheckout:: |
constant | |||
ExpressCheckout:: |
constant | |||
ExpressCheckout:: |
constant | |||
ExpressCheckout:: |
public | function |
Form submission handler. Overrides PaymentGatewayBase:: |
|
ExpressCheckout:: |
public | function |
Form validation handler. Overrides PaymentGatewayBase:: |
|
ExpressCheckout:: |
public | function |
Voids the given payment. Overrides SupportsVoidsInterface:: |
|
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
OffsitePaymentGatewayBase:: |
public | function |
Gets the URL to the "notify" page. Overrides OffsitePaymentGatewayInterface:: |
|
OffsitePaymentGatewayBase:: |
public | function |
Processes the "cancel" request. Overrides OffsitePaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
protected | property | The ID of the parent config entity. | |
PaymentGatewayBase:: |
protected | property | The entity type manager. | |
PaymentGatewayBase:: |
protected | property | The minor units converter. | |
PaymentGatewayBase:: |
protected | property | The parent config entity. | |
PaymentGatewayBase:: |
protected | property | The payment method types handled by the gateway. | |
PaymentGatewayBase:: |
protected | property | The payment type used by the gateway. | |
PaymentGatewayBase:: |
protected | function | Asserts that the payment method is neither empty nor expired. | |
PaymentGatewayBase:: |
protected | function | Asserts that the payment state matches one of the allowed states. | |
PaymentGatewayBase:: |
protected | function | Asserts that the refund amount is valid. | |
PaymentGatewayBase:: |
public | function |
Builds a label for the given AVS response code and card type. Overrides PaymentGatewayInterface:: |
2 |
PaymentGatewayBase:: |
public | function |
Builds the available operations for the given payment. Overrides PaymentGatewayInterface:: |
1 |
PaymentGatewayBase:: |
public | function |
Calculates dependencies for the configured plugin. Overrides DependentPluginInterface:: |
|
PaymentGatewayBase:: |
public | function | ||
PaymentGatewayBase:: |
public | function | ||
PaymentGatewayBase:: |
public | function | ||
PaymentGatewayBase:: |
public | function |
Gets whether the payment gateway collects billing information. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets this plugin's configuration. Overrides ConfigurableInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the credit card types handled by the gateway. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
protected | function | Gets the default payment gateway forms. | 1 |
PaymentGatewayBase:: |
public | function |
Gets the default payment method type. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the payment gateway display label. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the JS library ID. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the payment gateway label. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the mode in which the payment gateway is operating. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the payment method types handled by the payment gateway. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the payment type used by the payment gateway. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
protected | function | Gets the remote customer ID for the given user. | |
PaymentGatewayBase:: |
public | function |
Gets the supported modes. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Sets the configuration for this plugin instance. Overrides ConfigurableInterface:: |
|
PaymentGatewayBase:: |
protected | function | Sets the remote customer ID for the given user. | |
PaymentGatewayBase:: |
public | function |
Converts the given amount to its minor units. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Constructs a new PaymentGatewayBase object. Overrides PluginBase:: |
3 |
PaymentGatewayBase:: |
public | function |
Overrides DependencySerializationTrait:: |
|
PaymentGatewayBase:: |
public | function |
Overrides DependencySerializationTrait:: |
|
PluginBase:: |
protected | property | Configuration information passed into the plugin. | 1 |
PluginBase:: |
protected | property | The plugin implementation definition. | 1 |
PluginBase:: |
protected | property | The plugin_id. | |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
3 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
PluginWithFormsTrait:: |
public | function | ||
PluginWithFormsTrait:: |
public | function | ||
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |