VulpesOS/gaia/apps/system/js/payment.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

259 lines
8.7 KiB
JavaScript
Executable file

/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* global Service */
// TODO: Blocked by [Payment] UX and visuals for the payment request
// confirmation screen https://github.com/mozilla-b2g/gaia/issues/2692
'use strict';
const kPaymentConfirmationScreen = '../payment.html';
var Payment = {
// We need to save a match between chrome event id and payment
// request id so we can return the appropriate value when a payment
// window is manually closed by the user.
paymentWindows: new Map(),
init: function init() {
// XXX Use payment specific event - Bug 1126812
window.addEventListener('mozChromeEvent', this);
},
dispatchEvent: function dispatchEvent(obj) {
var event = document.createEvent('CustomEvent');
event.initCustomEvent('mozContentEvent', true, true, obj);
window.dispatchEvent(event);
},
handleEvent: function onMozChromeEvent(e) {
var self = this;
// We save the mozChromeEvent identifiers to correlate chrome with content
// events.
var chromeEventId = e.detail.id;
if (!chromeEventId) {
return;
}
// requestId identifies the mozPay DOMRequest identifier.
var requestId = e.detail.requestId;
var frame;
switch (e.detail.type) {
// Chrome asks Gaia to show the payment request confirmation dialog.
case 'open-payment-confirmation-dialog':
var requests = e.detail.paymentRequests;
if (!requests) {
self.dispatchEvent({
id: chromeEventId,
type: 'cancel',
errorMsg: 'INVALID_REQUEST'
});
return;
}
var returnSelection = (function(chromeEventId) {
return function(selection) {
if (!selection) {
return;
}
self.dispatchEvent({
id: chromeEventId,
userSelection: selection
});
};
})(chromeEventId);
// If there is only one request, we skip the confirmation dialog and
// send the request type back to the chrome as a user selection, so
// the payment flow can continue.
if (requests.length == 1) {
returnSelection(requests[0].type, e.detail.id);
return;
}
frame = document.createElement('iframe');
frame.setAttribute('mozbrowser', 'true');
frame.setAttribute('remote', true);
frame.classList.add('screen');
frame.src = kPaymentConfirmationScreen;
frame.addEventListener('mozbrowserloadend', function addReqs(evt) {
var frame = evt.target;
if (!frame || !requests) {
return;
}
// TODO: Temp layout until issue #2692 is solved.
var frameDocument = frame.contentWindow.document;
var requestsList = frameDocument.getElementById('requests')
.getElementsByTagName('ul')[0];
for (var i in requests) {
var requestElement = frameDocument.createElement('li');
var button = frameDocument.createElement('button');
button.setAttribute('value', requests[i].type);
var requestText = 'Pay with ' + requests[i].providerName + '\n' +
requests[i].productName + '\n' +
requests[i].productDescription + '\n' +
requests[i].productPrice[0].amount + ' ' +
requests[i].productPrice[0].currency;
button.appendChild(frameDocument.createTextNode(requestText));
/* jshint -W083 */
button.onclick = function selectRequest() {
// We send the selected request back to Chrome so it can start
// the appropriate payment flow.
returnSelection(this.getAttribute('value'));
};
/* jshint +W083 */
requestElement.appendChild(button);
requestsList.appendChild(requestElement);
}
});
this.openPaymentWindow(frame, requestId, chromeEventId);
break;
// Chrome asks Gaia to show the payment flow according to the
// payment request selected by the user.
case 'open-payment-flow-dialog':
// We create the payment iframe but we don't set its src. Instead
// we simply return the iframe instance to the platform once it is
// inserted in the trusted UI container. At that point the platform
// sets the corresponding payment information in the iframe.
frame = document.createElement('iframe');
frame.setAttribute('mozbrowser', 'true');
frame.classList.add('screen');
// We place the iframe in the DOM so we can get a DocShell and once
// we did that, we give the iframe back to the platform so it can set
// the appropriate flags including the iframe source.
var ontrustedopened = (function(requestId, chromeEventId) {
return function(event) {
if (!event.detail ||
!event.detail.config ||
event.detail.config.requestId != requestId) {
return;
}
window.removeEventListener('trustedopened', ontrustedopened);
// The iframe is already inserted in the trusted UI container at
// this point.
self.dispatchEvent({
id: chromeEventId,
frame: frame
});
};
})(requestId, chromeEventId);
window.addEventListener('trustedopened', ontrustedopened);
this.openPaymentWindow(frame, requestId, chromeEventId);
break;
case 'close-payment-flow-dialog':
var ontrustedclosed = (function(requestId, chromeEventId) {
return function(event) {
if (!event.detail ||
!event.detail.config ||
event.detail.config.requestId != requestId) {
return;
}
window.removeEventListener('trustedclosed', ontrustedclosed);
self.dispatchEvent({
id: chromeEventId
});
};
})(requestId, chromeEventId);
if (!this.removePaymentWindow(requestId)) {
return;
}
window.addEventListener('trustedclosed', ontrustedclosed);
window.dispatchEvent(new CustomEvent('killtrusted', {
detail: {
requestId: requestId,
chromeId: chromeEventId
}
}));
break;
}
},
openPaymentWindow: function openPaymentWindow(frame, requestId,
chromeEventId) {
this.addPaymentWindow(requestId, chromeEventId);
// The payment flow is shown within the trusted UI with the name of
// the mozPay caller application as title.
var app = Service.query('getTopMostWindow');
var title = app ? app.manifest.name : null;
var titlePromise = title ?
Promise.resolve(title) :
navigator.mozL10n.formatValue('payment-flow');
titlePromise.then(value => {
window.dispatchEvent(new CustomEvent('launchtrusted', {
detail: {
name: value,
frame: frame,
requestId: requestId,
chromeId: chromeEventId
}
}));
});
},
addPaymentWindow: function addPaymentWindow(requestId, chromeEventId) {
if (this.paymentWindows.has(requestId)) {
return;
}
// We need to tell the chrome side about the user manually closing the
// payment flow so the payment API can notify to the mozPay caller about
// the flow being canceled.
if (!this.paymentWindows.length) {
window.addEventListener('trustedclosed',
this.onPaymentWindowClosedByUser.bind(this));
}
this.paymentWindows.set(requestId, chromeEventId);
},
removePaymentWindow: function removePaymentWindow(requestId) {
if (!this.paymentWindows.has(requestId)) {
return false;
}
this.paymentWindows.delete(requestId);
if (!this.paymentWindows.size) {
window.removeEventListener('trustedclosed',
this.onPaymentWindowClosedByUser);
}
return true;
},
onPaymentWindowClosedByUser: function onPaymentWindowClosedByUser(event) {
var detail = event.detail;
if (!detail || !detail.config ||
!this.paymentWindows.has(detail.config.requestId)) {
return;
}
var requestId = detail.config.requestId;
this.dispatchEvent({
id: this.paymentWindows.get(requestId),
type: 'cancel',
errorMsg: 'DIALOG_CLOSED_BY_USER'
});
this.removePaymentWindow(requestId);
}
};
// unit tests call init() manually
if (navigator.mozL10n) {
navigator.mozL10n.once(Payment.init.bind(Payment));
}