@akinon/pz-tabby-extension 1.94.0 → 1.95.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @akinon/pz-tabby-extension
2
2
 
3
+ ## 1.95.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1913efc: ZERO-3296: Add Tabby payment availability check API and update related components
8
+ - 2e3e8ab: ZERO-3296: Refactor POST handler to check currency-specific environment variables for hash key and extension URL
9
+
3
10
  ## 1.94.0
4
11
 
5
12
  ## 1.93.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/pz-tabby-extension",
3
- "version": "1.94.0",
3
+ "version": "1.95.0",
4
4
  "license": "MIT",
5
5
  "main": "src/index.tsx",
6
6
  "peerDependencies": {
package/readme.md CHANGED
@@ -40,6 +40,71 @@ const TabbyGateway = async ({
40
40
  export default TabbyGateway;
41
41
  ```
42
42
 
43
+ ## API Routes
44
+
45
+ ### Check Availability API
46
+
47
+ To enable Tabby payment availability checks, you need to create an API route. Create a file at `src/app/api/tabby-check-availability/route.ts` with the following content:
48
+
49
+ ```typescript
50
+ import { POST } from '@akinon/pz-tabby-extension/src/pages/api/check-availability';
51
+
52
+ export { POST };
53
+ ```
54
+
55
+ This API endpoint handles checking the availability of Tabby payment for a given:
56
+
57
+ - Order amount
58
+ - Email
59
+ - Phone number
60
+ - Currency
61
+
62
+ The endpoint automatically validates the request and response using hash-based security measures.
63
+
64
+ ### Using checkTabbyAvailability Mutation
65
+
66
+ The extension provides a Redux mutation hook that you can use to check Tabby payment availability. Here's an example of how to implement it:
67
+
68
+ ```typescript
69
+ import { useCheckTabbyAvailabilityMutation } from '@akinon/pz-tabby-extension/src/redux/api';
70
+
71
+ const YourComponent = () => {
72
+ const [checkTabbyAvailability] = useCheckTabbyAvailabilityMutation();
73
+ const [isTabbyAvailable, setIsTabbyAvailable] = useState(false);
74
+
75
+ useEffect(() => {
76
+ const checkAvailability = async () => {
77
+ try {
78
+ const response = await checkTabbyAvailability({
79
+ amount: 1000, // Order total amount
80
+ phone: '+971123456789', // Customer's phone number
81
+ email: 'example@example.com', // Customer's email ,
82
+ name: 'Akinon Akinon', // Customer's Full Name
83
+ }).unwrap();
84
+
85
+ setIsTabbyAvailable(response.is_available);
86
+ } catch (error) {
87
+ console.error('Error checking Tabby availability:', error);
88
+ setIsTabbyAvailable(false);
89
+ }
90
+ };
91
+
92
+ checkAvailability();
93
+ }, [checkTabbyAvailability]);
94
+
95
+ // Use isTabbyAvailable to conditionally render Tabby payment option
96
+ return (
97
+ // Your component JSX
98
+ );
99
+ };
100
+ ```
101
+
102
+ The mutation returns an object with the following properties:
103
+
104
+ - `is_available`: boolean indicating if Tabby payment is available
105
+ - `salt`: string used for hash verification
106
+ - `hash`: string for response validation
107
+
43
108
  ## Configuration
44
109
 
45
110
  Add these variables to your `.env` file
@@ -61,7 +61,7 @@ export const TabbyPaymentGateway = async ({
61
61
  ]);
62
62
 
63
63
  const salt = getRandomString(10);
64
- const hash = generateHash(hashKey, sessionId, salt);
64
+ const hash = generateHash(salt, sessionId, hashKey);
65
65
 
66
66
  const context = {
67
67
  salt,
@@ -0,0 +1,118 @@
1
+ import { NextResponse } from 'next/server';
2
+ import type { NextRequest } from 'next/server';
3
+ import { getRandomString, generateHash } from '../../utils';
4
+
5
+ interface CheckAvailabilityResponse {
6
+ salt: string;
7
+ hash: string;
8
+ is_available: boolean;
9
+ }
10
+ const verifyResponseHash = (
11
+ salt: string,
12
+ hasAvailability: boolean,
13
+ hashKey: string,
14
+ responseHash: string
15
+ ): boolean => {
16
+ return (
17
+ generateHash(salt, hasAvailability ? 'True' : 'False', hashKey) ===
18
+ responseHash
19
+ );
20
+ };
21
+
22
+ export async function POST(request: NextRequest) {
23
+ try {
24
+ const body = await request.json();
25
+ const { amount, phone, email, name } = body;
26
+ const currency = request.cookies.get('pz-currency')?.value;
27
+
28
+ if (!currency) {
29
+ return NextResponse.json(
30
+ { message: 'Currency not found in cookies' },
31
+ { status: 400 }
32
+ );
33
+ }
34
+
35
+ if (!amount || !phone || !email || !name) {
36
+ return NextResponse.json(
37
+ { message: 'Missing required fields' },
38
+ { status: 400 }
39
+ );
40
+ }
41
+
42
+ const upperCurrency = currency.toUpperCase();
43
+
44
+ const currencySpecificHashKey =
45
+ process.env[`TABBY_HASH_KEY_${upperCurrency}`];
46
+ const currencySpecificUrl =
47
+ process.env[`TABBY_EXTENSION_URL_${upperCurrency}`];
48
+
49
+ const hashKey = currencySpecificHashKey || process.env.TABBY_HASH_KEY;
50
+ const extensionUrl = currencySpecificUrl || process.env.TABBY_EXTENSION_URL;
51
+
52
+ if (!hashKey) {
53
+ return NextResponse.json(
54
+ { message: 'TABBY_HASH_KEY environment variable is not set' },
55
+ { status: 500 }
56
+ );
57
+ }
58
+
59
+ if (!extensionUrl) {
60
+ return NextResponse.json(
61
+ { message: 'TABBY_EXTENSION_URL environment variable is not set' },
62
+ { status: 500 }
63
+ );
64
+ }
65
+
66
+ const salt = getRandomString(10);
67
+ const hash = generateHash(salt, amount, email, phone, hashKey);
68
+
69
+ const response = await fetch(`${extensionUrl}/check-availability`, {
70
+ method: 'POST',
71
+ headers: {
72
+ 'Content-Type': 'application/json'
73
+ },
74
+ body: JSON.stringify({
75
+ salt,
76
+ hash,
77
+ payment: {
78
+ amount,
79
+ currency: upperCurrency,
80
+ buyer: {
81
+ phone,
82
+ email,
83
+ name
84
+ }
85
+ }
86
+ })
87
+ });
88
+
89
+ if (!response.ok) {
90
+ const errorData = await response.json();
91
+ return NextResponse.json(errorData, { status: response.status });
92
+ }
93
+
94
+ const data: CheckAvailabilityResponse = await response.json();
95
+
96
+ const isValidHash = verifyResponseHash(
97
+ data.salt,
98
+ data.is_available,
99
+ hashKey,
100
+ data.hash
101
+ );
102
+
103
+ if (!isValidHash) {
104
+ return NextResponse.json(
105
+ { message: 'Invalid response hash' },
106
+ { status: 400 }
107
+ );
108
+ }
109
+
110
+ return NextResponse.json(data);
111
+ } catch (error) {
112
+ console.error('Error checking Tabby availability:', error);
113
+ return NextResponse.json(
114
+ { message: 'Internal server error' },
115
+ { status: 500 }
116
+ );
117
+ }
118
+ }
@@ -0,0 +1,37 @@
1
+ import { api } from '@akinon/next/data/client/api';
2
+ import { setPaymentStepBusy } from '@akinon/next/redux/reducers/checkout';
3
+
4
+ interface CheckTabbyAvailabilityRequest {
5
+ amount: string;
6
+ phone: string;
7
+ email: string;
8
+ name: string;
9
+ }
10
+
11
+ interface CheckTabbyAvailabilityResponse {
12
+ salt: string;
13
+ hash: string;
14
+ is_available: boolean;
15
+ }
16
+
17
+ export const tabbyApi = api.injectEndpoints({
18
+ endpoints: (build) => ({
19
+ checkTabbyAvailability: build.mutation<
20
+ CheckTabbyAvailabilityResponse,
21
+ CheckTabbyAvailabilityRequest
22
+ >({
23
+ query: (body) => ({
24
+ url: '/api/tabby-check-availability/',
25
+ method: 'POST',
26
+ body
27
+ }),
28
+ async onQueryStarted(arg, { dispatch, queryFulfilled }) {
29
+ dispatch(setPaymentStepBusy(true));
30
+ await queryFulfilled;
31
+ dispatch(setPaymentStepBusy(false));
32
+ }
33
+ })
34
+ })
35
+ });
36
+
37
+ export const { useCheckTabbyAvailabilityMutation } = tabbyApi;
@@ -14,12 +14,8 @@ export const getRandomString = (length: number): string => {
14
14
  ).join('');
15
15
  };
16
16
 
17
- export const generateHash = (
18
- hashKey: string,
19
- sessionId: string,
20
- salt: string
21
- ): string => {
22
- const hashStr = `${salt}|${sessionId}|${hashKey}`;
17
+ export const generateHash = (...values: string[]): string => {
18
+ const hashStr = values.join('|');
23
19
  return crypto.createHash('sha512').update(hashStr, 'utf8').digest('hex');
24
20
  };
25
21