Top Banner
The Recurring Nightmare Rosa Gutiérrez AMSTERDAM 11-12 MAY 2016
59

The recurring nightmare - Rosa Gutierrez - Codemotion Amsterdam 2016

Apr 16, 2017

Download

Technology

Codemotion
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

The Recurring NightmareRosa Gutiérrez

AMSTERDAM 11-12 MAY 2016

Page 2: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Rosa Gutiérrez

@rosapolis@rosaSoftware engineer at Plex

Page 3: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016
Page 4: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016
Page 5: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016
Page 6: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

The Recurring Nightmare

Cross platform in-app subscription purchases

Page 7: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

In-app purchases

Purchases made from within a mobile app

Everything processed via the mobile platform provider

(in exchange for ~30% of

the money spent)

Page 8: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

One-time vs. recurring in-app purchases

One-timeStore (checkout, transactions…)

Client API

Page 9: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

One-timeStore (checkout, transactions…)

Client API

Recurring

Our backend

Store (checkout, transactions…)

Client API

One-time vs. recurring in-app purchases

Server side APIOur API

Page 10: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

What we need to do

● Verification and fraud prevention

● Grant subscription benefits globally

● Revoke benefits when recurring payments stop

● Reactivate benefits when payments start again

Page 11: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

The app store server-side API of our dreams

Page 12: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

The app store server-side API of our dreams

Simple unique IDs to get status and all relevant info about subscriptions

Page 13: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

The app store server-side API of our dreams

Webhook system to notify changes

Page 14: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

The app store server-side API of our dreams

Allow cancellations, refunds

Page 15: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

The app store server-side API of our dreams

Good sandbox environment to test

Page 16: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

The reality is a bit different...

iTunes Google Play Amazon Appstore

Simple IDs

Webhooks

Cancel, refund

Relevant info

Good sandbox

Page 17: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

The reality is a bit different...

iTunes Google Play Amazon Appstore

Simple IDs

Webhooks

Cancel, refund

Relevant info

Good sandbox

No quirks and traps

Page 18: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

iTunes “Simple IDs to query purchases you said?”

Page 19: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

ewoJInNpZ25hdHVyZSIgPSAiQXJHdWUxa1dYYWhuVEZpU3hKVjhFbXFBaVB5UWhZQUpTNjFDNE1hUzZucWZHSUN4UTZKakRjL1Irczd1SUZBRUU3VDBOVEhkcjA4QXg3R0ZpU3VCeXdBcVUzMEZrRkdCTURUS3VudXA5Qm81eHpTV21TTVZlWmtBUVFXODVqTkNTaGhHTjdTc2hOTWpMZ3NUaGVnd1J3OHhNaTNSYVIxakoxMzllK0x2OTJPL0FBQURWekNDQTFNd2dnSTdvQU1DQVFJQ0NCdXA0K1BBaG0vTE1BMEdDU3FHU0liM0RRRUJCUVVBTUg4eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURXpNREVHQTFVRUF3d3FRWEJ3YkdVZ2FWUjFibVZ6SUZOMGIzSmxJRU5sY25ScFptbGpZWFJwYjI0Z1FYVjBhRzl5YVhSNU1CNFhEVEUwTURZd056QXdNREl5TVZvWERURTJNRFV4T0RFNE16RXpNRm93WkRFak1DRUdBMVVFQXd3YVVIVnlZMmhoYzJWU1pXTmxhWEIwUTJWeWRHbG1hV05oZEdVeEd6QVpCZ05WQkFzTUVrRndjR3hsSUdsVWRXNWxjeUJUZEc5eVpURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd2daOHdEUVlKS29aSWh2Y05BUUVCQlFBRGdZMEFNSUdKQW9HQkFNbVRFdUxnamltTHdSSnh5MW9FZjBlc1VORFZFSWU2d0Rzbm5hbDE0aE5CdDF2MTk1WDZuOTNZTzdnaTNvclBTdXg5RDU1NFNrTXArU2F5Zzg0bFRjMzYyVXRtWUxwV25iMzRucXlHeDlLQlZUeTVPR1Y0bGpFMU93QytvVG5STStRTFJDbWVOeE1iUFpoUzQ3VCtlWnRERWhWQjl1c2szK0pNMkNvZ2Z3bzdBZ01CQUFHamNqQndNQjBHQTFVZERnUVdCQlNKYUVlTnVxOURmNlpmTjY4RmUrSTJ1MjJzc0RBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkRZZDZPS2RndElCR0xVeWF3N1hRd3VSV0VNNk1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0JnVUJCQUlGQURBTkJna3Foa2lHOXcwQkFRVUZBQU9DQVFFQWVhSlYyVTUxcnhmY3FBQWU1QzIvZkVXOEtVbDRpTzRsTXV0YTdONlh6UDFwWkl6MU5ra0N0SUl3ZXlOajVVUllISytIalJLU1U5UkxndU5sMG5rZnhxT2JpTWNrd1J1ZEtTcTY5Tkluclp5Q0Q2NlI0Szc3bmI5bE1UQUJTU1lsc0t0OG9OdGxoZ1IvMWtqU1NSUWNIa3RzRGNTaVFHS01ka1NscDRBeVhmN3ZuSFBCZTR5Q3dZVjJQcFNOMDRrYm9pSjNwQmx4c0d3Vi9abEwyNk0ydWVZSEtZQ3VYaGRxRnd4VmdtNTJoM29lSk9PdC92WTRFY1FxN2VxSG02bTAzWjliN1BSellNMktHWEhEbU9Nazd2RHBlTVZsTERQU0dZejErVTNzRHhKemViU3BiYUptVDdpbXpVS2ZnZ0VZN3h4ZjRjemZIMHlqNXdOelNHVE92UT09IjsKCSJwdXJjaGFzZS1pbmZvIiA9ICJld29KSW05eWFXZHBibUZzTFhCMWNtTm9ZWE5sTFdSaGRHVXRjSE4wSWlBOUlDSXlNREUxTFRBMkxUSTUKSURBNU9qRTVPakV5SUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93b0pJbkIxY21Ob1lYTmxMV1JoCmRHVXRiWE1pSUQwZ0lqRTBNelUxT1RVMk5URXdNREFpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeQpJaUE5SUNJNE1EWmxaRFUwTnpCbVpqVmtNVFl3T0RWak9EY3dNemN5WldZMVkySTRNVEZrWTJRMk5qWTEKSWpzS0NTSnZjbWxuYVc1aGJDMTBjbUZ1YzJGamRHbHZiaTFwWkNJZ1BTQWlNVEF3TURBd01ERTJNVEkzCk5UZzBNaUk3Q2draVpYaHdhWEpsY3kxa1lYUmxJaUE5SUNJeE5ETTFOVGsxT1RVeE1EQXdJanNLQ1NKMApjbUZ1YzJGamRHbHZiaTFwWkNJZ1BTQWlNVEF3TURBd01ERTJNVEkzT0RnMU5pSTdDZ2tpYjNKcFoybHUKWVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFF6TlRVNU5EYzFNakF3TUNJN0Nna2lkMlZpCkxXOXlaR1Z5TFd4cGJtVXRhWFJsYlMxcFpDSWdQU0FpTVRBd01EQXdNREF6TURBME5UWTNNaUk3Q2draQpZblp5Y3lJZ1BTQWlNUzR3SWpzS0NTSjFibWx4ZFdVdGRtVnVaRzl5TFdsa1pXNTBhV1pwWlhJaUlEMGcKSWtSRk16Y3lSREV5TFVNNE5qWXRORUV6UkMxQk16VTNMVFZETXpOR1FqZ3pNa0ZFUXlJN0Nna2laWGh3CmFYSmxjeTFrWVhSbExXWnZjbTFoZEhSbFpDMXdjM1FpSUQwZ0lqSXdNVFV0TURZdE1qa2dNRGs2TXprNgpNVEVnUVcxbGNtbGpZUzlNYjNOZlFXNW5aV3hsY3lJN0Nna2lhWFJsYlMxcFpDSWdQU0FpTVRBeE16azIKTlRRMU5DSTdDZ2tpWlhod2FYSmxjeTFrWVhSbExXWnZjbTFoZEhSbFpDSWdQU0FpTWpBeE5TMHdOaTB5Ck9TQXhOam96T1RveE1TQkZkR012UjAxVUlqc0tDU0p3Y205a2RXTjBMV2xrSWlBOUlDSmpiMjB1YlhsaApjSEF1WVhCd0xtbHVZWEF1Y0dGemN5NXRiMjUwYUd4NWMzVmljMk55YVhCMGFXOXVJanNLQ1NKd2RYSmoKYUdGelpTMWtZWFJsSWlBOUlDSXlNREUxTFRBMkxUSTVJREUyT2pNME9qRXhJRVYwWXk5SFRWUWlPd29KCkltOXlhV2RwYm1Gc0xYQjFjbU5vWVhObExXUmhkR1VpSUQwZ0lqSXdNVFV0TURZdE1qa2dNVFk2TVRrNgpNVElnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHViWGxoY0hBdVlYQndJanNLQ1NKd2RYSmoKYUdGelpTMWtZWFJsTFhCemRDSWdQU0FpTWpBeE5TMHdOaTB5T1NBd09Ub3pORG94TVNCQmJXVnlhV05oCkwweHZjMTlCYm1kbGJHVnpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q24wPSI7CgkiZW52aXJvbm1lbnQiID0gIlNhbmRib3giOwoJInBvZCIgPSAiMTAwIjsKCSJzaWduaW5nLXN0YXR1cyIgPSAiMCI7Cn0=

iTunes “Simple IDs to query purchases you said?”

Receipt data

Page 20: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Our backend

iTunes

huge base64encoded receipt

huge base64encoded receipt

huge ...receipt response with

subscription data

Page 21: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

iTunes

payload = { "receipt-data" => @receipt, "password" => self.class.password}

resp = HTTParty.post url, :body => payload.to_json

Send to iTunes for validation

Sandbox or production

App's shared secret

Page 22: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

iTunes

payload = { "receipt-data" => @receipt, "password" => self.class.password}

resp = HTTParty.post url, :body => payload.to_json

Send to iTunes for validation

Parse response{"receipt":{... <json receipt data> ...},

"latest_receipt_info":{... <json latest receipt data> ...}

"status":0, # error codes 21000 to 21008

"latest_receipt":<huge base64 encoded receipt data> }

Store latest receipt for next time

Page 23: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

iTunes{

"purchase-date-ms": "1435595651000",

"unique-identifier": "806ed5470ff5d16085c870372ef5cb811dcd6665",

"original-transaction-id": "1000000161275842",

"expires-date": "1435595951000",

"transaction-id": "1000000161278856",

"original-purchase-date-ms": "1435594752000",

"item-id": "1014965453",

"product-id": "com.myapp.app.inap.discworldpass.monthlysubscription",

"purchase-date": "2015-06-29 16:34:11 Etc/GMT",

"bid": "com.myapp.app",

...

}

JSON Receipt data

Page 24: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

iTunes

Always check duplicates "transaction-id" => "1000000161278856"

Always check product id "product-id" => "com.zeptolab.ctrbonus.superpower1"

Page 25: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

ewoJInNpZ25hdHVyZSIgPSAiQXJHdWUxa1dYYWhuVEZpU3hKVjhFbXFBaVB5UWhZQUpTNjFDNE1hUzZucWZHSUN4UTZKakRjL1Irczd1SUZBRUU3VDBOVEhkcjA4QXg3R0ZpU3VCeXdBcVUzMEZrRkdCTURUS3VudXA5Qm81eHpTV21TTVZlWmtBUVFXODVqTkNTaGhHTjdTc2hOTWpMZ3NUaGVnd1J3OHhNaTNSYVIxakoxMzllK0x2OTJPL0FBQURWekNDQTFNd2dnSTdvQU1DQVFJQ0NCdXA0K1BBaG0vTE1BMEdDU3FHU0liM0RRRUJCUVVBTUg4eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURXpNREVHQTFVRUF3d3FRWEJ3YkdVZ2FWUjFibVZ6SUZOMGIzSmxJRU5sY25ScFptbGpZWFJwYjI0Z1FYVjBhRzl5YVhSNU1CNFhEVEUwTURZd056QXdNREl5TVZvWERURTJNRFV4T0RFNE16RXpNRm93WkRFak1DRUdBMVVFQXd3YVVIVnlZMmhoYzJWU1pXTmxhWEIwUTJWeWRHbG1hV05oZEdVeEd6QVpCZ05WQkFzTUVrRndjR3hsSUdsVWRXNWxjeUJUZEc5eVpURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd2daOHdEUVlKS29aSWh2Y05BUUVCQlFBRGdZMEFNSUdKQW9HQkFNbVRFdUxnamltTHdSSnh5MW9FZjBlc1VORFZFSWU2d0Rzbm5hbDE0aE5CdDF2MTk1WDZuOTNZTzdnaTNvclBTdXg5RDU1NFNrTXArU2F5Zzg0bFRjMzYyVXRtWUxwV25iMzRucXlHeDlLQlZUeTVPR1Y0bGpFMU93QytvVG5STStRTFJDbWVOeE1iUFpoUzQ3VCtlWnRERWhWQjl1c2szK0pNMkNvZ2Z3bzdBZ01CQUFHamNqQndNQjBHQTFVZERnUVdCQlNKYUVlTnVxOURmNlpmTjY4RmUrSTJ1MjJzc0RBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkRZZDZPS2RndElCR0xVeWF3N1hRd3VSV0VNNk1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0JnVUJCQUlGQURBTkJna3Foa2lHOXcwQkFRVUZBQU9DQVFFQWVhSlYyVTUxcnhmY3FBQWU1QzIvZkVXOEtVbDRpTzRsTXV0YTdONlh6UDFwWkl6MU5ra0N0SUl3ZXlOajVVUllISytIalJLU1U5UkxndU5sMG5rZnhxT2JpTWNrd1J1ZEtTcTY5Tkluclp5Q0Q2NlI0Szc3bmI5bE1UQUJTU1lsc0t0OG9OdGxoZ1IvMWtqU1NSUWNIa3RzRGNTaVFHS01ka1NscDRBeVhmN3ZuSFBCZTR5Q3dZVjJQcFNOMDRrYm9pSjNwQmx4c0d3Vi9abEwyNk0ydWVZSEtZQ3VYaGRxRnd4VmdtNTJoM29lSk9PdC92WTRFY1FxN2VxSG02bTAzWjliN1BSellNMktHWEhEbU9Nazd2RHBlTVZsTERQU0dZejErVTNzRHhKemViU3BiYUptVDdpbXpVS2ZnZ0VZN3h4ZjRjemZIMHlqNXdOelNHVE92UT09IjsKCSJwdXJjaGFzZS1pbmZvIiA9ICJld29KSW05eWFXZHBibUZzTFhCMWNtTm9ZWE5sTFdSaGRHVXRjSE4wSWlBOUlDSXlNREUxTFRBMkxUSTUKSURBNU9qRTVPakV5SUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93b0pJbkIxY21Ob1lYTmxMV1JoCmRHVXRiWE1pSUQwZ0lqRTBNelUxT1RVMk5URXdNREFpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeQpJaUE5SUNJNE1EWmxaRFUwTnpCbVpqVmtNVFl3T0RWak9EY3dNemN5WldZMVkySTRNVEZrWTJRMk5qWTEKSWpzS0NTSnZjbWxuYVc1aGJDMTBjbUZ1YzJGamRHbHZiaTFwWkNJZ1BTQWlNVEF3TURBd01ERTJNVEkzCk5UZzBNaUk3Q2draVpYaHdhWEpsY3kxa1lYUmxJaUE5SUNJeE5ETTFOVGsxT1RVeE1EQXdJanNLQ1NKMApjbUZ1YzJGamRHbHZiaTFwWkNJZ1BTQWlNVEF3TURBd01ERTJNVEkzT0RnMU5pSTdDZ2tpYjNKcFoybHUKWVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFF6TlRVNU5EYzFNakF3TUNJN0Nna2lkMlZpCkxXOXlaR1Z5TFd4cGJtVXRhWFJsYlMxcFpDSWdQU0FpTVRBd01EQXdNREF6TURBME5UWTNNaUk3Q2draQpZblp5Y3lJZ1BTQWlNUzR3SWpzS0NTSjFibWx4ZFdVdGRtVnVaRzl5TFdsa1pXNTBhV1pwWlhJaUlEMGcKSWtSRk16Y3lSREV5TFVNNE5qWXRORUV6UkMxQk16VTNMVFZETXpOR1FqZ3pNa0ZFUXlJN0Nna2laWGh3CmFYSmxjeTFrWVhSbExXWnZjbTFoZEhSbFpDMXdjM1FpSUQwZ0lqSXdNVFV0TURZdE1qa2dNRGs2TXprNgpNVEVnUVcxbGNtbGpZUzlNYjNOZlFXNW5aV3hsY3lJN0Nna2lhWFJsYlMxcFpDSWdQU0FpTVRBeE16azIKTlRRMU5DSTdDZ2tpWlhod2FYSmxjeTFrWVhSbExXWnZjbTFoZEhSbFpDSWdQU0FpTWpBeE5TMHdOaTB5Ck9TQXhOam96T1RveE1TQkZkR012UjAxVUlqc0tDU0p3Y205a2RXTjBMV2xrSWlBOUlDSmpiMjB1YlhsaApjSEF1WVhCd0xtbHVZWEF1Y0dGemN5NXRiMjUwYUd4NWMzVmljMk55YVhCMGFXOXVJanNLQ1NKd2RYSmoKYUdGelpTMWtZWFJsSWlBOUlDSXlNREUxTFRBMkxUSTVJREUyT2pNME9qRXhJRVYwWXk5SFRWUWlPd29KCkltOXlhV2RwYm1Gc0xYQjFjbU5vWVhObExXUmhkR1VpSUQwZ0lqSXdNVFV0TURZdE1qa2dNVFk2TVRrNgpNVElnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHViWGxoY0hBdVlYQndJanNLQ1NKd2RYSmoKYUdGelpTMWtZWFJsTFhCemRDSWdQU0FpTWpBeE5TMHdOaTB5T1NBd09Ub3pORG94TVNCQmJXVnlhV05oCkwweHZjMTlCYm1kbGJHVnpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q24wPSI7CgkiZW52aXJvbm1lbnQiID0gIlNhbmRib3giOwoJInBvZCIgPSAiMTAwIjsKCSJzaWduaW5nLXN0YXR1cyIgPSAiMCI7Cn0=

iTunes “Simple IDs to query purchases you said?”

Receipt data

Page 26: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

{ "signature" = "ArGue1kWXahnTFiSxJV8EmqAiPyQhYAJS61C4MaS6nqfGICxQ6JjDc/R+s7uIFAEE7T0NTHdr08Ax7GFiSuBywAqU30FkFGBMDTKunup9Bo5xzSWmSMVeZkAQQW85jNCShhGN7SshNMjLgsThegwRw8xMi3RaR1jJ139e+Lv92O/AAADVzCCA1MwggI7oAMCAQICCBup4+PAhm/LMA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEzMDEGA1UEAwwqQXBwbGUgaVR1bmVzIFN0b3JlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTE0MDYwNzAwMDIyMVoXDTE2MDUxODE4MzEzMFowZDEjMCEGA1UEAwwaUHVyY2hhc2VSZWNlaXB0Q2VydGlmaWNhdGUxGzAZBgNVBAsMEkFwcGxlIGlUdW5lcyBTdG9yZTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMmTEuLgjimLwRJxy1oEf0esUNDVEIe6wDsnnal14hNBt1v195X6n93YO7gi3orPSux9D554SkMp+Sayg84lTc362UtmYLpWnb34nqyGx9KBVTy5OGV4ljE1OwC+oTnRM+QLRCmeNxMbPZhS47T+eZtDEhVB9usk3+JM2Cogfwo7AgMBAAGjcjBwMB0GA1UdDgQWBBSJaEeNuq9Df6ZfN68Fe+I2u22ssDAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFDYd6OKdgtIBGLUyaw7XQwuRWEM6MA4GA1UdDwEB/wQEAwIHgDAQBgoqhkiG92NkBgUBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAeaJVxVgm52h3oeJOOt/vY4EcQq7eqHm6m03Z9b7PRzYM2KGXHDmOMk7vDpeMVlLDPSGYz1+U3sDxJzebSpbaJmT7imzUKfggEY7xxf4czfH0yj5wNzSGTOvA==";"purchase-info"="ewoJIm9yaWdpbmFsLXB1cmNoYXNlLWRhdGUtcHN0IiA9ICIyMDE1LTA2LTI5IDA5OjE5OjEyIEFtZXJpY2EvTG9zX0FuZ2VsZXMiOwoJInB1cmNoYXNlLWRhdGUtbXMiID0gIjE0MzU1OTU2NTEwMDAiOwoJInVuaXF1ZS1pZGVudGlmaWVyIiA9ICI4MDZlZDU0NzBmZjVkMTYwODVjODcwMzcyZWY1Y2I4MTFkY2Q2NjY1IjsKCSJvcmlnaW5hbC10cmFuc2FjdGlvbi1pZCIgPSAiMTAwMDAwMDE2MTI3NTg0MiI7CgkiZXhwaXJlcy1kYXRlIiA9ICIxNDM1NTk1OTUxMDAwIjsKCSJ0cmFuc2FjdGlvbi1pZCIgPSAiMTAwMDAwMDE2MTI3ODg1NiI7Cgkib3JpZ2luYWwtcHVyY2hhc2UtZGF0ZS1tcyIgPSAiMTQzNTU5NDc1MjAwMCI7Cgkid2ViLW9yZGVyLWxpbmUtaXRlbS1pZCIgPSAiMTAwMDAwMDAzMDA0NTY3MiI7CgkiYnZycyIgPSAiMS4wIjsKCSJ1bmlxdWUtdmVuZG9yLWlkZW50aWZpZXIiID0gIkRFMzcyRDEyLUM4NjYtNEEzRC1BMzU3LTVDMzNGQjgzMkFEQyI7CgkiZXhwaXJlcy1kYXRlLWZvcm1hdHRlZC1wc3QiID0gIjIwMTUtMDYtMjkgMDk6Mzk6MTEgQW1lcmljYS9Mb3NfQW5nZWxlcyI7CgkiaXRlbS1pZCIgPSAiMTAxMzk2NTQ1NCI7CgkiZXhwaXJlcy1kYXRlLWZvcm1hdHRlZCIgPSAiMjAxNS0wNi0yOSAxNjozOToxMSBFdGMvR01UIjsKCSJwcm9kdWN0LWlkIiA9ICJjb20ubXlhcHAuYXBwLmluYXAucGFzcy5tb250aGx5c3Vic2NyaXB0aW9uIjsKCSJwdXJjaGFzZS1kYXRlIiA9ICIyMDE1LTA2LTI5IDE2OjM0OjExIEV0Yy9HTVQiOwoJIm9yaWdpbmFsLXB1cmNoYXNlLWRhdGUiID0gIjIwMTUtMDYtMjkgMTY6MTk6MTIgRXRjL0dNVCI7CgkiYmlkIiA9ICJjb20ubXlhcHAuYXBwIjsKCSJwdXJjaGFzZS1kYXRlLXBzdCIgPSAiMjAxNS0wNi0yOSAwOTozNDoxMSBBbWVyaWNhL0xvc19BbmdlbGVzIjsKCSJxdWFudGl0eSIgPSAiMSI7Cn0="; "environment" = "Sandbox"; "pod" = "100"; "signing-status" = "0";}

iTunes

"purchase-info"

Page 27: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

{ "signature" = "ArGue1kWXahnTFiSxJV8EmqAiPyQhYAJS61C4MaS6nqfGICxQ6JjDc/R+s7uIFAEE7T0NTHdr08Ax7GFiSuBywAqU30FkFGBMDTKunup9Bo5xzSWmSMVeZkAQQW85jNCShhGN7SshNMjLgsThegwRw8xMi3RaR1jJ139e+Lv92O/AAADVzCCA1MwggI7oAMCAQICCBup4+PAhm/LMA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEzMDEGA1UEAwwqQXBwbGUgaVR1bmVzIFN0b3JlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTE0MDYwNzAwMDIyMVoXDTE2MDUxODE4MzEzMFowZDEjMCEGA1UEAwwaUHVyY2hhc2VSZWNlaXB0Q2VydGlmaWNhdGUxGzAZBgNVBAsMEkFwcGxlIGlUdW5lcyBTdG9yZTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMmTEuLgjimLwRJxy1oEf0esUNDVEIe6wDsnnal14hNBt1v195X6n93YO7gi3orPSux9D554SkMp+Sayg84lTc362UtmYLpWnb34nqyGx9KBVTy5OGV4ljE1OwC+oTnRM+QLRCmeNxMbPZhS47T+eZtDEhVB9usk3+JM2Cogfwo7AgMBAAGjcjBwMB0GA1UdDgQWBBSJaEeNuq9Df6ZfN68Fe+I2u22ssDAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFDYd6OKdgtIBGLUyaw7XQwuRWEM6MA4GA1UdDwEB/wQEAwIHgDAQBgoqhkiG92NkBgUBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAeaJVxVgm52h3oeJOOt/vY4EcQq7eqHm6m03Z9b7PRzYM2KGXHDmOMk7vDpeMVlLDPSGYz1+U3sDxJzebSpbaJmT7imzUKfggEY7xxf4czfH0yj5wNzSGTOvA==";"purchase-info"="ewoJIm9yaWdpbmFsLXB1cmNoYXNlLWRhdGUtcHN0IiA9ICIyMDE1LTA2LTI5IDA5OjE5OjEyIEFtZXJpY2EvTG9zX0FuZ2VsZXMiOwoJInB1cmNoYXNlLWRhdGUtbXMiID0gIjE0MzU1OTU2NTEwMDAiOwoJInVuaXF1ZS1pZGVudGlmaWVyIiA9ICI4MDZlZDU0NzBmZjVkMTYwODVjODcwMzcyZWY1Y2I4MTFkY2Q2NjY1IjsKCSJvcmlnaW5hbC10cmFuc2FjdGlvbi1pZCIgPSAiMTAwMDAwMDE2MTI3NTg0MiI7CgkiZXhwaXJlcy1kYXRlIiA9ICIxNDM1NTk1OTUxMDAwIjsKCSJ0cmFuc2FjdGlvbi1pZCIgPSAiMTAwMDAwMDE2MTI3ODg1NiI7Cgkib3JpZ2luYWwtcHVyY2hhc2UtZGF0ZS1tcyIgPSAiMTQzNTU5NDc1MjAwMCI7Cgkid2ViLW9yZGVyLWxpbmUtaXRlbS1pZCIgPSAiMTAwMDAwMDAzMDA0NTY3MiI7CgkiYnZycyIgPSAiMS4wIjsKCSJ1bmlxdWUtdmVuZG9yLWlkZW50aWZpZXIiID0gIkRFMzcyRDEyLUM4NjYtNEEzRC1BMzU3LTVDMzNGQjgzMkFEQyI7CgkiZXhwaXJlcy1kYXRlLWZvcm1hdHRlZC1wc3QiID0gIjIwMTUtMDYtMjkgMDk6Mzk6MTEgQW1lcmljYS9Mb3NfQW5nZWxlcyI7CgkiaXRlbS1pZCIgPSAiMTAxMzk2NTQ1NCI7CgkiZXhwaXJlcy1kYXRlLWZvcm1hdHRlZCIgPSAiMjAxNS0wNi0yOSAxNjozOToxMSBFdGMvR01UIjsKCSJwcm9kdWN0LWlkIiA9ICJjb20ubXlhcHAuYXBwLmluYXAucGFzcy5tb250aGx5c3Vic2NyaXB0aW9uIjsKCSJwdXJjaGFzZS1kYXRlIiA9ICIyMDE1LTA2LTI5IDE2OjM0OjExIEV0Yy9HTVQiOwoJIm9yaWdpbmFsLXB1cmNoYXNlLWRhdGUiID0gIjIwMTUtMDYtMjkgMTY6MTk6MTIgRXRjL0dNVCI7CgkiYmlkIiA9ICJjb20ubXlhcHAuYXBwIjsKCSJwdXJjaGFzZS1kYXRlLXBzdCIgPSAiMjAxNS0wNi0yOSAwOTozNDoxMSBBbWVyaWNhL0xvc19BbmdlbGVzIjsKCSJxdWFudGl0eSIgPSAiMSI7Cn0="; "environment" = "Sandbox"; "pod" = "100"; "signing-status" = "0";}

iTunes

def decode_itunes_object(encoded)

decoded = Base64.decode64(encoded)

JSON.parse(decoded.gsub(/(\s+)"?([\w\-]+)"? = (.*);/, '\1"\2": \3,').sub(",\n}", "\n}"))

rescue JSON::ParserError

nil

end

end

Page 28: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

{ "original-purchase-date-pst" = "2015-06-29 09:19:12 America/Los_Angeles"; "purchase-date-ms" = "1435595651000"; "unique-identifier" = "806ed5470ff5d16085c870372ef5cb811dcd6665"; "original-transaction-id" = "1000000161275842"; "expires-date" = "1435595951000"; "transaction-id" = "1000000161278856"; "original-purchase-date-ms" = "1435594752000"; "web-order-line-item-id" = "1000000030045672"; "bvrs" = "1.0"; "unique-vendor-identifier" = "DE372D12-C866-4A3D-A357-5C33FB832ADC"; "expires-date-formatted-pst" = "2015-06-29 09:39:11 America/Los_Angeles"; "item-id" = "1013965454"; "expires-date-formatted" = "2015-06-29 16:39:11 Etc/GMT"; "product-id" = "com.myapp.app.inap.pass.monthlysubscription"; "purchase-date" = "2015-06-29 16:34:11 Etc/GMT"; "original-purchase-date" = "2015-06-29 16:19:12 Etc/GMT"; "bid" = "com.myapp.app"; "purchase-date-pst" = "2015-06-29 09:34:11 America/Los_Angeles"; "quantity" = "1";}

iTunes

Page 29: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

iTunesTrap #1: Actions outside the app

Cancellations, refunds: detect expired subscriptions and revoke{

"receipt":{... <json receipt data> ...},

"status":21006,

"latest_expired_receipt_info":<huge base64 encoded receipt data>

}

Page 30: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

iTunesTrap #1: Actions outside the app

Cancellations, refunds: detect expired subscriptions and revoke{

"receipt":{... <json receipt data> ...},

"status":21006,

"latest_expired_receipt_info":<huge base64 encoded receipt data>

}

Yes, we only notice when it has expired ¬¬

~ $ crontab -l30 10 * * * cd /data/myapp/current && rake myapp:check_subscriptions[itunes] 2>&1 | logger -t check_subscriptions_iap

Page 31: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

iTunesTrap #1: Actions outside the app

Renewals:{"receipt":{... <json receipt data> ...},

"latest_receipt_info":{... <json latest receipt data> ...}

"Status":0, # error codes 21000 to 21008

"latest_receipt":<huge base64 encoded receipt data> }

Reactivations: "Restore Purchase" button

Page 32: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

iTunesTrap #2: Support for sandbox & production

def validate_with_itunes(environment = :production)

url = if environment == :production

'https://buy.itunes.apple.com/verifyReceipt'

else

'https://sandbox.itunes.apple.com/verifyReceipt'

end

...

if json["status"] == 0

# process subscription

elsif json["status"] == 21007 && environment == :production

validate_with_itunes(:sandbox)

Page 33: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Google Play “Better late than never”

Page 34: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Our backend

Google Playtoken

tokenpackage namesubscription id

tokensubscription id

package nameresponse with subscription data

Page 35: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Google Play “Better late than never”

All very nice at first lookGET https://www.googleapis.com/androidpublisher/v2/applications/package_name/purchases/subscriptions/subscription_id/tokens/token

Package name: com.myapp.androidSubscription id: monthly_subToken: lppdmbnkljkkgcldbinmhmji.AO-J1OyBx_FJSc6_fZwPnb9urd6u3jOJdaPmonghNlWcFlqG9hLAIphJia8ETGqY6bIZJNzLVKm226pCc91DjvRPkipLHbhh5IEHniNGNj5yDxnXejuSvd-1wXA-z2HVh49wQAe4fFKs9uRA53TMxHWPLTmMGO5gpB

Even with cancel, refund and revoke!POST https://www.googleapis.com/androidpublisher/v2/applications/package_name/purchases/subscriptions/subscription_id/tokens/token:(cancel|refund|revoke)

Page 36: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

HTTP/1.1 200

{

"kind": "androidpublisher#subscriptionPurchase",

"startTimeMillis": "1454584613607",

"expiryTimeMillis": "1457090213000",

"autoRenewing": true,

"priceCurrencyCode": "GBP",

"priceAmountMicros": "3990000",

"countryCode": "GB",

"developerPayload": "Rincewind",

"paymentState": 1

}

Google Play “Better late than never”

A totally sane response

Page 37: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Google Play

HTTP/1.1 200

{

"kind": "androidpublisher#subscriptionPurchase",

"startTimeMillis": "1454584613607",

"expiryTimeMillis": "1457090213000",

"autoRenewing": true,

"priceCurrencyCode": "GBP",

"priceAmountMicros": "3990000",

"countryCode": "GB",

"developerPayload": "Rincewind",

"paymentState": 1

}

“Better late than never”

A totally sane response

Page 38: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Google PlaySurprises with the token

lppdmbnkljkkgcldbinmhmji.AO-J1OyBx_FJSc6_fZwPnb9urd6u3jOJdaPmonghNlWcFlqG9hLAIphJia8ETGqY6bIZJNzLVKm226pCc91DjvRPkipLHbhh5IEHniNGNj5yDxnXejuSvd-1wXA-z2HVh49wQAe4fFKs9uRA53TMxHWPLTmMGO5gpB

Page 39: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Google PlaySurprises with the token

lppdmbnkljkkgcldbinmhmji.AO-J1OyBx_FJSc6_fZwPnb9urd6u3jOJdaPmonghNlWcFlqG9hLAIphJia8ETGqY6bIZJNzLVKm226pCc91DjvRPkipLHbhh5IEHniNGNj5yDxnXejuSvd-1wXA-z2HVh49wQAe4fFKs9uRA53TMxHWPLTmMGO5gpB

Page 40: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Google PlayDeveloper payload to the rescueHTTP/1.1 200

{

"kind": "androidpublisher#subscriptionPurchase",

"startTimeMillis": "1454584613607",

"expiryTimeMillis": "1457090213000",

"autoRenewing": true,

"priceCurrencyCode": "GBP",

"priceAmountMicros": "3990000",

"countryCode": "GB",

"developerPayload": "{\"username\": \"Rincewind\", \"id\": \"42-8-42\"}",

"paymentState": 1

}

Page 41: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Trap #1: Order IDs(What the users will send to your Support team)

Base Merchant Order number + recurrenceGPA.1234-5678-9012-34567 (base order number)GPA.1234-5678-9012-34567..0 (first recurrence orderID)GPA.1234-5678-9012-34567..1 (second recurrence orderID)GPA.1234-5678-9012-34567..2 (third recurrence orderID)

Google Play

Page 42: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Our backend

Google Playbase order numbertoken

tokensubscription id

package nametokenpackage namesubscription idbase order number

response with subscription data

Page 43: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

android.test.purchased, android.test.canceled...

Trap #1.5: No real sandbox

Static responses (useless)

Google Play

Test accounts

In-app purchases without paying

Set up test accounts

Publish app to the alpha distribution channel

No emulators, only devices allowed

Page 44: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Trap #2: Actions outside the app

Google PlayWe already know this one!

Detect cancellations and renewals{

...

"expiryTimeMillis": "1457090213000",

"autoRenewing": false,

"cancelReason": 0,

...

}No reactivations outside \o/

~ $ crontab -l30 10 * * * cd /data/myapp/current && rake myapp:check_subscriptions[itunes,google] 2>&1 | logger -t check_subscriptions_iap

Page 45: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Trap #3: false renewals with payment problems

Google Play

{

...

"expiryTimeMillis": "1457090213000",

"autoRenewing": true,

"paymentState": 0,

...

}

Get updated every ~24 hours

Page 46: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Amazon Appstore “The Pandora’s sandbox”

Page 47: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Amazon Appstore “The Pandora’s sandbox”

“Oh, this seems nice!”

Image from: https://developer.amazon.com/public/apis/earn/in-app-purchasing/docs-v2/verifying-receipts-in-iap-2.0

Page 48: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

user IDreceipt ID

Our backend

Amazon RVSuser IDreceipt ID

shared secretreceipt IDuser ID

response with subscription data

Page 49: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Amazon Appstore “The Pandora’s sandbox”

GET https://appstore-sdk.amazon.com/version/1.0/verifyReceiptId/developer/

shared_secret/user/user_id/receiptId/receipt_id

Shared secret: 2:ZzH1YJ4gFT6Y87blswfPDgidMX7VAv71Xaog:8912WkCsHQayBgdjxK2g==User Id: l3HL7XppEMhrOGDnur9-ujhyhdSg6qyODKmah76lJU=Receipt Id: HiOJ8ji36YngnQovTqSIHQxR53GsMLqkR1tKLp5c=:5:12

Request to RVS Different in sandbox

Page 50: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Amazon Appstore“Let’s test with the sandbox! … ouch -_-”

1. Use App Tester tool in Android device

2. Install and configure Tomcat locally

3. Deploy RVSSandbox.war locally

4. Deploy RVSSandbox.war somewhere

public (optional)

Page 51: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Amazon Appstore“Let’s test with the sandbox! … ouch -_-”

1. Use App Tester tool in Android device

2. Install and configure Tomcat locally

3. Deploy RVSSandbox.war locally

4. Deploy RVSSandbox.war somewhere

public (optional)

5. … And better forget about it

Page 52: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

HTTP/1.1 200

{

"betaProduct":true,

"cancelDate":null,

"parentProductId":null,

"productId":"my_subscription_v1",

"productType":"SUBSCRIPTION",

"purchaseDate":1400784371000,

"quantity":null,

"receiptId":"HiOJ8ji36YngnQovTqSIHQxR53GsMLqkR1tKLp5c=:5:12",

"testTransaction":true

}

Sandbox response (and current docs!)

Amazon Appstore

Page 53: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

HTTP/1.1 200

{

...

"cancelDate":null,

"parentProductId":null,

"productId":"my_subscription_v1",

"productType":"SUBSCRIPTION",

"renewalDate":1463407610000,

"term":"1 Month",

"termSku":"my_subscription_monthly_v1"

...

}

Real responseAmazon Appstore

Page 54: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

HTTP/1.1 200

{

...

"cancelDate":null,

"parentProductId":null,

"productId":"my_subscription_v1",

"productType":"SUBSCRIPTION",

"renewalDate":1463407610000,

"term":"1 Month",

"termSku":"my_subscription_monthly_v1"

...

}

Real responseAmazon Appstore

Use Live App Testing!

Page 55: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Trap #1: Actions outside the app

Cancellations: detect expired subscriptions and revoke

Amazon AppstoreYep, same story

HTTP/1.1 200

{

...

"cancelDate":1463407610000,

...

}

Page 56: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Trap #1: Actions outside the app

Cancellations: detect expired subscriptions and revoke

Amazon AppstoreYep, same story

HTTP/1.1 200

{

...

"cancelDate":1463407610000,

...

}

Again, we only notice when it

has expired ¬¬

~ $ crontab -l30 10 * * * cd /data/myapp/current && rake myapp:check_subscriptions[itunes,google,amazon] 2>&1 | logger -t check_subscriptions_iap

Page 57: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

Trap #1: Actions outside the app

Cancellations: detect expired subscriptions and revoke

Amazon AppstoreYep, same story

HTTP/1.1 200

{

...

"cancelDate":1463407610000,

...

}

Again, we only notice when it

has expired ¬¬

~ $ crontab -l30 10 * * * cd /data/myapp/current && rake myapp:check_subscriptions[itunes,google,amazon] 2>&1 | logger -t check_subscriptions_iap

Page 58: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

In future adventures...

Page 59: The recurring nightmare  - Rosa Gutierrez - Codemotion Amsterdam 2016

The End

All Pictures and logos belong to their respective owners/companies