Skip to content

Commit

Permalink
feat: dynamic messaging (#2349)
Browse files Browse the repository at this point in the history
Co-authored-by: paypal-rosman <102186205+paypal-rosman@users.noreply.github.com>
Co-authored-by: Nathan Schott <nschott@paypal.com>
  • Loading branch information
3 people committed Apr 12, 2024
1 parent d4d5080 commit 4b3d77a
Show file tree
Hide file tree
Showing 20 changed files with 1,151 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .bundlemonrc.json
Expand Up @@ -5,7 +5,7 @@
"path": "size.min.js",
"compression": "gzip",
"maxPercentIncrease": 3.6,
"maxSize": "77kb"
"maxSize": "79kb"
}
],
"reportOutput": [
Expand Down
49 changes: 49 additions & 0 deletions demo/dev/button-with-message.htm
@@ -0,0 +1,49 @@
<!DOCTYPE html>

<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://localhost.paypal.com:9001/sdk.js?client-id=alc_client1&currency=USD"></script>
<script src="https://localhost.paypal.com:9001/sdk-constants.js"></script>
<style>
.paypal-button-container {
border: 2px solid black
}
hr {
margin: 5px 0 5px
}
</style>
</head>

<body>
<div class="buttons-1"></div>
<hr />
<div class="buttons-2"></div>
<hr />
<div class="buttons-3"></div>

<script>
const buttons1 = paypal.Buttons({
style: {
layout: "vertical",
},
});
const buttons2 = paypal.Buttons({
style: {
layout: "horizontal",
tagline: true,
},
});
const buttons3 = paypal.Buttons({
fundingSource: "paypal",
style: {
// layout: "horizontal",
// tagline: true,
},
});

buttons1.render(".buttons-1");
buttons2.render(".buttons-2");
buttons3.render(".buttons-3");
</script>
</body>
1 change: 1 addition & 0 deletions globals.js
Expand Up @@ -32,6 +32,7 @@ module.exports = {
__CARD_FIELD__: "/smart/card-field",
__WALLET__: "/smart/wallet",
__PAYMENT_FIELDS__: "/altpayfields",
__MESSAGE_MODAL__: "https://www.paypalobjects.com/upstream/bizcomponents/js/modal.js",
},
},
};
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -114,7 +114,7 @@
"@paypal/connect-loader-component": "1.1.1",
"@paypal/funding-components": "^1.0.31",
"@paypal/sdk-client": "^4.0.184",
"@paypal/sdk-constants": "^1.0.141",
"@paypal/sdk-constants": "^1.0.146",
"@paypal/sdk-logos": "^2.2.6"
},
"lint-staged": {
Expand Down
21 changes: 21 additions & 0 deletions src/constants/button.js
Expand Up @@ -63,3 +63,24 @@ export const MENU_PLACEMENT = {
ABOVE: ("above": "above"),
BELOW: ("below": "below"),
};

export const MESSAGE_OFFER = {
PAY_LATER_LONG_TERM: ("pay_later_long_term": "pay_later_long_term"),
PAY_LATER_SHORT_TERM: ("pay_later_short_term": "pay_later_short_term"),
};

export const MESSAGE_COLOR = {
BLACK: ("black": "black"),
WHITE: ("white": "white"),
};

export const MESSAGE_POSITION = {
TOP: ("top": "top"),
BOTTOM: ("bottom": "bottom"),
};

export const MESSAGE_ALIGN = {
CENTER: ("center": "center"),
LEFT: ("left": "left"),
RIGHT: ("right": "right"),
};
5 changes: 5 additions & 0 deletions src/constants/class.js
Expand Up @@ -42,4 +42,9 @@ export const CLASS = {
HIDDEN: ("hidden": "hidden"),

IMMEDIATE: ("immediate": "immediate"),

BUTTON_MESSAGE: ("paypal-button-message": "paypal-button-message"),

BUTTON_MESSAGE_RESERVE:
("paypal-button-message-reserved": "paypal-button-message-reserved"),
};
1 change: 1 addition & 0 deletions src/declarations.js
Expand Up @@ -18,6 +18,7 @@ declare var __PAYPAL_CHECKOUT__: {|
__VENMO__: string,
__WALLET__: string,
__PAYMENT_FIELDS__: string,
__MESSAGE_MODAL__: string,
|},
|};

Expand Down
26 changes: 22 additions & 4 deletions src/ui/buttons/buttons.jsx
Expand Up @@ -15,6 +15,7 @@ import {
BUTTON_NUMBER,
BUTTON_LAYOUT,
BUTTON_FLOW,
MESSAGE_POSITION,
} from "../../constants";
import {
determineEligibleFunding,
Expand All @@ -34,6 +35,8 @@ import { Button } from "./button";
import { TagLine } from "./tagline";
import { Script } from "./script";
import { PoweredByPayPal } from "./poweredBy";
import { Message } from "./message";
import { calculateShowPoweredBy } from "./util";

type GetWalletInstrumentOptions = {|
wallet: ?Wallet,
Expand Down Expand Up @@ -177,6 +180,8 @@ export function Buttons(props: ButtonsProps): ElementNode {
supportedNativeBrowser,
showPayLabel,
displayOnly,
message,
messageMarkup,
} = normalizeButtonProps(props);
const { layout, shape, tagline } = style;

Expand Down Expand Up @@ -237,6 +242,14 @@ export function Buttons(props: ButtonsProps): ElementNode {
return i;
};

const showTagline =
tagline &&
layout === BUTTON_LAYOUT.HORIZONTAL &&
!fundingSource &&
!message;

const showPoweredBy = calculateShowPoweredBy(layout, fundingSources);

return (
<div
class={[
Expand All @@ -256,6 +269,10 @@ export function Buttons(props: ButtonsProps): ElementNode {
fundingEligibility={fundingEligibility}
/>

{message && message.position === MESSAGE_POSITION.TOP ? (
<Message markup={messageMarkup} position={message.position} />
) : null}

{fundingSources.map((source, i) => (
<Button
content={content}
Expand Down Expand Up @@ -285,7 +302,7 @@ export function Buttons(props: ButtonsProps): ElementNode {
/>
))}

{tagline && layout === BUTTON_LAYOUT.HORIZONTAL && !fundingSource ? (
{showTagline ? (
<TagLine
fundingSource={fundingSources[0]}
style={style}
Expand All @@ -307,9 +324,10 @@ export function Buttons(props: ButtonsProps): ElementNode {
/>
) : null}

{layout === BUTTON_LAYOUT.VERTICAL &&
fundingSources.indexOf(FUNDING.CARD) !== -1 ? (
<PoweredByPayPal locale={locale} nonce={nonce} />
{showPoweredBy ? <PoweredByPayPal locale={locale} nonce={nonce} /> : null}

{message && message.position === MESSAGE_POSITION.BOTTOM ? (
<Message markup={messageMarkup} position={message.position} />
) : null}

<Script nonce={nonce} />
Expand Down
31 changes: 31 additions & 0 deletions src/ui/buttons/message.jsx
@@ -0,0 +1,31 @@
/* @flow */
/** @jsx node */

import { node, type ChildType } from "@krakenjs/jsx-pragmatic/src";

import { CLASS } from "../../constants";

const INITIAL_RESERVED_HEIGHT = "36px";

type MessageProps = {|
markup: ?string,
position: string,
|};

export function Message({ markup, position }: MessageProps): ChildType {
const messageClassNames = [
CLASS.BUTTON_MESSAGE,
`${CLASS.BUTTON_MESSAGE}-${position}`,
].join(" ");

if (typeof markup !== "string") {
return (
<div
class={`${messageClassNames} ${CLASS.BUTTON_MESSAGE_RESERVE}`}
style={`height:${INITIAL_RESERVED_HEIGHT}`}
/>
);
}

return <div class={messageClassNames} innerHTML={markup} />;
}
105 changes: 104 additions & 1 deletion src/ui/buttons/props.js
Expand Up @@ -40,11 +40,15 @@ import {
BUTTON_SIZE,
BUTTON_FLOW,
MENU_PLACEMENT,
MESSAGE_OFFER,
MESSAGE_COLOR,
MESSAGE_POSITION,
MESSAGE_ALIGN,
} from "../../constants";
import { getFundingConfig, isFundingEligible } from "../../funding";

import { BUTTON_SIZE_STYLE } from "./config";
import { isBorderRadiusNumber } from "./util";
import { isBorderRadiusNumber, calculateMessagePosition } from "./util";

export type CreateOrderData = {||} | {||};

Expand Down Expand Up @@ -424,6 +428,22 @@ export type ApplePaySessionConfigRequest = (
request: Object
) => ApplePaySessionConfig;

export type ButtonMessage = {|
amount?: number,
offer?: $ReadOnlyArray<$Values<typeof MESSAGE_OFFER>>,
color: $Values<typeof MESSAGE_COLOR>,
position: $Values<typeof MESSAGE_POSITION>,
align: $Values<typeof MESSAGE_ALIGN>,
|};

export type ButtonMessageInputs = {|
amount?: number | void,
offer?: $ReadOnlyArray<$Values<typeof MESSAGE_OFFER>> | void,
color?: $Values<typeof MESSAGE_COLOR> | void,
position?: $Values<typeof MESSAGE_POSITION> | void,
align?: $Values<typeof MESSAGE_ALIGN> | void,
|};

export type RenderButtonProps = {|
style: ButtonStyle,
locale: LocaleType,
Expand Down Expand Up @@ -458,6 +478,8 @@ export type RenderButtonProps = {|
supportedNativeBrowser: boolean,
showPayLabel: boolean,
displayOnly?: $ReadOnlyArray<$Values<typeof DISPLAY_ONLY_VALUES>>,
message?: ButtonMessage,
messageMarkup?: string,
|};

export type PrerenderDetails = {|
Expand Down Expand Up @@ -517,6 +539,8 @@ export type ButtonProps = {|
createVaultSetupToken: CreateVaultSetupToken,
displayOnly?: $ReadOnlyArray<$Values<typeof DISPLAY_ONLY_VALUES>>,
hostedButtonId?: string,
message?: ButtonMessage,
messageMarkup?: string,
|};

// eslint-disable-next-line flowtype/require-exact-type
Expand Down Expand Up @@ -559,6 +583,9 @@ export type ButtonPropsInputs = {
supportedNativeBrowser: boolean,
showPayLabel: boolean,
displayOnly: $ReadOnlyArray<$Values<typeof DISPLAY_ONLY_VALUES>>,
message?: ButtonMessageInputs | void,
messageMarkup?: string | void,
renderedButtons: $ReadOnlyArray<$Values<typeof FUNDING>>,
};

export const DEFAULT_STYLE = {
Expand Down Expand Up @@ -707,6 +734,72 @@ export function normalizeButtonStyle(
};
}

export function normalizeButtonMessage(
message: ButtonMessageInputs,
layout: $Values<typeof BUTTON_LAYOUT>,
fundingSources: $ReadOnlyArray<$Values<typeof FUNDING>>
): ButtonMessage {
const {
amount,
offer,
color = MESSAGE_COLOR.BLACK,
position,
align = MESSAGE_ALIGN.CENTER,
} = message;

if (typeof amount !== "undefined") {
if (typeof amount !== "number") {
throw new TypeError(
`Expected message.amount to be a number, got: ${amount}`
);
}
if (amount < 0) {
throw new Error(
`Expected message.amount to be a positive number, got: ${amount}`
);
}
}

if (typeof offer !== "undefined") {
if (!Array.isArray(offer)) {
throw new TypeError(
`Expected message.offer to be an array of strings, got: ${String(
offer
)}`
);
}
const invalidOffers = offer.filter(
(o) => !values(MESSAGE_OFFER).includes(o)
);
if (invalidOffers.length > 0) {
throw new Error(`Invalid offer(s): ${invalidOffers.join(",")}`);
}
}

if (typeof color !== "undefined" && !values(MESSAGE_COLOR).includes(color)) {
throw new Error(`Invalid color: ${color}`);
}

if (
typeof position !== "undefined" &&
!values(MESSAGE_POSITION).includes(position)
) {
throw new Error(`Invalid position: ${position}`);
}

if (typeof align !== "undefined" && !values(MESSAGE_ALIGN).includes(align)) {
throw new Error(`Invalid align: ${align}`);
}

return {
amount,
offer,
color,
position: calculateMessagePosition(fundingSources, layout, position),
align,
};
}

const COUNTRIES = values(COUNTRY);
const FUNDING_SOURCES = values(FUNDING);
const ENVS = values(ENV);
Expand Down Expand Up @@ -761,6 +854,9 @@ export function normalizeButtonProps(
supportedNativeBrowser = false,
showPayLabel = true,
displayOnly = [],
message,
messageMarkup,
renderedButtons,
} = props;

const { country, lang } = locale;
Expand Down Expand Up @@ -819,6 +915,11 @@ export function normalizeButtonProps(
}

style = normalizeButtonStyle(props, style);
const { layout } = style;

message = message
? normalizeButtonMessage(message, layout, renderedButtons)
: undefined;

return {
clientID,
Expand Down Expand Up @@ -852,5 +953,7 @@ export function normalizeButtonProps(
supportedNativeBrowser,
showPayLabel,
displayOnly,
message,
messageMarkup,
};
}

0 comments on commit 4b3d77a

Please sign in to comment.