@paypal/checkout-components 5.0.362 → 5.0.364

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,6 +36,7 @@ import type { ContentType, Wallet, Experiment } from "../../types";
36
36
  import {
37
37
  BUTTON_LABEL,
38
38
  BUTTON_COLOR,
39
+ BUTTON_COLOR_REBRAND,
39
40
  BUTTON_LAYOUT,
40
41
  BUTTON_SHAPE,
41
42
  BUTTON_SIZE,
@@ -47,6 +48,7 @@ import {
47
48
  MESSAGE_ALIGN,
48
49
  } from "../../constants";
49
50
  import { getFundingConfig, isFundingEligible } from "../../funding";
51
+ import type { StateGetSet } from "../../lib/session";
50
52
 
51
53
  import { BUTTON_SIZE_STYLE } from "./config";
52
54
  import { isBorderRadiusNumber, calculateMessagePosition } from "./util";
@@ -327,6 +329,7 @@ export type ButtonStyle = {|
327
329
  disableMaxWidth?: boolean,
328
330
  disableMaxHeight?: boolean,
329
331
  borderRadius?: number,
332
+ shouldApplyRebrandedStyles: boolean,
330
333
  |};
331
334
 
332
335
  export type ButtonStyleInputs = {|
@@ -510,6 +513,52 @@ type HidePayPalAppSwitchOverlay = {|
510
513
  close: () => void,
511
514
  |};
512
515
 
516
+ type ButtonColor = {|
517
+ shouldApplyRebrandedStyles: boolean,
518
+ color: $Values<typeof BUTTON_COLOR>,
519
+ |};
520
+
521
+ type ColorABTestStorage = {|
522
+ ...ButtonColor,
523
+ sessionID: string,
524
+ |};
525
+
526
+ type GetButtonColorArgs = {|
527
+ experiment: Experiment,
528
+ fundingSource: ?$Values<typeof FUNDING>,
529
+ sessionID: ?string,
530
+ storageState: StateGetSet,
531
+ style: ?ButtonStyle,
532
+ |};
533
+
534
+ type GetColorForABTestArgs = {|
535
+ style: ?ButtonStyle,
536
+ sessionID: ?string,
537
+ storageState: StateGetSet,
538
+ |};
539
+
540
+ type GetColorForFullRedesignArgs = {|
541
+ fundingSource: ?$Values<typeof FUNDING>,
542
+ style: ?ButtonStyle,
543
+ |};
544
+
545
+ type GetDefaultColorForFundingSourceArgs = {|
546
+ fundingSource: ?$Values<typeof FUNDING>,
547
+ style: ?ButtonStyle,
548
+ |};
549
+
550
+ type GetButtonColorExperienceArgs = {|
551
+ experiment: Experiment,
552
+ fundingSource: ?$Values<typeof FUNDING>,
553
+ style: ?ButtonStyle,
554
+ |};
555
+
556
+ type ThrowErrorForInvalidButtonColorArgs = {|
557
+ fundingSource: ?$Values<typeof FUNDING>,
558
+ fundingSourceColors: $ReadOnlyArray<$Values<typeof BUTTON_COLOR>>,
559
+ invalidButtonColor: $Values<typeof BUTTON_COLOR>,
560
+ |};
561
+
513
562
  export type ButtonProps = {|
514
563
  // app switch properties
515
564
  appSwitchWhenAvailable: boolean,
@@ -627,6 +676,7 @@ export type ButtonPropsInputs = {
627
676
  message?: ButtonMessageInputs | void,
628
677
  messageMarkup?: string | void,
629
678
  renderedButtons: $ReadOnlyArray<$Values<typeof FUNDING>>,
679
+ buttonColor: ButtonColor,
630
680
  };
631
681
 
632
682
  export const DEFAULT_STYLE = {
@@ -647,6 +697,266 @@ export const DEFAULT_PROPS = {
647
697
  PLATFORM: PLATFORM.DESKTOP,
648
698
  };
649
699
 
700
+ export function getColorABTestFromStorage(
701
+ storageState: StateGetSet
702
+ ): ?ColorABTestStorage {
703
+ const sessionState = storageState.get("colorABTest");
704
+
705
+ if (sessionState && sessionState.value) {
706
+ return sessionState.value;
707
+ }
708
+
709
+ return null;
710
+ }
711
+
712
+ export function determineRandomButtonColor({
713
+ buttonColorInput,
714
+ }: {|
715
+ buttonColorInput: ?$Values<typeof BUTTON_COLOR>,
716
+ |}): ButtonColor {
717
+ let shouldApplyRebrandedStyles;
718
+ let buttonColor;
719
+
720
+ const randomButtonColor = Math.floor(Math.random() * 3);
721
+
722
+ switch (randomButtonColor) {
723
+ case 0:
724
+ buttonColor = BUTTON_COLOR.REBRAND_BLUE;
725
+ shouldApplyRebrandedStyles = true;
726
+ break;
727
+ case 1:
728
+ buttonColor = BUTTON_COLOR.REBRAND_DARKBLUE;
729
+ shouldApplyRebrandedStyles = true;
730
+ break;
731
+ default:
732
+ // the AB test is only run on PayPal buttons
733
+ // we can set default color for PayPal buttons if buttonColorInput is undefined
734
+ buttonColor = buttonColorInput || BUTTON_COLOR.GOLD;
735
+ shouldApplyRebrandedStyles = false;
736
+ }
737
+
738
+ return {
739
+ shouldApplyRebrandedStyles,
740
+ color: buttonColor,
741
+ };
742
+ }
743
+
744
+ export function hasInvalidScriptOptionsForFullRedesign({
745
+ fundingSource,
746
+ }: {|
747
+ fundingSource?: ?$Values<typeof FUNDING>,
748
+ |}): boolean {
749
+ const validFundingSourcesForRedesign = [FUNDING.PAYPAL];
750
+
751
+ if (validFundingSourcesForRedesign.includes(fundingSource)) {
752
+ return false;
753
+ }
754
+
755
+ return true;
756
+ }
757
+
758
+ export function throwErrorForInvalidButtonColor({
759
+ fundingSource,
760
+ fundingSourceColors,
761
+ invalidButtonColor,
762
+ }: ThrowErrorForInvalidButtonColorArgs) {
763
+ const rebrandedColors = Object.values(BUTTON_COLOR_REBRAND);
764
+ const filteredColors = fundingSourceColors.filter(
765
+ (fundingConfigColor) => !rebrandedColors.includes(fundingConfigColor)
766
+ );
767
+
768
+ // Throw an error if color specified by merchant is not valid for the funding source
769
+ throw new Error(
770
+ `Unexpected style.color for ${
771
+ fundingSource || FUNDING.PAYPAL
772
+ } button: ${invalidButtonColor}, expected ${filteredColors.join(", ")}`
773
+ );
774
+ }
775
+
776
+ export function getDefaultColorForFundingSource({
777
+ fundingSource,
778
+ style,
779
+ }: GetDefaultColorForFundingSourceArgs): $Values<typeof BUTTON_COLOR> {
780
+ // $FlowFixMe this is handled if the fundingSource is undefined
781
+ const fundingSourceConfig = getFundingConfig()[fundingSource];
782
+ const { color: buttonColorInput } = style || {};
783
+
784
+ if (fundingSourceConfig) {
785
+ const { colors } = fundingSourceConfig;
786
+
787
+ if (!buttonColorInput) {
788
+ // return the default color for the funding source
789
+ return colors[0];
790
+ }
791
+ // verify button color is a valid color for the funding source
792
+ if (colors.includes(buttonColorInput)) {
793
+ // $FlowFixMe
794
+ return buttonColorInput;
795
+ } else {
796
+ throwErrorForInvalidButtonColor({
797
+ fundingSource,
798
+ fundingSourceColors: colors,
799
+ invalidButtonColor: buttonColorInput,
800
+ });
801
+ }
802
+ }
803
+
804
+ // gold is the default color for the smart stack
805
+ return buttonColorInput || BUTTON_COLOR.GOLD;
806
+ }
807
+
808
+ export function getColorForABTest({
809
+ storageState,
810
+ sessionID,
811
+ style,
812
+ }: GetColorForABTestArgs): ButtonColor {
813
+ const buttonColorABTestFromStorage = getColorABTestFromStorage(storageState);
814
+
815
+ if (buttonColorABTestFromStorage) {
816
+ const { sessionID: sessionIdFromStorageState, ...buttonColorABTest } =
817
+ buttonColorABTestFromStorage;
818
+
819
+ // If the sessionID matches, return colorABTest from storage
820
+ if (sessionIdFromStorageState && sessionID === sessionIdFromStorageState) {
821
+ return buttonColorABTest;
822
+ }
823
+ }
824
+
825
+ const buttonColorABTest = determineRandomButtonColor({
826
+ buttonColorInput: style?.color,
827
+ });
828
+
829
+ storageState.set("colorABTest", { ...buttonColorABTest, sessionID });
830
+
831
+ return buttonColorABTest;
832
+ }
833
+
834
+ export function getColorForFullRedesign({
835
+ style,
836
+ fundingSource,
837
+ }: GetColorForFullRedesignArgs): ButtonColor {
838
+ const rebrandColorMap = {
839
+ [BUTTON_COLOR.BLUE]: BUTTON_COLOR.REBRAND_BLUE,
840
+ [BUTTON_COLOR.DARKBLUE]: BUTTON_COLOR.REBRAND_DARKBLUE,
841
+ [BUTTON_COLOR.GOLD]: BUTTON_COLOR.REBRAND_BLUE,
842
+
843
+ // not mapped yet since the styles are not setup
844
+ // These should never be hit since legacy experience should be set
845
+ [BUTTON_COLOR.BLACK]: BUTTON_COLOR.BLACK,
846
+ [BUTTON_COLOR.WHITE]: BUTTON_COLOR.WHITE,
847
+ [BUTTON_COLOR.SILVER]: BUTTON_COLOR.SILVER,
848
+ [BUTTON_COLOR.TRANSPARENT]: BUTTON_COLOR.TRANSPARENT,
849
+ [BUTTON_COLOR.DEFAULT]: BUTTON_COLOR.DEFAULT,
850
+
851
+ // normalizeButtonStyle gets called multiple times and
852
+ // it can be called after color is already be mapped to rebranded style
853
+ [BUTTON_COLOR.REBRAND_BLUE]: BUTTON_COLOR.REBRAND_BLUE,
854
+ [BUTTON_COLOR.REBRAND_DARKBLUE]: BUTTON_COLOR.REBRAND_DARKBLUE,
855
+ [BUTTON_COLOR.REBRAND_BLACK]: BUTTON_COLOR.REBRAND_BLACK,
856
+ [BUTTON_COLOR.REBRAND_WHITE]: BUTTON_COLOR.REBRAND_WHITE,
857
+ };
858
+
859
+ // if color is invalid, buttonColor will be undefined
860
+ // $FlowFixMe
861
+ let buttonColor = rebrandColorMap[style?.color];
862
+
863
+ // either style.color is undefined and we need to get the default color for the fundingSource
864
+ // or an invalid color was passed in by the merchant
865
+ if (!buttonColor) {
866
+ // an error will be thrown in getDefaultColorForFundingSource if
867
+ // the style.color is not valid for the funding source
868
+ const defaultButtonColor = getDefaultColorForFundingSource({
869
+ fundingSource,
870
+ style,
871
+ });
872
+
873
+ buttonColor = rebrandColorMap[defaultButtonColor];
874
+ }
875
+
876
+ return {
877
+ color: buttonColor,
878
+ shouldApplyRebrandedStyles: true,
879
+ };
880
+ }
881
+
882
+ export function getButtonColorExperience({
883
+ experiment,
884
+ fundingSource,
885
+ style,
886
+ }: GetButtonColorExperienceArgs): "abTest" | "fullRebrand" | "legacy" {
887
+ const { isPaypalRebrandEnabled, isPaypalRebrandABTestEnabled } =
888
+ experiment || {};
889
+ const rejectRedesign = hasInvalidScriptOptionsForFullRedesign({
890
+ fundingSource,
891
+ });
892
+
893
+ if (!isPaypalRebrandEnabled) {
894
+ return "legacy";
895
+ }
896
+
897
+ if (isPaypalRebrandABTestEnabled) {
898
+ // were only running AB Test on PayPal buttons
899
+ return rejectRedesign ? "legacy" : "abTest";
900
+ }
901
+
902
+ const rebrandColorsNotDevComplete = [
903
+ BUTTON_COLOR.BLACK,
904
+ BUTTON_COLOR.WHITE,
905
+ BUTTON_COLOR.SILVER,
906
+ BUTTON_COLOR.TRANSPARENT,
907
+ BUTTON_COLOR.DEFAULT,
908
+ ];
909
+
910
+ const isRebrandColorNotDevComplete = rebrandColorsNotDevComplete.includes(
911
+ style?.color
912
+ );
913
+
914
+ return rejectRedesign || isRebrandColorNotDevComplete
915
+ ? "legacy"
916
+ : "fullRebrand";
917
+ }
918
+
919
+ export function getButtonColor({
920
+ experiment,
921
+ style,
922
+ sessionID,
923
+ storageState,
924
+ fundingSource,
925
+ }: GetButtonColorArgs): ButtonColor {
926
+ const experience = getButtonColorExperience({
927
+ experiment,
928
+ fundingSource,
929
+ style,
930
+ });
931
+
932
+ switch (experience) {
933
+ case "abTest":
934
+ // calling this function means AB test is eligible
935
+ return getColorForABTest({
936
+ storageState,
937
+ sessionID,
938
+ style,
939
+ });
940
+
941
+ case "fullRebrand":
942
+ // calling this function means full redesign is eligible
943
+ return getColorForFullRedesign({
944
+ fundingSource,
945
+ style,
946
+ });
947
+
948
+ // legacy case
949
+ default:
950
+ return {
951
+ shouldApplyRebrandedStyles: false,
952
+ color: getDefaultColorForFundingSource({
953
+ fundingSource,
954
+ style,
955
+ }),
956
+ };
957
+ }
958
+ }
959
+
650
960
  const getDefaultButtonPropsInput = (): ButtonPropsInputs => {
651
961
  return {};
652
962
  };
@@ -660,8 +970,8 @@ export function normalizeButtonStyle(
660
970
  }
661
971
 
662
972
  props = props || getDefaultButtonPropsInput();
663
- const { fundingSource, experiment } = props;
664
- const { isPaypalRebrandEnabled, defaultBlueButtonColor } = experiment || {};
973
+ const { fundingSource, buttonColor } = props;
974
+ const { color, shouldApplyRebrandedStyles } = buttonColor || {};
665
975
 
666
976
  const FUNDING_CONFIG = getFundingConfig();
667
977
  const fundingConfig =
@@ -675,7 +985,6 @@ export function normalizeButtonStyle(
675
985
  }
676
986
 
677
987
  let {
678
- color,
679
988
  label,
680
989
  layout = fundingSource
681
990
  ? BUTTON_LAYOUT.HORIZONTAL
@@ -690,13 +999,7 @@ export function normalizeButtonStyle(
690
999
  borderRadius,
691
1000
  } = style;
692
1001
 
693
- // This sets the button color so it gets passed to the query string parameter style.color to scnw
694
- if (isPaypalRebrandEnabled) {
695
- const shouldRenderButtonColorControl = defaultBlueButtonColor === "gold";
696
- const experimentButtonColor = defaultBlueButtonColor || "gold";
697
-
698
- color = shouldRenderButtonColorControl ? color : experimentButtonColor;
699
- }
1002
+ const rebrandedColors = Object.values(BUTTON_COLOR_REBRAND);
700
1003
 
701
1004
  // $FlowFixMe
702
1005
  if (tagline === "false") {
@@ -713,10 +1016,15 @@ export function normalizeButtonStyle(
713
1016
  }
714
1017
 
715
1018
  if (color && fundingConfig.colors.indexOf(color) === -1) {
1019
+ // We don't want to include rebranded colors in the error message
1020
+ const filteredColors = fundingConfig.colors.filter(
1021
+ (fundingConfigColor) => !rebrandedColors.includes(fundingConfigColor)
1022
+ );
1023
+
716
1024
  throw new Error(
717
1025
  `Unexpected style.color for ${
718
1026
  fundingSource || FUNDING.PAYPAL
719
- } button: ${color}, expected ${fundingConfig.colors.join(", ")}`
1027
+ } button: ${color}, expected ${filteredColors.join(", ")}`
720
1028
  );
721
1029
  }
722
1030
 
@@ -809,6 +1117,7 @@ export function normalizeButtonStyle(
809
1117
  disableMaxWidth,
810
1118
  disableMaxHeight,
811
1119
  borderRadius,
1120
+ shouldApplyRebrandedStyles,
812
1121
  };
813
1122
  }
814
1123