Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d57c87e69d | |||
| c3eef703ab | |||
| 538302d69c | |||
| 9c87bd3f71 | |||
| 3d7fe8e045 | |||
| f2fbd4227a | |||
| 86ee3389bb | |||
| 29f22e4c7d | |||
| 6f15dda97a | |||
| f7c3e26500 | |||
| 61794a63e3 | |||
| 137a767294 | |||
| 11b8faf96c | |||
| ee521f9429 | |||
| bf7010545b | |||
| 6a1b919093 | |||
| e8146f7122 | |||
| 2f54c5b042 | |||
| f0ba73b45e | |||
| 0ff100054d | |||
| 15e3bb5471 | |||
| c42f8d9ac9 | |||
| 0efe3dd839 | |||
| cb4690217b | |||
| 8266af7edb | |||
| 51d190ece0 |
+2
-2
@@ -1,5 +1,5 @@
|
||||
language: objective-c
|
||||
osx_image: xcode10
|
||||
osx_image: xcode10.2
|
||||
|
||||
env:
|
||||
global:
|
||||
@@ -7,7 +7,7 @@ env:
|
||||
- LANG=en_US.UTF-8
|
||||
- PROJECT=AppReceiptValidator/AppReceiptValidator.xcodeproj
|
||||
matrix:
|
||||
- DESTINATION="OS=12.0,name=iPhone X" SCHEME="AppReceiptValidator Demo iOS"
|
||||
- DESTINATION="OS=12.2,name=iPhone X" SCHEME="AppReceiptValidator Demo iOS"
|
||||
- DESTINATION="arch=x86_64" SCHEME="AppReceiptValidator Demo macOS"
|
||||
|
||||
script:
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
import AppReceiptValidator
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - ViewController
|
||||
|
||||
/// Displays two textfields. One to paste a receipt into as base64 string, the other displaying the parsed receipt.
|
||||
/// A device identifier for validation is not supported, have a look at the mac demo instead.
|
||||
class ViewController: UIViewController, UITextViewDelegate {
|
||||
|
||||
@IBOutlet private var inputTextView: UITextView!
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="15400" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15400"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -620,7 +620,7 @@
|
||||
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleSourceList:" target="Ady-hI-5gd" id="iwa-gc-5KM"/>
|
||||
<action selector="toggleSidebar:" target="Ady-hI-5gd" id="iwa-gc-5KM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
|
||||
@@ -707,21 +707,21 @@
|
||||
<objects>
|
||||
<viewController id="XfG-lQ-9wD" customClass="ViewController" customModule="AppReceiptValidator_Demo_macOS" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" wantsLayer="YES" id="m2S-Jp-Qdl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="631" height="270"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="698" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<scrollView horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ynK-dw-fFc">
|
||||
<rect key="frame" x="0.0" y="22" width="316" height="248"/>
|
||||
<rect key="frame" x="0.0" y="71" width="349" height="199"/>
|
||||
<clipView key="contentView" drawsBackground="NO" id="yEL-yO-YvB">
|
||||
<rect key="frame" x="1" y="1" width="314" height="246"/>
|
||||
<rect key="frame" x="1" y="1" width="347" height="197"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView toolTip="Paste Base64 here" importsGraphics="NO" richText="NO" verticallyResizable="YES" usesFontPanel="YES" findStyle="panel" allowsCharacterPickerTouchBarItem="NO" allowsUndo="YES" usesRuler="YES" allowsNonContiguousLayout="YES" textCompletion="NO" id="0xW-mT-lME" customClass="DropAcceptingTextView" customModule="AppReceiptValidator_Demo_macOS" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="314" height="246"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="347" height="197"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<size key="minSize" width="314" height="246"/>
|
||||
<size key="minSize" width="347" height="197"/>
|
||||
<size key="maxSize" width="629" height="10000000"/>
|
||||
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
</textView>
|
||||
@@ -732,22 +732,22 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="hkR-e4-WtN">
|
||||
<rect key="frame" x="299" y="1" width="16" height="246"/>
|
||||
<rect key="frame" x="332" y="1" width="16" height="197"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<scrollView toolTip="Parsed Receipt will be shown here" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6mt-ay-mAL">
|
||||
<rect key="frame" x="316" y="0.0" width="315" height="270"/>
|
||||
<rect key="frame" x="349" y="0.0" width="349" height="270"/>
|
||||
<clipView key="contentView" drawsBackground="NO" id="YwZ-F9-Cvh">
|
||||
<rect key="frame" x="1" y="1" width="313" height="268"/>
|
||||
<rect key="frame" x="1" y="1" width="347" height="268"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" usesFontPanel="YES" findStyle="panel" allowsCharacterPickerTouchBarItem="NO" allowsUndo="YES" usesRuler="YES" allowsNonContiguousLayout="YES" textCompletion="NO" id="GHT-gS-G1g" customClass="TextView" customModule="AppReceiptValidator_Demo_macOS" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="313" height="268"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="347" height="268"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<size key="minSize" width="313" height="268"/>
|
||||
<size key="minSize" width="347" height="268"/>
|
||||
<size key="maxSize" width="463" height="10000000"/>
|
||||
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
</textView>
|
||||
@@ -758,46 +758,112 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="uk9-Sg-RUp">
|
||||
<rect key="frame" x="298" y="1" width="16" height="268"/>
|
||||
<rect key="frame" x="332" y="1" width="16" height="268"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="yvX-By-nNw">
|
||||
<rect key="frame" x="0.0" y="0.0" width="316" height="22"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Optional DeviceIdentifier for Validation (MAC Address, UUID, Base64)" drawsBackground="YES" id="Z2r-sB-vIS">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<action selector="identifierDidChange:" target="XfG-lQ-9wD" id="ccM-mH-NPQ"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<stackView distribution="fill" orientation="vertical" alignment="leading" spacing="0.0" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="X6E-h2-FTp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="349" height="71"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="yvX-By-nNw">
|
||||
<rect key="frame" x="0.0" y="50" width="316" height="21"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Optional DeviceIdentifier for Validation (MAC Address, UUID, Base64)" drawsBackground="YES" id="Z2r-sB-vIS">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<action selector="identifierDidChange:" target="XfG-lQ-9wD" id="ccM-mH-NPQ"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<customView verticalHuggingPriority="251" verticalCompressionResistancePriority="749" translatesAutoresizingMaskIntoConstraints="NO" id="MIY-2Q-BBh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="276" height="50"/>
|
||||
<subviews>
|
||||
<stackView distribution="fill" orientation="horizontal" alignment="top" spacing="14" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3Fk-0l-MkU">
|
||||
<rect key="frame" x="5" y="2" width="264" height="48"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="252" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="3HO-XF-hNE">
|
||||
<rect key="frame" x="-2" y="32" width="122" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Local MAC Address" id="cII-wV-fEg">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MYU-Qf-vgG">
|
||||
<rect key="frame" x="130" y="32" width="37" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" title="Label" id="toH-92-xds">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7M8-eM-sps">
|
||||
<rect key="frame" x="173" y="20" width="97" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Reload" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="nhJ-Nu-6Mt">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="determineDeviceIdentifier:" target="XfG-lQ-9wD" id="0Iu-Ou-tt2"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<visibilityPriorities>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
</visibilityPriorities>
|
||||
<customSpacing>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="3Fk-0l-MkU" firstAttribute="top" secondItem="MIY-2Q-BBh" secondAttribute="top" id="G9h-eg-R7c"/>
|
||||
<constraint firstItem="3Fk-0l-MkU" firstAttribute="leading" secondItem="MIY-2Q-BBh" secondAttribute="leading" constant="5" id="XhE-X8-Yll"/>
|
||||
<constraint firstAttribute="bottom" secondItem="3Fk-0l-MkU" secondAttribute="bottom" constant="2" id="eS8-I6-F8I"/>
|
||||
<constraint firstAttribute="trailing" secondItem="3Fk-0l-MkU" secondAttribute="trailing" constant="7" id="u5d-kZ-Hiq"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
</subviews>
|
||||
<visibilityPriorities>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
</visibilityPriorities>
|
||||
<customSpacing>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="6mt-ay-mAL" firstAttribute="leading" secondItem="yvX-By-nNw" secondAttribute="trailing" id="231-cE-39Q"/>
|
||||
<constraint firstItem="6mt-ay-mAL" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" id="CdY-lv-wee"/>
|
||||
<constraint firstItem="X6E-h2-FTp" firstAttribute="top" secondItem="ynK-dw-fFc" secondAttribute="bottom" id="0st-m4-dJZ"/>
|
||||
<constraint firstItem="ynK-dw-fFc" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" id="1Pf-3A-rBH"/>
|
||||
<constraint firstAttribute="bottom" secondItem="X6E-h2-FTp" secondAttribute="bottom" id="1kz-OO-S1i"/>
|
||||
<constraint firstItem="6mt-ay-mAL" firstAttribute="leading" secondItem="X6E-h2-FTp" secondAttribute="trailing" id="9Vb-5X-3ef"/>
|
||||
<constraint firstItem="ynK-dw-fFc" firstAttribute="leading" secondItem="m2S-Jp-Qdl" secondAttribute="leading" id="Cvz-FD-lzg"/>
|
||||
<constraint firstItem="yvX-By-nNw" firstAttribute="top" secondItem="ynK-dw-fFc" secondAttribute="bottom" id="KSy-yc-TCD"/>
|
||||
<constraint firstItem="ynK-dw-fFc" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" id="Rno-M6-Pjq"/>
|
||||
<constraint firstItem="6mt-ay-mAL" firstAttribute="width" secondItem="m2S-Jp-Qdl" secondAttribute="width" multiplier="1:2" id="SWu-Yn-pVc"/>
|
||||
<constraint firstAttribute="bottom" secondItem="yvX-By-nNw" secondAttribute="bottom" id="WOQ-2e-jWj"/>
|
||||
<constraint firstAttribute="trailing" secondItem="6mt-ay-mAL" secondAttribute="trailing" id="Yun-Rd-wsm"/>
|
||||
<constraint firstItem="6mt-ay-mAL" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" id="clC-xc-JDh"/>
|
||||
<constraint firstAttribute="bottom" secondItem="6mt-ay-mAL" secondAttribute="bottom" id="lKc-RV-ybx"/>
|
||||
<constraint firstItem="yvX-By-nNw" firstAttribute="leading" secondItem="m2S-Jp-Qdl" secondAttribute="leading" id="s2r-QB-ZQb"/>
|
||||
<constraint firstItem="ynK-dw-fFc" firstAttribute="width" secondItem="m2S-Jp-Qdl" secondAttribute="width" multiplier="1:2" id="wsI-iB-DvJ"/>
|
||||
<constraint firstItem="X6E-h2-FTp" firstAttribute="leading" secondItem="m2S-Jp-Qdl" secondAttribute="leading" id="ysr-U6-n9c"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="dropReceivingView" destination="0xW-mT-lME" id="cuR-H1-BKN"/>
|
||||
<outlet property="identifierTextField" destination="yvX-By-nNw" id="pW5-lJ-8Cy"/>
|
||||
<outlet property="inputTextView" destination="0xW-mT-lME" id="8Y7-yb-63r"/>
|
||||
<outlet property="localDeviceIdentifierLabel" destination="MYU-Qf-vgG" id="cqz-uf-16l"/>
|
||||
<outlet property="outputTextView" destination="GHT-gS-G1g" id="cVN-4m-knJ"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="75.5" y="726"/>
|
||||
<point key="canvasLocation" x="75" y="726"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
import AppReceiptValidator
|
||||
import Cocoa
|
||||
|
||||
|
||||
// MARK: - ViewController
|
||||
|
||||
/// Displays two textfields. One to paste a receipt into as base64 string, the other displaying the parsed receipt.
|
||||
/// A device identifier can be added in a third field, which is then used to validate the receipt.
|
||||
class ViewController: NSViewController, NSTextViewDelegate, NSTextFieldDelegate {
|
||||
|
||||
private var textFieldObserver: Any?
|
||||
@@ -19,6 +18,7 @@ class ViewController: NSViewController, NSTextViewDelegate, NSTextFieldDelegate
|
||||
@IBOutlet private var identifierTextField: NSTextField!
|
||||
@IBOutlet private var outputTextView: NSTextView!
|
||||
@IBOutlet private var dropReceivingView: DropAcceptingTextView!
|
||||
@IBOutlet private var localDeviceIdentifierLabel: NSTextField!
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
@@ -30,11 +30,12 @@ class ViewController: NSViewController, NSTextViewDelegate, NSTextFieldDelegate
|
||||
self.dropReceivingView.handleDroppedFile = { [unowned self] url in
|
||||
self.update(url: url)
|
||||
}
|
||||
self.textFieldObserver = NotificationCenter.default.addObserver(forName: NSTextField.textDidChangeNotification, object: self.identifierTextField, queue: .main) { [weak self] notification in
|
||||
self.textFieldObserver = NotificationCenter.default.addObserver(forName: NSTextField.textDidChangeNotification, object: self.identifierTextField, queue: .main) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.identifierDidChange(self.identifierTextField)
|
||||
}
|
||||
self.renderLocalDeviceIdentifierText()
|
||||
}
|
||||
|
||||
// MARK: - NSTextViewDelegate
|
||||
@@ -56,6 +57,10 @@ class ViewController: NSViewController, NSTextViewDelegate, NSTextFieldDelegate
|
||||
func paste(_ sender: Any) {
|
||||
self.inputTextView.paste(sender)
|
||||
}
|
||||
|
||||
@IBAction func determineDeviceIdentifier(_ sender: Any) {
|
||||
self.renderLocalDeviceIdentifierText()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -114,9 +119,21 @@ private extension ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func localDeviceIdentifierString() -> String {
|
||||
guard let device = AppReceiptValidator.Parameters.DeviceIdentifier.getPrimaryNetworkMACAddress() else { return "DeviceIdentifier could not be determined" }
|
||||
|
||||
return "\(device.addressString) (HEX), \(device.data.base64EncodedString()) (B64)"
|
||||
}
|
||||
|
||||
func render(string: String) {
|
||||
self.outputTextView.string = string
|
||||
}
|
||||
|
||||
func renderLocalDeviceIdentifierText() {
|
||||
NSLog("Local MAC Address: " + localDeviceIdentifierString())
|
||||
self.localDeviceIdentifierLabel.attributedStringValue =
|
||||
NSAttributedString(string: localDeviceIdentifierString())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - TextView
|
||||
|
||||
+17
-16
@@ -54,7 +54,7 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
expirationDate: nil,
|
||||
inAppPurchaseReceipts: [
|
||||
InAppPurchaseReceipt(
|
||||
quantity: nil,
|
||||
quantity: 1,
|
||||
productIdentifier: "consumable",
|
||||
transactionIdentifier: "1000000166865231",
|
||||
originalTransactionIdentifier: "1000000166865231",
|
||||
@@ -62,10 +62,10 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-07T20:37:55Z"),
|
||||
subscriptionExpirationDate: nil,
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 0
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: nil,
|
||||
quantity: 1,
|
||||
productIdentifier: "monthly",
|
||||
transactionIdentifier: "1000000166965150",
|
||||
originalTransactionIdentifier: "1000000166965150",
|
||||
@@ -73,10 +73,10 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T06:49:33Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T06:54:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 1000000030274153
|
||||
),
|
||||
InAppPurchaseReceipt( // restores
|
||||
quantity: nil,
|
||||
InAppPurchaseReceipt(
|
||||
quantity: 1,
|
||||
productIdentifier: "monthly",
|
||||
transactionIdentifier: "1000000166965327",
|
||||
originalTransactionIdentifier: "1000000166965150",
|
||||
@@ -84,10 +84,10 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T06:53:18Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T06:59:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 1000000030274154
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: nil,
|
||||
quantity: 1,
|
||||
productIdentifier: "monthly",
|
||||
transactionIdentifier: "1000000166965895",
|
||||
originalTransactionIdentifier: "1000000166965150",
|
||||
@@ -95,10 +95,10 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T06:57:34Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T07:04:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 1000000030274165
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: nil,
|
||||
quantity: 1,
|
||||
productIdentifier: "monthly",
|
||||
transactionIdentifier: "1000000166967152",
|
||||
originalTransactionIdentifier: "1000000166965150",
|
||||
@@ -106,10 +106,10 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T07:02:33Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T07:09:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 1000000030274192
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: nil,
|
||||
quantity: 1,
|
||||
productIdentifier: "monthly",
|
||||
transactionIdentifier: "1000000166967484",
|
||||
originalTransactionIdentifier: "1000000166965150",
|
||||
@@ -117,10 +117,10 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T07:08:30Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T07:14:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 1000000030274219
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: nil,
|
||||
quantity: 1,
|
||||
productIdentifier: "monthly",
|
||||
transactionIdentifier: "1000000166967782",
|
||||
originalTransactionIdentifier: "1000000166965150",
|
||||
@@ -128,8 +128,9 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T07:12:34Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T07:19:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 1000000030274249
|
||||
)
|
||||
])
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ extension KnownOrUnknown: RawRepresentable {
|
||||
|
||||
extension KnownOrUnknown: Hashable {
|
||||
|
||||
public var hashValue: Int {
|
||||
return self.rawValue.hashValue
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(self.rawValue.hashValue)
|
||||
}
|
||||
|
||||
public static func == (lhs: KnownOrUnknown<Known>, rhs: KnownOrUnknown<Known>) -> Bool {
|
||||
|
||||
@@ -1213,7 +1213,7 @@
|
||||
TargetAttributes = {
|
||||
D19095801F6000A40095729B = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
LastSwiftMigration = 1000;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.Sandbox = {
|
||||
@@ -1223,27 +1223,27 @@
|
||||
};
|
||||
D19095911F6000A40095729B = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
LastSwiftMigration = 1000;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
D19095A81F6001800095729B = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
LastSwiftMigration = 1000;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
D1D6F4B41F5D684C00E86FE1 = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
LastSwiftMigration = 1000;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
D1D6F4C11F5D687400E86FE1 = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
LastSwiftMigration = 1000;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
D1D6F4E31F5D691400E86FE1 = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
LastSwiftMigration = 1000;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
@@ -1529,7 +1529,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1547,7 +1547,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1565,7 +1565,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator-Demo-macOSTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1583,7 +1583,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator-Demo-macOSTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1596,7 +1596,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator-iOSTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1610,7 +1610,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator-iOSTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
@@ -1755,7 +1755,7 @@
|
||||
PRODUCT_NAME = AppReceiptValidator;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@@ -1786,7 +1786,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ideasoncanvas.AppReceiptValidator;
|
||||
PRODUCT_NAME = AppReceiptValidator;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@@ -1819,7 +1819,7 @@
|
||||
PRODUCT_NAME = AppReceiptValidator;
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
@@ -1851,7 +1851,7 @@
|
||||
PRODUCT_NAME = AppReceiptValidator;
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
@@ -1869,7 +1869,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator.Demo-iOS";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1885,7 +1885,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator.Demo-iOS";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -12,7 +12,7 @@ public extension AppReceiptValidator {
|
||||
|
||||
/// Describes how to validate a receipt, and how/where to obtain the dependencies (receipt, deviceIdentifier, apple root certificate)
|
||||
/// Use .default to initialize the standard parameters. By default, no `propertyValidations` are active.
|
||||
public struct Parameters {
|
||||
struct Parameters {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
@@ -62,11 +62,14 @@ extension AppReceiptValidator.Parameters {
|
||||
|
||||
case installedInMainBundle
|
||||
case data(Data)
|
||||
case dynamic(() -> Data?)
|
||||
|
||||
public func loadData() -> Data? {
|
||||
switch self {
|
||||
case .data(let data):
|
||||
return data
|
||||
case .dynamic(let maker):
|
||||
return maker()
|
||||
case .installedInMainBundle:
|
||||
guard let receiptUrl = Bundle.main.appStoreReceiptURL else { return nil }
|
||||
guard (try? receiptUrl.checkResourceIsReachable()) ?? false else { return nil }
|
||||
@@ -85,7 +88,7 @@ public extension AppReceiptValidator.Parameters {
|
||||
///
|
||||
/// - currentDevice: Obtains it from the system location: MAC Address on macOS, deviceIdentifierForVendor on iOS
|
||||
/// - data: Specific Data to use
|
||||
public enum DeviceIdentifier {
|
||||
enum DeviceIdentifier {
|
||||
|
||||
case currentDevice
|
||||
case data(Data)
|
||||
|
||||
@@ -117,15 +117,14 @@ private extension AppReceiptValidator {
|
||||
var sha1Context = SHA_CTX()
|
||||
|
||||
SHA1_Init(&sha1Context)
|
||||
|
||||
deviceIdentifierData.withUnsafeBytes { pointer -> Void in
|
||||
SHA1_Update(&sha1Context, pointer, deviceIdentifierData.count)
|
||||
_ = deviceIdentifierData.withUnsafeBytes { pointer -> Void in
|
||||
SHA1_Update(&sha1Context, pointer.baseAddress, deviceIdentifierData.count)
|
||||
}
|
||||
receiptOpaqueValueData.withUnsafeBytes { pointer -> Void in
|
||||
SHA1_Update(&sha1Context, pointer, receiptOpaqueValueData.count)
|
||||
_ = receiptOpaqueValueData.withUnsafeBytes { pointer -> Void in
|
||||
SHA1_Update(&sha1Context, pointer.baseAddress, receiptOpaqueValueData.count)
|
||||
}
|
||||
receiptBundleIdData.withUnsafeBytes { pointer -> Void in
|
||||
SHA1_Update(&sha1Context, pointer, receiptBundleIdData.count)
|
||||
_ = receiptBundleIdData.withUnsafeBytes { pointer -> Void in
|
||||
SHA1_Update(&sha1Context, pointer.baseAddress, receiptBundleIdData.count)
|
||||
}
|
||||
SHA1_Final(&computedHash, &sha1Context)
|
||||
|
||||
@@ -243,28 +242,30 @@ private extension AppReceiptValidator {
|
||||
return (receipt: receipt, unofficialReceipt: unofficialReceipt)
|
||||
}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
private func parseInAppPurchaseReceipt(pointer: UnsafePointer<UInt8>, length: Int) throws -> InAppPurchaseReceipt {
|
||||
var inAppPurchaseReceipt = InAppPurchaseReceipt()
|
||||
try self.parseASN1Set(pointer: pointer, length: length) { attributeType, value in
|
||||
guard let attribute = KnownInAppPurchaseAttribute(rawValue: attributeType) else { return }
|
||||
guard let value = value.unwrapped else { return } // always unwrap set members
|
||||
|
||||
switch attribute {
|
||||
case .quantity:
|
||||
inAppPurchaseReceipt.quantity = value.intValue
|
||||
case .productIdentifier:
|
||||
inAppPurchaseReceipt.productIdentifier = value.unwrappedStringValue
|
||||
inAppPurchaseReceipt.productIdentifier = value.stringValue
|
||||
case .transactionIdentifier:
|
||||
inAppPurchaseReceipt.transactionIdentifier = value.unwrappedStringValue
|
||||
inAppPurchaseReceipt.transactionIdentifier = value.stringValue
|
||||
case .originalTransactionIdentifier:
|
||||
inAppPurchaseReceipt.originalTransactionIdentifier = value.unwrappedStringValue
|
||||
inAppPurchaseReceipt.originalTransactionIdentifier = value.stringValue
|
||||
case .purchaseDate:
|
||||
inAppPurchaseReceipt.purchaseDate = value.unwrappedDateValue
|
||||
inAppPurchaseReceipt.purchaseDate = value.dateValue
|
||||
case .originalPurchaseDate:
|
||||
inAppPurchaseReceipt.originalPurchaseDate = value.unwrappedDateValue
|
||||
inAppPurchaseReceipt.originalPurchaseDate = value.dateValue
|
||||
case .subscriptionExpirationDate:
|
||||
inAppPurchaseReceipt.subscriptionExpirationDate = value.unwrappedDateValue
|
||||
inAppPurchaseReceipt.subscriptionExpirationDate = value.dateValue
|
||||
case .cancellationDate:
|
||||
inAppPurchaseReceipt.cancellationDate = value.unwrappedDateValue
|
||||
inAppPurchaseReceipt.cancellationDate = value.dateValue
|
||||
case .webOrderLineItemId:
|
||||
inAppPurchaseReceipt.webOrderLineItemId = value.intValue
|
||||
}
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ extension AppReceiptValidator.Parameters.DeviceIdentifier {
|
||||
/// Original implementation https://gist.github.com/mminer/82975d3781e2f42fc644d7fbfbf4f905
|
||||
///
|
||||
/// - Returns: The MAC Address as Data and String representation
|
||||
static func getPrimaryNetworkMACAddress() -> (data: Data, addressString: String)? {
|
||||
public static func getPrimaryNetworkMACAddress() -> (data: Data, addressString: String)? {
|
||||
let matching = IOServiceMatching("IOEthernetInterface") as NSMutableDictionary
|
||||
matching[kIOPropertyMatchKey] = ["IOPrimaryInterface": true]
|
||||
var servicesIterator: io_iterator_t = 0
|
||||
@@ -53,6 +53,6 @@ extension AppReceiptValidator.Parameters.DeviceIdentifier {
|
||||
.map { String(format: "%02x", $0) }
|
||||
.joined(separator: ":")
|
||||
|
||||
return (data: Data(bytes: address), addressString: addressString)
|
||||
return (data: Data(address), addressString: addressString)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
import AppReceiptValidator.OpenSSL
|
||||
import Foundation
|
||||
|
||||
// A resource for ASN1 can be https://www.oss.com/asn1/resources/reference/asn1-reference-card.html
|
||||
|
||||
/// An ASN1 Sequence Object. Of interest are the attributeType and the valueObject.
|
||||
/// The attributeType determines how to interpret the valueObject.
|
||||
///
|
||||
@@ -116,7 +118,11 @@ extension ASN1Object {
|
||||
|
||||
extension ASN1Object {
|
||||
|
||||
/// Unwraps an OCTET_STRING, which is a binary container, that can contain another ASN1Object.
|
||||
/// This is the case when we are looking at an entry in an ASN1Set
|
||||
var unwrapped: ASN1Object? {
|
||||
guard self.type == V_ASN1_OCTET_STRING else { return nil }
|
||||
|
||||
guard let endPointer = valuePointer?.advanced(by: length) else { return nil }
|
||||
|
||||
var innerPointer = valuePointer
|
||||
@@ -155,7 +161,11 @@ extension ASN1Object {
|
||||
|
||||
extension ASN1Object {
|
||||
|
||||
var intValue: Int? {
|
||||
var unwrappedIntValue: Int64? {
|
||||
return self.unwrapped?.intValue
|
||||
}
|
||||
|
||||
var intValue: Int64? {
|
||||
guard self.type == V_ASN1_INTEGER else { return nil }
|
||||
|
||||
var pointer = self.valuePointer
|
||||
@@ -163,11 +173,11 @@ extension ASN1Object {
|
||||
defer {
|
||||
ASN1_INTEGER_free(integer)
|
||||
}
|
||||
let result = ASN1_INTEGER_get(integer)
|
||||
let result = Int64(ASN1_INTEGER_get(integer))
|
||||
return result
|
||||
}
|
||||
|
||||
func intValue(byAdvancingPointer pointer: inout UnsafePointer<UInt8>?, length: Int? = nil) -> Int? {
|
||||
func intValue(byAdvancingPointer pointer: inout UnsafePointer<UInt8>?, length: Int? = nil) -> Int64? {
|
||||
let length = length ?? self.length
|
||||
pointer = pointer?.advanced(by: length)
|
||||
guard let intValue = self.intValue else { return nil }
|
||||
|
||||
@@ -14,8 +14,8 @@ final class BIOWrapper {
|
||||
let bio = BIO_new(BIO_s_mem())
|
||||
|
||||
init(data: Data) {
|
||||
data.withUnsafeBytes { pointer -> Void in
|
||||
BIO_write(self.bio, pointer, Int32(data.count))
|
||||
_ = data.withUnsafeBytes { pointer in
|
||||
BIO_write(self.bio, pointer.baseAddress, Int32(data.count))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ public struct Receipt: Equatable {
|
||||
/// The in-app purchase receipt for a non-consumable product, auto-renewable subscription, non-renewing subscription, or free subscription remains in the receipt indefinitely.
|
||||
public internal(set) var inAppPurchaseReceipts: [InAppPurchaseReceipt] = []
|
||||
|
||||
/// For documentation of parameters, have a look at the documented properties of `Receipt`
|
||||
public init(bundleIdentifier: String?, bundleIdData: Data?, appVersion: String?, opaqueValue: Data?, sha1Hash: Data?, originalAppVersion: String?, receiptCreationDate: Date?, expirationDate: Date?, inAppPurchaseReceipts: [InAppPurchaseReceipt]) {
|
||||
self.bundleIdentifier = bundleIdentifier
|
||||
self.bundleIdData = bundleIdData
|
||||
@@ -67,6 +68,27 @@ public struct Receipt: Equatable {
|
||||
self.inAppPurchaseReceipts = inAppPurchaseReceipts
|
||||
}
|
||||
|
||||
/// Convenience initializer with stringified parameters for `Date` and `Data` type parameters.
|
||||
/// When logging a `Receipt`'s (`CustomStringConvertible`) description, you can use that as swift source code calling this initializer,
|
||||
/// which can simplify creating tests.
|
||||
///
|
||||
/// For documentation of parameters, have a look at the documented properties of `Receipt`.
|
||||
///
|
||||
/// - Note: Dates must be formatted as "2017-01-01T12:00:00Z", otherwise will be assigned `nil`.
|
||||
/// Data must be formatted as Base64, otherwise will be assigned `nil`.
|
||||
/// Correct formatting will be asserted in DEBUG builds.
|
||||
public init(bundleIdentifier: String, bundleIdData: String, appVersion: String, opaqueValue: String, sha1Hash: String, originalAppVersion: String, receiptCreationDate: String, expirationDate: String?, inAppPurchaseReceipts: [InAppPurchaseReceipt]) {
|
||||
self.init(bundleIdentifier: bundleIdentifier,
|
||||
bundleIdData: parseBase64(string: bundleIdData),
|
||||
appVersion: appVersion,
|
||||
opaqueValue: parseBase64(string: opaqueValue),
|
||||
sha1Hash: parseBase64(string: sha1Hash),
|
||||
originalAppVersion: originalAppVersion,
|
||||
receiptCreationDate: parseDate(string: receiptCreationDate),
|
||||
expirationDate: expirationDate.flatMap { parseDate(string: $0) },
|
||||
inAppPurchaseReceipts: inAppPurchaseReceipts)
|
||||
}
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
@@ -74,6 +96,8 @@ public struct Receipt: Equatable {
|
||||
|
||||
extension Receipt: CustomStringConvertible, CustomDebugStringConvertible {
|
||||
|
||||
// This description is carefully matched to match the stringy convenience initializer of `Receipt`
|
||||
|
||||
public var description: String {
|
||||
let formatter = StringFormatter()
|
||||
let props: [(String, String)] = [
|
||||
@@ -101,7 +125,7 @@ extension Receipt: CustomStringConvertible, CustomDebugStringConvertible {
|
||||
///
|
||||
/// Documentation was obtained from: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
|
||||
///
|
||||
/// The following fields are part of JSON communication but not part of the parsed version (matched Sept 2017):
|
||||
/// The following fields are part of JSON communication but not part of the parsed version (matched May 2019):
|
||||
/// - Subscription Expiration Intent
|
||||
/// - Subscription Retry Flag
|
||||
/// - Subscription Trial Period
|
||||
@@ -115,7 +139,7 @@ public struct InAppPurchaseReceipt: Equatable {
|
||||
|
||||
/// The number of items purchased. ASN.1 Field Type 1701.
|
||||
/// This value corresponds to the quantity property of the `SKPayment` object stored in the transaction’s payment property.
|
||||
public internal(set) var quantity: Int?
|
||||
public internal(set) var quantity: Int64?
|
||||
|
||||
/// The product identifier of the item that was purchased. ASN.1 Field Type 1702.
|
||||
/// This value corresponds to the `productIdentifier` property of the `SKPayment` object stored in the transaction’s `payment` property.
|
||||
@@ -167,10 +191,10 @@ public struct InAppPurchaseReceipt: Equatable {
|
||||
|
||||
/// The primary key for identifying subscription purchases. ASN.1 Field Type 1711.
|
||||
/// This value is a unique ID that identifies purchase events across devices, including subscription renewal purchase events.
|
||||
public internal(set) var webOrderLineItemId: Int?
|
||||
public internal(set) var webOrderLineItemId: Int64?
|
||||
|
||||
/// For documentation see InAppPurchaseReceipt itself.
|
||||
public init(quantity: Int?, productIdentifier: String?, transactionIdentifier: String?, originalTransactionIdentifier: String?, purchaseDate: Date?, originalPurchaseDate: Date?, subscriptionExpirationDate: Date?, cancellationDate: Date?, webOrderLineItemId: Int?) {
|
||||
/// For documentation of parameters, have a look at the documented properties of `InAppPurchaseReceipt`.
|
||||
public init(quantity: Int64?, productIdentifier: String?, transactionIdentifier: String?, originalTransactionIdentifier: String?, purchaseDate: Date?, originalPurchaseDate: Date?, subscriptionExpirationDate: Date?, cancellationDate: Date?, webOrderLineItemId: Int64?) {
|
||||
self.quantity = quantity
|
||||
self.productIdentifier = productIdentifier
|
||||
self.transactionIdentifier = transactionIdentifier
|
||||
@@ -182,6 +206,27 @@ public struct InAppPurchaseReceipt: Equatable {
|
||||
self.webOrderLineItemId = webOrderLineItemId
|
||||
}
|
||||
|
||||
/// Convenience initializer with stringified parameters for `Date` and `Data` type parameters.
|
||||
/// When logging a `Receipt`'s (`CustomStringConvertible`) description, you can use that as swift source code calling this initializer,
|
||||
/// which can simplify creating tests.
|
||||
///
|
||||
/// For documentation of parameters, have a look at the documented properties of `InAppPurchaseReceipt`.
|
||||
///
|
||||
/// - Note: Dates must be formatted as "2017-01-01T12:00:00Z", otherwise will be assigned `nil`.
|
||||
/// Data must be formatted as Base64, otherwise will be assigned `nil`.
|
||||
/// Correct formatting will be asserted in DEBUG builds.
|
||||
public init(quantity: Int64?, productIdentifier: String, transactionIdentifier: String, originalTransactionIdentifier: String, purchaseDate: String, originalPurchaseDate: String, subscriptionExpirationDate: String?, cancellationDate: String?, webOrderLineItemId: Int64?) {
|
||||
self.init(quantity: quantity,
|
||||
productIdentifier: productIdentifier,
|
||||
transactionIdentifier: transactionIdentifier,
|
||||
originalTransactionIdentifier: originalTransactionIdentifier,
|
||||
purchaseDate: parseDate(string: purchaseDate),
|
||||
originalPurchaseDate: parseDate(string: originalPurchaseDate),
|
||||
subscriptionExpirationDate: subscriptionExpirationDate.flatMap { parseDate(string: $0) },
|
||||
cancellationDate: cancellationDate.flatMap { parseDate(string: $0) },
|
||||
webOrderLineItemId: webOrderLineItemId)
|
||||
}
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
@@ -189,6 +234,8 @@ public struct InAppPurchaseReceipt: Equatable {
|
||||
|
||||
extension InAppPurchaseReceipt: CustomStringConvertible, CustomDebugStringConvertible {
|
||||
|
||||
// This description is carefully matched to match the stringy convenience initializer of `InAppPurchaseReceipt`
|
||||
|
||||
public var description: String {
|
||||
let formatter = StringFormatter()
|
||||
let props: [(String, String)] = [
|
||||
@@ -228,7 +275,7 @@ private struct StringFormatter {
|
||||
return pairs.map { self.format(key: $0, value: $1) }.joined(separator: ",\n")
|
||||
}
|
||||
|
||||
func format(_ int: Int?) -> String {
|
||||
func format(_ int: Int64?) -> String {
|
||||
guard let int = int else { return fallback }
|
||||
|
||||
return "\(int)"
|
||||
@@ -241,16 +288,40 @@ private struct StringFormatter {
|
||||
func format(_ data: Data?) -> String {
|
||||
guard let data = data else { return fallback }
|
||||
|
||||
return data.base64EncodedString()
|
||||
return quoted(data.base64EncodedString())
|
||||
}
|
||||
|
||||
func format(_ date: Date?) -> String {
|
||||
guard let date = date else { return fallback }
|
||||
|
||||
return AppReceiptValidator.asn1DateFormatter.string(from: date)
|
||||
return quoted(AppReceiptValidator.asn1DateFormatter.string(from: date))
|
||||
}
|
||||
|
||||
func format(_ string: String?) -> String {
|
||||
return string ?? fallback
|
||||
return string.map(quoted) ?? fallback
|
||||
}
|
||||
|
||||
/// Surrounds a string with quotes "…"
|
||||
func quoted(_ string: String) -> String {
|
||||
return "\"" + string + "\""
|
||||
}
|
||||
}
|
||||
|
||||
private func parseBase64(string: String) -> Data? {
|
||||
guard let data = Data(base64Encoded: string) else {
|
||||
assertionFailure("Data could not be parsed from string '\(string)', make sure it is non-empty nad base64 encoded.")
|
||||
return nil
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Parses a string of type "2017-01-01T12:00:00Z"
|
||||
private func parseDate(string: String) -> Date? {
|
||||
guard let date = AppReceiptValidator.asn1DateFormatter.date(from: string) else {
|
||||
assertionFailure("Date could not be parsed from string '\(string)', make sure it has a correct format, example `2017-01-01T12:00:00Z`")
|
||||
return nil
|
||||
}
|
||||
|
||||
return date
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://github.com/Carthage/Carthage)
|
||||

|
||||

|
||||

|
||||
[](LICENSE)
|
||||
[](https://travis-ci.org/IdeasOnCanvas/AppReceiptValidator)
|
||||
[](https://twitter.com/hannesoid)
|
||||
|
||||
Reference in New Issue
Block a user