Skip to content

Commit 9bd61f9

Browse files
committed
WIP: Merge OXID7 with OXID6.3
1 parent 3b085f0 commit 9bd61f9

35 files changed

+748
-134
lines changed

out/src/js/paypal-frontend.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// After receiving PAYER_ACTION_REQUIRED from PayPal
2+
3+
window.OxidPayPalGooglePay3DS = {
4+
handle: async (paypalOrderResponse) => {
5+
// Create new payment data request with 3DS parameters
6+
const paymentDataRequest = await window.OxidPayPalGooglePay.getGooglePaymentDataRequest();
7+
const threeDSPaymentDataRequest = {
8+
...paymentDataRequest,
9+
paymentData: {
10+
tokenizationData: {
11+
type: 'PAYMENT_GATEWAY',
12+
token: paypalOrderResponse.id // Original PayPal order ID
13+
},
14+
// 3DS specific parameters
15+
threeDSData: {
16+
authenticationParameters: {
17+
threeDSRequestData: {
18+
threeDSServerTransID: generateTransactionId(),
19+
challengeWindowSize: "03", // Full screen
20+
messageCategory: "PAYMENT_AUTHENTICATION"
21+
}
22+
}
23+
}
24+
}
25+
};
26+
27+
try {
28+
// This triggers the 3DS popup
29+
const paymentData = await paymentClient.loadPaymentData(threeDSPaymentDataRequest);
30+
31+
// After successful 3DS authentication
32+
// Update PayPal order with new payment token
33+
const updateOrderResponse = await fetch(`/v2/checkout/orders/${paypalOrderResponse.id}`, {
34+
method: 'PATCH',
35+
headers: {
36+
'Content-Type': 'application/json'
37+
},
38+
body: JSON.stringify([{
39+
op: 'replace',
40+
path: '/payment_source/google_pay/card/authentication_result',
41+
value: {
42+
liability_shift: "POSSIBLE",
43+
three_d_secure: {
44+
authentication_status: "Y",
45+
enrollment_status: "Y"
46+
}
47+
}
48+
}])
49+
});
50+
51+
// Finally capture the order
52+
if (updateOrderResponse.ok) {
53+
const captureResponse = await fetch(`/v2/checkout/orders/${paypalOrderResponse.id}/capture`, {
54+
method: 'POST'
55+
});
56+
return captureResponse;
57+
}
58+
} catch (error) {
59+
// Handle 3DS or payment errors
60+
console.error('3DS Authentication failed:', error);
61+
throw error;
62+
}
63+
}
64+
};
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
window.OxidPayPalGooglePay = {
2+
3+
baseRequest: {
4+
apiVersion: 2,
5+
apiVersionMinor: 0,
6+
},
7+
paymentsClient: null,
8+
allowedPaymentMethods: null,
9+
merchantInfo: null,
10+
googlePayContainer: null,
11+
buttonId: null,
12+
token: null,
13+
selfLink: null,
14+
useGooglePayAddress: null,
15+
isSandbox: null,
16+
merchantName: null,
17+
totalPrice: null,
18+
currency: null,
19+
deliveryAddressMD5: null,
20+
language: null,
21+
loadingContainer: null,
22+
init: async function () {
23+
this.googlePayContainer = document.getElementById('oscpaypal_googlepay');
24+
if (this.googlePayContainer) {
25+
this.buttonId = this.googlePayContainer.dataset.buttonId;
26+
this.token = this.googlePayContainer.dataset.token;
27+
this.selfLink = this.googlePayContainer.dataset.selfLink;
28+
this.useGooglePayAddress = !!Number(this.googlePayContainer.dataset.useGooglePayAddress);
29+
this.isSandbox = !!Number(this.googlePayContainer.dataset.isSandbox);
30+
this.merchantName = this.googlePayContainer.dataset.merchantName;
31+
this.totalPrice = this.googlePayContainer.dataset.totalPrice;
32+
this.currency = this.googlePayContainer.dataset.currency;
33+
this.deliveryAddressMD5 = this.googlePayContainer.dataset.deliveryAddressMd5;
34+
this.language = this.googlePayContainer.dataset.language;
35+
let elements = document.getElementsByClassName(this.googlePayContainer.dataset.loadingContainerClassName);
36+
this.loadingContainer = elements[0];
37+
38+
await window.googlePayReady;
39+
this.onGooglePayLoaded();
40+
}
41+
},
42+
getGoogleIsReadyToPayRequest: function (allowedPaymentMethods) {
43+
return Object.assign({}, this.baseRequest, {
44+
allowedPaymentMethods: allowedPaymentMethods
45+
});
46+
},
47+
48+
/* Fetch Default Config from PayPal via PayPal SDK */
49+
getGooglePayConfig: async function () {
50+
if (this.allowedPaymentMethods == null || this.merchantInfo == null) {
51+
const googlePayConfig = await paypal.Googlepay().config();
52+
this.allowedPaymentMethods = googlePayConfig.allowedPaymentMethods;
53+
this.merchantInfo = googlePayConfig.merchantInfo;
54+
this.merchantInfo.merchantName = this.merchantName;
55+
}
56+
return {
57+
allowedPaymentMethods: this.allowedPaymentMethods,
58+
merchantInfo: this.merchantInfo
59+
};
60+
},
61+
62+
/* Configure support for the Google Pay API */
63+
getGooglePaymentDataRequest: async function () {
64+
const paymentDataRequest = Object.assign({}, this.baseRequest);
65+
const {allowedPaymentMethods, merchantInfo} = await this.getGooglePayConfig();
66+
67+
paymentDataRequest.transactionInfo = this.getGoogleTransactionInfo();
68+
paymentDataRequest.allowedPaymentMethods = allowedPaymentMethods;
69+
paymentDataRequest.merchantInfo = merchantInfo;
70+
paymentDataRequest.callbackIntents = ["PAYMENT_AUTHORIZATION"];
71+
paymentDataRequest.emailRequired = true;
72+
paymentDataRequest.shippingAddressRequired = this.useGooglePayAddress;
73+
paymentDataRequest.shippingAddressParameters = {'phoneNumberRequired': true};
74+
75+
return paymentDataRequest;
76+
},
77+
78+
onPaymentAuthorized: function (paymentData) {
79+
return new Promise(function (resolve) {
80+
this.processPayment(paymentData)
81+
.then(function () {
82+
resolve({transactionState: "SUCCESS"});
83+
})
84+
.catch(function () {
85+
resolve({transactionState: "ERROR"});
86+
});
87+
}.bind(this));
88+
},
89+
90+
getGooglePaymentsClient: function () {
91+
if (this.paymentsClient === null) {
92+
this.paymentsClient = new google.payments.api.PaymentsClient({
93+
environment: this.isSandbox ? "TEST" : "PRODUCTION",
94+
paymentDataCallbacks: {
95+
onPaymentAuthorized: this.onPaymentAuthorized.bind(this),
96+
},
97+
});
98+
}
99+
return this.paymentsClient;
100+
},
101+
onGooglePayLoaded: async function () {
102+
if (window.OxidPayPal && window.OxidPayPal.isSDKLoaded()) {
103+
const paymentsClient = this.getGooglePaymentsClient();
104+
const {allowedPaymentMethods} = await this.getGooglePayConfig();
105+
paymentsClient
106+
.isReadyToPay(this.getGoogleIsReadyToPayRequest(allowedPaymentMethods))
107+
.then(function (response) {
108+
if (response.result) {
109+
this.loadingContainer.style.display = 'none';
110+
this.addGooglePayButton();
111+
}
112+
}.bind(this))
113+
.catch(function (err) {
114+
console.error(err);
115+
});
116+
} else {
117+
window.setTimeout(this.onGooglePayLoaded.bind(this), 500);
118+
}
119+
},
120+
121+
addGooglePayButton: function () {
122+
const paymentsClient = this.getGooglePaymentsClient();
123+
const button = paymentsClient.createButton({
124+
buttonType: 'buy',
125+
buttonLocale: this.language,
126+
onClick: this.onGooglePaymentButtonClicked.bind(this),
127+
});
128+
document.getElementById("oscpaypal_googlepay").appendChild(button);
129+
},
130+
131+
getGoogleTransactionInfo: function () {
132+
return {
133+
currencyCode: this.currency,
134+
totalPriceStatus: "FINAL",
135+
totalPrice: this.totalPrice,
136+
totalPriceLabel: "Total",
137+
};
138+
},
139+
140+
onGooglePaymentButtonClicked: async function () {
141+
const paymentDataRequest = await this.getGooglePaymentDataRequest();
142+
paymentDataRequest.transactionInfo = this.getGoogleTransactionInfo();
143+
const paymentsClient = this.getGooglePaymentsClient();
144+
paymentsClient.loadPaymentData(paymentDataRequest);
145+
},
146+
147+
processPayment: async function (paymentData) {
148+
try {
149+
const createOrderUrl = this.selfLink + '&cl=oscpaypalproxy&fnc=createGooglepayOrder&paymentid=oscpaypal_googlepay&context=continue&stoken=' + this.token;
150+
151+
const {id: orderId, status, links} = await fetch(createOrderUrl, {
152+
method: "POST",
153+
headers: {"Content-Type": "application/json"},
154+
body: JSON.stringify(paymentData),
155+
}).then((res) => res.json());
156+
157+
const hateoasLinks = new OxidPayPalHateoasLinks();
158+
const approveLink = hateoasLinks.getApproveLink(links);
159+
160+
if (approveLink) {
161+
window.location.href = approveLink;
162+
} else if (status === "APPROVED") {
163+
/* Capture the Order */
164+
this.captureOrder(orderId);
165+
return {transactionState: "SUCCESS"};
166+
} else if (status === "PAYER_ACTION_REQUIRED") {
167+
console.log("==== Confirm Payment Completed Payer Action Required =====");
168+
this.googlePayUserActionRequired(orderId);
169+
} else {
170+
console.error("Payment was not approved");
171+
return {transactionState: "ERROR"};
172+
}
173+
} catch (err) {
174+
return {
175+
transactionState: "ERROR",
176+
error: {
177+
message: err.message,
178+
},
179+
};
180+
}
181+
},
182+
googlePayUserActionRequired: function (orderId) {
183+
paypal
184+
.Googlepay()
185+
.initiatePayerAction({ orderId: orderId })
186+
.then(async () => {
187+
console.log("===== Payer Action Completed =====");
188+
await this.createOxidOrder(orderId);
189+
});
190+
},
191+
createOxidOrder: async function (orderId) {
192+
const url = this.selfLink + '&cl=order&fnc=createGooglePayOrder&context=continue&stoken=' + this.token + '&sDeliveryAddressMD5=' + this.deliveryAddressMD5;
193+
createData = new FormData();
194+
createData.append('orderID', orderId);
195+
fetch(url, {
196+
method: 'POST',
197+
body: createData
198+
}).then(function (res) {
199+
return res.json();
200+
}).then(function (data) {
201+
if (data.status === "ERROR") {
202+
location.reload();
203+
}
204+
});
205+
},
206+
captureOrder: async function (orderId) {
207+
captureData = new FormData();
208+
captureData.append('orderID', orderId);
209+
await fetch(this.selfLink + '&cl=order&fnc=captureGooglePayOrder&context=continue&stoken=' + this.token + '&sDeliveryAddressMD5=' + this.deliveryAddressMD5, {
210+
method: 'post',
211+
body: captureData
212+
}).then(function (res) {
213+
return res.json();
214+
}).then(function (data) {
215+
console.log("==== Capture Order Completed ====");
216+
var goNext = Array.isArray(data.location) && data.location[0];
217+
218+
window.location.href = this.selfLink + goNext;
219+
if (data.status === "ERROR") {
220+
location.reload();
221+
}
222+
}.bind(this)).catch(reason => {
223+
console.error(reason);
224+
});
225+
}
226+
};
227+
228+
OxidPayPalGooglePay.init();
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
(function () {
2+
'use strict';
3+
4+
function OxidPayPalHateoasLinks() {
5+
}
6+
/**
7+
* @typedef {Object} Link
8+
* @property {string} rel - The relation type.
9+
* @property {string} href - The link URL.
10+
*/
11+
12+
/**
13+
* Process an array of links.
14+
* @param {Link[]} links - An array of link objects.
15+
*
16+
* @returns {string|null}
17+
*/
18+
OxidPayPalHateoasLinks.prototype.getApproveLink = function (links) {
19+
const approveHateoasLink = links.find(link => link.rel === 'approve');
20+
21+
if (approveHateoasLink) {
22+
return approveHateoasLink.href;
23+
}
24+
25+
return null;
26+
};
27+
28+
window.OxidPayPalHateoasLinks = OxidPayPalHateoasLinks;
29+
})();
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
window.OxidPayPal = {
2+
sdkLoaded: false,
3+
onSDKLoaded: function () {
4+
this.sdkLoaded = true;
5+
},
6+
isSDKLoaded: function () {
7+
return this.sdkLoaded;
8+
}
9+
};

resources/grunt/concat.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ module.exports = {
1010
"node_modules/jquery/dist/jquery.js",
1111
"node_modules/popper.js/dist/umd/popper.js",
1212
"node_modules/bootstrap/dist/js/bootstrap.js"
13+
],
14+
"../out/src/js/paypal-frontend.min.js": [
15+
"build/js/paypal-frontend-paypal.js",
16+
"build/js/paypal-frontend-googlepay.js",
17+
"build/js/paypal-frontend-googlepay-3ds.js",
18+
"build/js/paypal-frontend-hateoaslinks.js",
1319
]
1420
}
1521
}

resources/grunt/jshint.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ module.exports = {
77
browser: true,
88
globals: {
99
jQuery: true
10-
}
10+
},
11+
esversion: 9
1112
},
1213
moduleproduction: {
1314
src: [

0 commit comments

Comments
 (0)