Last active
April 19, 2023 06:16
-
-
Save 3m1n3nc3/940ebfdaab671473583944100ebf094b to your computer and use it in GitHub Desktop.
An elegant VUE/Quasar component to handle all paystack payment initialization and verification. This gist is a custom solution built originally for use in the Quasar framework, you can customize and use for other vue projects as well.
This file contains hidden or 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
<template> | |
<q-btn @click="initializeNewPayment()" v-if="!hidden"> | |
<q-tooltip v-if="tooltip"> | |
<q-icon v-if="tooltipIcon" :name="tooltipIcon" /> {{ tooltip }} | |
</q-tooltip> | |
</q-btn> | |
</template> | |
<script> | |
import { QSpinnerPuff } from "quasar"; | |
import { dexieStorage } from "src/plugins/builder"; | |
import loader from "src/stores/loader"; | |
import { ref } from "vue"; | |
export default { | |
name: "TPaystack", | |
props: { | |
data: { | |
type: Object, | |
default: () => ({}), | |
}, | |
confirmation: { | |
type: [String, Object], | |
}, | |
successMessage: { | |
type: [Boolean, String, Object], | |
}, | |
hidden: { | |
type: Boolean, | |
default: false, | |
}, | |
dontVerify: { | |
type: Boolean, | |
default: false, | |
}, | |
redirectRoute: { | |
type: Object, | |
default: null, | |
validator: (e) => { | |
return e.hasOwnProperty("name") || e.hasOwnProperty("params"); | |
}, | |
}, | |
endpoint: { | |
type: [String, Object], | |
validator: (e) => { | |
if (typeof e === "string") { | |
return e.length > 0; | |
} else if (typeof e === "object") { | |
// Check if the object has the required properties | |
return e.hasOwnProperty("init") || e.hasOwnProperty("verify"); | |
} | |
return false; | |
}, | |
initEndpoint: { | |
type: String, | |
default: "/payment/initialize", | |
}, | |
}, | |
tooltip: { | |
type: String, | |
default: null, | |
}, | |
tooltipIcon: { | |
type: String, | |
default: null, | |
}, | |
}, | |
emits: [ | |
"success", | |
"error", | |
"verified", | |
"initialized", | |
"closed", | |
"init", | |
"destroyed", | |
], | |
watch: { | |
data: { | |
handler(data) {}, | |
}, | |
"$route.query.trxref": { | |
handler(trxref) { | |
if (trxref && !this.dontVerify) { | |
this.verifyPayment(trxref); | |
} | |
}, | |
immediate: true, | |
}, | |
}, | |
setup() { | |
return { | |
paystack_public_key: ref(loader.bootstrap.settings.paystack_public_key), | |
}; | |
}, | |
async created() { | |
const boot = await dexieStorage("pinia").getItem("bootstrap"); | |
if (boot.settings.paystack_public_key) { | |
this.paystack_public_key = boot.settings.paystack_public_key; | |
} | |
this.$emit("init", this); | |
}, | |
mounted() { | |
this.$emit("init", this); | |
}, | |
beforeUnmount() { | |
this.$emit("destroyed", this); | |
}, | |
methods: { | |
initializeNewPayment() { | |
if (this.confirmation) { | |
this.$helper | |
.notify({ | |
class: "tw-rounded-3xl important", | |
status: this.confirmation.color || "info", | |
message: | |
typeof this.confirmation === "object" | |
? this.confirmation.message | |
: this.confirmation, | |
alert: true, | |
ok: this.confirmation.ok || "Continue", | |
cancel: this.confirmation.cancel || "Cancel", | |
title: this.confirmation.title || "Confirm", | |
}) | |
.onOk(() => { | |
this.initialize(); | |
}); | |
} else { | |
this.initialize(); | |
} | |
}, | |
showLoading(show = true) { | |
show && | |
this.$q.loading.show({ | |
message: "Your request is proccessing. Hold on an minute...", | |
spinner: QSpinnerPuff, | |
}); | |
}, | |
initialize() { | |
this.$q.loading.show(); | |
let endpoint = | |
typeof this.endpoint === "object" ? this.endpoint.init : this.endpoint; | |
let request; | |
if (this.endpoint.request_method) { | |
request = this.$api[this.endpoint.request_method]; | |
} else { | |
request = this.$api.post; | |
} | |
request(endpoint || this.initEndpoint, { | |
...this.data, | |
inline: this.$q.platform.is.mobile, | |
}) | |
.then(({ data }) => { | |
this.$emit("initialized", data, this.data); | |
const payload = data.data.payload || data.payload || {}; | |
if (this.$q.platform.is.mobile) { | |
this.$q.loading.hide(); | |
return this.paystack( | |
data.data.amount, | |
payload.data.reference | |
).openIframe(); | |
} else { | |
setTimeout((e) => { | |
this.$q.loading.hide(); | |
this.showLoading(); | |
window.location.href = payload.data.authorization_url; | |
}, 10000); | |
} | |
}) | |
.catch((e) => { | |
this.$q.loading.hide(); | |
let error = this.$plugins.reader.error(e); | |
this.$helper.notify(error.message || error, error.status || "error"); | |
this.$emit("error", error); | |
}); | |
}, | |
verifyPayment(reference) { | |
let endpoint = | |
typeof this.endpoint === "object" | |
? this.endpoint.verify | |
: this.endpoint; | |
if (!endpoint) { | |
return; | |
} | |
let request; | |
let method; | |
if (this.endpoint.request_method || this.endpoint.v_request_method) { | |
method = ( | |
this.endpoint.v_request_method | |
? this.endpoint.v_request_method | |
: this.endpoint.request_method | |
).toLowerCase(); | |
request = this.$api[method]; | |
} else { | |
request = this.$api.get; | |
} | |
this.$q.loading.show(); | |
let params = { | |
trxref: this.$route.query.trxref, | |
reference: reference, | |
...(typeof this.endpoint.params == "object" | |
? this.endpoint.params | |
: {}), | |
}; | |
let data = | |
method == "get" | |
? { params } | |
: { | |
reference, | |
...this.data, | |
}; | |
request(endpoint, data, { params }) | |
.then(({ data }) => { | |
this.$q.loading.hide(); | |
this.$emit("verified", data); | |
const amount = this.$helper.money( | |
data.amount || data.data.amount || 0 | |
); | |
const routeTo = { | |
query: { | |
...this.$route.query, | |
trxref: undefined, | |
reference: undefined, | |
}, | |
}; | |
if (this.successMessage && typeof this.successMessage === "object") { | |
this.$helper | |
.notify({ | |
status: this.successMessage.color || "info", | |
message: (this.successMessage.message || "").replace( | |
"{amount}", | |
amount | |
), | |
alert: true, | |
ok: this.successMessage.ok, | |
cancel: this.successMessage.cancel, | |
title: this.successMessage.title, | |
}) | |
.onOk(() => { | |
if ( | |
this.successMessage.onOk && | |
typeof this.successMessage.onOk === "function" | |
) { | |
this.successMessage.onOk(data, () => | |
this.$router.replace(routeTo) | |
); | |
} | |
}); | |
} else if ( | |
this.successMessage && | |
typeof this.successMessage === "boolean" | |
) { | |
this.$helper | |
.notify({ | |
class: "tw-rounded-3xl important", | |
status: data.status || "success", | |
message: (data.message || "").replace("{amount}", amount), | |
alert: true, | |
ok: "Continue", | |
cancel: false, | |
title: "Payment Complete", | |
}) | |
.onOk(() => { | |
this.$router.replace(routeTo); | |
}); | |
} else if (this.successMessage) { | |
this.$helper.notify( | |
(this.successMessage.message || "").replace("{amount}", amount) | |
); | |
} else { | |
this.$router.replace(routeTo); | |
} | |
}) | |
.catch((e) => { | |
this.$q.loading.hide(); | |
let error = this.$plugins.reader.error(e); | |
this.$helper.notify(error.message || error, error.status || "error"); | |
this.$emit("error", error); | |
}); | |
}, | |
paystack(amount = 0, ref = null) { | |
return PaystackPop.setup({ | |
key: this.paystack_public_key, // Replace with your public key | |
email: this.$user.email, | |
amount: amount * 100, | |
ref: ref, | |
label: this.$user.fullname, | |
onClose: () => { | |
this.$helper.notify("Transaction cancelled.", "error"); | |
this.$api.delete("/payment/terminate", { | |
data: { reference: ref }, | |
}); | |
this.$emit("closed", { reference: ref }); | |
}, | |
callback: (response) => { | |
this.$emit("success", response); | |
if (this.redirectRoute) { | |
this.$router.push({ | |
...this.redirectRoute, | |
query: { | |
...this.$route.query, | |
trxref: response.trxref, | |
reference: response.reference, | |
}, | |
}); | |
} else { | |
this.$helper.notify("Transaction completed successfully.", "info"); | |
} | |
}, | |
}); | |
}, | |
}, | |
}; | |
</script> |
Verify Payment
<paystack-handler
hidden
:endpoint=`events/:event_id/tickets/:ticket_id/verify`
:data="{
method: 'card',
reference: 'TRX-Y8O20CW0WUV14708C0Q7',
}"
@verified="(e) => {}"
@error="(e) => {}"
/>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Initializing a payment