diff --git a/README.md b/README.md index 8e6149f..e4abedb 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,31 @@ let object = try drop.stripe?.balance.history().serializedResponse() ~~~~ The object is returned response model, or model array. +## Testing + +To avoid having to remember to add tests to `LinuxMain.swift` you can use [Sourcery][sourcery] to add your tets cases there for you. Just install the sourcery binary with Homebrew `brew install sourcery`, navigate to your project folder, and from the command line run the following: +~~~~bash +sourcery --sources Tests/ --templates Sourcery/LinuxMain.stencil --args testimports='@testable import StripeTests' +~~~~ +It will generate the following with your tests added: + +~~~~swift +import XCTest +@testable import StripeTests +extension BalanceTests { +static var allTests = [ + ("testBalance", testBalance), + ... +] +} +. +. +XCTMain([ + testCase(BalanceTests.allTests), + ... +]) +~~~~ + ## Whats Implemented * [x] Balance Fetching * [x] History @@ -52,6 +77,12 @@ The object is returned response model, or model array. * [x] Deleting * [x] Fetching by Customer ID * [x] Listing All Customers (With filters) +* [x] Coupons + * [x] Creating + * [x] Updating + * [x] Deleting + * [x] Fetching by Coupon ID + * [x] Listing All Coupons (With filters) * [ ] Disputes * [x] Refunds * [x] Creating a Refund @@ -68,3 +99,4 @@ The object is returned response model, or model array. [stripe_home]: http://stripe.com "Stripe" [stripe_api]: https://stripe.com/docs/api "Stripe API Endpoints" +[sourcery]: https://github.com/krzysztofzablocki/Sourcery "Sourcery" diff --git a/Sourcery/LinuxMain.stencil b/Sourcery/LinuxMain.stencil new file mode 100644 index 0000000..5b3f946 --- /dev/null +++ b/Sourcery/LinuxMain.stencil @@ -0,0 +1,17 @@ +// sourcery:file:Tests/LinuxMain.swift +import XCTest +{{ argument.testimports }} + +{% for type in types.classes|based:"XCTestCase" %} +{% if not type.annotations.disableTests %}extension {{ type.name }} { +static var allTests = [ +{% for method in type.methods %}{% if method.parameters.count == 0 and method.shortName|hasPrefix:"test" %} ("{{ method.shortName }}", {{ method.shortName }}), +{% endif %}{% endfor %}] +} + +{% endif %}{% endfor %} + +XCTMain([ +{% for type in types.classes|based:"XCTestCase" %}{% if not type.annotations.disableTests %} testCase({{ type.name }}.allTests), +{% endif %}{% endfor %}]) +// sourcery:end diff --git a/Sources/API/Filter/Filter.swift b/Sources/API/Filter/StripeFilter.swift similarity index 76% rename from Sources/API/Filter/Filter.swift rename to Sources/API/Filter/StripeFilter.swift index b503375..9660362 100644 --- a/Sources/API/Filter/Filter.swift +++ b/Sources/API/Filter/StripeFilter.swift @@ -1,5 +1,5 @@ // -// Filter.swift +// StripeFilter.swift // Stripe // // Created by Anthony Castelli on 4/16/17. @@ -12,7 +12,7 @@ import Helpers import Node import HTTP -public final class Filter { +public final class StripeFilter { public init() { } @@ -31,6 +31,26 @@ public final class Filter { */ public var created: Node? + /** + Can either be a UNIX timestamp, or a dictionary with these key/values + + - gt: Return values where the created field is after this timestamp. + - gte: Return values where the created field is after or equal to this timestamp. + - lt: Return values where the created field is before this timestamp. + - lte: Return values where the created field is before or equal to this timestamp. + + Example: + availableOn = Node(node: ["UNIX_TIMESTAMP": "gte"]) + or + availableOn = Node(node: UNIX_TIMESTAMP) + */ + public var availableOn: Node? + + /** + A currency + */ + public var currency: StripeCurrency? + /** Only return charges for the customer specified by this customer ID. */ @@ -93,6 +113,21 @@ public final class Filter { } } + if let value = self.created { + if let value = value.object { + for (key, value) in value { + node["available_on[\(key)]"] = value + } + } else { + node["available_on"] = value + } + } + + if let value = self.currency + { + node["currency"] = value.rawValue.makeNode(in: nil) + } + if let value = self.customerId { node["customer"] = value } diff --git a/Sources/API/Helpers/Endpoints.swift b/Sources/API/Helpers/Endpoints.swift index c0fce5c..f1c9e53 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-04-06" + StripeHeader.Version: "2017-05-25" ] internal struct StripeHeader { @@ -76,6 +76,14 @@ internal enum API { case refunds case refund(String) + /** + COUPONS + A coupon contains information about a percent-off or amount-off discount you might want to + apply to a customer. Coupons may be applied to invoices or orders. + */ + case coupons + case coupon(String) + var endpoint: String { switch self { case .balance: return APIBase + APIVersion + "balance" @@ -95,6 +103,9 @@ internal enum API { case .refunds: return APIBase + APIVersion + "refunds" case .refund(let id): return APIBase + APIVersion + "refunds/\(id)" + + case .coupons: return APIBase + APIVersion + "coupons" + case .coupon(let id): return APIBase + APIVersion + "coupons/\(id)" } } diff --git a/Sources/API/Routes/BalanceRoutes.swift b/Sources/API/Routes/BalanceRoutes.swift index d0b0b3d..b793d88 100644 --- a/Sources/API/Routes/BalanceRoutes.swift +++ b/Sources/API/Routes/BalanceRoutes.swift @@ -43,11 +43,11 @@ public final class BalanceRoutes { Returns a list of transactions that have contributed to the Stripe account balance (e.g., charges, transfers, and so forth). The transactions are returned in sorted order, with the most recent transactions appearing first. - - parameter filter: A Filter item to ass query parameters when fetching results + - parameter filter: A Filter item to pass query parameters when fetching results - returns: A StripeRequest<> item which you can then use to convert to the corresponding node */ - public func history(forFilter filter: Filter?=nil) throws -> StripeRequest { + public func history(forFilter filter: StripeFilter?=nil) throws -> StripeRequest { var query = [String : NodeRepresentable]() if let data = try filter?.createQuery() { query = data diff --git a/Sources/API/Routes/ChargeRoutes.swift b/Sources/API/Routes/ChargeRoutes.swift index a198eee..1c4f70a 100644 --- a/Sources/API/Routes/ChargeRoutes.swift +++ b/Sources/API/Routes/ChargeRoutes.swift @@ -216,11 +216,11 @@ public final class ChargeRoutes { Returns a list of charges you’ve previously created. The charges are returned in sorted order, with the most recent charges appearing first. - - parameter filter: A Filter item to ass query parameters when fetching results + - parameter filter: A Filter item to pass query parameters when fetching results - returns: A StripeRequest<> item which you can then use to convert to the corresponding node */ - public func listAll(filter: Filter?=nil) throws -> StripeRequest { + public func listAll(filter: StripeFilter?=nil) throws -> StripeRequest { var query = [String : NodeRepresentable]() if let data = try filter?.createQuery() { query = data diff --git a/Sources/API/Routes/CouponRoutes.swift b/Sources/API/Routes/CouponRoutes.swift new file mode 100644 index 0000000..4493d6a --- /dev/null +++ b/Sources/API/Routes/CouponRoutes.swift @@ -0,0 +1,208 @@ +// +// CouponRoutes.swift +// Stripe +// +// Created by Andrew Edwards on 5/28/17. +// +// + +import Foundation +import Node +import HTTP +import Models +import Helpers +import Errors + +public final class CouponRoutes { + let client: StripeClient + + init(client: StripeClient) { + self.client = client + } + + /** + Create a coupon + Creates a new coupon object. + + - parameter id: Unique string of your choice that will be used to identify this coupon when applying it to a customer. + This is often a specific code you’ll give to your customer to use when signing up (e.g. FALL25OFF). If + you don’t want to specify a particular code, you can leave the ID blank and Stripe will generate a + random code for you. + + - parameter duration: Specifies how long the discount will be in effect. + + - parameter amountOff: Amount to subtract from an invoice total (required if percent_off is not passed). + + - parameter currency: The currency in which the charge will be under. (required if amount_off passed). + + - parameter durationInMonths: Required only if duration is `repeating`, in which case it must be a positive + integer that specifies the number of months the discount will be in effect. + + - parameter maxRedemptions: A positive integer specifying the number of times the coupon can be redeemed before it’s + no longer valid. + + - parameter percentOff: A positive integer between 1 and 100 that represents the discount the coupon will apply + (required if amount_off is not passed). + + - parameter redeemBy: Unix timestamp specifying the last time at which the coupon can be redeemed. + + - parameter metaData: A set of key/value pairs that you can attach to a coupon object. + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node. + */ + + public func create(id: String?, duration: StripeDuration, amountOff: Int?, currency: StripeCurrency?, durationInMonths: Int?, maxRedemptions: Int?, percentOff: Int?, redeemBy: Date?, metadata: [String: String]?) throws -> StripeRequest { + var body = Node([:]) + + body["duration"] = Node(duration.rawValue) + + if let amountOff = amountOff { + body["amount_off"] = Node(amountOff) + } + + if let currency = currency { + body["currency"] = Node(currency.rawValue) + } + + if let durationInMonths = durationInMonths { + body["duration_in_months"] = Node(durationInMonths) + } + + if let maxRedemptions = maxRedemptions { + body["max_redemptions"] = Node(maxRedemptions) + } + + if let percentOff = percentOff { + body["percent_off"] = Node(percentOff) + } + + if let redeemBy = redeemBy { + body["redeem_by"] = Node(Int(redeemBy.timeIntervalSince1970)) + } + + if let metadata = metadata { + for (key, value) in metadata { + body["metadata[\(key)]"] = try Node(node: value) + } + } + + return try StripeRequest(client: self.client, method: .post, route: .coupons, query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + } + + /** + Create a coupon + Creates a new coupon object. + + - parameter coupon: A coupon object created with appropriate values set. Any unset parameters (nil) + will unset the value on stripe. + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node. + */ + + public func create(coupon: Coupon) throws -> StripeRequest { + var body = Node([:]) + + body["duration"] = Node(coupon.duration.rawValue) + + if let amountOff = coupon.amountOff { + body["amount_off"] = Node(amountOff) + } + + if let currency = coupon.currency { + body["currency"] = Node(currency.rawValue) + } + + if let durationInMonths = coupon.durationInMonths { + body["duration_in_months"] = Node(durationInMonths) + } + + if let maxRedemptions = coupon.maxRedemptions { + body["max_redemptions"] = Node(maxRedemptions) + } + + if let percentOff = coupon.percentOff { + body["percent_off"] = Node(percentOff) + } + + if let redeemBy = coupon.redeemBy { + body["redeem_by"] = Node(redeemBy.timeIntervalSince1970) + } + + if let metadata = coupon.metadata?.object { + for (key, value) in metadata { + body["metadata[\(key)]"] = value + } + } + + return try StripeRequest(client: self.client, method: .post, route: .coupons, query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + } + + /** + Retrieve a coupon + Retrieves the coupon with the given ID. + + - parameter couponId: The ID of the desired coupon. + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + + public func retrieve(coupon couponId: String) throws -> StripeRequest { + return try StripeRequest(client: self.client, method: .get, route: .coupon(couponId), query: [:], body: nil, headers: nil) + } + + /** + Update a coupon + Updates the metadata of a coupon. Other coupon details (currency, duration, amount_off) are, by design, not editable. + + - parameter couponId: The identifier of the coupon to be updated. + + - parameter metaData: A set of key/value pairs that you can attach to a coupon object. It can be useful for storing additional + information about the coupon in a structured format. + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + + public func update(metadata: [String:String], forCouponId couponId: String) throws -> StripeRequest { + var body = Node([:]) + + for (key, value) in metadata + { + body["metadata[\(key)]"] = try Node(node: "\(String(describing: value))") + } + + return try StripeRequest(client: self.client, method: .post, route: .coupon(couponId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + } + + /** + Delete a coupon + Deletes the coupon with the given ID. + + - parameter couponId: The identifier of the coupon to be deleted. + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + + public func delete(coupon couponId: String) throws -> StripeRequest { + return try StripeRequest(client: self.client, method: .delete, route: .coupon(couponId), query: [:], body: nil, headers: nil) + } + + /** + List all coupons + Returns a list of your coupons. The coupons are returned sorted by creation date, with the + most recent coupons appearing first. + + - parameter filter: A Filter item to pass query parameters when fetching results. + + - returns: A StripeRequest<> item which you can then use to convert to the corresponding node + */ + + 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: .coupons, query: query, body: nil, headers: nil) + } +} diff --git a/Sources/API/Routes/CustomerRoutes.swift b/Sources/API/Routes/CustomerRoutes.swift index 3b7485d..450a5b6 100644 --- a/Sources/API/Routes/CustomerRoutes.swift +++ b/Sources/API/Routes/CustomerRoutes.swift @@ -255,11 +255,11 @@ public final class CustomerRoutes { Returns a list of your customers. The customers are returned sorted by creation date, with the most recent customers appearing first. - - parameter filter: A Filter item to ass query parameters when fetching results + - parameter filter: A Filter item to pass query parameters when fetching results - returns: A StripeRequest<> item which you can then use to convert to the corresponding node */ - public func listAll(filter: Filter?=nil) throws -> StripeRequest { + public func listAll(filter: StripeFilter?=nil) throws -> StripeRequest { var query = [String : NodeRepresentable]() if let data = try filter?.createQuery() { query = data diff --git a/Sources/API/Routes/RefundRoutes.swift b/Sources/API/Routes/RefundRoutes.swift index 41df57b..a31f196 100644 --- a/Sources/API/Routes/RefundRoutes.swift +++ b/Sources/API/Routes/RefundRoutes.swift @@ -133,11 +133,11 @@ public final class RefundRoutes { recent refunds appearing first. For convenience, the 10 most recent refunds are always available by default on the charge object. - - parameter filter: A Filter item to ass query parameters when fetching results + - parameter filter: A Filter item to pass query parameters when fetching results - returns: A StripeRequest<> item which you can then use to convert to the corresponding node */ - public func listAll(by charge: String? = nil, filter: Filter?=nil) throws -> StripeRequest { + public func listAll(by charge: String? = nil, filter: StripeFilter?=nil) throws -> StripeRequest { var query = [String : NodeRepresentable]() if let data = try filter?.createQuery() { query = data diff --git a/Sources/API/StripeClient.swift b/Sources/API/StripeClient.swift index cf7f57a..2b51552 100644 --- a/Sources/API/StripeClient.swift +++ b/Sources/API/StripeClient.swift @@ -17,6 +17,7 @@ public class StripeClient { public private(set) var customer: CustomerRoutes! public private(set) var tokens: TokenRoutes! public private(set) var refunds: RefundRoutes! + public private(set) var coupons: CouponRoutes! public init(apiKey: String) throws { self.apiKey = apiKey @@ -28,6 +29,7 @@ public class StripeClient { self.customer = CustomerRoutes(client: self) self.tokens = TokenRoutes(client: self) self.refunds = RefundRoutes(client: self) + self.coupons = CouponRoutes(client: self) } } diff --git a/Sources/API/StripeRequest.swift b/Sources/API/StripeRequest.swift index a195095..675f33a 100644 --- a/Sources/API/StripeRequest.swift +++ b/Sources/API/StripeRequest.swift @@ -85,5 +85,4 @@ public class StripeRequest { guard let value = self.response.json else { throw StripeError.serializationError } return value } - } diff --git a/Sources/Helpers/ActionType.swift b/Sources/Helpers/ActionType.swift index f32d6a3..4546fcc 100644 --- a/Sources/Helpers/ActionType.swift +++ b/Sources/Helpers/ActionType.swift @@ -21,4 +21,6 @@ public enum ActionType: String { case ideal = "ideal" case sofort = "sofort" case bancontact = "bancontact" + + case none = "none" } diff --git a/Sources/Helpers/StripeDuration.swift b/Sources/Helpers/StripeDuration.swift new file mode 100644 index 0000000..5a604c2 --- /dev/null +++ b/Sources/Helpers/StripeDuration.swift @@ -0,0 +1,18 @@ +// +// StripeDuration.swift +// Stripe +// +// Created by Andrew Edwards on 5/28/17. +// +// + +public enum StripeDuration: String { + case forever = "forever" + case once = "once" + case repeating = "repeating" + case never = "never" + + var description: String { + return self.rawValue.uppercased() + } +} diff --git a/Sources/Models/Balance/BalanceTransactionItem.swift b/Sources/Models/Balance/BalanceTransactionItem.swift index 7f443ba..1eb7669 100644 --- a/Sources/Models/Balance/BalanceTransactionItem.swift +++ b/Sources/Models/Balance/BalanceTransactionItem.swift @@ -34,8 +34,8 @@ public final class BalanceTransactionItem: StripeModelProtocol { self.fees = try node.get("fee_details") self.net = try node.get("net") self.source = try node.get("source") - self.status = try StripeStatus(rawValue: node.get("status"))! - self.type = try ActionType(rawValue: node.get("type"))! + self.status = try StripeStatus(rawValue: node.get("status")) ?? StripeStatus.failed + self.type = try ActionType(rawValue: node.get("type")) ?? ActionType.none } public func makeNode(in context: Context?) throws -> Node { diff --git a/Sources/Models/Charges/Charge.swift b/Sources/Models/Charges/Charge.swift index 3897e79..de1d17f 100644 --- a/Sources/Models/Charges/Charge.swift +++ b/Sources/Models/Charges/Charge.swift @@ -22,7 +22,7 @@ public final class Charge: StripeModelProtocol { public let amountRefunded: Int public let application: String? public let applicationFee: Int? - public let balanceTransactionId: String + public let balanceTransactionId: String? public let isCaptured: Bool public let created: Date public let customerId: String? diff --git a/Sources/Models/CouponList.swift b/Sources/Models/CouponList.swift new file mode 100644 index 0000000..4ac021a --- /dev/null +++ b/Sources/Models/CouponList.swift @@ -0,0 +1,35 @@ +// +// CouponList.swift +// Stripe +// +// Created by Andrew Edwards on 5/28/17. +// +// + +import Foundation +import Vapor +import Helpers + +public final class CouponList: StripeModelProtocol { + public let object: String + public let url: String + public let hasMore: Bool + public let items: [Coupon]? + + 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/Coupons/Coupon.swift b/Sources/Models/Coupons/Coupon.swift new file mode 100644 index 0000000..4d931af --- /dev/null +++ b/Sources/Models/Coupons/Coupon.swift @@ -0,0 +1,88 @@ +// +// Coupon.swift +// Stripe +// +// Created by Andrew Edwards on 5/28/17. +// +// + +import Foundation +import Vapor +import Helpers + +/** + Coupon Model + https://stripe.com/docs/api/curl#coupon_object + */ +public final class Coupon: StripeModelProtocol { + public var id: String? + public var amountOff: Int? + public var currency: StripeCurrency? + public var duration: StripeDuration = .never + public var durationInMonths: Int? + public var maxRedemptions: Int? + public var percentOff: Int? + public var redeemBy: Date? + public private(set) var object: String? + public private(set) var created: Date? + public private(set) var timesRedeemed: Int? + public private(set) var isValid: Bool? + public private(set) var isLive: Bool? + + /** + Only metadata is mutable/updatable. + https://stripe.com/docs/api/curl#update_coupon + */ + public var metadata: Node? + + + required public init(duration: StripeDuration) { + self.duration = duration + } + + public init(node: Node) throws { + self.id = try node.get("id") + self.amountOff = try node.get("amount_off") + self.created = try node.get("created") + + if let currency = node["currency"]?.string + { + self.currency = StripeCurrency(rawValue: currency) + } + + if let duration = node["duration"]?.string + { + self.duration = StripeDuration(rawValue: duration) ?? StripeDuration.never + } + self.object = try node.get("object") + self.durationInMonths = try node.get("duration_in_months") + self.isLive = try node.get("livemode") + self.maxRedemptions = try node.get("max_redemptions") + self.metadata = try node.get("metadata") + self.percentOff = try node.get("percent_off") + self.redeemBy = try node.get("redeem_by") + self.timesRedeemed = try node.get("times_redeemed") + self.isValid = try node.get("valid") + } + + public func makeNode(in context: Context?) throws -> Node { + let object: [String: Any?] = [ + "id": self.id, + "object": self.object, + "amount_off": self.amountOff, + "created": self.created, + "currency": self.currency, + "duration": self.duration, + "duration_in_months": self.durationInMonths, + "livemode": self.isLive, + "max_redemptions": self.maxRedemptions, + "metadata": self.metadata, + "percent_off": self.percentOff, + "redeem_by": self.redeemBy, + "times_redeemed": self.timesRedeemed, + "valid": self.isValid + ] + + return try Node(node: object) + } +} diff --git a/Sources/Models/Customer/CustomerList.swift b/Sources/Models/Customer/CustomerList.swift index a2a0f97..159b7a0 100644 --- a/Sources/Models/Customer/CustomerList.swift +++ b/Sources/Models/Customer/CustomerList.swift @@ -13,11 +13,13 @@ import Helpers public final class CustomerList: StripeModelProtocol { public let object: String + public let url: String public let hasMore: Bool public let items: [Customer]? 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") } @@ -25,10 +27,10 @@ public final class CustomerList: StripeModelProtocol { 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/Tokens/Token.swift b/Sources/Models/Tokens/Token.swift index b97f84a..e6e3fcd 100644 --- a/Sources/Models/Tokens/Token.swift +++ b/Sources/Models/Tokens/Token.swift @@ -8,7 +8,7 @@ import Vapor -/* +/** Token Model https://stripe.com/docs/api/curl#token_object */ diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 6186ec2..0e4ec6a 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1,12 +1,81 @@ +// Generated using Sourcery 0.6.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT + import XCTest @testable import StripeTests +extension BalanceTests { +static var allTests = [ + ("testBalance", testBalance), + ("testBalanceTransactionItem", testBalanceTransactionItem), + ("testBalanceHistory", testBalanceHistory), + ("testFilterBalanceHistory", testFilterBalanceHistory), +] +} + +extension ChargeTests { +static var allTests = [ + ("testCharge", testCharge), + ("testRetrieveCharge", testRetrieveCharge), + ("testListAllCharges", testListAllCharges), + ("testFilterAllCharges", testFilterAllCharges), + ("testChargeUpdate", testChargeUpdate), + ("testChargeCapture", testChargeCapture), +] +} + +extension CouponTests { +static var allTests = [ + ("testCreateCoupon", testCreateCoupon), + ("testRetrieveCoupon", testRetrieveCoupon), + ("testUpdateCoupon", testUpdateCoupon), + ("testDeleteCoupon", testDeleteCoupon), + ("testListAllCoupons", testListAllCoupons), + ("testFilterCoupons", testFilterCoupons), +] +} + +extension CustomerTests { +static var allTests = [ + ("testCreateCustomer", testCreateCustomer), + ("testRetrieveCustomer", testRetrieveCustomer), + ("testUpdateCustomer", testUpdateCustomer), + ("testDeleteCustomer", testDeleteCustomer), + ("testRetrieveAllCustomers", testRetrieveAllCustomers), + ("testFilterCustomers", testFilterCustomers), +] +} + +extension ProviderTests { +static var allTests = [ + ("testProvider", testProvider), +] +} + +extension RefundTests { +static var allTests = [ + ("testRefunding", testRefunding), + ("testUpdatingRefund", testUpdatingRefund), + ("testRetrievingRefund", testRetrievingRefund), + ("testListingAllRefunds", testListingAllRefunds), +] +} + +extension TokenTests { +static var allTests = [ + ("testTokenCreation", testTokenCreation), + ("testTokenRetrieval", testTokenRetrieval), + ("testBankAccountCreation", testBankAccountCreation), +] +} + + XCTMain([ - testCase(ProviderTests.allTests), - - testCase(BalanceTests.allTests), - testCase(ChargeTests.allTests), - testCase(CustomerTests.allTests), - testCase(TokenTests.allTests), - testCase(RefundTests.allTests), + testCase(BalanceTests.allTests), + testCase(ChargeTests.allTests), + testCase(CouponTests.allTests), + testCase(CustomerTests.allTests), + testCase(ProviderTests.allTests), + testCase(RefundTests.allTests), + testCase(TokenTests.allTests), ]) diff --git a/Tests/StripeTests/BalanceTests.swift b/Tests/StripeTests/BalanceTests.swift index 809bbe4..6c15088 100644 --- a/Tests/StripeTests/BalanceTests.swift +++ b/Tests/StripeTests/BalanceTests.swift @@ -13,23 +13,42 @@ import XCTest @testable import API class BalanceTests: XCTestCase { + + var drop: Droplet? + var transactionId: String = "" - static var allTests = [ - ("testBalance", testBalance), - ("testBalanceTransactionItem", testBalanceTransactionItem), - ("testBalanceHistory", testBalanceHistory), - ("testFilterBalanceHistory", testFilterBalanceHistory), - ] + override func setUp() { + do + { + drop = try self.makeDroplet() + + let paymentToken = try drop?.stripe?.tokens.createCard(withCardNumber: "4242 4242 4242 4242", + expirationMonth: 10, + expirationYear: 2018, + cvc: 123, + name: "Test Card") + .serializedResponse().id ?? "" + + transactionId = try drop?.stripe?.charge.create(amount: 10_00, + in: .usd, + for: .source(paymentToken), + description: "Vapor Stripe: Test Description") + .serializedResponse() + .balanceTransactionId ?? "" + } + catch + { + fatalError("Setup failed: \(error.localizedDescription)") + } + } func testBalance() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.balance.retrieveBalance().serializedResponse() + let object = try drop?.stripe?.balance.retrieveBalance().serializedResponse() XCTAssertNotNil(object) } func testBalanceTransactionItem() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.balance.retrieveBalance(forTransaction: TestTransactionID).serializedResponse() + let object = try drop?.stripe?.balance.retrieveBalance(forTransaction: transactionId).serializedResponse() XCTAssertNotNil(object) } @@ -41,10 +60,9 @@ class BalanceTests: XCTestCase { func testFilterBalanceHistory() throws { let drop = try self.makeDroplet() - let filter = Filter() + let filter = StripeFilter() filter.limit = 1 let object = try drop.stripe?.balance.history(forFilter: filter).serializedResponse() XCTAssertEqual(object?.items.count, 1) } - } diff --git a/Tests/StripeTests/ChargeTests.swift b/Tests/StripeTests/ChargeTests.swift index f526081..192cdb7 100644 --- a/Tests/StripeTests/ChargeTests.swift +++ b/Tests/StripeTests/ChargeTests.swift @@ -14,44 +14,68 @@ import XCTest @testable import API class ChargeTests: XCTestCase { - - static var allTests = [ - ("testCharge", testCharge), - ("testRetrieveCharge", testRetrieveCharge), - ("testListAllCharges", testListAllCharges), - ("testFilterAllCharges", testFilterAllCharges), - ("testChargeUpdate", testChargeUpdate), - ("testChargeCapture", testChargeCapture) - ] + + var drop: Droplet? + var chargeId: String = "" + + override func setUp() { + do + { + drop = try self.makeDroplet() + + let tokenId = try drop?.stripe?.tokens.createCard(withCardNumber: "4242 4242 4242 4242", + expirationMonth: 10, + expirationYear: 2018, + cvc: 123, + name: "Test Card") + .serializedResponse().id ?? "" + + chargeId = try drop?.stripe?.charge.create(amount: 10_00, + in: .usd, + for: .source(tokenId), + description: "Vapor Stripe: Test Description") + .serializedResponse().id ?? "" + } + catch + { + fatalError("Setup failed: \(error.localizedDescription)") + } + } func testCharge() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.charge.create(amount: 10_000, in: .usd, for: .source(TestChargeSourceTokenID), description: "Vapor Stripe: Test Description").serializedResponse() + let paymentToken = try drop?.stripe?.tokens.createCard(withCardNumber: "4242 4242 4242 4242", + expirationMonth: 10, + expirationYear: 2018, + cvc: 123, + name: "Test Card") + .serializedResponse() + + let object = try drop?.stripe?.charge.create(amount: 10_00, + in: .usd, + for: .source(paymentToken?.id ?? ""), + description: "Vapor Stripe: Test Description") + .serializedResponse() XCTAssertNotNil(object) } func testRetrieveCharge() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.charge.retrieve(charge: TestChargeID).serializedResponse() + let object = try drop?.stripe?.charge.retrieve(charge: chargeId).serializedResponse() XCTAssertNotNil(object) } func testListAllCharges() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.charge.listAll().serializedResponse() + let object = try drop?.stripe?.charge.listAll().serializedResponse() XCTAssertNotNil(object) } func testFilterAllCharges() throws { - let drop = try self.makeDroplet() - let filter = Filter() + let filter = StripeFilter() filter.limit = 1 - let object = try drop.stripe?.charge.listAll(filter: filter).serializedResponse() + let object = try drop?.stripe?.charge.listAll(filter: filter).serializedResponse() XCTAssertEqual(object?.items.count, 1) } func testChargeUpdate() throws { - let drop = try self.makeDroplet() let shippingAddress = ShippingAddress() shippingAddress.addressLine1 = "123 Test St" shippingAddress.addressLine2 = "456 Apt" @@ -68,14 +92,30 @@ class ChargeTests: XCTestCase { shippingLabel.address = shippingAddress let metadata = ["test": "metadata"] - let object = try drop.stripe?.charge.update(charge: TestChargeID, metadata: metadata, receiptEmail: "test-email@test.com", shippingLabel: shippingLabel).serializedResponse() + let object = try drop?.stripe?.charge.update(charge: chargeId, + metadata: metadata, + receiptEmail: "test-email@test.com", + shippingLabel: shippingLabel) + .serializedResponse() XCTAssertNotNil(object) } func testChargeCapture() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.charge.capture(charge: TestChargeID).serializedResponse() + let paymentToken = try drop?.stripe?.tokens.createCard(withCardNumber: "4242 4242 4242 4242", + expirationMonth: 10, + expirationYear: 2018, + cvc: 123, + name: "Test Card") + .serializedResponse().id ?? "" + + let uncapturedCharge = try drop?.stripe?.charge.create(amount: 10_00, + in: .usd, + for: .source(paymentToken), + description: "Vapor Stripe: test Description", + capture: false) + .serializedResponse().id ?? "" + + let object = try drop?.stripe?.charge.capture(charge: uncapturedCharge).serializedResponse() XCTAssertNotNil(object) } } - diff --git a/Tests/StripeTests/CouponTests.swift b/Tests/StripeTests/CouponTests.swift new file mode 100644 index 0000000..b7dc0b6 --- /dev/null +++ b/Tests/StripeTests/CouponTests.swift @@ -0,0 +1,111 @@ +// +// CouponTests.swift +// Stripe +// +// Created by Andrew Edwards on 5/28/17. +// +// + +import XCTest + +@testable import Stripe +@testable import Vapor +@testable import Helpers +@testable import API + +class CouponTests: XCTestCase +{ + var drop: Droplet? + var couponId: String = "" + + override func setUp() { + do + { + drop = try self.makeDroplet() + + couponId = try drop?.stripe?.coupons.create(id: nil, + duration: .once, + amountOff: 5, + currency: .usd, + durationInMonths: nil, + maxRedemptions: 5, + percentOff: nil, + redeemBy: Date().addingTimeInterval(3000), + metadata: ["meta":"data"]).serializedResponse().id ?? "" + } + catch + { + fatalError("Setup failed: \(error.localizedDescription)") + } + } + + func testCreateCoupon() throws { + let coupon = try drop?.stripe?.coupons.create(id: nil, + duration: .once, + amountOff: 5, + currency: .usd, + durationInMonths: nil, + maxRedemptions: 5, + percentOff: nil, + redeemBy: Date().addingTimeInterval(3000), + metadata: ["meta":"data"]).serializedResponse() + XCTAssertNotNil(coupon) + } + + func testRetrieveCoupon() throws { + let coupon = try drop?.stripe?.coupons.retrieve(coupon: couponId).serializedResponse() + + XCTAssertNotNil(coupon) + } + + func testUpdateCoupon() throws { + let metadata = ["hello":"world"] + let updatedCoupon = try drop?.stripe?.coupons.update(metadata: metadata, forCouponId: couponId).serializedResponse() + + XCTAssertNotNil(updatedCoupon) + + XCTAssert(updatedCoupon?.metadata?["hello"] == "world") + } + + func testDeleteCoupon() throws { + let deletedCoupon = try drop?.stripe?.coupons.delete(coupon: couponId).serializedResponse() + + XCTAssertNotNil(deletedCoupon) + + XCTAssertTrue(deletedCoupon?.deleted ?? false) + } + + func testListAllCoupons() throws { + let coupons = try drop?.stripe?.coupons.listAll().serializedResponse() + + XCTAssertNotNil(coupons) + + if let couponItems = coupons?.items + { + XCTAssertGreaterThanOrEqual(couponItems.count, 1) + } + else + { + XCTFail("Coupons are not present") + } + } + + func testFilterCoupons() throws { + let filter = StripeFilter() + + filter.limit = 1 + + let coupons = try drop?.stripe?.coupons.listAll(filter: filter).serializedResponse() + + XCTAssertNotNil(coupons) + + if let couponItems = coupons?.items + { + XCTAssertEqual(couponItems.count, 1) + } + else + { + XCTFail("Coupons are not present") + } + } +} diff --git a/Tests/StripeTests/CustomerTests.swift b/Tests/StripeTests/CustomerTests.swift index f2545f1..29d03a5 100644 --- a/Tests/StripeTests/CustomerTests.swift +++ b/Tests/StripeTests/CustomerTests.swift @@ -14,58 +14,64 @@ import XCTest @testable import API class CustomerTests: XCTestCase { + + var drop: Droplet? + var customerId: String = "" - static var allTests = [ - ("testCreateCustomer ", testCreateCustomer), - ("testRetrieveCustomer", testRetrieveCustomer), - ("testUpdateCustomer", testUpdateCustomer), - ("testDeleteCustomer", testDeleteCustomer), - ("testRetrieveAllCustomers", testRetrieveAllCustomers), - ("testFilterCustomers", testFilterCustomers) - ] + override func setUp() { + do + { + drop = try self.makeDroplet() + + let customer = Customer() + + customer.email = "test@stripetest.com" + + customer.description = "This is a test customer" + + customerId = try drop?.stripe?.customer.create(customer: customer).serializedResponse().id ?? "" + } + catch + { + fatalError("Setup failed: \(error.localizedDescription)") + } + } func testCreateCustomer() throws { - let drop = try self.makeDroplet() let customer = Customer() customer.email = "test@stripetest.com" customer.description = "This is a test customer" - let object = try drop.stripe?.customer.create(customer: customer).serializedResponse() + let object = try drop?.stripe?.customer.create(customer: customer).serializedResponse() XCTAssertNotNil(object) } func testRetrieveCustomer() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.customer.retrieve(customer: TestCustomerID).serializedResponse() + let object = try drop?.stripe?.customer.retrieve(customer: customerId).serializedResponse() XCTAssertNotNil(object) } func testUpdateCustomer() throws { - let drop = try self.makeDroplet() let customer = Customer() - customer.email = "test@stripetest.com" + customer.email = "tester@stripetest.com" customer.description = "This is a test customer updated" - let object = try drop.stripe?.customer.update(customer: customer, forCustomerId: TestCustomerID).serializedResponse() + let object = try drop?.stripe?.customer.update(customer: customer, forCustomerId: customerId).serializedResponse() XCTAssertNotNil(object) } func testDeleteCustomer() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.customer.delete(customer: TestCustomerID).serializedResponse() + let object = try drop?.stripe?.customer.delete(customer: customerId).serializedResponse() XCTAssertEqual(object?.deleted, true) } func testRetrieveAllCustomers() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.customer.listAll().serializedResponse() + let object = try drop?.stripe?.customer.listAll().serializedResponse() XCTAssertGreaterThanOrEqual(object!.items!.count, 1) } func testFilterCustomers() throws { - let drop = try self.makeDroplet() - let filter = Filter() + let filter = StripeFilter() filter.limit = 1 - let object = try drop.stripe?.customer.listAll(filter: filter).serializedResponse() + let object = try drop?.stripe?.customer.listAll(filter: filter).serializedResponse() XCTAssertEqual(object?.items?.count, 1) } - } diff --git a/Tests/StripeTests/ProviderTests.swift b/Tests/StripeTests/ProviderTests.swift index 10cf04f..9132b5c 100644 --- a/Tests/StripeTests/ProviderTests.swift +++ b/Tests/StripeTests/ProviderTests.swift @@ -4,10 +4,6 @@ import XCTest class ProviderTests: XCTestCase { - static var allTests = [ - ("testProvider", testProvider), - ] - func testProvider() throws { let config = Config([ "stripe": [ diff --git a/Tests/StripeTests/RefundTests.swift b/Tests/StripeTests/RefundTests.swift index 76fff6e..38f8d1a 100644 --- a/Tests/StripeTests/RefundTests.swift +++ b/Tests/StripeTests/RefundTests.swift @@ -13,36 +13,63 @@ import XCTest @testable import API class RefundTests: XCTestCase { + + var drop: Droplet? + var refundId: String = "" - static var allTests = [ - ("testRefunding", testRefunding), - ("testUpdatingRefund", testUpdatingRefund), - ("testRetrievingRefund", testRetrievingRefund), - ("testListingAllRefunds", testListingAllRefunds) - ] + override func setUp() { + do + { + drop = try self.makeDroplet() + + let tokenId = try drop?.stripe?.tokens.createCard(withCardNumber: "4242 4242 4242 4242", + expirationMonth: 10, + expirationYear: 2018, + cvc: 123, + name: "Test Card").serializedResponse().id ?? "" + + let chargeId = try drop?.stripe?.charge.create(amount: 10_00, + in: .usd, + for: .source(tokenId), + description: "Vapor Stripe: Test Description").serializedResponse().id ?? "" + + refundId = try drop?.stripe?.refunds.refund(charge: chargeId).serializedResponse().id ?? "" + } + catch + { + fatalError("Setup failed: \(error.localizedDescription)") + } + } func testRefunding() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.refunds.refund(charge: TestChargeID).serializedResponse() + + let paymentToken = try drop?.stripe?.tokens.createCard(withCardNumber: "4242 4242 4242 4242", + expirationMonth: 10, + expirationYear: 2018, + cvc: 123, + name: "Test Card").serializedResponse().id ?? "" + + let charge = try drop?.stripe?.charge.create(amount: 10_00, + in: .usd, + for: .source(paymentToken), + description: "Vapor Stripe: Test Description").serializedResponse().id ?? "" + + let object = try drop?.stripe?.refunds.refund(charge: charge).serializedResponse() XCTAssertNotNil(object) } func testUpdatingRefund() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.refunds.update(refund: TestRefundID, metadata: ["test": "Test Updating a charge"]).serializedResponse() + let object = try drop?.stripe?.refunds.update(refund: refundId, metadata: ["test": "Test Updating a charge"]).serializedResponse() XCTAssertNotNil(object) } func testRetrievingRefund() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.refunds.retrieve(refund: TestRefundID).serializedResponse() + let object = try drop?.stripe?.refunds.retrieve(refund: refundId).serializedResponse() XCTAssertNotNil(object) } func testListingAllRefunds() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.refunds.listAll().serializedResponse() - XCTAssertGreaterThanOrEqual(object!.items!.count, 2) - } - + let object = try drop?.stripe?.refunds.listAll().serializedResponse() + XCTAssertGreaterThanOrEqual(object!.items!.count, 1) + } } diff --git a/Tests/StripeTests/Test+Droplet.swift b/Tests/StripeTests/Test+Droplet.swift index 2779845..e048a1b 100644 --- a/Tests/StripeTests/Test+Droplet.swift +++ b/Tests/StripeTests/Test+Droplet.swift @@ -11,29 +11,11 @@ import XCTest import Vapor import Stripe -// Used for fetching a specific Transaction in the history -let TestTransactionID = "txn_1A9KJiJm6xi4tXu1EkbD0ZEC" - -// Used for creating charges -let TestChargeSourceTokenID = "tok_1A9M61Jm6xi4tXu1sSQMnsxp" - -// Used for fetching a specific charge -let TestChargeID = "ch_1A9M5oJm6xi4tXu1iWO7wxMy" - -// Used for fetching a specific customer -let TestCustomerID = "cus_AVqRAm2Gv8EMyd" - -// Used for token testing -let TestTokenID = "tok_1AIiMpJm6xi4tXu1hoimk73q" - -// Used for refund testing -let TestRefundID = "re_1AJ4FFJm6xi4tXu1YFFB9N7P" - extension XCTestCase { func makeDroplet() throws -> Droplet { let config = Config([ "stripe": [ - "apiKey": "sk_test_Pb8SpX80t1XhJengpQ3oE67H" // Add your own API Key for tests + "apiKey": "" // Add your own API Key for tests ], ]) try config.addProvider(Stripe.Provider.self) diff --git a/Tests/StripeTests/TokenTests.swift b/Tests/StripeTests/TokenTests.swift index bb10602..aeca7dc 100644 --- a/Tests/StripeTests/TokenTests.swift +++ b/Tests/StripeTests/TokenTests.swift @@ -14,28 +14,48 @@ import XCTest class TokenTests: XCTestCase { - static var allTests = [ - ("testTokenCreation", testTokenCreation), - ("testTokenRetrieval", testTokenRetrieval), - ("testBankAccountCreation", testBankAccountCreation), - ] - - func testTokenCreation() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.tokens.createCard(withCardNumber: "4242 4242 4242 4242", expirationMonth: 10, expirationYear: 2018, cvc: 123, name: "Test Card").serializedResponse() + var drop: Droplet? + var tokenId: String = "" + + override func setUp() { + do + { + drop = try self.makeDroplet() + + tokenId = try drop?.stripe?.tokens.createCard(withCardNumber: "4242 4242 4242 4242", + expirationMonth: 10, + expirationYear: 2018, + cvc: 123, + name: "Test Card").serializedResponse().id ?? "" + } + catch + { + fatalError("Setup failed: \(error.localizedDescription)") + } + } + + func testTokenCreation() throws + { + let object = try drop?.stripe?.tokens.createCard(withCardNumber: "4242 4242 4242 4242", + expirationMonth: 10, + expirationYear: 2018, + cvc: 123, + name: "Test Card").serializedResponse() XCTAssertNotNil(object?.card) } func testTokenRetrieval() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.tokens.retrieve(TestTokenID).serializedResponse() + let object = try drop?.stripe?.tokens.retrieve(tokenId).serializedResponse() XCTAssertNotNil(object) } func testBankAccountCreation() throws { - let drop = try self.makeDroplet() - let object = try drop.stripe?.tokens.createBank(country: "US", currency: .usd, accountNumber: "000123456789", routingNumber: "110000000", accountHolderName: "Test Person", accountHolderType: "Individual").serializedResponse() + let object = try drop?.stripe?.tokens.createBank(country: "US", + currency: .usd, + accountNumber: "000123456789", + routingNumber: "110000000", + accountHolderName: "Test Person", + accountHolderType: "Individual").serializedResponse() XCTAssertNotNil(object?.bankAccount) } - }