mirror of
https://github.com/lubosz/overte.git
synced 2025-04-10 15:57:39 +02:00
Merge pull request #11172 from howard-stearns/asynchronous-commerce
Asynchronous commerce
This commit is contained in:
commit
56d57ea160
7 changed files with 88 additions and 38 deletions
|
@ -32,6 +32,32 @@ Rectangle {
|
|||
color: hifi.colors.baseGray;
|
||||
Hifi.QmlCommerce {
|
||||
id: commerce;
|
||||
onBuyResult: {
|
||||
/*
|
||||
if (buyFailed) {
|
||||
sendToScript({method: 'checkout_cancelClicked', params: itemId});
|
||||
} else {
|
||||
var success = commerce.buy(itemId, parseInt(itemPriceText.text));
|
||||
sendToScript({method: 'checkout_buyClicked', success: success, itemId: itemId, itemHref: itemHref});
|
||||
if (success) {
|
||||
if (urlHandler.canHandleUrl(itemHref)) {
|
||||
urlHandler.handleUrl(itemHref);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (failureMessage.length) {
|
||||
console.log('buy failed', failureMessage);
|
||||
//fixme sendToScript({method: 'checkout_cancelClicked', params: itemId});
|
||||
} else {
|
||||
console.log('buy ok');
|
||||
//fixme sendToScript({method: 'checkout_buyClicked', success: , itemId: itemId, itemHref: itemHref});
|
||||
}
|
||||
}
|
||||
// FIXME: remove these two after testing
|
||||
onBalanceResult: console.log('balance', balance, failureMessage);
|
||||
onInventoryResult: console.log('inventory', inventory, failureMessage);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -340,13 +366,13 @@ Rectangle {
|
|||
width: parent.width/2 - anchors.leftMargin*2;
|
||||
text: "Cancel"
|
||||
onClicked: {
|
||||
sendToScript({method: 'checkout_cancelClicked', params: itemId});
|
||||
sendToScript({method: 'checkout_cancelClicked', params: itemId}); //fixme
|
||||
}
|
||||
}
|
||||
|
||||
// "Buy" button
|
||||
HifiControlsUit.Button {
|
||||
property bool buyFailed: false;
|
||||
property bool buyFailed: false; // fixme
|
||||
id: buyButton;
|
||||
enabled: balanceAfterPurchase >= 0 && !alreadyOwned;
|
||||
color: hifi.buttons.black;
|
||||
|
@ -360,17 +386,7 @@ Rectangle {
|
|||
width: parent.width/2 - anchors.rightMargin*2;
|
||||
text: alreadyOwned ? "Already Owned" : "Buy";
|
||||
onClicked: {
|
||||
if (buyFailed) {
|
||||
sendToScript({method: 'checkout_cancelClicked', params: itemId});
|
||||
} else {
|
||||
var success = commerce.buy(itemId, parseInt(itemPriceText.text));
|
||||
sendToScript({method: 'checkout_buyClicked', success: success, itemId: itemId, itemHref: itemHref});
|
||||
if (success) {
|
||||
if (urlHandler.canHandleUrl(itemHref)) {
|
||||
urlHandler.handleUrl(itemHref);
|
||||
}
|
||||
}
|
||||
}
|
||||
commerce.buy(itemId, parseInt(itemPriceText.text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#include "Ledger.h"
|
||||
#include "CommerceLogging.h"
|
||||
|
||||
bool Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername) {
|
||||
void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername) {
|
||||
QJsonObject transaction;
|
||||
transaction["hfc_key"] = hfc_key;
|
||||
transaction["hfc"] = cost;
|
||||
|
@ -34,32 +34,41 @@ bool Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons
|
|||
|
||||
qCInfo(commerce) << "Transaction:" << QJsonDocument(request).toJson(QJsonDocument::Compact);
|
||||
// FIXME: talk to server instead
|
||||
QStringList keySet{ hfc_key };
|
||||
if (initializedBalance() < cost) return false;
|
||||
if (_inventory.contains(asset_id)) {
|
||||
// This is here more for testing than as a definition of semantics.
|
||||
// When we have popcerts, you will certainly be able to buy a new instance of an item that you already own a different instance of.
|
||||
// I'm not sure what the server should do for now in this project's MVP.
|
||||
return emit buyResult("Already owned.");
|
||||
}
|
||||
if (initializedBalance() < cost) {
|
||||
return emit buyResult("Insufficient funds.");
|
||||
}
|
||||
_balance -= cost;
|
||||
_inventory.push_back(asset_id);
|
||||
return true; // FIXME send to server.
|
||||
emit buyResult("");
|
||||
}
|
||||
|
||||
bool Ledger::receiveAt(const QString& hfc_key) {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
if (!accountManager->isLoggedIn()) {
|
||||
qCWarning(commerce) << "Cannot set receiveAt when not logged in.";
|
||||
return false;
|
||||
emit receiveAtResult("Not logged in");
|
||||
return false; // We know right away that we will fail, so tell the caller.
|
||||
}
|
||||
auto username = accountManager->getAccountInfo().getUsername();
|
||||
qCInfo(commerce) << "Setting default receiving key for" << username;
|
||||
return true; // FIXME send to server.
|
||||
emit receiveAtResult(""); // FIXME: talk to server instead.
|
||||
return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in.
|
||||
}
|
||||
|
||||
int Ledger::balance(const QStringList& keys) {
|
||||
void Ledger::balance(const QStringList& keys) {
|
||||
// FIXME: talk to server instead
|
||||
qCInfo(commerce) << "Balance:" << initializedBalance();
|
||||
return _balance;
|
||||
emit balanceResult(_balance, "");
|
||||
}
|
||||
|
||||
QStringList Ledger::inventory(const QStringList& keys) {
|
||||
void Ledger::inventory(const QStringList& keys) {
|
||||
// FIXME: talk to server instead
|
||||
qCInfo(commerce) << "Inventory:" << _inventory;
|
||||
return _inventory;
|
||||
emit inventoryResult(_inventory, "");
|
||||
}
|
|
@ -21,10 +21,16 @@ class Ledger : public QObject, public Dependency {
|
|||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
bool buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername = "");
|
||||
void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername = "");
|
||||
bool receiveAt(const QString& hfc_key);
|
||||
int balance(const QStringList& keys);
|
||||
QStringList inventory(const QStringList& keys);
|
||||
void balance(const QStringList& keys);
|
||||
void inventory(const QStringList& keys);
|
||||
|
||||
signals:
|
||||
void buyResult(const QString& failureReason);
|
||||
void receiveAtResult(const QString& failureReason);
|
||||
void balanceResult(int balance, const QString& failureReason);
|
||||
void inventoryResult(QStringList inventory, const QString& failureReason);
|
||||
|
||||
private:
|
||||
// These in-memory caches is temporary, until we start sending things to the server.
|
||||
|
|
|
@ -17,29 +17,35 @@
|
|||
|
||||
HIFI_QML_DEF(QmlCommerce)
|
||||
|
||||
bool QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) {
|
||||
QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult);
|
||||
connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult);
|
||||
connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult);
|
||||
}
|
||||
|
||||
void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
QStringList keys = wallet->listPublicKeys();
|
||||
if (keys.count() == 0) {
|
||||
return false;
|
||||
return emit buyResult("Uninitialized Wallet.");
|
||||
}
|
||||
QString key = keys[0];
|
||||
// For now, we receive at the same key that pays for it.
|
||||
bool success = ledger->buy(key, cost, assetId, key, buyerUsername);
|
||||
ledger->buy(key, cost, assetId, key, buyerUsername);
|
||||
// FIXME: until we start talking to server, report post-transaction balance and inventory so we can see log for testing.
|
||||
balance();
|
||||
inventory();
|
||||
return success;
|
||||
}
|
||||
|
||||
int QmlCommerce::balance() {
|
||||
void QmlCommerce::balance() {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
return ledger->balance(wallet->listPublicKeys());
|
||||
ledger->balance(wallet->listPublicKeys());
|
||||
}
|
||||
QStringList QmlCommerce::inventory() {
|
||||
void QmlCommerce::inventory() {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
return ledger->inventory(wallet->listPublicKeys());
|
||||
ledger->inventory(wallet->listPublicKeys());
|
||||
}
|
|
@ -21,13 +21,20 @@ class QmlCommerce : public OffscreenQmlDialog {
|
|||
Q_OBJECT
|
||||
HIFI_QML_DECL
|
||||
|
||||
public:
|
||||
QmlCommerce(QQuickItem* parent = nullptr);
|
||||
|
||||
signals:
|
||||
void buyResult(const QString& failureMessage);
|
||||
// Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and
|
||||
// because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain).
|
||||
void balanceResult(int balance, const QString& failureMessage);
|
||||
void inventoryResult(QStringList inventory, const QString& failureMessage);
|
||||
|
||||
protected:
|
||||
Q_INVOKABLE bool buy(const QString& assetId, int cost, const QString& buyerUsername = "");
|
||||
Q_INVOKABLE int balance();
|
||||
Q_INVOKABLE QStringList inventory();
|
||||
Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = "");
|
||||
Q_INVOKABLE void balance();
|
||||
Q_INVOKABLE void inventory();
|
||||
};
|
||||
|
||||
#endif // hifi_QmlCommerce_h
|
||||
|
|
|
@ -25,7 +25,12 @@ bool Wallet::generateKeyPair() {
|
|||
// FIXME: need private key, too, and persist in file.
|
||||
qCInfo(commerce) << "Generating keypair.";
|
||||
QString key = QUuid::createUuid().toString();
|
||||
_publicKeys.push_back(key);
|
||||
|
||||
_publicKeys.push_back(key); // Keep in memory for synchronous speed.
|
||||
// It's arguable whether we want to change the receiveAt every time, but:
|
||||
// 1. It's certainly needed the first time, when createIfNeeded answers true.
|
||||
// 2. It is maximally private, and we can step back from that later if desired.
|
||||
// 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
return ledger->receiveAt(key);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ class Wallet : public QObject, public Dependency {
|
|||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
// These are currently blocking calls, although they might take a moment.
|
||||
bool createIfNeeded();
|
||||
bool generateKeyPair();
|
||||
QStringList listPublicKeys();
|
||||
|
|
Loading…
Reference in a new issue