Skip to content

Commit f77b47d

Browse files
committed
fix marketplace flow
1 parent 9e317e9 commit f77b47d

File tree

9 files changed

+113
-50
lines changed

9 files changed

+113
-50
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ The product billing dimension name should be "apigateway" with description "Requ
148148

149149
### Marketplace Flow
150150

151-
When buyers subscribe through the AWS Marketplace console, the buyers browser will send a POST request to /marketplace-confirm/[USAGE_PLAN_ID] in your backend API. By default, this operation simply redirects the buyer to marketplace-subscribe.html in the developer portal.
151+
When buyers subscribe through the AWS Marketplace console, the buyers browser will send a POST request to /marketplace-confirm/[USAGE_PLAN_ID] in your backend API. By default, this redirects the request to the developer portal with the `usagePlanId` and `token` in the query string.
152152

153-
From here, the buyer is asked to login or register for the developer portal (or the existing session will be used). Once authenticated the buyer confirms the subscription and a PUT request is made to /marketplace-subscriptions/[USAGE_PLAN_ID].
153+
From here, the buyer is asked to login or register for the developer portal (or the existing session will be used). Once authenticated, a PUT request is made to /marketplace-subscriptions/[USAGE_PLAN_ID].
154154

155155
This operation makes a request to Marketplace Metering Service to resolve the buyer customer ID as well as the marketplace product code being subscribed to. By default, this operation simply associates the marketplace customer ID with the currently authenticated Cognito identity in DynamoDb for later use. It also calls API Gateway to get or create an API key for this user and associates this API Key with the marketplace customer ID.
156156

dev-portal/src/components/Register/index.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'
22
import { Button, Form, Message, Modal } from 'semantic-ui-react'
33
import { Redirect } from 'react-router-dom'
44
import { register } from '../../services/self'
5+
import { confirmMarketplaceSubscription } from '../../services/api-catalog'
56

67
export default class Register extends PureComponent {
78
state = {
@@ -18,8 +19,17 @@ import { register } from '../../services/self'
1819
_handleRegister(event, serializedForm) {
1920
event.preventDefault()
2021
this.setState({isSubmitting: true})
22+
2123
register(serializedForm.email, serializedForm.password)
22-
.then(() => this.setState({signedIn: true, isSubmitting: false, errorMessage: ''}))
24+
.then(() => {
25+
this.setState({signedIn: true, isSubmitting: false, errorMessage: ''})
26+
27+
const { usagePlanId, token } = this.props
28+
29+
if (usagePlanId && token) {
30+
return confirmMarketplaceSubscription(usagePlanId, token)
31+
}
32+
})
2333
.catch((e) => this.setState({errorMessage: e.message, isSubmitting: false}))
2434
}
2535

dev-portal/src/components/SignIn/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'
22
import { Button, Form, Message, Modal } from 'semantic-ui-react'
33
import { Redirect } from 'react-router-dom'
44
import { login } from '../../services/self'
5+
import { confirmMarketplaceSubscription } from '../../services/api-catalog'
56

67
export default class SignIn extends PureComponent {
78
state = {
@@ -19,7 +20,15 @@ import { login } from '../../services/self'
1920
this.setState({isSubmitting: true})
2021

2122
login(serializedForm.email, serializedForm.password)
22-
.then(() => this.setState({signedIn: true, isSubmitting: false, errorMessage: ''}))
23+
.then(() => {
24+
this.setState({signedIn: true, isSubmitting: false, errorMessage: ''})
25+
26+
const { usagePlanId, token } = this.props
27+
28+
if (usagePlanId && token) {
29+
return confirmMarketplaceSubscription(usagePlanId, token)
30+
}
31+
})
2332
.catch((e) => this.setState({errorMessage: e.message, isSubmitting: false}))
2433
}
2534

dev-portal/src/pages/Home.js

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,58 @@
1-
import React from 'react'
1+
import React, { PureComponent } from 'react'
22
import { Link } from 'react-router-dom'
33
import { Container, Segment, Divider, Card } from 'semantic-ui-react'
44
import SignIn from '../components/SignIn'
55
import Register from '../components/Register'
66
import { isAuthenticated } from '../services/self'
7+
import { confirmMarketplaceSubscription } from '../services/api-catalog'
8+
import { getQueryString } from '../services/misc'
79

8-
export default (props) => (
9-
<Container>
10-
<Card.Group itemsPerRow={3} stackable style={{textAlign: 'center'}}>
11-
<Card>
12-
<Card.Content>
13-
<Card.Header><Link to='/case-studies'>Case Studies</Link></Card.Header>
14-
<Card.Description>Want to learn about what you can achieve by integrating with our APIs? The possibilities are endless, but <Link to='/case-studies'>here are just a few examples</Link>.</Card.Description>
15-
</Card.Content>
16-
</Card>
17-
<Card>
18-
<Card.Content>
19-
<Card.Header><Link to='/apis'>APIs</Link></Card.Header>
20-
<Card.Description><Link to='/apis'>See what APIs we have on offer</Link>, including extensive documentation. Sign in to manage your subscriptions, see your current usage, get your API Key, and test against our live API.</Card.Description>
21-
</Card.Content>
22-
</Card>
23-
<Card>
24-
<Card.Content>
25-
<Card.Header><Link to='/getting-started'>Getting Started</Link></Card.Header>
26-
<Card.Description>Ready to get started? This is the place that answers all your questions. We'll have you up and running in no time. <Link to='/getting-started'>Let's get started!</Link></Card.Description>
27-
</Card.Content>
28-
</Card>
29-
</Card.Group>
30-
{ isAuthenticated() ? '' : (<Segment padded>
31-
<SignIn />
32-
<Divider horizontal>Or</Divider>
33-
<Register />
34-
</Segment>) }
35-
</Container>
36-
)
10+
export default class HomePage extends PureComponent {
11+
constructor() {
12+
super()
13+
this.state = {}
14+
15+
const { usagePlanId, token } = getQueryString()
16+
if (usagePlanId && token) {
17+
this.state = { usagePlanId, token }
18+
19+
if (isAuthenticated()) {
20+
confirmMarketplaceSubscription(usagePlanId, token).then(() => {
21+
window.location.href = '/apis'
22+
})
23+
}
24+
}
25+
}
26+
27+
render() {
28+
return (
29+
<Container>
30+
<Card.Group itemsPerRow={3} stackable style={{textAlign: 'center'}}>
31+
<Card>
32+
<Card.Content>
33+
<Card.Header><Link to='/case-studies'>Case Studies</Link></Card.Header>
34+
<Card.Description>Want to learn about what you can achieve by integrating with our APIs? The possibilities are endless, but <Link to='/case-studies'>here are just a few examples</Link>.</Card.Description>
35+
</Card.Content>
36+
</Card>
37+
<Card>
38+
<Card.Content>
39+
<Card.Header><Link to='/apis'>APIs</Link></Card.Header>
40+
<Card.Description><Link to='/apis'>See what APIs we have on offer</Link>, including extensive documentation. Sign in to manage your subscriptions, see your current usage, get your API Key, and test against our live API.</Card.Description>
41+
</Card.Content>
42+
</Card>
43+
<Card>
44+
<Card.Content>
45+
<Card.Header><Link to='/getting-started'>Getting Started</Link></Card.Header>
46+
<Card.Description>Ready to get started? This is the place that answers all your questions. We'll have you up and running in no time. <Link to='/getting-started'>Let's get started!</Link></Card.Description>
47+
</Card.Content>
48+
</Card>
49+
</Card.Group>
50+
{ isAuthenticated() ? '' : (<Segment padded>
51+
<SignIn usagePlanId={this.state.usagePlanId} token={this.state.token} />
52+
<Divider horizontal>Or</Divider>
53+
<Register usagePlanId={this.state.usagePlanId} token={this.state.token} />
54+
</Segment>) }
55+
</Container>
56+
)
57+
}
58+
}

dev-portal/src/services/misc.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export function getQueryString() {
2+
const { search: q } = window.location
3+
4+
if (!q) return {}
5+
6+
return (/^[?#]/.test(q) ? q.slice(1) : q)
7+
.split('&')
8+
.reduce((params, param) => {
9+
let [ key, value ] = param.split('=');
10+
params[key] = value ? decodeURIComponent(value) : '';
11+
return params;
12+
}, { })
13+
}

lambdas/_common/customers-controller.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
'use strict'
22
const AWS = require('aws-sdk')
3-
const doc = require('dynamodb-doc')
43

5-
const dynamodb = new doc.DynamoDB()
6-
const dynamodbdoc = new AWS.DynamoDB.DocumentClient()
4+
const dynamoDb = new AWS.DynamoDB.DocumentClient()
75
const apigateway = new AWS.APIGateway()
86

97
const customersTable = 'DevPortalCustomers'
@@ -18,7 +16,7 @@ function ensureCustomerItem(cognitoIdentityId, keyId, error, callback) {
1816
Id: customerId
1917
}
2018
}
21-
dynamodbdoc.get(getParams, (err, data) => {
19+
dynamoDb.get(getParams, (err, data) => {
2220
if (err) {
2321
error(err)
2422
} else if (data.Item === undefined) {
@@ -29,7 +27,8 @@ function ensureCustomerItem(cognitoIdentityId, keyId, error, callback) {
2927
ApiKeyId: keyId
3028
}
3129
}
32-
dynamodb.putItem(putParams, (customerErr, customerData) => {
30+
31+
dynamoDb.putItem(putParams, (customerErr, customerData) => {
3332
if (customerErr) {
3433
error(customerErr)
3534
} else {
@@ -54,7 +53,7 @@ function getCognitoIdentityId(marketplaceCustomerId, error, callback) {
5453
},
5554
ProjectionExpression: "MarketplaceCustomerId, Id"
5655
}
57-
dynamodbdoc.query(params, (err, data) => {
56+
dynamoDb.query(params, (err, data) => {
5857
if (err) {
5958
error(err)
6059
} else if (data.Items === undefined || data.Items.length === 0) {
@@ -211,7 +210,7 @@ function getUsagePlanForProductCode(productCode, error, callback) {
211210
error(err)
212211
} else {
213212
console.log(`Got usage plans ${JSON.stringify(data.items)}`)
214-
213+
215214
// note: ensure that only one usage plan maps to a given marketplace product code
216215
const usageplan = data.items.find(function (item) {
217216
return item.productCode !== undefined && item.productCode === productCode
@@ -242,7 +241,7 @@ function updateCustomerMarketplaceId(cognitoIdentityId, marketplaceCustomerId, e
242241

243242
// update DDB customer record with marketplace customer id
244243
// and update API Gateway API Key with marketplace customer id
245-
dynamodbdoc.update(dynamoDbParams, (dynamoDbErr) => {
244+
dynamoDb.update(dynamoDbParams, (dynamoDbErr) => {
246245
if (dynamoDbErr) {
247246
error(dynamoDbErr)
248247
} else {
@@ -310,7 +309,7 @@ function updateCustomerApiKeyId(cognitoIdentityId, apiKeyId, error, success) {
310309
}
311310
}
312311

313-
dynamodbdoc.update(dynamoDbParams, (dynamoDbErr) => {
312+
dynamoDb.update(dynamoDbParams, (dynamoDbErr) => {
314313
if (dynamoDbErr) {
315314
error(dynamoDbErr)
316315
} else {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const app = require('./express-server')
2+
const port = 4000
3+
4+
app.listen(port)
5+
console.log(`listening on http://localhost:${port}`)

lambdas/backend/express-server.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,9 @@ app.post('/marketplace-confirm/:usagePlanId', (req, res) => {
200200
console.log(`Marketplace token: ${marketplaceToken}`)
201201
const usagePlanId = req.params.usagePlanId
202202

203-
// TODO: update to new location of marketplace-subscribe route
204203
// WARNING: the redirect URL should be HTTPS as the token is subject to MITM attacks over HTTP. Token expires after 60min
205204
// ideally this should be saved in a secure manner (i.e. DDB) until the subscription completes
206-
const confirmUrl = `${baseUrl}marketplace-subscribe.html?usagePlanId=${usagePlanId}&token=${marketplaceToken}`
205+
const confirmUrl = `${baseUrl}?usagePlanId=${usagePlanId}&token=${marketplaceToken}`
207206

208207
// redirect to the registration/login page
209208
res.redirect(302, confirmUrl)
@@ -212,9 +211,9 @@ app.post('/marketplace-confirm/:usagePlanId', (req, res) => {
212211
app.put('/marketplace-subscriptions/:usagePlanId', (req, res) => {
213212
const marketplaceToken = req.body.token
214213
const usagePlanId = req.params.usagePlanId
214+
console.log(`Marketplace token: ${marketplaceToken} usage plan id: ${usagePlanId}`)
215215
const cognitoIdentityId = getCognitoIdentityId(req)
216-
217-
console.log(`Marketplace token: ${marketplaceToken} usage plan id: ${usagePlanId} cognito id: ${cognitoIdentityId}`)
216+
console.log(`cognito id: ${cognitoIdentityId}`)
218217

219218
function error(data) {
220219
console.log(`error: ${data}`)
@@ -225,6 +224,10 @@ app.put('/marketplace-subscriptions/:usagePlanId', (req, res) => {
225224
res.status(200).json(data)
226225
}
227226

227+
function subscribeCustomerToUsagePlan(data) {
228+
customersController.subscribe(cognitoIdentityId, usagePlanId, error, success)
229+
}
230+
228231
const marketplace = new AWS.MarketplaceMetering()
229232

230233
const params = {
@@ -235,14 +238,14 @@ app.put('/marketplace-subscriptions/:usagePlanId', (req, res) => {
235238
marketplace.resolveCustomer(params, (err, data) => {
236239
if (err) {
237240
console.log(`marketplace error: ${JSON.stringify(err)}`)
238-
res.status(400).json(err.data.message)
241+
res.status(400).json(err.message)
239242
} else {
240243
console.log(`marketplace data: ${JSON.stringify(data)}`)
241244

242245
// persist the marketplaceCustomerId in DDB
243246
// this is used when the subscription listener receives the subscribe notification
244247
const marketplaceCustomerId = data.CustomerIdentifier
245-
customersController.updateCustomerMarketplaceId(cognitoIdentityId, marketplaceCustomerId, error, success)
248+
customersController.updateCustomerMarketplaceId(cognitoIdentityId, marketplaceCustomerId, error, subscribeCustomerToUsagePlan)
246249
}
247250
})
248251
})

lambdas/backend/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
"description": "Example application for running a Serverless Developer Portal with API Gateway and Lambda",
55
"main": "index.js",
66
"config": {},
7-
"scripts": {},
7+
"scripts": {
8+
"local": "node ./express-server-local"
9+
},
810
"license": "Apache-2.0",
911
"dependencies": {
1012
"aws-sdk": "^2.7.10",

0 commit comments

Comments
 (0)