buycatalog.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. #include "buycatalog.h"
  2. #include "productinfo.h"
  3. #include "mainwindow.h"
  4. #include "DownloadManager.h"
  5. #include <QtCore/QFile>
  6. #include <QtCore/QTextStream>
  7. #include <QtGui/QApplication>
  8. #include <QtCore/QDebug>
  9. #include <QtCore/QMetaType>
  10. #include <QtGui/QMessageBox>
  11. #include <QProgressDialog>
  12. #include <QSettings>
  13. #include <QDir>
  14. Q_DECLARE_METATYPE(IAPClient::ProductDataList) //to be able to transfer data this type with Qt::QueuedConnection
  15. #define INVALID_VALUE -1
  16. #define KSettingCompanyName "NokiaProjects"
  17. #define KSettingApplicationName "BuyAndDownloadExample"
  18. #define KSettingUserDighestKey "accountdigest"
  19. /*
  20. * BuyCatalog - a QDialog inherited class wich will display a list of available
  21. * products allowing the user to trigger the In-Application Purchase experience
  22. * at a click of a button.
  23. *
  24. * This constuctor reads a list of product IDs from the catalog.dat file and
  25. * asks for the corresponding product data (IAPClient::ProductData) which the
  26. * API will fetch from Ovi.
  27. */
  28. BuyCatalog::BuyCatalog(QWidget *parent)
  29. : QDialog(parent),
  30. productsRequested(0),
  31. current_requestId(INVALID_VALUE),
  32. infoDialog(NULL)
  33. {
  34. // required so that IAPClient::ProductData can be queued in the signal
  35. qRegisterMetaType<IAPClient::ProductDataHash>("IAPClient::ProductDataHash");
  36. qRegisterMetaType<IAPClient::UserAndDeviceDataHash>("IAPClient::ProductDataHash");
  37. iap_client = new IAPClient(this);
  38. // connect IAP API's signals to app's slots
  39. connect(iap_client, SIGNAL(productDataReceived( int, QString, IAPClient::ProductDataHash)), this,
  40. SLOT(productDataReceived(int, QString, IAPClient::ProductDataHash)), Qt::QueuedConnection);
  41. connect(iap_client, SIGNAL(purchaseCompleted( int , QString, QString)), this,
  42. SLOT(purchaseCompleted( int , QString, QString)),Qt::QueuedConnection);
  43. connect(iap_client, SIGNAL(purchaseFlowFinished(int)), this, SLOT(purchaseFlowFinished(int)), Qt::QueuedConnection);
  44. connect(iap_client, SIGNAL(restorableProductsReceived( int, QString, IAPClient::ProductDataList)), this,
  45. SLOT(restorableProductsReceived( int, QString, IAPClient::ProductDataList)));
  46. connect(iap_client, SIGNAL(restorationCompleted(int, QString, QString)), this,
  47. SLOT(restorationCompleted(int, QString, QString)),Qt::QueuedConnection);
  48. ui.setupUi(this);
  49. ui.listWidget->setStyleSheet(QString::fromUtf8("QListWidget{ background-color: grey; border-style: outset; border-width: 1px; border-radius: 1px; border-color: black; selection-color: black; selection-background-color: red; }" \
  50. "QPushButton{ background-color: green; border-style: outset; border-width: 2px; border-radius: 10px; border-color: beige; font: bold 14px; min-width: 4em; max-width:5em; padding: 6px; }" \
  51. "QPushButton:pressed { background-color: rgb(224, 0, 0); border-style: inset;}"));
  52. connect(ui.listWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(moreProductInfo(QListWidgetItem*)));
  53. Q_ASSERT(parent);
  54. connect(ui.backButton, SIGNAL(clicked()), parent, SLOT(catalogClosed()));
  55. connect(ui.restoreButton, SIGNAL(clicked()), this, SLOT(restoreProducts()));
  56. ui.restoreButton->setEnabled(false);
  57. busyIndicator = new QProgressDialog("", "", 1, 1, this);
  58. busyIndicator->setLabelText("...please wait...");
  59. busyIndicator->setCancelButton(NULL);
  60. busyIndicator->setWindowModality(Qt::WindowModal);
  61. dm = new DownloadManager(this);
  62. connect(dm, SIGNAL(productListComplete()), this, SLOT(productListComplete()));
  63. connect(dm, SIGNAL(productComplete(QByteArray*)), this, SLOT(productComplete(QByteArray*)));
  64. connect(iap_client, SIGNAL(userAndDeviceDataReceived(int,
  65. QString, IAPClient::UserAndDeviceDataHash)),
  66. this, SLOT(userAndDeviceDataReceived(int,
  67. QString, IAPClient::UserAndDeviceDataHash)));
  68. QSettings settings(KSettingCompanyName, KSettingApplicationName);
  69. userAuth = settings.value(KSettingUserDighestKey, "").toString();
  70. if(isReadyToBuy()){
  71. dm->getProductList(products);
  72. }else{
  73. current_requestId = iap_client->getUserAndDeviceId(IAPClient::OnlyInSilentAuthentication);
  74. }
  75. }
  76. /*
  77. *
  78. *
  79. *
  80. */
  81. BuyCatalog::~BuyCatalog()
  82. {
  83. delete infoDialog;
  84. delete iap_client;
  85. }
  86. /*
  87. *
  88. *
  89. *
  90. */
  91. void BuyCatalog::infoDialogClosed(int result)
  92. {
  93. if(result==QDialog::Accepted)
  94. {
  95. IAPClient::ProductDataHash info = available_products[current_product_index];
  96. current_productId = info.value("id").toString();
  97. dm->getProduct(userAuth, current_productId);
  98. }
  99. }
  100. /*
  101. *
  102. *
  103. *
  104. */
  105. void BuyCatalog::moreProductInfo(QListWidgetItem* item)
  106. {
  107. if(isIAPclientBusy())
  108. return;
  109. qDebug() << "moreProductInfo";
  110. if(infoDialog!=NULL)
  111. {
  112. disconnect(infoDialog, SIGNAL(finished(int)), this, SLOT(infoDialogClosed(int)));
  113. delete infoDialog;
  114. infoDialog = NULL;
  115. }
  116. current_product_index = ui.listWidget->row(item);
  117. IAPClient::ProductDataHash info = available_products[current_product_index];
  118. // create a dialog for showing detailed product info
  119. infoDialog = new ProductInfo(info.value("info").toString(), info.value("description").toString(), info.value("price").toString(), this);
  120. // get notified when the product info dialog is closed
  121. connect(infoDialog, SIGNAL(finished(int)), this, SLOT(infoDialogClosed(int)));
  122. // show the dialog
  123. infoDialog->showFullScreen();
  124. }
  125. /*
  126. * Returns true if the product files can be accessed or false if the purchase/restore is needed
  127. */
  128. bool BuyCatalog::isProductActivated(QString product)
  129. {
  130. // TODO
  131. return false;
  132. }
  133. /*
  134. *
  135. *
  136. *
  137. */
  138. void BuyCatalog::buyProduct()
  139. {
  140. if(isIAPclientBusy() || !isReadyToBuy())
  141. return;
  142. busyIndicator->show();
  143. qDebug() << "buyProduct";
  144. // locate the list item which holds the button that send a signal to this slot
  145. QWidget *clickedWidget = qobject_cast<QWidget *>(sender()->parent());
  146. for (int index = 0; index < ui.listWidget->count(); index++)
  147. {
  148. QListWidgetItem *runningItem = ui.listWidget->item(index);
  149. QWidget *widget = ui.listWidget->itemWidget(runningItem);
  150. if (clickedWidget == widget)
  151. {
  152. IAPClient::ProductDataHash info = available_products[index];
  153. // issue a buy request for the corresponding product
  154. // if the user has paid for this content already, restoration of the license is needed
  155. current_productId = info.value("id").toString();
  156. dm->getProduct(userAuth,current_productId);
  157. break;
  158. }
  159. }
  160. }
  161. /*
  162. *
  163. *
  164. *
  165. */
  166. void BuyCatalog::productDataReceived( int requestId, QString status, IAPClient::ProductDataHash productData )
  167. {
  168. if(!resetIAPrequest(requestId))
  169. return;
  170. qDebug() << "productDataReceived";
  171. //Q_ASSERT(requestId == current_requestId);
  172. if(QString::compare(status, "OK", Qt::CaseInsensitive)==0)
  173. {
  174. available_products.append(productData);
  175. // add a new item to the products catalog UI's list
  176. QListWidgetItem *item = new QListWidgetItem();
  177. ui.listWidget->addItem(item);
  178. QLabel *labelTitle = new QLabel(productData.value("info").toString());
  179. QLabel *labelPrice = new QLabel(productData.value("price").toString());
  180. QHBoxLayout *layoutTitleAndPrice= new QHBoxLayout();
  181. layoutTitleAndPrice->addWidget(labelTitle);
  182. layoutTitleAndPrice->addWidget(labelPrice);
  183. QPushButton *button = new QPushButton("Buy...");
  184. QLabel *labelShort = new QLabel(productData.value("shortdescription").toString());
  185. QHBoxLayout *layoutShortBuy= new QHBoxLayout();
  186. layoutShortBuy->addWidget(labelShort);
  187. layoutShortBuy->addWidget(button);
  188. QVBoxLayout *layoutItem = new QVBoxLayout();
  189. layoutItem->addItem(layoutTitleAndPrice);
  190. layoutItem->addItem(layoutShortBuy);
  191. QWidget *widget = new QWidget();
  192. widget->setLayout(layoutItem);
  193. item->setSizeHint(widget->sizeHint());
  194. ui.listWidget->setItemWidget(item, widget);
  195. // if multiple products are available, let's highlight the one that triggered the shopping session
  196. if( productData.value("id").toString() == defaultProduct)
  197. ui.listWidget->setCurrentItem(item);
  198. connect(button, SIGNAL(clicked()), this, SLOT(buyProduct()));
  199. }
  200. else // what are all the possible status messages?
  201. {
  202. // the product is not available for one reason or another?
  203. // probably you can ignore this
  204. // or report it to your server for analysis
  205. qDebug() << "Requested product could not be retrived: " << status;
  206. }
  207. // if we don't have any more products ...
  208. if(productsRequested == products.count())
  209. {
  210. ui.progressBar->setVisible(false);
  211. // have we got any product data?
  212. if(!available_products.count())
  213. {
  214. QMessageBox message;
  215. message.setText("No products available. Please try again later!");
  216. message.exec();
  217. // close catalog
  218. reject();
  219. }
  220. }
  221. else
  222. {
  223. // let's look for the next product's data
  224. requestNextProduct();
  225. }
  226. }
  227. /*
  228. *
  229. *
  230. *
  231. */
  232. void BuyCatalog::requestNextProduct()
  233. {
  234. qDebug() << "requestNextProduct";
  235. if(productsRequested < products.count())
  236. {
  237. QString prod = products[productsRequested++];
  238. current_requestId = iap_client->getProductData(prod);
  239. qDebug() << "+ Request for product" << prod << " returned id: " << current_requestId;
  240. ui.progressBar->setValue(productsRequested-1);
  241. }
  242. else
  243. ui.progressBar->setValue(productsRequested);
  244. if(productsRequested == products.count() - 1){
  245. ui.restoreButton->setEnabled(true);
  246. }
  247. }
  248. /*
  249. *
  250. *
  251. *
  252. */
  253. void BuyCatalog::purchaseCompleted( int requestId, QString status, QString purchaseTicket )
  254. {
  255. qDebug() << "purchaseCompleted with status: " << status;
  256. if(!resetIAPrequest(requestId))
  257. return;
  258. if(QString::compare(status, "OK", Qt::CaseInsensitive)==0
  259. || QString::compare(status, "RestorableProduct", Qt::CaseInsensitive)==0)
  260. {
  261. dm->getProduct(userAuth, current_productId, &purchaseTicket);
  262. }
  263. else
  264. {
  265. // some error
  266. // error message already displayed by In-Application Purchase UI, may also be reflected in app's UI.
  267. }
  268. }
  269. /*
  270. *
  271. *
  272. *
  273. */
  274. void BuyCatalog::userAndDeviceDataReceived(int requestId, QString status,
  275. IAPClient::UserAndDeviceDataHash response)
  276. {
  277. if(!resetIAPrequest(requestId))
  278. return;
  279. qDebug() << "userAndDeviceDataReceived : " << status;
  280. if(status.compare("SilentOperationFailed") == 0){
  281. current_requestId = iap_client->getUserAndDeviceId(IAPClient::DefaultAuthentication);
  282. // NOTE: you have to sign in to OVI Store client
  283. // to have getUserAndDeviceId(IAPClient::OnlyInSilentAuthentication)
  284. // working properly. Otherwise use explicit user authentication
  285. return;
  286. }
  287. if(status.compare("OK") != 0)
  288. return;
  289. userAuth = response.value("accountdigest").toString();
  290. QSettings settings(KSettingCompanyName, KSettingApplicationName);
  291. settings.setValue(KSettingUserDighestKey, userAuth);
  292. products.clear();
  293. dm->getProductList(products);
  294. }
  295. /*
  296. *
  297. *
  298. *
  299. */
  300. void BuyCatalog::restorableProductsReceived( int requestId, QString status,
  301. IAPClient::ProductDataList items )
  302. {
  303. if(!resetIAPrequest(requestId))
  304. return;
  305. qDebug() << "restorableProductsReceived with status: " << status;
  306. current_requestId = INVALID_VALUE;
  307. if(!items.empty()){
  308. restorableProductItems.append(items);
  309. current_productId = restorableProductItems.takeFirst().value("id").toString();
  310. current_requestId = iap_client->restoreProduct(current_productId);
  311. }
  312. if(current_requestId == INVALID_VALUE){
  313. busyIndicator->close();
  314. ui.backButton->click();
  315. }else{
  316. ui.progressBar->setRange(0, restorableProductItems.count() + 1);
  317. ui.progressBar->setTextVisible(false);
  318. ui.progressBar->setVisible(true);
  319. }
  320. }
  321. /*
  322. *
  323. *
  324. *
  325. */
  326. void BuyCatalog::restorationFlowFinished( int requestId )
  327. {
  328. resetIAPrequest(requestId);
  329. }
  330. /*
  331. *
  332. *
  333. *
  334. */
  335. void BuyCatalog::restorationCompleted( int requestId, QString status, QString purchaseTicket )
  336. {
  337. if(!resetIAPrequest(requestId))
  338. return;
  339. qDebug() << "restorationCompleted with status: " << status;
  340. purchaseCompleted(requestId, status, purchaseTicket);
  341. if(restorableProductItems.empty()){
  342. busyIndicator->close();
  343. ui.backButton->click();
  344. }else{
  345. ui.progressBar->setValue(ui.progressBar->value() + 1);
  346. current_productId = restorableProductItems.takeFirst().value("id").toString();
  347. current_requestId = iap_client->restoreProduct(current_productId);
  348. }
  349. }
  350. /*
  351. *
  352. *
  353. *
  354. */
  355. void BuyCatalog::purchaseFlowFinished( int requestId )
  356. {
  357. resetIAPrequest(requestId);
  358. }
  359. void BuyCatalog::restoreProducts()
  360. {
  361. if(isIAPclientBusy())
  362. return;
  363. current_requestId = iap_client->getRestorableProducts();
  364. //lock ui
  365. busyIndicator->setLabelText("restoration in progress...");
  366. busyIndicator->show();
  367. restorableProductItems.clear();
  368. }
  369. void BuyCatalog::productListComplete()
  370. {
  371. qDebug() << "productListComplete count: " << products.count();
  372. if(products.count()>0)
  373. {
  374. ui.progressBar->setRange(0, products.count());
  375. ui.progressBar->setTextVisible(false);
  376. // request product data from Ovi for the products
  377. requestNextProduct();
  378. }
  379. else
  380. ui.progressBar->setVisible(false);
  381. }
  382. void BuyCatalog::productComplete(QByteArray* buf)
  383. {
  384. if(dm->isNeedToBuy(*buf)){
  385. qDebug() << "start IAP experience";
  386. //in purchaseProduct call flag value 'IAPClient::NoForcedRestoration' does not fetches
  387. //ticket from OVI and there is no other API call to get already purchased item ticket except restoration -
  388. //-- so 'IAPClient::ForcedAutomaticRestoration' flag value is reverted back -- fix related to
  389. //https://support.preminetsolution.com/jira/browse/TECHPUBS-2
  390. //https://support.preminetsolution.com/jira/browse/JUNIPERQA-24766
  391. current_requestId = iap_client->purchaseProduct(current_productId,
  392. IAPClient::ForcedAutomaticRestoration);
  393. }else{
  394. QString fname = MainWindow::getPurchaseUri(current_productId);
  395. QFile file(fname);
  396. qDebug() << " BuyCatalog::productComplete : saving to file : " << fname;
  397. QString privatedir(MainWindow::getPurchaseDir());
  398. if (!QDir(privatedir).exists())
  399. QDir().mkdir(privatedir);
  400. if(file.open(QIODevice::WriteOnly)) {
  401. file.write(*buf);
  402. file.close();
  403. qDebug() << "content " << current_productId << "saved";
  404. busyIndicator->close();
  405. ui.backButton->click();
  406. }
  407. }
  408. }
  409. bool BuyCatalog::isIAPclientBusy()
  410. {
  411. qDebug() << "IAPclient is busy";
  412. return (current_requestId != INVALID_VALUE);
  413. }
  414. bool BuyCatalog::resetIAPrequest(int requestId)
  415. {
  416. if(requestId != current_requestId)
  417. return false;
  418. busyIndicator->close();
  419. current_requestId = INVALID_VALUE;
  420. return true;
  421. }
  422. bool BuyCatalog::isReadyToBuy()
  423. {
  424. return (!userAuth.isEmpty());
  425. }