@achyutlabsau/vue-payment-gateway 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,452 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { QSpinnerHourglass, Dialog } from "quasar";
5
+ import { i as isNetworkError, a as isServerError, t as timeout, g as generateTransactionId } from "./index-C8vc_75e.js";
6
+ import axios, { isAxiosError, HttpStatusCode } from "axios";
7
+ import { v7 } from "uuid";
8
+ import { e as environment, b as state } from "./state-0HFa2Xwz.js";
9
+ const LINKLY_CONSTANTS = {
10
+ PATHS: {
11
+ TRANSACTION: "transaction",
12
+ PAIRING: "pairing/cloudpos",
13
+ TOKENS: "tokens/cloudpos",
14
+ REPRINT_RECEIPT: "reprintreceipt"
15
+ },
16
+ STORAGE_KEYS: {
17
+ PAIRING_SECRET: "___linklyPairingSecret",
18
+ REFRESH_TOKEN: "___linklyRefreshToken",
19
+ TOKEN_EXPIRY: "___linklyRefreshTokenExpiry",
20
+ LAST_TRANSACTION_ID: "___linklyLastTransactionId"
21
+ },
22
+ TIMEOUT: {
23
+ MAX_INTERVAL: 18e4,
24
+ // 3 minutes
25
+ BASE_INTERVAL: 2e3,
26
+ INTERVAL_INCREMENT: 1e3
27
+ },
28
+ URLS: {
29
+ DEVELOPMENT: {
30
+ AUTH_BASE: "https://auth.sandbox.cloud.pceftpos.com/v1/",
31
+ BASE: "https://rest.pos.sandbox.cloud.pceftpos.com/v1/sessions/"
32
+ },
33
+ PRODUCTION: {
34
+ AUTH_BASE: "https://auth.cloud.pceftpos.com/v1/",
35
+ BASE: "https://rest.pos.cloud.pceftpos.com/v1/sessions/"
36
+ }
37
+ }
38
+ };
39
+ const DEFAULT_REQUEST_PARAMS = {
40
+ async: false
41
+ };
42
+ const MAX_INTERVAL_TIME = 18e4;
43
+ const BASE_INTERVAL = 2e3;
44
+ const INTERVAL_INCREMENT = 1e3;
45
+ const { PATHS, STORAGE_KEYS } = LINKLY_CONSTANTS;
46
+ class LinklyPaymentGateway {
47
+ constructor() {
48
+ __publicField(this, "_api");
49
+ __publicField(this, "_authApi");
50
+ __publicField(this, "token", null);
51
+ __publicField(this, "tokenExpiry", null);
52
+ __publicField(this, "pairingSecret", null);
53
+ const urlConfig = environment.value === "production" ? LINKLY_CONSTANTS.URLS.PRODUCTION : LINKLY_CONSTANTS.URLS.DEVELOPMENT;
54
+ this._api = axios.create({
55
+ baseURL: urlConfig.BASE,
56
+ headers: { Accept: "application/json" }
57
+ });
58
+ this._authApi = axios.create({
59
+ baseURL: urlConfig.AUTH_BASE,
60
+ headers: { Accept: "application/json" }
61
+ });
62
+ this._setupInterceptors();
63
+ this._loadSavedCredentials();
64
+ }
65
+ _loadSavedCredentials() {
66
+ const { STORAGE_KEYS: STORAGE_KEYS2 } = LINKLY_CONSTANTS;
67
+ this.pairingSecret = localStorage.getItem(STORAGE_KEYS2.PAIRING_SECRET);
68
+ this.token = localStorage.getItem(STORAGE_KEYS2.REFRESH_TOKEN);
69
+ const savedTokenExpiry = localStorage.getItem(STORAGE_KEYS2.TOKEN_EXPIRY);
70
+ if (savedTokenExpiry) {
71
+ this.tokenExpiry = parseInt(savedTokenExpiry, 10);
72
+ }
73
+ }
74
+ _setupInterceptors() {
75
+ this._api.interceptors.request.use((config) => {
76
+ if (this.token) {
77
+ config.headers.Authorization = `Bearer ${this.token}`;
78
+ }
79
+ return config;
80
+ });
81
+ }
82
+ // New method to check and renew token if needed
83
+ async _ensureValidToken() {
84
+ if (!this.token || !this.tokenExpiry || this.tokenExpiry - Date.now() < 60 * 60 * 1e3) {
85
+ try {
86
+ if (this.pairingSecret) {
87
+ await this.getLinklyAuthToken(this.pairingSecret);
88
+ } else {
89
+ throw new Error("No valid authentication method available");
90
+ }
91
+ } catch (error) {
92
+ throw new Error("Failed to renew authentication token. Please check your Linkly account credentials.");
93
+ }
94
+ }
95
+ }
96
+ async pairPinpad(pairingData) {
97
+ var _a;
98
+ try {
99
+ const response = await this._authApi.post(PATHS.PAIRING, pairingData);
100
+ if (response.data.secret) {
101
+ this._savePairingSecret(response.data.secret);
102
+ await this.getLinklyAuthToken(response.data.secret);
103
+ }
104
+ return {
105
+ success: true,
106
+ secret: response.data.secret,
107
+ message: "Pinpad Paired successfully."
108
+ };
109
+ } catch (error) {
110
+ if (isAxiosError(error)) {
111
+ const status = (_a = error.response) == null ? void 0 : _a.status;
112
+ let message = "An unexpected error occurred during pinpad pairing.";
113
+ switch (status) {
114
+ case HttpStatusCode.Unauthorized:
115
+ message = "Looks like the username, password or pair code is no longer valid, or your account has been disabled. Try re-pairing the PIN pad or resetting the password.";
116
+ break;
117
+ case HttpStatusCode.BadRequest:
118
+ message = "Invalid request! Correct the request and try again.";
119
+ break;
120
+ case HttpStatusCode.RequestTimeout:
121
+ message = "Request Timeout. This should be rare: a transient error has occurred, possibly due to server overloading.";
122
+ break;
123
+ default:
124
+ if (isServerError(status)) {
125
+ message = "A server error has occurred. Wait a few seconds and attempt the request again. If the problem persists, contact Linkly Support.";
126
+ }
127
+ }
128
+ if (isNetworkError(error)) {
129
+ message = "You are not connected to the internet";
130
+ }
131
+ return {
132
+ success: false,
133
+ message,
134
+ secret: ""
135
+ };
136
+ }
137
+ return {
138
+ success: false,
139
+ message: "An unexpected error occurred.",
140
+ secret: ""
141
+ };
142
+ }
143
+ }
144
+ _savePairingSecret(secret) {
145
+ this.pairingSecret = secret;
146
+ localStorage.setItem(STORAGE_KEYS.PAIRING_SECRET, secret);
147
+ }
148
+ _clearCredentials() {
149
+ const { STORAGE_KEYS: STORAGE_KEYS2 } = LINKLY_CONSTANTS;
150
+ this.pairingSecret = null;
151
+ this.token = null;
152
+ this.tokenExpiry = null;
153
+ localStorage.removeItem(STORAGE_KEYS2.PAIRING_SECRET);
154
+ localStorage.removeItem(STORAGE_KEYS2.REFRESH_TOKEN);
155
+ localStorage.removeItem(STORAGE_KEYS2.TOKEN_EXPIRY);
156
+ }
157
+ async getLinklyAuthToken(secret) {
158
+ var _a;
159
+ const { STORAGE_KEYS: STORAGE_KEYS2 } = LINKLY_CONSTANTS;
160
+ try {
161
+ const tokenData = {
162
+ secret,
163
+ posName: state.productName,
164
+ posVersion: state.productVersion,
165
+ posId: state.posId,
166
+ posVendorId: state.productVendorName
167
+ };
168
+ const response = await this._authApi.post(PATHS.TOKENS, tokenData);
169
+ this.token = response.data.token;
170
+ this.tokenExpiry = Date.now() + response.data.expirySeconds * 1e3;
171
+ localStorage.setItem(STORAGE_KEYS2.REFRESH_TOKEN, response.data.token);
172
+ localStorage.setItem(STORAGE_KEYS2.TOKEN_EXPIRY, String(this.tokenExpiry));
173
+ return response.data;
174
+ } catch (error) {
175
+ console.error("Get token error:", error);
176
+ if (axios.isAxiosError(error) && ((_a = error.response) == null ? void 0 : _a.status) === HttpStatusCode.Unauthorized) {
177
+ this._clearCredentials();
178
+ throw new Error("Authentication failed. Pairing may have been reset or credentials changed.");
179
+ }
180
+ throw error;
181
+ }
182
+ }
183
+ async sendTransactionRequest(data) {
184
+ await this._ensureValidToken();
185
+ const sessionId = v7();
186
+ localStorage.setItem(STORAGE_KEYS.LAST_TRANSACTION_ID, sessionId);
187
+ try {
188
+ const response = await this._api.post(`${sessionId}/${PATHS.TRANSACTION}`, data, {
189
+ params: DEFAULT_REQUEST_PARAMS
190
+ });
191
+ if (response.status === HttpStatusCode.Accepted) {
192
+ return this.getTransactionStatus(sessionId);
193
+ }
194
+ return response.data;
195
+ } catch (error) {
196
+ if (axios.isAxiosError(error) && error.response) {
197
+ const status = error.response.status;
198
+ if (status === HttpStatusCode.Unauthorized) {
199
+ try {
200
+ await this.getLinklyAuthToken(this.token);
201
+ return this.sendTransactionRequest(data);
202
+ } catch {
203
+ throw new Error("Failed to authenticate. Please check your Linkly account credentials.");
204
+ }
205
+ }
206
+ if (status === HttpStatusCode.RequestTimeout || isServerError(error)) {
207
+ return this.getTransactionStatus(sessionId);
208
+ }
209
+ }
210
+ throw error;
211
+ }
212
+ }
213
+ async getTransactionStatus(transactionId) {
214
+ let interval = BASE_INTERVAL;
215
+ const doRequest = async () => {
216
+ var _a;
217
+ try {
218
+ const response = await this._api.get(`${transactionId}/${PATHS.TRANSACTION}`, {
219
+ params: DEFAULT_REQUEST_PARAMS
220
+ });
221
+ if (response.status === HttpStatusCode.Accepted) {
222
+ return doRequest();
223
+ }
224
+ return response.data;
225
+ } catch (error) {
226
+ if (axios.isAxiosError(error)) {
227
+ const status = ((_a = error.response) == null ? void 0 : _a.status) ?? 0;
228
+ if (status === HttpStatusCode.RequestTimeout || isServerError(error)) {
229
+ if (interval < MAX_INTERVAL_TIME) {
230
+ await timeout(BASE_INTERVAL);
231
+ interval = Math.min(interval + INTERVAL_INCREMENT, MAX_INTERVAL_TIME);
232
+ return doRequest();
233
+ }
234
+ throw new Error("Unable to get the transaction details. Please contact service provider!");
235
+ } else if (status === HttpStatusCode.Unauthorized) {
236
+ await this.getLinklyAuthToken(this.token);
237
+ return doRequest();
238
+ }
239
+ }
240
+ throw error;
241
+ }
242
+ };
243
+ return doRequest();
244
+ }
245
+ async getLastTransactionStatus() {
246
+ const txnId = localStorage.getItem(STORAGE_KEYS.LAST_TRANSACTION_ID);
247
+ if (!txnId) {
248
+ throw new Error("No transaction found.");
249
+ }
250
+ return this.getTransactionStatus(txnId);
251
+ }
252
+ async reprintReceipt(data) {
253
+ const uuid = v7();
254
+ const res = await this._api.post(`${uuid}/${PATHS.REPRINT_RECEIPT}`, data, {
255
+ params: DEFAULT_REQUEST_PARAMS
256
+ });
257
+ return res.data;
258
+ }
259
+ }
260
+ var TransactionTypes = /* @__PURE__ */ ((TransactionTypes2) => {
261
+ TransactionTypes2["Purchase"] = "P";
262
+ TransactionTypes2["Refund"] = "R";
263
+ return TransactionTypes2;
264
+ })(TransactionTypes || {});
265
+ var CurrencyCodes = /* @__PURE__ */ ((CurrencyCodes2) => {
266
+ CurrencyCodes2["AustralianDollar"] = "AUD";
267
+ return CurrencyCodes2;
268
+ })(CurrencyCodes || {});
269
+ var ReceiptAutoPrint = /* @__PURE__ */ ((ReceiptAutoPrint2) => {
270
+ ReceiptAutoPrint2["ReturnToPOS"] = "0";
271
+ ReceiptAutoPrint2["MixedPrint"] = "7";
272
+ ReceiptAutoPrint2["AllReceiptsFromPINPad"] = "9";
273
+ return ReceiptAutoPrint2;
274
+ })(ReceiptAutoPrint || {});
275
+ var ResponseCodes = /* @__PURE__ */ ((ResponseCodes2) => {
276
+ ResponseCodes2["APPROVED"] = "00";
277
+ ResponseCodes2["Approved"] = "08";
278
+ ResponseCodes2["TXN_CANCELLED"] = "HD";
279
+ ResponseCodes2["INSUFFICIENT_FUND"] = "HB";
280
+ ResponseCodes2["GENERAL_DECLINE"] = "99";
281
+ ResponseCodes2["PINPAD_OFFLINE"] = "PF";
282
+ return ResponseCodes2;
283
+ })(ResponseCodes || {});
284
+ const dialogDefaultOpts = {
285
+ message: "Processing the payment...",
286
+ class: "text-lg",
287
+ progress: {
288
+ // spinner: QSpinnerClock,
289
+ spinner: QSpinnerHourglass,
290
+ color: "amber"
291
+ },
292
+ persistent: true,
293
+ html: true,
294
+ ok: false,
295
+ cancel: {
296
+ label: "Close",
297
+ noCaps: true,
298
+ color: "red",
299
+ outline: true
300
+ }
301
+ };
302
+ const updateDialog = (dialog, transactionOutcome) => {
303
+ if (transactionOutcome === TransactionOutcome.APPROVED) {
304
+ dialog.update({
305
+ title: "Success!",
306
+ class: "text-green-500",
307
+ progress: false,
308
+ message: '<div class="text-gray-800 text-base">Transaction Accepted!</div>'
309
+ });
310
+ } else if (transactionOutcome === TransactionOutcome.DECLINED) {
311
+ dialog.update({
312
+ title: "Failed!",
313
+ class: "text-red-500",
314
+ progress: false,
315
+ message: '<div class="text-gray-800 text-base">Transaction Declined!</div>'
316
+ });
317
+ } else if (transactionOutcome === TransactionOutcome.CANCELLED) {
318
+ dialog.update({
319
+ title: "Failed!",
320
+ class: "text-red-500",
321
+ progress: false,
322
+ message: '<div class="text-gray-800 text-base">Transaction Cancelled!</div>'
323
+ });
324
+ } else if (transactionOutcome === TransactionOutcome.UNKNOWN) {
325
+ dialog.update({
326
+ title: "Failed!",
327
+ class: "text-red-500",
328
+ progress: false,
329
+ message: '<div class="text-gray-800 text-base">Transaction Failed!</div>'
330
+ });
331
+ } else {
332
+ dialog.update({
333
+ title: "Failed!",
334
+ class: "text-red-500",
335
+ progress: false,
336
+ message: '<div class="text-gray-800 text-base">Transaction Failed!</div>'
337
+ });
338
+ }
339
+ };
340
+ var TransactionOutcome = /* @__PURE__ */ ((TransactionOutcome2) => {
341
+ TransactionOutcome2["APPROVED"] = "APPROVED";
342
+ TransactionOutcome2["CANCELLED"] = "CANCELLED";
343
+ TransactionOutcome2["DECLINED"] = "DECLINED";
344
+ TransactionOutcome2["UNKNOWN"] = "UNKNOWN";
345
+ return TransactionOutcome2;
346
+ })(TransactionOutcome || {});
347
+ const useLinkly = () => {
348
+ const linklyAPI = new LinklyPaymentGateway();
349
+ const pairPinpad = async (pairData) => {
350
+ const dialog = Dialog.create({ ...dialogDefaultOpts, message: "Pairing with the terminal..." });
351
+ const res = await linklyAPI.pairPinpad(pairData);
352
+ dialog.update({
353
+ title: res.success ? "Success!" : "Failed!",
354
+ class: res.success ? "text-green-500" : "text-red-500",
355
+ message: res.message,
356
+ progress: false
357
+ });
358
+ return res;
359
+ };
360
+ const _initiateTransaction = async (data, TxnType) => {
361
+ var _a, _b, _c;
362
+ const payload = {
363
+ Request: {
364
+ Merchant: "00",
365
+ Application: "00",
366
+ TxnRef: generateTransactionId(),
367
+ TxnType,
368
+ CurrencyCode: CurrencyCodes.AustralianDollar,
369
+ CutReceipt: data.CutReceipt,
370
+ AmtPurchase: data.AmtPurchase,
371
+ ReceiptAutoPrint: data.ReceiptAutoPrint
372
+ }
373
+ };
374
+ const dialog = Dialog.create({ ...dialogDefaultOpts });
375
+ try {
376
+ const res = await linklyAPI.sendTransactionRequest(payload);
377
+ if (((_a = res == null ? void 0 : res.Response) == null ? void 0 : _a.ResponseCode) === ResponseCodes.APPROVED || ((_b = res == null ? void 0 : res.Response) == null ? void 0 : _b.ResponseCode) === ResponseCodes.Approved) {
378
+ updateDialog(
379
+ dialog,
380
+ "APPROVED"
381
+ /* APPROVED */
382
+ );
383
+ return {
384
+ result: "APPROVED",
385
+ data: res.Response
386
+ };
387
+ }
388
+ if (((_c = res == null ? void 0 : res.Response) == null ? void 0 : _c.ResponseCode) === ResponseCodes.GENERAL_DECLINE) {
389
+ updateDialog(
390
+ dialog,
391
+ "DECLINED"
392
+ /* DECLINED */
393
+ );
394
+ return {
395
+ result: "DECLINED",
396
+ data: res.Response
397
+ };
398
+ }
399
+ updateDialog(
400
+ dialog,
401
+ "UNKNOWN"
402
+ /* UNKNOWN */
403
+ );
404
+ return {
405
+ result: "UNKNOWN",
406
+ data: res.Response
407
+ };
408
+ } catch (error) {
409
+ updateDialog(
410
+ dialog,
411
+ "UNKNOWN"
412
+ /* UNKNOWN */
413
+ );
414
+ throw error;
415
+ }
416
+ };
417
+ const initiatePurchase = async (data) => {
418
+ return _initiateTransaction(data, TransactionTypes.Purchase);
419
+ };
420
+ const initiateRefund = async (data) => {
421
+ return _initiateTransaction(data, TransactionTypes.Refund);
422
+ };
423
+ const reprintReceipt = async (txnRef) => {
424
+ const reprintPayload = {
425
+ Request: {
426
+ Merchant: "00",
427
+ Application: "00",
428
+ ReceiptAutoPrint: ReceiptAutoPrint.ReturnToPOS,
429
+ ReprintType: "2",
430
+ OriginalTxnRef: txnRef
431
+ }
432
+ };
433
+ return linklyAPI.reprintReceipt(reprintPayload);
434
+ };
435
+ return {
436
+ pairPinpad,
437
+ initiatePurchase,
438
+ initiateRefund,
439
+ reprintReceipt,
440
+ getTransactionBySessionId: linklyAPI.getTransactionStatus,
441
+ getLastTransaction: linklyAPI.getLastTransactionStatus
442
+ };
443
+ };
444
+ export {
445
+ CurrencyCodes as C,
446
+ ReceiptAutoPrint as R,
447
+ TransactionOutcome as T,
448
+ TransactionTypes as a,
449
+ ResponseCodes as b,
450
+ dialogDefaultOpts as d,
451
+ useLinkly as u
452
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@achyutlabsau/vue-payment-gateway",
3
+ "private": false,
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "main": "./dist/index.js",
10
+ "module": "./dist/index.js",
11
+ "exports": {
12
+ "./linkly": {
13
+ "types": "./dist/linkly.d.ts",
14
+ "import": "./dist/linkly.js"
15
+ },
16
+ "./smartpay": {
17
+ "types": "./dist/smartpay.d.ts",
18
+ "import": "./dist/smartpay.js"
19
+ },
20
+ "./tyro": {
21
+ "types": "./dist/tyro.d.ts",
22
+ "import": "./dist/tyro.js"
23
+ },
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ }
28
+ },
29
+ "scripts": {
30
+ "dev": "vite",
31
+ "build": "vue-tsc -b && vite build",
32
+ "preview": "vite preview"
33
+ },
34
+ "dependencies": {},
35
+ "peerDependencies": {
36
+ "uuid": "^11.x.x",
37
+ "axios": "^1.7.x",
38
+ "quasar": "^2.x.x",
39
+ "vue": "^3.5.x"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^22.10.1",
43
+ "@vitejs/plugin-vue": "^5.2.1",
44
+ "uuid": "^11.0.3",
45
+ "axios": "^1.7.9",
46
+ "quasar": "^2.x.x",
47
+ "typescript": "5.7.2",
48
+ "vite": "^6.0.3",
49
+ "vite-plugin-dts": "^4.3.0",
50
+ "vue": "^3.5.13",
51
+ "vue-tsc": "^2.1.10"
52
+ }
53
+ }