Vue.jsと決済APIのStripeを用いて決済処理を実装しています。
Stripeのカード用フォームを、通常のページに表示させると問題ないのですが、
モーダルに出そうとすると、カードNoを入力するところが表示されません。
以下のようなエラーもでます。
▼その時出ているエラー
IntegrationError: The selector you specified (#card-element) applies to no DOM elements that are currently on the page. Make sure the element exists on the page before calling mount().
###以下コードです
■ShopOrderCheck.vue(モーダルの呼び出し元のVue)
<template> <div> <main class="page-form"> <h1 class="page-heading">ご注文ページ</h1> <div class="bg-secondary-lighten-1 py-10"> <div class="container"> <div class="grouping shadow-md mb-6 md:w-10/12 max-w-screen-sm mx-auto"> <form class="form form--check" action="../epsilon/order_ep.php" method="POST" id="order_form"> <div class="form__group"> <label class="form__label">メールアドレス</label> <div> <div>{{ request.buy_email }}<p style="color:#D31004;" class="text-sm">※正しいメールアドレスかをご確認ください。</p></div> </div> </div> </form> <div class="mt-8" v-if="request.payment_method == 1"> <button type="button" class="form__button w-full button " id="order_card" @click="show">クレジットカード</button> </div> <div class="mt-8" v-else> <button type="button" class="form__button w-full button" id="order_bank">銀行振込</button> </div> <div class="mt-4 ml-2 font-bold" style="color: #285399;"> <a :href="getBackLink" class="back-link">≪ 戻る</a> </div> </div> </div> </div> </main> <stripe-card :product="product" :request="request" :public_key="public_key" :csrf = "csrf" ></stripe-card> </div> </template> <script> export default { props: { csrf: { type: String, }, product: { type: Object, }, request: { type: Object }, public_key: { type: Object, } }, data() { return { showContent: false, errors: [], } }, computed: { getBackLink: function() { let back_url = "/shop/order?id=" + this.product.id; return back_url; } }, methods: { openModal : function() { this.showContent = true; }, closeModal () { this.showContent = false }, show : function() { this.$modal.show('stripe-card'); }, hide : function () { this.$modal.hide('stripe-card'); }, regist: function() { [まだ未実装] }, }, } </script>
■モーダルコンポーネント StripeCard.vue
<template> <modal name="stripe-card"> <div> <input type="hidden" name="_token" v-bind:value="this.csrf"> <label for="exampleInputEmail1">お名前</label> <input type="test" class="form-control col-sm-5" id="card-holder-name" required v-model="Form.cardHolderName"> <label for="exampleInputPassword1">カード番号</label> <div class="form-group MyCardElement col-sm-5" id="card-element"></div> <div id="card-errors" role="alert" style='color:red'></div> <button class="btn btn-primary" id="card-button" >送信する</button> </div> </modal> </template> <script> import {loadStripe} from '@stripe/stripe-js'; export default { props: { product: { type: Object, }, request: { type: Object, }, public_key: { type: Object, }, csrf: { type: String, } }, data() { return { stripe: null, stripeCard: null, Form: { cardHolderName: null, }, } }, async mounted() { this.stripe = await loadStripe(this.public_key.value); const elements = this.stripe.elements(); var style = { base: { color: "#32325d", fontFamily: '"Helvetica Neue", Helvetica, sans-serif', fontSmoothing: "antialiased", fontSize: "16px", "::placeholder": { color: "#aab7c4" } }, invalid: { color: "#fa755a", iconColor: "#fa755a" } }; this.stripeCard = elements.create('card', {style: style, hidePostalCode: true}); this.stripeCard.mount('#card-element'); this.stripeCard.on('change', function(event) { var displayError = document.getElementById('card-errors'); if (event.error) { displayError.textContent = event.error.message; } else { displayError.textContent = ''; } }); }, methods: { } } </script>
mountが呼び出されている時にDOMがないというエラーぽく、
モーダルでカードのフォームを表示させず、ShopOrderCheck.vueの中に普通に表示させるとこの問題は起きませんでした。
ググってみましたら、こちらのページが回答に近いのかなと思いましたが、
Vue SPA Single File Component Element binding with Stripe Elements
元々のVue.jsの知識が浅く、私にコードに置き換えてどのように変更すればよいかがわかりません…
アドバイスいただけますと幸いです。
Laravel8系を使ってます。
StripeCard.vue の async mounted をただの mounted に変更するとどうなりますか?
メッセージありがとうございます。
ただのmountedにしますと、その下のthis.stripe = await loadStripe(this.public_key.value);でエラーになってしまうので、
mounted() {
this.stripe = loadStripe(this.public_key);
const elements = this.stripe.elements();
var style = {
base: {
color: "#32325d",
にしました。すると、
Error in mounted hook: "TypeError: this.stripe.elements is not a function"
Error in mounted hook (Promise/async): "IntegrationError: The selector you specified (#cardNumber) applies to no DOM elements that are currently on the page.
Make sure the element exists on the page before calling mount()."
と出ます…
mounted のときには DOM が用意されているはずなので、
#card-element がないのはおかしい、async のせいだと思ったのですが、
async を外そうとすると mounted を全体的に書き換えないといけなくなってしまいますね。
async mounted のままで、
this.stripeCard.mount('#card-element'); を
以下のように this.$nextTick で囲むとどうなるでしょうか?
this.$nextTick(() => {
this.stripeCard.mount('#card-element');
})
ありがとうございます。$nextTickで囲んでみました。
async mounted() {
this.stripe = await loadStripe(this.public_key);
const elements = this.stripe.elements();
var style = {
省略
};
this.stripeCard = elements.create('card', {style: style, hidePostalCode: true});
this.$nextTick(() => {
this.stripeCard.mount('#card-element');
});
$nextTickを調べてみて、ビュー全体がレンダリングされるまで待つとありましたので、これならできるかもと思いましたが、以下のようなエラーが出ています…
[Vue warn]: Error in nextTick: "IntegrationError: The selector you specified (#card-element) applies to no DOM elements that are currently on the page.
Make sure the element exists on the page before calling mount()."
found in
---> <StripeCard>
-----------------------------------
IntegrationError: The selector you specified (#card-element) applies to no DOM elements that are currently on the page.
Make sure the element exists on the page before calling mount().
使っているライブラリは vue-js-modal でしょうか?
これは v-show ではなく v-if で表示状態を制御しているので、
mounted が走る状態では v-if="false" になっており DOM が存在しないのかもしれません。
@opened というイベントが用意されているので、
これが発火したタイミングで初期化処理を実行させます。
1. StripeCard.vue の mounted を methods.setupStripe に移動させる
2. <modal name="stripe-card" @opened="setupStripe"> とする
こうすると opened のたびに setupStripe が実行されるので、
data に initialized: false とかを作り、スキップするようにする
methods: {
setupStripe () {
if (this.initialized) return true
...
}
}
ありがとうございます。
確かに、vue-js-modalを使っています。仰る通り、mounted が走る段階ではDOMが存在しないのかもしれません。
>2. <modal name="stripe-card" @opened="setupStripe"> とする
>こうすると opened のたびに setupStripe が実行されるので、~
までは理解ができましたが、なぜ、initialized変数を作り、setupStripeメソッドの中で分岐をさせているのでしょうか?initialized変数はどこでTrueになりますでしょうか??T
if(this.initialized == true) {
ここに
this.stripe = await loadStripe(this.public_key);
const elements = this.stripe.elements();
…のようにasync montedの処理を入れるのでしょうか??
}
何から何まで申し訳ございません…
initialized はモーダルを開くたびに opened が発火し、何度も setupStripe が実行されるのを防ぐためです。
setupStripe () {
if (this.initialized) {
// true なら処理をやめる
return
} else {
this.initialized = true
}
// 以下に mounted にあった内容
}
なるほどですね!ありがとうございます。やってみます!
neko_daisuki様
こちらありがとうございます。できました!
あなたの回答
tips
プレビュー