j3k0 / cordova-plugin-purchase

In-App Purchase for Cordova on iOS, Android and Windows
https://purchase.cordova.fovea.cc
1.29k stars 529 forks source link

Incorrect documentation for Purchase type #1484

Closed ptmkenny closed 6 months ago

ptmkenny commented 6 months ago

Observed behavior

I'm using the latest version of the plugin. Based on the docs at https://www.iaptic.com/documentation/api/v3/#api-Types-Purchase, I started to create a JSON Schema to verify the webhook call to my server.

I noticed that the list of fields for Purchase is incomplete.

Specifically, the requests sent from Iaptic to my webhook include amountMicros and amountUSD fields, which are not listed in the docs.

My schema so far

I will be preparing a full JSON schema for your webhook response; if you're interested, I would be happy to prepare it as a PR as a doc example.

{
  "$id": "https://www.iaptic.com/purchase.schema.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Iaptic Purchase",
  "description": "Iaptic purchase: https://www.iaptic.com/documentation/api/v3/#api-Types-Purchase",
  "required": ["purchaseId", "transactionId", "productId", "platform"],
  "type": "object",
  "properties": {
    "purchaseId": {
      "type": "string",
      "description": "Purchase identifier"
    },
    "transactionId": {
      "type": "string",
      "description": "Last transaction identifier"
    },
    "productId": {
      "type": "string",
      "description": "Purchased product"
    },
    "platform": {
      "type": "string",
      "description": "Platform the purchase was made from",
      "enum": [
        "apple",
        "google",
        "windows",
        "stripe"
      ]
    },
    "sandbox": {
      "description": "True when the purchase was made in sandbox environment.",
      "type": "boolean"
    },
    "purchaseDate": {
      "type": "string",
      "description": "Time the purchase was made, in ISO 8601 date-time format.",
      "pattern": "^20([0-9]{2})-[0-1]{1}[0-9]{1}-[0-3]{1}[0-9]{1}T([0-9]{2}:([0-9]{2}):([0-9]{2}).000Z)"
    },
    "lastRenewalDate": {
      "type": "string",
      "description": "Time a subscription was last renewed, in ISO 8601 date-time format.",
      "pattern": "^20([0-9]{2})-[0-1]{1}[0-9]{1}-[0-3]{1}[0-9]{1}T([0-9]{2}:([0-9]{2}):([0-9]{2}).000Z)"
    },
   "expirationDate": {
     "type": "string",
     "description": "When the subscription is set to expire or auto-renew, in ISO 8601 date-time format.",
     "pattern": "^20([0-9]{2})-[0-1]{1}[0-9]{1}-[0-3]{1}[0-9]{1}T([0-9]{2}:([0-9]{2}):([0-9]{2}).000Z)"
    },
    "renewalIntent": {
      "type": "string",
      "description": "Is the subscription set to renew.",
      "enum": [
        "Lapse",
        "Renew"
      ]
    },
    "renewalIntentChangeDate": {
      "description": "When the renewal intent was changed, in ISO 8601 date-time format.",
      "type": "string"
    },
    "cancelationReason": {
      "type": "string",
      "description": "Reason for a purchase to have been cancelled.",
      "enum": [
        "",
        "Developer",
        "System",
        "System.Replaced",
        "System.ProductUnavailable",
        "System.BillingError",
        "System.Deleted",
        "Customer",
        "Customer.TechnicalIssues",
        "Customer.PriceIncrease",
        "Customer.Cost",
        "Customer.FoundBetterApp",
        "Customer.NotUsefulEnough",
        "Customer.OtherReason",
        "Unknown"
      ]
    },
    "isPending": {
      "description": "True when the transaction is still pending payment.",
      "type": "boolean"
    },
    "isAcknowledged": {
      "description": "True when the transaction has been acknowledged to the platform.",
      "type": "boolean"
    },
    "isConsumed": {
      "description": "True when the transaction was consumed.",
      "type": "boolean"
    },
    "isBillingRetryPeriod": {
      "description": "True when the subscription is in the grace period.",
      "type": "boolean"
    },
    "isTrialPeriod": {
      "description": "True when this is a transaction for the trial period.",
      "type": "boolean"
    },
    "isIntroPeriod": {
      "description": "True when this is the introductory period.",
      "type": "boolean"
    },
    "priceConsentStatus": {
      "description": "Whether the user approved a price change.",
      "type": "string",
      "enum": ["Notified", "Agreed"]
    },
    "discountId": {
      "description": "Identifier of a discount applied to this transaction.",
      "type": "string"
    },
    "quantity": {
      "description": "Number of elements purchases (only meaningful for consumables).",
      "type": "number"
    },
    "amountUSD": {
      "type": "number"
    },
    "amountMicros": {
      "type": "number"
    },
    "currency": {
      "description": "Currency used to make this transaction.",
      "type": "string",
      "minLength": 3,
      "maxLength": 3
    },
    "isSharedPurchase": {
      "description": "Whether or not the user made the purchase or it was shared with him for free (like family sharing).",
      "type": "boolean"
    },
    "isExpired": {
      "description": "Set to true if a subscription is expired.",
      "type": "boolean"
    },
    "entitledUsers": {
      "description": "List of application usernames entitled to this purchase.",
      "type": "array",
      "items": {
        "type": "string"
      }
    }
 },
  "additionalProperties": false
}
j3k0 commented 6 months ago

amountUSD and amountMicros are included but not part of spec, use at your own risk. It's the amount from the last transaction (which is where the purchase state is retrieved from).

Notice also that iaptic's reported amounts are a best guess, as it's not an information provided by Apple / Google.

ptmkenny commented 6 months ago

Ok, thank you for the information.

May I ask why there are details outside the spec that are also included? I wanted to do a strict JSON schema validation, but the presence of additional properties will cause such validation to fail.

j3k0 commented 6 months ago

In short, it's for internal reasons (in that instance, data from the last transactions is merged into purchase data).

But I advise you set "additionalProperties": true. When we will extend the webhook content with new data, this shouldn't make your server crash.

ptmkenny commented 6 months ago

Ok, thank you. I ended up by validating the receipt with "additionalProperties": true and then processing the receipt to strip the properties before validating again with a stricter spec for my own server and then saving it in the database.

I am doing this in Drupal, so I removed the site-specific parts of my code and published an example module for Drupal here:

https://github.com/ptmkenny/iaptic_webhook_validator

The JSON schemas are here:

https://github.com/ptmkenny/iaptic_webhook_validator/tree/main/json_schema