@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 +7 -0
- package/package.json +1 -1
- package/readme.md +65 -0
- package/src/pages/TabbyPaymentGateway.tsx +1 -1
- package/src/pages/api/check-availability.ts +118 -0
- package/src/redux/api.ts +37 -0
- package/src/utils/index.ts +2 -6
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
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
|
|
@@ -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
|
+
}
|
package/src/redux/api.ts
ADDED
|
@@ -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;
|
package/src/utils/index.ts
CHANGED
|
@@ -14,12 +14,8 @@ export const getRandomString = (length: number): string => {
|
|
|
14
14
|
).join('');
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
export const generateHash = (
|
|
18
|
-
|
|
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
|
|