Skip to content
3 changes: 3 additions & 0 deletions .github/workflows-config/typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ extend-ignore-re = [
"Vestibulum lectus .*",
]

[default.extend-words]
ba = "ba"

[default.extend-identifiers]
Automattic = "Automattic"
automattic = "automattic"
Expand Down
10 changes: 7 additions & 3 deletions modules/ppcp-api-client/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,13 @@
'api.paypal-host' => function ( ContainerInterface $container ): string {
return PAYPAL_API_URL;
},
// It seems this 'api.paypal-website-url' key is always overridden in ppcp-onboarding/services.php.
'api.paypal-website-url' => function ( ContainerInterface $container ): string {
return PAYPAL_URL;
'api.paypal-website-url' => static function ( ContainerInterface $container ): string {
$environment = $container->get( 'settings.environment' );
assert( $environment instanceof Environment );
if ( $environment->is_sandbox() ) {
return $container->get( 'api.paypal-website-url-sandbox' );
}
return $container->get( 'api.paypal-website-url-production' );
},
'api.factory.paypal-checkout-url' => function ( ContainerInterface $container ): callable {
return function ( string $id ) use ( $container ): string {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useEffect } from '@wordpress/element';

export const PayPalPlaceOrderContent = ( {
description,
placeOrderButtonDescription,
eventRegistration,
emitResponse,
} ) => {
const { onPaymentSetup } = eventRegistration;
const { responseTypes } = emitResponse;

useEffect(
() =>
onPaymentSetup( () => {
return { type: responseTypes.SUCCESS };
} ),
[ onPaymentSetup, responseTypes ]
);

if ( placeOrderButtonDescription ) {
return (
<div>
<p dangerouslySetInnerHTML={ { __html: description } } />
<p
style={ { textAlign: 'center' } }
className="ppcp-place-order-description"
dangerouslySetInnerHTML={ {
__html: placeOrderButtonDescription,
} }
/>
</div>
);
}
return <div dangerouslySetInnerHTML={ { __html: description } } />;
};
44 changes: 18 additions & 26 deletions modules/ppcp-blocks/resources/js/checkout-block.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import BlockCheckoutMessagesBootstrap from './Bootstrap/BlockCheckoutMessagesBoo
import { PayPalComponent } from './Components/paypal';
import { BlockEditorPayPalComponent } from './Components/block-editor-paypal';
import { PaypalLabel } from './Components/paypal-label';
import { PayPalPlaceOrderContent } from './Components/paypal-place-order-content';
const namespace = 'ppcpBlocksPaypalExpressButtons';
const config = wc.wcSettings.getSetting( 'ppcp-gateway_data' );

Expand Down Expand Up @@ -72,35 +73,24 @@ if ( cartHasSubscriptionProducts( config.scriptData ) ) {

if ( blockEnabled ) {
if ( config.placeOrderEnabled && ! config.scriptData.continuation ) {
let descriptionElement = (
<div
dangerouslySetInnerHTML={ { __html: config.description } }
></div>
);
if ( config.placeOrderButtonDescription ) {
descriptionElement = (
<div>
<p
dangerouslySetInnerHTML={ {
__html: config.description,
} }
></p>
<p
style={ { textAlign: 'center' } }
className={ 'ppcp-place-order-description' }
dangerouslySetInnerHTML={ {
__html: config.placeOrderButtonDescription,
} }
></p>
</div>
);
}

registerPaymentMethod( {
name: config.id,
label: <PaypalLabel config={ config } />,
content: descriptionElement,
edit: descriptionElement,
content: (
<PayPalPlaceOrderContent
description={ config.description }
placeOrderButtonDescription={
config.placeOrderButtonDescription
}
/>
),
edit: (
<div
dangerouslySetInnerHTML={ {
__html: config.description,
} }
/>
),
placeOrderButtonLabel: config.placeOrderButtonText,
ariaLabel: config.title,
canMakePayment: ( cartData ) => {
Expand All @@ -109,6 +99,7 @@ if ( blockEnabled ) {
},
supports: {
features,
showSavedCards: true,
},
} );
}
Expand Down Expand Up @@ -187,6 +178,7 @@ if ( blockEnabled ) {
supports: {
features,
style: [ 'height', 'borderRadius' ],
showSavedCards: true,
},
} );
}
Expand Down
2 changes: 1 addition & 1 deletion modules/ppcp-vaulting/src/VaultingModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ function ( $tokens ) use ( $container ) {
&& ! $is_post // Don't check on POST so we have all payment methods on form submissions.
) {
foreach ( $tokens as $index => $token ) {
if ( $token instanceof PaymentTokenApplePay || $token instanceof PaymentTokenPayPal || $token instanceof PaymentTokenVenmo ) {
if ( $token instanceof PaymentTokenApplePay ) {
unset( $tokens[ $index ] );
}
}
Expand Down
16 changes: 15 additions & 1 deletion modules/ppcp-wc-gateway/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
use WooCommerce\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset;
use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\CaptureCardPayment;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\CapturePayPalPayment;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ShippingCallbackEndpoint;
Expand Down Expand Up @@ -118,7 +119,10 @@
$container->get( 'vaulting.vault-v3-enabled' ),
$container->get( 'vaulting.wc-payment-tokens' ),
$container->get( 'wcgateway.asset_getter' ),
$container->get( 'wcgateway.settings.admin-settings-enabled' )
$container->get( 'wcgateway.settings.admin-settings-enabled' ),
$container->get( 'wcgateway.endpoint.capture-paypal-payment' ),
$container->get( 'api.endpoint.order' ),
$container->get( 'api.prefix' )
);
},
'wcgateway.credit-card-gateway' => static function ( ContainerInterface $container ): CreditCardGateway {
Expand Down Expand Up @@ -1226,6 +1230,16 @@ static function ( ContainerInterface $container ): AuthorizeOrderActionNotice {
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'wcgateway.endpoint.capture-paypal-payment' => static function ( ContainerInterface $container ): CapturePayPalPayment {
return new CapturePayPalPayment(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
$container->get( 'api.factory.order' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'settings.settings-provider' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},

'wcgateway.settings.wc-tasks.simple-redirect-task-factory' => static function (): SimpleRedirectTaskFactoryInterface {
return new SimpleRedirectTaskFactory();
Expand Down
122 changes: 122 additions & 0 deletions modules/ppcp-wc-gateway/src/Endpoint/CapturePayPalPayment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

declare(strict_types=1);

namespace WooCommerce\PayPalCommerce\WcGateway\Endpoint;

use Psr\Log\LoggerInterface;
use RuntimeException;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\RequestTrait;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Settings\Data\SettingsProvider;
use WP_Error;

class CapturePayPalPayment {

use RequestTrait;

private string $host;

private Bearer $bearer;

private OrderFactory $order_factory;

private PurchaseUnitFactory $purchase_unit_factory;

private SettingsProvider $settings_provider;

private LoggerInterface $logger;

public function __construct(
string $host,
Bearer $bearer,
OrderFactory $order_factory,
PurchaseUnitFactory $purchase_unit_factory,
SettingsProvider $settings_provider,
LoggerInterface $logger
) {
$this->host = $host;
$this->bearer = $bearer;
$this->order_factory = $order_factory;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->settings_provider = $settings_provider;
$this->logger = $logger;
}

/**
* Creates PayPal order from the given PayPal/Venmo vault ID.
*
* @throws RuntimeException When request fails.
*/
public function create_order(
string $vault_id,
string $custom_id,
string $invoice_id,
WC_Order $wc_order,
string $payment_source_name = 'paypal'
): Order {
$intent = strtoupper( $this->settings_provider->payment_intent() ) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE';
$items = array( $this->purchase_unit_factory->from_wc_cart( null, false, $wc_order->get_payment_method() ) );

// phpcs:disable WordPress.Security.NonceVerification
$pay_for_order = wc_clean( wp_unslash( $_GET['pay_for_order'] ?? '' ) );
$order_key = wc_clean( wp_unslash( $_GET['key'] ?? '' ) );
// phpcs:enable
if ( $pay_for_order && $order_key === $wc_order->get_order_key() ) {
$items = array( $this->purchase_unit_factory->from_wc_order( $wc_order ) );
}

$data = array(
'intent' => $intent,
'purchase_units' => array_map(
static function ( PurchaseUnit $item ): array {
return $item->to_array();
},
$items
),
'payment_source' => array(
$payment_source_name => array(
'vault_id' => $vault_id,
'experience_context' => array(
'payment_method_preference' => 'IMMEDIATE_PAYMENT_REQUIRED',
),
),
),
'custom_id' => $custom_id,
'invoice_id' => $invoice_id,
);

$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v2/checkout/orders';
$args = array(
'method' => 'POST',
'headers' => array(
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
'PayPal-Request-Id' => uniqid( 'ppcp-', true ),
),
'body' => wp_json_encode( $data ),
);

$response = $this->request( $url, $args );
if ( $response instanceof WP_Error ) {
throw new RuntimeException( $response->get_error_message() );
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, do we also want to check the status code here? like is done in orderEndpoint for example?

$status_code = (int) wp_remote_retrieve_response_code( $response );
if ( 200 !== $status_code ) {
	throw new PayPalApiException( $json, $status_code );
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks!

$json = json_decode( $response['body'] );
$status_code = (int) wp_remote_retrieve_response_code( $response );
if ( ! in_array( $status_code, array( 200, 201 ), true ) ) {
$error = new PayPalApiException( $json, $status_code );
$this->logger->warning( $error->getMessage() );
throw $error;
}

return $this->order_factory->from_paypal_response( $json );
}
}
Loading