@akinon/pz-tabby-extension 1.55.0

Sign up to get free protection for your applications and to get access to all the features.
package/.prettierrc ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "bracketSameLine": false,
3
+ "tabWidth": 2,
4
+ "singleQuote": true,
5
+ "jsxSingleQuote": false,
6
+ "bracketSpacing": true,
7
+ "semi": true,
8
+ "useTabs": false,
9
+ "arrowParens": "always",
10
+ "endOfLine": "lf",
11
+ "proseWrap": "never",
12
+ "trailingComma": "none"
13
+ }
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@akinon/pz-tabby-extension",
3
+ "version": "1.55.0",
4
+ "license": "MIT",
5
+ "main": "src/index.tsx",
6
+ "peerDependencies": {
7
+ "react": "^18.0.0",
8
+ "react-dom": "^18.0.0"
9
+ },
10
+ "devDependencies": {
11
+ "@types/node": "^18.7.8",
12
+ "@types/react": "^18.0.17",
13
+ "@types/react-dom": "^18.0.6",
14
+ "prettier": "^3.0.3",
15
+ "react": "^18.2.0",
16
+ "react-dom": "^18.2.0",
17
+ "typescript": "^5.2.2"
18
+ }
19
+ }
package/readme.md ADDED
@@ -0,0 +1,59 @@
1
+ # Tabby Payment Gateway Extension
2
+
3
+ ## Installation
4
+
5
+ There are two ways to install the Tabby Payment Gateway extension:
6
+
7
+ ### 1. Install the npm package using Yarn
8
+
9
+ For the latest version, you can install the package using Yarn:
10
+
11
+ ```bash
12
+ yarn add @akinon/pz-tabby-extension
13
+ ```
14
+
15
+ ### 2. Preferred installation method
16
+
17
+ You can also use the following command to install the extension with the latest plugins:
18
+
19
+ ```bash
20
+ npx @akinon/projectzero@latest --plugins
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ Once the extension is installed, you can easily integrate the Tabby payment gateway into your application. Here's an example of how to use it.
26
+
27
+ 1. Navigate to the `src/app/[commerce]/[locale]/[currency]/payment-gateway/tabby/` directory.
28
+
29
+ 2. Create a file named `page.tsx` and include the following code:
30
+
31
+ ```jsx
32
+ import { TabbyPaymentGateway } from '@akinon/pz-tabby-extension';
33
+
34
+ const TabbyGateway = ({
35
+ searchParams: { sessionId },
36
+ params: { currency }
37
+ }: {
38
+ searchParams: Record<string, string>;
39
+ params: { currency: string };
40
+ }) => (
41
+ <TabbyPaymentGateway
42
+ currency={currency}
43
+ extensionUrl={process.env.TABBY_EXTENSION_URL}
44
+ hashKey={process.env.TABBY_HASH_KEY}
45
+ sessionId={sessionId}
46
+ />
47
+ );
48
+
49
+ export default TabbyGateway;
50
+ ```
51
+
52
+ ## Configuration
53
+
54
+ Add these variables to your `.env` file
55
+
56
+ ```env
57
+ TABBY_EXTENSION_URL=<your_extension_url>
58
+ TABBY_HASH_KEY=<your_hash_key>
59
+ ```
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import { cookies } from 'next/headers';
3
+
4
+ type FormComponentProps = {
5
+ extensionUrl: string;
6
+ sessionId: string;
7
+ hash: string;
8
+ salt: string;
9
+ context: any;
10
+ };
11
+
12
+ const FormComponent = ({
13
+ extensionUrl,
14
+ sessionId,
15
+ hash,
16
+ salt,
17
+ context
18
+ }: FormComponentProps) => {
19
+ const nextCookies = cookies();
20
+
21
+ const extensionUrlWithoutSlash = `${extensionUrl}`.endsWith('/')
22
+ ? extensionUrl.slice(0, -1)
23
+ : extensionUrl;
24
+
25
+ const csrfToken = nextCookies.get('csrftoken')?.value ?? '';
26
+
27
+ return (
28
+ <form
29
+ action={`${extensionUrlWithoutSlash}/form-page/?sessionId=${sessionId}`}
30
+ method="post"
31
+ encType="multipart/form-data"
32
+ id="tabby-extension-form"
33
+ >
34
+ <input type="hidden" name="csrf_token" value={csrfToken} />
35
+ <input type="hidden" name="hash" value={hash} />
36
+ <input type="hidden" name="salt" value={salt} />
37
+ <input type="hidden" name="data" value={JSON.stringify(context)} />
38
+
39
+ <script
40
+ dangerouslySetInnerHTML={{
41
+ __html: "document.getElementById('tabby-extension-form').submit()"
42
+ }}
43
+ />
44
+ </form>
45
+ );
46
+ };
47
+
48
+ export default FormComponent;
package/src/index.tsx ADDED
@@ -0,0 +1 @@
1
+ export * from './pages/TabbyPaymentGateway';
@@ -0,0 +1,104 @@
1
+ import { URLS } from '@akinon/next/data/urls';
2
+ import settings from '@theme/settings';
3
+ import { cookies } from 'next/headers';
4
+ import React from 'react';
5
+ import FormComponent from '../components/FormComponent';
6
+ import {
7
+ fetchData,
8
+ generateHash,
9
+ getOrderStatus,
10
+ getRandomString,
11
+ groupByProductId
12
+ } from '../utils';
13
+
14
+ type TabbyPaymentGatewayProps = {
15
+ sessionId: string;
16
+ currency: string;
17
+ extensionUrl: string;
18
+ hashKey: string;
19
+ };
20
+
21
+ export const TabbyPaymentGateway = async ({
22
+ sessionId,
23
+ currency,
24
+ extensionUrl,
25
+ hashKey
26
+ }: TabbyPaymentGatewayProps) => {
27
+ const nextCookies = cookies();
28
+
29
+ const requestHeaders = {
30
+ Cookie: `osessionid=${nextCookies.get('osessionid')?.value}`,
31
+ 'Content-Type': 'application/json',
32
+ 'x-currency': currency,
33
+ 'X-Requested-With': 'XMLHttpRequest'
34
+ };
35
+
36
+ const [preOrder, userProfile, successOrders, orderHistory, wishlist] =
37
+ await Promise.all([
38
+ fetchData(
39
+ `${settings.commerceUrl}/orders/checkout/?page=OrderNotePage`,
40
+ requestHeaders
41
+ ),
42
+ fetchData(URLS.user.currentUser, requestHeaders),
43
+ fetchData(`${URLS.account.order}?limit=1&status=550`, requestHeaders),
44
+ fetchData(`${URLS.account.order}?limit=10`, requestHeaders),
45
+ fetchData(
46
+ URLS.wishlist.getFavorites({ page: 1, limit: 1 }),
47
+ requestHeaders
48
+ )
49
+ ]);
50
+
51
+ const salt = getRandomString(10);
52
+ const hash = generateHash(hashKey, sessionId, salt);
53
+
54
+ const context = {
55
+ salt,
56
+ hash,
57
+ shipping_address: {
58
+ city: preOrder.pre_order.shipping_address.city.name,
59
+ address: preOrder.pre_order.shipping_address.line,
60
+ zip: preOrder.pre_order.shipping_address.postcode
61
+ },
62
+ order_items: preOrder.pre_order.basket.basketitem_set.map((item: any) => ({
63
+ unit_price: item.unit_price,
64
+ title: item.product.name,
65
+ quantity: item.quantity,
66
+ category: item.product.category.name
67
+ })),
68
+ buyer_history: {
69
+ registered_since: userProfile.date_joined,
70
+ loyalty_level: successOrders.count,
71
+ wishlist_count: wishlist.count,
72
+ is_email_verified: userProfile.is_email_verified,
73
+ is_social_networks_connected: userProfile.is_social_networks_connected,
74
+ is_phone_number_verified: userProfile.attributes?.verified_phone ?? false
75
+ },
76
+ order_history: orderHistory.results.map((order_history: any) => ({
77
+ purchased_at: order_history.created_date,
78
+ amount: order_history.amount,
79
+ payment_method: order_history.card === null ? 'cod' : 'card',
80
+ status: getOrderStatus(order_history.status.value),
81
+ buyer: {
82
+ phone: order_history.billing_address.phone_number,
83
+ email: order_history.billing_address.email,
84
+ name: `${order_history.billing_address.first_name} ${order_history.billing_address.last_name}`
85
+ },
86
+ shipping_address: {
87
+ city: order_history.shipping_address.city.name,
88
+ address: order_history.shipping_address.line,
89
+ zip: order_history.shipping_address.postcode
90
+ },
91
+ order_items: groupByProductId(order_history.orderitem_set)
92
+ }))
93
+ };
94
+
95
+ return (
96
+ <FormComponent
97
+ extensionUrl={extensionUrl}
98
+ sessionId={sessionId}
99
+ hash={hash}
100
+ salt={salt}
101
+ context={context}
102
+ />
103
+ );
104
+ };
@@ -0,0 +1,28 @@
1
+ export enum OrderServiceStatus {
2
+ New = 'new',
3
+ Processing = 'processing',
4
+ Completed = 'complete',
5
+ Refunded = 'refunded',
6
+ Canceled = 'canceled',
7
+ Unknown = 'unknown'
8
+ }
9
+
10
+ export enum OrderCommerceStatus {
11
+ CancellationWaiting = '50',
12
+ Cancelled = '100',
13
+ Waiting = '200',
14
+ PaymentWaiting = '300',
15
+ ConfirmationWaiting = '350',
16
+ Approved = '400',
17
+ Preparing = '450',
18
+ Shipped = '500',
19
+ ShippedAndInformed = '510',
20
+ ReadyForPickup = '520',
21
+ AttemptedDelivery = '540',
22
+ ReviewStarted = '544',
23
+ ReviewWaiting = '545',
24
+ WaitingForPayment = '546',
25
+ Paid = '547',
26
+ Delivered = '550',
27
+ Refunded = '600'
28
+ }
@@ -0,0 +1,66 @@
1
+ import crypto from 'crypto';
2
+ import { OrderCommerceStatus, OrderServiceStatus } from '../types';
3
+
4
+ export const fetchData = async (url: string, requestHeaders: HeadersInit) => {
5
+ const response = await fetch(url, { headers: requestHeaders });
6
+ return response.json();
7
+ };
8
+
9
+ export const getRandomString = (length: number): string => {
10
+ const characters =
11
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
12
+ return Array.from({ length }, () =>
13
+ characters.charAt(Math.floor(Math.random() * characters.length))
14
+ ).join('');
15
+ };
16
+
17
+ export const generateHash = (
18
+ hashKey: string,
19
+ sessionId: string,
20
+ salt: string
21
+ ): string => {
22
+ const hashStr = `${salt}|${sessionId}|${hashKey}`;
23
+ return crypto.createHash('sha512').update(hashStr, 'utf8').digest('hex');
24
+ };
25
+
26
+ export const groupByProductId = (orderItems: any[]) => {
27
+ return Object.values(
28
+ orderItems.reduce((acc, item) => {
29
+ const productId = item.product.pk;
30
+ acc[productId] = acc[productId] || {
31
+ unit_price: item.price,
32
+ title: item.product.name,
33
+ quantity: 0,
34
+ category: item.product.category.name
35
+ };
36
+ acc[productId].quantity += 1;
37
+ return acc;
38
+ }, {})
39
+ );
40
+ };
41
+
42
+ export const getOrderStatus = (
43
+ commerceOrderStatusValue: string
44
+ ): OrderServiceStatus => {
45
+ switch (commerceOrderStatusValue) {
46
+ case OrderCommerceStatus.Waiting:
47
+ case OrderCommerceStatus.PaymentWaiting:
48
+ case OrderCommerceStatus.ConfirmationWaiting:
49
+ return OrderServiceStatus.New;
50
+ case OrderCommerceStatus.Approved:
51
+ case OrderCommerceStatus.Preparing:
52
+ case OrderCommerceStatus.Shipped:
53
+ case OrderCommerceStatus.ShippedAndInformed:
54
+ case OrderCommerceStatus.ReadyForPickup:
55
+ case OrderCommerceStatus.AttemptedDelivery:
56
+ return OrderServiceStatus.Processing;
57
+ case OrderCommerceStatus.Delivered:
58
+ return OrderServiceStatus.Completed;
59
+ case OrderCommerceStatus.Refunded:
60
+ return OrderServiceStatus.Refunded;
61
+ case OrderCommerceStatus.Cancelled:
62
+ return OrderServiceStatus.Canceled;
63
+ default:
64
+ return OrderServiceStatus.Unknown;
65
+ }
66
+ };