strapi-plugin-payone-provider 5.6.9 → 5.6.11
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/README.md +66 -0
- package/admin/src/pages/hooks/useSettings.js +0 -1
- package/admin/src/pages/utils/paymentUtils.js +0 -1
- package/package.json +1 -1
- package/server/bootstrap.js +5 -6
- package/server/controllers/payone.js +18 -0
- package/server/policies/index.js +2 -1
- package/server/policies/is-payone-notification.js +31 -0
- package/server/routes/index.js +11 -0
- package/server/services/payone.js +7 -0
- package/server/services/transactionService.js +1 -3
- package/server/services/transactionStatusService.js +87 -0
- package/server/utils/requestBuilder.js +6 -5
package/README.md
CHANGED
|
@@ -1566,3 +1566,69 @@ For wallet payments (PayPal, Google Pay, Apple Pay), you can specify:
|
|
|
1566
1566
|
|
|
1567
1567
|
- `capturemode: "full"`: Capture the entire preauthorized amount
|
|
1568
1568
|
- `capturemode: "partial"`: Capture less than the preauthorized amount
|
|
1569
|
+
|
|
1570
|
+
---
|
|
1571
|
+
|
|
1572
|
+
## 📢 TransactionStatus Notifications
|
|
1573
|
+
|
|
1574
|
+
The Payone platform provides an asynchronous way of notifying your system of changes to a transaction. These notifications are called "TransactionStatus" and are automatically handled by this plugin.
|
|
1575
|
+
|
|
1576
|
+
### What are TransactionStatus Notifications?
|
|
1577
|
+
|
|
1578
|
+
TransactionStatus notifications are POST requests sent from Payone's servers to your endpoint when transaction status changes occur. This is especially important for:
|
|
1579
|
+
|
|
1580
|
+
- **Redirect Payment Methods**: Verifying that payments were actually completed (prevents fraud)
|
|
1581
|
+
- **Chargeback Processes**: Being notified when customers initiate chargebacks
|
|
1582
|
+
- **Real-time Tracking**: Keeping your system updated with the latest transaction status
|
|
1583
|
+
|
|
1584
|
+
### How It Works
|
|
1585
|
+
|
|
1586
|
+
1. **Payone sends notification** → Your Strapi endpoint receives POST request
|
|
1587
|
+
2. **Plugin verifies request** → Checks IP address, User-Agent, and hash signature
|
|
1588
|
+
3. **Plugin processes notification** → Updates transaction history automatically
|
|
1589
|
+
4. **Plugin responds** → Returns `TSOK` to confirm receipt
|
|
1590
|
+
|
|
1591
|
+
### Endpoint Configuration
|
|
1592
|
+
|
|
1593
|
+
The plugin automatically provides the TransactionStatus endpoint at:
|
|
1594
|
+
|
|
1595
|
+
**URL**: `POST /api/strapi-plugin-payone-provider/transaction-status`
|
|
1596
|
+
|
|
1597
|
+
**No authentication required** - The endpoint is secured by:
|
|
1598
|
+
|
|
1599
|
+
- IP address verification (only Payone IPs allowed)
|
|
1600
|
+
- User-Agent verification (must be "PAYONE FinanceGate")
|
|
1601
|
+
- Hash signature verification (MD5 hash of transaction data)
|
|
1602
|
+
|
|
1603
|
+
### PMI Configuration
|
|
1604
|
+
|
|
1605
|
+
You need to configure this endpoint in your Payone Merchant Interface (PMI):
|
|
1606
|
+
|
|
1607
|
+
1. Log into your Payone Merchant Interface (PMI)
|
|
1608
|
+
2. Navigate to **Configuration** → **Payment Portals** → **[Your Portal]**
|
|
1609
|
+
3. Find the **TransactionStatus Endpoint** setting
|
|
1610
|
+
4. Enter your endpoint URL: `https://yourdomain.com/api/strapi-plugin-payone-provider/transaction-status`
|
|
1611
|
+
5. Save the configuration
|
|
1612
|
+
|
|
1613
|
+
> ⚠️ **Important**: The endpoint must be accessible via HTTPS. Payone will not send notifications to HTTP endpoints.
|
|
1614
|
+
|
|
1615
|
+
### Security Features
|
|
1616
|
+
|
|
1617
|
+
The plugin automatically verifies:
|
|
1618
|
+
|
|
1619
|
+
1. **IP Address**: Only accepts requests from Payone's IP ranges:
|
|
1620
|
+
|
|
1621
|
+
- `185.60.20.0/24`
|
|
1622
|
+
- `54.246.203.105`
|
|
1623
|
+
|
|
1624
|
+
2. **User-Agent**: Must be exactly `"PAYONE FinanceGate"`
|
|
1625
|
+
|
|
1626
|
+
3. **Hash Signature**: Verifies MD5 hash using your Portal Key:
|
|
1627
|
+
|
|
1628
|
+
```
|
|
1629
|
+
MD5(portalid + aid + txid + sequencenumber + price + currency + mode + key)
|
|
1630
|
+
```
|
|
1631
|
+
|
|
1632
|
+
4. **Credentials**: Verifies that `portalid` and `aid` match your configured settings
|
|
1633
|
+
|
|
1634
|
+
> 📖 **Reference**: For more details, see [Payone TransactionStatus Notification Documentation](https://docs.payone.com/integration/response-handling/transactionstatus-notification)
|
|
@@ -137,7 +137,6 @@ const useSettings = () => {
|
|
|
137
137
|
setTestResult(null);
|
|
138
138
|
try {
|
|
139
139
|
const response = await testConnection();
|
|
140
|
-
console.log("response in handleTestConnection:", response.data);
|
|
141
140
|
if (response.data && response.data.success) {
|
|
142
141
|
setTestResult(response.data);
|
|
143
142
|
toggleNotification({
|
package/package.json
CHANGED
package/server/bootstrap.js
CHANGED
|
@@ -22,12 +22,11 @@ module.exports = async ({ strapi }) => {
|
|
|
22
22
|
displayName: "",
|
|
23
23
|
domainName: "",
|
|
24
24
|
merchantIdentifier: "",
|
|
25
|
-
enableCreditCard:
|
|
26
|
-
enablePayPal:
|
|
27
|
-
enableGooglePay:
|
|
28
|
-
enableApplePay:
|
|
29
|
-
|
|
30
|
-
enableSepaDirectDebit: true
|
|
25
|
+
enableCreditCard: false,
|
|
26
|
+
enablePayPal: false,
|
|
27
|
+
enableGooglePay: false,
|
|
28
|
+
enableApplePay: false,
|
|
29
|
+
enableSepaDirectDebit: false
|
|
31
30
|
}
|
|
32
31
|
});
|
|
33
32
|
}
|
|
@@ -295,5 +295,23 @@ module.exports = ({ strapi }) => ({
|
|
|
295
295
|
}
|
|
296
296
|
};
|
|
297
297
|
}
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
async handleTransactionStatus(ctx) {
|
|
301
|
+
try {
|
|
302
|
+
const notificationData = ctx.request.body || {};
|
|
303
|
+
await getPayoneService(strapi).processTransactionStatus(notificationData);
|
|
304
|
+
|
|
305
|
+
ctx.status = 200;
|
|
306
|
+
ctx.body = "TSOK";
|
|
307
|
+
ctx.type = "text/plain";
|
|
308
|
+
console.log(`[Payone TransactionStatus] Responded TSOK`);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.log("[Payone TransactionStatus] Error handling notification:", error);
|
|
311
|
+
ctx.status = 200;
|
|
312
|
+
ctx.body = "TSOK";
|
|
313
|
+
ctx.type = "text/plain";
|
|
314
|
+
}
|
|
298
315
|
}
|
|
316
|
+
|
|
299
317
|
});
|
package/server/policies/index.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = async (policyContext, config, { strapi }) => {
|
|
4
|
+
const { request } = policyContext;
|
|
5
|
+
const userAgent = request.header["user-agent"] || request.header["User-Agent"] || "";
|
|
6
|
+
const clientIp = request.ip || request.connection?.remoteAddress || "";
|
|
7
|
+
|
|
8
|
+
if (userAgent !== "PAYONE FinanceGate") {
|
|
9
|
+
console.log(`[Payone TransactionStatus] Invalid User-Agent: ${userAgent}, IP: ${clientIp}`);
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const isValidIp = (ip) => {
|
|
15
|
+
if (ip.startsWith("185.60.20.")) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (ip === "54.246.203.105") {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (!isValidIp(clientIp)) {
|
|
26
|
+
console.log(`[Payone TransactionStatus] Invalid IP address: ${clientIp}, User-Agent: ${userAgent}`);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return true;
|
|
31
|
+
};
|
package/server/routes/index.js
CHANGED
|
@@ -162,6 +162,17 @@ module.exports = {
|
|
|
162
162
|
auth: false
|
|
163
163
|
}
|
|
164
164
|
},
|
|
165
|
+
|
|
166
|
+
{
|
|
167
|
+
method: "POST",
|
|
168
|
+
path: "/transaction-status",
|
|
169
|
+
handler: "payone.handleTransactionStatus",
|
|
170
|
+
config: {
|
|
171
|
+
policies: ["plugin::strapi-plugin-payone-provider.is-payone-notification"],
|
|
172
|
+
auth: false
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
165
176
|
]
|
|
166
177
|
}
|
|
167
178
|
};
|
|
@@ -5,6 +5,7 @@ const transactionService = require("./transactionService");
|
|
|
5
5
|
const paymentService = require("./paymentService");
|
|
6
6
|
const testConnectionService = require("./testConnectionService");
|
|
7
7
|
const applePayService = require("./applePayService");
|
|
8
|
+
const transactionStatusService = require("./transactionStatusService");
|
|
8
9
|
|
|
9
10
|
module.exports = ({ strapi }) => ({
|
|
10
11
|
// Settings
|
|
@@ -59,5 +60,11 @@ module.exports = ({ strapi }) => ({
|
|
|
59
60
|
|
|
60
61
|
async initializeApplePaySession(params) {
|
|
61
62
|
return await applePayService.initializeApplePaySession(strapi, params);
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// TransactionStatus Notification
|
|
66
|
+
async processTransactionStatus(notificationData) {
|
|
67
|
+
return await transactionStatusService.processTransactionStatus(strapi, notificationData);
|
|
62
68
|
}
|
|
69
|
+
|
|
63
70
|
});
|
|
@@ -29,8 +29,6 @@ const logTransaction = async (strapi, transactionData) => {
|
|
|
29
29
|
transactionData.Error?.CustomerMessage ||
|
|
30
30
|
null,
|
|
31
31
|
body: transactionData || null,
|
|
32
|
-
raw_request: transactionData.raw_request || null,
|
|
33
|
-
raw_response: transactionData.raw_response || transactionData,
|
|
34
32
|
created_at: new Date().toISOString(),
|
|
35
33
|
updated_at: new Date().toISOString()
|
|
36
34
|
};
|
|
@@ -46,7 +44,7 @@ const logTransaction = async (strapi, transactionData) => {
|
|
|
46
44
|
value: transactionHistory
|
|
47
45
|
});
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
console.log(`Transaction logged: ${logEntry}`);
|
|
50
48
|
};
|
|
51
49
|
|
|
52
50
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const crypto = require("crypto");
|
|
4
|
+
const { getPluginStore, getSettings } = require("./settingsService");
|
|
5
|
+
|
|
6
|
+
const verifyHash = (notificationData, portalKey) => {
|
|
7
|
+
const {
|
|
8
|
+
portalid = "",
|
|
9
|
+
aid = "",
|
|
10
|
+
txid = "",
|
|
11
|
+
sequencenumber = "",
|
|
12
|
+
price = "",
|
|
13
|
+
currency = "",
|
|
14
|
+
mode = "",
|
|
15
|
+
} = notificationData;
|
|
16
|
+
|
|
17
|
+
const hashString = `${portalid}${aid}${txid}${sequencenumber}${price}${currency}${mode}${portalKey}`;
|
|
18
|
+
const expectedHash = crypto.createHash("md5").update(hashString).digest("hex");
|
|
19
|
+
|
|
20
|
+
return expectedHash.toLowerCase() === (notificationData.key || "").toLowerCase();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const processTransactionStatus = async (strapi, notificationData) => {
|
|
24
|
+
try {
|
|
25
|
+
const settings = await getSettings(strapi);
|
|
26
|
+
const txid = notificationData.txid;
|
|
27
|
+
|
|
28
|
+
if (!settings || !settings.key) {
|
|
29
|
+
console.log("[Payone TransactionStatus] Settings not found or key missing");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const isValid = verifyHash(notificationData, settings.key);
|
|
34
|
+
if (!isValid) {
|
|
35
|
+
console.log(`[Payone TransactionStatus] Hash verification failed txid: ${txid}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (notificationData.portalid !== settings.portalid || notificationData.aid !== settings.aid) {
|
|
40
|
+
console.log(`[Payone TransactionStatus] Portal ID or AID mismatch txid: ${txid}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const pluginStore = getPluginStore(strapi);
|
|
45
|
+
let transactionHistory = (await pluginStore.get({ key: "transactionHistory" })) || [];
|
|
46
|
+
|
|
47
|
+
const transaction = transactionHistory.find((t) => t.txid === txid || t.id === txid);
|
|
48
|
+
|
|
49
|
+
if (transaction) {
|
|
50
|
+
Object.assign(transaction, {
|
|
51
|
+
...notificationData,
|
|
52
|
+
status: notificationData?.transaction_status,
|
|
53
|
+
txaction: notificationData?.txaction,
|
|
54
|
+
txtime: notificationData?.txtime,
|
|
55
|
+
sequencenumber: notificationData?.sequencenumber,
|
|
56
|
+
balance: notificationData?.balance,
|
|
57
|
+
receivable: notificationData?.receivable,
|
|
58
|
+
price: notificationData?.price,
|
|
59
|
+
amount: notificationData?.price ? parseFloat(notificationData?.price) * 100 : transaction?.amount,
|
|
60
|
+
userid: notificationData?.userid,
|
|
61
|
+
updated_at: new Date().toISOString(),
|
|
62
|
+
body: {
|
|
63
|
+
...transaction?.body,
|
|
64
|
+
...notificationData,
|
|
65
|
+
status: notificationData?.transaction_status
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await pluginStore.set({
|
|
70
|
+
key: "transactionHistory",
|
|
71
|
+
value: transactionHistory,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
console.log(`[Payone TransactionStatus] Successfully updated transaction txid: ${txid}`);
|
|
75
|
+
} else {
|
|
76
|
+
console.log(`[Payone TransactionStatus] Transaction ${txid} not found in history. Notification ignored.`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.log(`[Payone TransactionStatus] Error processing notification: ${error}`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
module.exports = {
|
|
85
|
+
verifyHash,
|
|
86
|
+
processTransactionStatus,
|
|
87
|
+
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const crypto = require("crypto");
|
|
4
4
|
const { normalizeCustomerId } = require("./normalize");
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
const buildClientRequestParams = (settings, params, logger = null) => {
|
|
7
8
|
const requestParams = {
|
|
8
9
|
request: params.request,
|
|
@@ -14,16 +15,16 @@ const buildClientRequestParams = (settings, params, logger = null) => {
|
|
|
14
15
|
...params
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
requestParams.key = crypto
|
|
18
|
-
.createHash("md5")
|
|
19
|
-
.update(settings.portalKey || settings.key)
|
|
20
|
-
.digest("hex");
|
|
21
|
-
|
|
22
18
|
requestParams.customerid = normalizeCustomerId(
|
|
23
19
|
requestParams.customerid,
|
|
24
20
|
logger
|
|
25
21
|
);
|
|
26
22
|
|
|
23
|
+
requestParams.key = crypto
|
|
24
|
+
.createHash("md5")
|
|
25
|
+
.update(settings.portalKey || settings.key)
|
|
26
|
+
.digest("hex");
|
|
27
|
+
|
|
27
28
|
const isCreditCard = requestParams.clearingtype === "cc";
|
|
28
29
|
const enable3DSecure = settings.enable3DSecure !== false;
|
|
29
30
|
|