From 5bd8244dc9c1e1573330a172b96a920e9194a7bb Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Tue, 22 Aug 2017 23:44:15 -0400 Subject: [PATCH 01/26] Added new filter fields and fixed typo for availableOn. --- Sources/API/Filter/StripeFilter.swift | 48 +++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/Sources/API/Filter/StripeFilter.swift b/Sources/API/Filter/StripeFilter.swift index f4c46cb..33a2047 100644 --- a/Sources/API/Filter/StripeFilter.swift +++ b/Sources/API/Filter/StripeFilter.swift @@ -100,7 +100,6 @@ public final class StripeFilter { */ public var balanceType: BalanceType? - /** The status of the subscriptions to retrieve. */ @@ -111,6 +110,37 @@ public final class StripeFilter { */ public var plan: Node? + /** + Only return SKUs that have the specified key/value pairs in this partially constructed dictionary. + */ + public var attributes: Node? + + /** + Only return SKUs that are active or inactive + Also used for products. + */ + public var active: Node? + + /** + Only return SKUs that are either in stock or out of stock + */ + public var inStock: Node? + + /** + The ID of the product whose SKUs will be retrieved. + */ + public var product: Node? + + /** + Only return products that can be shipped (i.e., physical, not digital products). + */ + public var shippable: Node? + + /** + Only return products with the given url. + */ + public var url: Node? + internal func createBody() throws -> Node { var node = Node([:]) if let value = self.created { @@ -123,7 +153,7 @@ public final class StripeFilter { } } - if let value = self.created { + if let value = self.availableOn { if let value = value.object { for (key, value) in value { node["available_on[\(key)]"] = value @@ -178,6 +208,20 @@ public final class StripeFilter { node["plan"] = value } + if let value = attributes?.object { + for (key, value) in value { + node["attributes[\(key)]"] = value + } + } + + if let shippable = self.shippable { + node["shippable"] = shippable + } + + if let url = self.url { + node["url"] = url + } + return node } From 588d80fc8cd45982dce97044c41aaeff0d81c0da Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Tue, 22 Aug 2017 23:45:01 -0400 Subject: [PATCH 02/26] Updated to latest API version and added new route endpoints. --- Sources/API/Helpers/Endpoints.swift | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Sources/API/Helpers/Endpoints.swift b/Sources/API/Helpers/Endpoints.swift index ce12b48..ff924bf 100644 --- a/Sources/API/Helpers/Endpoints.swift +++ b/Sources/API/Helpers/Endpoints.swift @@ -14,7 +14,7 @@ internal let APIVersion = "v1/" internal let DefaultHeaders = [ HeaderKey.contentType: "application/x-www-form-urlencoded", - StripeHeader.Version: "2017-06-05" + StripeHeader.Version: "2017-08-15" ] internal struct StripeHeader { @@ -131,6 +131,19 @@ internal enum API { case disputes(String) case closeDispute(String) + /** + SKUS + Stores representations of stock keeping units. SKUs describe specific product variations. + */ + case sku + case skus(String) + + /** + Products + Store representations of products you sell in product objects, used in conjunction with SKUs. + */ + case product + case products(String) var endpoint: String { switch self { @@ -177,6 +190,12 @@ internal enum API { case .dispute: return APIBase + APIVersion + "disputes" case .disputes(let id): return APIBase + APIVersion + "disputes/\(id)" case .closeDispute(let id): return APIBase + APIVersion + "disputes/\(id)/close" + + case .sku: return APIBase + APIVersion + "skus" + case .skus(let id): return APIBase + APIVersion + "skus/\(id)" + + case .product: return APIBase + APIVersion + "products" + case .products(let id): return APIBase + APIVersion + "products/\(id)" } } } From b17c02a8572123833452e6704b06a137bcb45ea1 Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Tue, 22 Aug 2017 23:45:45 -0400 Subject: [PATCH 03/26] Removed unnecessary init method. --- Sources/Models/Coupons/Coupon.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/Models/Coupons/Coupon.swift b/Sources/Models/Coupons/Coupon.swift index ca275d3..6a1c80c 100644 --- a/Sources/Models/Coupons/Coupon.swift +++ b/Sources/Models/Coupons/Coupon.swift @@ -35,8 +35,6 @@ public final class Coupon: StripeModelProtocol { */ public private(set) var metadata: Node? - public init() {} - public init(node: Node) throws { self.id = try node.get("id") self.amountOff = try node.get("amount_off") From 0980028d0de599bf281a316ee5193ddb38f4adaf Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Tue, 22 Aug 2017 23:46:17 -0400 Subject: [PATCH 04/26] Updated documentation. --- Sources/API/Routes/CustomerRoutes.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/API/Routes/CustomerRoutes.swift b/Sources/API/Routes/CustomerRoutes.swift index 2943a9a..fcf1e42 100644 --- a/Sources/API/Routes/CustomerRoutes.swift +++ b/Sources/API/Routes/CustomerRoutes.swift @@ -384,7 +384,8 @@ public final class CustomerRoutes { /** Delete a customer discount - Permanently deletes a customer. It cannot be undone. Also immediately cancels any active subscriptions on the customer. + Removes the currently applied discount on a customer. + - parameter customerId: The Customer's ID @@ -395,8 +396,8 @@ public final class CustomerRoutes { } /** - Delete a customer discount - Removes the currently applied discount on a customer. + Delete a customer + Permanently deletes a customer. It cannot be undone. Also immediately cancels any active subscriptions on the customer. - parameter customerId: The Customer's ID From 8eacd40672867aa65f560870f78b2eac7fffbdab Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Tue, 22 Aug 2017 23:46:49 -0400 Subject: [PATCH 05/26] Added Inventory types. --- Sources/Helpers/InventoryType.swift | 23 ++++++++++++++++++ Sources/Models/Inventory.swift | 37 +++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 Sources/Helpers/InventoryType.swift create mode 100644 Sources/Models/Inventory.swift diff --git a/Sources/Helpers/InventoryType.swift b/Sources/Helpers/InventoryType.swift new file mode 100644 index 0000000..ba8b539 --- /dev/null +++ b/Sources/Helpers/InventoryType.swift @@ -0,0 +1,23 @@ +// +// InventoryType.swift +// Stripe +// +// Created by Andrew Edwards on 8/22/17. +// +// + +import Foundation + +public enum InventoryType: String { + case finite = "finite" + case bucket = "bucket" + case infinite = "infinite" + case unknown = "unknown" +} + +public enum InventoryTypeValue: String { + case inStock = "in_stock" + case limited = "limited" + case outOfStock = "out_of_stock" + case unknown = "unknown" +} diff --git a/Sources/Models/Inventory.swift b/Sources/Models/Inventory.swift new file mode 100644 index 0000000..02717b5 --- /dev/null +++ b/Sources/Models/Inventory.swift @@ -0,0 +1,37 @@ +// +// Inventory.swift +// Stripe +// +// Created by Andrew Edwards on 8/22/17. +// +// + +import Foundation +import Vapor +import Helpers + +public final class Inventory: StripeModelProtocol { + + public private(set) var quantity: Int? + public private(set) var type: InventoryType? + public private(set) var value: InventoryTypeValue? + + public init(node: Node) throws { + self.quantity = try node.get("quantity") + if let type = node["type"]?.string { + self.type = InventoryType(rawValue: type) + } + if let value = node["value"]?.string { + self.value = InventoryTypeValue(rawValue: value) + } + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String: Any?] = [ + "quantity": self.quantity, + "type": self.type?.rawValue, + "value": self.value?.rawValue + ] + return try Node(node: object) + } +} From ac1bd07e57edf40ba99bec5b29460bcc610b3c8d Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Tue, 22 Aug 2017 23:47:29 -0400 Subject: [PATCH 06/26] Added Products API and routes. --- Sources/API/Routes/ProductRoutes.swift | 149 +++++++++++++++++++++ Sources/Models/PackageDimensions.swift | 36 +++++ Sources/Models/Products/Products.swift | 81 +++++++++++ Sources/Models/Products/ProductsList.swift | 35 +++++ 4 files changed, 301 insertions(+) create mode 100644 Sources/API/Routes/ProductRoutes.swift create mode 100644 Sources/Models/PackageDimensions.swift create mode 100644 Sources/Models/Products/Products.swift create mode 100644 Sources/Models/Products/ProductsList.swift diff --git a/Sources/API/Routes/ProductRoutes.swift b/Sources/API/Routes/ProductRoutes.swift new file mode 100644 index 0000000..35a2ffd --- /dev/null +++ b/Sources/API/Routes/ProductRoutes.swift @@ -0,0 +1,149 @@ +// +// ProductRoutes.swift +// Stripe +// +// Created by Andrew Edwards on 8/22/17. +// +// + +import Foundation +import Node +import Models +import Helpers +import HTTP + +public final class ProductRoutes { + let client: StripeClient + + init(client: StripeClient) { + self.client = client + } + + public func create(name: String, id: String?, active: Bool?, attributes: Node?, caption: String?, deactivateOn: Node?, description: String?, images: Node?, packageDimensions: Node?, shippable: Bool?, url: String?, metadata: Node? = nil) throws -> StripeRequest { + + var body = Node([:]) + + body["name"] = Node(name) + + if let id = id { + body["id"] = Node(id) + } + + if let active = active { + body["active"] = Node(active) + } + + if let attributes = attributes?.array { + body["attributes"] = Node(attributes) + } + + if let caption = caption { + body["caption"] = Node(caption) + } + + if let deactivateOn = deactivateOn?.array { + body["deactivate_on"] = Node(deactivateOn) + } + + if let description = description { + body["description"] = Node(description) + } + + if let images = images?.array { + body["images"] = Node(images) + } + + if let packagedimensions = packageDimensions?.object { + for (key,value) in packagedimensions { + body["package_dimensions[\(key)]"] = value + } + } + + if let shippable = shippable { + body["shippable"] = Node(shippable) + } + + if let url = url { + body["url"] = Node(url) + } + + if let metadata = metadata?.object { + for (key, value) in metadata { + body["metadata[\(key)]"] = value + } + } + + return try StripeRequest(client: self.client, method: .post, route: .product, query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + } + + public func retrieve(product productId: String) throws -> StripeRequest { + return try StripeRequest(client: self.client, method: .get, route: .products(productId), query: [:], body: nil, headers: nil) + } + + public func update(product productId: String, active: Bool?, attributes: Node?, caption: String?, deactivateOn: Node?, description: String?, images: Node?, name: String?, packageDimensions: Node?, shippable: Bool?, url: String?, metadata: Node? = nil) throws -> StripeRequest { + + var body = Node([:]) + + if let name = name { + body["name"] = Node(name) + } + + if let active = active { + body["active"] = Node(active) + } + + if let attributes = attributes?.array { + body["attributes"] = Node(attributes) + } + + if let caption = caption { + body["caption"] = Node(caption) + } + + if let deactivateOn = deactivateOn?.array { + body["deactivate_on"] = Node(deactivateOn) + } + + if let description = description { + body["description"] = Node(description) + } + + if let images = images?.array { + body["images"] = Node(images) + } + + if let packagedimensions = packageDimensions?.object { + for (key,value) in packagedimensions { + body["package_dimensions[\(key)]"] = value + } + } + + if let shippable = shippable { + body["shippable"] = Node(shippable) + } + + if let url = url { + body["url"] = Node(url) + } + + if let metadata = metadata?.object { + for (key, value) in metadata { + body["metadata[\(key)]"] = value + } + } + + return try StripeRequest(client: self.client, method: .post, route: .products(productId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + } + + public func listAll(filter: StripeFilter? = nil) throws -> StripeRequest { + var query = [String : NodeRepresentable]() + if let data = try filter?.createQuery() { + query = data + } + return try StripeRequest(client: self.client, method: .get, route: .product, query: query, body: nil, headers: nil) + } + + public func delete(product productId: String) throws -> StripeRequest { + return try StripeRequest(client: self.client, method: .delete, route: .products(productId), query: [:], body: nil, headers: nil) + } +} diff --git a/Sources/Models/PackageDimensions.swift b/Sources/Models/PackageDimensions.swift new file mode 100644 index 0000000..a45f90d --- /dev/null +++ b/Sources/Models/PackageDimensions.swift @@ -0,0 +1,36 @@ +// +// PackageDimensions.swift +// Stripe +// +// Created by Andrew Edwards on 8/22/17. +// +// + +import Foundation +import Vapor + +public final class PackageDimensions: StripeModelProtocol { + + public private(set) var height: Decimal? + public private(set) var length: Decimal? + public private(set) var weight: Decimal? + public private(set) var width: Decimal? + + public init(node: Node) throws { + + self.height = try Decimal(node.get("height") as Int) + self.length = try Decimal(node.get("length") as Int) + self.weight = try Decimal(node.get("weight") as Int) + self.width = try Decimal(node.get("width") as Int) + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String: Any?] = [ + "height": self.height, + "length": self.length, + "weight": self.weight, + "width": self.width + ] + return try Node(node: object) + } +} diff --git a/Sources/Models/Products/Products.swift b/Sources/Models/Products/Products.swift new file mode 100644 index 0000000..b624dce --- /dev/null +++ b/Sources/Models/Products/Products.swift @@ -0,0 +1,81 @@ +// +// Products.swift +// Stripe +// +// Created by Andrew Edwards on 8/22/17. +// +// + +import Foundation +import Vapor +import Helpers + +/** + Product object + https://stripe.com/docs/api#products + */ + +public final class Product: StripeModelProtocol { + + public private(set) var id: String? + public private(set) var object: String? + public private(set) var active: Bool? + public private(set) var attributes: Node? + public private(set) var caption: String? + public private(set) var created: Date? + public private(set) var deactivateOn: Node? + public private(set) var description: String? + public private(set) var images: Node? + public private(set) var isLive: Bool? + public private(set) var metadata: Node? + public private(set) var name: String? + public private(set) var packageDimensions: PackageDimensions? + public private(set) var shippable: Bool? + public private(set) var skus: SKUList? + public private(set) var updated: Date? + public private(set) var url: String? + + public init(node: Node) throws { + self.id = try node.get("id") + self.object = try node.get("object") + self.active = try node.get("active") + self.attributes = try node.get("attributes") + self.caption = try node.get("caption") + self.created = try node.get("created") + self.deactivateOn = try node.get("deactivate_on") + self.description = try node.get("description") + self.images = try node.get("images") + self.isLive = try node.get("livemode") + self.metadata = try node.get("metadata") + self.name = try node.get("name") + self.packageDimensions = try node.get("package_dimensions") + self.shippable = try node.get("shippable") + self.skus = try node.get("skus") + self.updated = try node.get("updated") + self.url = try node.get("url") + } + + public func makeNode(in context: Context?) throws -> Node { + + let object: [String: Any?] = [ + "id": self.id, + "object": self.object, + "active": self.active, + "attributes": self.attributes, + "caption": self.caption, + "created": self.created, + "deactivate_on": self.deactivateOn, + "description": self.description, + "images": self.images, + "livemode": self.isLive, + "name": self.name, + "metadata": self.metadata, + "package_dimensions": self.packageDimensions, + "shippable": self.shippable, + "skus": self.skus, + "updated": self.updated, + "url": self.url + ] + return try Node(node: object) + } +} diff --git a/Sources/Models/Products/ProductsList.swift b/Sources/Models/Products/ProductsList.swift new file mode 100644 index 0000000..cef03ff --- /dev/null +++ b/Sources/Models/Products/ProductsList.swift @@ -0,0 +1,35 @@ +// +// ProductsList.swift +// Stripe +// +// Created by Andrew Edwards on 8/22/17. +// +// + +import Foundation +import Vapor +import Helpers + +public final class ProductsList: StripeModelProtocol { + public private(set) var object: String? + public private(set) var url: String? + public private(set) var hasMore: Bool? + public private(set) var items: [Product]? + + public init(node: Node) throws { + self.object = try node.get("object") + self.url = try node.get("url") + self.hasMore = try node.get("has_more") + self.items = try node.get("data") + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String : Any?] = [ + "object": self.object, + "url": self.url, + "has_more": self.hasMore, + "data": self.items + ] + return try Node(node: object) + } +} From 68b8f08a1c48032b000605d673463b648d64e488 Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Tue, 22 Aug 2017 23:48:31 -0400 Subject: [PATCH 07/26] Added SKU API, routes and tests. --- Sources/API/Routes/SKURoutes.swift | 131 ++++++++++++ Sources/Models/SKU/SKU.swift | 74 +++++++ Sources/Models/SKU/SKUList.swift | 35 ++++ Tests/StripeTests/SKUTests.swift | 322 +++++++++++++++++++++++++++++ 4 files changed, 562 insertions(+) create mode 100644 Sources/API/Routes/SKURoutes.swift create mode 100644 Sources/Models/SKU/SKU.swift create mode 100644 Sources/Models/SKU/SKUList.swift create mode 100644 Tests/StripeTests/SKUTests.swift diff --git a/Sources/API/Routes/SKURoutes.swift b/Sources/API/Routes/SKURoutes.swift new file mode 100644 index 0000000..c32959f --- /dev/null +++ b/Sources/API/Routes/SKURoutes.swift @@ -0,0 +1,131 @@ +// +// SKURoutes.swift +// Stripe +// +// Created by Andrew Edwards on 8/22/17. +// +// + +import Foundation +import Node +import Models +import Helpers +import HTTP + +public final class SKURoutes { + let client: StripeClient + + init(client: StripeClient) { + self.client = client + } + + public func create(currency: StripeCurrency, inventory: Node, price: Int, product: String, id: String?, active: Bool?, attributes: Node?, image: String?, packageDimensions: Node?, metadata: Node? = nil) throws -> StripeRequest { + var body = Node([:]) + + body["currency"] = Node(currency.rawValue) + + if let inventory = inventory.object { + for (key,value) in inventory { + body["inventory[\(key)]"] = value + } + } + + body["price"] = Node(price) + + body["product"] = Node(product) + + if let active = active { + body["active"] = Node(active) + } + + if let attributes = attributes?.object { + for (key,value) in attributes { + body["attributes[\(key)]"] = value + } + } + + if let image = image { + body["image"] = Node(image) + } + + if let packagedimensions = packageDimensions?.object { + for (key,value) in packagedimensions { + body["package_dimensions[\(key)]"] = value + } + } + + if let metadata = metadata?.object { + for (key, value) in metadata { + body["metadata[\(key)]"] = value + } + } + + return try StripeRequest(client: self.client, method: .post, route: .sku, query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + } + + public func retrieve(sku skuId: String) throws -> StripeRequest { + return try StripeRequest(client: self.client, method: .get, route: .skus(skuId), query: [:], body: nil, headers: nil) + } + + public func update(sku skuId: String, active: Bool?, attributes: Node?, currency: StripeCurrency?, image: String?, inventory: Node?, packageDimensions: Node?, price: Int?, product: String?, metadata: Node? = nil) throws -> StripeRequest { + var body = Node([:]) + + + if let active = active { + body["active"] = Node(active) + } + + if let attributes = attributes?.object { + for (key,value) in attributes { + body["attributes[\(key)]"] = value + } + } + + if let currency = currency { + body["currency"] = Node(currency.rawValue) + } + + if let image = image { + body["image"] = Node(image) + } + + if let inventory = inventory?.object { + for (key,value) in inventory { + body["inventory[\(key)]"] = value + } + } + + if let packagedimensions = packageDimensions?.object { + for (key,value) in packagedimensions { + body["package_dimensions[\(key)]"] = value + } + } + if let price = price { + body["price"] = Node(price) + } + + if let product = product { + body["product"] = Node(product) + } + + if let metadata = metadata?.object { + for (key, value) in metadata { + body["metadata[\(key)]"] = value + } + } + + return try StripeRequest(client: self.client, method: .post, route: .skus(skuId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + } + + public func listAll(filter: StripeFilter? = nil) throws -> StripeRequest { + var query = [String : NodeRepresentable]() + if let data = try filter?.createQuery() { + query = data + } + return try StripeRequest(client: self.client, method: .get, route: .sku, query: query, body: nil, headers: nil) + } + + public func delete(sku skuId: String) throws -> StripeRequest { + return try StripeRequest(client: self.client, method: .delete, route: .skus(skuId), query: [:], body: nil, headers: nil) + } +} diff --git a/Sources/Models/SKU/SKU.swift b/Sources/Models/SKU/SKU.swift new file mode 100644 index 0000000..273d65f --- /dev/null +++ b/Sources/Models/SKU/SKU.swift @@ -0,0 +1,74 @@ +// +// SKU.swift +// Stripe +// +// Created by Andrew Edwards on 8/22/17. +// +// + +import Foundation +import Vapor +import Helpers + +/** + SKU object + https://stripe.com/docs/api#skus + */ + +public final class SKU: StripeModelProtocol { + + public private(set) var id: String? + public private(set) var object: String? + public private(set) var active: Bool? + public private(set) var attributes: Node? + public private(set) var created: Date? + public private(set) var currency: StripeCurrency? + public private(set) var image: String? + public private(set) var inventory: Inventory? + public private(set) var isLive: Bool? + public private(set) var metadata: Node? + public private(set) var packageDimensions: PackageDimensions? + public private(set) var price: Int? + public private(set) var product: String? + public private(set) var updated: Date? + + public init(node: Node) throws { + self.id = try node.get("id") + self.object = try node.get("object") + self.active = try node.get("active") + self.attributes = try node.get("attributes") + self.created = try node.get("created") + if let currency = node["currency"]?.string { + self.currency = StripeCurrency(rawValue: currency) + } + self.image = try node.get("image") + self.inventory = try node.get("inventory") + self.isLive = try node.get("livemode") + self.metadata = try node.get("metadata") + self.packageDimensions = try node.get("package_dimensions") + self.price = try node.get("price") + self.product = try node.get("product") + self.updated = try node.get("updated") + } + + public func makeNode(in context: Context?) throws -> Node { + + let object: [String: Any?] = [ + "id": self.id, + "object": self.object, + "active": self.active, + "attributes": self.attributes, + "created": self.created, + "currency": self.currency?.rawValue, + "image": self.image, + "inventory": self.inventory, + "livemode": self.isLive, + "metadata": self.metadata, + "package_dimensions": self.packageDimensions, + "price": self.price, + "product": self.product, + "updated": self.updated + ] + return try Node(node: object) + } +} diff --git a/Sources/Models/SKU/SKUList.swift b/Sources/Models/SKU/SKUList.swift new file mode 100644 index 0000000..b08ef30 --- /dev/null +++ b/Sources/Models/SKU/SKUList.swift @@ -0,0 +1,35 @@ +// +// SKUList.swift +// Stripe +// +// Created by Andrew Edwards on 8/22/17. +// +// + +import Foundation +import Vapor +import Helpers + +public final class SKUList: StripeModelProtocol { + public private(set) var object: String? + public private(set) var url: String? + public private(set) var hasMore: Bool? + public private(set) var items: [SKU]? + + public init(node: Node) throws { + self.object = try node.get("object") + self.url = try node.get("url") + self.hasMore = try node.get("has_more") + self.items = try node.get("data") + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String : Any?] = [ + "object": self.object, + "url": self.url, + "has_more": self.hasMore, + "data": self.items + ] + return try Node(node: object) + } +} diff --git a/Tests/StripeTests/SKUTests.swift b/Tests/StripeTests/SKUTests.swift new file mode 100644 index 0000000..2385517 --- /dev/null +++ b/Tests/StripeTests/SKUTests.swift @@ -0,0 +1,322 @@ +// +// SKUTests.swift +// Stripe +// +// Created by Andrew Edwards on 8/22/17. +// +// + +import XCTest + +@testable import Stripe +@testable import Vapor +@testable import Models +@testable import API +@testable import Helpers +@testable import Errors + +class SKUTests: XCTestCase { + + var drop: Droplet? + var skuId: String = "" + + override func setUp() { + do { + drop = try makeDroplet() + + + let productId = try drop?.stripe?.products.create(name: "Vapor Node", + id: nil, + active: nil, + attributes: try Node(node:["size"]), + caption: nil, + deactivateOn: nil, + description: "A Vapor Node", + images: nil, + packageDimensions: nil, + shippable: nil, + url: nil) + .serializedResponse().id ?? "" + + let inventory = try Node(node:[ + "quantity":55, + "type":InventoryType.finite.rawValue,]) + + + let attributes = try Node(node:["size": "xl"]) + + skuId = try drop?.stripe?.skus.create(currency: .usd, + inventory: inventory, + price: 2500, + product: productId, + id: nil, + active: nil, + attributes: attributes, + image: nil, + packageDimensions: nil) + .serializedResponse().id ?? "" + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + fatalError("Setup failed: \(error.localizedDescription)") + } + } + + override func tearDown() { + drop = nil + skuId = "" + } + + func testRetrieveSKU() throws { + do { + let sku = try drop?.stripe?.skus.retrieve(sku: skuId) + + XCTAssertNotNil(sku) + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testUpdateSKU() throws { + do { + let metadata = try Node(node:["hello":"world"]) + let attributes = try Node(node:["size":"xxl"]) + let inventory = try Node(node:[ + "quantity":54, + "type":InventoryType.finite.rawValue,]) + let packageDimenson = try Node(node: [ + "height": 12, + "length": 22, + "weight": 92, + "width": 33]) + let price = 2200 + + + let updatedSKU = try drop?.stripe?.skus.update(sku: skuId, + active: nil, + attributes: attributes, + currency: nil, + image: nil, + inventory: inventory, + packageDimensions: packageDimenson, + price: price, + product: nil, + metadata: metadata) + .serializedResponse() + + XCTAssertNotNil(updatedSKU) + + XCTAssertEqual(updatedSKU?.metadata?["hello"], metadata["hello"]) + + XCTAssertEqual(updatedSKU?.attributes?["size"], attributes["size"]) + + XCTAssertEqual(updatedSKU?.inventory?.quantity, inventory["quantity"]?.int) + + XCTAssertEqual(updatedSKU?.inventory?.type, InventoryType.finite) + + XCTAssertEqual(updatedSKU?.inventory?.value, nil) + + XCTAssertEqual(updatedSKU?.packageDimensions?.height, Decimal(packageDimenson["height"]?.int ?? 0)) + + XCTAssertEqual(updatedSKU?.packageDimensions?.length, Decimal(packageDimenson["length"]?.int ?? 0)) + + XCTAssertEqual(updatedSKU?.packageDimensions?.weight, Decimal(packageDimenson["weight"]?.int ?? 0)) + + XCTAssertEqual(updatedSKU?.packageDimensions?.width, Decimal(packageDimenson["width"]?.int ?? 0)) + + XCTAssertEqual(updatedSKU?.price, price) + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testDeleteSKU() throws { + do { + let deletedSKU = try drop?.stripe?.skus.delete(sku: skuId).serializedResponse() + + XCTAssertNotNil(deletedSKU) + + XCTAssertTrue(deletedSKU?.deleted ?? false) + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testListAllSKUs() throws { + do { + let skus = try drop?.stripe?.skus.listAll(filter: nil).serializedResponse() + + XCTAssertNotNil(skus) + + if let skuItems = skus?.items { + XCTAssertGreaterThanOrEqual(skuItems.count, 1) + } else { + XCTFail("SKUs are not present") + } + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testFilterSKUs() throws { + do { + let filter = StripeFilter() + + filter.limit = 1 + + let skus = try drop?.stripe?.skus.listAll(filter: filter).serializedResponse() + + XCTAssertNotNil(skus) + + if let skuItems = skus?.items { + XCTAssertEqual(skuItems.count, 1) + } else { + XCTFail("SKUs are not present") + } + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } +} From fc97d7d2ac7aa579c7a571f877225a0fddd06a75 Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Tue, 22 Aug 2017 23:48:58 -0400 Subject: [PATCH 08/26] Updated to new API Key --- Tests/StripeTests/Test+Droplet.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/StripeTests/Test+Droplet.swift b/Tests/StripeTests/Test+Droplet.swift index 0e002eb..38761ce 100644 --- a/Tests/StripeTests/Test+Droplet.swift +++ b/Tests/StripeTests/Test+Droplet.swift @@ -15,7 +15,7 @@ extension XCTestCase { func makeDroplet() throws -> Droplet { let config = Config([ "stripe": [ - "apiKey": "sk_test_O4dmN96uthPP7tvR8dNkPWZg" // Add your own API Key for tests + "apiKey": "sk_test_Wxn8UzIs9dkIR4qJYAtHhvY8" // Add your own API Key for tests ], ]) try config.addProvider(Stripe.Provider.self) From 9812ea611508f31ecb3ff3e465153c50cb4a950b Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Tue, 22 Aug 2017 23:49:28 -0400 Subject: [PATCH 09/26] Added new routes to client. --- Sources/API/StripeClient.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/API/StripeClient.swift b/Sources/API/StripeClient.swift index 84d35ba..4be25d8 100644 --- a/Sources/API/StripeClient.swift +++ b/Sources/API/StripeClient.swift @@ -24,6 +24,8 @@ public class StripeClient { public private(set) var subscriptions: SubscriptionRoutes! public private(set) var account: AccountRoutes! public private(set) var disputes: DisputeRoutes! + public private(set) var skus: SKURoutes! + public private(set) var products: ProductRoutes! public init(apiKey: String) throws { self.apiKey = apiKey @@ -42,5 +44,7 @@ public class StripeClient { self.subscriptions = SubscriptionRoutes(client: self) self.account = AccountRoutes(client: self) self.disputes = DisputeRoutes(client: self) + self.skus = SKURoutes(client: self) + self.products = ProductRoutes(client: self) } } From cc9e13945d3c22e2f087e1b4627d7e90791deae4 Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Tue, 22 Aug 2017 23:49:42 -0400 Subject: [PATCH 10/26] Fixed typo --- Sources/Models/Subscriptions/Subscription.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Models/Subscriptions/Subscription.swift b/Sources/Models/Subscriptions/Subscription.swift index 815b9ce..9893173 100644 --- a/Sources/Models/Subscriptions/Subscription.swift +++ b/Sources/Models/Subscriptions/Subscription.swift @@ -34,7 +34,7 @@ public final class Subscription: StripeModelProtocol { public private(set) var trialStart: Date? /** - Only these values ore mutable/updatable + Only these values are mutable/updatable https://stripe.com/docs/api/curl#update_subscription */ public private(set) var applicationFeePercent: Double? From f0c045ee09ab05fcd39848a0902359850197f425 Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Tue, 22 Aug 2017 23:50:23 -0400 Subject: [PATCH 11/26] Removed if lets and instead used Node initializer for model types. --- Sources/Models/Tokens/Token.swift | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/Sources/Models/Tokens/Token.swift b/Sources/Models/Tokens/Token.swift index de887e1..32cb693 100644 --- a/Sources/Models/Tokens/Token.swift +++ b/Sources/Models/Tokens/Token.swift @@ -33,35 +33,22 @@ public final class Token: StripeModelProtocol { self.created = try node.get("created") self.isLive = try node.get("livemode") self.isUsed = try node.get("used") - - if let _ = node["card"] { - self.card = try node.get("card") - } - - if let _ = node["bank_account"] { - self.bankAccount = try node.get("bank_account") - } + self.card = try node.get("card") + self.bankAccount = try node.get("bank_account") } public func makeNode(in context: Context?) throws -> Node { - var object: [String: Any?] = [ + let object: [String: Any?] = [ "id": self.id, "object": self.object, "type": self.type, "client_ip": self.clientIp, "created": self.created, "livemode": self.isLive, - "used": self.isUsed + "used": self.isUsed, + "card": self.card, + "banl_account": self.bankAccount ] - - if let value = self.card { - object["card"] = value - } - - if let value = self.bankAccount { - object["bank_account"] = value - } - return try Node(node: object) } From 4b50a3cbddb1a4db110df87b89155dbbcd7c95e8 Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Tue, 22 Aug 2017 23:51:52 -0400 Subject: [PATCH 12/26] Updated LinuxMain with SKU Tests. --- Tests/LinuxMain.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index f4461d5..39905a7 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -98,6 +98,16 @@ static var allTests = [ ] } +extension SKUTests { +static var allTests = [ + ("testRetrieveSKU", testRetrieveSKU), + ("testUpdateSKU", testUpdateSKU), + ("testDeleteSKU", testDeleteSKU), + ("testListAllSKUs", testListAllSKUs), + ("testFilterSKUs", testFilterSKUs), +] +} + extension SourceTests { static var allTests = [ ("testRetrieveSource", testRetrieveSource), @@ -143,6 +153,7 @@ XCTMain([ testCase(PlanTests.allTests), testCase(ProviderTests.allTests), testCase(RefundTests.allTests), + testCase(SKUTests.allTests), testCase(SourceTests.allTests), testCase(SubscriptionItemTests.allTests), testCase(SubscriptionTests.allTests), From 6c6a30ee005ed2bc42530b90ae67bf969135c3ea Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Fri, 25 Aug 2017 22:49:29 -0400 Subject: [PATCH 13/26] Added new models for Orders, Order Items and Order returns. --- Sources/Models/OrderList.swift | 35 +++++++ Sources/Models/Orders/OrderItem.swift | 55 +++++++++++ Sources/Models/Orders/OrderReturn.swift | 59 +++++++++++ Sources/Models/Orders/OrderReturnList.swift | 35 +++++++ Sources/Models/Orders/Orders.swift | 102 ++++++++++++++++++++ 5 files changed, 286 insertions(+) create mode 100644 Sources/Models/OrderList.swift create mode 100644 Sources/Models/Orders/OrderItem.swift create mode 100644 Sources/Models/Orders/OrderReturn.swift create mode 100644 Sources/Models/Orders/OrderReturnList.swift create mode 100644 Sources/Models/Orders/Orders.swift diff --git a/Sources/Models/OrderList.swift b/Sources/Models/OrderList.swift new file mode 100644 index 0000000..24ca4cf --- /dev/null +++ b/Sources/Models/OrderList.swift @@ -0,0 +1,35 @@ +// +// OrderList.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation +import Vapor +import Helpers + +public final class OrderList: StripeModelProtocol { + public private(set) var object: String? + public private(set) var url: String? + public private(set) var hasMore: Bool? + public private(set) var items: [Order]? + + public init(node: Node) throws { + self.object = try node.get("object") + self.url = try node.get("url") + self.hasMore = try node.get("has_more") + self.items = try node.get("data") + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String : Any?] = [ + "object": self.object, + "url": self.url, + "has_more": self.hasMore, + "data": self.items + ] + return try Node(node: object) + } +} diff --git a/Sources/Models/Orders/OrderItem.swift b/Sources/Models/Orders/OrderItem.swift new file mode 100644 index 0000000..65853fc --- /dev/null +++ b/Sources/Models/Orders/OrderItem.swift @@ -0,0 +1,55 @@ +// +// OrderItem.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation +import Vapor +import Helpers + +/** + OrderItem object + https://stripe.com/docs/api#order_item_object-object + */ + +public final class OrderItem: StripeModelProtocol { + + public private(set) var object: String? + public private(set) var amount: Int? + public private(set) var currency: StripeCurrency? + public private(set) var description: String? + public private(set) var parent: String? + public private(set) var quantity: Int? + public private(set) var type: OrderItemType? + + public init(node: Node) throws { + self.object = try node.get("object") + self.amount = try node.get("amount") + if let currency = node["currency"]?.string { + self.currency = StripeCurrency(rawValue: currency) + } + self.description = try node.get("description") + self.parent = try node.get("parent") + self.quantity = try node.get("quantity") + if let type = node["type"]?.string { + self.type = OrderItemType(rawValue: type) + } + } + + public func makeNode(in context: Context?) throws -> Node { + + let object: [String: Any?] = [ + "object": self.object, + "amount": self.amount, + "currency": self.currency?.rawValue, + "description": self.description, + "parent": self.parent, + "quantity": self.quantity, + "type": self.type + ] + return try Node(node: object) + } +} diff --git a/Sources/Models/Orders/OrderReturn.swift b/Sources/Models/Orders/OrderReturn.swift new file mode 100644 index 0000000..6b9ac45 --- /dev/null +++ b/Sources/Models/Orders/OrderReturn.swift @@ -0,0 +1,59 @@ +// +// OrderReturn.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation +import Vapor +import Helpers + +/** + OrderReturn object + https://stripe.com/docs/api#order_return_object + */ + +public final class OrderReturn: StripeModelProtocol { + + public private(set) var id: String? + public private(set) var object: String? + public private(set) var amount: Int? + public private(set) var created: Date? + public private(set) var currency: StripeCurrency? + public private(set) var items: [OrderItem]? + public private(set) var isLive: Bool? + public private(set) var order: String? + public private(set) var refund: String? + + public init(node: Node) throws { + self.id = try node.get("id") + self.object = try node.get("object") + self.amount = try node.get("amount") + self.created = try node.get("created") + if let currency = node["currency"]?.string { + self.currency = StripeCurrency(rawValue: currency) + } + self.items = try node.get("items") + self.isLive = try node.get("livemode") + self.order = try node.get("order") + self.refund = try node.get("refund") + } + + public func makeNode(in context: Context?) throws -> Node { + + let object: [String: Any?] = [ + "id": self.id, + "object": self.object, + "amount": self.amount, + "created": self.created, + "currency": self.currency?.rawValue, + "items": self.items, + "livemode": self.isLive, + "order": self.order, + "refund": self.refund, + ] + return try Node(node: object) + } +} diff --git a/Sources/Models/Orders/OrderReturnList.swift b/Sources/Models/Orders/OrderReturnList.swift new file mode 100644 index 0000000..748aec0 --- /dev/null +++ b/Sources/Models/Orders/OrderReturnList.swift @@ -0,0 +1,35 @@ +// +// OrderReturnList.swift +// Stripe +// +// Created by Andrew Edwards on 8/25/17. +// +// + +import Foundation +import Vapor +import Helpers + +public final class OrderReturnList: StripeModelProtocol { + public private(set) var object: String? + public private(set) var url: String? + public private(set) var hasMore: Bool? + public private(set) var items: [OrderReturn]? + + public init(node: Node) throws { + self.object = try node.get("object") + self.url = try node.get("url") + self.hasMore = try node.get("has_more") + self.items = try node.get("data") + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String : Any?] = [ + "object": self.object, + "url": self.url, + "has_more": self.hasMore, + "data": self.items + ] + return try Node(node: object) + } +} diff --git a/Sources/Models/Orders/Orders.swift b/Sources/Models/Orders/Orders.swift new file mode 100644 index 0000000..ea2148c --- /dev/null +++ b/Sources/Models/Orders/Orders.swift @@ -0,0 +1,102 @@ +// +// Orders.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation +import Vapor +import Helpers + +/** + Order object + https://stripe.com/docs/api#order_object + */ + +public final class Order: StripeModelProtocol { + + public private(set) var id: String? + public private(set) var object: String? + public private(set) var amount: Int? + public private(set) var amountReturned: Int? + public private(set) var application: String? + public private(set) var applicationFee: Int? + public private(set) var charge: String? + public private(set) var created: Date? + public private(set) var currency: StripeCurrency? + public private(set) var customer: String? + public private(set) var email: String? + public private(set) var externalCouponCode: String? + public private(set) var items: [OrderItem]? + public private(set) var isLive: Bool? + public private(set) var metadata: Node? + public private(set) var returns: [OrderReturn]? + public private(set) var selectedShippingMethod: String? + public private(set) var shipping: ShippingLabel? + public private(set) var shippingMethods: [ShippingMethod]? + public private(set) var status: OrderStatus? + public private(set) var statusTransitions: StatusTransitions? + public private(set) var updated: Date? + public private(set) var upstreamId: String? + + public init(node: Node) throws { + self.id = try node.get("id") + self.object = try node.get("object") + self.amount = try node.get("amount") + self.amountReturned = try node.get("amount_returned") + self.application = try node.get("application") + self.applicationFee = try node.get("application_fee") + self.charge = try node.get("charge") + self.created = try node.get("created") + if let currency = node["currency"]?.string { + self.currency = StripeCurrency(rawValue: currency) + } + self.customer = try node.get("customer") + self.email = try node.get("email") + self.externalCouponCode = try node.get("external_coupon_code") + self.items = try node.get("items") + self.isLive = try node.get("livemode") + self.metadata = try node.get("metadata") + self.returns = try node.get("returns") + self.selectedShippingMethod = try node.get("selected_shipping_method") + self.shipping = try node.get("shipping") + self.shippingMethods = try node.get("shipping_methods") + if let status = node["status"]?.string { + self.status = OrderStatus(rawValue: status) + } + self.statusTransitions = try node.get("status_transitions") + self.updated = try node.get("updated") + self.upstreamId = try node.get("upstream_id") + } + + public func makeNode(in context: Context?) throws -> Node { + + let object: [String: Any?] = [ + "id": self.id, + "object": self.object, + "amount": self.amount, + "amount_returned": self.amountReturned, + "application": self.application, + "application_fee": self.applicationFee, + "charge": self.charge, + "created": self.created, + "currency": self.currency?.rawValue, + "customer": self.customer, + "email": self.email, + "external_coupon_code": self.externalCouponCode, + "items": self.items, + "livemode": self.isLive, + "metadata": self.metadata, + "selected_shipping_method": self.selectedShippingMethod, + "shipping": self.shipping, + "shipping_methods": self.shippingMethods, + "status": self.status, + "status_transitions": self.statusTransitions, + "updated": self.updated, + "upstream_id": self.upstreamId + ] + return try Node(node: object) + } +} From ac0ccd3242637a4a0f846f397243d4d6cacf658d Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Fri, 25 Aug 2017 22:50:44 -0400 Subject: [PATCH 14/26] Added models for ShippingMethod, Delivery Estimate, and StatusTransitions. --- .../Models/Shipping/DeliveryEstimate.swift | 39 +++++++++++++++++ Sources/Models/Shipping/ShippingMethod.swift | 42 +++++++++++++++++++ .../Models/Shipping/StatusTransitions.swift | 37 ++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 Sources/Models/Shipping/DeliveryEstimate.swift create mode 100644 Sources/Models/Shipping/ShippingMethod.swift create mode 100644 Sources/Models/Shipping/StatusTransitions.swift diff --git a/Sources/Models/Shipping/DeliveryEstimate.swift b/Sources/Models/Shipping/DeliveryEstimate.swift new file mode 100644 index 0000000..f269c2c --- /dev/null +++ b/Sources/Models/Shipping/DeliveryEstimate.swift @@ -0,0 +1,39 @@ +// +// DeliveryEstimate.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation +import Vapor +import Helpers + +public final class DeliveryEstimate: StripeModelProtocol { + + public private(set) var date: String? + public private(set) var earliest: String? + public private(set) var latest: String? + public private(set) var type: DeliveryEstimateType? + + public init(node: Node) throws { + self.date = try node.get("date") + self.earliest = try node.get("earliest") + self.latest = try node.get("latest") + if let type = node["type"]?.string { + self.type = DeliveryEstimateType(rawValue: type) + } + } + + public func makeNode(in context: Context?) throws -> Node { + + let object: [String: Any?] = [ + "date": self.date, + "earliest": self.earliest, + "latest": self.latest, + "type": self.type?.rawValue, + ] + return try Node(node: object) + } +} diff --git a/Sources/Models/Shipping/ShippingMethod.swift b/Sources/Models/Shipping/ShippingMethod.swift new file mode 100644 index 0000000..35895d9 --- /dev/null +++ b/Sources/Models/Shipping/ShippingMethod.swift @@ -0,0 +1,42 @@ +// +// ShippingMethod.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation +import Vapor +import Helpers + +public final class ShippingMethod: StripeModelProtocol { + + public private(set) var id: String? + public private(set) var amount: Int? + public private(set) var currency: StripeCurrency? + public private(set) var deliveryEstimate: DeliveryEstimate? + public private(set) var description: String? + + public init(node: Node) throws { + self.id = try node.get("id") + self.amount = try node.get("amount") + if let currency = node["currency"]?.string { + self.currency = StripeCurrency(rawValue: currency) + } + self.deliveryEstimate = try node.get("delivery_estimate") + self.description = try node.get("description") + } + + public func makeNode(in context: Context?) throws -> Node { + + let object: [String: Any?] = [ + "id": self.id, + "amount": self.amount, + "currency": self.currency?.rawValue, + "delivery_estimate": self.deliveryEstimate, + "description": self.description, + ] + return try Node(node: object) + } +} diff --git a/Sources/Models/Shipping/StatusTransitions.swift b/Sources/Models/Shipping/StatusTransitions.swift new file mode 100644 index 0000000..6eb6d74 --- /dev/null +++ b/Sources/Models/Shipping/StatusTransitions.swift @@ -0,0 +1,37 @@ +// +// StatusTransitions.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation +import Vapor +import Helpers + +public final class StatusTransitions: StripeModelProtocol { + + public private(set) var canceled: Date? + public private(set) var fufilled: Date? + public private(set) var paid: Date? + public private(set) var returned: Date? + + public init(node: Node) throws { + self.canceled = try node.get("canceled") + self.fufilled = try node.get("fufilled") + self.paid = try node.get("paid") + self.returned = try node.get("returned") + } + + public func makeNode(in context: Context?) throws -> Node { + + let object: [String: Any?] = [ + "canceled": self.canceled, + "fufilled": self.fufilled, + "paid": self.paid, + "returned": self.returned, + ] + return try Node(node: object) + } +} From 66a66f0aa14ec12158df939324b101949770d58c Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Fri, 25 Aug 2017 22:51:42 -0400 Subject: [PATCH 15/26] Added new enum types for Orders. --- Sources/Helpers/DeliveryEstimateType.swift | 14 ++++++++++++++ Sources/Helpers/OrderItemType.swift | 17 +++++++++++++++++ Sources/Helpers/OrderStatus.swift | 17 +++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 Sources/Helpers/DeliveryEstimateType.swift create mode 100644 Sources/Helpers/OrderItemType.swift create mode 100644 Sources/Helpers/OrderStatus.swift diff --git a/Sources/Helpers/DeliveryEstimateType.swift b/Sources/Helpers/DeliveryEstimateType.swift new file mode 100644 index 0000000..96f1f57 --- /dev/null +++ b/Sources/Helpers/DeliveryEstimateType.swift @@ -0,0 +1,14 @@ +// +// DeliveryEstimateType.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation + +public enum DeliveryEstimateType: String { + case range + case latest +} diff --git a/Sources/Helpers/OrderItemType.swift b/Sources/Helpers/OrderItemType.swift new file mode 100644 index 0000000..bfc44da --- /dev/null +++ b/Sources/Helpers/OrderItemType.swift @@ -0,0 +1,17 @@ +// +// OrderItemType.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation + +public enum OrderItemType: String { + case sku = "sku" + case tax = "tax" + case shipping = "shipping" + case discount = "discount" + case none = "none" +} diff --git a/Sources/Helpers/OrderStatus.swift b/Sources/Helpers/OrderStatus.swift new file mode 100644 index 0000000..09454e4 --- /dev/null +++ b/Sources/Helpers/OrderStatus.swift @@ -0,0 +1,17 @@ +// +// OrderStatus.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation + +public enum OrderStatus: String { + case created = "created" + case paid = "paid" + case canceled = "canceled" + case fulfilled = "fulfilled" + case returned = "returned" +} From 58aedc97d494d493929f10116a4cf53c67e5b5eb Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Fri, 25 Aug 2017 22:52:15 -0400 Subject: [PATCH 16/26] Added new routes. --- Sources/API/StripeClient.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/API/StripeClient.swift b/Sources/API/StripeClient.swift index 4be25d8..b2dface 100644 --- a/Sources/API/StripeClient.swift +++ b/Sources/API/StripeClient.swift @@ -26,6 +26,8 @@ public class StripeClient { public private(set) var disputes: DisputeRoutes! public private(set) var skus: SKURoutes! public private(set) var products: ProductRoutes! + public private(set) var orders: OrderRoutes! + public private(set) var orderReturns: OrderReturnRoutes! public init(apiKey: String) throws { self.apiKey = apiKey @@ -46,5 +48,7 @@ public class StripeClient { self.disputes = DisputeRoutes(client: self) self.skus = SKURoutes(client: self) self.products = ProductRoutes(client: self) + self.orders = OrderRoutes(client: self) + self.orderReturns = OrderReturnRoutes(client: self) } } From 6cb510275b523857440e266133bf767c9d140781 Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Fri, 25 Aug 2017 22:52:56 -0400 Subject: [PATCH 17/26] Added new skids for filters. --- Sources/API/Filter/StripeFilter.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sources/API/Filter/StripeFilter.swift b/Sources/API/Filter/StripeFilter.swift index 33a2047..afd3748 100644 --- a/Sources/API/Filter/StripeFilter.swift +++ b/Sources/API/Filter/StripeFilter.swift @@ -141,6 +141,11 @@ public final class StripeFilter { */ public var url: Node? + /** + Only return SKUs with the given IDs. + */ + public var skuIds: Node? + internal func createBody() throws -> Node { var node = Node([:]) if let value = self.created { @@ -222,6 +227,10 @@ public final class StripeFilter { node["url"] = url } + if let skuids = skuIds?.array { + node["id"] = Node(skuids) + } + return node } From 79ebda652a515c7371c24df971276c482797cdb8 Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Fri, 25 Aug 2017 22:53:37 -0400 Subject: [PATCH 18/26] Added new routes and cases. --- Sources/API/Helpers/Endpoints.swift | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Sources/API/Helpers/Endpoints.swift b/Sources/API/Helpers/Endpoints.swift index ff924bf..57e430f 100644 --- a/Sources/API/Helpers/Endpoints.swift +++ b/Sources/API/Helpers/Endpoints.swift @@ -139,12 +139,28 @@ internal enum API { case skus(String) /** - Products + PRODUCTS Store representations of products you sell in product objects, used in conjunction with SKUs. */ case product case products(String) + /** + ORDERS + The purchase of previously defined products + */ + case order + case orders(String) + case ordersPay(String) + case ordersReturn(String) + + /** + RETURNS + A return represents the full or partial return of a number of order items. + */ + case orderReturn + case orderReturns(String) + var endpoint: String { switch self { case .balance: return APIBase + APIVersion + "balance" @@ -196,6 +212,14 @@ internal enum API { case .product: return APIBase + APIVersion + "products" case .products(let id): return APIBase + APIVersion + "products/\(id)" + + case .order: return APIBase + APIVersion + "orders" + case .orders(let id): return APIBase + APIVersion + "orders/\(id)" + case .ordersPay(let id): return APIBase + APIVersion + "orders/\(id)/pay" + case .ordersReturn(let id): return APIBase + APIVersion + "orders/\(id)/returns" + + case .orderReturn: return APIBase + APIVersion + "order_returns" + case .orderReturns(let id): return APIBase + APIVersion + "order_returns/\(id)" } } } From d0842a1385f92ae4a84d62f5217e7a751615deb1 Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Fri, 25 Aug 2017 22:58:10 -0400 Subject: [PATCH 19/26] Fixed the items array not properly being parsed. --- Sources/API/Routes/SubscriptionRoutes.swift | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Sources/API/Routes/SubscriptionRoutes.swift b/Sources/API/Routes/SubscriptionRoutes.swift index 124afb0..64c563a 100644 --- a/Sources/API/Routes/SubscriptionRoutes.swift +++ b/Sources/API/Routes/SubscriptionRoutes.swift @@ -60,17 +60,22 @@ public final class SubscriptionRoutes { if let plan = plan { body["plan"] = Node(plan) } + if let appFeePercent = applicationFeePercent { body["application_fee_percent"] = Node(appFeePercent) } + if let couponId = couponId { body["coupon"] = Node(couponId) } - if let items = items?.object { - for (key,value) in items { - body["items[\(key)"] = value + + if let items = items?.array { + + for x in 0.. Date: Fri, 25 Aug 2017 22:58:49 -0400 Subject: [PATCH 20/26] Added new routes for Orders. --- Sources/API/Routes/OrderReturnRoutes.swift | 34 +++ Sources/API/Routes/OrderRoutes.swift | 231 +++++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 Sources/API/Routes/OrderReturnRoutes.swift create mode 100644 Sources/API/Routes/OrderRoutes.swift diff --git a/Sources/API/Routes/OrderReturnRoutes.swift b/Sources/API/Routes/OrderReturnRoutes.swift new file mode 100644 index 0000000..c1d8a12 --- /dev/null +++ b/Sources/API/Routes/OrderReturnRoutes.swift @@ -0,0 +1,34 @@ +// +// OrderReturnRoutes.swift +// Stripe +// +// Created by Andrew Edwards on 8/25/17. +// +// + +import Foundation +import Node +import Models +import Helpers +import HTTP + +public final class OrderReturnRoutes { + let client: StripeClient + + init(client: StripeClient) { + self.client = client + } + + public func retrieve(orderReturn orderReturnId: String) throws -> StripeRequest { + return try StripeRequest(client: self.client, method: .get, route: .orderReturns(orderReturnId), query: [:], body: nil, headers: nil) + } + + public func listAll(filter: StripeFilter? = nil) throws -> StripeRequest { + var query = [String : NodeRepresentable]() + + if let data = try filter?.createQuery() { + query = data + } + return try StripeRequest(client: self.client, method: .get, route: .orderReturn, query: query, body: nil, headers: nil) + } +} diff --git a/Sources/API/Routes/OrderRoutes.swift b/Sources/API/Routes/OrderRoutes.swift new file mode 100644 index 0000000..d2e2f0d --- /dev/null +++ b/Sources/API/Routes/OrderRoutes.swift @@ -0,0 +1,231 @@ +// +// OrderRoutes.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation +import Node +import Models +import Helpers +import HTTP + +public final class OrderRoutes { + let client: StripeClient + + init(client: StripeClient) { + self.client = client + } + + public func create(currency: StripeCurrency, coupon: String?, customer: String?, email: String?, items: Node?, shipping: Node?, metadata: Node? = nil) throws -> StripeRequest { + + var body = Node([:]) + + body["currency"] = Node(currency.rawValue) + + if let coupon = coupon { + body["coupon"] = Node(coupon) + } + + if let customer = customer { + body["customer"] = Node(customer) + } + + if let email = email { + body["email"] = Node(email) + } + + if let items = items?.array { + + for x in 0.. StripeRequest { + return try StripeRequest(client: self.client, method: .get, route: .orders(orderId), query: [:], body: nil, headers: nil) + } + + public func update(order orderId: String, coupon: String?, selectedShippingMethod: String?, shippingInformation: Node?, status: OrderStatus?, metadata: Node? = nil) throws -> StripeRequest { + + var body = Node([:]) + + if let coupon = coupon { + body["coupon"] = Node(coupon) + } + + if let shippingMethod = selectedShippingMethod { + body["selected_shipping_method"] = Node(shippingMethod) + } + + if let shippingInformation = shippingInformation?.object { + if let carrier = shippingInformation["carrier"]?.string { + body["shipping[carrier]"] = Node(carrier) + } + + if let trackingNumber = shippingInformation["tracking_number"]?.string { + body["shipping[tracking_number]"] = Node(trackingNumber) + } + } + + if let status = status { + body["status"] = Node(status.rawValue) + } + + if let metadata = metadata?.object { + for (key, value) in metadata { + body["metadata[\(key)]"] = value + } + } + + return try StripeRequest(client: self.client, method: .post, route: .orders(orderId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + } + + public func pay(order orderId: String, customer: String?, source: Node?, applicationFee: Int?, email: String?, metadata: Node? = nil) throws -> StripeRequest { + var body = Node([:]) + + if let customer = customer { + body["customer"] = Node(customer) + } + + if let source = source?.object { + + if let exp = source["exp_month"]?.int { + body["source[exp_month]"] = Node(exp) + } + + if let expYear = source["exp_year"]?.int { + body["source[exp_year]"] = Node(expYear) + } + + if let number = source["number"]?.string { + body["source[number]"] = Node(number) + } + + if let object = source["object"]?.string { + body["source[object]"] = Node(object) + } + + if let cvc = source["cvc"]?.int { + body["source[cvc]"] = Node(cvc) + } + + if let city = source["address_city"]?.string { + body["source[address_city]"] = Node(city) + } + + if let country = source["address_country"]?.string { + body["source[address_country]"] = Node(country) + } + + if let line1 = source["address_line1"]?.string { + body["source[address_line1]"] = Node(line1) + } + + if let line2 = source["address_line2"]?.string { + body["source[address_line2]"] = Node(line2) + } + + if let name = source["name"]?.string { + body["source[name]"] = Node(name) + } + + if let state = source["address_state"]?.string { + body["source[address_state]"] = Node(state) + } + + if let zip = source["address_zip"]?.string { + body["source[address_zip]"] = Node(zip) + } + } + + if let source = source?.string { + body["source"] = Node(source) + } + + if let appFee = applicationFee { + body["application_fee"] = Node(appFee) + } + + if let email = email { + body["email"] = Node(email) + } + + if let metadata = metadata?.object { + for (key, value) in metadata { + body["metadata[\(key)]"] = value + } + } + + return try StripeRequest(client: self.client, method: .post, route: .ordersPay(orderId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + } + + public func listAll(filter: StripeFilter? = nil) throws -> StripeRequest { + var query = [String : NodeRepresentable]() + if let data = try filter?.createQuery() { + query = data + } + return try StripeRequest(client: self.client, method: .get, route: .order, query: query, body: nil, headers: nil) + } + + public func `return`(order orderId: String, items: Node?) throws -> StripeRequest { + + var body = Node([:]) + + if let items = items?.array { + + for x in 0.. Date: Fri, 25 Aug 2017 22:59:26 -0400 Subject: [PATCH 21/26] Added new tests for Orders and products. --- Tests/StripeTests/OrderReturnTests.swift | 238 +++++++++++++++ Tests/StripeTests/OrderTests.swift | 367 +++++++++++++++++++++++ Tests/StripeTests/ProductTests.swift | 298 ++++++++++++++++++ 3 files changed, 903 insertions(+) create mode 100644 Tests/StripeTests/OrderReturnTests.swift create mode 100644 Tests/StripeTests/OrderTests.swift create mode 100644 Tests/StripeTests/ProductTests.swift diff --git a/Tests/StripeTests/OrderReturnTests.swift b/Tests/StripeTests/OrderReturnTests.swift new file mode 100644 index 0000000..1a31b5f --- /dev/null +++ b/Tests/StripeTests/OrderReturnTests.swift @@ -0,0 +1,238 @@ +// +// OrderReturnTests.swift +// Stripe +// +// Created by Andrew Edwards on 8/25/17. +// +// + +import XCTest + +@testable import Stripe +@testable import Vapor +@testable import Models +@testable import API +@testable import Helpers +@testable import Errors + +class OrderReturnTests: XCTestCase { + + var drop: Droplet? + var orderReturnId: String = "" + + override func setUp() { + do { + drop = try makeDroplet() + + let productId = try drop?.stripe?.products.create(name: "Vapor Node", + id: nil, + active: nil, + attributes: try Node(node:["size"]), + caption: nil, + deactivateOn: nil, + description: "A Vapor Node", + images: nil, + packageDimensions: nil, + shippable: true, + url: nil) + .serializedResponse().id ?? "" + + let inventory = try Node(node:[ + "quantity":55, + "type":InventoryType.finite.rawValue,]) + + + let attributes = try Node(node:["size": "xl"]) + + let skuId = try drop?.stripe?.skus.create(currency: .usd, + inventory: inventory, + price: 2500, + product: productId, + id: nil, + active: nil, + attributes: attributes, + image: nil, + packageDimensions: nil) + .serializedResponse().id ?? "" + + let items = try Node(node: [["parent": skuId]]) + + let shippingInfo = Node([ + "name": "Mr. Vapor", + "address": ["line1": "123 main street"] + ]) + + let orderId = try drop?.stripe?.orders.create(currency: .usd, + coupon: nil, + customer: nil, + email: nil, + items: items, + shipping: shippingInfo).serializedResponse().id ?? "" + + let source = Node([ + "exp_month": 12, + "exp_year": 2020, + "number": "4242424242424242", + "object": "card", + "cvc": 123 + ]) + + let paidOrder = try drop?.stripe?.orders.pay(order: orderId, + customer: nil, + source: source, + applicationFee: nil, + email: "john@sno.com").serializedResponse().id ?? "" + + orderReturnId = try drop?.stripe?.orders.return(order: paidOrder, items: nil).serializedResponse().id ?? "" + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + fatalError("Setup failed: \(error.localizedDescription)") + } + } + + override func tearDown() { + drop = nil + orderReturnId = "" + } + + func testRetrieveOrderReturn() throws { + do { + let orderReturn = try drop?.stripe?.orderReturns.retrieve(orderReturn: orderReturnId).serializedResponse() + + XCTAssertNotNil(orderReturn) + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testListAllOrderReturns() throws { + do { + let orderReturns = try drop?.stripe?.orderReturns.listAll(filter: nil).serializedResponse() + + XCTAssertNotNil(orderReturns) + + if let orderReturnItems = orderReturns?.items { + XCTAssertGreaterThanOrEqual(orderReturnItems.count, 1) + } else { + XCTFail("OrderReturns are not present") + } + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testFilterOrderReturns() throws { + do { + let filter = StripeFilter() + + filter.limit = 1 + + let orderReturns = try drop?.stripe?.orderReturns.listAll(filter: filter).serializedResponse() + + XCTAssertNotNil(orderReturns) + + if let orderReturnItems = orderReturns?.items { + XCTAssertEqual(orderReturnItems.count, 1) + } else { + XCTFail("OrderReturns are not present") + } + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } +} diff --git a/Tests/StripeTests/OrderTests.swift b/Tests/StripeTests/OrderTests.swift new file mode 100644 index 0000000..f1a9449 --- /dev/null +++ b/Tests/StripeTests/OrderTests.swift @@ -0,0 +1,367 @@ +// +// OrderTests.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import XCTest + +@testable import Stripe +@testable import Vapor +@testable import Models +@testable import API +@testable import Helpers +@testable import Errors + +class OrderTests: XCTestCase { + + var drop: Droplet? + var orderId: String = "" + + override func setUp() { + do { + drop = try makeDroplet() + + let productId = try drop?.stripe?.products.create(name: "Vapor Node", + id: nil, + active: nil, + attributes: try Node(node:["size"]), + caption: nil, + deactivateOn: nil, + description: "A Vapor Node", + images: nil, + packageDimensions: nil, + shippable: true, + url: nil) + .serializedResponse().id ?? "" + + let inventory = try Node(node:[ + "quantity":55, + "type":InventoryType.finite.rawValue,]) + + + let attributes = try Node(node:["size": "xl"]) + + let skuId = try drop?.stripe?.skus.create(currency: .usd, + inventory: inventory, + price: 2500, + product: productId, + id: nil, + active: nil, + attributes: attributes, + image: nil, + packageDimensions: nil) + .serializedResponse().id ?? "" + + let items = try Node(node: [["parent": skuId]]) + + let shippingInfo = Node([ + "name": "Mr. Vapor", + "address": ["line1": "123 main street"] + ]) + + orderId = try drop?.stripe?.orders.create(currency: .usd, + coupon: nil, + customer: nil, + email: nil, + items: items, + shipping: shippingInfo).serializedResponse().id ?? "" + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + fatalError("Setup failed: \(error.localizedDescription)") + } + } + + override func tearDown() { + drop = nil + orderId = "" + } + + func testRetrieveOrder() throws { + do { + let order = try drop?.stripe?.orders.retrieve(order: orderId).serializedResponse() + + XCTAssertNotNil(order) + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testUpdateOrder() throws { + do { + let metadata = try Node(node:["hello":"world"]) + + let updatedOrder = try drop?.stripe?.orders.update(order: orderId, + coupon: nil, + selectedShippingMethod: nil, + shippingInformation: nil, + status: nil, + metadata: metadata).serializedResponse() + + XCTAssertNotNil(updatedOrder) + + XCTAssertEqual(updatedOrder?.metadata?["hello"], metadata["hello"]) + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testPayOrder() throws { + do { + + let source = Node([ + "exp_month": 12, + "exp_year": 2020, + "number": "4242424242424242", + "object": "card", + "cvc": 123 + ]) + + let paidOrder = try drop?.stripe?.orders.pay(order: orderId, + customer: nil, + source: source, + applicationFee: nil, + email: "john@sno.com").serializedResponse() + + XCTAssertNotNil(paidOrder) + + XCTAssertEqual(paidOrder?.status, .paid) + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testListAllOrders() throws { + do { + let orders = try drop?.stripe?.orders.listAll(filter: nil).serializedResponse() + + XCTAssertNotNil(orders) + + if let orderItems = orders?.items { + XCTAssertGreaterThanOrEqual(orderItems.count, 1) + } else { + XCTFail("Orders are not present") + } + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testFilterOrders() throws { + do { + let filter = StripeFilter() + + filter.limit = 1 + + let orders = try drop?.stripe?.orders.listAll(filter: filter).serializedResponse() + + XCTAssertNotNil(orders) + + if let orderItems = orders?.items { + XCTAssertEqual(orderItems.count, 1) + } else { + XCTFail("Orders are not present") + } + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testReturnOrder() throws { + do { + + let source = Node([ + "exp_month": 12, + "exp_year": 2020, + "number": "4242424242424242", + "object": "card", + "cvc": 123 + ]) + + let paidOrder = try drop?.stripe?.orders.pay(order: orderId, + customer: nil, + source: source, + applicationFee: nil, + email: "john@sno.com").serializedResponse() + + XCTAssertNotNil(paidOrder) + + XCTAssertEqual(paidOrder?.status, .paid) + + let returnedOrder = try drop?.stripe?.orders.return(order: orderId, items: nil).serializedResponse() + + XCTAssertNotNil(returnedOrder) + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } +} diff --git a/Tests/StripeTests/ProductTests.swift b/Tests/StripeTests/ProductTests.swift new file mode 100644 index 0000000..500fe86 --- /dev/null +++ b/Tests/StripeTests/ProductTests.swift @@ -0,0 +1,298 @@ +// +// ProductTests.swift +// Stripe +// +// Created by Andrew Edwards on 8/25/17. +// +// + +import XCTest + +@testable import Stripe +@testable import Vapor +@testable import Models +@testable import API +@testable import Helpers +@testable import Errors + +class ProductTests: XCTestCase { + + var drop: Droplet? + var productId: String = "" + + override func setUp() { + do { + drop = try makeDroplet() + + + productId = try drop?.stripe?.products.create(name: "Vapor Node", + id: nil, + active: nil, + attributes: try Node(node:["size"]), + caption: nil, + deactivateOn: nil, + description: "A Vapor Node", + images: nil, + packageDimensions: nil, + shippable: nil, + url: nil) + .serializedResponse().id ?? "" + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + fatalError("Setup failed: \(error.localizedDescription)") + } + } + + override func tearDown() { + drop = nil + productId = "" + } + + func testRetrieveProduct() throws { + do { + let product = try drop?.stripe?.products.retrieve(product: productId).serializedResponse() + + XCTAssertNotNil(product) + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testUpdateProduct() throws { + do { + let metadata = try Node(node:["hello":"world"]) + let attributes = try Node(node:["size","color"]) + let caption = "A super cool shirt" + let description = "A new Vapor Node" + let dimensions = Node([ + "height": 1, + "length": 1, + "weight": 1, + "width": 1 + ]) + + let updatedProduct = try drop?.stripe?.products.update(product: productId, + active: nil, + attributes: attributes, + caption: caption, + deactivateOn: nil, + description: description, + images: nil, + name: nil, + packageDimensions: dimensions, + shippable: nil, + url: nil, + metadata: metadata).serializedResponse() + XCTAssertNotNil(updatedProduct) + + XCTAssertEqual(updatedProduct?.metadata?["hello"], metadata["hello"]) + + XCTAssertEqual(updatedProduct?.attributes, attributes) + + XCTAssertEqual(updatedProduct?.caption, caption) + + XCTAssertEqual(updatedProduct?.description, description) + + XCTAssertEqual(updatedProduct?.packageDimensions?.height, Decimal(dimensions["height"]?.int ?? 0)) + + XCTAssertEqual(updatedProduct?.packageDimensions?.length, Decimal(dimensions["length"]?.int ?? 0)) + + XCTAssertEqual(updatedProduct?.packageDimensions?.weight, Decimal(dimensions["weight"]?.int ?? 0)) + + XCTAssertEqual(updatedProduct?.packageDimensions?.width, Decimal(dimensions["width"]?.int ?? 0)) + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testDeleteProduct() throws { + do { + let deletedProduct = try drop?.stripe?.products.delete(product: productId).serializedResponse() + + XCTAssertNotNil(deletedProduct) + + XCTAssertTrue(deletedProduct?.deleted ?? false) + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testListAllProducts() throws { + do { + let products = try drop?.stripe?.products.listAll(filter: nil).serializedResponse() + + XCTAssertNotNil(products) + + if let productItems = products?.items { + XCTAssertGreaterThanOrEqual(productItems.count, 1) + } else { + XCTFail("Products are not present") + } + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } + + func testFilterProducts() throws { + do { + let filter = StripeFilter() + + filter.limit = 1 + + let products = try drop?.stripe?.products.listAll(filter: filter).serializedResponse() + + XCTAssertNotNil(products) + + if let productItems = products?.items { + XCTAssertEqual(productItems.count, 1) + } else { + XCTFail("Products are not present") + } + } + catch let error as StripeError { + + switch error { + case .apiConnectionError: + XCTFail(error.localizedDescription) + case .apiError: + XCTFail(error.localizedDescription) + case .authenticationError: + XCTFail(error.localizedDescription) + case .cardError: + XCTFail(error.localizedDescription) + case .invalidRequestError: + XCTFail(error.localizedDescription) + case .rateLimitError: + XCTFail(error.localizedDescription) + case .validationError: + XCTFail(error.localizedDescription) + case .invalidSourceType: + XCTFail(error.localizedDescription) + default: + XCTFail(error.localizedDescription) + } + } + catch { + XCTFail(error.localizedDescription) + } + } +} From 90fe17b503c7ad15ffad3ac9da4577108d08e35b Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Fri, 25 Aug 2017 22:59:58 -0400 Subject: [PATCH 22/26] Updated LinuxMain Tests --- Tests/LinuxMain.swift | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 39905a7..dc858f1 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -72,6 +72,25 @@ static var allTests = [ ] } +extension OrderReturnTests { +static var allTests = [ + ("testRetrieveOrderReturn", testRetrieveOrderReturn), + ("testListAllOrderReturns", testListAllOrderReturns), + ("testFilterOrderReturns", testFilterOrderReturns), +] +} + +extension OrderTests { +static var allTests = [ + ("testRetrieveOrder", testRetrieveOrder), + ("testUpdateOrder", testUpdateOrder), + ("testPayOrder", testPayOrder), + ("testListAllOrders", testListAllOrders), + ("testFilterOrders", testFilterOrders), + ("testReturnOrder", testReturnOrder), +] +} + extension PlanTests { static var allTests = [ ("testCreatePlan", testCreatePlan), @@ -83,6 +102,16 @@ static var allTests = [ ] } +extension ProductTests { +static var allTests = [ + ("testRetrieveProduct", testRetrieveProduct), + ("testUpdateProduct", testUpdateProduct), + ("testDeleteProduct", testDeleteProduct), + ("testListAllProducts", testListAllProducts), + ("testFilterProducts", testFilterProducts), +] +} + extension ProviderTests { static var allTests = [ ("testProvider", testProvider), @@ -150,7 +179,10 @@ XCTMain([ testCase(CouponTests.allTests), testCase(CustomerTests.allTests), testCase(DisputeTests.allTests), + testCase(OrderReturnTests.allTests), + testCase(OrderTests.allTests), testCase(PlanTests.allTests), + testCase(ProductTests.allTests), testCase(ProviderTests.allTests), testCase(RefundTests.allTests), testCase(SKUTests.allTests), From e65b97ed4112a250a3a9bb0d686faba017aa1f77 Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Fri, 25 Aug 2017 23:03:08 -0400 Subject: [PATCH 23/26] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 44f5d85..18c2b5f 100644 --- a/README.md +++ b/README.md @@ -116,10 +116,11 @@ XCTMain([ * [x] Listing All Accounts (With filters) * [x] Rejecting accounts * [x] Creating dashboard login link for express accounts +* [x] Orders +* [x] Order Items +* [x] Products * [ ] Disputes * [ ] Cards -* [ ] Orders -* [ ] Order Items [stripe_home]: http://stripe.com "Stripe" [stripe_api]: https://stripe.com/docs/api "Stripe API Endpoints" From 9ddade60025a5df6a5c5922983f3b0c83221de5f Mon Sep 17 00:00:00 2001 From: Andrew Edwards Date: Sat, 26 Aug 2017 10:08:20 -0400 Subject: [PATCH 24/26] Added enumerated sequence for items parsing. --- Sources/API/Routes/OrderRoutes.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/API/Routes/OrderRoutes.swift b/Sources/API/Routes/OrderRoutes.swift index d2e2f0d..6bed9c3 100644 --- a/Sources/API/Routes/OrderRoutes.swift +++ b/Sources/API/Routes/OrderRoutes.swift @@ -39,8 +39,8 @@ public final class OrderRoutes { if let items = items?.array { - for x in 0.. Date: Sat, 26 Aug 2017 10:09:29 -0400 Subject: [PATCH 25/26] Added enumeration sequence for items parsing. --- Sources/API/Routes/SubscriptionRoutes.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/API/Routes/SubscriptionRoutes.swift b/Sources/API/Routes/SubscriptionRoutes.swift index 64c563a..13f112f 100644 --- a/Sources/API/Routes/SubscriptionRoutes.swift +++ b/Sources/API/Routes/SubscriptionRoutes.swift @@ -170,8 +170,8 @@ public final class SubscriptionRoutes { if let items = items?.array { - for x in 0.. Date: Sat, 26 Aug 2017 12:33:07 -0400 Subject: [PATCH 26/26] Added enumeration for items parsing. --- Sources/API/Routes/SubscriptionRoutes.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/API/Routes/SubscriptionRoutes.swift b/Sources/API/Routes/SubscriptionRoutes.swift index 13f112f..8bd213e 100644 --- a/Sources/API/Routes/SubscriptionRoutes.swift +++ b/Sources/API/Routes/SubscriptionRoutes.swift @@ -71,8 +71,8 @@ public final class SubscriptionRoutes { if let items = items?.array { - for x in 0..