Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

In-app payments without a server

111 views
Skip to first unread message

Kumar McMillan

unread,
Feb 26, 2013, 5:42:10 PM2/26/13
to dev-w...@lists.mozilla.org, Raymond Forbes
Hi all.
I know many of haven't had a chance to test in-app payments yet because our simulation mode is not fully live. However, you can read about how it works here: https://developer.mozilla.org/en-US/docs/Apps/Publishing/In-app_payments

There is currently an awkward requirement: as a developer you must run server code in order to process in-app payments. This means you can't host your app on github pages and you can'd do payments from a packaged app (without a server). The rationale for this is that JWTs must be signed with a secret key and this is how we verify their authenticity. If anyone on the Internet could sign JWTs then someone could edit the price to $0.00 and get free products. The secret key must be secured on a server; it's not safe on the client.


Here is a proposal for how to do in-app payments without a server. Let me know if this sounds sane as far as ease-of-use and security.

First, this proposal would not require any changes to the underlying navigator.mozPay() API or the current JWT spec. No B2G client changes would be needed. It would be built on top of these existing APIs.

I think we can introduce server-less in-app payments by hosting a Mozilla web service on behalf of developers -- that is, a service that any developer can use.

Instead of this on the app server:

signedJWT = jwt.encode({
request: {
name: 'Rainbow Unicorn',
description: 'A virtual unicorn for Adventure Game',
pricePoint: 1 // EUR 0.89
}
}, '<secret-key>')

and this on the client:

navigator.mozPay([signedJWT])

the developer could do something like this on the client only:

var unicornId = 1234;
payment.start(unicornId, function(jwt, transaction) {

var request = navigator.mozPay([jwt]);
request.onsuccess = function() {
transaction.whenDone(function(error) {
if (!error) {
alert('You bought a Rainbow Unicorn!');
}
});
};

});

How would it work? The Firefox Marketplace (or any server, really) could host a product catalog that the merchant could manage through a management website. The merchant would *pre-define* the price, description, etc, for products such as the Rainbow Unicorn and whatever else. The concept of pre-defining the price in the product catalog would give us the security needed (I think) to let anyone on the Internet hit payment.start(unicornId, ...) and retrieve a signed JWT without harm. An attacker can't edit the price. They can't receive any funds. If they make a fake app that sells the same product, what is the incentive? The customer might be confused but the original app owner would receive the funds, not the attacker.

This theoretical payment JS lib would be a wrapper around the web service. We'd get the JWT like this:

GET https://mkt/payments/jwt/<productId>

And the JWT would have custom postback URLs, like this:

{
request: {
name: 'Rainbow Unicorn',
...
postbackURL: 'https://mkt/payments/postback/<productId>',
chargebackURL: 'https://mkt/payments/chargeback/<productId>',
productData: 'transactionId=XYZ'
}
}

This would tell the payment provider to post to the service instead of to the app, like:

POST https://mkt/payments/postback/<productId>

For the app to check that the product was paid for (i.e. postback was received), it would need to poll something like:

GET /payment/status/<transactionId>

_

That's it. The pros:
- the app developer does not have to host a server
- Thus, more devs can experiment with in-app payments
- the developer does not need to sign JWTs or verify JWT signatures (which might require compiling crypto libraries)
- the hosted service could double as collecting stats and produce useful reports like product conversion rate
- we could maybe integrate persona and make payments a bit easier for users since they'll have one login

The cons:
- if the hosted service goes down, no payments
- we'll need to have a privacy policy, obviously
- the developer will have to manage a catalog of products as opposed to setting that info in a custom JWT

Are there any security issues with this? Anything I'm missing? The payment lib code and server endpoints obviously could be cleaned up; I'm looking more for feedback on security and the general concept.


Thanks for reading.
-Kumar

Fernando Jiménez

unread,
Feb 27, 2013, 6:01:19 AM2/27/13
to Kumar McMillan, dev-w...@lists.mozilla.org, Daniel Coloma, Raymond Forbes, Jimenez Ernesto, Jonas Sicking, JOSE MANUEL CANTERA FONSECA, ANTONIO MANUEL AMAYA CALVO, Andreas Gal
Thanks Kumar!

I really love the idea of allowing in-app payments without forcing developers to maintain a server side for their apps. IIRC there were already some discussions about a possible web service for signing JWTs on the Marketplace side during the Barcelona work week. I'm glad that this is still an option.

A few comments inline.

On 26/02/2013, at 23:42, Kumar McMillan wrote:

> First, this proposal would not require any changes to the underlying navigator.mozPay() API or the current JWT spec. No B2G client changes would be needed. It would be built on top of these existing APIs.

Is there any reason why you are not considering these changes an option for the navigator.mozPay() API?

> I think we can introduce server-less in-app payments by hosting a Mozilla web service on behalf of developers -- that is, a service that any developer can use.

\o/ That'd be awesome!

>
> Instead of this on the app server:
>
> signedJWT = jwt.encode({
> request: {
> name: 'Rainbow Unicorn',
> description: 'A virtual unicorn for Adventure Game',
> pricePoint: 1 // EUR 0.89
> }
> }, '<secret-key>')
>
> and this on the client:
>
> navigator.mozPay([signedJWT])
>
> the developer could do something like this on the client only:
>
> var unicornId = 1234;
> payment.start(unicornId, function(jwt, transaction) {
>
> var request = navigator.mozPay([jwt]);
> request.onsuccess = function() {
> transaction.whenDone(function(error) {
> if (!error) {
> alert('You bought a Rainbow Unicorn!');
> }
> });
> };
>
> });
>

I like your proposal, but I have a few concerns about it:

1. This seems to be very tied and dependent of the Mozilla Marketplace. It would be great if we could allow developers to build and use its own JWT signing service, while we still give them the chance to use the one exposed by the Mozilla Marketplace or any other market (in the end, we are trying to build the most open API as possible). You may say devs already have that chance with the current API, and I agree on that, but I think that we still can make the process easier (see my proposal below, please) and somehow provide an "standard" way of signing payment requests.

2. IMHO a wrapper around the current API adds unnecessary complexity. We should (and probably can) build what you are proposing extending the current navigator.mozPay API. I am not sure if you are proposing the wrapper as a long term solution though. Maybe you also have in mind modifying the API with these additions in the future.

3. How would you manage refunds? I can't see how would you tell the web service that your intention is to get a JWT for a refund request and not for a payment one.


So my proposal, based on yours, would be extending the API this way:

"DOMDOMRequest mozPay(DOMString signingService, jsval dataToBeSigned)"

where:
- "signingService" would be the URL of the Mozilla web service or any other JWT signing web service.
- "dataToBeSigned" would be a JSON object containing whatever is expected by the signing service. It could be the identifier of the product being sold or refunded and a flag for setting a payment or a refund, for example.

The process of getting the signed JWT and sending the payment request would be completely handled by the native implementation.

We could also still keep the current navigator.mozPay([JWT]) along with this addition.

Let me know if this makes sense, please.

Cheers!

/ Fernando

Kumar McMillan

unread,
Feb 27, 2013, 11:58:58 AM2/27/13
to Fernando Jiménez, dev-w...@lists.mozilla.org, Daniel Coloma, Raymond Forbes, Jimenez Ernesto, Jonas Sicking, JOSE MANUEL CANTERA FONSECA, ANTONIO MANUEL AMAYA CALVO, Andreas Gal
Hi Fernando.

On Feb 27, 2013, at 5:01 AM, Fernando Jiménez <ferjm...@gmail.com> wrote:

>> First, this proposal would not require any changes to the underlying navigator.mozPay() API or the current JWT spec. No B2G client changes would be needed. It would be built on top of these existing APIs.
>
> Is there any reason why you are not considering these changes an option for the navigator.mozPay() API?

I had a couple reasons in mind. First is that not changing the API means we could ship a server-less solution e.g. tomorrow or whenever it's ready. Another is that this feels to me more like a "helper library" solution rather than an API enhancement. Lastly, it might make sense to apply it to real solutions and let it evolve before we bake it into the API. That would prove out its usefulness and reveal warts. If there is a clear way to fit it into the API then I'd be all for it.

>>
>
> I like your proposal, but I have a few concerns about it:
>
> 1. This seems to be very tied and dependent of the Mozilla Marketplace. It would be great if we could allow developers to build and use its own JWT signing service, while we still give them the chance to use the one exposed by the Mozilla Marketplace or any other market (in the end, we are trying to build the most open API as possible). You may say devs already have that chance with the current API, and I agree on that, but I think that we still can make the process easier (see my proposal below, please) and somehow provide an "standard" way of signing payment requests.

How is it tied specifically to Mozilla's Marketplace? Do you mean that for whatever server it's hosted on, the implementation is tied to *that* server? Then, yes. That's the only way I could see it working from a security perspective.

The key point is that the client has to fetch a signed payment request (JWT) in a way that does not compromise the integrity of the payment request. If the developer wants to lock down the product price, she has to refer to the product by some identifier. The server has to know what the price is and keep it secure.

But, yes, of course, if we implement this for Firefox Marketplace then we could easily do it as a standalone, easy-to-install service. Anyone could run their own service on AWS/Heroku and pay their own server bills.

>
> 2. IMHO a wrapper around the current API adds unnecessary complexity. We should (and probably can) build what you are proposing extending the current navigator.mozPay API. I am not sure if you are proposing the wrapper as a long term solution though. Maybe you also have in mind modifying the API with these additions in the future.

It is not clear to me how the current API could be modified to securely allow server-less payments. I'm open to ideas. I think we would need some kind of drastic API change, perhaps one that managed *public* keys per merchant so that a decentralized chain of trust can be established. Even in such a case, I think servers need to be involved in that for security.

At that point, we may just want to throw the whole navigator.mozPay() API out and join the Pay Swarm API :) See http://payswarm.com/ and http://www.w3.org/community/webpayments/ That could be a whole other thread. I have a few issues with their approach too.

>
> 3. How would you manage refunds? I can't see how would you tell the web service that your intention is to get a JWT for a refund request and not for a payment one.

That's a good question! However, refunds are so hard for in-app payments that we've pretty much already ignored them. The current API also has no reliable way to do refunds. Take the example of ad-hoc JWTs (in the current API). Let's say a customer paid to "unlock level 10" then she beat level 10 and decided to be sneaky and ask for a refund. How do we grant a refund? The current navigator.mozPay() has no memory of "the product." It cannot take away "Unlock level 10" after a refund because it doesn't know how. In this case, it would be impossible to take away Unlock level 10.

For now we are not supporting refunds for in-app payments. In the future, maybe developers could opt-in to refunds and then our app reviewers would have to be sure that they *can* grant refunds before we allow them to.

For Marketplace app purchases, it's easier to implement refunds because we know what the app logic is inside the Firefox Marketplace. We know how to take away apps.

>
>
> So my proposal, based on yours, would be extending the API this way:
>
> "DOMDOMRequest mozPay(DOMString signingService, jsval dataToBeSigned)"
>
> where:
> - "signingService" would be the URL of the Mozilla web service or any other JWT signing web service.
> - "dataToBeSigned" would be a JSON object containing whatever is expected by the signing service. It could be the identifier of the product being sold or refunded and a flag for setting a payment or a refund, for example.

ok, let's say an app does this:

nav.mozPay('https://mkt/payments/signing-service', {
request: {
name: 'Rainbow Unicorn',
pricePoint: 4 // USD $3.99
}
})

What would that do? POST the raw JSON blob to the signing service and get backed a signed JWT? That would defeat the purpose of the signature because then anyone can sign anything. An attacker could just patch the app and change the JS to do this (note the price change):

nav.mozPay('https://mkt/payments/signing-service', {
request: {
name: 'Rainbow Unicorn',
pricePoint: 1 // USD $0.99
}
})

and they would get the product for whatever price they wanted. Am I missing something?


-Kumar

Fernando Jiménez

unread,
Feb 27, 2013, 1:39:36 PM2/27/13
to Kumar McMillan, dev-w...@lists.mozilla.org, Daniel Coloma, Raymond Forbes, Jimenez Ernesto, Jonas Sicking, JOSE MANUEL CANTERA FONSECA, ANTONIO MANUEL AMAYA CALVO, Andreas Gal
Hi :)

On 27/02/2013, at 17:58, Kumar McMillan wrote:
> On Feb 27, 2013, at 5:01 AM, Fernando Jiménez <ferjm...@gmail.com> wrote:
>
>>> First, this proposal would not require any changes to the underlying navigator.mozPay() API or the current JWT spec. No B2G client changes would be needed. It would be built on top of these existing APIs.
>>
>> Is there any reason why you are not considering these changes an option for the navigator.mozPay() API?
>
> I had a couple reasons in mind. First is that not changing the API means we could ship a server-less solution e.g. tomorrow or whenever it's ready.

Sorry, I am not sure that I am properly understanding you. How is that we can't ship a server-less solution *changing* the API? We can always include an API change in the next version of Firefox OS, right? I guess that you are talking about being tied to an specific date or client side version. In that case, the wrapper solution could be a temporary one, but that shouldn't be a reason to not changing the API if we feel that it can be improved.

> Another is that this feels to me more like a "helper library" solution rather than an API enhancement.

An API requiring a "helper library" is probably a symptom that the API needs to be improved. However, as I mentioned before, the helper library could be a completely valid temporary solution.

>>>
>>
>> I like your proposal, but I have a few concerns about it:
>>
>> 1. This seems to be very tied and dependent of the Mozilla Marketplace. It would be great if we could allow developers to build and use its own JWT signing service, while we still give them the chance to use the one exposed by the Mozilla Marketplace or any other market (in the end, we are trying to build the most open API as possible). You may say devs already have that chance with the current API, and I agree on that, but I think that we still can make the process easier (see my proposal below, please) and somehow provide an "standard" way of signing payment requests.
>
> How is it tied specifically to Mozilla's Marketplace? Do you mean that for whatever server it's hosted on, the implementation is tied to *that* server? Then, yes. That's the only way I could see it working from a security perspective.
>
> The key point is that the client has to fetch a signed payment request (JWT) in a way that does not compromise the integrity of the payment request. If the developer wants to lock down the product price, she has to refer to the product by some identifier. The server has to know what the price is and keep it secure.

I totally agree on this and it fits perfectly well with my suggestion too. The difference is the way that we allow the client to fetch the signed JWT (with a helper, or directly with our API).

>
> But, yes, of course, if we implement this for Firefox Marketplace then we could easily do it as a standalone, easy-to-install service. Anyone could run their own service on AWS/Heroku and pay their own server bills.

That's the idea! To keep it as much open as possible. Any market or developer should be able to expose and use its own JWT signing service (based on the one for Firefox Marketplace or not). I am not arguing about the server side, but the client one. My point is that there is no need to use any wrapper in the client as we can allow to communicate with a signing service extending the current API. Why forcing the server side to also expose a shim to allow the client to communicate with it when we can do it directly from the client API?

>
>>
>> 2. IMHO a wrapper around the current API adds unnecessary complexity. We should (and probably can) build what you are proposing extending the current navigator.mozPay API. I am not sure if you are proposing the wrapper as a long term solution though. Maybe you also have in mind modifying the API with these additions in the future.
>
> It is not clear to me how the current API could be modified to securely allow server-less payments. I'm open to ideas. I think we would need some kind of drastic API change, perhaps one that managed *public* keys per merchant so that a decentralized chain of trust can be established. Even in such a case, I think servers need to be involved in that for security.

I am proposing an idea :)

>>
>> 3. How would you manage refunds? I can't see how would you tell the web service that your intention is to get a JWT for a refund request and not for a payment one.
>
> That's a good question! However, refunds are so hard for in-app payments that we've pretty much already ignored them. The current API also has no reliable way to do refunds. Take the example of ad-hoc JWTs (in the current API). Let's say a customer paid to "unlock level 10" then she beat level 10 and decided to be sneaky and ask for a refund. How do we grant a refund? The current navigator.mozPay() has no memory of "the product." It cannot take away "Unlock level 10" after a refund because it doesn't know how. In this case, it would be impossible to take away Unlock level 10.
>
> For now we are not supporting refunds for in-app payments. In the future, maybe developers could opt-in to refunds and then our app reviewers would have to be sure that they *can* grant refunds before we allow them to.
>
> For Marketplace app purchases, it's easier to implement refunds because we know what the app logic is inside the Firefox Marketplace. We know how to take away apps.

The current API does not *do* refunds but provides a way to *request* refunds. The same way as it does for payments.

How is the Marketplace different to any other application using in-app payments in terms of API usage?

A developer is free to implement a mechanism to cancel the product that she is selling as soon as she gets the chargeback notification. Anyway, I am not going to discuss about refunds as I don't want to divert attention from the main topic, but IMHO we should leave that door open on the client side.

>
>>
>>
>> So my proposal, based on yours, would be extending the API this way:
>>
>> "DOMDOMRequest mozPay(DOMString signingService, jsval dataToBeSigned)"
>>
>> where:
>> - "signingService" would be the URL of the Mozilla web service or any other JWT signing web service.
>> - "dataToBeSigned" would be a JSON object containing whatever is expected by the signing service. It could be the identifier of the product being sold or refunded and a flag for setting a payment or a refund, for example.
>
> ok, let's say an app does this:
>
> nav.mozPay('https://mkt/payments/signing-service', {
> request: {
> name: 'Rainbow Unicorn',
> pricePoint: 4 // USD $3.99
> }
> })
>
> What would that do? POST the raw JSON blob to the signing service and get backed a signed JWT? That would defeat the purpose of the signature because then anyone can sign anything. An attacker could just patch the app and change the JS to do this (note the price change):
>
> nav.mozPay('https://mkt/payments/signing-service', {
> request: {
> name: 'Rainbow Unicorn',
> pricePoint: 1 // USD $0.99
> }
> })
>
> and they would get the product for whatever price they wanted. Am I missing something?

No.

Sorry if I didn't explained myself properly.

What an app should do depends on what the signing service expects and it is very similar to what you are proposing.

Let's say that the signing service is the one exposed by the Firefox Marketplace, which expects a "productId". Instead of doing:

var unicornId = 1234;
payment.start(unicornId, function(jwt, transaction) {
var request = navigator.mozPay([jwt]);
request.onsuccess = function() {
transaction.whenDone(function(error) {
if (!error) {
alert('You bought a Rainbow Unicorn!');
}
});
};
});

The developer could do something like:

var unicornId = 1234;
var req = navigator.mozPay('https://mkt/payments/signing-service', {
productId: unicornId
});
req.onsuccess = function(evt) {
alert('Payment request done!);
// Use evt.target.result.transactionId to check that the product was paid for, as you suggest, polling something like: GET /payment/status/<transactionId>
});
req.onerror = function(evt) {
alert('Error');
});

Cheers!

/ Fernando

Kumar McMillan

unread,
Feb 27, 2013, 3:46:12 PM2/27/13
to Fernando Jiménez, dev-w...@lists.mozilla.org, Daniel Coloma, Raymond Forbes, Jimenez Ernesto, Jonas Sicking, JOSE MANUEL CANTERA FONSECA, ANTONIO MANUEL AMAYA CALVO, Andreas Gal

On Feb 27, 2013, at 12:39 PM, Fernando Jiménez <ferjm...@gmail.com> wrote:

>>>
>>> 3. How would you manage refunds? I can't see how would you tell the web service that your intention is to get a JWT for a refund request and not for a payment one.
>>
>> That's a good question! However, refunds are so hard for in-app payments that we've pretty much already ignored them. The current API also has no reliable way to do refunds. Take the example of ad-hoc JWTs (in the current API). Let's say a customer paid to "unlock level 10" then she beat level 10 and decided to be sneaky and ask for a refund. How do we grant a refund? The current navigator.mozPay() has no memory of "the product." It cannot take away "Unlock level 10" after a refund because it doesn't know how. In this case, it would be impossible to take away Unlock level 10.
>>
>> For now we are not supporting refunds for in-app payments. In the future, maybe developers could opt-in to refunds and then our app reviewers would have to be sure that they *can* grant refunds before we allow them to.
>>
>> For Marketplace app purchases, it's easier to implement refunds because we know what the app logic is inside the Firefox Marketplace. We know how to take away apps.
>
> The current API does not *do* refunds but provides a way to *request* refunds. The same way as it does for payments.

Oh, right, I forgot that we added refund requests to mozPay(). The dilemma I explained above is more for automated refunds where the user clicks some button to get a refund *without* the developer knowing about it. In the case you mention, the developer initiates the refund so that's fine; it's their refund request on behalf of the customer.

>
> How is the Marketplace different to any other application using in-app payments in terms of API usage?

Again, this only applied to automated refunds. My bad.
Ah, ok. I am following you now! I agree. Once we come up with a prototype for how it might work and we test it out then we could make it part of the mozPay() API and ship an update to clients. I think the details of the API calls will need some bikeshedding but there's no need for that on this thread.

Kumar

Kumar McMillan

unread,
Apr 2, 2013, 7:26:39 PM4/2/13
to Fernando Jiménez, dev-w...@lists.mozilla.org, Daniel Coloma, Raymond Forbes, Daniel Buchner, Jimenez Ernesto, Jonas Sicking, JOSE MANUEL CANTERA FONSECA, Nick Desaulniers, ANTONIO MANUEL AMAYA CALVO, Andreas Gal
In talking with Daniel Buchner more about in-app payments without a server I developed an itch that had to be scratched. I built a quick and dirty prototype: https://github.com/kumar303/mozpay-catalog/

You can see the developer facing API here -- https://github.com/kumar303/mozpay-catalog/blob/1bb6dbc4e3d9694aa9b3815c0278d3f8c0741ddd/www/js/index.js#L56-L64 -- but I didn't do much past simply getting it to work. It is currently working with simulated payments on Firefox Marketplace.

This prototype should make it easy to try out a few things for the developer facing API. I'm still not sure how we would support multiple JWT audiences and I'm not sure how best to merge the old DOMRequest model (open/close payment window) with the new model of payment completed/failed. Perhaps we would need to move the DOMRequest to events where you listen specifically to each event (like XHR).

More as it happens.

-Kumar

Andy McKay

unread,
Apr 17, 2013, 11:44:18 AM4/17/13
to Kumar McMillan, dev-w...@lists.mozilla.org, Raymond Forbes

On 2013-02-26, at 2:42 PM, Kumar McMillan <kmcm...@mozilla.com> wrote:
> For the app to check that the product was paid for (i.e. postback was received), it would need to poll something like:
>
> GET /payment/status/<transactionId>


What would be the advantage of doing this over say using receipts. We've already got the code and infrastructure in place for receipts (although I admit there would be a few things I'd need to change). But a receipt is a proof of payment that's signed. It has a built in verification structure if the developer would like to do so, but with in-app receipts that may or may not be necessary.

Receipts don't need to be limited to just the initial app payment.

Kumar McMillan

unread,
Apr 17, 2013, 9:25:49 PM4/17/13
to Andy McKay, dev-w...@lists.mozilla.org, Raymond Forbes
Well, huh, using receipts might actually solve things nicely :)

Is this what you're thinking? : When mozPay() completes, it passes a receipt to the JS callback. The app verifies the receipt either on its own server or using the receipt's own verification API. Like app purchases, it could whitelist verification domains to prevent hacks. If that were to work, the app could be purely client side and accept in-app payments similar to how a paid app can be purely client side.

Another benefit of using receipts is that an app would not be forced to manage user identity and track purchase history on its own. If the receipt is on device then the item is purchased.

-Kumar

Andy McKay

unread,
Apr 18, 2013, 12:44:35 PM4/18/13
to Kumar McMillan, dev-w...@lists.mozilla.org, Raymond Forbes

On 2013-04-17, at 6:25 PM, Kumar McMillan <kmcm...@mozilla.com> wrote:
> Is this what you're thinking? : When mozPay() completes, it passes a receipt to the JS callback. The app verifies the receipt either on its own server or using the receipt's own verification API. Like app purchases, it could whitelist verification domains to prevent hacks. If that were to work, the app could be purely client side and accept in-app payments similar to how a paid app can be purely client side.

Yes. Although that makes me wonder about the verification process. The verification process requires the marketplace to maintain a list of all the sales. I actually think that is a good thing since we are doing this anyway. But at the moment I know we don't do that for in-app payments. Is it sensible to think about a receipt that can't be verified against the marketplace?

You will know that the receipt is correctly signed using a trusted key. You just wouldn't be able to verify it. Would that be enough?

> Another benefit of using receipts is that an app would not be forced to manage user identity and track purchase history on its own. If the receipt is on device then the item is purchased.

And there's nothing stopping an app developer pushing those receipts up to their server if they wanted to track it, but the device is the source of truth, not the server.
0 new messages