Skip to content

Instantly share code, notes, and snippets.

@3m1n3nc3
Last active April 19, 2023 06:16
Show Gist options
  • Save 3m1n3nc3/940ebfdaab671473583944100ebf094b to your computer and use it in GitHub Desktop.
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.
<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>
@3m1n3nc3
Copy link
Author

3m1n3nc3 commented Feb 1, 2023

Initializing a payment

    <paystack-handler
      no-caps
      unelevated
      dont-verify
      class="full-width q-mt-md"
      color="green-10"
      label="Continue"
      icon="fa-duotone fa-credit-card"
      :endpoint="{ init: '/events/:event_id/tickets/:ticket_id', verify: false }"
      :disable="any.reason.to.disable === true"
      :confirmation="{
        message: `You are about to buy a ticket for {Event Name} for {Ticket Price}, do you want to continue?`,
        title: 'Confirm Ticket Purchase',
        ok: 'Yes, Continue',
        cancel: 'No, Cancel',
        color: 'info',
      }"
      :data="{
        redirect: `${location.origin}${$router.resolve({ name: 'payment.verify' }).href}`,
        qty: 1,
        fullname: 'Samuel Ojo',
        email: '[email protected]',
        phone: 0903123456,
      }"
      @success="
        () => {
        }
      "
      @error="
        () => {
        }
      "
      @close="
        () => {
        }
      "
      @init="
        () => {
        }
      "
      @destroyed="
        () => {
        }
      "
    />

@3m1n3nc3
Copy link
Author

3m1n3nc3 commented Feb 1, 2023

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