-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: new StripePaymentInput component
- Loading branch information
Showing
6 changed files
with
302 additions
and
3 deletions.
There are no files selected for viewing
113 changes: 113 additions & 0 deletions
113
package/src/components/StripePaymentInput/v1/StripePaymentInput.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import React, { Component } from "react"; | ||
import PropTypes from "prop-types"; | ||
import { withComponents } from "@reactioncommerce/components-context"; | ||
import styled from "styled-components"; | ||
import { addTypographyStyles, CustomPropTypes } from "../../../utils"; | ||
|
||
const SecureCaption = styled.div` | ||
${addTypographyStyles("StripePaymentInputCaption", "captionText")} | ||
`; | ||
|
||
const IconLockSpan = styled.span` | ||
display: inline-block; | ||
height: 20px; | ||
width: 20px; | ||
`; | ||
|
||
const Span = styled.span` | ||
vertical-align: super; | ||
`; | ||
|
||
class StripePaymentInput extends Component { | ||
static propTypes = { | ||
/** | ||
* You can provide a `className` prop that will be applied to the outermost DOM element | ||
* rendered by this component. We do not recommend using this for styling purposes, but | ||
* it can be useful as a selector in some situations. | ||
*/ | ||
className: PropTypes.string, | ||
/** | ||
* If you've set up a components context using | ||
* [@reactioncommerce/components-context](https://github.com/reactioncommerce/components-context) | ||
* (recommended), then this prop will come from there automatically. If you have not | ||
* set up a components context or you want to override one of the components in a | ||
* single spot, you can pass in the components prop directly. | ||
*/ | ||
components: PropTypes.shape({ | ||
/** | ||
* Secured lock icon | ||
*/ | ||
iconLock: PropTypes.node, | ||
/** | ||
* Pass either the Reaction StripeForm component or your own component that | ||
* accepts compatible props. | ||
*/ | ||
StripeForm: CustomPropTypes.component.isRequired | ||
}), | ||
/** | ||
* Is the payment input being saved? | ||
*/ | ||
isSaving: PropTypes.bool, | ||
/** | ||
* When this action's input data switches between being | ||
* ready for saving and not ready for saving, this will | ||
* be called with `true` (ready) or `false` | ||
*/ | ||
onReadyForSaveChange: PropTypes.func, | ||
/** | ||
* Called with an object value when this component's `submit` | ||
* method is called. The object may have `data`, `displayName`, | ||
* and `amount` properties. | ||
*/ | ||
onSubmit: PropTypes.func | ||
}; | ||
|
||
static defaultProps = { | ||
onReadyForSaveChange() {}, | ||
onSubmit() {} | ||
}; | ||
|
||
componentDidMount() { | ||
const { onReadyForSaveChange } = this.props; | ||
onReadyForSaveChange(false); | ||
} | ||
|
||
async submit() { | ||
const { onSubmit } = this.props; | ||
const { token } = await this._stripe.createToken(); | ||
|
||
await onSubmit({ | ||
displayName: `${token.card.brand} ending in ${token.card.last4}`, | ||
data: { | ||
stripeTokenId: token.id | ||
} | ||
}); | ||
} | ||
|
||
handleChangeReadyState = (isReady) => { | ||
const { onReadyForSaveChange } = this.props; | ||
|
||
if (isReady !== this.lastIsReady) { | ||
onReadyForSaveChange(isReady); | ||
} | ||
this.lastIsReady = isReady; | ||
} | ||
|
||
render() { | ||
const { className, components: { iconLock, StripeForm } } = this.props; | ||
|
||
return ( | ||
<div className={className}> | ||
<StripeForm | ||
isComplete={this.handleChangeReadyState} | ||
stripeRef={(stripe) => { this._stripe = stripe; }} | ||
/> | ||
<SecureCaption> | ||
<IconLockSpan>{iconLock}</IconLockSpan> <Span>Your Information is private and secure.</Span> | ||
</SecureCaption> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default withComponents(StripePaymentInput); |
42 changes: 42 additions & 0 deletions
42
package/src/components/StripePaymentInput/v1/StripePaymentInput.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
### Overview | ||
|
||
The `StripePaymentInput` component is intended to be used as the `InputComponent` for the `stripe_card` payment method in a Reaction client UI. Provide it in the `paymentMethods` array passed to the [PaymentsCheckoutAction](./#!/PaymentsCheckoutAction) component. | ||
|
||
### Usage | ||
|
||
```jsx | ||
class Example extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
|
||
this.state = { isReady: false }; | ||
} | ||
|
||
render() { | ||
return ( | ||
<div> | ||
<StripePaymentInput | ||
ref={(ref) => { this.form = ref; }} | ||
onChange={(...args) => { console.log("onChange", ...args); }} | ||
onReadyForSaveChange={(isReady) => { | ||
console.log("onReadyForSaveChange", isReady); | ||
this.setState({ isReady }); | ||
}} | ||
onSubmit={(doc) => { console.log("onSubmit", doc); }} | ||
/> | ||
<Button isDisabled={!this.state.isReady} onClick={() => { this.form.submit(); }}>Submit</Button> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
<Example /> | ||
``` | ||
|
||
### Theme | ||
|
||
Assume that any theme prop that does not begin with "rui" is within `rui_components`. See [Theming Components](./#!/Theming%20Components). | ||
|
||
#### Typography | ||
|
||
- The "Your Information is private and secure" text uses `captionText` style with `rui_components.StripePaymentInputCaption` override |
95 changes: 95 additions & 0 deletions
95
package/src/components/StripePaymentInput/v1/StripePaymentInput.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import React from "react"; | ||
import renderer from "react-test-renderer"; | ||
import { mount } from "enzyme"; | ||
import { StripeProvider } from "react-stripe-elements"; | ||
import { ComponentsProvider } from "@reactioncommerce/components-context"; | ||
import mockComponents from "../../../tests/mockComponents"; | ||
import realComponents from "../../../tests/realComponents"; | ||
import StripePaymentInput from "./StripePaymentInput"; | ||
|
||
// Mock the Stripe instance | ||
const elementMock = { | ||
mount: jest.fn(), | ||
destroy: jest.fn(), | ||
on: jest.fn(), | ||
update: jest.fn() | ||
}; | ||
const elementsMock = { | ||
create: jest.fn().mockReturnValue(elementMock) | ||
}; | ||
const stripeMock = { | ||
elements: jest.fn().mockReturnValue(elementsMock), | ||
createToken: jest.fn(), | ||
createSource: jest.fn() | ||
}; | ||
|
||
test("basic snapshot", () => { | ||
const component = renderer.create(<StripePaymentInput components={mockComponents} />); | ||
|
||
const tree = component.toJSON(); | ||
expect(tree).toMatchSnapshot(); | ||
}); | ||
|
||
test("calls onReadyForSaveChange on mount and change", () => { | ||
const onReadyForSaveChange = jest.fn(); | ||
|
||
let formEl; | ||
mount(( | ||
<StripeProvider stripe={stripeMock}> | ||
<ComponentsProvider value={realComponents}> | ||
<StripePaymentInput | ||
ref={(ref) => { formEl = ref; }} | ||
onReadyForSaveChange={onReadyForSaveChange} | ||
/> | ||
</ComponentsProvider> | ||
</StripeProvider> | ||
)); | ||
|
||
expect(onReadyForSaveChange).toHaveBeenCalledTimes(1); | ||
expect(onReadyForSaveChange).toHaveBeenLastCalledWith(false); | ||
|
||
onReadyForSaveChange.mockClear(); | ||
|
||
formEl.handleChangeReadyState(true); | ||
|
||
expect(onReadyForSaveChange).toHaveBeenCalledTimes(1); | ||
expect(onReadyForSaveChange).toHaveBeenLastCalledWith(true); | ||
}); | ||
|
||
test("calls onSubmit when submit method is called", (done) => { | ||
const onSubmit = jest.fn(); | ||
|
||
stripeMock.createToken.mockReturnValueOnce(Promise.resolve({ | ||
token: { | ||
card: { | ||
brand: "Visa", | ||
last4: "1234" | ||
}, | ||
id: "abc123" | ||
} | ||
})); | ||
|
||
let formEl; | ||
mount(( | ||
<StripeProvider stripe={stripeMock}> | ||
<ComponentsProvider value={realComponents}> | ||
<StripePaymentInput ref={(ref) => { formEl = ref; }} onSubmit={onSubmit} /> | ||
</ComponentsProvider> | ||
</StripeProvider> | ||
)); | ||
|
||
expect(onSubmit).not.toHaveBeenCalled(); | ||
|
||
formEl.submit(); | ||
|
||
setTimeout(() => { | ||
expect(onSubmit).toHaveBeenCalledTimes(1); | ||
expect(onSubmit).toHaveBeenLastCalledWith({ | ||
data: { | ||
stripeTokenId: "abc123" | ||
}, | ||
displayName: "Visa ending in 1234" | ||
}); | ||
done(); | ||
}, 0); | ||
}); |
47 changes: 47 additions & 0 deletions
47
package/src/components/StripePaymentInput/v1/__snapshots__/StripePaymentInput.test.js.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`basic snapshot 1`] = ` | ||
.c0 { | ||
-webkit-font-smoothing: antialiased; | ||
color: #b3b3b3; | ||
font-family: 'Source Sans Pro','Helvetica Neue',Helvetica,sans-serif; | ||
font-size: 14px; | ||
font-style: normal; | ||
font-stretch: normal; | ||
font-weight: 400; | ||
-webkit-letter-spacing: .02em; | ||
-moz-letter-spacing: .02em; | ||
-ms-letter-spacing: .02em; | ||
letter-spacing: .02em; | ||
line-height: 1.25; | ||
} | ||
.c1 { | ||
display: inline-block; | ||
height: 20px; | ||
width: 20px; | ||
} | ||
.c2 { | ||
vertical-align: super; | ||
} | ||
<div | ||
className={undefined} | ||
> | ||
StripeForm({}) | ||
<div | ||
className="c0" | ||
> | ||
<span | ||
className="c1" | ||
/> | ||
<span | ||
className="c2" | ||
> | ||
Your Information is private and secure. | ||
</span> | ||
</div> | ||
</div> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from "./StripePaymentInput"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters