Skip to content

Commit bab513b

Browse files
Add component for payButton element (#344)
* bump stripe-js version * Add PBE component * Custom ready payload * Copy/paste + declare PayButtonElement
1 parent c11af14 commit bab513b

File tree

6 files changed

+244
-7
lines changed

6 files changed

+244
-7
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"@babel/preset-env": "^7.7.1",
6363
"@babel/preset-react": "^7.7.0",
6464
"@storybook/react": "^6.5.0-beta.8",
65-
"@stripe/stripe-js": "^1.42.0",
65+
"@stripe/stripe-js": "^1.44.1",
6666
"@testing-library/jest-dom": "^5.16.4",
6767
"@testing-library/react": "^13.1.1",
6868
"@testing-library/react-hooks": "^8.0.0",
@@ -106,7 +106,7 @@
106106
"@types/react": "18.0.5"
107107
},
108108
"peerDependencies": {
109-
"@stripe/stripe-js": "^1.42.1",
109+
"@stripe/stripe-js": "^1.44.1",
110110
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
111111
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
112112
}

src/components/createElementComponent.test.tsx

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
PaymentElementComponent,
1010
PaymentRequestButtonElementComponent,
1111
CartElementComponent,
12+
PayButtonElementComponent,
1213
} from '../types';
1314

1415
const {Elements} = ElementsModule;
@@ -29,6 +30,10 @@ describe('createElementComponent', () => {
2930
let simulateNetworksChange: any;
3031
let simulateCheckout: any;
3132
let simulateLineItemClick: any;
33+
let simulateConfirm: any;
34+
let simulateCancel: any;
35+
let simulateShippingAddressChange: any;
36+
let simulateShippingRateChange: any;
3237

3338
beforeEach(() => {
3439
mockStripe = mocks.mockStripe();
@@ -72,6 +77,18 @@ describe('createElementComponent', () => {
7277
case 'lineitemclick':
7378
simulateLineItemClick = fn;
7479
break;
80+
case 'confirm':
81+
simulateConfirm = fn;
82+
break;
83+
case 'cancel':
84+
simulateCancel = fn;
85+
break;
86+
case 'shippingaddresschange':
87+
simulateShippingAddressChange = fn;
88+
break;
89+
case 'shippingratechange':
90+
simulateShippingRateChange = fn;
91+
break;
7592
default:
7693
throw new Error('TestSetupError: Unexpected event registration.');
7794
}
@@ -159,6 +176,11 @@ describe('createElementComponent', () => {
159176
false
160177
);
161178

179+
const PayButtonElement: PayButtonElementComponent = createElementComponent(
180+
'payButton',
181+
false
182+
);
183+
162184
it('Can remove and add CardElement at the same time', () => {
163185
let cardMounted = false;
164186
mockElement.mount.mockImplementation(() => {
@@ -534,6 +556,98 @@ describe('createElementComponent', () => {
534556
expect(mockHandler).not.toHaveBeenCalled();
535557
});
536558

559+
it('propagates the Element`s confirm event to the current onConfirm prop', () => {
560+
const mockHandler = jest.fn();
561+
const mockHandler2 = jest.fn();
562+
const {rerender} = render(
563+
<Elements stripe={mockStripe}>
564+
<PayButtonElement onConfirm={mockHandler} />
565+
</Elements>
566+
);
567+
rerender(
568+
<Elements stripe={mockStripe}>
569+
<PayButtonElement onConfirm={mockHandler2} />
570+
</Elements>
571+
);
572+
573+
const confirmEventMock = Symbol('confirm');
574+
simulateConfirm(confirmEventMock);
575+
expect(mockHandler2).toHaveBeenCalledWith(confirmEventMock);
576+
expect(mockHandler).not.toHaveBeenCalled();
577+
});
578+
579+
it('propagates the Element`s cancel event to the current onCancel prop', () => {
580+
const mockHandler = jest.fn();
581+
const mockHandler2 = jest.fn();
582+
const {rerender} = render(
583+
<Elements stripe={mockStripe}>
584+
<PayButtonElement onConfirm={() => {}} onCancel={mockHandler} />
585+
</Elements>
586+
);
587+
rerender(
588+
<Elements stripe={mockStripe}>
589+
<PayButtonElement onConfirm={() => {}} onCancel={mockHandler2} />
590+
</Elements>
591+
);
592+
593+
const cancelEventMock = Symbol('cancel');
594+
simulateCancel(cancelEventMock);
595+
expect(mockHandler2).toHaveBeenCalledWith(cancelEventMock);
596+
expect(mockHandler).not.toHaveBeenCalled();
597+
});
598+
599+
it('propagates the Element`s shippingaddresschange event to the current onShippingAddressChange prop', () => {
600+
const mockHandler = jest.fn();
601+
const mockHandler2 = jest.fn();
602+
const {rerender} = render(
603+
<Elements stripe={mockStripe}>
604+
<PayButtonElement
605+
onConfirm={() => {}}
606+
onShippingAddressChange={mockHandler}
607+
/>
608+
</Elements>
609+
);
610+
rerender(
611+
<Elements stripe={mockStripe}>
612+
<PayButtonElement
613+
onConfirm={() => {}}
614+
onShippingAddressChange={mockHandler2}
615+
/>
616+
</Elements>
617+
);
618+
619+
const shippingAddressChangeEventMock = Symbol('shippingaddresschange');
620+
simulateShippingAddressChange(shippingAddressChangeEventMock);
621+
expect(mockHandler2).toHaveBeenCalledWith(shippingAddressChangeEventMock);
622+
expect(mockHandler).not.toHaveBeenCalled();
623+
});
624+
625+
it('propagates the Element`s shippingratechange event to the current onShippingRateChange prop', () => {
626+
const mockHandler = jest.fn();
627+
const mockHandler2 = jest.fn();
628+
const {rerender} = render(
629+
<Elements stripe={mockStripe}>
630+
<PayButtonElement
631+
onConfirm={() => {}}
632+
onShippingRateChange={mockHandler}
633+
/>
634+
</Elements>
635+
);
636+
rerender(
637+
<Elements stripe={mockStripe}>
638+
<PayButtonElement
639+
onConfirm={() => {}}
640+
onShippingRateChange={mockHandler2}
641+
/>
642+
</Elements>
643+
);
644+
645+
const shippingRateChangeEventMock = Symbol('shippingratechange');
646+
simulateShippingRateChange(shippingRateChangeEventMock);
647+
expect(mockHandler2).toHaveBeenCalledWith(shippingRateChangeEventMock);
648+
expect(mockHandler).not.toHaveBeenCalled();
649+
});
650+
537651
it('updates the Element when options change', () => {
538652
const {rerender} = render(
539653
<Elements stripe={mockStripe}>

src/components/createElementComponent.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ interface PrivateElementProps {
3434
onNetworksChange?: UnknownCallback;
3535
onCheckout?: UnknownCallback;
3636
onLineItemClick?: UnknownCallback;
37+
onConfirm?: UnknownCallback;
38+
onCancel?: UnknownCallback;
39+
onShippingAddressChange?: UnknownCallback;
40+
onShippingRateChange?: UnknownCallback;
3741
options?: UnknownOptions;
3842
}
3943

@@ -62,6 +66,10 @@ const createElementComponent = (
6266
onNetworksChange = noop,
6367
onCheckout = noop,
6468
onLineItemClick = noop,
69+
onConfirm = noop,
70+
onCancel = noop,
71+
onShippingAddressChange = noop,
72+
onShippingRateChange = noop,
6573
}) => {
6674
const {elements} = useElementsContextWithUseCase(`mounts <${displayName}>`);
6775
const elementRef = React.useRef<stripeJs.StripeElement | null>(null);
@@ -82,6 +90,12 @@ const createElementComponent = (
8290
const callOnNetworksChange = useCallbackReference(onNetworksChange);
8391
const callOnCheckout = useCallbackReference(onCheckout);
8492
const callOnLineItemClick = useCallbackReference(onLineItemClick);
93+
const callOnConfirm = useCallbackReference(onConfirm);
94+
const callOnCancel = useCallbackReference(onCancel);
95+
const callOnShippingAddressChange = useCallbackReference(
96+
onShippingAddressChange
97+
);
98+
const callOnShippingRateChange = useCallbackReference(onShippingRateChange);
8599

86100
React.useLayoutEffect(() => {
87101
if (elementRef.current == null && elements && domNode.current != null) {
@@ -104,6 +118,8 @@ const createElementComponent = (
104118
}
105119
// the cart ready event returns a CartStatePayload instead of the CartElement
106120
callOnReady(event);
121+
} else if (type === 'payButton') {
122+
callOnReady(event);
107123
} else {
108124
callOnReady(element);
109125
}
@@ -174,6 +190,29 @@ const createElementComponent = (
174190
// just as they could listen for the `lineitemclick` event on any Element,
175191
// but only certain Elements will trigger the event.
176192
(element as any).on('lineitemclick', callOnLineItemClick);
193+
194+
// Users can pass an onConfirm prop on any Element component
195+
// just as they could listen for the `confirm` event on any Element,
196+
// but only certain Elements will trigger the event.
197+
(element as any).on('confirm', callOnConfirm);
198+
199+
// Users can pass an onCancel prop on any Element component
200+
// just as they could listen for the `cancel` event on any Element,
201+
// but only certain Elements will trigger the event.
202+
(element as any).on('cancel', callOnCancel);
203+
204+
// Users can pass an onShippingAddressChange prop on any Element component
205+
// just as they could listen for the `shippingaddresschange` event on any Element,
206+
// but only certain Elements will trigger the event.
207+
(element as any).on(
208+
'shippingaddresschange',
209+
callOnShippingAddressChange
210+
);
211+
212+
// Users can pass an onShippingRateChange prop on any Element component
213+
// just as they could listen for the `shippingratechange` event on any Element,
214+
// but only certain Elements will trigger the event.
215+
(element as any).on('shippingratechange', callOnShippingRateChange);
177216
}
178217
});
179218

@@ -229,6 +268,10 @@ const createElementComponent = (
229268
onNetworksChange: PropTypes.func,
230269
onCheckout: PropTypes.func,
231270
onLineItemClick: PropTypes.func,
271+
onConfirm: PropTypes.func,
272+
onCancel: PropTypes.func,
273+
onShippingAddressChange: PropTypes.func,
274+
onShippingRateChange: PropTypes.func,
232275
options: PropTypes.object as any,
233276
};
234277

src/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
AffirmMessageElementComponent,
2020
AfterpayClearpayMessageElementComponent,
2121
PaymentMethodMessagingElementComponent,
22+
PayButtonElementComponent,
2223
} from './types';
2324

2425
export * from './types';
@@ -122,6 +123,17 @@ export const PaymentElement: PaymentElementComponent = createElementComponent(
122123
isServer
123124
);
124125

126+
/**
127+
* Requires beta access:
128+
* Contact [Stripe support](https://support.stripe.com/) for more information.
129+
*
130+
* @docs https://stripe.com/docs/stripe-js/react#element-components
131+
*/
132+
export const PayButtonElement: PayButtonElementComponent = createElementComponent(
133+
'payButton',
134+
isServer
135+
);
136+
125137
/**
126138
* @docs https://stripe.com/docs/stripe-js/react#element-components
127139
*/

src/types/index.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,62 @@ export interface PaymentElementProps extends ElementProps {
431431

432432
export type PaymentElementComponent = FunctionComponent<PaymentElementProps>;
433433

434+
export interface PayButtonElementProps extends ElementProps {
435+
/**
436+
* An object containing Element configuration options.
437+
*/
438+
options?: stripeJs.StripePayButtonElementOptions;
439+
440+
/**
441+
* Triggered when the Element is fully rendered and can accept imperative `element.focus()` calls.
442+
* The list of payment methods that could possibly show in the element, or undefined if no payment methods can show.
443+
*/
444+
onReady?: (event: stripeJs.StripePayButtonElementReadyEvent) => any;
445+
446+
/**
447+
* Triggered when the escape key is pressed within the Element.
448+
*/
449+
onEscape?: () => any;
450+
451+
/**
452+
* Triggered when the Element fails to load.
453+
*/
454+
onLoadError?: (event: {elementType: 'payButton'; error: StripeError}) => any;
455+
456+
/**
457+
* Triggered when a button on the Element is clicked.
458+
*/
459+
onClick?: (event: stripeJs.StripePayButtonElementClickEvent) => any;
460+
461+
/**
462+
* Triggered when a buyer authorizes a payment within a supported payment method.
463+
*/
464+
onConfirm: (event: stripeJs.StripePayButtonElementConfirmEvent) => any;
465+
466+
/**
467+
* Triggered when a payment interface is dismissed (e.g., a buyer closes the payment interface)
468+
*/
469+
onCancel?: (event: {elementType: 'payButton'}) => any;
470+
471+
/**
472+
* Triggered when a buyer selects a different shipping address.
473+
*/
474+
onShippingAddressChange?: (
475+
event: stripeJs.StripePayButtonElementShippingAddressChangeEvent
476+
) => any;
477+
478+
/**
479+
* Triggered when a buyer selects a different shipping rate.
480+
*/
481+
onShippingRateChange?: (
482+
event: stripeJs.StripePayButtonElementShippingRateChangeEvent
483+
) => any;
484+
}
485+
486+
export type PayButtonElementComponent = FunctionComponent<
487+
PayButtonElementProps
488+
>;
489+
434490
export interface PaymentRequestButtonElementProps extends ElementProps {
435491
/**
436492
* An object containing [Element configuration options](https://stripe.com/docs/js/elements_object/create_element?type=paymentRequestButton).
@@ -707,17 +763,29 @@ declare module '@stripe/stripe-js' {
707763
): stripeJs.StripeEpsBankElement | null;
708764

709765
/**
710-
* Returns the underlying [element instance](https://stripe.com/docs/js/elements_object/create_element?type=card) for the `LinkAuthenticationElement` component in the current [Elements](https://stripe.com/docs/stripe-js/react#elements-provider) provider tree.
766+
* Returns the underlying [element instance](https://stripe.com/docs/js/elements_object/create_link_authentication_element) for the `LinkAuthenticationElement` component in the current [Elements](https://stripe.com/docs/stripe-js/react#elements-provider) provider tree.
711767
* Returns `null` if no `LinkAuthenticationElement` is rendered in the current `Elements` provider tree.
712768
*/
713769
getElement(
714770
component: LinkAuthenticationElementComponent
715771
): stripeJs.StripeLinkAuthenticationElement | null;
716772

773+
/**
774+
* Returns the underlying [element instance](https://stripe.com/docs/js/elements_object/create_payment_element) for the `PaymentElement` component in the current [Elements](https://stripe.com/docs/stripe-js/react#elements-provider) provider tree.
775+
* Returns `null` if no `PaymentElement` is rendered in the current `Elements` provider tree.
776+
*/
717777
getElement(
718778
component: PaymentElementComponent
719779
): stripeJs.StripeElement | null;
720780

781+
/**
782+
* Returns the underlying [element instance](https://stripe.com/docs/js/elements_object/create_pay_button_element) for the `PayButtonElement` component in the current [Elements](https://stripe.com/docs/stripe-js/react#elements-provider) provider tree.
783+
* Returns `null` if no `PayButtonElement` is rendered in the current `Elements` provider tree.
784+
*/
785+
getElement(
786+
component: PayButtonElementComponent
787+
): stripeJs.StripeElement | null;
788+
721789
/**
722790
* Returns the underlying [element instance](https://stripe.com/docs/js/elements_object/create_element?type=card) for the `PaymentRequestButtonElement` component in the current [Elements](https://stripe.com/docs/stripe-js/react#elements-provider) provider tree.
723791
* Returns `null` if no `PaymentRequestButtonElement` is rendered in the current `Elements` provider tree.

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2130,10 +2130,10 @@
21302130
regenerator-runtime "^0.13.7"
21312131
resolve-from "^5.0.0"
21322132

2133-
"@stripe/stripe-js@^1.42.0":
2134-
version "1.42.0"
2135-
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.42.0.tgz#6074d0ac184bd70c9e5b5bc00e126719277e0128"
2136-
integrity sha512-ZaQpZo5PRv/mN6157OywMW4fc7FIS+eI/tS12I1gU9MdvnL8fzFktuAU/g9FyUOL22E4t9hF1tsfLQAZcQDokQ==
2133+
"@stripe/stripe-js@^1.44.1":
2134+
version "1.44.1"
2135+
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.44.1.tgz#376fdbed2b394c84deaa2041b8029b97e7eab3a7"
2136+
integrity sha512-DKj3U6tS+sCNsSXsoZbOl5gDrAVD3cAZ9QCiVSykLC3iJo085kkmw/3BAACRH54Bq2bN34yySuH6G1SLh2xHXA==
21372137

21382138
"@testing-library/dom@^8.5.0":
21392139
version "8.13.0"

0 commit comments

Comments
 (0)