Commit 29d5d708 authored by Benoit Lavenier's avatar Benoit Lavenier

[enh] Finish payment API (doc + transfer form) - fix #527

parent e970c46b
......@@ -763,5 +763,64 @@
"END_LOGIN": "This guided visit has <b>ended</b>.<br/><br/>Welcome to the <b>free economy</b>!",
"END_NOT_LOGIN": "This guided visit has <b>ended</b>.<br/><br/>If you wish to join the currency {{currency|capitalize}}, simply click <b>{{'LOGIN.CREATE_ACCOUNT'|translate}}</b> below."
}
},
"API" :{
"COMMON": {
"LINK_DOC": "API documentation",
"LINK_DOC_HELP": "API documentation for developers"
},
"HOME": {
"TITLE": "{{'COMMON.APP_NAME'|translate}} API Documentation",
"MESSAGE": "Welcome to the {{'COMMON.APP_NAME'|translate}} <b>API documentation </b>.<br/>Connect your web site to <a href=\"http://duniter.org\" target=\"_system\">Duniter</a> very easily!",
"MESSAGE_SHORT": "Connect your websites to <a href=\"http://duniter.org\" target=\"_system\">Duniter</a> very easily!",
"DOC_HEADER": "Available services:"
},
"TRANSFER": {
"TITLE": "{{'COMMON.APP_NAME'|translate}} - Online payment",
"TITLE_SHORT": "Online payment",
"SUMMARY": "Order summary:",
"AMOUNT": "Amount:",
"NAME": "Recipient:",
"PUBKEY": "Public key of the recipient:",
"COMMENT": "Order reference:",
"DEMO": {
"SALT": "demo",
"PASSWORD": "demo",
"PUBKEY": "3G28bL6deXQBYpPBpLFuECo46d3kfYMJwst7uhdVBnD1",
"HELP": "<b>Demonstration mode</b>: No payment will actually be sent during this simulation.<br/>Please use credentials: <b>{{'API.TRANSFER.DEMO.SALT'|translate}} / {{'API.TRANSFER.DEMO.PASSWORD'|translate}}</b>",
"BAD_CREDENTIALS": "Invalid credentials.<br/>In demonstration mode, credentials should be: {{'API.TRANSFER.DEMO.SALT'|translate}} / {{'API.TRANSFER.DEMO.PASSWORD'|translate}}"
},
"INFO": {
"SUCCESS_REDIRECTING_WITH_NAME": "Payment sent.<br/>Redirect to <b>{{name}}</b>...",
"SUCCESS_REDIRECTING": "Payment sent.<br/>Redirect to the seller's website...",
"CANCEL_REDIRECTING_WITH_NAME": "Payment cancelled.<br/>Redirect to <b>{{name}}</b>...",
"CANCEL_REDIRECTING": "Payment cancelled.<br/>Redirect to the seller's website..."
},
"ERROR": {
"TRANSFER_FAILED": "Payment failed"
}
},
"DOC": {
"BTN_DEMO": "Try",
"DESCRIPTION_DIVIDER": "Description",
"URL_DIVIDER": "Access URL",
"PARAMETERS_DIVIDER": "Parameters",
"AVAILABLE_PARAMETERS": "Here is the list of parameters and options:",
"SUCCEED": "<i class=\"icon ion-checkmark\"></i> Success!",
"CANCELLED": "<i class=\"icon ion-close\"></i> Canceled by user",
"RESULT": "Result returned by call:",
"TRANSFER": {
"TITLE": "Payments",
"DESCRIPTION": "From a site (eg online marketplace) you can delegate payment in free currency to Cesium API. To do this, simply open a page at the following address:",
"PARAM_PUBKEY_HELP": "Recipient's public key (required)",
"PARAM_AMOUNT": "Amount",
"PARAM_AMOUNT_HELP": "Transaction amount (required)",
"PARAM_COMMENT_HELP": "Reference or comment. You will allow for example to identify the payment in the BlockChain.",
"PARAM_NAME_HELP": "The name of your website. This can be a readable name (eg \"My online site\"), or a web address (eg \"www.MySite.com\").",
"PARAM_REDIRECT_URL_HELP": "Redirect URL. Can contain the following strings, which will be replaced by the values of the transaction: \"{tx}\", \"{hash}\", \"{comment}\" and \"{amount}\".",
"PARAM_CANCEL_URL_HELP": "URL in case of cancellation"
}
}
}
}
......@@ -763,5 +763,64 @@
"END_LOGIN": "This guided visit has <b>ended</b>.<br/><br/>Welcome to the <b>free economy</b>!",
"END_NOT_LOGIN": "This guided visit has <b>ended</b>.<br/><br/>If you wish to join the currency {{currency|capitalize}}, simply click <b>{{'LOGIN.CREATE_ACCOUNT'|translate}}</b> below."
}
},
"API" :{
"COMMON": {
"LINK_DOC": "API documentation",
"LINK_DOC_HELP": "API documentation for developers"
},
"HOME": {
"TITLE": "{{'COMMON.APP_NAME'|translate}} API Documentation",
"MESSAGE": "Welcome to the {{'COMMON.APP_NAME'|translate}} <b>API documentation </b>.<br/>Connect your web site to <a href=\"http://duniter.org\" target=\"_system\">Duniter</a> very easily!",
"MESSAGE_SHORT": "Connect your websites to <a href=\"http://duniter.org\" target=\"_system\">Duniter</a> very easily!",
"DOC_HEADER": "Available services:"
},
"TRANSFER": {
"TITLE": "{{'COMMON.APP_NAME'|translate}} - Online payment",
"TITLE_SHORT": "Online payment",
"SUMMARY": "Order summary:",
"AMOUNT": "Amount:",
"NAME": "Recipient:",
"PUBKEY": "Public key of the recipient:",
"COMMENT": "Order reference:",
"DEMO": {
"SALT": "demo",
"PASSWORD": "demo",
"PUBKEY": "3G28bL6deXQBYpPBpLFuECo46d3kfYMJwst7uhdVBnD1",
"HELP": "<b>Demonstration mode</b>: No payment will actually be sent during this simulation.<br/>Please use credentials: <b>{{'API.TRANSFER.DEMO.SALT'|translate}} / {{'API.TRANSFER.DEMO.PASSWORD'|translate}}</b>",
"BAD_CREDENTIALS": "Invalid credentials.<br/>In demonstration mode, credentials should be: {{'API.TRANSFER.DEMO.SALT'|translate}} / {{'API.TRANSFER.DEMO.PASSWORD'|translate}}"
},
"INFO": {
"SUCCESS_REDIRECTING_WITH_NAME": "Payment sent.<br/>Redirect to <b>{{name}}</b>...",
"SUCCESS_REDIRECTING": "Payment sent.<br/>Redirect to the seller's website...",
"CANCEL_REDIRECTING_WITH_NAME": "Payment cancelled.<br/>Redirect to <b>{{name}}</b>...",
"CANCEL_REDIRECTING": "Payment cancelled.<br/>Redirect to the seller's website..."
},
"ERROR": {
"TRANSFER_FAILED": "Payment failed"
}
},
"DOC": {
"BTN_DEMO": "Try",
"DESCRIPTION_DIVIDER": "Description",
"URL_DIVIDER": "Access URL",
"PARAMETERS_DIVIDER": "Parameters",
"AVAILABLE_PARAMETERS": "Here is the list of parameters and options:",
"SUCCEED": "<i class=\"icon ion-checkmark\"></i> Success!",
"CANCELLED": "<i class=\"icon ion-close\"></i> Canceled by user",
"RESULT": "Result returned by call:",
"TRANSFER": {
"TITLE": "Payments",
"DESCRIPTION": "From a site (eg online marketplace) you can delegate payment in free currency to Cesium API. To do this, simply open a page at the following address:",
"PARAM_PUBKEY_HELP": "Recipient's public key (required)",
"PARAM_AMOUNT": "Amount",
"PARAM_AMOUNT_HELP": "Transaction amount (required)",
"PARAM_COMMENT_HELP": "Reference or comment. You will allow for example to identify the payment in the BlockChain.",
"PARAM_NAME_HELP": "The name of your website. This can be a readable name (eg \"My online site\"), or a web address (eg \"www.MySite.com\").",
"PARAM_REDIRECT_URL_HELP": "Redirect URL. Can contain the following strings, which will be replaced by the values of the transaction: \"{tx}\", \"{hash}\", \"{comment}\" and \"{amount}\".",
"PARAM_CANCEL_URL_HELP": "URL in case of cancellation"
}
}
}
}
......@@ -783,6 +783,20 @@
"NAME": "Destinataire :",
"PUBKEY": "Clé publique du destinaire :",
"COMMENT": "Référence de la commande :",
"DEMO": {
"SALT": "demo",
"PASSWORD": "demo",
"PUBKEY": "3G28bL6deXQBYpPBpLFuECo46d3kfYMJwst7uhdVBnD1",
"HELP": "<b>Mode démonstration</b> : Aucun paiement ne sera réellement envoyé pendant cette simulation.<br/>Veuillez utiliser les identifiants : <b>{{'API.TRANSFER.DEMO.SALT'|translate}} / {{'API.TRANSFER.DEMO.PASSWORD'|translate}}</b>",
"BAD_CREDENTIALS": "Vérifiez votre saisie.<br/>En mode démonstration, les identifiants sont : {{'API.TRANSFER.DEMO.SALT'|translate}} / {{'API.TRANSFER.DEMO.PASSWORD'|translate}}"
},
"INFO": {
"SUCCESS_REDIRECTING_WITH_NAME": "Paiement envoyé.<br/>Redirection vers <b>{{name}}</b>...",
"SUCCESS_REDIRECTING": "Paiement envoyé.<br/>Redirection vers le site du vendeur...",
"CANCEL_REDIRECTING_WITH_NAME": "Paiement annulé.<br/>Redirection vers <b>{{name}}</b>...",
"CANCEL_REDIRECTING": "Paiement annulé.<br/>Redirection vers le site du vendeur..."
},
"ERROR": {
"TRANSFER_FAILED": "Echec du paiement"
}
......@@ -791,19 +805,20 @@
"BTN_DEMO": "Démo",
"DESCRIPTION_DIVIDER": "Description",
"URL_DIVIDER": "URL d'accès",
"PARAMETERS_DIVIDER": "Options",
"AVAILABLE_PARAMETERS": "Voici la liste des options possibles :",
"PARAMETERS_DIVIDER": "Paramètres",
"AVAILABLE_PARAMETERS": "Voici la liste des paramètres et options :",
"SUCCEED": "<i class=\"icon ion-checkmark\"></i> Succès !",
"CANCELLED": "<i class=\"icon ion-close\"></i> Annulé par l'utilisateur",
"RESULT": "Résultat retourné par l'appel :",
"TRANSFER": {
"TITLE": "Paiements",
"DESCRIPTION": "Depuis un site (ex: vente en ligne) vous pouvez déléguer le paiement en monnaie libre à Cesium API. Pour cela, il vous suffit de déclencher l'ouveture d'un page sur l'adresse suivante :",
"PARAM_PUBKEY_HELP": "Clé publique du destinataire (obligatoire)",
"PARAM_AMOUNT": "Montant",
"PARAM_AMOUNT_HELP": "Montant de la transaction (obligatoire)",
"PARAM_COMMENT_HELP": "Référence ou commentaire. Vous permettra par exemple d'identifier le paiement dans la BlockChain.",
"PARAM_NAME_HELP": "Le nom de votre site. Cela peut-etre un nom lisible (\"Mon site en ligne\"), ou encore une pseudo-adresse web (\"MonSite.com\").",
"PARAM_REDIRECT_URL_HELP": "URL de redirection",
"PARAM_NAME_HELP": "Le nom de votre site web. Cela peut-etre un nom lisible (\"Mon site en ligne\"), ou encore une pseudo-adresse web (\"MonSite.com\").",
"PARAM_REDIRECT_URL_HELP": "URL de redirection. Peut contenir les chaines suivantes, qui seront remplacée par les valeurs de la transaction : \"{tx}\", \"{hash}\", \"{comment}\" et \"{amount}\".",
"PARAM_CANCEL_URL_HELP": "URL en cas d'annulation"
}
}
......
......@@ -42,6 +42,7 @@ angular.module('cesium-api', ['ionic', 'ionic-material', 'ngMessages', 'pascalpr
})
.state('api.transfer', {
cache: false,
url: "/payment/:pubkey?name&amount&udAmount&comment&redirect_url&cancel_url&demo",
views: {
'menuContent': {
......@@ -78,8 +79,8 @@ angular.module('cesium-api', ['ionic', 'ionic-material', 'ngMessages', 'pascalpr
comment: 'REF-0111',
name: 'mon-site.com',
pubkey: 'G2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU',
redirect_url: $rootScope.rootPath + '#/app/home?result={comment}&service=payment',
cancel_url: $rootScope.rootPath + '#/app/home?cancel&service=payment'
redirect_url: $rootScope.rootPath + '#/app/home?service=payment&result={comment}%0A{hash}%0A{tx}',
cancel_url: $rootScope.rootPath + '#/app/home?service=payment&cancel'
};
$scope.result = {};
......@@ -100,7 +101,7 @@ angular.module('cesium-api', ['ionic', 'ionic-material', 'ngMessages', 'pascalpr
})
.controller('ApiTransferCtrl', function ($scope, $rootScope, $timeout, $controller, $state, $q, $translate, $filter,
BMA, UIUtils, csCurrency, csTx, csWallet){
BMA, CryptoUtils, UIUtils, csCurrency, csTx, csWallet){
'ngInject';
// Initialize the super class and extend it.
......@@ -141,27 +142,74 @@ angular.module('cesium-api', ['ionic', 'ionic-material', 'ngMessages', 'pascalpr
};
$scope.$on('$ionicView.enter', $scope.enter);
function createDemoWallet() {
function createDemoWallet(authData) {
var demoPubkey = '3G28bL6deXQBYpPBpLFuECo46d3kfYMJwst7uhdVBnD1';
return {
start: function() {
return $q.when();
},
login: function() {
return $q.when({
uid: 'Demo',
pubkey: 'fakeDemoPublicKey'
});
var self = this;
return $translate('API.TRANSFER.DEMO.PUBKEY')
.then(function(pubkey) {
if (!authData || authData.pubkey != '3G28bL6deXQBYpPBpLFuECo46d3kfYMJwst7uhdVBnD1') {
throw {message: 'API.TRANSFER.DEMO.BAD_CREDENTIALS'};
}
self.data = {
keypair: authData.keypair
};
return {
uid: 'Demo',
pubkey: demoPubkey
};
});
},
transfer: function() {
return $q.when();
var self = this;
return BMA.blockchain.current()
.then(function(block) {
var tx = 'Version: '+ BMA.constants.PROTOCOL_VERSION +'\n' +
'Type: Transaction\n' +
'Currency: ' + block.currency + '\n' +
'Blockstamp: ' + block.number + '-' + block.hash + '\n' +
'Locktime: 0\n' + // no lock
'Issuers:\n' +
demoPubkey + '\n' +
'Inputs:\n' +
[$scope.transferData.amount, block.unitbase, 'T', 'FakeId27jQMAf3jqL2fr75ckZ6Jgi9TZL9fMf9TR9vBvG', 0].join(':')+ '\n' +
'Unlocks:\n' +
'0:SIG(0)\n' +
'Outputs:\n' +
[$scope.transferData.amount, block.unitbase, 'SIG(' + $scope.transferData.pubkey + ')'].join(':')+'\n' +
'Comment: '+ ($scope.transferData.comment||'') + '\n';
return CryptoUtils.sign(tx, self.data.keypair)
.then(function(signature) {
var signedTx = tx + signature + "\n";
return CryptoUtils.util.hash(signedTx)
.then(function(txHash) {
return $q.when({
tx: signedTx,
hash: txHash
});
});
})
});
}
};
}
function onLogin(authData) {
if (!authData) return;
var wallet = $scope.demo ? createDemoWallet() : csWallet.instance('api', BMA);
// User cancelled
if (!authData) return $scope.onCancel();
// Avoid multiple click
if ($scope.sending) return;
$scope.sending = true;
var wallet = $scope.demo ? createDemoWallet(authData) : csWallet.instance('api', BMA);
UIUtils.loading.show();
......@@ -177,33 +225,37 @@ angular.module('cesium-api', ['ionic', 'ionic-material', 'ngMessages', 'pascalpr
})
.then(function(confirm) {
if (!confirm) return;
// sent transfer
return UIUtils.loading.show()
.then(function(){
var amount = parseInt($scope.transferData.amount); // remove 2 decimals on quantitative mode
return wallet.transfer($scope.transferData.pubkey, amount, $scope.transferData.comment, false /*always quantitative mode*/);
})
.then(function() {
.then(function(txRes) {
UIUtils.loading.hide();
return true; // sent
return txRes;
})
.catch(UIUtils.onError('API.TRANSFER.ERROR.TRANSFER_FAILED'));
.catch(function(err) {
UIUtils.onError()(err);
return false;
});
})
.then(function(sent) {
if (sent && $scope.transferData.redirect_url) {
return $timeout(function () {
// if iframe: send to parent
if (window.top && window.top.location) {
window.top.location.href = $scope.transferData.redirect_url;
}
else if (parent && parent.document && parent.document.location) {
parent.document.location.href = $scope.transferData.redirect_url;
}
else {
window.location.assign($scope.transferData.redirect_url);
}
}, 3000);
.then(function(txRes) {
if (txRes) {
return $scope.onSuccess(txRes);
}
else {
$scope.sending = false;
}
})
.catch(function(err){
if (err && err === 'CANCELLED') {
return $scope.onCancel();
}
$scope.sending = false;
UIUtils.onError()(err);
});
}
......@@ -222,6 +274,62 @@ angular.module('cesium-api', ['ionic', 'ionic-material', 'ngMessages', 'pascalpr
.then(UIUtils.alert.confirm);
};
$scope.onSuccess = function(txRes) {
if (!$scope.transferData.redirect_url) {
return UIUtils.toast.show('INFO.TRANSFER_SENT');
}
return ($scope.transferData.name ? $translate('API.TRANSFER.INFO.SUCCESS_REDIRECTING_WITH_NAME', $scope.transferData) : $translate('API.TRANSFER.INFO.SUCCESS_REDIRECTING'))
.then(function(message){
return UIUtils.loading.show({template: message});
})
.then(function() {
var url = $scope.transferData.redirect_url;
// Make replacements
url = url.replace(/\{hash\}/g, txRes.hash||'');
url = url.replace(/\{comment\}/g, $scope.transferData.comment||'');
url = url.replace(/\{amount\}/g, $scope.transferData.amount.toString());
url = url.replace(/\{tx\}/g, encodeURI(txRes.tx));
return $scope.redirectToUrl(url, 2500);
});
};
$scope.onCancel = function() {
if (!$scope.transferData.cancel_url) {
// TODO: clean form ?
$scope.formData.salt = undefined;
$scope.formData.password = undefined;
return; // nothing to do
}
return ($scope.transferData.name ? $translate('API.TRANSFER.INFO.CANCEL_REDIRECTING_WITH_NAME', $scope.transferData) : $translate('API.TRANSFER.INFO.CANCEL_REDIRECTING'))
.then(function(message){
return UIUtils.loading.show({template: message});
})
.then(function() {
return $scope.redirectToUrl($scope.transferData.cancel_url, 1500);
});
};
$scope.redirectToUrl = function(url, timeout) {
if (!url) return;
return $timeout(function() {
// if iframe: send to parent
if (window.top && window.top.location) {
window.top.location.href = url;
}
else if (parent && parent.document && parent.document.location) {
parent.document.location.href = url;
}
else {
window.location.assign(url);
}
return UIUtils.loading.hide();
}, timeout||0);
};
/* -- methods need by Login controller -- */
$scope.setForm = function(form) {
......
......@@ -67,7 +67,7 @@ angular.module("cesium.config", [])
}
},
"version": "0.16.1",
"build": "2017-08-21T21:55:39.967Z",
"build": "2017-08-22T06:59:53.332Z",
"newIssueUrl": "https://github.com/duniter/cesium/issues/new?labels=bug"
})
......
......@@ -129,7 +129,7 @@ angular.module('cesium.utils.services', [])
return $translate('COMMON.LOADING')
.then(function(translation){
loadingTextCache = translation;
return showLoading();
return showLoading(options);
});
}
options = options || {};
......
......@@ -936,6 +936,11 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
// API extension
api.data.raise.balanceChanged(data);
api.data.raise.newTx(data);
// Return TX hash (if chained TXs, return the last tx hash) - required by Cesium-API
return {
hash: res.hash
};
});
})
.catch(function(err) {
......@@ -1133,6 +1138,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
output.pending = true;
});
return {
tx: signedTx,
hash: txHash,
sources: newSources
};
......
......@@ -30,6 +30,10 @@
<div class="item item-text-wrap">
<p translate>API.DOC.AVAILABLE_PARAMETERS</p>
<div class="row">
<div class="col col-20 text-italic">pubkey</div>
<div class="col" translate>API.DOC.TRANSFER.PARAM_PUBKEY_HELP</div>
</div>
<div class="row">
<div class="col col-20 text-italic">amount</div>
<div class="col" translate>API.DOC.TRANSFER.PARAM_AMOUNT_HELP</div>
......@@ -61,7 +65,7 @@
<div class="item item-text-wrap" ng-if="result.type === 'payment' && !result.cancelled">
<h2 class="text-right balanced" translate>API.DOC.SUCCEED</h2>
<h4 class="gray" translate>API.DOC.RESULT</h4>
<p class="balanced-100-bg padding dark"> {{result.content}}</p>
<p class="balanced-100-bg padding dark text-keep-lines">{{result.content}}</p>
</div>
<div class="item item-text-wrap" ng-if="result.type === 'payment' && result.cancelled">
<h2 class="text-right assertive" translate>API.DOC.CANCELLED</h2>
......
......@@ -16,7 +16,7 @@
</button>
</ion-nav-buttons>
<ion-content class=" has-header no-padding-xs positive-900-bg text-center">
<ion-content class=" has-header no-padding-xs positive-900-bg">
<br class="hidden-xs"/>
......@@ -28,11 +28,18 @@
<div class="light-bg">
<h2 class="padding-top">
<h2 class="padding-top text-center">
<span class="hidden-xs" translate>API.TRANSFER.TITLE</span>
<span class="visible-xs" translate>API.TRANSFER.TITLE_SHORT</span>
</h2>
<div class="no-padding energized-100-bg" ng-if="demo">
<div class="item item-icon-left item-text-wrap no-border">
<i class="icon ion-information-circled positive" ></i>
<p translate>API.TRANSFER.DEMO.HELP</p>
</div>
</div>
<ng-include src="'templates/login/form_login.html'"></ng-include>
</div>
......@@ -78,12 +85,12 @@
</div>
</div>
<p class="visible-xs visible-sm light padding-top">
<p class="visible-xs visible-sm light padding-top text-center">
{{'COMMON.APP_NAME'|translate}}
- <a href="#" ng-click="showAboutModal($event)">v{{$root.config.version}}</a>
</p>
<p class="hidden-xs hidden-sm gray padding-top">
<p class="hidden-xs hidden-sm gray padding-top text-center">
{{'COMMON.APP_NAME'|translate}} v{{$root.config.version}}
- <a href="#" ng-click="showAboutModal($event)" title="{{'HOME.BTN_ABOUT'|translate}}">{{'HOME.BTN_ABOUT'|translate}}</a>
- <a ui-sref="app.home" target="_blank" title="{{'API.COMMON.LINK_DOC_HELP'|translate}}">{{'API.COMMON.LINK_DOC'|translate}}</a>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment