From 281d0054b85099131fd9ae2981cc5fcb76506e12 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Mon, 25 Mar 2024 10:16:45 -0400 Subject: [PATCH] feat: add Merchant Name label on payment sheet (#18663) CXSPA-6508 --- .../apple-pay/apple-pay.service.spec.ts | 14 +- .../apple-pay/apple-pay.service.ts | 58 +++---- .../google-pay/google-pay.service.spec.ts | 6 + .../google-pay/google-pay.service.ts | 142 +++++++++--------- .../opf-quick-buy.service.spec.ts | 19 +++ .../opf-quick-buy/opf-quick-buy.service.ts | 8 + .../base/root/model/opf-quick-buy.model.ts | 2 + 7 files changed, 150 insertions(+), 99 deletions(-) diff --git a/integration-libs/opf/base/components/opf-quick-buy/apple-pay/apple-pay.service.spec.ts b/integration-libs/opf/base/components/opf-quick-buy/apple-pay/apple-pay.service.spec.ts index fe0eb6a9d4c..a61187007e4 100644 --- a/integration-libs/opf/base/components/opf-quick-buy/apple-pay/apple-pay.service.spec.ts +++ b/integration-libs/opf/base/components/opf-quick-buy/apple-pay/apple-pay.service.spec.ts @@ -125,6 +125,7 @@ describe('ApplePayService', () => { 'getQuickBuyLocationContext', 'getQuickBuyProviderConfig', 'getQuickBuyDeliveryInfo', + 'getMerchantName', ]); applePaySessionFactoryMock = jasmine.createSpyObj( @@ -197,6 +198,7 @@ describe('ApplePayService', () => { describe('observable callbacks', () => { let config: ApplePayObservableConfig; + const merchantNameMock = 'Nakano'; beforeEach(() => { ( applePayObservableFactoryMock.initApplePayEventsHandler as jasmine.Spy @@ -209,6 +211,9 @@ describe('ApplePayService', () => { type: OpfQuickBuyDeliveryType.SHIPPING, }) ); + opfQuickBuyServiceMock.getMerchantName.and.returnValue( + of(merchantNameMock) + ); }); it('should handle validateMerchant change', () => { @@ -309,7 +314,7 @@ describe('ApplePayService', () => { expect(paymentMethodChangeResult.newTotal).toEqual({ amount: mockProduct.price?.value?.toString(), - label: mockProduct.name, + label: merchantNameMock, }); }); @@ -345,7 +350,7 @@ describe('ApplePayService', () => { .subscribe((actural) => (shippingMethodChangeResult = actural)); expect(shippingMethodChangeResult.newTotal).toEqual({ amount: mockCart.totalPrice?.value?.toString(), - label: mockProduct.name, + label: merchantNameMock, }); }); @@ -665,6 +670,7 @@ describe('ApplePayService', () => { it('should handle errors during Apple Pay session start', () => { service = TestBed.inject(ApplePayService); + const merchantNameMock = 'Nakano'; opfQuickBuyServiceMock.getQuickBuyDeliveryInfo.and.returnValue( of({ type: OpfQuickBuyDeliveryType.SHIPPING, @@ -680,6 +686,10 @@ describe('ApplePayService', () => { throwError('Error') ); + opfQuickBuyServiceMock.getMerchantName.and.returnValue( + of(merchantNameMock) + ); + service .start({ product: mockProduct, quantity: 1, countryCode: 'us' }) .subscribe({ diff --git a/integration-libs/opf/base/components/opf-quick-buy/apple-pay/apple-pay.service.ts b/integration-libs/opf/base/components/opf-quick-buy/apple-pay/apple-pay.service.ts index 85bf724c38f..148ee6914b9 100644 --- a/integration-libs/opf/base/components/opf-quick-buy/apple-pay/apple-pay.service.ts +++ b/integration-libs/opf/base/components/opf-quick-buy/apple-pay/apple-pay.service.ts @@ -7,7 +7,7 @@ /// import { Injectable, inject } from '@angular/core'; import { Address, Product, WindowRef } from '@spartacus/core'; -import { Observable, of, throwError } from 'rxjs'; +import { Observable, forkJoin, of, throwError } from 'rxjs'; import { catchError, finalize, @@ -27,11 +27,11 @@ import { ApplePayShippingType, ApplePayTransactionInput, OpfPaymentFacade, - OpfQuickBuyDeliveryInfo, OpfQuickBuyDeliveryType, OpfQuickBuyLocation, PaymentMethod, QuickBuyTransactionDetails, + defaultMerchantName, } from '@spartacus/opf/base/root'; import { Cart, DeliveryMode } from '@spartacus/cart/base/root'; @@ -61,7 +61,7 @@ export class ApplePayService { quantity: 0, addressIds: [], total: { - label: '', + label: defaultMerchantName, amount: '', currency: '', }, @@ -185,33 +185,35 @@ export class ApplePayService { countryCode, }; - return this.opfQuickBuyService - .getQuickBuyDeliveryInfo( + return forkJoin({ + deliveryInfo: this.opfQuickBuyService.getQuickBuyDeliveryInfo( this.transactionDetails.context as OpfQuickBuyLocation - ) - .pipe( - switchMap((deliveryInfo: OpfQuickBuyDeliveryInfo) => { - this.transactionDetails.deliveryInfo = deliveryInfo; - if (deliveryInfo.type === OpfQuickBuyDeliveryType.PICKUP) { - // Don't display shipping contact form on payment sheet - initialRequest.requiredShippingContactFields = []; - initialRequest.shippingType = ApplePayShippingType.STORE_PICKUP; - - return this.transactionDetails.context === - OpfQuickBuyLocation.PRODUCT - ? this.opfPickupInStoreHandlerService.getSingleProductDeliveryInfo() - : of(undefined); - } - return of(undefined); - }), - map((opfQuickBuyDeliveryInfo) => { - if (!opfQuickBuyDeliveryInfo) { - return initialRequest; - } - this.transactionDetails.deliveryInfo = opfQuickBuyDeliveryInfo; + ), + merchantName: this.opfQuickBuyService.getMerchantName(), + }).pipe( + switchMap(({ deliveryInfo, merchantName }) => { + this.transactionDetails.total.label = merchantName; + initialRequest.total.label = merchantName; + this.transactionDetails.deliveryInfo = deliveryInfo; + if (deliveryInfo.type === OpfQuickBuyDeliveryType.PICKUP) { + // Don't display shipping contact form on payment sheet + initialRequest.requiredShippingContactFields = []; + initialRequest.shippingType = ApplePayShippingType.STORE_PICKUP; + + return this.transactionDetails.context === OpfQuickBuyLocation.PRODUCT + ? this.opfPickupInStoreHandlerService.getSingleProductDeliveryInfo() + : of(undefined); + } + return of(undefined); + }), + map((opfQuickBuyDeliveryInfo) => { + if (!opfQuickBuyDeliveryInfo) { return initialRequest; - }) - ); + } + this.transactionDetails.deliveryInfo = opfQuickBuyDeliveryInfo; + return initialRequest; + }) + ); } protected handleSingleProductTransaction( diff --git a/integration-libs/opf/base/components/opf-quick-buy/google-pay/google-pay.service.spec.ts b/integration-libs/opf/base/components/opf-quick-buy/google-pay/google-pay.service.spec.ts index 0aa7ee9f563..3bab112149a 100644 --- a/integration-libs/opf/base/components/opf-quick-buy/google-pay/google-pay.service.spec.ts +++ b/integration-libs/opf/base/components/opf-quick-buy/google-pay/google-pay.service.spec.ts @@ -23,6 +23,7 @@ import { OpfQuickBuyService } from '../opf-quick-buy.service'; import { OpfGooglePayService } from './google-pay.service'; describe('OpfGooglePayService', () => { + const mockMerchantName = 'mockMerchantName'; let service: OpfGooglePayService; let mockResourceLoaderService: jasmine.SpyObj; let mockItemCounterService: jasmine.SpyObj; @@ -69,6 +70,7 @@ describe('OpfGooglePayService', () => { 'getQuickBuyLocationContext', 'getQuickBuyProviderConfig', 'getQuickBuyDeliveryInfo', + 'getMerchantName', ]); const googlePayApiMock = { @@ -495,6 +497,8 @@ describe('OpfGooglePayService', () => { of(OpfQuickBuyLocation.PRODUCT) ); + mockQuickBuyService.getMerchantName.and.returnValue(of(mockMerchantName)); + service.initTransaction(); expect(service['transactionDetails'].context).toBe( @@ -518,6 +522,8 @@ describe('OpfGooglePayService', () => { of(OpfQuickBuyLocation.CART) ); + mockQuickBuyService.getMerchantName.and.returnValue(of(mockMerchantName)); + service.initTransaction(); expect(service['transactionDetails'].context).toBe( diff --git a/integration-libs/opf/base/components/opf-quick-buy/google-pay/google-pay.service.ts b/integration-libs/opf/base/components/opf-quick-buy/google-pay/google-pay.service.ts index 54813256729..7684e8e07a2 100644 --- a/integration-libs/opf/base/components/opf-quick-buy/google-pay/google-pay.service.ts +++ b/integration-libs/opf/base/components/opf-quick-buy/google-pay/google-pay.service.ts @@ -14,12 +14,12 @@ import { ActiveConfiguration, OpfPaymentFacade, OpfProviderType, - OpfQuickBuyDeliveryInfo, OpfQuickBuyDeliveryType, OpfQuickBuyLocation, OpfResourceLoaderService, PaymentMethod, QuickBuyTransactionDetails, + defaultMerchantName, } from '@spartacus/opf/base/root'; import { CurrentProductService, @@ -72,7 +72,7 @@ export class OpfGooglePayService { // @ts-ignore merchantInfo: { // merchantId: 'spartacusStorefront', - merchantName: 'Spartacus Storefront', + merchantName: defaultMerchantName, }, shippingOptionRequired: true, shippingAddressRequired: true, @@ -116,7 +116,8 @@ export class OpfGooglePayService { } protected setGooglePaymentRequestConfig( - deliveryType: OpfQuickBuyDeliveryType + deliveryType: OpfQuickBuyDeliveryType, + merchantName: string ) { if (deliveryType === OpfQuickBuyDeliveryType.PICKUP) { this.googlePaymentClientOptions = { @@ -139,7 +140,7 @@ export class OpfGooglePayService { }; this.googlePaymentRequest = this.initialGooglePaymentRequest; } - + this.googlePaymentRequest.merchantInfo.merchantName = merchantName; this.updateGooglePaymentClient(); } @@ -261,79 +262,82 @@ export class OpfGooglePayService { handleSingleProductTransaction(): Observable { this.transactionDetails.context = OpfQuickBuyLocation.PRODUCT; - return this.opfQuickBuyService - .getQuickBuyDeliveryInfo(this.transactionDetails.context) - .pipe( - switchMap((deliveryInfo: OpfQuickBuyDeliveryInfo) => { - this.transactionDetails.deliveryInfo = deliveryInfo; - this.setGooglePaymentRequestConfig(deliveryInfo.type); - - return this.currentProductService.getProduct().pipe( - take(1), - switchMap((product: Product | null) => { - const count = this.itemCounterService.getCounter(); - this.transactionDetails.product = product as Product; - this.transactionDetails.quantity = count; - return this.opfCartHandlerService - .addProductToCart( - product?.code || '', - count, - this.transactionDetails.deliveryInfo?.pickupDetails?.name - ) - .pipe( - tap(() => { - this.setDeliveryMode( - undefined, - this.transactionDetails.deliveryInfo?.type - ); - this.updateTransactionInfo({ - totalPrice: this.estimateTotalPrice( - product?.price?.value - ), - currencyCode: - product?.price?.currencyIso || - this.initialTransactionInfo.currencyCode, - totalPriceStatus: - this.initialTransactionInfo.totalPriceStatus, - }); - }) - ); - }) - ); - }) - ); - } - - handleActiveCartTransaction(): Observable { - this.transactionDetails.context = OpfQuickBuyLocation.CART; - - return this.opfQuickBuyService - .getQuickBuyDeliveryInfo(this.transactionDetails.context) - .pipe( - switchMap((deliveryInfo: OpfQuickBuyDeliveryInfo) => { - this.transactionDetails.deliveryInfo = deliveryInfo; - this.setGooglePaymentRequestConfig(deliveryInfo.type); - - return this.setDeliveryMode(undefined, deliveryInfo.type).pipe( - switchMap(() => - this.opfCartHandlerService.getCurrentCart().pipe( - take(1), - tap((cart: Cart) => { - this.transactionDetails.cart = cart; + return forkJoin({ + deliveryInfo: this.opfQuickBuyService.getQuickBuyDeliveryInfo( + this.transactionDetails.context as OpfQuickBuyLocation + ), + merchantName: this.opfQuickBuyService.getMerchantName(), + }).pipe( + switchMap(({ deliveryInfo, merchantName }) => { + this.transactionDetails.deliveryInfo = deliveryInfo; + this.setGooglePaymentRequestConfig(deliveryInfo.type, merchantName); + return this.currentProductService.getProduct().pipe( + take(1), + switchMap((product: Product | null) => { + const count = this.itemCounterService.getCounter(); + this.transactionDetails.product = product as Product; + this.transactionDetails.quantity = count; + return this.opfCartHandlerService + .addProductToCart( + product?.code || '', + count, + this.transactionDetails.deliveryInfo?.pickupDetails?.name + ) + .pipe( + tap(() => { + this.setDeliveryMode( + undefined, + this.transactionDetails.deliveryInfo?.type + ); this.updateTransactionInfo({ - totalPrice: `${cart.totalPrice?.value}`, + totalPrice: this.estimateTotalPrice(product?.price?.value), currencyCode: - cart.totalPrice?.currencyIso || + product?.price?.currencyIso || this.initialTransactionInfo.currencyCode, totalPriceStatus: this.initialTransactionInfo.totalPriceStatus, }); }) - ) + ); + }) + ); + }) + ); + } + + handleActiveCartTransaction(): Observable { + this.transactionDetails.context = OpfQuickBuyLocation.CART; + + return forkJoin({ + deliveryInfo: this.opfQuickBuyService.getQuickBuyDeliveryInfo( + this.transactionDetails.context as OpfQuickBuyLocation + ), + merchantName: this.opfQuickBuyService.getMerchantName(), + }).pipe( + switchMap(({ deliveryInfo, merchantName }) => { + this.transactionDetails.deliveryInfo = deliveryInfo; + this.setGooglePaymentRequestConfig(deliveryInfo.type, merchantName); + + return this.setDeliveryMode(undefined, deliveryInfo.type).pipe( + switchMap(() => + this.opfCartHandlerService.getCurrentCart().pipe( + take(1), + tap((cart: Cart) => { + this.transactionDetails.cart = cart; + this.updateTransactionInfo({ + totalPrice: `${cart.totalPrice?.value}`, + currencyCode: + cart.totalPrice?.currencyIso || + this.initialTransactionInfo.currencyCode, + totalPriceStatus: + this.initialTransactionInfo.totalPriceStatus, + }); + }) ) - ); - }) - ); + ) + ); + }) + ); } initTransaction(): void { diff --git a/integration-libs/opf/base/components/opf-quick-buy/opf-quick-buy.service.spec.ts b/integration-libs/opf/base/components/opf-quick-buy/opf-quick-buy.service.spec.ts index b409c1ad3c3..c0c66522ebd 100644 --- a/integration-libs/opf/base/components/opf-quick-buy/opf-quick-buy.service.spec.ts +++ b/integration-libs/opf/base/components/opf-quick-buy/opf-quick-buy.service.spec.ts @@ -14,6 +14,7 @@ import { OpfPaymentProviderType, OpfProviderType, OpfQuickBuyLocation, + defaultMerchantName, } from '../../root/model'; import { OpfQuickBuyService } from './opf-quick-buy.service'; @@ -269,4 +270,22 @@ describe('OpfQuickBuyService', () => { expect(result).toBe(config); }); }); + describe('getMerchantName', () => { + it('should return baseSite name', (done) => { + const mockName = 'Electronics store'; + baseSiteServiceMock.get.and.returnValue(of({ name: mockName })); + service.getMerchantName().subscribe((merchantName) => { + expect(merchantName).toBe(mockName); + done(); + }); + }); + it('should return default MerchantName name when empty', (done) => { + const mockName = undefined; + baseSiteServiceMock.get.and.returnValue(of({ name: mockName })); + service.getMerchantName().subscribe((merchantName) => { + expect(merchantName).toBe(defaultMerchantName); + done(); + }); + }); + }); }); diff --git a/integration-libs/opf/base/components/opf-quick-buy/opf-quick-buy.service.ts b/integration-libs/opf/base/components/opf-quick-buy/opf-quick-buy.service.ts index 764c6897df9..3c9b3ea57fb 100644 --- a/integration-libs/opf/base/components/opf-quick-buy/opf-quick-buy.service.ts +++ b/integration-libs/opf/base/components/opf-quick-buy/opf-quick-buy.service.ts @@ -16,6 +16,7 @@ import { OpfProviderType, OpfQuickBuyDeliveryInfo, OpfQuickBuyLocation, + defaultMerchantName, } from '@spartacus/opf/base/root'; import { Observable, of } from 'rxjs'; import { map, switchMap, take } from 'rxjs/operators'; @@ -116,4 +117,11 @@ export class OpfQuickBuyService { return deliveryTypeObservable.pipe(take(1)); } + + getMerchantName(): Observable { + return this.baseSiteService.get().pipe( + take(1), + map((baseSite) => baseSite?.name ?? defaultMerchantName) + ); + } } diff --git a/integration-libs/opf/base/root/model/opf-quick-buy.model.ts b/integration-libs/opf/base/root/model/opf-quick-buy.model.ts index 55e911ecc92..2f985c4e9ba 100644 --- a/integration-libs/opf/base/root/model/opf-quick-buy.model.ts +++ b/integration-libs/opf/base/root/model/opf-quick-buy.model.ts @@ -167,3 +167,5 @@ export enum ApplePayShippingType { STORE_PICKUP = 'storePickup', SERVICE_PICKUP = 'servicePickup', } + +export const defaultMerchantName: string = 'Store';