Implementing a new backend for Qt Purchasing

A bit more than a year ago, we released the Qt Purchasing module as part of our commercial offering. This module provides cross-platform APIs to enable in-app purchases in your application, and the current released version works together with the app store on iOS and the Google Play store on Android.

Recently, however, we decided to release the module into open source under LGPLv3 in Qt 5.7, hoping that it will prove to be useful for the Qt community as a whole, and that we can work together to evolve it further. There are many different app stores out there, and the ambitious goal should of course be to support all of them! So far, so good: Just a few days after the Qt Purchasing module had been uploaded to the Qt Project, Jake Petroules contributed a patch which enables module to work with the Mac Store on OS X as well.

To assist those of your out there who might have interest in a particular online store and want to participate in increasing the value of Qt Purchasing, I decided to write a guide on how you would go about actually implementing a new backend, as the interfaces are currently largely undocumented outside of Andy Nichols’ and my own brain.

QInAppPurchaseBackend

The first thing you want to do, is to create a subclass of QInAppPurchaseBackend. Throughout this blog, I will refer to a hypothetical store instead of an actual one, as this will allow us to focus on how the APIs were intended to be used instead of gritty platform details.

class QAcmeInAppPurchaseBackend: public QInAppPurchaseBackend
{
public:
    void initialize();
    bool isReady() const;
    void queryProducts(const QList &products);
    void queryProduct(QInAppProduct::ProductType productType, const QString &identifier);
    void restorePurchases();
    void setPlatformProperty(const QString &propertyName, const QString &value);
};

I’ll go through the functions one by one, but first, to make sure your backend is created when the application initializes Qt Purchasing, edit the QInAppPurchaseBackendFactory to create an instance of your class.

QInAppPurchaseBackend *QInAppPurchaseBackendFactory::create()
{
#if defined(Q_OS_ANDROID)
    return new QAndroidInAppPurchaseBackend;
#elif defined (Q_OS_MAC)
    return new QMacInAppPurchaseBackend;

// Making a backend for ACME OS
#elif defined (Q_OS_ACME)
    return new QAcmeInAppPurchaseBackend;

#else
    return new QInAppPurchaseBackend;
#endif
}

Note that an application is assumed to connect to single store which is identifiable at run-time.

The initialize() and isReady() functions

If your backend needs to do any initialization work, implement the initialize() function. This is called when the user of the APIs registers the first product, either by calling QInAppStore::registerProduct(), or declaratively in QML.

The initialize() function should either execute all necessary steps to initialize() synchronously and then emit the ready() signal, or, if the process is asynchronous, as it is with Google Play, it should launch the initialization process and emit ready() when it’s done.

void QAcmeInAppPurchaseBackend::initialize()
{
    NativeAcmeAPI::connectToAppStore(this);
}

void QAcmeInAppPurchaseBackend::nativeAcmeAppStoreReadyCallback()
{
    m_isReady = true;
    emit ready();
}

bool QAcmeInAppPurchaseBackend::isReady() const
{
    return m_isReady;
}

The isReady() function should return true if, and only if, the backend is ready.

The queryProducts() and queryProduct() functions

Once the backend has been initialized, it should be ready for calls to either queryProducts() or queryProduct(). Since the underlying APIs might not support getting a list of all products associated with the application, the user will register them manually with the IDs they have been assigned in the external store.

The queryProduct() function should be implemented to collect information about the product from the external store. If the product does not exist, it should emit productQueryFailed(), or it should emit productQueryDone() when the information has been collected. This can also be done asynchronously if necessary.

Implementing the queryProducts() function is optional. It can be used to bundle more than one product in a single request. By default, the function will just call queryProduct() for each of the items in the list.

void QAcmeInAppPurchaseBackend::queryProduct(QInAppProduct::ProductType productType,
                                             const QString &identifier)
{
    NativeAcmeAPIProduct *nativeProduct = NativeAcmeAPI::getProduct(identifier);
    if (nativeProduct == 0) {
        emit productQueryFailed(productType, identifier);
        return;
    }

    QAcmeInAppProduct *product = new QAcmeInAppProduct(identifier,
                                                       productType,
                                                       nativeProduct->price(),
                                                       nativeProduct->title(),
                                                       nativeProduct->description());
    emit productQueryDone(product);
}

The product type in the query helps the user differentiate between products that can only be purchased once (unlockables) and that can be purchased any number of times (consumables). In the case where the external store supports the same categories, the backend should report failure if the product was queried with the wrong type. On some systems, however, such as Google Play, the type will only affect how the product is used: If it is requested as consumable, the product will be consumed and can be purchased again once the application has finalized the transaction. If it is unlockable, the transaction will be stored in Google Play’s database forever and will never happen again.

Note the use of a custom QInAppProduct subclass in the hypothetical code above. More about this later.

The restorePurchases() function

Restoring previous purchases originates from the concept on iOS and OS X. In case the user has uninstalled and reinstalled the application (or installed the same application on a new device), the application is expected to provide a way for the user to re-register all the unlockable products they have previously purchased. This requires the user to input their password on the device, so the event should be triggered by them, e.g. by clicking on a button in the UI.

When this function is called, the backend is expected to emit a transactionReady() signal for each of the unlockable products currently owned by the user, and they will each need to be finalized again.

void QAcmeInAppPurchaseBackend::restorePurchases()
{
    for (int i = 0; i < NativeAcmeAPI::transactionCount(); ++i) {
            NativeAcmeTransaction *nativeTransaction = NativeAcmeAPI::transaction(i);
            nativeTransaction->setFinalized(false);
            
            QAcmeInAppTransaction *transaction = new QAcmeInAppTransaction(QinAppTransaction::PurchaseApproved,
                                                                           product(nativeTransaction->identifier())); 
            transaction->setOrderId(nativeTransaction->orderId());
            transaction->setTimestamp(nativeTransaction->purchaseTimestamp());

            emit transactionReady(transaction);
        }
    }
}

More about finalization of transactions later.

The setPlatformProperty() function

The setPlatformProperty() function is optional, and for the sake of giving a good cross-platform experience, it should be used as little as possible. It is provided, though, as a last resort for those very platform-specific APIs which do not make sense on other platforms.

For instance, Google Play has the concept of a public key which can be used to verify purchases. This is inherently platform-specific, because even if another store had the exact same concept, the public key would not be the same, so the developer would still have to specify it on a per-platform level.

If we were to attempt to provide a cross-platform API for this, we would quickly end up with ugly application code such as the following.

void MyApplication::initialize()
{
// Yuck
#if defined(Q_OS_ANDROID)
    m_store->setPublicKey("ABCABCABC");
#elif defined(Q_OS_ACME)
    m_store->setPublicKey("DEFDEFDEF");
#endif
}

A better solution is to bake the platform name into the property name and provide a solution which does not require #ifdefs when using the APIs.

void MyApplication::initialize()
{
    m_store->setPlatformProperty("AndroidPublicKey", "ABCABCABC");
    m_store->setPlatformProperty("AcmePublicKey", "DEFDEFDEF");
}

So you can implement the function if there are similar concepts on the platform you are targeting, but always consider whether expanding the cross-platform APIs make the end result simpler.

QInAppProduct

The next class we want to subclass is QInAppProduct. This is an abstraction of a single product which can be purchased in the store. It should be populated with information on construction, and the purchase() function must be implemented.

QAcmeInAppProduct::QAcmeInAppProduct(const QString &price,
                                     const QString &title,
                                     const QString &description,
                                     QInAppProduct::ProductType productType,
                                     const QString &identifier)
: QInAppProduct(price, title, description, productType, identifier) {}

void QAcmeInAppProduct::purchase()
{
    NativeAcmeAPI::attemptPurchase(identifier());
}

void QAcmeInAppPurchaseBackend::nativeAcmePurchaseAcceptedCallback(const QString &orderId,
                                                                   const QString &timestamp)
{
    QAcmeInAppTransaction *transaction = new QAcmeInAppTransaction(QInAppTransaction::PurchaseApproved,
                                                                   product(identifier()));
    transaction->setOrderId(orderId);
    transaction->setTimestamp(timestamp);

    emit transactionReady(transaction);
}

void QAcmeInAppPurchaseBackend::nativeAcmePurchaseRejectedCallback()
{
    QAcmeInAppTransaction *transaction = new QAcmeInAppTransaction(QInAppTransaction::PurchaseFailed,
                                                                   product(identifier()));
    transaction->setOrderId(orderId);
    transaction->setTimestamp(timestamp);
    transaction->setFailureReason(QInAppTransaction::CanceledByUser);

    emit transactionReady(transaction);
}

void QAcmeInAppPurchaseBackend::nativeAcmePurchaseErrorCallback(const QString &errorString)
{
    QAcmeInAppTransaction *transaction = new QAcmeInAppTransaction(QInAppTransaction::PurchaseFailed,
                                                                   product(identifier()));
    transaction->setOrderId(orderId);
    transaction->setTimestamp(timestamp);
    transaction->setFailureReason(QInAppTransaction::ErrorOccurred);
    transaction->setErrorString(errorString);

    emit transactionReady(transaction);
}

You should always follow up a call to purchase() with an emission of the transactionReady() signal. The signal is used to communicate both successful purchases and failed ones. If possible, differentiate between failures caused by errors and those caused by the end user rejecting the transaction.

QInAppTransaction

The final class to subclass is QInAppTransaction. At the minimum, you will have to implement the finalize() function, but there are also other virtual functions that can be used to provide useful information from the system.

The orderId(), failureReason(), errorString(), timestamp() and platformProperty() functions

First off are some simple accessor functions. They are implemented as virtual functions to provide flexibility with regards to how they are implemented. A simple implementation, for instance, would be to have symmetrical setters for them in the class.

QString QAcmeInAppTransaction::orderId() const
{
    return m_orderId;
}

void QAcmeInAppTransaction::setOrderId(const QString &orderId)
{
    m_orderId = orderId;
}

…and so forth.

The order ID is a unique identifier for the transaction, if it were successful, or just empty if not. This and the timestamp can be used by the application developer for logging purposes, e.g. to help the customer later on if they wish to cancel the purchase after it has already been approved.

The failure reason gives the cause of a failed transaction (or NoFailure if it were successful), and the error string can give extra information from the system if an error occurred.

The platformProperty() function is the inverse of the setPlatformProperty() in QInAppPurchaseBackend. If the transaction contains platform specific data that does not make sense as common APIs, it can be exposed through this function, otherwise it can be ignored.

The finalize() function

The last function we’ll discuss is the finalize() function. In order to make transactions safe against sudden crashes or shutdowns due to battery running out, each transaction is handled in two steps. First the user approves the purchase on their side, which emits a transactionReady() signal, and then the application verifies that it has registered the necessary information from the transaction by calling finalize() on it. If a product has been purchased by the user and the transaction has not yet been finalized, the application should keep emitting transactionReady() signals for the transaction until it has been finalized to make sure the information is not lost. This can be done e.g. on startup, when the backend is initialized.

The application has to be written to support multiple transactionReady() signals for the same transactions, so it’s better to emit the signal too many times than too few.

void QAcmeInAppPurchaseBackend::initialize()
{
    /* ... */

    for (int i = 0; i < NativeAcmeAPI::unfinalizedTransactionCount(); ++i) {
        NativeAcmeTransaction *nativeTransaction = NativeAcmeAPI::unfinalizedTransaction(i);
        QAcmeInAppTransaction *transaction = new QAcmeInAppTransaction(QinAppTransaction::PurchaseApproved,
                                                                       product(nativeTransaction->identifier())); 
        transaction->setOrderId(nativeTransaction->orderId());
        transaction->setTimestamp(nativeTransaction->purchaseTimestamp());

        emit transactionReady(transaction);
    }
}

This addition to the initialize() function will go through all transactions that have yet to be finalized on startup and emit new transactionReady() signals for them, ensuring that the application is notified of the transactions.

Thank you!

I hope this was a useful introduction to the Qt Purchasing backend interfaces. Excuse any mistakes or typos in the code snippets. They were all done inline for illustration purposes, so even if there was such a thing as a NativeAcmeAPI, it’s quite possible they do not compile.

Do let me know if you have any questions, either here, or look me up on IRC. Good luck!

The post Implementing a new backend for Qt Purchasing appeared first on Qt Blog.



Source :- Qt Blog http://ift.tt/1Ejv0CO

No PowerPoint presentations more confusing than a Rubik’s Cube here

Announcing the Developer Unconference Track & Lightning Talks at Qt World Summit 2015

We are naturally gifted at connecting to each other personally and at connecting each other’s ideas into something brilliant together. We co-create by feeding off and enabling each other. If you are a fan of peer-to-peer learning, collaboration and creativity read on.

Developer Unconference Track

At Qt World Summit 2015 we give you the opportunity to take a break from the conference agenda and take part in our Developer Unconference Track on Day 2, Wednesday October 7. We will open a conference room for round table discussions that live by the participation of its attendees. We encourage you to engage in discussions around new ideas of how to use Qt, a new module you have been developing, etc. Anything you find interesting and you hope others at the conference will find interesting—and that relates to Qt, of course.

You are not allowed to talk about yourself or show a PowerPoint presentation that is more confusing than a Rubik’s Cube, but rather engage the group in active and constructive discussion. Send us your suggested topics and we will stitch together a schedule and book a conference room. The agenda for each hourly session will then be created at the beginning of each meeting by the participants that choose to attend.

Read more and submit your ideas here.

Lightning Talks

We are also launching a call for lightning talks, if you prefer a more prepared approach to your topic. Lightning talks provide a great opportunity to speak about your experiences, opinions or ideas relating to Qt in a short talk. We welcome any participant of the conference to give a 10 minute lightning talk on any subject of interest to Qt developers. The talk can be about anything really, as long as it has a relationship to Qt. It’s your call as long as a Qt developer or tech strategist would find it interesting. The lightning talks will be strictly time-controlled so that we can get as many talks included as possible in the time allotted.

Read more and submit your proposal here.

We are excited to see how this track will unfold. That’s the beauty of unconferences – they are unexpected. Unrehearsed. Surely not dull. Join us!

The post No PowerPoint presentations more confusing than a Rubik’s Cube here appeared first on Qt Blog.



Source :- Qt Blog http://ift.tt/1Jt9d7k

Looking for Qt Champions 2015

2015 is well on it’s way, and it’s time to take a look at who has been going that extra mile in the Qt community.

Qt_Champion_200

It’s Qt Champions 2015 time.

Qt champions are the people that make the community a better place, the ones that enable you to build your world with Qt. They can be developers, people who help out on the forum, make videos or fix the documentation for everyone.

Take a moment to reflect who has been doing something special this year and go nominate them!

Like last year there are several categories for Qt Champions. The title is for one year, so go and nominate your Qt Champions!

The post Looking for Qt Champions 2015 appeared first on Qt Blog.



Source :- Qt Blog http://ift.tt/1EfzfPd

Qt Creator 3.5.0 released

We are happy to announce the release of Qt Creator 3.5.0.

The most apparent new feature in this version is probably the highlighting that we added to the editors’ vertical scroll bars. You can now easily see where
bookmarks, breakpoints, warnings, errors and search results are located in the open document.

qtc_scrollbar_markers

We also improved the performance of the global searches in Qt Creator. While the code model based searches like Find Usages were always multi-threaded, that wasn’t the case for the purely text based searches, which is fixed now.

Qt Creator’s own C++ code model received many fixes, for example for many issues with templates again. There also was much progress with the Clang based code model, which is now running in a separate process, which should make us safer from bugs in Clang itself. We also upgraded our binary packages to use Clang 3.6.

Since Qt Quick 1 was succeeded by Qt Quick 2 in Qt 5 and is now officially deprecated in Qt 5.5, we removed support for Qt Quick 1 from Qt Quick Designer. We also removed the Qt Quick 1 wizards and support for profiling with V8. The visual designer for Qt Quick 2, the code editor for Qt Quick 1 and 2, and profiling of Qt Quick 2 from Qt 5.2 and later and of Qt Quick 1 are not affected.

We also removed support for BlackBerry 10 development, lacking a maintainer. QNX development is not affected by this.

On Windows, we fixed an issue that could lead to Qt Creator sometimes freezing for some seconds when a user application was running. On Linux, we fixed a performance issue when Qt Creator was compiled with journald support. For Android development, we fixed issues with Android M, 64 bit, and Google AVDs.

Of course this is just a very small excerpt of the full list of changes. Please have a look at our change log for more details. Many thanks go to all the contributors that made this release happen!

You find the opensource version on the Qt download page, and commercially licensed packages on the Qt Account Portal. Please post issues in our bug tracker. You can also find us on IRC on #qt-creator on irc.freenode.net, and on the Qt Creator mailing list.

The post Qt Creator 3.5.0 released appeared first on Qt Blog.



Source :- Qt Blog http://ift.tt/1KxvfGI

Improvements to Commercial Qt Downloads in China

We have changed to a new Content Delivery Network (CDN) for the download of commercial packages and evaluations in China. The new provider will offer better download speed and reliability, especially when using the online installer.

Earlier we have had some issues with the download of commercial packages inside the People’s Republic of China. Especially problematic has been that some online installer downloads have been failing. With the new provider we have improved download speed and reliability. Open-source downloads of Qt are unaffected by this change.

The new system is already in production for the online installer. Later in August we plan to also use it for offline packages downloaded via the Qt Account. Downloads outside China are unaffected and continue to use the same CDN as earlier. However, when the download request is detected as coming from China, we now transfer the connection to the new Chinese CDN system.

If you experience any issues downloading, please contact us via the Qt Support Center inside the Qt Account portal.

The post Improvements to Commercial Qt Downloads in China appeared first on Qt Blog.



Source :- Qt Blog http://ift.tt/1MrEkHd

Qt Creator 3.5 RC1 released

We are happy to announce the release of Qt Creator 3.5 RC1. After four weeks of fixing and polishing we now think that the new version is almost ready for release. So please use the opportunity to download and try the RC, and give us last feedback on our bug tracker, on the Qt Creator mailing list, or on IRC on #qt-creator on irc.freenode.net.

If you are interested in what has changed in 3.5, I’d like to redirect you to the beta blog post. You also find the change log on code.qt.io.

You find the opensource version on the Qt Project download page, and Enterprise packages on the Qt Account Portal.

The post Qt Creator 3.5 RC1 released appeared first on Qt Blog.



Source :- Qt Blog http://ift.tt/1Eadhbh