mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-05 08:59:14 +08:00
Merge pull request #251 from woocommerce/PCP-244-saved-credit-card-does-not-auto-
Improve UX for stored credit card payments
This commit is contained in:
commit
caa33ab51e
8 changed files with 167 additions and 63 deletions
|
@ -139,24 +139,7 @@ class PaymentTokenEndpoint {
|
|||
foreach ( $json->payment_tokens as $token_value ) {
|
||||
$tokens[] = $this->factory->from_paypal_response( $token_value );
|
||||
}
|
||||
if ( empty( $tokens ) ) {
|
||||
$error = new RuntimeException(
|
||||
sprintf(
|
||||
// translators: %d is the customer id.
|
||||
__( 'No token stored for customer %d.', 'woocommerce-paypal-payments' ),
|
||||
$id
|
||||
)
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$error->getMessage(),
|
||||
array(
|
||||
'args' => $args,
|
||||
'response' => $response,
|
||||
)
|
||||
);
|
||||
throw $error;
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,3 +7,7 @@
|
|||
.payments-sdk-contingency-handler {
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
|
||||
.ppcp-credit-card-gateway-form-field-disabled {
|
||||
opacity: .5 !important;
|
||||
}
|
||||
|
|
|
@ -24,9 +24,11 @@ class CheckoutBootstap {
|
|||
|
||||
})
|
||||
|
||||
jQuery('#saved-credit-card').on('change', () => {
|
||||
this.displayPlaceOrderButtonForSavedCreditCards()
|
||||
})
|
||||
jQuery(document).on('hosted_fields_loaded', () => {
|
||||
jQuery('#saved-credit-card').on('change', () => {
|
||||
this.displayPlaceOrderButtonForSavedCreditCards()
|
||||
})
|
||||
});
|
||||
|
||||
this.switchBetweenPayPalandOrderButton()
|
||||
this.displayPlaceOrderButtonForSavedCreditCards()
|
||||
|
@ -100,13 +102,41 @@ class CheckoutBootstap {
|
|||
this.renderer.hideButtons(this.gateway.messages.wrapper)
|
||||
this.renderer.hideButtons(this.gateway.hosted_fields.wrapper)
|
||||
jQuery('#place_order').show()
|
||||
this.disableCreditCardFields()
|
||||
} else {
|
||||
jQuery('#place_order').hide()
|
||||
this.renderer.hideButtons(this.gateway.button.wrapper)
|
||||
this.renderer.hideButtons(this.gateway.messages.wrapper)
|
||||
this.renderer.showButtons(this.gateway.hosted_fields.wrapper)
|
||||
this.enableCreditCardFields()
|
||||
}
|
||||
}
|
||||
|
||||
disableCreditCardFields() {
|
||||
jQuery('label[for="ppcp-credit-card-gateway-card-number"]').addClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('#ppcp-credit-card-gateway-card-number').addClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('label[for="ppcp-credit-card-gateway-card-expiry"]').addClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('#ppcp-credit-card-gateway-card-expiry').addClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('label[for="ppcp-credit-card-gateway-card-cvc"]').addClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('#ppcp-credit-card-gateway-card-cvc').addClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('label[for="vault"]').addClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('#ppcp-credit-card-vault').addClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('#ppcp-credit-card-vault').attr("disabled", true)
|
||||
this.renderer.disableCreditCardFields()
|
||||
}
|
||||
|
||||
enableCreditCardFields() {
|
||||
jQuery('label[for="ppcp-credit-card-gateway-card-number"]').removeClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('#ppcp-credit-card-gateway-card-number').removeClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('label[for="ppcp-credit-card-gateway-card-expiry"]').removeClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('#ppcp-credit-card-gateway-card-expiry').removeClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('label[for="ppcp-credit-card-gateway-card-cvc"]').removeClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('#ppcp-credit-card-gateway-card-cvc').removeClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('label[for="vault"]').removeClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('#ppcp-credit-card-vault').removeClass('ppcp-credit-card-gateway-form-field-disabled')
|
||||
jQuery('#ppcp-credit-card-vault').attr("disabled", false)
|
||||
this.renderer.enableCreditCardFields()
|
||||
}
|
||||
}
|
||||
|
||||
export default CheckoutBootstap
|
||||
|
|
|
@ -100,6 +100,7 @@ class CreditCardRenderer {
|
|||
}
|
||||
}
|
||||
}).then(hostedFields => {
|
||||
document.dispatchEvent(new CustomEvent("hosted_fields_loaded"));
|
||||
this.currentHostedFieldsInstance = hostedFields;
|
||||
|
||||
hostedFields.on('inputSubmitRequest', () => {
|
||||
|
@ -141,6 +142,36 @@ class CreditCardRenderer {
|
|||
)
|
||||
}
|
||||
|
||||
disableFields() {
|
||||
this.currentHostedFieldsInstance.setAttribute({
|
||||
field: 'number',
|
||||
attribute: 'disabled'
|
||||
})
|
||||
this.currentHostedFieldsInstance.setAttribute({
|
||||
field: 'cvv',
|
||||
attribute: 'disabled'
|
||||
})
|
||||
this.currentHostedFieldsInstance.setAttribute({
|
||||
field: 'expirationDate',
|
||||
attribute: 'disabled'
|
||||
})
|
||||
}
|
||||
|
||||
enableFields() {
|
||||
this.currentHostedFieldsInstance.removeAttribute({
|
||||
field: 'number',
|
||||
attribute: 'disabled'
|
||||
})
|
||||
this.currentHostedFieldsInstance.removeAttribute({
|
||||
field: 'cvv',
|
||||
attribute: 'disabled'
|
||||
})
|
||||
this.currentHostedFieldsInstance.removeAttribute({
|
||||
field: 'expirationDate',
|
||||
attribute: 'disabled'
|
||||
})
|
||||
}
|
||||
|
||||
_submit(contextConfig) {
|
||||
this.spinner.block();
|
||||
this.errorHandler.clear();
|
||||
|
|
|
@ -43,6 +43,14 @@ class Renderer {
|
|||
domElement.style.display = 'block';
|
||||
return true;
|
||||
}
|
||||
|
||||
disableCreditCardFields() {
|
||||
this.creditCardRenderer.disableFields();
|
||||
}
|
||||
|
||||
enableCreditCardFields() {
|
||||
this.creditCardRenderer.enableFields();
|
||||
}
|
||||
}
|
||||
|
||||
export default Renderer;
|
||||
export default Renderer;
|
||||
|
|
|
@ -107,13 +107,8 @@ class PaymentTokenRepository {
|
|||
* @param PaymentToken[] $tokens The tokens.
|
||||
* @return bool Whether tokens contains card or not.
|
||||
*/
|
||||
public function tokens_contains_card( $tokens ): bool {
|
||||
foreach ( $tokens as $token ) {
|
||||
if ( isset( $token->source()->card ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public function tokens_contains_card( array $tokens ): bool {
|
||||
return $this->token_contains_source( $tokens, 'card' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,13 +117,8 @@ class PaymentTokenRepository {
|
|||
* @param PaymentToken[] $tokens The tokens.
|
||||
* @return bool Whether tokens contains card or not.
|
||||
*/
|
||||
public function tokens_contains_paypal( $tokens ): bool {
|
||||
foreach ( $tokens as $token ) {
|
||||
if ( isset( $token->source()->paypal ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public function tokens_contains_paypal( array $tokens ): bool {
|
||||
return $this->token_contains_source( $tokens, 'paypal' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -145,4 +135,21 @@ class PaymentTokenRepository {
|
|||
update_user_meta( $id, self::USER_META, $token_array );
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if tokens has the given source.
|
||||
*
|
||||
* @param array $tokens Payment tokens.
|
||||
* @param string $source_type Payment token source type.
|
||||
* @return bool Whether tokens contains source or not.
|
||||
*/
|
||||
private function token_contains_source( array $tokens, string $source_type ): bool {
|
||||
foreach ( $tokens as $token ) {
|
||||
if ( isset( $token->source()->card ) && 'card' === $source_type || isset( $token->source()->paypal ) && 'paypal' === $source_type ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,33 +127,6 @@ class PaymentTokenEndpointTest extends TestCase
|
|||
$this->sut->for_user($id);
|
||||
}
|
||||
|
||||
public function testForUserFailBecauseEmptyTokens()
|
||||
{
|
||||
$id = 1;
|
||||
$token = Mockery::mock(Token::class);
|
||||
$headers = Mockery::mock(Requests_Utility_CaseInsensitiveDictionary::class);
|
||||
$headers->shouldReceive('getAll');
|
||||
$rawResponse = [
|
||||
'body' => '{"payment_tokens":[]}',
|
||||
'headers' => $headers,
|
||||
];
|
||||
$this->bearer->shouldReceive('bearer')
|
||||
->andReturn($token);
|
||||
$token->shouldReceive('token')
|
||||
->andReturn('bearer');
|
||||
$this->ensureRequestForUser($rawResponse, $id);
|
||||
|
||||
|
||||
expect('wp_remote_get')->andReturn($rawResponse);
|
||||
expect('is_wp_error')->with($rawResponse)->andReturn(false);
|
||||
expect('wp_remote_retrieve_response_code')->with($rawResponse)->andReturn(200);
|
||||
$this->logger->shouldReceive('log');
|
||||
$this->logger->shouldReceive('debug');
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->sut->for_user($id);
|
||||
}
|
||||
|
||||
public function testDeleteToken()
|
||||
{
|
||||
$paymentToken = Mockery::mock(PaymentToken::class);
|
||||
|
|
|
@ -90,4 +90,72 @@ class PaymentTokenRepositoryTest extends TestCase
|
|||
|
||||
$this->sut->delete_token($id, $paymentToken);
|
||||
}
|
||||
|
||||
public function testAllForUserId()
|
||||
{
|
||||
$id = 1;
|
||||
$tokens = [];
|
||||
|
||||
$this->endpoint->shouldReceive('for_user')
|
||||
->with($id)
|
||||
->andReturn($tokens);
|
||||
expect('update_user_meta')->with($id, $this->sut::USER_META, $tokens);
|
||||
|
||||
$result = $this->sut->all_for_user_id($id);
|
||||
$this->assertSame($tokens, $result);
|
||||
}
|
||||
|
||||
public function test_AllForUserIdReturnsEmptyArrayIfGettingTokenFails()
|
||||
{
|
||||
$id = 1;
|
||||
$tokens = [];
|
||||
|
||||
$this->endpoint
|
||||
->expects('for_user')
|
||||
->with($id)
|
||||
->andThrow(RuntimeException::class);
|
||||
|
||||
$result = $this->sut->all_for_user_id($id);
|
||||
$this->assertSame($tokens, $result);
|
||||
}
|
||||
|
||||
public function testTokensContainCardReturnsTrue()
|
||||
{
|
||||
$source = new \stdClass();
|
||||
$card = new \stdClass();
|
||||
$source->card = $card;
|
||||
$token = Mockery::mock(PaymentToken::class);
|
||||
$tokens = [$token];
|
||||
|
||||
$token->shouldReceive('source')->andReturn($source);
|
||||
|
||||
$this->assertTrue($this->sut->tokens_contains_card($tokens));
|
||||
}
|
||||
|
||||
public function testTokensContainCardReturnsFalse()
|
||||
{
|
||||
$tokens = [];
|
||||
$this->assertFalse($this->sut->tokens_contains_card($tokens));
|
||||
}
|
||||
|
||||
public function testTokensContainPayPalReturnsTrue()
|
||||
{
|
||||
$source = new \stdClass();
|
||||
$paypal = new \stdClass();
|
||||
$source->paypal = $paypal;
|
||||
$token = Mockery::mock(PaymentToken::class);
|
||||
$tokens = [$token];
|
||||
|
||||
$token->shouldReceive('source')->andReturn($source);
|
||||
|
||||
$this->assertTrue($this->sut->tokens_contains_paypal($tokens));
|
||||
}
|
||||
|
||||
public function testTokensContainPayPalReturnsFalse()
|
||||
{
|
||||
$tokens = [];
|
||||
$this->assertFalse($this->sut->tokens_contains_paypal($tokens));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue