A simple Ember wrapper for Stripe Elements.
If you can spare some time in helping maintain this addon, please let us know in the Discord adopted-ember-addons
channel or open an issue.
<script src="https://js.stripe.com/v3/"></script>
into your application's <body>
Stripe
with your publishable keystripev3
service into your controllers so you can use the functions usually available on the stripe
object (see https://stripe.com/docs/stripe-js/reference#the-stripe-object):
stripe.elements()
stripe.confirmCardPayment()
stripe.createToken()
stripe.createSource()
stripe.createPaymentMethod()
stripe.retrieveSource()
stripe.paymentRequest()
stripe.redirectToCheckout()
stripe.retrievePaymentIntent()
stripe.handleCardPayment()
stripe.handleCardAction()
stripe.confirmPaymentIntent()
stripe.handleCardSetup()
stripe.confirmCardSetup()
stripe.retrieveSetupIntent()
stripe.confirmSetupIntent()
<StripeCard/>
(demoed in the gif above)ember install @adopted-ember-addons/ember-stripe-elements
Version 2.0.0
test helpers need to be imported from '@adopted-ember-addons/ember-stripe-elements/test-support'
You must set your publishable key in config/environment.js
.
Also, stripe options contains optional values that you could configure if you want to.
ENV.stripe = {
publishableKey: 'pk_thisIsATestKey',
stripeOptions: {
stripeAccount: 'acct_test_account',
locale: 'en',
},
};
You can configure the Stripe API to be mocked instead of loaded from https://js.stripe.com/v3/
. This is useful for testing.
ENV.stripe = {
mock: true,
};
When enabled, a mock Stripe object will be assigned to window.Stripe
when your app is initialized.
When using the Stripe mock in tests you will likely need to override the mock's methods according to the needs of your test like so:
this.owner.lookup('service:stripev3').createToken = () => ({ token: { id: 'token' } });
When a {{stripe-element}} is instantiated and in the DOM, the underlying stripeElement
is available via the stripev3
service. Calling stripeService.getActiveElements()
will return an array of those native stripeElements.
This is primarily useful in testing. Stripe renders an iframe which is mostly inaccessible in a test environment, making simulating user input impossible.
You can fill this gap by making the stripeElement
emit compatible events, which is a reasonable simulation of the results when in a test context.
This add-on includes some handy utilities for this purpose that can be imported from stripe-mock.
import StripeMock, { stripeEventUtils } from '@adopted-ember-addons/ember-stripe-elements/test-support';
hooks.beforeEach(() => window.Stripe = StripeMock);
stripeEventUtils.triggerReady(stripeElement);
stripeEventUtils.triggerBlur(stripeElement);
stripeEventUtils.triggerFocus(stripeElement);
stripeEventUtils.triggerIncomplete(stripeElement);
stripeEventUtils.triggerComplete(stripeElement);
stripeEventUtils.triggerError(stripeElement, additionalArgs);
stripeEventUtils.triggerChange(stripeElement, additionalArgs);
Both triggerError
and triggerChange
accept a second argument that can be used to override the default event attributes provided by this addon.
Note: these will not actually change the content of the Stripe UI, they simply force the stripeElement to emit events that are being listened for. WARNING: These utilities rely on undocumented methods, so this may break in the future. This is only intended for use in a test environment. The events are also not exhaustive, but cover the core user flows.
import StripeMock, { stripeEventUtils } from '@adopted-ember-addons/ember-stripe-elements/test-support';
module('...', function (hooks) {
hooks.beforeEach(() => window.Stripe = StripeMock);
test('user enters valid data', function (assert) {
//...some code rendering a {{stripe element}}
const [stripeElement] = stripeService.getActiveElements();
stripeEventUtils.triggerComplete(stripeElement);
...
});
});
You can configure Stripe.js to lazy load when you need it.
ENV.stripe = {
lazyLoad: true,
};
When enabled, Stripe.js will not be loaded until you call the load()
function on the service. It's best to call this function in a route's beforeModel
hook.
// subscription page route
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default class SubscriptionRoute extends Route {
@service('stripev3') stripe;
beforeModel() {
return this.stripe.load();
}
}
Note that the load
function returns a Promise
. By returning this promise you ensure that Stripe is fully loaded before the route procedes to the next model
hook.
You can also pass publishableKey
and optional stripeOptions
to the load
function.
this.stripe.load('pk_thisIsATestKey', {
locale: 'en',
stripeAccount: 'acct_24BFMpJ1svR5A89k',
});
Every component will:
options
accepted by Stripe Elementsupdate
on the Stripe element
if the options
are updatedStripeElement
in a <div role="mount-point">
on didInsertElement
willDestroyElement
stripev3
service.ember-stripe-element
<EmberStripeCard/>
has the class .ember-stripe-card
autofocus=true
passed directly in the component, e.g. <StripeCard @autofocus={{true}}/>
Every component extends from a
StripeElement
base component which is not exposed to your application.
The components bubble up all of the JavaScript events that can be handled by the Stripe Element in element.on()
from the Ember component using the following actions:
onReady
onBlur
onChange
(also sets/unsets the stripeError
property on the component, which can be yielded with the block)onFocus
onComplete
onError
You could handle these actions yourself, for example:
<StripeCard @onBlur={{this.onBlur}} />
This addon gives you components that match the different Element types:
Stripe recommends using the their card
element -
The <StripeCard />
component provides this input.
Additionally Stripe provides the following elements, which you can use to build your own form to collect card details:
cardNumber
: the card number.cardExpiry
: the card's expiration date.cardCvc
: the card's CVC number.postalCode
: the ZIP/postal code.These are provided via our <StripeElements />
contextual component, which yields sub-components for each element type:
<StripeElements as |elements|>
<elements.cardNumber />
<elements.cardExpiry />
<elements.cardCvc />
<elements.postalCode />
</StripeElements>
The
<StripeElements />
component is a tagless component, so does not have any classes etc on it.
The <StripeElements />
contextual component ensures all the individual elements are created from
the same Stripe Elements object.
If you want to pass options to the Stripe Elements object, pass them to the <StripeElements />
contextual component. For example, when using the single-line card
element:
<StripeElements @options={{this.elementOptions}} as |elements|>
<elements.card @options={{this.cardOptions}} />
</StripeElements>
Or when creating your own form:
<StripeElements @options={{this.elementsOptions}} as |Elements|>
<Elements.cardNumber @options={{this.cardNumberOptions}} />
<Elements.cardExpiry />
<Elements.cardCvc />
<StripeElements/>
When you are creating your own form, you will need access to the Stripe
Elements object that links all the individual inputs. To do this, use the
onReady
action on any one of the components to store the object for
use when submitting the form. For example:
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class FormComponent extends Component {
stripeElement = null;
@action
handleReady(stripeElement) {
this.stripeElement = stripeElement;
}
@action
handleSubmit(evt) {
evt.preventDefault();
this.args.onSubmit(this.stripeElement);
}
}
<form {{on "submit" this.handleSubmit}}>
<StripeElements as |Elements|>
<Elements.cardNumber @onReady={{this.handleReady}} />
<Elements.cardExpiry />
<Elements.cardCvc />
<button type="submit">Submit</button>
<StripeElements/>
</form>
options
In addition to the simple usage above, like <StripeCard />
, you can also yield to a block, which will yield both an stripeError
object and the stripeElement
itself.
For example, you can choose to render out the stripeError
, as below (runnable in our dummy app).
<StripeCard @options={{this.options}} as |stripeElement stripeError|>
{{#if stripeError}}
<p class="error">{{stripeError.message}}</p>
{{/if}}
<button {{on "click" (fn this.submit stripeElement)}}>Submit</button>
{{#if this.token}}
<p>Your token: <code>{{this.token.id}}</code></p>
{{/if}}
</StripeCard>
Also notice the submit
action which passes the stripeElement
; you could define this in your controller like so:
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { tracked } from "@glimmer/tracking";
import { action } from '@ember/object';
export default class SubscriptionController extends Controller {
@service stripev3;
options = {
hidePostalCode: true,
style: {
base: {
color: '#333',
},
},
};
@tracked token = null;
@action
async submit(stripeElement) {
const { token } = await this.stripe.createToken(stripeElement);
this.token = token;
}
}
Note the naming convention stripeElement
instead of element
, as this could conflict with usage of element
in an Ember component.
Note that you can use CSS to style some aspects of the components, but keep in mind that the styles
object of the options
takes precedence.
Fork this repo, make a new branch, and send a pull request. Please add tests in order to have your change merged.
git clone git@github.com:adopted-ember-addons/ember-stripe-elements.git
cd ember-stripe-elements
npm install
ember serve
Visit your app at http://localhost:4200.
ember test
There are self-signed certs in /ssl
that will allow you to test autofill inside of the dummy app (or serve as a blueprint for doing this yourself in your own app).
To run using the self-signed certificate, you must:
127.0.0.1 localhost.ssl
to your hosts
fileember serve --ssl
ember build
For more information on using ember-cli, visit https://ember-cli.com/.
Thanks to @begedin, @snewcomer, @filipecrosk, and @Kilowhisky for your early help on this!