Contents
API available through the Ubuntu Store.
Staging: https://myapps.developer.staging.ubuntu.com/
Production: https://myapps.developer.ubuntu.com/
Endpoints require clients to send POST parameters encoded as JSON, setting the respective header (Content-type: application/json), as shown in the examples.
GET /api/2.0/click/paymentmethods/¶| Query Parameters: | |
|---|---|
|
|
Note
valid currencies are USD, EUR and GBP; if not specified, all available
payment methods will be returned.
OAuth signed request with user credentials. It will return available payment methods for the user as a json response.
Warning
As this is an OAuth signed request, it’s important that the URL actually ends with a slash. Using a different URL, even if slightly (eg, missing trailing slash) will mean the OAuth signature validation will fail, resulting in a 401 Unauthorized response.
Request:
GET /api/2.0/click/paymentmethods/ HTTP/1.1
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
[
{
"description": "PayPal",
"id": "paypal",
"preferred": false,
"choices": [
{
"currencies": [
"USD",
"GBP",
"EUR"
],
"id": 532,
"requires_interaction": false,
"preferred": true,
"description": "PayPal Preapproved Payment (exp. 2014-04-12)"
}
]
},
{
"description": "Credit or Debit Card",
"id": "credit_card",
"preferred": true,
"choices": [
{
"currencies": [
"USD"
],
"id": 1767,
"requires_interaction": false,
"preferred": true,
"description": "**** **** **** 1111 (Visa, exp. 02/2015)"
},
{
"currencies": [
"USD"
],
"id": 1726,
"requires_interaction": false,
"preferred": false,
"description": "**** **** **** 1111 (Visa, exp. 03/2015)"
}
]
}
]
Optionally you can pass a currency to filter available methods by.
Request:
GET /api/2.0/click/paymentmethods/?currency=EUR HTTP/1.1
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
[
{
"description": "PayPal",
"id": "paypal",
"preferred": false,
"choices": [
{
"currencies": [
"USD",
"GBP",
"EUR"
],
"id": 532,
"requires_interaction": false,
"preferred": true,
"description": "PayPal Preapproved Payment (exp. 2014-04-12)"
}
]
}
]
GET /api/2.0/click/paymentmethods/add/¶| Query Parameters: | |
|---|---|
|
|
Note
valid currencies are USD, EUR and GBP; if not specified, USD will
be assumed.
An OAuth signed GET to this endpoint (if going through a web view), will log in the user and redirect to the ‘Add credit card’ web page.
Warning
As this is an OAuth signed request, it’s important that the URL actually ends with a slash. Using a different URL, even if slightly (eg, missing trailing slash) will mean the OAuth signature validation will fail, resulting in a 401 Unauthorized response.
You can specify the preferred currency via a query parameter:
Request:
GET /api/2.0/click/paymentmethods/add/?currency=EUR HTTP/1.1
DELETE /api/2.0/click/paymentmethods/(backend_id)/(method_id)/¶| Parameters: |
|
|---|
An OAuth signed DELETE to this endpoint, will (request to) disable the specified payment method. In the case of credit cards, the disabling is immediate; for PayPal preapprovals this could take a bit, until PayPal processes the request and notifies us back (async).
Warning
As this is an OAuth signed request, it’s important that the URL actually ends with a slash. Using a different URL, even if slightly (eg, missing trailing slash) will mean the OAuth signature validation will fail, resulting in a 401 Unauthorized response.
Request:
DELETE /api/2.0/click/paymentmethods/credit_card/1726/ HTTP/1.1
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"success": true}
POST /api/2.0/click/purchases/¶| Request Headers: | |
|---|---|
|
|
| Form Parameters: | |
|
|
| Status Codes: |
|
Note
The Partner ID is case sensitive; invalid IDs will be ignored.
The only valid partner ID a this time is bq.
Note
The device id SHOULD be specified using the X-Device-Id header.
Sending the device_id parameter in the body is still supported for
backwards compatibility.
OAuth signed request with user credentials. Token freshness will be checked against SSO (age < 15min). If signing token is not fresh enough (although valid), a 401 Unauthorized response will be returned, specifying the error and the expected threshold (in seconds).
Warning
As this is an OAuth signed request, it’s important that the URL actually ends with a slash. Using a different URL, even if slightly (eg, missing trailing slash) will mean the OAuth signature validation will fail, resulting in a 401 Unauthorized response.
If the user already purchased the application, the subscription details will be returned without taking a new payment.
Request:
POST /api/2.0/click/purchases/ HTTP/1.1
Content-Type: application/json
X-Device-Id: 1234567890abcdef
{
"name": "com.ubuntu.developer.dev.appname",
"backend_id": "credit_card",
"method_id": 1726
}
Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"error": "TOKEN_NEEDS_REFRESH",
"threshold": 900
}
Request:
POST /api/2.0/click/purchases/ HTTP/1.1
Content-Type: application/json
X-Partner-Id: bq
X-Device-Id: 1234567890abcdef
{
"name": "com.ubuntu.developer.dev.appname",
"backend_id": "credit_card",
"method_id": 1726,
"currency": "GBP"
}
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"open_id": "https://login.staging.ubuntu.com/+id/open_id",
"package_name": "com.ubuntu.developer.dev.appname",
"refundable_until": "2015-07-15 18:46:21",
"state": "Complete"
}
The refundable_until field is a UTC timestamp value indicating the date/time
limit allowing the user to request a self-refund.
Once the limit expires, the returned value will be null, that is:
{
"open_id": "https://login.staging.ubuntu.com/+id/open_id",
"package_name": "com.ubuntu.developer.dev.appname",
"refundable_until": null,
"state": "Complete"
}
Request:
POST /api/2.0/click/purchases/ HTTP/1.1
Content-Type: application/json
X-Device-Id: 1234567890abcdef
{
"name": "com.ubuntu.developer.dev.appname",
"item_sku": "item_sku",
"currency": "USD",
"backend_id": "credit_card",
"method_id": 1726
}
Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"open_id": "https://login.staging.ubuntu.com/+id/open_id",
"package_name": "com.ubuntu.developer.dev.appname",
"item_sku": "item_sku",
"purchase_id": "1",
"refundable_until": null,
"state": "Complete"
}
Note
In-app item purchases are not automatically refundable.
If backend_id and method_id are not specified, the user’s default
payment method will be used (if available).
If currency is not specified, USD will be assumed. This defines the
currency (and then, the amount) the user desires to be billed into; if the
specified application does not provide an amount for the given currency,
the USD amount will be used.
If the payment method to be used requires user interaction a URL will be returned in the response; to complete the purchase the user should follow that URL (previously OAuth signing it).
A possible response in that case could be:
HTTP/1.1 200 OK
Content-Type: application/json
{
"state": "InProgress",
"redirect_to": "https://some-url"
}
If a partner ID was provided, the header (X-Partner-Id) should also be added to this new request (to the URL where the user is redirected to).
GET /api/2.0/click/purchases/(package_name)/¶| Parameters: |
|
|---|---|
| Query Parameters: | |
|
|
| Status Codes: |
|
Also, a GET request will return the subscription details, if available (or a 404 response if not):
Warning
As this is an OAuth signed request, it’s important that the URL actually ends with a slash. Using a different URL, even if slightly (eg, missing trailing slash) will mean the OAuth signature validation will fail, resulting in a 401 Unauthorized response.
Request:
GET /api/2.0/click/purchases/com.ubuntu.developer.dev.appname/ HTTP/1.1
Accept: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"open_id": "https://login.staging.ubuntu.com/+id/open_id",
"package_name": "com.ubuntu.developer.dev.appname",
"refundable_until": "2015-07-15 18:46:21",
"state": "Complete"
}
If include_item_purchases was specified in the url, then a list of
subscription details will be returned which might include:
Note
If the package is gratis the list might not include a subscription for the package itself.
Request:
GET /api/2.0/click/purchases/com.ubuntu.developer.dev.appname/?include_item_purchases=true HTTP/1.1
Accept: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
[
{
"open_id": "https://login.staging.ubuntu.com/+id/open_id",
"package_name": "com.ubuntu.developer.dev.appname",
"refundable_until": "2015-07-15 18:46:21",
"state": "Complete"
},
{
"open_id": "https://login.staging.ubuntu.com/+id/open_id",
"package_name": "com.ubuntu.developer.dev.appname",
"item_sku": "item-1-sku",
"purchase_id": "1",
"refundable_until": null,
"state": "Complete"
}
]
If purchase_id was specified in the url, then only the item subscription
matching the purchase_id will be returned.
Request:
GET /api/2.0/click/purchases/com.ubuntu.developer.dev.appname/?purchase_id=1 HTTP/1.1
Accept: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"open_id": "https://login.staging.ubuntu.com/+id/open_id",
"package_name": "com.ubuntu.com.developer.dev.appname",
"item_sku": "item-1-sku",
"purchase_id": "1",
"refundable_until": null,
"state": "Complete"
}
GET /api/2.0/click/purchases/¶Finally, a GET request on /purchases/ will return a list of the current completed subscriptions for the user, which might include:
Note
If the package is gratis the list might not include a subscription for the package itself.
Warning
As this is an OAuth signed request, it’s important that the URL actually ends with a slash. Using a different URL, even if slightly (eg, missing trailing slash) will mean the OAuth signature validation will fail, resulting in a 401 Unauthorized response.
Request:
GET /api/2.0/click/purchases/ HTTP/1.1
Accept: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
[
{
"open_id": "https://login.staging.ubuntu.com/+id/open_id",
"package_name": "com.ubuntu.developer.dev.appname",
"refundable_until": "2015-07-15 18:46:21",
"state": "Complete"
},
{
"open_id": "https://login.staging.ubuntu.com/+id/open_id",
"package_name": "com.ubuntu.developer.dev.appname",
"item_sku": "item-1-sku",
"purchase_id": "1",
"refundable_until": null,
"state": "Complete"
},
{
"open_id": "https://login.staging.ubuntu.com/+id/open_id",
"package_name": "com.ubuntu.developer.dev.otherapp",
"refundable_until": "2015-07-17 11:33:29",
"state": "Complete"
}
]
POST /api/2.0/click/confirm-download/¶| Form Parameters: | |
|---|---|
|
|
Expected to be called once an application download is completed. This will trigger a payment capture 15 minutes later, if the application was paid, giving then a 15-minute window to allow a user request a self-refund.
If download is not confirmed, the payment will be captured (this is, completed) 1 hour after the purchase was completed.
An OAuth signed POST request with the package name is expected. In case the subscription does not exist, or is not completed, a failure response will be returned.
Warning
As this is an OAuth signed request, it’s important that the URL actually ends with a slash. Using a different URL, even if slightly (eg, missing trailing slash) will mean the OAuth signature validation will fail, resulting in a 401 Unauthorized response.
Request:
POST /api/2.0/click/confirm-download/ HTTP/1.1
Content-Type: application/json
{
"name": "com.ubuntu.developer.dev.appname"
}
Response (success):
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"success": true}
Response (failure):
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"success": false}
POST /api/2.0/click/refunds/¶| Form Parameters: | |
|---|---|
|
|
Between purchase completed/confirmed download and final payment capture there is time window in which a request to this API will cancel the payment without any other interaction (and without costs to the user or us).
self-refund window = MIN ((confirmed download + 15 minutes), (purchase completed + 1h))
An OAuth signed POST request with the package name is expected. In case the subscription does not exist, or refund time window expired, a {“success”: false} response will be returned.
Warning
As this is an OAuth signed request, it’s important that the URL actually ends with a slash. Using a different URL, even if slightly (eg, missing trailing slash) will mean the OAuth signature validation will fail, resulting in a 401 Unauthorized response.
Request:
POST /api/2.0/click/refunds/ HTTP/1.1
Content-Type: application/json
{
"name": "com.ubuntu.developer.dev.appname"
}
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"success": true}
import json
from urlparse import urljoin
import requests
from requests_oauthlib import OAuth1Session
# staging base URLs
PAY_URL = 'https://pay.staging.ubuntu.com/'
SCA_URL = 'https://myapps.developer.staging.ubuntu.com/'
SSO_URL = 'https://login.staging.ubuntu.com/'
# SSO existing user credentials
USER_EMAIL = 'some-user@example.com'
USER_PASSWORD = 'thepassword'
# staging click app with a purchase price set
CLICK_APP = 'com.ubuntu.developer.diegosarmentero.TabuGame.tabugame'
# get credential from SSO
# use a different token name? (if not, any refresh would work to allow purchases)
creds = requests.post(
urljoin(SSO_URL, '/api/v2/tokens/oauth'),
data=json.dumps(dict(email=USER_EMAIL,
password=USER_PASSWORD, token_name='clickbuy')),
headers={'content-type': 'application/json', 'accept': 'application/json'})
credentials = creds.json()
# create a session to work with
u1 = OAuth1Session(
credentials.get('consumer_key'), credentials.get('consumer_secret'),
credentials.get('token_key'), credentials.get('token_secret'))
# retrieve current available payment methods for this user
response = u1.get(
urljoin(SCA_URL, '/api/2.0/click/paymentmethods/'),
headers={'Accept': 'application/json'})
# adding a new payment method redirects
response = u1.get(urljoin(SCA_URL, '/api/2.0/click/paymentmethods/add/'))
response.history
## (<Response [302]>,)
response.url
## u'https://myapps.developer.staging.ubuntu.com/click/payment-method/add/'
# check if user already purchased the app
response = u1.get(
urljoin(SCA_URL, '/api/2.0/click/purchases/%s/' % CLICK_APP),
headers={'Accept': 'application/json'})
response.status_code
## 404
# retrieve current purchases information for this user
response = u1.get(
urljoin(SCA_URL, '/api/2.0/click/purchases/'),
headers={'Accept': 'application/json'})
response.status_code
## 200
response.json()
## []
# try to purchase the app
# (without refreshing the token, more than 15 minutes later)
response = u1.post(
urljoin(SCA_URL, '/api/2.0/click/purchases/'),
data=json.dumps({'name': CLICK_APP}),
headers={'Content-Type': 'application/json', 'Accept': 'application/json'})
response.status_code
## 401
response.json()
## {u'threshold': 900, u'error': u'TOKEN_NEEDS_REFRESH'}
# refresh token and retry purchase
# refresh token
response = requests.post(
urljoin(SSO_URL, '/api/v2/tokens/oauth'),
data=json.dumps(dict(email=USER_EMAIL,
password=USER_PASSWORD, token_name='clickbuy')),
headers={'content-type': 'application/json', 'accept': 'application/json'})
# purchase
response = u1.post(
urljoin(SCA_URL, '/api/2.0/click/purchases/'),
data=json.dumps({'name': CLICK_APP}),
headers={'Content-Type': 'application/json', 'Accept': 'application/json'})
response.status_code
## 201 (or 200 if user already purchased)
response.json()
## [{u'open_id': u'https://login.staging.ubuntu.com/+id/PDTAH8r',
## u'package_name': u'com.ubuntu.developer.diegosarmentero.TabuGame.tabugame',
## u'refundable_until': u'2015-07-15 18:46:21',
## u'state': u'Complete'}]
# retrieve current purchases information for this user
# (after the self-refund period expired)
response = u1.get(
urljoin(SCA_URL, '/api/2.0/click/purchases/'),
headers={'Accept': 'application/json'})
response.status_code
## 200
response.json()
## [{u'open_id': u'https://login.staging.ubuntu.com/+id/PDTAH8r',
## u'package_name': u'com.ubuntu.developer.diegosarmentero.TabuGame.tabugame',
## u'refundable_until': null,
## u'state': u'Complete'}]
# check if user already purchased the app
response = u1.get(
urljoin(SCA_URL, '/api/2.0/click/purchases/%s/' % CLICK_APP),
headers={'Accept': 'application/json'})
response.status_code
## 200
response.json()
## {u'open_id': u'https://login.staging.ubuntu.com/+id/PDTAH8r',
## u'package_name': u'com.ubuntu.developer.diegosarmentero.TabuGame.tabugame',
## u'state': u'Complete'}
# request self-refund
response = u1.post(
urljoin(SCA_URL, '/api/2.0/click/refunds/'),
data=json.dumps({'name': CLICK_APP}),
headers={'Content-Type': 'application/json', 'Accept': 'application/json'})
response.status_code
## 200
response.json()
## {u'success': true} (or {u'success': false} if refundable window expires)
# after previous initialization and credentials setup
# valid fake card details
card_data = {
'open_id': credentials.get('openid'),
'billing_address': {'street': 'Street',
'state': 'State',
'country_code': 'AR',
'postal_code': '5000'},
'card_type': 'Visa',
'card_holder': 'Test',
'card_number': '4111111111111111',
'card_ccv': '111',
'card_expiration_month': 12,
'card_expiration_year': 2021,
'currency': 'USD',
'make_unattended_default': True,
}
response = u1.post(
urljoin(PAY_URL, '/api/2.0/credit_card/'),
data=json.dumps(card_data),
headers={'content-type': 'application/json', 'accept': 'application/json'})
response.json()
## {u'card_type': u'Visa',
## u'expiration': u'2021-12-31',
## u'masked_number': u'1111'}
# retrieve current available payment methods for this user
response = u1.get(
urljoin(SCA_URL, '/api/2.0/click/paymentmethods/'),
headers={'Accept': 'application/json'})
response.json()
## [{u'choices': [{u'currencies': [u'USD'], u'id': 2547, u'preferred': True,
## u'description': u'**** **** **** 1111 (Visa, exp. 12/2021)'}, {u'currencies': [u'USD'], u'id': 2566,
## u'preferred': False, u'description': u'**** **** **** 1111 (Visa, exp. 03/2016)'}], u'id': u'credit_card',
## u'preferred': True, u'description': u'Credit or Debit Card'}]
response = u1.delete(
urljoin(SCA_URL, '/api/2.0/click/paymentmethods/credit_card/2566/'),
headers={'Accept': 'application/json'})
response
## <Response [200]>
response.json()
## {u'success': True}
response = u1.get(
urljoin(SCA_URL, '/api/2.0/click/paymentmethods/'),
headers={'Accept': 'application/json'})
response.json()
## [{u'choices': [{u'currencies': [u'USD'], u'id': 2547, u'preferred': True,
## u'description': u'**** **** **** 1111 (Visa, exp. 12/2021)'}], u'id': u'credit_card',
## u'preferred': True, u'description': u'Credit or Debit Card'}]