diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..035a42c --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,25 @@ +version: 2 + +jobs: + macos: + macos: + xcode: "9.2" + steps: + - checkout + - run: swift build + - run: swift test + linux: + docker: + - image: norionomura/swift:swift-4.1-branch + steps: + - checkout + - run: apt-get update + - run: apt-get install -yq libssl-dev + - run: swift build + - run: swift test +workflows: + version: 2 + tests: + jobs: + - linux + # - macos diff --git a/Package.swift b/Package.swift index 4a40097..43e25c6 100644 --- a/Package.swift +++ b/Package.swift @@ -1,14 +1,16 @@ -// swift-tools-version:3.1 - +// swift-tools-version:4.0 import PackageDescription let package = Package( name: "Stripe", - targets: [ - Target(name: "Stripe"), + products: [ + .library(name: "Stripe", targets: ["Stripe"]) ], dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2), - .Package(url: "https://github.com/vapor/random.git", majorVersion: 1), + .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0-rc"), + ], + targets: [ + .target(name: "Stripe", dependencies: ["Vapor"]), + .testTarget(name: "StripeTests", dependencies: ["Vapor", "Stripe"]) ] ) diff --git a/Package@swift-4.swift b/Package@swift-4.swift deleted file mode 100644 index a4b5c5e..0000000 --- a/Package@swift-4.swift +++ /dev/null @@ -1,17 +0,0 @@ -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Stripe", - products: [ - .library(name: "Stripe", targets: ["Stripe"]), - ], - dependencies: [ - .package(url: "https://github.com/vapor/vapor.git", from: "2.0.0"), - .package(url: "https://github.com/vapor/random.git", from: "1.0.0"), - ], - targets: [ - .target(name: "Stripe", dependencies: ["Vapor", "Random"]), - .testTarget(name: "StripeTests", dependencies: ["Stripe"]), - ] -) diff --git a/README.md b/README.md index 5de12d4..79bee9a 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,102 @@ # Vapor Stripe Provider -![Swift](http://img.shields.io/badge/swift-3.1-brightgreen.svg) -![Vapor](http://img.shields.io/badge/vapor-2.0-brightgreen.svg) -![Build](https://img.shields.io/badge/build-passing-brightgreen.svg) +![Swift](http://img.shields.io/badge/swift-4.1-brightgreen.svg) +![Vapor](http://img.shields.io/badge/vapor-3.0-brightgreen.svg) +[![CircleCI](https://circleci.com/gh/vapor-community/stripe-provider/tree/beta.svg?style=svg)](https://circleci.com/gh/vapor-community/stripe-provider/tree/beta) [Stripe][stripe_home] is a payment platform that handles credit cards, bitcoin and ACH transfers. They have become one of the best platforms for handling payments for projects, services or products. -## Why Create this? -There wasn't a library for it that worked with Vapor, and I needed one for my project. -The Stripe API is huge, and therefor I only plan on implementing the things that deal with payments. If there is something you need outside of that scope, feel free to submit a Pull Request. - ## Getting Started In your `Package.swift` file, add a Package -For Swift 3 -~~~~swift -.Package(url: "https://github.com/vapor-community/stripe.git", Version(1,0,0)) -~~~~ + For Swift 4 ~~~~swift -.package(url: "https://github.com/vapor-community/stripe.git", .exact(Version(1,0,0))) -~~~~ - -You'll need a config file as well. Place a `stripe.json` file in your `Config` folder -~~~~json -{ - "apiKey": "YOUR_API_KEY" -} +.package(url: "https://github.com/vapor-community/stripe-provider.git", .branch("beta")) ~~~~ -Add the provider to your droplet +Register the config and the provider to your Application ~~~~swift -try drop.addProvider(Stripe.Provider.self) -~~~~ +let config = StripeConfig(apiKey: "sk_12345678") -And you are all set. Interacting with the API is quite easy. Everything is Node backed with a simple API. +services.register(config) -Making calls to the api is a simple one line -~~~~swift -let object = try drop.stripe?.balance.history().serializedResponse() -~~~~ -The object is returned response model, or model array. +try services.register(StripeProvider()) -## Testing +app = try Application(services: services) -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' +stripeClient = try app.make(StripeClient.self) ~~~~ -It will generate the following with your tests added: +And you are all set. Interacting with the API is quite easy and adopts the `Future` syntax used in Vapor 3. +Making calls to the api is straight forward. ~~~~swift -import XCTest -@testable import StripeTests -extension BalanceTests { -static var allTests = [ - ("testBalance", testBalance), - ... -] -} -. -. -XCTMain([ - testCase(BalanceTests.allTests), - ... -]) +let cardParams = ["exp_month": 1, + "exp_year": 2030, + "number": "4242424242424242", + "cvc": 123, + "object": "card"] + +let futureCharge = try stripeClient.charge.create(amount: 2500, currency: .usd, source: cardParams) + +futureCharge.do({ (charge) in + // do something with charge object... +}).catch({ (error) in + print(error) +}) ~~~~ +And you can always check the documentation to see the required paramaters for specific API calls. + +## Linux compatibility +Currently the project won't compile for linux. +You can track this issue [here](https://bugs.swift.org/browse/SR-7180) and [here](https://github.com/apple/swift-corelibs-foundation/pull/1347) + ## Whats Implemented -* [x] Balance Fetching + +### Core Resources +* [x] Balance * [x] Charges * [x] Customers -* [x] Coupons -* [x] Plans +* [x] Disputes +* [ ] Events +* [ ] File Uploads +* [ ] Payouts * [x] Refunds * [x] Tokens +--- +### Payment Methods +* [x] Bank Accounts +* [x] Cards * [x] Sources +--- +### Subscriptions +* [x] Coupons +* [x] Discounts +* [x] Invoices +* [x] Invoice Items +* [x] Plans * [x] Subscriptions -* [x] Connect account +* [x] Subscription items +--- +### Connect +* [x] Account +* [ ] Application Fee Refunds +* [ ] Application Fees +* [ ] Country Specs +* [x] External Accounts +* [ ] Transfers +* [ ] Transfer Reversals +--- +### Relay * [x] Orders * [x] Order Items * [x] Products -* [x] Disputes -* [x] Invoices -* [x] Invoice Items +* [x] Returns +* [x] SKUs * [x] Ephemeral Keys [stripe_home]: http://stripe.com "Stripe" [stripe_api]: https://stripe.com/docs/api "Stripe API Endpoints" -[sourcery]: https://github.com/krzysztofzablocki/Sourcery "Sourcery" ## License diff --git a/Sources/Stripe/API/Filter/StripeFilter.swift b/Sources/Stripe/API/Filter/StripeFilter.swift deleted file mode 100644 index 737c310..0000000 --- a/Sources/Stripe/API/Filter/StripeFilter.swift +++ /dev/null @@ -1,239 +0,0 @@ -// -// StripeFilter.swift -// Stripe -// -// Created by Anthony Castelli on 4/16/17. -// -// - -import Foundation -import Vapor -import Node -import HTTP - -public final class StripeFilter { - - public init() { } - - /** - 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: - created = Node(node: ["UNIX_TIMESTAMP": "gte"]) - or - created = Node(node: UNIX_TIMESTAMP) - */ - 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. - */ - public var customerId: Node? - - /** - A cursor for use in pagination. `ending_before` is an object ID that defines your place in the list. - For instance, if you make a list request and receive 100 objects, starting with `obj_bar`, your - subsequent call can include `ending_before=obj_bar` in order to fetch the previous page of the list. - - Example: endingBefore = "obj_bar" - */ - public var endingBefore: Node? - - /** - A limit on the number of objects to be returned. Limit can range between 1 and 100 items. - This field MUST be a possitive Integer or String. Default is 10 - */ - public var limit: Node? - - /** - A filter on the list based on the source of the charge. - */ - public var source: SourceType? - - /** - A cursor for use in pagination. `starting_after` is an object ID that defines your place in the list. - For instance, if you make a list request and receive 100 objects, ending with `obj_foo`, your - subsequent call can include `starting_after=obj_foo` in order to fetch the next page of the list. - - Example: startingAfter = "obj_foo" - */ - public var startingAfter: Node? - - /** - Only return charges for this transfer group. - */ - public var transferGroup: Node? - - /** - For automatic Stripe payouts only, only returns transactions that were payed out on the specified payout ID. - */ - public var payout: Node? - - /** - Only returns transactions of the given type. One of BalanceType - */ - public var balanceType: BalanceType? - - /** - The status of the subscriptions to retrieve. - */ - public var subscriptionStatus: StripeSubscriptionStatus? - - /** - The ID of the plan whose subscriptions will be retrieved. - */ - 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? - - /** - Only return SKUs with the given IDs. - */ - public var skuIds: Node? - - internal func createBody() throws -> Node { - var node = Node([:]) - if let value = self.created { - if let value = value.object { - for (key, value) in value { - node["created[\(key)]"] = value - } - } else { - node["created"] = value - } - } - - if let value = self.availableOn { - 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 - } - - if let value = self.endingBefore { - node["ending_before"] = value - } - - if let value = self.limit { - node["limit"] = value - } - - if let value = self.source { - node["source"] = value.rawValue.makeNode(in: nil) - } - - if let value = self.startingAfter { - node["starting_after"] = value - } - - if let value = self.transferGroup { - node["transfer_group"] = value - } - - if let value = self.payout { - node["payout"] = value - } - - if let value = self.balanceType { - node["type"] = value.rawValue.makeNode(in: nil) - } - - if let value = self.subscriptionStatus { - node["status"] = value.rawValue.makeNode(in: nil) - } - - if let value = self.plan { - 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 - } - - if let skuids = skuIds?.array { - node["id"] = Node(skuids) - } - - return node - } - - internal func createQuery() throws -> [String : NodeRepresentable]? { - return try self.createBody().object - } -} diff --git a/Sources/Stripe/API/Helpers/Endpoints.swift b/Sources/Stripe/API/Helpers/Endpoints.swift index afcf60b..384db83 100644 --- a/Sources/Stripe/API/Helpers/Endpoints.swift +++ b/Sources/Stripe/API/Helpers/Endpoints.swift @@ -7,23 +7,11 @@ // import Foundation -import HTTP internal let APIBase = "https://api.stripe.com/" internal let APIVersion = "v1/" -internal let DefaultHeaders = [ - HeaderKey.contentType: "application/x-www-form-urlencoded", - StripeHeader.Version: "2017-08-15" -] - -internal struct StripeHeader { - static let Version = HeaderKey("Stripe-Version") - static let Authorization = HeaderKey("Authorization") - static let Account = HeaderKey("Stripe-Account") -} - -internal enum API { +internal enum StripeAPIEndpoint { /** BALANCE @@ -55,6 +43,7 @@ internal enum API { case customers case customer(String) case customerSources(String) + case customerDetachSources(String,String) case customerDiscount(String) /** @@ -189,7 +178,6 @@ internal enum API { /** EPHEMERAL KEYS - */ case ephemeralKeys case ephemeralKey(String) @@ -207,6 +195,7 @@ internal enum API { case .customers: return APIBase + APIVersion + "customers" case .customer(let id): return APIBase + APIVersion + "customers/\(id)" case .customerSources(let id): return APIBase + APIVersion + "customers/\(id)/sources" + case .customerDetachSources(let id, let src): return APIBase + APIVersion + "customers/\(id)/sources\(src)" case .customerDiscount(let id): return APIBase + APIVersion + "customers/\(id)/discount" case .tokens: return APIBase + APIVersion + "tokens" diff --git a/Sources/Stripe/API/Routes/AccountRoutes.swift b/Sources/Stripe/API/Routes/AccountRoutes.swift index 2a45579..64bbcd8 100644 --- a/Sources/Stripe/API/Routes/AccountRoutes.swift +++ b/Sources/Stripe/API/Routes/AccountRoutes.swift @@ -6,387 +6,190 @@ // // -import Node -import HTTP +import Vapor -open class AccountRoutes { - let client: StripeClient +public protocol AccountRoutes { + associatedtype AC: ConnectAccount + associatedtype LE: LegalEntity + associatedtype TOS: TOSAcceptance + associatedtype DO: DeletedObject + associatedtype L: List + associatedtype CLL: ConnectLoginLink - init(client: StripeClient) { - self.client = client + func create(type: ConnectedAccountType, email: String?, country: String?, metadata: [String: String]?) throws -> Future + func retrieve(account: String) throws -> Future + func update(account: String, businessName: String?, businessPrimaryColor: String?, businessUrl: String?, debitNegativeBalances: Bool?, declineChargeOn: [String: Bool]?, defaultCurrency: StripeCurrency?, email: String?, externalAccount: Any?, legalEntity: LE?, metadata: [String: String]?, payoutSchedule: [String: String]?, payoutStatementDescriptor: String?, productDescription: String?, statementDescriptor: String?, supportEmail: String?, supportPhone: String?, supportUrl: String?, tosAcceptance: TOS?) throws -> Future + func delete(account: String) throws -> Future + func reject(account: String, for: AccountRejectReason) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future + func createLoginLink(for: String) throws -> Future +} + +public struct StripeConnectAccountRoutes: AccountRoutes { + private let request: StripeRequest + + init(request: StripeRequest) { + self.request = request } - /** - Create an account - With Connect, you can create Stripe accounts for your users. To do this, you'll first need to register your platform. - - - parameter type: Whether you'd like to create a Custom or Standard account. Custom accounts have extra parameters - available to them, and require that you, the platform, handle all communication with the account - holder. Standard accounts are normal Stripe accounts: Stripe will email the account holder to setup - a username and password, and handle all account management directly with them. Possible values are - custom and standard. - - parameter email: The email address of the account holder. For Standard accounts, Stripe will email your user with - instructions for how to set up their account. For Custom accounts, this is only to make the account - easier to identify to you: Stripe will never directly reach out to your users. - - parameter country: The country the account holder resides in or that the business is legally established in. For example, - if you are in the United States and the business you're creating an account for is legally represented - in Canada, you would use "CA" as the country for the account being created. This should be an ISO 3166-1 - alpha-2 country code. - - parameter metadata: A set of key/value pairs that you can attach to an account object. It can be useful for storing additional - information about the account in a structured format. You can unset individual keys if you POST an empty - value for that key. You can clear all keys if you POST an empty value for metadata. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func create(type: ConnectedAccountType, email: String? = nil, country: String? = nil, metadata: Node? = nil) throws -> StripeRequest { - var body = Node([:]) - - body["type"] = Node(type.rawValue) + /// Create an account + /// [Learn More →](https://stripe.com/docs/api/curl#create_account) + public func create(type: ConnectedAccountType, + email: String? = nil, + country: String? = nil, + metadata: [String: String]? = nil) throws -> Future { + var body: [String: String] = [:] + body["type"] = type.rawValue if let email = email { - body["email"] = Node(email) + body["email"] = email } if let country = country { - body["country"] = Node(country) + body["country"] = country } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - return try StripeRequest(client: self.client, method: .post, route: .account, query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .POST, path: StripeAPIEndpoint.account.endpoint, body: body.queryParameters) } - /** - Retrieve account details - Retrieves the details of the account. - - - parameter accountId: The identifier of the account to be retrieved. If none is provided, - will default to the account of the API key. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func retrieve(account accountId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .accounts(accountId), query: [:], body: nil, headers: nil) + /// Retrieve account details + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_account) + public func retrieve(account accountId: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.accounts(accountId).endpoint) } - /** - Update an account - Updates an account by setting the values of the parameters passed. Any parameters not provided will - be left unchanged. - You may only update Custom and Express accounts that you manage. To update your own account, - you can currently only do so via the dashboard. - - - parameter accountId: The account ID to update on to - - parameter bussinessName: The publicly sharable name for this account. - - parameter bussinessPrimaryColor: A CSS hex color value representing the primary branding color for this account. - - parameter businessUrl: The URL that best shows the service or product provided for this account. - - parameter debitNegativeBalances: A boolean for whether or not Stripe should try to reclaim negative balances - from the account holder’s bank account. - - parameter declineChargeOn: Account-level settings to automatically decline certain types of charges - regardless of the bank’s decision. - - parameter defaultCurrency: Three-letter ISO currency code representing the default currency for the account - - parameter email: Email address of the account holder. For Standard accounts, this is used to email - them asking them to claim their Stripe account. For Custom accounts, this is only - to make the account easier to identify to you: Stripe will not email the account holder. - - parameter externalAccount: A card or bank account to attach to the account. You can provide either a token, - like the ones returned by Stripe.js, or a dictionary as documented in the external_account - parameter for bank account creation. This will create a new external account object, make it - the new default external account for its currency, and delete the old default if one exists. - If you want to add additional external accounts instead of replacing the existing default for - this currency, use the bank account or card creation API. - - parameter legalEntity: Information about the account holder; varies by account country and account status. - - parameter payoutSchedule: Details on when this account will make funds from charges available, and when they will be - paid out to the account holder’s bank account. - - parameter payoutStatementDescriptor: The text that will appear on the account’s bank account statement for payouts. If not set, this - will default to your platform’s bank descriptor set on the Dashboard. This will be unset if you - POST an empty value. - - parameter productDescription: Internal-only description of the product being sold or service being provided by this account. - It’s used by Stripe for risk and underwriting purposes. - - parameter statementDescriptor: The text that will appear on credit card statements by default if a charge is being made - directly on the account. - - parameter supportEmail: A publicly shareable email address that can be reached for support for this account. - - parameter supportPhone: A publicly shareable phone number that can be reached for support for this account. - - parameter supportUrl: A publicly shareable URL that can be reached for support for this account. - - parameter tosAcceptance: Details on who accepted the Stripe terms of service, and when they accepted it. - - paramater metadata: A set of key/value pairs that you can attach to an account object. It can be useful for storing - additional information about the account in a structured format. You can unset individual keys - if you POST an empty value for that key. You can clear all keys if you POST an empty value for metadata. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func update(account accountId: String, businessName: String? = nil, businessPrimaryColor: String? = nil, businessUrl: String? = nil, debitNegativeBalances: Bool? = nil, declineChargeOn: Node? = nil, defaultCurrency: StripeCurrency? = nil, email: String? = nil, externalAccount: Node? = nil, legalEntity: Node? = nil, payoutSchedule: Node? = nil, payoutStatementDescriptor: String? = nil, productDescription: String? = nil, statementDescriptor: String? = nil, supportEmail: String? = nil, supportPhone: String? = nil, supportUrl: String? = nil, tosAcceptance: Node? = nil, metadata: Node? = nil) throws -> StripeRequest { - var body = Node([:]) + /// Update an account + /// [Learn More →](https://stripe.com/docs/api/curl#update_account) + public func update(account accountId: String, + businessName: String? = nil, + businessPrimaryColor: String? = nil, + businessUrl: String? = nil, + debitNegativeBalances: Bool? = nil, + declineChargeOn: [String : Bool]? = nil, + defaultCurrency: StripeCurrency? = nil, + email: String? = nil, + externalAccount: Any? = nil, + legalEntity: StripeConnectAccountLegalEntity? = nil, + metadata: [String : String]? = nil, + payoutSchedule: [String : String]? = nil, + payoutStatementDescriptor: String? = nil, + productDescription: String? = nil, + statementDescriptor: String? = nil, + supportEmail: String? = nil, + supportPhone: String? = nil, + supportUrl: String? = nil, + tosAcceptance: StripeTOSAcceptance? = nil) throws -> Future { + var body: [String: Any] = [:] if let businessname = businessName { - body["business_name"] = Node(businessname) + body["business_name"] = businessname } if let businesscolor = businessPrimaryColor { - body["business_primary_color"] = Node(businesscolor) + body["business_primary_color"] = businesscolor } if let businessurl = businessUrl { - body["business_url"] = Node(businessurl) + body["business_url"] = businessurl } if let debNegBal = debitNegativeBalances { - body["debit_negative_balances"] = Node(debNegBal) + body["debit_negative_balances"] = debNegBal } - if let declinechargeon = declineChargeOn?.object { - for (key, value) in declinechargeon { - body["decline_charge_on[\(key)]"] = value - } + if let declinechargeon = declineChargeOn { + declinechargeon.forEach { body["decline_charge_on[\($0)]"] = $1 } } if let currency = defaultCurrency { - body["default_currency"] = Node(currency.rawValue) + body["default_currency"] = currency.rawValue } if let email = email { - body["email"] = Node(email) + body["email"] = email } - if let externalaccount = externalAccount?.object { - for (key,value) in externalaccount { - body["external_account[\(key)]"] = value - } + if let externalBankAccount = externalAccount as? StripeExternalBankAccount { + try externalBankAccount.toEncodedDictionary().forEach { body["external_account[\($0)]"] = $1 } } - if let legalentity = legalEntity { - - if let directors = legalentity["additional_directors"]?.object { - - for (key,value) in directors { - body["legal_entity[additional_directors][\(key)]"] = value - } - } - - if let owners = legalentity["additional_owners"]?.array { - - for index in 0.. item which you can then use to convert to the corresponding node - */ - public func delete(account accountId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .delete, route: .accounts(accountId), query: [:], body: nil, headers: nil) + /// Delete an account + /// [Learn More →](https://stripe.com/docs/api/curl#delete_account) + public func delete(account accountId: String) throws -> Future { + return try request.send(method: .DELETE, path: StripeAPIEndpoint.accounts(accountId).endpoint) } - /** - Reject an account - With Connect, you may flag accounts as suspicious. - Test-mode Custom and Express accounts can be rejected at any time. Accounts created using live-mode keys may only - be rejected once all balances are zero. - - - parameter accountId: The identifier of the account to create a login link for. - - parameter reason: The reason for rejecting the account. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func reject(account accountId: String, for reason: AccountRejectReason) throws -> StripeRequest { - var body = Node([:]) - - body["reason"] = Node(reason.rawValue) - - return try StripeRequest(client: self.client, method: .post, route: .accountsReject(accountId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + /// Reject an account + /// [Learn More →](https://stripe.com/docs/api/curl#reject_account) + public func reject(account accountId: String, for rejectReason: AccountRejectReason) throws -> Future { + let body = ["reason": rejectReason.rawValue].queryParameters + return try request.send(method: .POST, path: StripeAPIEndpoint.accountsReject(accountId).endpoint, body: body) } - /** - List all connected accounts - Returns a list of accounts connected to your platform via Connect. If you’re not a platform, the list will be empty - - - 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: .account, query: query, body: nil, headers: nil) + /// List all connected accounts + /// [Learn More →](https://stripe.com/docs/api/curl#list_accounts) + public func listAll(filter: [String: Any]? = nil) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters + } + return try request.send(method: .GET, path: StripeAPIEndpoint.account.endpoint, query: queryParams) } - /** - Create a login link - Creates a single-use login link for an Express account to access their Stripe dashboard. - You may only create login links for Express accounts connected to your platform. - - - parameter accountId: The identifier of the account to create a login link for. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func createLoginLink(forAccount accountId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .post, route: .accountsLoginLink(accountId), query: [:], body: nil, headers: nil) + /// Create a login link + /// [Learn More →](https://stripe.com/docs/api/curl#create_login_link) + public func createLoginLink(for accountId: String) throws -> Future { + return try request.send(method: .POST, path: StripeAPIEndpoint.accountsLoginLink(accountId).endpoint) } } diff --git a/Sources/Stripe/API/Routes/BalanceRoutes.swift b/Sources/Stripe/API/Routes/BalanceRoutes.swift index 829de12..808849d 100644 --- a/Sources/Stripe/API/Routes/BalanceRoutes.swift +++ b/Sources/Stripe/API/Routes/BalanceRoutes.swift @@ -6,52 +6,44 @@ // // -import Node +import Vapor -open class BalanceRoutes { +public protocol BalanceRoutes { + associatedtype B: Balance + associatedtype BT: BalanceTransactionItem + associatedtype BHL: List - let client: StripeClient + func retrieve() throws -> Future + func retrieve(forTransaction: String) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future +} + +public struct StripeBalanceRoutes: BalanceRoutes { + private let request: StripeRequest - init(client: StripeClient) { - self.client = client + init(request: StripeRequest) { + self.request = request } - /** - Retrieve balance - Retrieves the current account balance, based on the authentication that was used to make the request. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func retrieveBalance() throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .balance, query: [:], body: nil, headers: nil) + /// Retrieve balance + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_balance) + public func retrieve() throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.balance.endpoint) } - /** - Retrieve a balance transaction - Retrieves the balance transaction with the given ID. - - - parameter transactionId: The ID of the desired balance transaction (as found on any API object that affects the balance, e.g. a charge or transfer). - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func retrieveBalance(forTransaction transactionId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .balanceHistoryTransaction(transactionId), query: [:], body: nil, headers: nil) + /// Retrieve a balance transaction + /// [Learn More →](https://stripe.com/docs/api/curl#balance_transaction_retrieve) + public func retrieve(forTransaction transactionId: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.balanceHistoryTransaction(transactionId).endpoint) } - /** - List all balance history - 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 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: StripeFilter? = nil) throws -> StripeRequest { - var query = [String : NodeRepresentable]() - if let data = try filter?.createQuery() { - query = data + /// List all balance history + /// [Learn More →](https://stripe.com/docs/api/curl#balance_history) + public func listAll(filter: [String: Any]? = nil) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters } - return try StripeRequest(client: self.client, method: .get, route: .balanceHistory, query: query, body: nil, headers: nil) + return try request.send(method: .GET, path: StripeAPIEndpoint.account.endpoint, query: queryParams) } } diff --git a/Sources/Stripe/API/Routes/ChargeRoutes.swift b/Sources/Stripe/API/Routes/ChargeRoutes.swift index ef58a79..8bbd434 100644 --- a/Sources/Stripe/API/Routes/ChargeRoutes.swift +++ b/Sources/Stripe/API/Routes/ChargeRoutes.swift @@ -6,89 +6,49 @@ // // -import Node -import HTTP +import Vapor -public enum ChargeType { - /** - A card or token id - */ - case source(String) +public protocol ChargeRoutes { + associatedtype CH: Charge + associatedtype SH: Shipping + associatedtype FD: FraudDetails + associatedtype CHL: List - /** - A customer id to charge - */ - case customer(String) + func create(amount: Int, currency: StripeCurrency, applicationFee: Int?, capture: Bool?, description: String?, destinationAccount: String?, destinationAmount: Int?, transferGroup: String?, onBehalfOf: String?, metadata: [String: String]?, receiptEmail: String?, shipping: SH?, customer: String?, source: Any?, statementDescriptor: String?) throws -> Future + func retrieve(charge: String) throws -> Future + func update(charge: String, customer: String?, description: String?, fraudDetails: FD?, metadata: [String: String]?, receiptEmail: String?, shipping: SH?, transferGroup: String?) throws -> Future + func capture(charge: String, amount: Int?, applicationFee: Int?, destinationAmount: Int?, receiptEmail: String?, statementDescriptor: String?) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future } -open class ChargeRoutes { +public struct StripeChargeRoutes: ChargeRoutes { + private let request: StripeRequest - let client: StripeClient - - init(client: StripeClient) { - self.client = client + init(request: StripeRequest) { + self.request = request } - /** - Create a Charge - To charge a credit card, you create a charge object. If your API key is in test mode, the supplied payment source - (e.g., card or Bitcoin receiver) won't actually be charged, though everything else will occur as if in live mode. - (Stripe assumes that the charge would have completed successfully). - - NOTE: Accounts and Fees are only applicable to connected accounts - - - parameter amount: The payment amount in cents - - - parameter currency: The currency in which the charge will be under - - - parameter fee: A fee to charge if you are using connected accounts (Must be in cents) - - - parameter account: The account id which the payment would be sent to. - - - parameter capture: Whether or not to immediately capture the charge. - - - parameter description: An optional description for charges. - - - parameter destinationAccountId: If specified, the charge will be attributed to the destination account for tax reporting, - and the funds from the charge will be transferred to the destination account. - - - parameter destinationAmount: The amount to transfer to the destination account without creating an Application Fee. - - - parameter transferGroup: A string that identifies this transaction as part of a group. - - - parameter onBehalfOf: The Stripe account ID that these funds are intended for. - - - parameter receiptEmail: The email address to send this charge's receipt to. - - - parameter shippingLabel: Shipping information for the charge. - - - parameter customer: The ID of an existing customer that will be charged in this request. - - - parameter source: A payment source to be charged, such as a credit card. - - - parameter statementDescriptor: An arbitrary string to be displayed on your customer's credit card statement. - - - parameter metadata: Set of key/value pairs that you can attach to an object. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func create(amount: Int, in currency: StripeCurrency, withFee fee: Int? = nil, toAccount account: String? = nil, capture: Bool? = nil, description: String? = nil, destinationAccountId: String? = nil, destinationAmount: Int? = nil, transferGroup: String? = nil, onBehalfOf: String? = nil, receiptEmail: String? = nil, shippingLabel: ShippingLabel? = nil, customer: String? = nil, statementDescriptor: String? = nil, source: String? = nil, metadata: Node? = nil) throws -> StripeRequest { - // Setup our params - var body: [String : Any] = [ - "amount": amount, - "currency": currency.rawValue, - ] + /// Create a charge + /// [Learn More →](https://stripe.com/docs/api/curl#create_charge) + public func create(amount: Int, + currency: StripeCurrency, + applicationFee: Int? = nil, + capture: Bool? = nil, + description: String? = nil, + destinationAccount: String? = nil, + destinationAmount: Int? = nil, + transferGroup: String? = nil, + onBehalfOf: String? = nil, + metadata: [String : String]? = nil, + receiptEmail: String? = nil, + shipping: ShippingLabel? = nil, + customer: String? = nil, + source: Any? = nil, + statementDescriptor: String? = nil) throws -> Future { + var body: [String: Any] = ["amount": amount, "currency": currency.rawValue] - // Create the headers - var headers: [HeaderKey : String]? - if let account = account { - headers = [ - StripeHeader.Account: account - ] - - if let fee = fee { - body["application_fee"] = fee - } + if let applicationFee = applicationFee { + body["application_fee"] = applicationFee } if let capture = capture { @@ -99,8 +59,11 @@ open class ChargeRoutes { body["description"] = description } - if let destinationAccountId = destinationAccountId, let destinationAmount = destinationAmount { - body["destination[account]"] = destinationAccountId + if let destinationAccount = destinationAccount { + body["destination[account]"] = destinationAccount + } + + if let destinationAmount = destinationAmount { body["destination[amount]"] = destinationAmount } @@ -112,263 +75,135 @@ open class ChargeRoutes { body["on_behalf_of"] = onBehalfOf } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1} } if let receiptEmail = receiptEmail { body["receipt_email"] = receiptEmail } - if let shippingLabel = shippingLabel { - if let name = shippingLabel.name { - body["shipping[name]"] = Node(name) - } - - if let carrier = shippingLabel.carrier { - body["shipping[carrier]"] = Node(carrier) - } - - if let phone = shippingLabel.phone { - body["shipping[phone]"] = Node(phone) - } - - if let trackingNumber = shippingLabel.trackingNumber { - body["shipping[tracking_number]"] = Node(trackingNumber) - } - - if let address = shippingLabel.address { - if let line1 = address.addressLine1 { - body["shipping[address][line1]"] = Node(line1) - } - - if let city = address.city { - body["shipping[address][city]"] = Node(city) - } - - if let country = address.country { - body["shipping[address][country]"] = Node(country) - } - - if let postalCode = address.postalCode { - body["shipping[address][postal_code]"] = Node(postalCode) - } - - if let state = address.state { - body["shipping[address][state]"] = Node(state) - } - - if let line2 = address.addressLine2 { - body["shipping[address][line2]"] = Node(line2) - } - } - } - - if let source = source { - body["source"] = source + if let shipping = shipping { + try shipping.toEncodedDictionary().forEach { body["shipping[\($0)]"] = $1 } } if let customer = customer { body["customer"] = customer } - if let statementDescriptor = statementDescriptor { - body["statement_descriptor"] = Node(statementDescriptor) + if let tokenSource = source as? String { + body["source"] = tokenSource + } + + if let cardDictionarySource = source as? [String: Any] { + cardDictionarySource.forEach { body["source[\($0)]"] = $1 } } - // Create the body node - let node = try Node(node: body) + if let statementDescriptor = statementDescriptor { + body["statement_descriptor"] = statementDescriptor + } - return try StripeRequest(client: self.client, method: .post, route: .charges, query: [:], body: Body.data(node.formURLEncoded()), headers: headers) + return try request.send(method: .POST, path: StripeAPIEndpoint.charges.endpoint, body: body.queryParameters) } - - /** - Retrieve a charge - Retrieves the details of a charge that has previously been created. Supply the unique charge ID that was - returned from your previous request, and Stripe will return the corresponding charge information. The same - information is returned when creating or refunding the charge. - - - parameter charge: The chargeId is the ID of the charge - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func retrieve(charge chargeId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .charge(chargeId), query: [:], body: nil, headers: nil) + /// Retrieve a charge + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_charge) + public func retrieve(charge: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.charge(charge).endpoint) } - - - /** - Update a charge - Updates the specified charge by setting the values of the parameters passed. Any parameters not provided will - be left unchanged. This request accepts only the `description`, `metadata`, `receipt_email`, `fraud_details`, - and `shipping` as arguments. - - - parameter description: An arbitrary string which you can attach to a charge object. - - - parameter fraud: A set of key/value pairs you can attach to a charge giving information about its riskiness. - - - parameter receiptEmail: This is the email address that the receipt for this charge will be sent to. - - - parameter shippingLabel: Shipping information for the charge. Helps prevent fraud on charges for physical goods. - - - parameter transferGroup: A string that identifies this transaction as part of a group. - - - parameter metadata: Set of key/value pairs that you can attach to an object. - - - parameter chargeId: A charges ID to update - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func update(charge chargeId: String, description: String? = nil, fraud: FraudDetails? = nil, receiptEmail: String? = nil, shippingLabel: ShippingLabel? = nil, transferGroup: String? = nil, metadata: Node? = nil) throws -> StripeRequest { - var body = Node([:]) + + /// Update a charge + /// [Learn More →](https://stripe.com/docs/api/curl#update_charge) + public func update(charge chargeId: String, + customer: String? = nil, + description: String? = nil, + fraudDetails: StripeFraudDetails? = nil, + metadata: [String: String]? = nil, + receiptEmail: String? = nil, + shipping: ShippingLabel? = nil, + transferGroup: String? = nil) throws -> Future { + var body: [String: Any] = [:] + + if let customer = customer { + body["customer"] = customer + } if let description = description { - body["description"] = Node(description) + body["description"] = description } - if let fraud = fraud { + if let fraud = fraudDetails { if let userReport = fraud.userReport?.rawValue { - body["fraud_details[user_report]"] = Node(userReport) + body["fraud_details[user_report]"] = userReport } if let stripeReport = fraud.stripeReport?.rawValue { - body["fraud_details[stripe_report]"] = Node(stripeReport) + body["fraud_details[stripe_report]"] = stripeReport } } - if let metadata = metadata?.object { - for (key, value) in metadata { + if let metadata = metadata { + metadata.forEach { key, value in body["metadata[\(key)]"] = value } } if let receiptEmail = receiptEmail { - body["receipt_email"] = Node(receiptEmail) + body["receipt_email"] = receiptEmail } - if let shippingLabel = shippingLabel { - if let name = shippingLabel.name { - body["shipping[name]"] = Node(name) - } - - if let carrier = shippingLabel.carrier { - body["shipping[carrier]"] = Node(carrier) - } - - if let phone = shippingLabel.phone { - body["shipping[phone]"] = Node(phone) - } - - if let trackingNumber = shippingLabel.trackingNumber { - body["shipping[tracking_number]"] = Node(trackingNumber) - } - - if let address = shippingLabel.address { - if let line1 = address.addressLine1 { - body["shipping[address][line1]"] = Node(line1) - } - - if let city = address.city { - body["shipping[address][city]"] = Node(city) - } - - if let country = address.country { - body["shipping[address][country]"] = Node(country) - } - - if let postalCode = address.postalCode { - body["shipping[address][postal_code]"] = Node(postalCode) - } - - if let state = address.state { - body["shipping[address][state]"] = Node(state) - } - - if let line2 = address.addressLine2 { - body["shipping[address][line2]"] = Node(line2) - } - } + if let shipping = shipping { + try shipping.toEncodedDictionary().forEach { body["shipping[\($0)]"] = $1 } } if let transferGroup = transferGroup { - body["transfer_group"] = Node(transferGroup) + body["transfer_group"] = transferGroup } - return try StripeRequest(client: self.client, method: .post, route: .charge(chargeId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .POST, path: StripeAPIEndpoint.charge(chargeId).endpoint, body: body.queryParameters) } - /** - Capture a charge - Capture the payment of an existing, uncaptured, charge. This is the second half of the two-step payment flow, - where first you created a charge with the capture option set to false. Uncaptured payments expire exactly - seven days after they are created. If they are not captured by that point in time, they will be marked as - refunded and will no longer be capturable. - - - parameter chargeId: The chargeId is the ID of the charge - - - parameter amount: The amount to capture, which must be less than or equal to the original amount. - Any additional amount will be automatically refunded. - - - parameter applicationFee: An application fee to add on to this charge. Can only be used with Stripe Connect. - - - parameter destinationAmount: A new destination amount to use. Can only be used with destination charges created - with Stripe Connect.The portion of this charge to send to the destination account. - Must be less than or equal to the captured amount of the charge. - - - parameter receiptEmail: The email address to send this charge’s receipt to. This will override the - previously-specified email address for this charge, if one was set. Receipts - will not be sent in test mode. - - - parameter statementDescriptor: An arbitrary string to be displayed on your customer’s credit card statement. - This may be up to 22 characters. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func capture(charge chargeId: String, amount: Int? = nil, applicationFee: Int? = nil, destinationAmount: Int? = nil, receiptEmail: String? = nil, statementDescriptor: String? = nil) throws -> StripeRequest { - var body = Node([:]) + /// Capture a charge + /// [Learn More →](https://stripe.com/docs/api/curl#capture_charge) + public func capture(charge: String, + amount: Int? = nil, + applicationFee: Int? = nil, + destinationAmount: Int? = nil, + receiptEmail: String? = nil, + statementDescriptor: String? = nil) throws -> Future { + var body: [String: Any] = [:] if let amount = amount { - body["amount"] = Node(amount) + body["amount"] = amount } if let applicationFee = applicationFee { - body["application_fee"] = Node(applicationFee) + body["application_fee"] = applicationFee } - if let destination = destinationAmount { - body["destination[amount]"] = Node(destination) + if let destinationAmount = destinationAmount { + body["destination[amount]"] = destinationAmount } if let receiptEmail = receiptEmail { - body["receipt_email"] = Node(receiptEmail) + body["receipt_email"] = receiptEmail } if let statementDescriptor = statementDescriptor { - body["statement_descriptor"] = Node(statementDescriptor) + body["statement_descriptor"] = statementDescriptor } - return try StripeRequest(client: self.client, method: .post, route: .captureCharge(chargeId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .POST, path: StripeAPIEndpoint.captureCharge(charge).endpoint, body: body.queryParameters) } - /** - List all Charges - 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 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 + /// List all charges + /// [Learn More →](https://stripe.com/docs/api/curl#list_charges) + public func listAll(filter: [String : Any]? = nil) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters } - return try StripeRequest(client: self.client, method: .get, route: .charges, query: query, body: nil, headers: nil) + + return try request.send(method: .GET, path: StripeAPIEndpoint.account.endpoint, query: queryParams) } - - } diff --git a/Sources/Stripe/API/Routes/CouponRoutes.swift b/Sources/Stripe/API/Routes/CouponRoutes.swift index 6f9cb57..60eadb3 100644 --- a/Sources/Stripe/API/Routes/CouponRoutes.swift +++ b/Sources/Stripe/API/Routes/CouponRoutes.swift @@ -6,151 +6,106 @@ // // -import Node -import HTTP +import Foundation +import Vapor -open class CouponRoutes { - let client: StripeClient +public protocol CouponRoutes { + associatedtype CP: Coupon + associatedtype DO: DeletedObject + associatedtype L: List - 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. - */ + func create(id: String?, duration: StripeDuration, amountOff: Int?, currency: StripeCurrency?, durationInMonths: Int?, maxRedemptions: Int?, metadata: [String: String]?, percentOff: Int?, redeemBy: Date?) throws -> Future + func retrieve(coupon: String) throws -> Future + func update(coupon: String, metadata: [String: String]?) throws -> Future + func delete(coupon: String) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future +} + +public struct StripeCouponRoutes: CouponRoutes { + private let request: StripeRequest - public func create(id: String? = nil, duration: StripeDuration, amountOff: Int? = nil, currency: StripeCurrency? = nil, durationInMonths: Int? = nil, maxRedemptions: Int? = nil, percentOff: Int? = nil, redeemBy: Date? = nil, metadata: Node? = nil) throws -> StripeRequest { - var body = Node([:]) - - body["duration"] = Node(duration.rawValue) + init(request: StripeRequest) { + self.request = request + } + + /// Create a coupon + /// [Learn More →](https://stripe.com/docs/api/curl#create_coupon) + public func create(id: String? = nil, + duration: StripeDuration, + amountOff: Int? = nil, + currency: StripeCurrency? = nil, + durationInMonths: Int? = nil, + maxRedemptions: Int? = nil, + metadata: [String : String]? = nil, + percentOff: Int? = nil, + redeemBy: Date? = nil) throws -> Future { + var body: [String: Any] = [:] + body["duration"] = duration.rawValue + if let amountOff = amountOff { - body["amount_off"] = Node(amountOff) + body["amount_off"] = amountOff } - + if let currency = currency { - body["currency"] = Node(currency.rawValue) + body["currency"] = currency.rawValue } - + if let durationInMonths = durationInMonths { - body["duration_in_months"] = Node(durationInMonths) + body["duration_in_months"] = durationInMonths } - + if let maxRedemptions = maxRedemptions { - body["max_redemptions"] = Node(maxRedemptions) + body["max_redemptions"] = maxRedemptions } - + if let percentOff = percentOff { - body["percent_off"] = Node(percentOff) + body["percent_off"] = percentOff } - + if let redeemBy = redeemBy { - body["redeem_by"] = Node(Int(redeemBy.timeIntervalSince1970)) + body["redeem_by"] = Int(redeemBy.timeIntervalSince1970) } - - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - return try StripeRequest(client: self.client, method: .post, route: .coupons, query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .POST, path: StripeAPIEndpoint.coupons.endpoint, body: body.queryParameters) } - /** - 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) + /// Retrieve coupon + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_coupon) + public func retrieve(coupon: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.coupon(coupon).endpoint) } - /** - 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 (Non optional since it's the only mutable property) - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - - public func update(metadata: Node, forCouponId couponId: String) throws -> StripeRequest { - var body = Node([:]) + /// Update coupon + /// [Learn More →](https://stripe.com/docs/api/curl#update_coupon) + public func update(coupon: String, metadata: [String : String]? = nil) throws -> Future { + var body: [String: Any] = [:] - if let metadata = metadata.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - return try StripeRequest(client: self.client, method: .post, route: .coupon(couponId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + + return try request.send(method: .POST, path: StripeAPIEndpoint.coupon(coupon).endpoint, body: body.queryParameters) } - /** - 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) + /// Delete coupon + /// [Learn More →](https://stripe.com/docs/api/curl#delete_coupon) + public func delete(coupon: String) throws -> Future { + return try request.send(method: .DELETE, path: StripeAPIEndpoint.coupon(coupon).endpoint) } - /** - 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 + /// List all coupons + /// [Learn More →](https://stripe.com/docs/api/curl#list_coupons) + public func listAll(filter: [String: Any]? = nil) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters } - return try StripeRequest(client: self.client, method: .get, route: .coupons, query: query, body: nil, headers: nil) + + return try request.send(method: .GET, path: StripeAPIEndpoint.coupons.endpoint, query: queryParams) } } diff --git a/Sources/Stripe/API/Routes/CustomerRoutes.swift b/Sources/Stripe/API/Routes/CustomerRoutes.swift index f0f6ed5..4bc6d2f 100644 --- a/Sources/Stripe/API/Routes/CustomerRoutes.swift +++ b/Sources/Stripe/API/Routes/CustomerRoutes.swift @@ -6,417 +6,244 @@ // // -import Node -import HTTP +import Vapor -open class CustomerRoutes { +public protocol CustomerRoutes { + associatedtype C: Customer + associatedtype SH: Shipping + associatedtype DO: DeletedObject + associatedtype L: List + associatedtype SRC: Source + associatedtype BNK: BankAccount + associatedtype CRD: Card - let client: StripeClient + func create(accountBalance: Int?, businessVatId: String?, coupon: String?, defaultSource: String?, description: String?, email: String?, metadata: [String: String]?, shipping: SH?, source: Any?) throws -> Future + func retrieve(customer: String) throws -> Future + func update(customer: String, accountBalance: Int?, businessVatId: String?, coupon: String?, defaultSource: String?, description: String?, email: String?, metadata: [String: String]?, shipping: SH?, source: Any?) throws -> Future + func delete(customer: String) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future + func addNewSource(customer: String, source: String, toConnectedAccount: String?) throws -> Future + func addNewBankAccountSource(customer: String, source: Any, toConnectedAccount: String?, metadata: [String: String]?) throws -> Future + func addNewCardSource(customer: String, source: Any, toConnectedAccount: String?, metadata: [String : String]?) throws -> Future + func deleteSource(customer: String, source: String) throws -> Future + func deleteDiscount(customer: String) throws -> Future +} + +public struct StripeCustomerRoutes: CustomerRoutes { + private let request: StripeRequest - init(client: StripeClient) { - self.client = client + init(request: StripeRequest) { + self.request = request } - /** - Create a customer - Creates a new customer object. - - - parameter accountBalance: An integer amount in cents that is the starting account balance for your customer. - A negative amount represents a credit that will be used before attempting any charges - to the customer’s card; a positive amount will be added to the next invoice. - - - parameter businessVATId: The customer’s VAT identification number. - - - parameter coupon: If you provide a coupon code, the customer will have a discount applied on all recurring charges. - - - parameter defaultSource: Either a card - - - parameter description: An arbitrary string that you can attach to a customer object. It is displayed - alongside the customer in the dashboard. This will be unset if you POST an - empty value. - - - parameter email: The Customer’s email address. It’s displayed alongside the customer in your - dashboard and can be useful for searching and tracking. - - - parameter shippingLabel: Shipping label. - - - parameter source: A one time token ID created from a source. - - - parameter metadata: A set of key/value pairs that you can attach to a customer object. It can be - useful for storing additional information about the customer in a structured - format. You can unset individual keys if you POST an empty value for that key. - You can clear all keys if you POST an empty value for metadata. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func create(accountBalance: Int? = nil, businessVATId: String? = nil, coupon: String? = nil, defaultSource: String? = nil, description: String? = nil, email: String? = nil, shipping: ShippingLabel? = nil, source: String? = nil, metadata: Node? = nil) throws -> StripeRequest { - var body = Node([:]) + /// Create a customer + /// [Learn More →](https://stripe.com/docs/api/curl#create_customer) + public func create(accountBalance: Int? = nil, + businessVatId: String? = nil, + coupon: String? = nil, + defaultSource: String? = nil, + description: String? = nil, + email: String? = nil, + metadata: [String: String]? = nil, + shipping: ShippingLabel? = nil, + source: Any? = nil) throws -> Future { + var body: [String: Any] = [:] if let accountBalance = accountBalance { - body["account_balance"] = Node(accountBalance) + body["account_balance"] = accountBalance } - if let businessVATId = businessVATId { - body["business_vat_id"] = Node(businessVATId) + if let businessVatId = businessVatId { + body["business_vat_id"] = businessVatId } if let coupon = coupon { - body["coupon"] = Node(coupon) + body["coupon"] = coupon } if let defaultSource = defaultSource { - body["default_source"] = Node(defaultSource) + body["default_source"] = defaultSource } if let description = description { - body["description"] = Node(description) + body["description"] = description } if let email = email { - body["email"] = Node(email) + body["email"] = email } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - if let shippingLabel = shipping { - if let name = shippingLabel.name { - body["shipping[name]"] = Node(name) - } - - if let carrier = shippingLabel.carrier { - body["shipping[carrier]"] = Node(carrier) - } - - if let phone = shippingLabel.phone { - body["shipping[phone]"] = Node(phone) - } - - if let trackingNumber = shippingLabel.trackingNumber { - body["shipping[tracking_number]"] = Node(trackingNumber) - } - - if let address = shippingLabel.address { - if let line1 = address.addressLine1 { - body["shipping[address][line1]"] = Node(line1) - } - - if let city = address.city { - body["shipping[address][city]"] = Node(city) - } - - if let country = address.country { - body["shipping[address][country]"] = Node(country) - } - - if let postalCode = address.postalCode { - body["shipping[address][postal_code]"] = Node(postalCode) - } - - if let state = address.state { - body["shipping[address][state]"] = Node(state) - } - - if let line2 = address.addressLine2 { - body["shipping[address][line2]"] = Node(line2) - } - } + if let shipping = shipping { + try shipping.toEncodedDictionary().forEach { body["shipping[\($0)]"] = $1 } } - if let source = source { - body["source"] = Node(source) + if let tokenSource = source as? String { + body["source"] = tokenSource } - return try StripeRequest(client: self.client, method: .post, route: .customers, query: [:], body: Body.data(body.formURLEncoded()), headers: nil) - } - - /** - Adds a new source for the customer. Either a bank account token or a card token. - - - parameter customerId: The customer object to add the source to - - parameter account: A connect account to add the customer to - - parameter source: The source token to add to the customer (Bank account or Card). - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - - public func addNewSource(forCustomer customerId: String, inConnectAccount account: String? = nil, source: String) throws -> StripeRequest { - let body = try Node(node: ["source": source]) - - var headers: [HeaderKey: String]? - - // Check if we have an account to set it to - if let account = account { - headers = [StripeHeader.Account : account] + if let cardDictionarySource = source as? [String: Any] { + cardDictionarySource.forEach { body["source[\($0)]"] = $1 } } - return try StripeRequest(client: self.client, method: .post, route: .customerSources(customerId), query: [:], body: Body.data(body.formURLEncoded()), headers: headers) + return try request.send(method: .POST, path: StripeAPIEndpoint.customers.endpoint, body: body.queryParameters) } - /** - Adds a new bank account source for the customer. - - - parameter customerId: The customer object to add the source to - - parameter account: A connect account to add the customer to - - parameter source: The bank account token or source token or a dictionary with bank account details. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ + /// Retrieve customer + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_customer) + public func retrieve(customer: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.customer(customer).endpoint) + } - public func addNewBankAccountSource(forCustomer customerId: String, inConnectAccount account: String? = nil, source: Node, metadata: Node? = nil) throws -> StripeRequest { - - var body = Node([:]) + /// Update customer + /// [Learn More →](https://stripe.com/docs/api/curl#update_customer) + public func update(customer: String, + accountBalance: Int? = nil, + businessVatId: String? = nil, + coupon: String? = nil, + defaultSource: String? = nil, + description: String? = nil, + email: String? = nil, + metadata: [String: String]? = nil, + shipping: ShippingLabel? = nil, + source: Any? = nil) throws -> Future { + var body: [String: Any] = [:] - if let sourceString = source.string { - body["source"] = Node(sourceString) + if let accountBalance = accountBalance { + body["account_balance"] = accountBalance } - if let bankDetails = source.object { - for (key,value) in bankDetails { - body["source[\(key)]"] = value - } + if let businessVatId = businessVatId { + body["business_vat_id"] = businessVatId } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let coupon = coupon { + body["coupon"] = coupon } - var headers: [HeaderKey: String]? - - // Check if we have an account to set it to - if let account = account { - headers = [StripeHeader.Account : account] + if let defaultSource = defaultSource { + body["default_source"] = defaultSource } - return try StripeRequest(client: self.client, method: .post, route: .customerSources(customerId), query: [:], body: Body.data(body.formURLEncoded()), headers: headers) - } - - /** - Adds a new card source for the customer. - - - parameter customerId: The customer object to add the source to - - parameter account: A connect account to add the customer to - - parameter source: The card token or source token or a dictionary with card details. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - - public func addNewCardSource(forCustomer customerId: String, inConnectAccount account: String? = nil, source: Node, metadata: Node? = nil) throws -> StripeRequest { - var body = Node([:]) + if let description = description { + body["description"] = description + } - if let sourceString = source.string { - body["source"] = Node(sourceString) + if let email = email { + body["email"] = email } - if let cardDetails = source.object { - for (key,value) in cardDetails { - body["source[\(key)]"] = value - } + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let shipping = shipping { + try shipping.toEncodedDictionary().forEach { body["shipping[\($0)]"] = $1 } } - var headers: [HeaderKey: String]? + if let tokenSource = source as? String { + body["source"] = tokenSource + } - // Check if we have an account to set it to - if let account = account { - headers = [StripeHeader.Account : account] + if let cardDictionarySource = source as? [String: Any] { + cardDictionarySource.forEach { body["source[\($0)]"] = $1 } } - return try StripeRequest(client: self.client, method: .post, route: .customerSources(customerId), query: [:], body: Body.data(body.formURLEncoded()), headers: headers) + return try request.send(method: .POST, path: StripeAPIEndpoint.customer(customer).endpoint, body: body.queryParameters) } - /** - Retrieve a customer - Retrieves the details of an existing customer. You need only supply the unique customer identifier - that was returned upon customer creation. - - - parameter customerId: The Customer's ID - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func retrieve(customer customerId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .customer(customerId), query: [:], body: nil, headers: nil) + /// Delete a customer + /// [Learn More →](https://stripe.com/docs/api/curl#delete_customer) + public func delete(customer: String) throws -> Future { + return try request.send(method: .DELETE, path: StripeAPIEndpoint.customer(customer).endpoint) } - /** - Update a customer - Updates the specified customer by setting the values of the parameters passed. Any parameters not - provided will be left unchanged. For example, if you pass the source parameter, that becomes the - customer’s active source (e.g., a card) to be used for all charges in the future. When you update a - customer to a new valid source: for each of the customer’s current subscriptions, if the subscription - is in the past_due state, then the latest unpaid, unclosed invoice for the subscription will be retried - (note that this retry will not count as an automatic retry, and will not affect the next regularly - scheduled payment for the invoice). (Note also that no invoices pertaining to subscriptions in the - unpaid state, or invoices pertaining to canceled subscriptions, will be retried as a result of updating - the customer’s source.) - - This request accepts mostly the same arguments as the customer creation call. - - - parameter accountBalance: An integer amount in cents that is the starting account balance for your customer. - A negative amount represents a credit that will be used before attempting any charges - to the customer’s card; a positive amount will be added to the next invoice. - - - parameter businessVATId: The customer’s VAT identification number. - - - parameter coupon: If you provide a coupon code, the customer will have a discount applied on all recurring charges. - - - parameter defaultSourceId: Either a card - - - parameter description: An arbitrary string that you can attach to a customer object. It is displayed - alongside the customer in the dashboard. This will be unset if you POST an - empty value. - - - parameter email: The Customer’s email address. It’s displayed alongside the customer in your - dashboard and can be useful for searching and tracking. - - - - parameter shippingLabel: Shipping label. - - - parameter newSource: A one time token ID created from a source. - - - parameter metadata: A set of key/value pairs that you can attach to a customer object. It can be - useful for storing additional information about the customer in a structured - format. You can unset individual keys if you POST an empty value for that key. - You can clear all keys if you POST an empty value for metadata. - - - parameter customerId: A customer class created with appropiate 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 update(accountBalance: Int? = nil, businessVATId: String? = nil, coupon: String? = nil, defaultSourceId: String? = nil, description:String? = nil, email: String? = nil, shipping: ShippingLabel? = nil, newSource: String? = nil, metadata: Node? = nil, forCustomerId customerId: String) throws -> StripeRequest { - var body = Node([:]) + /// List all customers + /// [Learn More →](https://stripe.com/docs/api/curl#list_customers) + public func listAll(filter: [String: Any]? = nil) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters + } + + return try request.send(method: .GET, path: StripeAPIEndpoint.customers.endpoint, query: queryParams) + } + + /// Attach a source + /// [Learn More →](https://stripe.com/docs/api/curl#attach_source) + public func addNewSource(customer: String, source: String, toConnectedAccount: String? = nil) throws -> Future { + let body: [String: Any] = ["source": source] + var headers: HTTPHeaders = [:] - if let accountBalance = accountBalance { - body["account_balance"] = Node(accountBalance) + if let connectedAccount = toConnectedAccount { + headers.add(name: .stripeAccount, value: connectedAccount) } - if let businessVATId = businessVATId { - body["business_vat_id"] = Node(businessVATId) + return try request.send(method: .POST, path: StripeAPIEndpoint.customerSources(customer).endpoint, body: body.queryParameters, headers: headers) + } + + /// Create a bank account + /// [Learn More →](https://stripe.com/docs/api/curl#customer_create_bank_account) + public func addNewBankAccountSource(customer: String, source: Any, toConnectedAccount: String? = nil, metadata: [String : String]? = nil) throws -> Future { + var body: [String: Any] = [:] + var headers: HTTPHeaders = [:] + + if let connectedAccount = toConnectedAccount { + headers.add(name: .stripeAccount, value: connectedAccount) } - if let coupon = coupon { - body["coupon"] = Node(coupon) + if let source = source as? String { + body["source"] = source } - if let defaultSourceId = defaultSourceId { - body["default_source"] = Node(defaultSourceId) + if let source = source as? [String: Any] { + source.forEach { body["source[\($0)]"] = $1 } } - if let description = description { - body["description"] = Node(description) + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - if let email = email { - body["email"] = Node(email) + return try request.send(method: .POST, path: StripeAPIEndpoint.customerSources(customer).endpoint, body: body.queryParameters, headers: headers) + } + + /// Create a card + /// [Learn More →](https://stripe.com/docs/api/curl#create_card) + public func addNewCardSource(customer: String, source: Any, toConnectedAccount: String? = nil, metadata: [String : String]? = nil) throws -> Future { + var body: [String: Any] = [:] + var headers: HTTPHeaders = [:] + + if let connectedAccount = toConnectedAccount { + headers.add(name: .stripeAccount, value: connectedAccount) } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let source = source as? String { + body["source"] = source } - - if let shippingLabel = shipping { - if let name = shippingLabel.name { - body["shipping[name]"] = Node(name) - } - - if let carrier = shippingLabel.carrier { - body["shipping[carrier]"] = Node(carrier) - } - - if let phone = shippingLabel.phone { - body["shipping[phone]"] = Node(phone) - } - - if let trackingNumber = shippingLabel.trackingNumber { - body["shipping[tracking_number]"] = Node(trackingNumber) - } - - if let address = shippingLabel.address { - if let line1 = address.addressLine1 { - body["shipping[address][line1]"] = Node(line1) - } - - if let city = address.city { - body["shipping[address][city]"] = Node(city) - } - - if let country = address.country { - body["shipping[address][country]"] = Node(country) - } - - if let postalCode = address.postalCode { - body["shipping[address][postal_code]"] = Node(postalCode) - } - - if let state = address.state { - body["shipping[address][state]"] = Node(state) - } - - if let line2 = address.addressLine2 { - body["shipping[address][line2]"] = Node(line2) - } - } + + if let source = source as? [String: Any] { + source.forEach { body["source[\($1)]"] = $1 } } - if let newSource = newSource { - body["source"] = Node(newSource) + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - return try StripeRequest(client: self.client, method: .post, route: .customer(customerId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .POST, path: StripeAPIEndpoint.customerSources(customer).endpoint, body: body.queryParameters, headers: headers) } - - /** - Delete a customer discount - Removes the currently applied discount on a customer. - - - - parameter customerId: The Customer's ID - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func deleteDiscount(onCustomer customerId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .delete, route: .customerDiscount(customerId), query: [:], body: nil, headers: nil) - } - - /** - 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 - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func delete(customer customerId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .delete, route: .customer(customerId), query: [:], body: nil, headers: nil) + + /// Detach a source + /// [Learn More →](https://stripe.com/docs/api/curl#detach_source) + public func deleteSource(customer: String, source: String) throws -> Future { + return try request.send(method: .DELETE, path: StripeAPIEndpoint.customerDetachSources(customer, source).endpoint) } - /** - List all customers - 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 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: .customers, query: query, body: nil, headers: nil) + /// Delete a customer discount + /// [Learn More →](https://stripe.com/docs/api/curl#delete_discount) + public func deleteDiscount(customer: String) throws -> Future { + return try request.send(method: .DELETE, path: StripeAPIEndpoint.customerDiscount(customer).endpoint) } } diff --git a/Sources/Stripe/API/Routes/DisputeRoutes.swift b/Sources/Stripe/API/Routes/DisputeRoutes.swift index ad4d898..81c2104 100644 --- a/Sources/Stripe/API/Routes/DisputeRoutes.swift +++ b/Sources/Stripe/API/Routes/DisputeRoutes.swift @@ -6,97 +6,66 @@ // // -import Node -import HTTP +import Vapor -open class DisputeRoutes { - let client: StripeClient +public protocol DisputeRoutes { + associatedtype D: Dispute + associatedtype DE: DisputeEvidence + associatedtype L: List - init(client: StripeClient) { - self.client = client + func retrieve(dispute: String) throws -> Future + func update(dispute: String, disputeEvidence: DE?, metadata: [String: String]?, submit: Bool?) throws -> Future + func close(dispute: String) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future +} + +public struct StripeDisputeRoutes: DisputeRoutes { + private let request: StripeRequest + + init(request: StripeRequest) { + self.request = request } - /** - Retrieve a dispute - Retrieves the dispute with the given ID. - - - parameter disputeId: ID of dispute to retrieve. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func retrieve(dispute disputeId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .disputes(disputeId), query: [:], body: nil, headers: nil) + /// Retrieve a dispute + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_dispute) + public func retrieve(dispute: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.disputes(dispute).endpoint) } - /** - Update a dispute - When you get a dispute, contacting your customer is always the best first step. If that doesn’t work, - you can submit evidence in order to help us resolve the dispute in your favor. You can do this in your - dashboard, but if you prefer, you can use the API to submit evidence programmatically. - Depending on your dispute type, different evidence fields will give you a better chance of winning your dispute. - You may want to consult our guide to dispute types to help you figure out which evidence fields to provide. - - - parameter disputeId: ID of dispute to retrieve. - - parameter evidence: Evidence to upload to respond to a dispute. Updating any field in the hash will submit - all fields in the hash for review. - - parameter submit: Whether or not to immediately submit evidence to the bank. If false, evidence is staged - on the dispute. Staged evidence is visible in the API and Dashboard, and can be submitted - to the bank by making another request with this attribute set to true (the default). - - parameter metadata: A set of key/value pairs that you can attach to a dispute object. It can be useful for storing - additional information about the dispute in a structured format. You can unset individual keys - if you POST an empty value for that key. You can clear all keys if you POST an empty value for metadata. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func update(dispute disputeId: String, evidence: Node? = nil, submit: Bool? = nil, metadata: Node? = nil) throws -> StripeRequest { - var body = Node([:]) + /// Update a dispute + /// [Learn More →](https://stripe.com/docs/api/curl#update_dispute) + public func update(dispute: String, disputeEvidence: StripeDisputeEvidence?, metadata: [String : String]?, submit: Bool?) throws -> Future { + var body: [String: Any] = [:] - if let evidence = evidence?.object { - for (key, value) in evidence { - body["evidence[\(key)]"] = value - } + if let disputeEvidence = disputeEvidence { + try disputeEvidence.toEncodedDictionary().forEach { body["evidence[\($0)]"] = $1 } } - if let submit = submit { - body["submit"] = Node(submit) + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let submit = submit { + body["submit"] = submit } - return try StripeRequest(client: self.client, method: .post, route: .disputes(disputeId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .POST, path: StripeAPIEndpoint.disputes(dispute).endpoint, body: body.queryParameters) } - /** - Close a dispute - Closing the dispute for a charge indicates that you do not have any evidence to submit and are essentially ‘dismissing’ - the dispute, acknowledging it as lost - The status of the dispute will change from needs_response to lost. Closing a dispute is irreversible. - - - parameter disputeId: ID of dispute to close. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func close(dispute disputeId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .post, route: .closeDispute(disputeId), query: [:], body: nil, headers: nil) + /// Close a dispute + /// [Learn More →](https://stripe.com/docs/api/curl#close_dispute) + public func close(dispute: String) throws -> Future { + return try request.send(method: .POST, path: StripeAPIEndpoint.closeDispute(dispute).endpoint) } - /** - List all disputes - Returns a list of your disputes. - - - 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 + /// List all disputes + /// [Learn More →](https://stripe.com/docs/api/curl#list_disputes) + public func listAll(filter: [String : Any]?) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters } - return try StripeRequest(client: self.client, method: .get, route: .dispute, query: query, body: nil, headers: nil) + + return try request.send(method: .GET, path: StripeAPIEndpoint.dispute.endpoint, query: queryParams) } } diff --git a/Sources/Stripe/API/Routes/EphemeralKeyRoutes.swift b/Sources/Stripe/API/Routes/EphemeralKeyRoutes.swift index 8dba2ce..5e84daa 100644 --- a/Sources/Stripe/API/Routes/EphemeralKeyRoutes.swift +++ b/Sources/Stripe/API/Routes/EphemeralKeyRoutes.swift @@ -5,25 +5,29 @@ // Created by Andrew Edwards on 10/17/17. // -import HTTP -import Node -open class EphemeralKeyRoutes { - let client: StripeClient +import Vapor + +public protocol EphemeralKeyRoutes { + associatedtype EK: EphemeralKey + + func create(customer: String) throws -> Future + func delete(ephemeralKey: String) throws -> Future +} + +public struct StripeEphemeralKeyRoutes: EphemeralKeyRoutes { + private let request: StripeRequest - init(client: StripeClient) { - self.client = client + init(request: StripeRequest) { + self.request = request } - public func create(customerId: String) throws -> StripeRequest { - - let node = try Node(node: ["customer": customerId]) - - return try StripeRequest(client: self.client, method: .post, route: .ephemeralKeys, query: [:], body: Body.data(node.formURLEncoded()), headers: nil) + public func create(customer: String) throws -> Future { + let body = ["customer": customer] + return try request.send(method: .POST, path: StripeAPIEndpoint.ephemeralKeys.endpoint, body: body.queryParameters) } - public func delete(ephemeralKeyId: String) throws -> StripeRequest { - - return try StripeRequest(client: self.client, method: .delete, route: .ephemeralKey(ephemeralKeyId), query: [:], body: nil, headers: nil) + public func delete(ephemeralKey: String) throws -> Future { + return try request.send(method: .DELETE, path: StripeAPIEndpoint.ephemeralKey(ephemeralKey).endpoint) } } diff --git a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift index 15f40c6..b9a7504 100644 --- a/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceItemRoutes.swift @@ -6,159 +6,114 @@ // // -import Node -import HTTP +import Vapor -open class InvoiceItemRoutes { +public protocol InvoiceItemRoutes { + associatedtype II: InvoiceItem + associatedtype DO: DeletedObject + associatedtype L: List - let client: StripeClient + func create(amount: Int, currency: StripeCurrency, customer: String, description: String?, discountable: Bool?, invoice: String?, metadata: [String: String]?, subscription: String?) throws -> Future + func retrieve(invoiceItem: String) throws -> Future + func update(invoiceItem: String, amount: Int?, description: String?, discountable: Bool?, metadata: [String: String]?) throws -> Future + func delete(invoiceItem: String) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future +} + +public struct StripeInvoiceItemRoutes: InvoiceItemRoutes { + private let request: StripeRequest - init(client: StripeClient) { - self.client = client + init(request: StripeRequest) { + self.request = request } - /** - Create an invoice item - Adds an arbitrary charge or credit to the customer’s upcoming invoice. - - - parameter customer: The ID of the customer who will be billed when this invoice item is billed. - - parameter amount: The integer amount in cents of the charge to be applied to the upcoming invoice. - To apply a credit to the customer’s account, pass a negative amount. - - parameter currency: Three-letter ISO currency code, in lowercase. Must be a supported currency. - - parameter invoice: The ID of an existing invoice to add this invoice item to. When left blank, the - invoice item will be added to the next upcoming scheduled invoice. Use this when adding - invoice items in response to an invoice.created webhook. You cannot add an invoice item - to an invoice that has already been paid, attempted or closed. - - parameter subscription: The ID of a subscription to add this invoice item to. When left blank, the invoice item - will be be added to the next upcoming scheduled invoice. When set, scheduled invoices for - subscriptions other than the specified subscription will ignore the invoice item. Use this - when you want to express that an invoice item has been accrued within the context of a - particular subscription. - - parameter description: An arbitrary string which you can attach to the invoice item. The description is displayed - in the invoice for easy tracking. This will be unset if you POST an empty value. - - parameter discountable: Controls whether discounts apply to this invoice item. Defaults to false for prorations or - negative invoice items, and true for all other invoice items. - - parameter metadata: A set of key/value pairs that you can attach to an invoice item object. It can be useful for - storing additional information about the invoice item in a structured format. You can unset - individual keys if you POST an empty value for that key. You can clear all keys if you POST - an empty value for metadata. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func createItem(forCustomer customer: String, amount: Int, inCurrency currency: StripeCurrency, toInvoice invoice: String? = nil, toSubscription subscription: String? = nil, description: String? = nil, discountable: Bool? = nil, metadata: Node? = nil) throws -> StripeRequest { - var body = Node([:]) + /// Create an invoice item + /// [Learn More →](https://stripe.com/docs/api/curl#create_invoiceitem) + public func create(amount: Int, + currency: StripeCurrency, + customer: String, + description: String? = nil, + discountable: Bool? = nil, + invoice: String? = nil, + metadata: [String : String]? = nil, + subscription: String? = nil) throws -> Future { + var body: [String: Any] = [:] - body["customer"] = Node(customer) - body["amount"] = Node(amount) - body["currency"] = Node(currency.rawValue) + body["amount"] = amount + body["currency"] = currency.rawValue + body["customer"] = customer - if let subscription = subscription { - body["subscription"] = Node(subscription) + if let description = description { + body["description"] = description } - if let invoice = invoice { - body["invoice"] = Node(invoice) + if let discountable = discountable { + body["discountable"] = discountable } - if let description = description { - body["description"] = Node(description) + if let invoice = invoice { + body["invoice"] = invoice } - if let discountable = discountable { - body["discountable"] = Node(discountable) + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let subscription = subscription { + body["subscription"] = subscription } - return try StripeRequest(client: self.client, method: .post, route: .invoiceItems, body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .POST, path: StripeAPIEndpoint.invoiceItems.endpoint, body: body.queryParameters) } - - /** - Fetch an invoice - Retrieves the invoice with the given ID. - - - parameter invoice: The ID of the desired invoice item. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func fetch(invoiceItem invoiceItemId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .post, route: .invoiceItem(invoiceItemId), body: nil, headers: nil) + + /// Retrieve an invoice item + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_invoiceitem) + public func retrieve(invoiceItem: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.invoiceItem(invoiceItem).endpoint) } - /** - Update an invoice item - Updates the amount or description of an invoice item on an upcoming invoice. Updating an invoice item - is only possible before the invoice it’s attached to is closed. - - - parameter amount: The integer amount in cents of the charge to be applied to the upcoming invoice. - To apply a credit to the customer’s account, pass a negative amount. - - parameter description: An arbitrary string which you can attach to the invoice item. The description is displayed - in the invoice for easy tracking. This will be unset if you POST an empty value. - - parameter discountable: Controls whether discounts apply to this invoice item. Defaults to false for prorations or - negative invoice items, and true for all other invoice items. - - parameter metadata: A set of key/value pairs that you can attach to an invoice item object. It can be useful for - storing additional information about the invoice item in a structured format. You can unset - individual keys if you POST an empty value for that key. You can clear all keys if you POST - an empty value for metadata. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func update(invoiceItem invoiceItemId: String, amount: Int, description: String? = nil, discountable: Bool? = nil, metadata: Node? = nil) throws -> StripeRequest { - var body = Node([:]) + /// Update an invoice item + /// [Learn More →](https://stripe.com/docs/api/curl#update_invoiceitem) + public func update(invoiceItem: String, + amount: Int? = nil, + description: String? = nil, + discountable: Bool? = nil, + metadata: [String : String]? = nil) throws -> Future { + var body: [String: Any] = [:] - body["amount"] = Node(amount) + if let amount = amount { + body["amount"] = amount + } if let description = description { - body["description"] = Node(description) + body["description"] = description } if let discountable = discountable { - body["discountable"] = Node(discountable) + body["discountable"] = discountable } - - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - return try StripeRequest(client: self.client, method: .post, route: .invoiceItem(invoiceItemId), body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .POST, path: StripeAPIEndpoint.invoiceItem(invoiceItem).endpoint, body: body.queryParameters) } - /** - Delete an invoice item - Removes an invoice item from the upcoming invoice. Removing an invoice item is only possible before the - invoice it’s attached to is closed. - - - parameter invoice: The ID of the desired invoice item. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func delete(invoiceItem invoiceItemId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .delete, route: .invoiceItem(invoiceItemId), body: nil, headers: nil) + /// Delete an invoice item + /// [Learn More →](https://stripe.com/docs/api/curl#delete_invoiceitem) + public func delete(invoiceItem: String) throws -> Future { + return try request.send(method: .DELETE, path: StripeAPIEndpoint.invoiceItem(invoiceItem).endpoint) } - /** - List all invoice items - Returns a list of your invoice items. Invoice items are returned sorted by creation date, with the most - recently created invoice items 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(customer: String? = nil, filter: StripeFilter? = nil) throws -> StripeRequest { - var query = [String : NodeRepresentable]() - if let customer = customer { - query["customer"] = customer + /// List all invoice items + /// [Learn More →](https://stripe.com/docs/api/curl#list_invoiceitems) + public func listAll(filter: [String: Any]? = nil) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters } - if let data = try filter?.createQuery() { - query = data - } - return try StripeRequest(client: self.client, method: .get, route: .invoiceItems, query: query, body: nil, headers: nil) + + return try request.send(method: .GET, path: StripeAPIEndpoint.invoiceItems.endpoint, query: queryParams) } } diff --git a/Sources/Stripe/API/Routes/InvoiceRoutes.swift b/Sources/Stripe/API/Routes/InvoiceRoutes.swift index 2f62baa..c0037c9 100644 --- a/Sources/Stripe/API/Routes/InvoiceRoutes.swift +++ b/Sources/Stripe/API/Routes/InvoiceRoutes.swift @@ -6,269 +6,189 @@ // // -import Node -import HTTP +import Vapor +import Foundation -open class InvoiceRoutes { +public protocol InvoiceRoutes { + associatedtype I: Invoice + associatedtype L: List - let client: StripeClient + func create(customer: String, applicationFee: Int?, connectAccount: String?, billing: String?, daysUntilDue: Int?, description: String?, dueDate: Date?, metadata: [String: String]?, statementDescriptor: String?, subscription: String?, taxPercent: Decimal?) throws -> Future + func retrieve(invoice: String) throws -> Future + func retrieveLineItems(invoice: String, filter: [String: Any]?) throws -> Future + func retrieveUpcomingInvoice(customer: String, filter: [String: Any]?) throws -> Future + func update(invoice: String, applicationFee: Int?, + connectAccount: String?, closed: Bool?, description: String?, forgiven: Bool?, metadata: [String: String]?, paid: Bool?, statementDescriptor: String?, taxPercent: Decimal?) throws -> Future + func pay(invoice: String, source: String?) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future +} + +public struct StripeInvoiceRoutes: InvoiceRoutes { + private let request: StripeRequest - init(client: StripeClient) { - self.client = client + init(request: StripeRequest) { + self.request = request } - /** - Create invoice - If you need to invoice your customer outside the regular billing cycle, you can create an invoice that pulls in all - pending invoice items, including prorations. The customer’s billing cycle and regular subscription won’t be affected. - - Once you create the invoice, Stripe will attempt to collect payment according to your subscriptions settings, - though you can choose to pay it right away. - - - parameter customer: The ID of the customer to attach the invoice to - - parameter subscription: The ID of the subscription to invoice. If not set, the created invoice will include all - pending invoice items for the customer. If set, the created invoice will exclude pending - invoice items that pertain to other subscriptions. - - parameter fee: A fee to charge if you are using connected accounts (Must be in cents) - - parameter account: The account to transfer the fee to - - parameter description: A description for the invoice - - parameter metadata: Aditional metadata info - - parameter taxPercent: The percent tax rate applied to the invoice, represented as a decimal number. - - parameter statementDescriptor: Extra information about a charge for the customer’s credit card statement. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - - public func create(forCustomer customer: String, subscription: String? = nil, withFee fee: Int? = nil, toAccount account: String? = nil, description: String? = nil, metadata: Node? = nil, taxPercent: Double? = nil, statementDescriptor: String? = nil) throws -> StripeRequest { - var body = Node([:]) - // Create the headers - var headers: [HeaderKey : String]? - if let account = account { - headers = [ - StripeHeader.Account: account - ] - - if let fee = fee { - body["application_fee"] = Node(fee) - } + /// Create an invoice + /// [Learn More →](https://stripe.com/docs/api/curl#create_invoice) + public func create(customer: String, + applicationFee: Int? = nil, + connectAccount: String? = nil, + billing: String? = nil, + daysUntilDue: Int? = nil, + description: String? = nil, + dueDate: Date? = nil, + metadata: [String : String]? = nil, + statementDescriptor: String? = nil, + subscription: String? = nil, + taxPercent: Decimal? = nil) throws -> Future { + var body: [String: Any] = [:] + var headers: HTTPHeaders = [:] + + body["customer"] = customer + + if let applicationFee = applicationFee { + body["application_fee"] = applicationFee } - body["customer"] = Node(customer) + if let connectAccount = connectAccount { + headers.add(name: .stripeAccount, value: connectAccount) + } - if let subscription = subscription { - body["subscription"] = Node(subscription) + if let billing = billing { + body["billing"] = billing + } + + if let daysUntilDue = daysUntilDue { + body["days_until_due"] = daysUntilDue } if let description = description { - body["description"] = Node(description) + body["description"] = description } - if let taxPercent = taxPercent { - body["tax_percent"] = Node(taxPercent) + if let dueDate = dueDate { + body["due_date"] = Int(dueDate.timeIntervalSince1970) + } + + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } if let statementDescriptor = statementDescriptor { - body["statement_descriptor"] = Node(statementDescriptor) + body["statement_descriptor"] = statementDescriptor + } + + if let subscription = subscription { + body["subscription"] = subscription } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let taxPercent = taxPercent { + body["tax_percent"] = taxPercent } - return try StripeRequest(client: self.client, method: .post, route: .invoices, body: Body.data(body.formURLEncoded()), headers: headers) + return try request.send(method: .POST, path: StripeAPIEndpoint.invoices.endpoint, body: body.queryParameters, headers: headers) } - /** - Fetch an invoice - Retrieves the invoice with the given ID. - - - parameter invoice: The Invoice ID to fetch - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func fetch(invoice invoiceId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .post, route: .invoice(invoiceId), body: nil, headers: nil) + /// Retrieve an invoice + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_invoice) + public func retrieve(invoice: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.invoice(invoice).endpoint) } - /** - List items for invoice - When retrieving an invoice, you’ll get a lines property containing the total count of line items and the first handful - of those items. There is also a URL where you can retrieve the full (paginated) list of line items. - - - parameter invoiceId: The Invoice ID to fetch - - parameter customer: In the case of upcoming invoices, the customer of the upcoming invoice is required. In other - cases it is ignored. - - parameter coupon: For upcoming invoices, preview applying this coupon to the invoice. If a subscription or - subscription_items is provided, the invoice returned will preview updating or creating a - subscription with that coupon. Otherwise, it will preview applying that coupon to the customer - for the next upcoming invoice from among the customer’s subscriptions. Otherwise this parameter - is ignored. This will be unset if you POST an empty value. - - parameter filter: Parameters used to filter the results - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ + /// Retrieve an invoice's line items + /// [Learn More →](https://stripe.com/docs/api/curl#invoice_lines) + public func retrieveLineItems(invoice: String, filter: [String: Any]? = nil) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.invoiceLines(invoice).endpoint, query: filter?.queryParameters ?? "") + } - public func listItems(forInvoice invoiceId: String, customer: String? = nil, coupon: String? = nil, filter: StripeFilter? = nil) throws -> StripeRequest { - var query = [String : NodeRepresentable]() - if let data = try filter?.createQuery() { - query = data - } - - if let customer = customer { - query["customer"] = customer - } + /// Retrieve an upcoming invoice + /// [Learn More →](https://stripe.com/docs/api/curl#upcoming_invoice) + public func retrieveUpcomingInvoice(customer: String, filter: [String: Any]? = nil) throws -> Future { + var query: [String: Any] = ["customer": customer] - if let coupon = coupon { - query["coupon"] = coupon + if let filter = filter { + filter.forEach { query["\($0)"] = $1 } } - return try StripeRequest(client: self.client, method: .get, route: .invoiceLines(invoiceId), query: query, body: nil, headers: nil) + return try request.send(method: .GET, path: StripeAPIEndpoint.upcomingInvoices.endpoint, query: query.queryParameters) } - /** - List Upcoming invoice for Customer - At any time, you can preview the upcoming invoice for a customer. This will show you all the charges that are pending, - including subscription renewal charges, invoice item charges, etc. It will also show you any discount that is applicable - to the customer. - - - parameter customerId: The identifier of the customer whose upcoming invoice you’d like to retrieve. - - parameter coupon: The code of the coupon to apply. If subscription or subscription_items is provided, the invoice - returned will preview updating or creating a subscription with that coupon. Otherwise, it will - preview applying that coupon to the customer for the next upcoming invoice from among the customer’s - subscriptions. The invoice can be previewed without a coupon by passing this value as an empty string. - - parameter subscription: The identifier of the subscription for which you’d like to retrieve the upcoming invoice. If not provided, - but a subscription_items is provided, you will preview creating a subscription with those items. If neither - subscription nor subscription_items is provided, you will retrieve the next upcoming invoice from among the - customer’s subscriptions. - - parameter filter: Parameters used to filter the result - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func upcomingInvoice(forCustomer customerId: String, coupon: String? = nil, subscription: String? = nil, filter: StripeFilter? = nil) throws -> StripeRequest { - var query = [String : NodeRepresentable]() - query["customer"] = customerId + /// Update an invoice + /// [Learn More →](https://stripe.com/docs/api/curl#update_invoice) + public func update(invoice: String, + applicationFee: Int? = nil, + connectAccount: String? = nil, + closed: Bool? = nil, + description: String? = nil, + forgiven: Bool? = nil, + metadata: [String : String]? = nil, + paid: Bool? = nil, + statementDescriptor: String? = nil, + taxPercent: Decimal? = nil) throws -> Future { + var body: [String: Any] = [:] + var headers: HTTPHeaders = [:] - if let data = try filter?.createQuery() { - query = data + if let applicationFee = applicationFee { + body["application_fee"] = applicationFee } - if let coupon = coupon { - query["coupon"] = coupon + if let connectAccount = connectAccount { + headers.add(name: .stripeAccount, value: connectAccount) } - if let subscription = subscription { - query["subscription"] = subscription - } - - return try StripeRequest(client: self.client, method: .get, route: .upcomingInvoices, query: query, body: nil, headers: nil) - } - - /** - Update Invoice - Until an invoice is paid, it is marked as open (closed=false). If you’d like to stop Stripe from attempting to collect payment on an - invoice or would simply like to close the invoice out as no longer owed by the customer, you can update the closed parameter. - - - parameter invoiceId: The ID of the Invoice to update - - parameter closed: Boolean representing whether an invoice is closed or not. To close an invoice, pass true. - - parameter forgiven: Boolean representing whether an invoice is forgiven or not. To forgive an invoice, pass true. - Forgiving an invoice instructs us to update the subscription status as if the invoice were successfully paid. - Once an invoice has been forgiven, it cannot be unforgiven or reopened. - - parameter applicationFee: A fee in cents that will be applied to the invoice and transferred to the application owner’s Stripe account. - The request must be made with an OAuth key or the Stripe-Account header in order to take an application fee. - For more information, see the application fees documentation. - - parameter account: The account to transfer the fee to - - parameter description: A description for the invoice - - parameter metadata: Aditional metadata info - - parameter taxPercent: The percent tax rate applied to the invoice, represented as a decimal number. The tax rate of an attempted, - paid or forgiven invoice cannot be changed. - - parameter statementDescriptor: Extra information about a charge for the customer’s credit card statement. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func update(invoice invoiceId: String, closed: Bool? = nil, forgiven: Bool? = nil, applicationFee: Int? = nil, toAccount account: String? = nil, description: String? = nil, metadata: Node? = nil, taxPercent: Double? = nil, statementDescriptor: String? = nil) throws -> StripeRequest { - var body = Node([:]) - // Create the headers - var headers: [HeaderKey : String]? - if let account = account { - headers = [ - StripeHeader.Account: account - ] - - if let fee = applicationFee { - body["application_fee"] = Node(fee) - } + if let closed = closed { + body["closed"] = closed } - if let closed = closed { - body["closed"] = Node(closed) + if let description = description { + body["description"] = description } if let forgiven = forgiven { - body["forgiven"] = Node(forgiven) + body["forgiven"] = forgiven } - if let description = description { - body["description"] = Node(description) + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - if let taxPercent = taxPercent { - body["tax_percent"] = Node(taxPercent) + if let paid = paid { + body["paid"] = paid } - + if let statementDescriptor = statementDescriptor { - body["statement_descriptor"] = Node(statementDescriptor) + body["statement_descriptor"] = statementDescriptor } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let taxPercent = taxPercent { + body["tax_percent"] = taxPercent } - - return try StripeRequest(client: self.client, method: .post, route: .invoice(invoiceId), body: Body.data(body.formURLEncoded()), headers: headers) + + return try request.send(method: .POST, path: StripeAPIEndpoint.invoice(invoice).endpoint, body: body.queryParameters, headers: headers) } - /** - Pay Invoice - Stripe automatically creates and then attempts to collect payment on invoices for customers on subscriptions according to your - subscriptions settings. However, if you’d like to attempt payment on an invoice out of the normal collection schedule or for some - other reason, you can do so. - - - parameter invoiceId: The ID of the invoice to pay. - - parameter source: A payment source to be charged. The source must be the ID of a source belonging to the customer associated - with the invoice being paid. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func pay(invoice invoiceId: String, source: String? = nil) throws -> StripeRequest { - var body = Node([:]) - + /// Pay an invoice + /// [Learn More →](https://stripe.com/docs/api/curl#pay_invoice) + public func pay(invoice: String, source: String? = nil) throws -> Future { + var body: [String: Any] = [:] + if let source = source { - body["source"] = Node(source) + body["source"] = source } - return try StripeRequest(client: self.client, method: .post, route: .payInvoice(invoiceId), body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .POST, path: StripeAPIEndpoint.payInvoice(invoice).endpoint, body: body.queryParameters) } - /** - List all Invoices - You can list all invoices, or list the invoices for a specific customer. The invoices are returned sorted by creation date, - with the most recently created invoices 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(customer: String? = nil, filter: StripeFilter? = nil) throws -> StripeRequest { - var query = [String : NodeRepresentable]() - if let customer = customer { - query["customer"] = customer + /// List all invoices + /// [Learn More →](https://stripe.com/docs/api/curl#list_invoices) + public func listAll(filter: [String : Any]? = nil) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters } - if let data = try filter?.createQuery() { - query = data - } - return try StripeRequest(client: self.client, method: .get, route: .invoices, query: query, body: nil, headers: nil) + + return try request.send(method: .GET, path: StripeAPIEndpoint.invoices.endpoint, query: queryParams) } } diff --git a/Sources/Stripe/API/Routes/OrderReturnRoutes.swift b/Sources/Stripe/API/Routes/OrderReturnRoutes.swift index d076518..12cd3e0 100644 --- a/Sources/Stripe/API/Routes/OrderReturnRoutes.swift +++ b/Sources/Stripe/API/Routes/OrderReturnRoutes.swift @@ -6,41 +6,37 @@ // // -import Node +import Vapor -open class OrderReturnRoutes { - let client: StripeClient +public protocol OrderReturnRoutes { + associatedtype OR: OrderReturn + associatedtype L: List - init(client: StripeClient) { - self.client = client + func retrieve(order: String) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future +} + +public struct StripeOrderReturnRoutes: OrderReturnRoutes { + private let request: StripeRequest + + init(request: StripeRequest) { + self.request = request } - /** - Retrieve an order return - Retrieves the returned order by the given id - - - parameter orderReturnId: The ID of the desired return order. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func retrieve(orderReturn orderReturnId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .orderReturns(orderReturnId), query: [:], body: nil, headers: nil) + /// Retrieve an order return + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_order_return) + public func retrieve(order: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.orderReturns(order).endpoint) } - /** - List all order returns - Returns a list of your order returns. - - - 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 + /// List all order returns + /// [Learn More →](https://stripe.com/docs/api/curl#list_order_returns) + public func listAll(filter: [String : Any]?) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters } - return try StripeRequest(client: self.client, method: .get, route: .orderReturn, query: query, body: nil, headers: nil) + + return try request.send(method: .GET, path: StripeAPIEndpoint.orderReturn.endpoint, query: queryParams) } } diff --git a/Sources/Stripe/API/Routes/OrderRoutes.swift b/Sources/Stripe/API/Routes/OrderRoutes.swift index c70f340..5d8362c 100644 --- a/Sources/Stripe/API/Routes/OrderRoutes.swift +++ b/Sources/Stripe/API/Routes/OrderRoutes.swift @@ -6,317 +6,172 @@ // // -import Node -import HTTP +import Vapor -open class OrderRoutes { - let client: StripeClient +public protocol OrderRoutes { + associatedtype O: Order + associatedtype L: List + associatedtype SH: Shipping - init(client: StripeClient) { - self.client = client + func create(currency: StripeCurrency, coupon: String?, customer: String?, email: String?, items: [[String: Any]]?, metadata: [String: String]?, shipping: SH?) throws -> Future + func retrieve(order: String) throws -> Future + func update(order: String, coupon: String?, metadata: [String: String]?, selectedShippingMethod: String?, shipping: SH?, status: String?) throws -> Future + func pay(order: String, customer: String?, source: Any?, applicationFee: Int?, connectAccount: String?, email: String?, metadata: [String: String]?) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future + func `return`(order: String, items: [[String: Any]]?) throws -> Future +} + +public struct StripeOrderRoutes: OrderRoutes { + private let request: StripeRequest + + init(request: StripeRequest) { + self.request = request } - /** - Creating a new order - Creates a new order object. - - - parameter currency: Three-letter ISO currency code, in lowercase. - - parameter coupon: A coupon code that represents a discount to be applied to this order. - Must be one-time duration and in same currency as the order. - - parameter customer: The ID of an existing customer to use for this order. If provided, - the customer email and shipping address will be used to create the order. - Subsequently, the customer will also be charged to pay the order. If email - or shipping are also provided, they will override the values retrieved from - the customer object. - - parameter email: The email address of the customer placing the order. - - parameter items: List of items constituting the order. - - parameter shipping: Shipping address for the order. Required if any of the SKUs are for products - that have shippable set to true. - - parameter metadata: A set of key/value pairs that you can attach to an order object. It can be - useful for storing additional information about the order in a structured format. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func create(currency: StripeCurrency, coupon: String? = nil, customer: String? = nil, email: String? = nil, items: Node? = nil, shipping: Node? = nil, metadata: Node? = nil) throws -> StripeRequest { - - var body = Node([:]) - - body["currency"] = Node(currency.rawValue) + /// Creating a new order + /// [Learn More →](https://stripe.com/docs/api/curl#create_order) + public func create(currency: StripeCurrency, + coupon: String? = nil, + customer: String? = nil, + email: String? = nil, + items: [[String : Any]]? = nil, + metadata: [String : String]? = nil, + shipping: ShippingLabel?) throws -> Future { + var body: [String: Any] = [:] if let coupon = coupon { - body["coupon"] = Node(coupon) + body["coupon"] = coupon } if let customer = customer { - body["customer"] = Node(customer) + body["customer"] = customer } if let email = email { - body["email"] = Node(email) + body["email"] = email } - if let items = items?.array { - - for(index, item) in items.enumerated() { - body["items[\(index)]"] = Node(item) + if let items = items { + for i in 0.. item which you can then use to convert to the corresponding node - */ - public func retrieve(order orderId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .orders(orderId), query: [:], body: nil, headers: nil) + /// Retrieve a new order + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_order) + public func retrieve(order: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.orders(order).endpoint) } - /** - Update an order - Updates the specific order by setting the values of the parameters passed. Any parameters not - provided will be left unchanged. This request accepts only the metadata, and status as arguments. - - - parameter orderId: The ID of the order to update. - - parameter coupon: A coupon code that represents a discount to be applied to this - order. Must be one-time duration and in same currency as the order. - - parameter selectedShippingMethod: The shipping method to select for fulfilling this order. - If specified, must be one of the ids of a shipping method in the - shipping_methods array. If specified, will overwrite the existing - selected shipping method, updating items as necessary. - - parameter shippingInformation: Tracking information once the order has been fulfilled. - - parameter status: Current order status. One of created, paid, canceled, fulfilled, - or returned. - - parameter metadata: A set of key/value pairs that you can attach to an order object. - It can be useful for storing additional information about the - order in a structured format. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func update(order orderId: String, coupon: String? = nil, selectedShippingMethod: String? = nil, shippingInformation: Node? = nil, status: OrderStatus? = nil, metadata: Node? = nil) throws -> StripeRequest { - - var body = Node([:]) + /// Update an order + /// [Learn More →](https://stripe.com/docs/api/curl#update_order) + public func update(order: String, + coupon: String? = nil, + metadata: [String : String]? = nil, + selectedShippingMethod: String? = nil, + shipping: ShippingLabel? = nil, + status: String? = nil) throws -> Future { + var body: [String: Any] = [:] if let coupon = coupon { - body["coupon"] = Node(coupon) + body["coupon"] = coupon } - if let shippingMethod = selectedShippingMethod { - body["selected_shipping_method"] = Node(shippingMethod) + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - - 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 selectedShippingMethod = selectedShippingMethod { + body["selected_shipping_method"] = selectedShippingMethod } - if let status = status { - body["status"] = Node(status.rawValue) + if let shipping = shipping { + try shipping.toEncodedDictionary().forEach { body["shipping[\($0)]"] = $1 } } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let status = status { + body["status"] = status } - return try StripeRequest(client: self.client, method: .post, route: .orders(orderId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .POST, path: StripeAPIEndpoint.orders(order).endpoint, body: body.queryParameters) } - /** - Pay an order - Pay an order by providing a source to create a payment. - - - parameter orderId: The ID of the order to pay. - - parameter customer: The ID of an existing customer that will be charged in this request. - - parameter source: A payment source to be charged, such as a credit card. If you also pass a - customer ID, the source must be the ID of a source belonging to the customer - (e.g., a saved card). Otherwise, if you do not pass a customer ID, the source - you provide must either be a token, like the ones returned by Stripe.js, or a - dictionary containing a user's credit card details, with the options described - below. Although not all information is required, the extra info helps prevent fraud. - - parameter applicationFee: A fee in cents that will be applied to the order and transferred to the - application owner's Stripe account. To use an application fee, the request - must be made on behalf of another account, using the Stripe-Account header - or OAuth key. For more information, see the application fees documentation. - - parameter email: The email address of the customer placing the order. If a customer is specified, - that customer's email address will be used. - - parameter metadata: A set of key/value pairs that you can attach to an order object. It can be useful - for storing additional information about the order in a structured format. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func pay(order orderId: String, customer: String? = nil, source: Node? = nil, applicationFee: Int? = nil, email: String? = nil, metadata: Node? = nil) throws -> StripeRequest { - var body = Node([:]) + /// Pay an order + /// [Learn More →](https://stripe.com/docs/api/curl#pay_order) + public func pay(order: String, + customer: String? = nil, + source: Any? = nil, + applicationFee: Int? = nil, + connectAccount: String? = nil, + email: String? = nil, + metadata: [String : String]? = nil) throws -> Future { + var body: [String: Any] = [:] + var headers: HTTPHeaders = [:] if let customer = customer { - body["customer"] = Node(customer) + body["customer"] = 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 as? String { + body["source"] = source } - if let source = source?.string { - body["source"] = Node(source) + if let source = source as? [String: Any] { + source.forEach { body["source[\($0)]"] = $1 } } - if let appFee = applicationFee { - body["application_fee"] = Node(appFee) + if let applicationfee = applicationFee { + body["application_fee"] = applicationfee } + if let connectAccount = connectAccount { + headers.add(name: .stripeAccount, value: connectAccount) + } + if let email = email { - body["email"] = Node(email) + body["email"] = email } - - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - - return try StripeRequest(client: self.client, method: .post, route: .ordersPay(orderId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + + return try request.send(method: .POST, path: StripeAPIEndpoint.ordersPay(order).endpoint, body: body.queryParameters, headers: headers) } - /** - List all orders - Returns a list of your orders. The orders are returned sorted by creation date, with the most - recently created orders 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 + /// List all orders + /// [Learn More →](https://stripe.com/docs/api/curl#list_orders) + public func listAll(filter: [String : Any]? = nil) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters } - return try StripeRequest(client: self.client, method: .get, route: .order, query: query, body: nil, headers: nil) - } - - /** - Return an order - Return all or part of an order. The order must have a status of paid or fulfilled before it can - be returned. Once all items have been returned, the order will become canceled or returned depending - on which status the order started in. - - - parameter orderId: The ID of the order to return - - parameter items: List of items to return. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func `return`(order orderId: String, items: Node? = nil) throws -> StripeRequest { - var body = Node([:]) + return try request.send(method: .GET, path: StripeAPIEndpoint.order.endpoint, query: queryParams) + } + + /// Return an order + /// [Learn More →](https://stripe.com/docs/api/curl#return_order) + public func `return`(order: String, items: [[String : Any]]? = nil) throws -> Future { + var body: [String: Any] = [:] - if let items = items?.array { - - for(index, item) in items.enumerated() { - body["items[\(index)]"] = Node(item) + if let items = items { + for i in 0.. Future

+ func retrieve(plan: String) throws -> Future

+ func update(plan: String, metadata: [String: String]?, nickname: String?, product: String?) throws -> Future

+ func delete(plan: String) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future +} + +public struct StripePlanRoutes: PlanRoutes { + private let request: StripeRequest + + init(request: StripeRequest) { + self.request = request } - /** - Create a plan - Creates a new plan object. - - - parameter id: Unique string of your choice that will be used to identify this plan when subscribing a customer. - This could be an identifier like “gold” or a primary key from your own database. - - - parameter amount: A positive integer in cents (or 0 for a free plan) representing how much to charge (on a recurring basis). - - - parameter currency: The currency in which the charge will be under. (required if amount_off passed). - - - parameter interval: Specifies billing frequency. Either day, week, month or year. - - - parameter name: Name of the plan, to be displayed on invoices and in the web interface. - - - parameter intervalCount: The number of intervals between each subscription billing. - - - parameter statementDescriptor: An arbitrary string to be displayed on your customer’s credit card statement. - This may be up to 22 characters. - - - parameter trialPeriodDays: Specifies a trial period in (an integer number of) days. - - - parameter metaData: A set of key/value pairs that you can attach to a plan object. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node. - */ - public func create(id: String, amount: Int, currency: StripeCurrency, interval: StripeInterval, name: String, intervalCount: Int? = nil, statementDescriptor: String? = nil, trialPeriodDays: Int? = nil, metadata: Node? = nil) throws -> StripeRequest { - var body = Node([:]) - - body["id"] = Node(id) - - body["amount"] = Node(amount) - - body["currency"] = Node(currency.rawValue) - - body["interval"] = Node(interval.rawValue) - - body["name"] = Node(name) + /// Create a plan + /// [Learn More →](https://stripe.com/docs/api/curl#create_plan) + public func create(id: String? = nil, + currency: StripeCurrency, + interval: StripePlanInterval, + product: String, + amount: Int? = nil, + intervalCount: Int? = nil, + metadata: [String : String]? = nil, + nickname: String? = nil) throws -> Future { + var body: [String: Any] = [:] + + body["currency"] = currency.rawValue + body["interval"] = interval.rawValue + body["product"] = product + + if let id = id { + body["id"] = id + } - if let intervalCount = intervalCount { - body["interval_count"] = Node(intervalCount) + if let amount = amount { + body["amount"] = amount } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let intervalCount = intervalCount { + body["interval_count"] = intervalCount } - if let statementDescriptor = statementDescriptor { - body["statement_descriptor"] = Node(statementDescriptor) + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - if let trialPeriodDays = trialPeriodDays { - body["trial_period_days"] = Node(trialPeriodDays) + if let nickname = nickname { + body["nickname"] = nickname } - return try StripeRequest(client: self.client, method: .post, route: .plans, query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .POST, path: StripeAPIEndpoint.plans.endpoint, body: body.queryParameters) } - /** - Retrieve a plan - Retrieves the plan with the given ID. - - - parameter planId: The ID of the desired plan. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func retrieve(plan planId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .plan(planId), query: [:], body: nil, headers: nil) + /// Retrieve a plan + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_plan) + public func retrieve(plan: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.plan(plan).endpoint) } - /** - Update a plan - Updates the name or other attributes of a plan. Other plan details (price, interval, etc.) are, by design, not editable. - - - parameter name: Name of the plan, to be displayed on invoices and in the web interface. - - - parameter statementDescriptor: An arbitrary string to be displayed on your customer’s credit card statement. - - - parameter trialPeriodDays: Specifies a trial period in (an integer number of) days. - - - parameter metaData: A set of key/value pairs that you can attach to a plan object. - - - parameter planId: The identifier of the plan to be updated. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func update(name: String? = nil, statementDescriptor: String? = nil, trialPeriodDays: Int? = nil, metadata: Node? = nil, forPlanId planId: String) throws -> StripeRequest { - var body = Node([:]) - - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } - } - - if let name = name { - body["name"] = Node(name) + /// Update a plan + /// [Learn More →](https://stripe.com/docs/api/curl#update_plan) + public func update(plan: String, + metadata: [String : String]? = nil, + nickname: String? = nil, + product: String? = nil) throws -> Future { + var body: [String: Any] = [:] + + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - - if let statementDescriptor = statementDescriptor { - body["statement_descriptor"] = Node(statementDescriptor) + + if let nickname = nickname { + body["nickname"] = nickname } - if let trialPeriodDays = trialPeriodDays { - body["trial_period_days"] = Node(trialPeriodDays) + if let product = product { + body["product"] = product } - return try StripeRequest(client: self.client, method: .post, route: .plan(planId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .POST, path: StripeAPIEndpoint.plan(plan).endpoint, body: body.queryParameters) } - /** - Delete a plan - Deleting a plan does not affect any current subscribers to the plan; it merely means that new subscribers can’t be added to that plan. - - - parameter couponId: The identifier of the plan to be deleted. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func delete(plan planId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .delete, route: .plan(planId), query: [:], body: nil, headers: nil) + /// Delete a plan + /// [Learn More →](https://stripe.com/docs/api/curl#delete_plan) + public func delete(plan: String) throws -> Future { + return try request.send(method: .DELETE, path: StripeAPIEndpoint.plan(plan).endpoint) } - /** - List all plans - Returns a list of your plans. The plans are returned sorted by creation date, with the - most recent plans 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 + /// List all plans + /// [Learn More →](https://stripe.com/docs/api/curl#list_plans) + public func listAll(filter: [String : Any]? = nil) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters } - return try StripeRequest(client: self.client, method: .get, route: .plans, query: query, body: nil, headers: nil) + return try request.send(method: .GET, path: StripeAPIEndpoint.plans.endpoint, query: queryParams) } } diff --git a/Sources/Stripe/API/Routes/ProductRoutes.swift b/Sources/Stripe/API/Routes/ProductRoutes.swift index d9bff56..6a1f7dc 100644 --- a/Sources/Stripe/API/Routes/ProductRoutes.swift +++ b/Sources/Stripe/API/Routes/ProductRoutes.swift @@ -6,228 +6,201 @@ // // -import Node -import HTTP +import Vapor -open class ProductRoutes { - let client: StripeClient +public protocol ProductRoutes { + associatedtype PR: Product + associatedtype DO: DeletedObject + associatedtype L: List + associatedtype PD: PackageDimensions - init(client: StripeClient) { - self.client = client - } - - /** - Create a product - Creates a new product object. - - - parameter name: The product’s name, meant to be displayable to the customer. - - parameter id: The identifier for the product. Must be unique. If not provided, - an identifier will be randomly generated. - - parameter active: Whether or not the product is currently available for purchase. - Defaults to true. - - parameter attributes: A list of up to 5 alphanumeric attributes that each SKU can provide - values for (e.g. ["color", "size"]). - - parameter caption: A short one-line description of the product, meant to be displayable - to the customer. - - parameter deactivateOn: An array of Connect application names or identifiers that should not - be able to order the SKUs for this product. - - parameter description: The product’s description, meant to be displayable to the customer. - - parameter images: A list of up to 8 URLs of images for this product, meant to be displayable - to the customer. - - parameter packageDimensions: The dimensions of this product for shipping purposes. A SKU associated with - this product can override this value by having its own package_dimensions. - - parameter shippable: Whether this product is shipped (i.e. physical goods). Defaults to true. - - parameter url: A URL of a publicly-accessible webpage for this product. - - parameter metadata: A set of key/value pairs that you can attach to a product object. It can be - useful for storing additional information about the product in a structured format. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func create(name: String, id: String?, active: Bool? = nil, attributes: Node? = nil, caption: String? = nil, deactivateOn: Node? = nil, description: String? = nil, images: Node? = nil, packageDimensions: Node? = nil, shippable: Bool? = nil, url: String? = nil, metadata: Node? = nil) throws -> StripeRequest { + func create(id: String?, name: String, type: String, active: Bool?, attributes: [String]?, caption: String?, deactivateOn: [String]?, description: String?, images: [String]?, metadata: [String: String]?, packageDimensions: PD?, shippable: Bool?, statementDescriptor: String?, url: String?) throws -> Future + func retrieve(id: String) throws -> Future + func update(product: String, active: Bool?, attributes: [String]?, caption: String?, deactivateOn: [String]?, description: String?, images: [String]?, metadata: [String: String]?, name: String?, packageDimensions: PD?, shippable: Bool?, statementDescriptor: String?, url: String?) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future + func delete(id: String) throws -> Future +} + +public struct StripeProductRoutes: ProductRoutes { + private let request: StripeRequest - var body = Node([:]) - - body["name"] = Node(name) - + init(request: StripeRequest) { + self.request = request + } + + /// Create a product + /// [Learn More →](https://stripe.com/docs/api/curl#create_product) + public func create(id: String? = nil, + name: String, + type: String, + active: Bool? = nil, + attributes: [String]? = nil, + caption: String? = nil, + deactivateOn: [String]? = nil, + description: String? = nil, + images: [String]? = nil, + metadata: [String : String]? = nil, + packageDimensions: StripePackageDimensions? = nil, + shippable: Bool? = nil, + statementDescriptor: String? = nil, + url: String? = nil) throws -> Future { + var body: [String: Any] = [:] + + body["name"] = name + + body["type"] = type + if let id = id { - body["id"] = Node(id) + body["id"] = id } - + if let active = active { - body["active"] = Node(active) + body["active"] = active } - if let attributes = attributes?.array { - body["attributes"] = Node(attributes) + if let attributes = attributes { + for i in 0.. item which you can then use to convert to the corresponding node - */ - public func retrieve(product productId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .products(productId), query: [:], body: nil, headers: nil) + /// Retrieve a product + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_product) + public func retrieve(id: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.products(id).endpoint) } - /** - Update a product - Updates the specific product by setting the values of the parameters passed. Any parameters - not provided will be left unchanged. - Note that a product’s attributes are not editable. Instead, you would need to deactivate the - existing product and create a new one with the new attribute values. - - - parameter productId: The ID of the product to update - - parameter active: Whether or not the product is available for purchase. - - parameter attributes: A list of up to 5 alphanumeric attributes that each SKU can - provide values for (e.g. ["color", "size"]). If a value for attributes - is specified, the list specified will replace the existing attributes - list on this product. Any attributes not present after the update will - be deleted from the SKUs for this product. This will be unset if you - POST an empty value. - - parameter caption: A short one-line description of the product, meant to be displayable - to the customer. - - parameter deactivateOn: An array of Connect application names or identifiers that should not be - able to order the SKUs for this product. This will be unset if you POST - an empty value. - - parameter description: The product’s description, meant to be displayable to the customer. - - parameter images: A list of up to 8 URLs of images for this product, meant to be displayable - to the customer. This will be unset if you POST an empty value. - - parameter name: The product’s name, meant to be displayable to the customer. - - parameter packageDimensions: The dimensions of this product for shipping purposes. A SKU associated with - this product can override this value by having its own package_dimensions. - - parameter shippable: Whether this product is shipped (i.e. physical goods). Defaults to true. - - parameter url: A URL of a publicly-accessible webpage for this product. - - parameter metadata: A set of key/value pairs that you can attach to a product object. It can be - useful for storing additional information about the product in a structured format. - You can unset individual keys if you POST an empty value for that key. - You can clear all keys if you POST an empty value for metadata. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func update(product productId: String, active: Bool? = nil, attributes: Node? = nil, caption: String? = nil, deactivateOn: Node? = nil, description: String? = nil, images: Node? = nil, name: String? = nil, packageDimensions: Node? = nil, shippable: Bool? = nil, url: String? = nil, metadata: Node? = nil) throws -> StripeRequest { - - var body = Node([:]) - - if let name = name { - body["name"] = Node(name) - } + /// Update a product + /// [Learn More →](https://stripe.com/docs/api/curl#update_product) + public func update(product: String, + active: Bool? = nil, + attributes: [String]? = nil, + caption: String? = nil, + deactivateOn: [String]? = nil, + description: String? = nil, + images: [String]? = nil, + metadata: [String : String]? = nil, + name: String? = nil, + packageDimensions: StripePackageDimensions? = nil, + shippable: Bool? = nil, + statementDescriptor: String? = nil, + url: String? = nil) throws -> Future { + var body: [String: Any] = [:] if let active = active { - body["active"] = Node(active) + body["active"] = active } - if let attributes = attributes?.array { - body["attributes"] = Node(attributes) + if let attributes = attributes { + for i in 0.. 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: .product, query: query, body: nil, headers: nil) + /// List all products + /// [Learn More →](https://stripe.com/docs/api/curl#list_products) + public func listAll(filter: [String : Any]? = nil) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters + } + + return try request.send(method: .GET, path: StripeAPIEndpoint.product.endpoint, query: queryParams) } - /** - Delete a product - Delete a product. Deleting a product is only possible if it has no SKUs associated with it. - - - parameter productId: The ID of the product to delete. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func delete(product productId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .delete, route: .products(productId), query: [:], body: nil, headers: nil) + /// Delete a product + /// [Learn More →](https://stripe.com/docs/api/curl#delete_product) + public func delete(id: String) throws -> Future { + return try request.send(method: .DELETE, path: StripeAPIEndpoint.products(id).endpoint) } } diff --git a/Sources/Stripe/API/Routes/RefundRoutes.swift b/Sources/Stripe/API/Routes/RefundRoutes.swift index 7f0dc3a..f4620c7 100644 --- a/Sources/Stripe/API/Routes/RefundRoutes.swift +++ b/Sources/Stripe/API/Routes/RefundRoutes.swift @@ -6,153 +6,86 @@ // // -import Node -import HTTP +import Vapor -open class RefundRoutes { +public protocol RefundRoutes { + associatedtype R: Refund + associatedtype L: List - let client: StripeClient + func create(charge: String, amount: Int?, metadata: [String: String]?, reason: RefundReason?, refundApplicationFee: Bool?, reverseTransfer: Bool?) throws -> Future + func retrieve(refund: String) throws -> Future + func update(refund: String, metadata: [String: String]?) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future +} + +public struct StripeRefundRoutes: RefundRoutes { + private let request: StripeRequest - init(client: StripeClient) { - self.client = client + init(request: StripeRequest) { + self.request = request } - - /** - Create a refund - When you create a new refund, you must specify a charge to create it on. - Creating a new refund will refund a charge that has previously been created but not yet refunded. - Funds will be refunded to the credit or debit card that was originally charged. The fees you were - originally charged are also refunded. - You can optionally refund only part of a charge. You can do so as many times as you wish until the - entire charge has been refunded. Once entirely refunded, a charge can't be refunded again. This - method will return an error when called on an already-refunded charge, or when trying to refund more - money than is left on a charge. - - - parameter charge: The identifier of the charge to refund. - - - parameter amount: A positive integer in cents representing how much of this charge to refund. - Can only refund up to the unrefunded amount remaining of the charge. - - - parameter reason: String indicating the reason for the refund. If set, possible values - are duplicate, fraudulent, and requestedByCustomer. Specifying fraudulent - as the reason when you believe the charge to be fraudulent will help us - improve our fraud detection algorithms. - - - parameter refundApplicationFee: Boolean indicating whether the application fee should be refunded when - refunding this charge. If a full charge refund is given, the full - application fee will be refunded. Else, the application fee will be - refunded with an amount proportional to the amount of the charge refunded. - An application fee can only be refunded by the application that created - the charge. - - - parameter reverseTransfer: Boolean indicating whether the transfer should be reversed when refunding this - charge. The transfer will be reversed for the same amount being refunded - (either the entire or partial amount). - A transfer can only be reversed by the application that created the charge. - - - parameter metadata: A set of key/value pairs that you can attach to a refund object. It can be - useful for storing additional information about the refund in a structured format. - You can unset individual keys if you POST an empty value for that key. You can clear - all keys if you POST an empty value for metadata. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func createRefund(charge: String, amount: Int? = nil, reason: RefundReason? = nil, refundApplicationFee: Bool? = nil, reverseTransfer: Bool? = nil, metadata: Node? = nil, inConnectAccount account: String? = nil) throws -> StripeRequest { - var body = Node([:]) + /// Create a refund + /// [Learn More →](https://stripe.com/docs/api/curl#create_refund) + public func create(charge: String, + amount: Int? = nil, + metadata: [String : String]? = nil, + reason: RefundReason? = nil, + refundApplicationFee: Bool? = nil, + reverseTransfer: Bool? = nil) throws -> Future { + var body: [String: Any] = [:] - body["charge"] = Node(charge) + body["charge"] = charge if let amount = amount { - body["amount"] = Node(amount) + body["amount"] = amount } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - - if let reason = reason { - body["reason"] = Node(reason.rawValue) + + if let refundReason = reason { + body["reason"] = refundReason.rawValue } if let refundApplicationFee = refundApplicationFee { - body["refund_application_fee"] = Node(refundApplicationFee) + body["refund_application_fee"] = refundApplicationFee } if let reverseTransfer = reverseTransfer { - body["reverse_transfer"] = Node(reverseTransfer) + body["reverse_transfer"] = reverseTransfer } - var headers: [HeaderKey: String]? - - // Check if we have an account to set it to - if let account = account { - headers = [StripeHeader.Account : account] - } - - return try StripeRequest(client: self.client, method: .post, route: .refunds, body: Body.data(body.formURLEncoded()), headers: headers) - } - - /** - Retrieve a refund - Retrieves the details of an existing refund. - - - parameter refundId: The ID of refund to retrieve. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func retrieve(refund refundId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .refund(refundId)) + return try request.send(method: .POST, path: StripeAPIEndpoint.refunds.endpoint, body: body.queryParameters) } - /** - Update a refund - Updates the specified refund by setting the values of the parameters passed. Any parameters not provided will - be left unchanged. - - - parameter metadata: A set of key/value pairs that you can attach to a refund object. It can be useful for - storing additional information about the refund in a structured format. You can unset - individual keys if you POST an empty value for that key. You can clear all keys if you - POST an empty value for metadata. - - - parameter refund: The ID of the refund to update. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func update(metadata: Node? = nil, refund refundId: String) throws -> StripeRequest { - var body = Node([:]) - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } - } - return try StripeRequest(client: self.client, method: .post, route: .refund(refundId), body: Body.data(body.formURLEncoded())) + /// Retrieve a refund + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_refund) + public func retrieve(refund: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.refund(refund).endpoint) } - /** - Returns a list of all refunds you’ve previously created. The refunds are returned in sorted order, with the most - recent refunds appearing first. For convenience, the 10 most recent refunds are always available by default on - the charge object. - - - parameter byChargeId: Only return refunds for the charge specified by this charge ID. - - - 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(byChargeId charge: String? = nil, filter: StripeFilter? = nil) throws -> StripeRequest { - var query = [String : NodeRepresentable]() + /// Update a refund + /// [Learn More →](https://stripe.com/docs/api/curl#update_refund) + public func update(refund: String, metadata: [String : String]? = nil) throws -> Future { + var body: [String: Any] = [:] - if let data = try filter?.createQuery() { - query = data + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - if let charge = charge { - query["charge"] = Node(charge) + return try request.send(method: .POST, path: StripeAPIEndpoint.refund(refund).endpoint, body: body.queryParameters) + } + + /// List all refunds + /// [Learn More →](https://stripe.com/docs/api/curl#list_refunds) + public func listAll(filter: [String : Any]? = nil) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters } - return try StripeRequest(client: self.client, method: .get, route: .refunds, query: query) + return try request.send(method: .GET, path: StripeAPIEndpoint.refunds.endpoint, query: queryParams) } } diff --git a/Sources/Stripe/API/Routes/SKURoutes.swift b/Sources/Stripe/API/Routes/SKURoutes.swift index 507f9c1..095f86c 100644 --- a/Sources/Stripe/API/Routes/SKURoutes.swift +++ b/Sources/Stripe/API/Routes/SKURoutes.swift @@ -6,206 +6,148 @@ // // -import Node -import HTTP +import Vapor -open class SKURoutes { - let client: StripeClient +public protocol SKURoutes { + associatedtype SK: SKU + associatedtype DO: DeletedObject + associatedtype L: List + associatedtype IN: Inventory + associatedtype PD: PackageDimensions - init(client: StripeClient) { - self.client = client - } + func create(id: String?, currency: StripeCurrency, inventory: IN, price: Int, product: String, active: Bool?, attributes: [String]?, image: String?, metadata: [String: String]?, packageDimensions: PD?) throws -> Future + func retrieve(id: String) throws -> Future + func update(sku: String, active: Bool?, attributes: [String]?, currency: StripeCurrency?, image: String?, inventory: IN?, metadata: [String: String]?, packageDimensions: PD?, price: Int?, product: String?) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future + func delete(sku: String) throws -> Future +} + +public struct StripeSKURoutes: SKURoutes { + private let request: StripeRequest - /** - Create a SKU - Creates a new SKU associated with a product. - - - parameter currency: Three-letter ISO currency code, in lowercase. - - parameter inventory: Description of the SKU’s inventory. - - parameter price: The cost of the item as a nonnegative integer in the smallest - currency unit (that is, 100 cents to charge $1.00, or 100 to - charge ¥100, Japanese Yen being a zero-decimal currency). - - parameter product: The ID of the product this SKU is associated with. - - parameter id: The ID of the product this SKU is associated with. - - parameter active: Whether or not the SKU is available for purchase. Default to true. - - parameter attributes: A dictionary of attributes and values for the attributes defined by - the product. If, for example, a product’s attributes are ["size", "gender"], - a valid SKU has the following dictionary of attributes: - {"size": "Medium", "gender": "Unisex"}. - - parameter image: The URL of an image for this SKU, meant to be displayable to the customer. - - parameter packageDimensions: The dimensions of this SKU for shipping purposes. - - parameter metadata: A set of key/value pairs that you can attach to a SKU object. It can be - useful for storing additional information about the SKU in a structured format. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func create(currency: StripeCurrency, inventory: Node, price: Int, product: String, id: String? = nil, active: Bool? = nil, attributes: Node? = nil, image: String? = nil, packageDimensions: Node? = nil, 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) + init(request: StripeRequest) { + self.request = request + } + + /// Create a SKU + /// [Learn More →](https://stripe.com/docs/api/curl#create_sku) + public func create(id: String? = nil, + currency: StripeCurrency, + inventory: StripeInventory, + price: Int, + product: String, + active: Bool? = nil, + attributes: [String]? = nil, + image: String? = nil, + metadata: [String : String]? = nil, + packageDimensions: StripePackageDimensions? = nil) throws -> Future { + var body: [String: Any] = [:] + + body["currency"] = currency.rawValue + try inventory.toEncodedDictionary().forEach { body["inventory[\($0)]"] = $1 } + body["price"] = price + body["product"] = product if let active = active { - body["active"] = Node(active) + body["active"] = active } - if let attributes = attributes?.object { - for (key,value) in attributes { - body["attributes[\(key)]"] = value + if let attributes = attributes { + for i in 0.. item which you can then use to convert to the corresponding node - */ - public func retrieve(sku skuId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .skus(skuId), query: [:], body: nil, headers: nil) + /// Retrieve a SKU + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_sku) + public func retrieve(id: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.skus(id).endpoint) } - /** - Update a SKU - Updates the specific SKU by setting the values of the parameters passed. Any parameters not - provided will be left unchanged. - Note that a SKU’s attributes are not editable. Instead, you would need to deactivate the - existing SKU and create a new one with the new attribute values. - - - parameter skuId: The identifier of the SKU to be updated. - - parameter active: Whether or not this SKU is available for purchase. - - parameter attributes: A dictionary of attributes and values for the attributes - defined by the product. When specified, attributes will - partially update the existing attributes dictionary on the - product, with the postcondition that a value must be present - for each attribute key on the product, and that all SKUs for - the product must have unique sets of attributes. - - parameter currency: hree-letter ISO currency code, in lowercase. - - parameter image: The URL of an image for this SKU, meant to be displayable to - the customer. This will be unset if you POST an empty value. - - parameter inventory: Description of the SKU’s inventory. - - parameter packageDimensions: The dimensions of this SKU for shipping purposes. - - parameter price: The cost of the item as a positive integer in the smallest - currency unit (that is, 100 cents to charge $1.00, or 100 - to charge ¥100, Japanese Yen being a zero-decimal currency). - - parameter product: The ID of the product that this SKU should belong to. The - product must exist and have the same set of attribute names - as the SKU’s current product. - - parameter metadata: A set of key/value pairs that you can attach to a SKU object. - It can be useful for storing additional information about the - SKU in a structured format. You can unset individual keys if - you POST an empty value for that key. You can clear all keys - if you POST an empty value for metadata. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func update(sku skuId: String, active: Bool? = nil, attributes: Node? = nil, currency: StripeCurrency? = nil, image: String? = nil, inventory: Node? = nil, packageDimensions: Node? = nil, price: Int? = nil, product: String? = nil, metadata: Node? = nil) throws -> StripeRequest { - var body = Node([:]) - + /// Update a SKU + /// [Learn More →](https://stripe.com/docs/api/curl#update_sku) + public func update(sku: String, + active: Bool? = nil, + attributes: [String]? = nil, + currency: StripeCurrency? = nil, + image: String? = nil, + inventory: StripeInventory? = nil, + metadata: [String : String]? = nil, + packageDimensions: StripePackageDimensions? = nil, + price: Int? = nil, + product: String? = nil) throws -> Future { + var body: [String: Any] = [:] if let active = active { - body["active"] = Node(active) + body["active"] = active } - if let attributes = attributes?.object { - for (key,value) in attributes { - body["attributes[\(key)]"] = value + if let attributes = attributes { + for i in 0.. Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters } - return try StripeRequest(client: self.client, method: .post, route: .skus(skuId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .GET, path: StripeAPIEndpoint.sku.endpoint, query: queryParams) } - /** - List all SKUs - Returns a list of your SKUs. The SKUs are returned sorted by creation date, with the most - recently created SKUs 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: .sku, query: query, body: nil, headers: nil) - } - - /** - Delete a SKU - Delete a SKU. Deleting a SKU is only possible until it has been used in an order. - - - parameter skuId: The identifier of the SKU to be deleted. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func delete(sku skuId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .delete, route: .skus(skuId), query: [:], body: nil, headers: nil) + /// Delete a SKU + /// [Learn More →](https://stripe.com/docs/api/curl#delete_sku) + public func delete(sku: String) throws -> Future { + return try request.send(method: .DELETE, path: StripeAPIEndpoint.skus(sku).endpoint) } } diff --git a/Sources/Stripe/API/Routes/SourceRoutes.swift b/Sources/Stripe/API/Routes/SourceRoutes.swift index 5bd916b..2160406 100644 --- a/Sources/Stripe/API/Routes/SourceRoutes.swift +++ b/Sources/Stripe/API/Routes/SourceRoutes.swift @@ -6,259 +6,117 @@ // // -import Node -import HTTP +import Vapor -open class SourceRoutes { +public protocol SourceRoutes { + associatedtype S: Source + associatedtype M: Mandate + associatedtype O: Owner - let client: StripeClient + func create(type: SourceType, amount: Int?, currency: StripeCurrency?, flow: Flow?, mandate: M?, metadata: [String: String]?, owner: O?, receiver: [String: String]?, redirect: [String: String]?, statementDescriptor: String?, token: String?, usage: String?) throws -> Future + func retrieve(source: String, clientSecret: String?) throws -> Future + func update(source: String, mandate: M?, metadata: [String: String]?, owner: O?) throws -> Future +} + +public struct StripeSourceRoutes: SourceRoutes { + private let request: StripeRequest - init(client: StripeClient) { - self.client = client + init(request: StripeRequest) { + self.request = request } - - /** - Create a Source - Creates a new customer object. - - - parameter type: The type of the source to create. - - - parameter amount: Amount associated with the source. This is the amount for which the source - will be chargeable once ready. - - - parameter currency: This is the currency for which the source will be chargeable once ready. - - - parameter flow: The authentication flow of the source to create. - - - parameter owner: Information about the owner of the payment instrument that may be used or - required by particular source types. - - - parameter redirectReturnUrl: The URL you provide to redirect the customer back to you after they - authenticated their payment. - - - parameter token: An optional token used to create the source. When passed, token properties - will override source parameters. - - - parameter usage: Either reusable or single_use. - - - parameter metadata: A set of key/value pairs that you can attach to a customer object. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - - public func createNewSource(sourceType: SourceType, source: Node, amount: Int? = nil, currency: StripeCurrency? = nil, flow: String? = nil, owner: Owner? = nil, redirectReturnUrl: String? = nil, token: String? = nil, usage: String? = nil, metadata: Node? = nil) throws -> StripeRequest { + + /// Create a source + /// [Learn More →](https://stripe.com/docs/api/curl#create_source) + public func create(type: SourceType, + amount: Int? = nil, + currency: StripeCurrency? = nil, + flow: Flow? = nil, + mandate: StripeMandate? = nil, + metadata: [String : String]? = nil, + owner: StripeOwner? = nil, + receiver: [String : String]? = nil, + redirect: [String : String]? = nil, + statementDescriptor: String? = nil, + token: String? = nil, + usage: String? = nil) throws -> Future { + var body: [String: Any] = [:] - var body = Node([:]) + body["type"] = type.rawValue - body["type"] = Node(sourceType.rawValue) + if let currency = currency { + body["currency"] = currency.rawValue + } - switch sourceType{ - - case .card: - if let source = source.object { - for (key,val) in source { - body["card[\(key)]"] = val - } - } - case .bitcoin: - if let source = source.object { - for (key,val) in source { - body["bitcoin[\(key)]"] = val - } - } - case .threeDSecure: - if let source = source.object { - for (key,val) in source { - body["three_d_secure[\(key)]"] = val - } - } - - case .bancontact: - if let source = source.object { - for (key,val) in source { - body["bancontact[\(key)]"] = val - } - } - case .giropay: - if let source = source.object { - for (key,val) in source { - body["giropay[\(key)]"] = val - } - } - case .ideal: - if let source = source.object { - for (key,val) in source { - body["ideal[\(key)]"] = val - } - } - case .sepaDebit: - if let source = source.object { - for (key,val) in source { - body["sepa_debit[\(key)]"] = val - } - } - case .sofort: - if let source = source.object { - for (key,val) in source { - body["sofort[\(key)]"] = val - } - } - default: - body[""] = "" + if let flow = flow { + body["flow"] = flow.rawValue } - if let amount = amount { - - body["amount"] = Node(amount) + if let mandate = mandate { + try mandate.toEncodedDictionary().forEach { body["mandate[\($0)]"] = $1 } } - if let currency = currency { - body["currency"] = Node(currency.rawValue) + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - if let flow = flow { - body["flow"] = Node(flow) + if let owner = owner { + try owner.toEncodedDictionary().forEach { body["owner[\($0)]"] = $1 } } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + if let receiver = receiver { + receiver.forEach { body["receiver[\($0)]"] = $1 } } - if let owner = owner { - - if let email = owner.email { - body["owner[email]"] = Node(email) - } - - if let name = owner.name { - body["owner[name]"] = Node(name) - } - - if let phone = owner.phone { - body["owner[phone]"] = Node(phone) - } - - if let address = owner.address { - - if let line1 = address.addressLine1 { - body["owner[address][line1]"] = Node(line1) - } - - if let city = address.city { - body["owner[address][city]"] = Node(city) - } - - if let country = address.country { - body["owner[address][country]"] = Node(country) - } - - if let postalCode = address.postalCode { - body["owner[address][postal_code]"] = Node(postalCode) - } - - if let state = address.state { - body["owner[address][state]"] = Node(state) - } - - if let line2 = address.addressLine2 { - body["owner[address][line2]"] = Node(line2) - } - } + if let redirect = redirect { + redirect.forEach { body["redirect[\($0)]"] = $1 } } - if let redirectReturnUrl = redirectReturnUrl { - body["redirect[return_url]"] = Node(redirectReturnUrl) + if let statementDescriptor = statementDescriptor { + body["statement_descriptor"] = statementDescriptor } if let token = token { - body["token"] = Node(token) + body["token"] = token } if let usage = usage { - body["usage"] = Node(usage) + body["usage"] = usage } - return try StripeRequest(client: self.client, method: .post, route: .sources, query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .POST, path: StripeAPIEndpoint.sources.endpoint, body: body.queryParameters) } - /** - Retrieve a source - Retrieves an existing source object. Supply the unique source ID from a source creation request and Stripe will return the corresponding up-to-date source object information. - - - parameter sourceId: The identifier of the source to be retrieved. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func retrieveSource(withId sourceId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .source(sourceId), query: [:], body: nil, headers: nil) + /// Retrieve a source + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_source) + public func retrieve(source: String, clientSecret: String?) throws -> Future { + var query = "" + if let clientSecret = clientSecret { + query = "client_secret=\(clientSecret)" + } + + return try request.send(method: .GET, path: StripeAPIEndpoint.source(source).endpoint, query: query) } - /** - Update a Source - Updates the specified source by setting the values of the parameters passed. Any parameters not provided will be left unchanged. - - - parameter metadata: A set of key/value pairs that you can attach to a source object. - - - parameter owner: Information about the owner of the payment instrument that may be used or required by particular - source types. - - - parameter sourceId: The identifier of the source to be updated. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node. - */ - - public func update(owner: Owner? = nil, metadata: Node? = nil, forSourceId sourceId: String) throws -> StripeRequest { - var body = Node([:]) + /// Update a source + /// [Learn More →](https://stripe.com/docs/api/curl#update_source) + public func update(source: String, + mandate: StripeMandate? = nil, + metadata: [String : String]? = nil, + owner: StripeOwner? = nil) throws -> Future { + var body: [String: Any] = [:] - if let owner = owner { - - if let email = owner.email { - body["owner[email]"] = Node(email) - } - - if let name = owner.name { - body["owner[name]"] = Node(name) - } - - if let phone = owner.phone { - body["owner[phone]"] = Node(phone) - } - - if let address = owner.address { - - if let line1 = address.addressLine1 { - body["owner[address][line1]"] = Node(line1) - } - - if let city = address.city { - body["owner[address][city]"] = Node(city) - } - - if let country = address.country { - body["owner[address][country]"] = Node(country) - } - - if let postalCode = address.postalCode { - body["owner[address][postal_code]"] = Node(postalCode) - } - - if let state = address.state { - body["owner[address][state]"] = Node(state) - } - - if let line2 = address.addressLine2 { - body["owner[address][line2]"] = Node(line2) - } - } + if let mandate = mandate { + try mandate.toEncodedDictionary().forEach { body["mandate[\($0)]"] = $1 } } - - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - return try StripeRequest(client: self.client, method: .post, route: .source(sourceId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + + if let owner = owner { + try owner.toEncodedDictionary().forEach { body["owner[\($0)]"] = $1 } + } + + return try request.send(method: .POST, path: StripeAPIEndpoint.source(source).endpoint, body: body.queryParameters) } } diff --git a/Sources/Stripe/API/Routes/SubscriptionItemRoutes.swift b/Sources/Stripe/API/Routes/SubscriptionItemRoutes.swift index 6aa3de3..b17825b 100644 --- a/Sources/Stripe/API/Routes/SubscriptionItemRoutes.swift +++ b/Sources/Stripe/API/Routes/SubscriptionItemRoutes.swift @@ -6,141 +6,124 @@ // // -import Node -import HTTP +import Vapor +import Foundation -open class SubscriptionItemRoutes { +public protocol SubscriptionItemRoutes { + associatedtype SI: SubscriptionItem + associatedtype L: List - let client: StripeClient + func create(plan: String, subscription: String, metadata: [String: String]?, prorate: Bool?, prorationDate: Date?, quantity: Int?) throws -> Future + func retrieve(item: String) throws -> Future + func update(item: String, metadata: [String: String]?, plan: String?, prorate: Bool?, prorationDate: Date?, quantity: Int?) throws -> Future + func delete(item: String, prorate: Bool?, prorationDate: Date?) throws -> Future + func listAll(subscription: String, filter: [String: Any]?) throws -> Future +} + +public struct StripeSubscriptionItemRoutes: SubscriptionItemRoutes { + private let request: StripeRequest - init(client: StripeClient) { - self.client = client + init(request: StripeRequest) { + self.request = request } - - /** - Create SubscriptionItem - - - parameter planId: The identifier of the plan to add to the subscription. - - - parameter prorate: Flag indicating whether to prorate switching plans during a billing cycle. - - - parameter prorationDate: If set, the proration will be calculated as though the subscription was updated at the given time. - - - parameter quantity: The quantity you’d like to apply to the subscription item you’re creating. - - - parameter subscriptionId: The identifier of the subscription to modify. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node. - */ - - public func create(planId: String, prorate: Bool, prorationDate: Date, quantity: Int, subscriptionId: String) throws -> StripeRequest { + + /// Create a subscription item + /// [Learn More →](https://stripe.com/docs/api/curl#create_subscription_item) + public func create(plan: String, + subscription: String, + metadata: [String : String]? = nil, + prorate: Bool? = nil, + prorationDate: Date? = nil, + quantity: Int? = nil) throws -> Future { + var body: [String: Any] = [:] - let body: [String: Any] = [ - "plan": planId, - "subscription": subscriptionId, - "prorate": prorate, - "proration_date": Int(prorationDate.timeIntervalSince1970), - "quantity": quantity - ] + body["plan"] = plan + body["subscription"] = subscription - let node = try Node(node: body) + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } + } + + if let prorate = prorate { + body["prorate"] = prorate + } - return try StripeRequest(client: self.client, method: .post, route: .subscriptionItem, query: [:], body: Body.data(node.formURLEncoded()), headers: nil) + if let prorationDate = prorationDate { + body["proration_date"] = Int(prorationDate.timeIntervalSince1970) + } + + if let quantity = quantity { + body["quantity"] = quantity + } + + return try request.send(method: .POST, path: StripeAPIEndpoint.subscriptionItem.endpoint, body: body.queryParameters) } - /** - Retrieve a subscriptionItem - Retrieves the invoice item with the given ID. - - - parameter subscriptionId: The identifier of the subscription item to retrieve. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func retrieve(subscriptionItem subscriptionItemId: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .subscriptionItems(subscriptionItemId), query: [:], body: nil, headers: nil) + /// Retrieve a subscription item + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_subscription_item) + public func retrieve(item: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.subscriptionItems(item).endpoint) } - /** - Update a subscription item - Updates the plan or quantity of an item on a current subscription. - - - parameter plan: The identifier of the new plan for this subscription item. - - - parameter prorate: Flag indicating whether to prorate switching plans during a billing cycle. - - - parameter prorationDate: If set, the proration will be calculated as though the subscription was updated at the given time. - - - parameter quantity: The quantity you’d like to apply to the subscription item you’re creating. - - - parameter subscriptionItemId: The identifier of the subscription item to modify. - */ - - public func update(plan: String? = nil, prorate: Bool? = nil, prorationDate: Date? = nil, quantity: Int? = nil, subscriptionItemId: String) throws -> StripeRequest { - var body = Node([:]) + /// Update a subscription item + /// [Learn More →](https://stripe.com/docs/api/curl#update_subscription_item) + public func update(item: String, + metadata: [String : String]? = nil, + plan: String? = nil, + prorate: Bool? = nil, + prorationDate: Date? = nil, + quantity: Int? = nil) throws -> Future { + var body: [String: Any] = [:] + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } + } + if let plan = plan { - body["plan"] = Node(plan) + body["plan"] = plan } - + if let prorate = prorate { - body["prorate"] = Node(prorate) + body["prorate"] = prorate } - if let prorationdate = prorationDate { - body["proration_date"] = Node(Int(prorationdate.timeIntervalSince1970)) + + if let prorationDate = prorationDate { + body["proration_date"] = Int(prorationDate.timeIntervalSince1970) } + if let quantity = quantity { - body["quantity"] = Node(quantity) + body["quantity"] = quantity } - - return try StripeRequest(client: self.client, method: .post, route: .subscriptionItems(subscriptionItemId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + + return try request.send(method: .POST, path: StripeAPIEndpoint.subscriptionItems(item).endpoint, body: body.queryParameters) } - /** - Delete a subscription item - Deletes an item from the subscription. - - - parameter subscriptionItemId: The identifier of the coupon to be deleted. - - - parameter prorate: Flag indicating whether to prorate switching plans during a billing cycle. - - - parameter prorationDate: If set, the proration will be calculated as though the subscription was updated at the given time. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - - public func delete(subscriptionItem subscriptionItemId: String, prorate: Bool? = nil, proprationDate: Date? = nil) throws -> StripeRequest { - - var body = Node([:]) - + /// Delete a subscription item + /// [Learn More →](https://stripe.com/docs/api/curl#delete_subscription_item) + public func delete(item: String, + prorate: Bool? = nil, + prorationDate: Date? = nil) throws -> Future { + var body: [String: Any] = [:] + if let prorate = prorate { - body["prorate"] = Node(prorate) - } - if let prorationdate = proprationDate { - body["proration_date"] = Node(Int(prorationdate.timeIntervalSince1970)) + body["prorate"] = prorate } - return try StripeRequest(client: self.client, method: .delete, route: .subscriptionItems(subscriptionItemId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) - } + if let prorationDate = prorationDate { + body["proration_date"] = Int(prorationDate.timeIntervalSince1970) + } - /** - List all subscription items - Returns a list of your subscription items for a given subscription. - - - 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 - */ + return try request.send(method: .DELETE, path: StripeAPIEndpoint.subscriptionItems(item).endpoint, body: body.queryParameters) + } - public func listAll(subscriptionId: String, filter: StripeFilter? = nil) throws -> StripeRequest { - var query = [String : NodeRepresentable]() - - if let data = try filter?.createQuery() - { - query = data + /// List all subscription items + /// [Learn More →](https://stripe.com/docs/api/curl#list_subscription_item) + public func listAll(subscription: String, filter: [String : Any]? = nil) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters } - query["subscription"] = Node(subscriptionId) - - return try StripeRequest(client: self.client, method: .get, route: .subscriptionItem, query: query, body: nil, headers: nil) + return try request.send(method: .GET, path: StripeAPIEndpoint.subscriptionItems(subscription).endpoint, query: queryParams) } } diff --git a/Sources/Stripe/API/Routes/SubscriptionRoutes.swift b/Sources/Stripe/API/Routes/SubscriptionRoutes.swift index 66ef9b7..544ac4e 100644 --- a/Sources/Stripe/API/Routes/SubscriptionRoutes.swift +++ b/Sources/Stripe/API/Routes/SubscriptionRoutes.swift @@ -6,257 +6,210 @@ // // -import HTTP -import Node - -open class SubscriptionRoutes { +import Vapor +import Foundation +// TODO: Support sources being different objects +public protocol SubscriptionRoutes { + associatedtype SB: Subscription + associatedtype L: List + associatedtype DO: DeletedObject - let client: StripeClient + func create(customer: String, applicationFeePercent: Decimal?, billing: String?, billingCycleAnchor: Date?, coupon: String?, daysUntilDue: Int?, items: [String: Any]?, metadata: [String: String]?, source: Any?, taxPercent: Decimal?, trialEnd: Any?, trialPeriodDays: Int?) throws -> Future + func retrieve(id: String) throws -> Future + func update(subscription: String, applicationFeePercent: Decimal?, billing: String?, billingCycleAnchor: String?, coupon: String?, daysUntilDue: Int?, items: [String: Any]?, metadata: [String: String]?, prorate: Bool?, prorationDate: Date?, source: Any?, taxPercent: Decimal?, trialEnd: Any?) throws -> Future + func cancel(subscription: String, atPeriodEnd: Bool?) throws -> Future + func listAll(filter: [String: Any]?) throws -> Future + func deleteDiscount(subscription: String) throws -> Future +} + +public struct StripeSubscriptionRoutes: SubscriptionRoutes { + private let request: StripeRequest - init(client: StripeClient) { - self.client = client + init(request: StripeRequest) { + self.request = request } - - /** - Create a subscription - Creates a new subscription on an existing customer. - - - parameter customerId: The identifier of the customer to subscribe. - - - parameter plan: The identifier of the plan to subscribe the customer to. - - - parameter applicationFeePercent: This represents the percentage of the subscription invoice subtotal that will be transferred to the application owner’s Stripe account. Must be a possitive integer betwee 0 and 100 - - - parameter coupon: The code of the coupon to apply to this subscription. - - - parameter items: List of subscription items, each with an attached plan. - - - parameter quantity: The quantity you’d like to apply to the subscription you’re creating. - - - parameter source: The source can either be a token, like the ones returned by Elements, or a dictionary containing a user’s credit card details - - - parameter taxPercent: A non-negative decimal between 0 and 100. - - - parameter trialEnd: Unix timestamp representing the end of the trial period the customer will get before being charged for the first time. - - - parameter trialPeriodDays: Integer representing the number of trial period days before the customer is charged for the first time. - - - parameter metadata: A set of key/value pairs that you can attach to a subscription object. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - - */ - - public func create(forCustomer customerId: String, plan: String? = nil, applicationFeePercent: Int? = nil, onAccount account: String? = nil, couponId: String? = nil, items: Node? = nil, quantity: Int? = nil, source: Node? = nil, taxPercent: Double? = nil, trialEnd: Date? = nil, trialPeriodDays: Int? = nil, metadata: Node? = nil) throws -> StripeRequest { + + /// Create a subscription + /// [Learn More →](https://stripe.com/docs/api/curl#create_subscription) + public func create(customer: String, + applicationFeePercent: Decimal? = nil, + billing: String? = nil, + billingCycleAnchor: Date? = nil, + coupon: String? = nil, + daysUntilDue: Int? = nil, + items: [String : Any]? = nil, + metadata: [String : String]? = nil, + source: Any? = nil, + taxPercent: Decimal? = nil, + trialEnd: Any? = nil, + trialPeriodDays: Int? = nil) throws -> Future { + var body: [String: Any] = [:] + + body["customer"] = customer - var headers: [HeaderKey : String]? - if let account = account { - headers = [ - StripeHeader.Account: account - ] + if let applicationFeePercent = applicationFeePercent { + body["application_fee_percent"] = applicationFeePercent } - var body = Node([:]) - body["customer"] = Node(customerId) + if let billing = billing { + body["billing"] = billing + } - if let plan = plan { - body["plan"] = Node(plan) + if let billingCycleAnchor = billingCycleAnchor { + body["billing_cycle_anchor"] = Int(billingCycleAnchor.timeIntervalSince1970) } - if let appFeePercent = applicationFeePercent { - body["application_fee_percent"] = Node(appFeePercent) + if let coupon = coupon { + body["coupon"] = coupon } - if let couponId = couponId { - body["coupon"] = Node(couponId) + if let daysUntilDue = daysUntilDue { + body["days_until_due"] = daysUntilDue } - if let items = items?.array { - - for(index, item) in items.enumerated() { - body["items[\(index)]"] = Node(item) - } + if let items = items { + items.forEach { body["items[\($0)]"] = $1 } } - if let quantity = quantity { - body["quantity"] = Node(quantity) + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } - /// source can either be a token or a dictionary - if let sourceToken = source?.string { - body["source"] = Node(sourceToken) + if let source = source as? String { + body["source"] = source } - /** - Since source is passed to us we assume it's in a dictionary format with correct keys/values set as per the Stripe API - So we just set the dictionary as the paramater because it's expecting that type anyway - https://stripe.com/docs/api/curl#create_subscription - */ - if let source = source?.object { - - body["source"] = Node(source) + + if let source = source as? [String: Any] { + source.forEach { body["source[\($0)]"] = $1 } } - if let tax = taxPercent { - body["tax_percent"] = Node(tax) + + if let taxPercent = taxPercent { + body["tax_percent"] = taxPercent } - if let trialEnd = trialEnd { - body["trial_end"] = Node(Int(trialEnd.timeIntervalSince1970)) + + if let trialEnd = trialEnd as? Date { + body["trial_end"] = Int(trialEnd.timeIntervalSince1970) } - if let trialDays = trialPeriodDays { - body["trial_period_days"] = Node(trialDays) + + if let trialEnd = trialEnd as? String { + body["trial_end"] = trialEnd } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + + if let trialPeriodDays = trialPeriodDays { + body["trial_period_days"] = trialPeriodDays } - return try StripeRequest(client: self.client, method: .post, route: .subscription, query: [:], body: Body.data(body.formURLEncoded()), headers: headers) + return try request.send(method: .POST, path: StripeAPIEndpoint.subscription.endpoint, body: body.queryParameters) } - /** - Retrieve a subscription - - - parameter subscriptionId: The identifier of the source to be retrieved. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func retrieve(subscription subscriptionId: String, onAccount account: String? = nil) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .get, route: .subscriptions(subscriptionId), query: [:], body: nil, headers: nil) + /// Retrieve a subscription + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_subscription) + public func retrieve(id: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.subscriptions(id).endpoint) } - /** - Update a subscription - - - parameter subscriptionId: The identifier of the subscription to update - - - parameter applicationFeePercent: This represents the percentage of the subscription invoice subtotal that will be transferred to the application owner’s Stripe account. - - - parameter coupon: The code of the coupon to apply to this subscription. - - - parameter items: List of subscription items, each with an attached plan. - - - parameter plan: The identifier of the plan to subscribe the customer to. - - - parameter prorate: Flag telling us whether to prorate switching plans during a billing cycle. - - - parameter prorationDate: If set, the proration will be calculated as though the subscription was updated at the given time. - - - parameter quantity: The quantity you’d like to apply to the subscription you’re creating. - - - parameter source: The source can either be a token, like the ones returned by Elements, or a dictionary containing a user’s credit card details - - - parameter taxPercent: A non-negative decimal between 0 and 100. - - - parameter trialEnd: Unix timestamp representing the end of the trial period the customer will get before being charged for the first time. - - - parameter metadata: A set of key/value pairs that you can attach to a subscription object. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - - */ - - public func update(subscription subscriptionId: String, applicationFeePercent: Double? = nil, couponId: String? = nil, items: Node? = nil, plan: String? = nil, prorate: Bool? = nil, quantity: Int? = nil, source: Node? = nil, taxPercent: Double? = nil, trialEnd: Date? = nil, onAccount account: String? = nil, metadata: Node? = nil) throws -> StripeRequest { - var body = Node([:]) + /// Update a subscription + /// [Learn More →](https://stripe.com/docs/api/curl#update_subscription) + public func update(subscription: String, + applicationFeePercent: Decimal? = nil, + billing: String? = nil, + billingCycleAnchor: String? = nil, + coupon: String? = nil, + daysUntilDue: Int? = nil, + items: [String : Any]? = nil, + metadata: [String : String]? = nil, + prorate: Bool? = nil, + prorationDate: Date? = nil, + source: Any? = nil, + taxPercent: Decimal? = nil, + trialEnd: Any? = nil) throws -> Future { + var body: [String: Any] = [:] + + if let applicationFeePercent = applicationFeePercent { + body["application_fee_percent"] = applicationFeePercent + } - if let plan = plan { - body["plan"] = Node(plan) + if let billing = billing { + body["billing"] = billing } - if let appFeePercent = applicationFeePercent { - body["application_fee_percent"] = Node(appFeePercent) + if let billingCycleAnchor = billingCycleAnchor { + body["billing_cycle_anchor"] = billingCycleAnchor } - if let couponId = couponId { - body["coupon"] = Node(couponId) + if let coupon = coupon { + body["coupon"] = coupon } - if let items = items?.array { - - for(index, item) in items.enumerated() { - body["items[\(index)]"] = Node(item) - } + if let daysUntilDue = daysUntilDue { + body["days_until_due"] = daysUntilDue + } + + if let items = items { + items.forEach { body["items[\($0)]"] = $1 } + } + + if let metadata = metadata { + metadata.forEach { body["metadata[\($0)]"] = $1 } } if let prorate = prorate { - body["prorate"] = Node(prorate) + body["prorate"] = prorate } - if let quantity = quantity { - body["quantity"] = Node(quantity) + if let prorationDate = prorationDate { + body["proration_date"] = Int(prorationDate.timeIntervalSince1970) } - /// source can either be a token or a dictionary - if let sourceToken = source?.string { - body["source"] = Node(sourceToken) + + if let source = source as? String { + body["source"] = source } - /** - Since source is passed to us we assume it's in a dictionary format with correct keys/values set as per the Stripe API - So we just set the dictionary as the paramater because it's expecting that type anyway - https://stripe.com/docs/api/curl#create_subscription - */ - if let source = source?.object { - - body["source"] = Node(source) + + if let source = source as? [String: Any] { + source.forEach { body["source[\($0)]"] = $1 } } - if let tax = taxPercent { - body["tax_percent"] = Node(tax) + + if let taxPercent = taxPercent { + body["tax_percent"] = taxPercent } - if let trialEnd = trialEnd { - body["trial_end"] = Node(Int(trialEnd.timeIntervalSince1970)) + + if let trialEnd = trialEnd as? Date { + body["trial_end"] = Int(trialEnd.timeIntervalSince1970) } - if let metadata = metadata?.object { - for (key, value) in metadata { - body["metadata[\(key)]"] = value - } + + if let trialEnd = trialEnd as? String { + body["trial_end"] = trialEnd } - return try StripeRequest(client: self.client, method: .post, route: .subscriptions(subscriptionId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) - } - - /** - Delete a subscription discount - Removes the currently applied discount on a subscription. - - - parameter subscriptionId: The Customer's ID - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func deleteDiscount(onSubscription subscriptionId: String, onAccount account: String? = nil) throws -> StripeRequest { - return try StripeRequest(client: self.client, method: .delete, route: .subscriptionDiscount(subscriptionId), query: [:], body: nil, headers: nil) + + return try request.send(method: .POST, path: StripeAPIEndpoint.subscriptions(subscription).endpoint, body: body.queryParameters) } - /** - Cancel a subscription - - - parameter subscriptionId: The identifier of the subscription to cancel - - - parameter atTrialEnd: A flag that if set to true will delay the cancellation of the subscription until the end of the current period. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - - public func cancel(subscription subscriptionId: String, atPeriodEnd: Bool? = nil, onAccount account: String? = nil) throws -> StripeRequest { + /// Cancel a subscription + /// [Learn More →](https://stripe.com/docs/api/curl#cancel_subscription) + public func cancel(subscription: String, atPeriodEnd: Bool? = nil) throws -> Future { + var body: [String: Any] = [:] - var body = Node([:]) - - if let atperiodend = atPeriodEnd { - body["at_period_end"] = Node(atperiodend) + if let atPeriodEnd = atPeriodEnd { + body["at_period_end"] = atPeriodEnd } - return try StripeRequest(client: self.client, method: .delete, route: .subscriptions(subscriptionId), query: [:], body: Body.data(body.formURLEncoded()), headers: nil) + return try request.send(method: .DELETE, path: StripeAPIEndpoint.subscriptions(subscription).endpoint, body: body.queryParameters) } - /** - List all customers - By default, returns a list of subscriptions that have not been canceled. - - - 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 + /// List subscriptions + /// [Learn More →](https://stripe.com/docs/api/curl#list_subscriptions) + public func listAll(filter: [String : Any]? = nil) throws -> Future { + var queryParams = "" + if let filter = filter { + queryParams = filter.queryParameters } - return try StripeRequest(client: self.client, method: .get, route: .subscription, query: query, body: nil, headers: nil) + + return try request.send(method: .GET, path: StripeAPIEndpoint.subscription.endpoint, query: queryParams) + } + + /// Delete a subscription discount + /// [Learn More →](https://stripe.com/docs/api/curl#delete_subscription_discount) + public func deleteDiscount(subscription: String) throws -> Future { + return try request.send(method: .DELETE, path: StripeAPIEndpoint.subscriptionDiscount(subscription).endpoint) } } diff --git a/Sources/Stripe/API/Routes/TokenRoutes.swift b/Sources/Stripe/API/Routes/TokenRoutes.swift index f91ee29..311e9ca 100644 --- a/Sources/Stripe/API/Routes/TokenRoutes.swift +++ b/Sources/Stripe/API/Routes/TokenRoutes.swift @@ -6,172 +6,85 @@ // // -import Node -import HTTP +import Vapor -open class TokenRoutes { +public protocol TokenRoutes { + associatedtype T: Token - let client: StripeClient - - init(client: StripeClient) { - self.client = client - } + func createCard(card: [String: Any]?, customer: String?, connectAccount: String?) throws -> Future + func createBankAccount(bankAcocunt: [String: Any]?, customer: String?, connectAccount: String?) throws -> Future + func createPII(personalId: String?) throws -> Future + func retrieve(token: String) throws -> Future +} + +public struct StripeTokenRoutes: TokenRoutes { + private let request: StripeRequest - func cleanNumber(_ number: String) -> String { - var number = number.replacingOccurrences(of: " ", with: "") - number = number.replacingOccurrences(of: "-", with: "") - return number + init(request: StripeRequest) { + self.request = request } - - /** - Create a card token - Creates a single use token that wraps the details of a credit card. This token can be used in place of a - credit card dictionary with any API method. These tokens can only be used once: by creating a new charge - object, or attaching them to a customer. In most cases, you should create tokens client-side using Checkout, - Elements, or Stripe libraries, instead of using the API. - - - parameter cardNumber: The card number, as a string without any separators. - - - parameter expirationMonth: Two digit number representing the card's expiration month. - - - parameter expirationYear: Two or four digit number representing the card's expiration year. - - - parameter cvc: Card security code. Highly recommended to always include this value, - but it's only required for accounts based in European countries. - - - parameter name: Cardholder's full name. - - - parameter customer: The customer (owned by the application's account) to create a token for. - For use with Stripe Connect only; this can only be used with an OAuth access token - or Stripe-Account header. For more details, see the shared customers documentation. - - - parameter currency: Required to be able to add the card to an account (in all other cases, - this parameter is not used). When added to an account, the card - (which must be a debit card) can be used as a transfer destination for - funds in this currency. Currently, the only supported currency for - debit card transfers is `usd`. MANAGED ACCOUNTS ONLY - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func createCardToken(withCardNumber cardNumber: String, expirationMonth: Int, expirationYear: Int, cvc: Int, name: String? = nil, customer: String? = nil, currency: StripeCurrency? = nil) throws -> StripeRequest { - var body = Node([:]) - var headers: [HeaderKey: String]? - - body["card[number]"] = Node(self.cleanNumber(cardNumber)) - body["card[exp_month]"] = Node(expirationMonth) - body["card[exp_year]"] = Node(expirationYear) - body["card[cvc]"] = Node(cvc) + + /// Create a card token + /// [Learn More →](https://stripe.com/docs/api/curl#create_card_token) + public func createCard(card: [String : Any]? = nil, + customer: String? = nil, + connectAccount: String? = nil) throws -> Future { + var body: [String: Any] = [:] + var headers: HTTPHeaders = [:] - if let name = name { - body["card[name]"] = Node(name) + if let card = card { + card.forEach { body["card[\($0)]"] = $1 } } - if let currency = currency { - body["card[currency]"] = Node(currency.rawValue) + if let customer = customer { + body["customer"] = customer } - // Check if we have an account to set it to - if let customer = customer { - headers = [StripeHeader.Account: customer] + if let connectAccount = connectAccount { + headers.add(name: .stripeAccount, value: connectAccount) } - return try StripeRequest(client: self.client, method: .post, route: .tokens, body: Body.data(body.formURLEncoded()), headers: headers) + return try request.send(method: .POST, path: StripeAPIEndpoint.tokens.endpoint, body: body.queryParameters, headers: headers) } - /** - Create a bank account token - Creates a single use token that wraps the details of a bank account. This token can be used in place of - a bank account dictionary with any API method. These tokens can only be used once: by attaching them to a - recipient or managed account. - - - parameter accountNumber: The account number for the bank account in string form. Must be a checking account. - - - parameter country: The country the bank account is in. - - - parameter currency: The currency the bank account is in. This must be a country/currency pairing - that Stripe supports. (Re StripeCurrency enum) - - - parameter routingNumber: The routing number, sort code, or other country-appropriate institution number for the - bank account. For US bank accounts, this is required and should be the ACH routing number, - not the wire routing number. If you are providing an IBAN for account_number, this field - is not required. - - - parameter accountHolderName: The name of the person or business that owns the bank account. This field is required - when attaching the bank account to a customer object. - - - parameter accountHolderType: The type of entity that holds the account. This can be either "individual" or "company". - This field is required when attaching the bank account to a customer object. - - - parameter customer: The customer (owned by the application's account) to create a token for. For use with Stripe - Connect only; this can only be used with an OAuth access token or Stripe-Account header. For - more details, see the shared customers documentation. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - - public func createBankAccountToken(withAccountNumber accountNumber: String, country: String, currency: StripeCurrency, routingNumber: String? = nil, accountHolderName: String? = nil, accountHolderType: String? = nil, customer: String? = nil) throws -> StripeRequest { - var body = Node([:]) - var headers: [HeaderKey: String]? + /// Create a bank account token + /// [Learn More →](https://stripe.com/docs/api/curl#create_bank_account_token) + public func createBankAccount(bankAcocunt: [String : Any]? = nil, + customer: String? = nil, + connectAccount: String? = nil) throws -> Future { + var body: [String: Any] = [:] + var headers: HTTPHeaders = [:] - body["bank_account[account_number]"] = Node(self.cleanNumber(accountNumber)) - body["bank_account[country]"] = Node(country) - body["bank_account[currency]"] = Node(currency.rawValue) - - if let routingNumber = routingNumber { - body["bank_account[routing_number]"] = Node(routingNumber) - } - - if let accountHolderName = accountHolderName { - body["bank_account[account_holder_name]"] = Node(accountHolderName) + if let bankAcocunt = bankAcocunt { + bankAcocunt.forEach { body["bank_account[\($0)]"] = $1 } } - if let accountHolderType = accountHolderType { - body["bank_account[account_holder_type]"] = Node(accountHolderType) + if let customer = customer { + body["customer"] = customer } - // Check if we have an account to set it to - if let customer = customer { - headers = [StripeHeader.Account: customer] + if let connectAccount = connectAccount { + headers.add(name: .stripeAccount, value: connectAccount) } - return try StripeRequest(client: self.client, method: .post, route: .tokens, body: Body.data(body.formURLEncoded()), headers: headers) + return try request.send(method: .POST, path: StripeAPIEndpoint.tokens.endpoint, body: body.queryParameters, headers: headers) } - public func createPIIToken(piiNumber: String) throws -> StripeRequest { - var body = Node([:]) + /// Create a PII token + /// [Learn More →](https://stripe.com/docs/api/curl#create_pii_token) + public func createPII(personalId: String? = nil) throws -> Future { + var body: [String: Any] = [:] - body["pii[personal_id_number]"] = Node(piiNumber) - - return try StripeRequest(client: self.client, method: .post, route: .tokens, body: Body.data(body.formURLEncoded()), headers: nil) - } - - /** - Create a payment token from a customer - - - parameter withCustomerId: The customer ID to generate the token with - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func createToken(withCustomerId customerId: String, onAccount account: String? = nil) throws -> StripeRequest { - let body = try Node(node: ["customer": customerId]) - var headers: [HeaderKey : String]? - if let account = account { - headers = [ - StripeHeader.Account: account - ] + if let personalId = personalId { + body["personal_id_number"] = personalId } - return try StripeRequest(client: self.client, method: .post, route: .tokens, body: Body.data(body.formURLEncoded()), headers: headers) + + return try request.send(method: .POST, path: StripeAPIEndpoint.tokens.endpoint, body: body.queryParameters) } - /** - Retrieve a token - Retrieves the token with the given ID. - - - parameter token: The ID of the desired token. - - - returns: A StripeRequest<> item which you can then use to convert to the corresponding node - */ - public func retrieve(_ token: String) throws -> StripeRequest { - return try StripeRequest(client: self.client, route: .token(token)) + /// Retrieve a token + /// [Learn More →](https://stripe.com/docs/api/curl#retrieve_token) + public func retrieve(token: String) throws -> Future { + return try request.send(method: .GET, path: StripeAPIEndpoint.token(token).endpoint) } } diff --git a/Sources/Stripe/API/StripeClient.swift b/Sources/Stripe/API/StripeClient.swift deleted file mode 100644 index b40da96..0000000 --- a/Sources/Stripe/API/StripeClient.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// Stripe.swift -// Stripe -// -// Created by Anthony Castelli on 4/13/17. -// -// - -import Vapor - -public class StripeClient { - - public let apiKey: String - - public private(set) var balance: BalanceRoutes! - public private(set) var charge: ChargeRoutes! - 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 private(set) var plans: PlanRoutes! - public private(set) var sources: SourceRoutes! - public private(set) var subscriptionItems: SubscriptionItemRoutes! - 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 private(set) var orders: OrderRoutes! - public private(set) var orderReturns: OrderReturnRoutes! - public private(set) var invoices: InvoiceRoutes! - public private(set) var invoiceItems: InvoiceItemRoutes! - public private(set) var ephemeralKeys: EphemeralKeyRoutes! - - public init(apiKey: String) throws { - self.apiKey = apiKey - } - - public func initializeRoutes() { - self.balance = BalanceRoutes(client: self) - self.charge = ChargeRoutes(client: self) - self.customer = CustomerRoutes(client: self) - self.tokens = TokenRoutes(client: self) - self.refunds = RefundRoutes(client: self) - self.coupons = CouponRoutes(client: self) - self.plans = PlanRoutes(client: self) - self.sources = SourceRoutes(client: self) - self.subscriptionItems = SubscriptionItemRoutes(client: self) - 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) - self.orders = OrderRoutes(client: self) - self.orderReturns = OrderReturnRoutes(client: self) - self.invoices = InvoiceRoutes(client: self) - self.invoiceItems = InvoiceItemRoutes(client: self) - self.ephemeralKeys = EphemeralKeyRoutes(client: self) - } -} diff --git a/Sources/Stripe/API/StripeRequest.swift b/Sources/Stripe/API/StripeRequest.swift index 0a82cbc..396b0d7 100644 --- a/Sources/Stripe/API/StripeRequest.swift +++ b/Sources/Stripe/API/StripeRequest.swift @@ -10,77 +10,71 @@ import Foundation import Vapor import HTTP -internal enum HTTPMethod { - case get - case post - case patch - case put - case delete +public protocol StripeRequest: class { + func serializedResponse(response: HTTPResponse, worker: EventLoop) throws -> Future + func send(method: HTTPMethod, path: String, query: String, body: String, headers: HTTPHeaders) throws -> Future } -public class StripeRequest { - var client: StripeClient! - var response: HTTP.Response! - let httpClient = EngineClient.factory - - init(client: StripeClient, method: HTTPMethod = .get, route: API, query: [String : NodeRepresentable] = [:], body: BodyRepresentable? = nil, headers: [HeaderKey : String]? = nil) throws { - self.client = client - - var allHeaders = DefaultHeaders - allHeaders[StripeHeader.Authorization] = "Bearer \(self.client.apiKey)" +public extension StripeRequest { + public func send(method: HTTPMethod, path: String, query: String = "", body: String = "", headers: HTTPHeaders = [:]) throws -> Future { + return try send(method: method, path: path, query: query, body: body, headers: headers) + } + + public func serializedResponse(response: HTTPResponse, worker: EventLoop) throws -> Future { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase - if let headers = headers { - headers.forEach { - allHeaders[$0.key] = $0.value + guard response.status == .ok else { + return try decoder.decode(StripeAPIError.self, from: response.body, on: worker).map(to: SM.self) { error in + throw StripeError.apiError(error) } } - switch method { - case .get: self.response = try self.httpClient.get(route.endpoint, query: query, allHeaders, body, through: []) - case .post: self.response = try self.httpClient.post(route.endpoint, query: query, allHeaders, body, through: []) - case .patch: self.response = try self.httpClient.patch(route.endpoint, query: query, allHeaders, body, through: []) - case .put: self.response = try self.httpClient.put(route.endpoint, query: query, allHeaders, body, through: []) - case .delete: self.response = try self.httpClient.delete(route.endpoint, query: query, allHeaders, body, through: []) - } + return try decoder.decode(SM.self, from: response.body, on: worker) } +} - @discardableResult - public func serializedResponse() throws -> T { - guard self.response.status == .ok else { - guard let error = self.response.json?["error"]?.object else { throw self.response.status } - guard let type = error["type"]?.string else { throw self.response.status } - switch type { - case "api_connection_error": throw StripeError.apiConnectionError(error["message"]?.string ?? "unknown error") - case "api_error": throw StripeError.apiError(error["message"]?.string ?? "unknown error") - case "authentication_error": throw StripeError.authenticationError(error["message"]?.string ?? "unknown error") - case "card_error": throw StripeError.cardError(error["message"]?.string ?? "unknown error") - case "invalid_request_error": throw StripeError.invalidRequestError(error["message"]?.string ?? "unknown error") - case "rate_limit_error": throw StripeError.rateLimitError(error["message"]?.string ?? "unknown error") - case "validation_error": throw StripeError.validationError(error["message"]?.string ?? "unknown error") - default: throw self.response.status - } - } - guard let value = self.response.json else { throw StripeError.serializationError } - return try T(node: value) +extension HTTPHeaderName { + public static var stripeVersion: HTTPHeaderName { + return .init("Stripe-Version") + } + public static var stripeAccount: HTTPHeaderName { + return .init("Stripe-Version") + } +} + +extension HTTPHeaders { + public static var stripeDefault: HTTPHeaders { + var headers: HTTPHeaders = [:] + headers.replaceOrAdd(name: .stripeVersion, value: "2018-02-28") + headers.replaceOrAdd(name: .contentType, value: MediaType.urlEncodedForm.description) + return headers + } +} + +public class StripeAPIRequest: StripeRequest { + private let httpClient: Client + private let apiKey: String + + init(httpClient: Client, apiKey: String) { + self.httpClient = httpClient + self.apiKey = apiKey } - @discardableResult - public func json() throws -> JSON { - guard self.response.status == .ok else { - guard let error = self.response.json?["error"]?.object else { throw self.response.status } - guard let type = error["type"]?.string else { throw self.response.status } - switch type { - case "api_connection_error": throw StripeError.apiConnectionError(error["message"]?.string ?? "unknown error") - case "api_error": throw StripeError.apiError(error["message"]?.string ?? "unknown error") - case "authentication_error": throw StripeError.authenticationError(error["message"]?.string ?? "unknown error") - case "card_error": throw StripeError.cardError(error["message"]?.string ?? "unknown error") - case "invalid_request_error": throw StripeError.invalidRequestError(error["message"]?.string ?? "unknown error") - case "rate_limit_error": throw StripeError.rateLimitError(error["message"]?.string ?? "unknown error") - case "validation_error": throw StripeError.validationError(error["message"]?.string ?? "unknown error") - default: throw self.response.status - } + public func send(method: HTTPMethod, path: String, query: String, body: String, headers: HTTPHeaders) throws -> Future { + let encodedHTTPBody = HTTPBody(string: body) + + var finalHeaders: HTTPHeaders = .stripeDefault + + headers.forEach { finalHeaders.add(name: $0.name, value: $0.value) } + + finalHeaders.add(name: .authorization, value: "Bearer \(apiKey)") + + let request = HTTPRequest(method: method, url: URL(string: "\(path)?\(query)") ?? .root, headers: finalHeaders, body: encodedHTTPBody) + + return try httpClient.respond(to: Request(http: request, using: httpClient.container)).flatMap(to: SM.self) { (response) -> Future in + return try self.serializedResponse(response: response.http, worker: self.httpClient.container.eventLoop) } - guard let value = self.response.json else { throw StripeError.serializationError } - return value } } diff --git a/Sources/Stripe/Errors/StripeAPIError.swift b/Sources/Stripe/Errors/StripeAPIError.swift new file mode 100644 index 0000000..e72613c --- /dev/null +++ b/Sources/Stripe/Errors/StripeAPIError.swift @@ -0,0 +1,30 @@ +// +// StripeAPIError.swift +// StripePackageDescription +// +// Created by Andrew Edwards on 1/19/18. +// + +import Foundation +/** + Error object + https://stripe.com/docs/api#errors + */ + +public protocol APIError { + var type: StripeAPIErrorType? { get } + var charge: String? { get } + var message: String? { get } + var code: StripeAPICardErrorType? { get } + var declineCode: StripeAPIDeclineCode? { get } + var param: String? { get } +} + +public struct StripeAPIError: APIError, StripeModel { + public var type: StripeAPIErrorType? + public var charge: String? + public var message: String? + public var code: StripeAPICardErrorType? + public var declineCode: StripeAPIDeclineCode? + public var param: String? +} diff --git a/Sources/Stripe/Errors/StripeError.swift b/Sources/Stripe/Errors/StripeError.swift index b958f43..d6e1e27 100644 --- a/Sources/Stripe/Errors/StripeError.swift +++ b/Sources/Stripe/Errors/StripeError.swift @@ -6,49 +6,115 @@ // // -import Node - public enum StripeError: Error { + case malformedEncodedBody + case malformedEncodedQuery + case apiError(StripeAPIError) + + public var localizedDescription: String { + switch self { + case .malformedEncodedBody: + return "The body for the request was malformed" + case .malformedEncodedQuery: + return "The query for the request was malformed" + case .apiError(let apiError): + return apiError.message ?? "Unknown Error" + } + } +} - // Provider Errors - case missingConfig - case missingAPIKey - - // Serialization - case serializationError +// https://stripe.com/docs/api#errors-type +public enum StripeAPIErrorType: String, StripeModel { + case apiConnectionError = "api_connection_error" + case apiError = "api_error" + case authenticationError = "authentication_error" + case cardError = "card_error" + case invalidRequestError = "invalid_request_error" + case rateLimitError = "rate_limit_error" + + public init(from decoder: Decoder) throws { + let value = try decoder.singleValueContainer() + guard let possible = StripeAPIErrorType(rawValue: try value.decode(String.self)) else { + throw DecodingError.typeMismatch(StripeAPIErrorType.self, DecodingError.Context(codingPath: [], debugDescription: "Unknown error type returned from stripe.")) + } + self = possible + } +} - // API Error's - case apiConnectionError(String) - case apiError(String) - case authenticationError(String) - case cardError(String) - case invalidRequestError(String) - case rateLimitError(String) - case validationError(String) +// https://stripe.com/docs/api#errors-code +public enum StripeAPICardErrorType: String, StripeModel { + case invalidNumber = "invalid_number" + case invalidExpiryMonth = "invalid_expiry_month" + case invalidExpiryYear = "invalid_expiry_year" + case invalidCVC = "invalid_cvc" + case invalidSwipeData = "invalid_swipe_data" + case incorrectNumber = "incorrect_number" + case expiredCard = "expired_card" + case incorrectCVC = "incorrect_cvc" + case incorrectZip = "incorrect_zip" + case cardDeclined = "card_declined" + case missing + case processingError = "processing_error" + + public init(from decoder: Decoder) throws { + let value = try decoder.singleValueContainer() + guard let possible = StripeAPICardErrorType(rawValue: try value.decode(String.self)) else { + throw DecodingError.typeMismatch(StripeAPICardErrorType.self, DecodingError.Context(codingPath: [], debugDescription: "Unknown card error returned from stripe.")) + } + self = possible + } +} - // Other - case invalidSourceType - case missingParamater(String) +// https://stripe.com/docs/api#errors-decline-code +public enum StripeAPIDeclineCode: String, StripeModel { + case approveWithId = "approve_with_id" + case callIssuer = "call_issuer" + case cardNotSupported = "card_not_supported" + case cardVelocityExceeded = "card_velocity_exceeded" + case currencyNotSupported = "currency_not_supported" + case doNotHonor = "do_not_honor" + case doNotTryAgain = "do_not_try_again" + case duplicateTransaction = "duplicate_transaction" + case expiredCard = "expired_card" + case fradulent + case genericDecline = "generic_decline" + case incorrectNumber = "incorrect_number" + case incorrectCVC = "incorrect_cvc" + case incorrectPin = "incorrect_pin" + case incorrectZip = "incorrect_zip" + case insufficientFunds = "insufficient_funds" + case invalidAccount = "invalid_account" + case invalidAmount = "invalid_amount" + case invalidCVC = "invalid_cvc" + case invalidExpiryYear = "invalid_expiry_year" + case invalidNumber = "invalid_number" + case invalidPin = "invalid_pin" + case issuerNotAvailable = "issuer_not_available" + case lostCard = "lost_card" + case newAccountInformationAvailable = "new_account_information_available" + case noActionTaken = "no_action_taken" + case notPermitted = "not_permitted" + case pickupCard = "pickup_card" + case pinTryExceeded = "pin_try_exceeded" + case processingError = "processing_error" + case reenterTransaction = "reenter_transaction" + case restrictedCard = "restricted_card" + case revocationOfAllAuthorizations = "revocation_of_all_authorizations" + case revocationOfAuthorization = "revocation_of_authorization" + case securityViolation = "security_violation" + case serviceNotAllowed = "service_not_allowed" + case stolenCard = "stolen_card" + case stopPaymentOrder = "stop_payment_order" + case testmodeDecline = "testmode_decline" + case transactionNotAllowed = "transaction_not_allowed" + case tryAgainLater = "try_again_later" + case withdrawalCountLimitExceeded = "withdrawal_count_limit_exceeded" - public var localizedDescription: String { - - switch self { - case .apiConnectionError(let err): - return err - case .apiError(let err): - return err - case .authenticationError(let err): - return err - case .cardError(let err): - return err - case .invalidRequestError(let err): - return err - case .rateLimitError(let err): - return err - case .validationError(let err): - return err - default: - return "unknown error" + public init(from decoder: Decoder) throws { + let value = try decoder.singleValueContainer() + guard let possible = StripeAPIDeclineCode(rawValue: try value.decode(String.self)) else { + throw DecodingError.typeMismatch(StripeAPIDeclineCode.self, DecodingError.Context(codingPath: [], debugDescription: "Unknown decline code returned from stripe.")) } + self = possible } } diff --git a/Sources/Stripe/Helpers/AccountRejectReason.swift b/Sources/Stripe/Helpers/AccountRejectReason.swift index 137a495..67fb9ac 100644 --- a/Sources/Stripe/Helpers/AccountRejectReason.swift +++ b/Sources/Stripe/Helpers/AccountRejectReason.swift @@ -6,10 +6,8 @@ // // -import Foundation - -public enum AccountRejectReason: String { - case fraud = "fraud" +public enum AccountRejectReason: String, Codable { + case fraud case termsOfService = "terms_of_service" - case other = "other" + case other } diff --git a/Sources/Stripe/Helpers/ActionType.swift b/Sources/Stripe/Helpers/ActionType.swift index df2a60c..eb9d326 100644 --- a/Sources/Stripe/Helpers/ActionType.swift +++ b/Sources/Stripe/Helpers/ActionType.swift @@ -6,10 +6,8 @@ // // -import Foundation - -public enum ActionType: String { - case charge = "charge" - case stripeFee = "stripe_fee" - case none = "none" +public enum ActionType: String, Codable { + case charge + case stripeFee = "stripe_fee" + case none } diff --git a/Sources/Stripe/Helpers/BalanceType.swift b/Sources/Stripe/Helpers/BalanceType.swift deleted file mode 100644 index fca29fd..0000000 --- a/Sources/Stripe/Helpers/BalanceType.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// BalanceType.swift -// Stripe -// -// Created by Anthony Castelli on 4/16/17. -// -// - -import Foundation - -public enum BalanceType: String { - case charge = "charge" - case refund = "refund" - case adjustment = "adjustment" - case applicationFee = "application_fee" - case applicationFeeRefund = "application_fee_refund" - case transfer = "transfer" - case payment = "payment" - case payout = "payout" - case payoutFailure = "payout_failure" -} diff --git a/Sources/Stripe/Helpers/CodeVerification.swift b/Sources/Stripe/Helpers/CodeVerification.swift new file mode 100644 index 0000000..95638d0 --- /dev/null +++ b/Sources/Stripe/Helpers/CodeVerification.swift @@ -0,0 +1,11 @@ +// +// CodeVerification.swift +// Stripe +// +// Created by Andrew Edwards on 12/4/17. +// + +public struct CodeVerification: StripeModel { + public var attemptsRemaining: Int? + public var status: StripeStatus? +} diff --git a/Sources/Stripe/Helpers/ConnectedAccountType.swift b/Sources/Stripe/Helpers/ConnectedAccountType.swift deleted file mode 100644 index c738a47..0000000 --- a/Sources/Stripe/Helpers/ConnectedAccountType.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// ConnectedAccountType.swift -// Stripe -// -// Created by Andrew Edwards on 7/9/17. -// -// - -import Foundation - -public enum ConnectedAccountType: String { - case custom = "custom" - case standard = "standard" -} diff --git a/Sources/Stripe/Helpers/DeliveryEstimateType.swift b/Sources/Stripe/Helpers/DeliveryEstimateType.swift deleted file mode 100644 index 96f1f57..0000000 --- a/Sources/Stripe/Helpers/DeliveryEstimateType.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// 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/Stripe/Helpers/FundingType.swift b/Sources/Stripe/Helpers/FundingType.swift index 80938b5..a21de75 100644 --- a/Sources/Stripe/Helpers/FundingType.swift +++ b/Sources/Stripe/Helpers/FundingType.swift @@ -6,22 +6,9 @@ // // -import Foundation - -public enum FundingType: String { - case credit = "credit" - case debit = "debit" - case prepaid = "prepaid" - case unknown = "unknown" -} - -extension FundingType { - public init?(optionalRawValue: String?) { - if let rawValue = optionalRawValue { - if let value = FundingType(rawValue: rawValue) { - self = value - } - } - return nil - } +public enum FundingType: String, Codable { + case credit + case debit + case prepaid + case unknown } diff --git a/Sources/Stripe/Helpers/InventoryType.swift b/Sources/Stripe/Helpers/InventoryType.swift index ba8b539..28fdfba 100644 --- a/Sources/Stripe/Helpers/InventoryType.swift +++ b/Sources/Stripe/Helpers/InventoryType.swift @@ -6,18 +6,16 @@ // // -import Foundation - -public enum InventoryType: String { - case finite = "finite" - case bucket = "bucket" - case infinite = "infinite" - case unknown = "unknown" +public enum InventoryType: String, Codable { + case finite + case bucket + case infinite + case unknown } -public enum InventoryTypeValue: String { +public enum InventoryTypeValue: String, Codable { case inStock = "in_stock" - case limited = "limited" + case limited case outOfStock = "out_of_stock" - case unknown = "unknown" + case unknown } diff --git a/Sources/Stripe/Helpers/OrderItemType.swift b/Sources/Stripe/Helpers/OrderItemType.swift deleted file mode 100644 index bfc44da..0000000 --- a/Sources/Stripe/Helpers/OrderItemType.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// 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/Stripe/Helpers/OrderStatus.swift b/Sources/Stripe/Helpers/OrderStatus.swift deleted file mode 100644 index 09454e4..0000000 --- a/Sources/Stripe/Helpers/OrderStatus.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// 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" -} diff --git a/Sources/Stripe/Helpers/RefundReason.swift b/Sources/Stripe/Helpers/RefundReason.swift deleted file mode 100644 index c34232f..0000000 --- a/Sources/Stripe/Helpers/RefundReason.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// RefundReason.swift -// Stripe -// -// Created by Anthony Castelli on 5/13/17. -// -// - -import Foundation - -public enum RefundReason: String { - case duplicate = "duplicate" - case fraudulent = "fraudulent" - case requestedByCustomer = "requested_by_customer" -} diff --git a/Sources/Stripe/Helpers/SourceType.swift b/Sources/Stripe/Helpers/SourceType.swift deleted file mode 100644 index 4b5ce43..0000000 --- a/Sources/Stripe/Helpers/SourceType.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// SourceType.swift -// Stripe -// -// Created by Anthony Castelli on 4/14/17. -// -// - -import Foundation - -public enum SourceType: String -{ - case card = "card" - case bitcoin = "bitcoin" - case threeDSecure = "three_d_secure" - case giropay = "giropay" - case sepaDebit = "sepa_debit" - case ideal = "ideal" - case sofort = "sofort" - case bancontact = "bancontact" - case none = "none" -} diff --git a/Sources/Stripe/Helpers/StripeCurrency.swift b/Sources/Stripe/Helpers/StripeCurrency.swift index 05840f3..1f256f2 100644 --- a/Sources/Stripe/Helpers/StripeCurrency.swift +++ b/Sources/Stripe/Helpers/StripeCurrency.swift @@ -6,19 +6,50 @@ // // -public enum StripeCurrency: String { - case aud = "aud" - case cad = "cad" - case dkk = "dkk" - case eur = "eur" - case gbp = "gbp" - case jpy = "jpy" - case nok = "nok" - case usd = "usd" - case sek = "sek" - case sgd = "sgd" - - var description: String { - return self.rawValue.uppercased() - } +public enum StripeCurrency: String, Codable { + case usd + case aed + case afn + case all + case amd + case ang + case aoa + case ars + case aud + case awg + case azn + case bam + case bbd + case bdt + case bgn + case bif + case bmd + case bnd + case bob + case brl + case bsd + case bwp + case bzd + case cad + case cdf + case chf + case clp + case cny + case cop + case crc + case cve + case czk + case djf + case dkk + case dop + case dzd + // TODO: - finish adding stripe currecies + case egp + case eur + case gbp + case jpy + case nok + case sek + case nzd + case sgd } diff --git a/Sources/Stripe/Helpers/StripeDuration.swift b/Sources/Stripe/Helpers/StripeDuration.swift index 5052607..7251f98 100644 --- a/Sources/Stripe/Helpers/StripeDuration.swift +++ b/Sources/Stripe/Helpers/StripeDuration.swift @@ -6,30 +6,27 @@ // // -public enum StripeDuration: String { - case forever = "forever" - case once = "once" - case repeating = "repeating" - case never = "never" - - var description: String { - return self.rawValue.uppercased() - } +public enum StripeDuration: String, Codable { + case forever + case once + case repeating } -public enum StripePayoutInterval: String { - case manual = "manual" - case daily = "daily" - case weekly = "weekly" - case monthly = "monthly" +// https://stripe.com/docs/api/curl#account_object-payout_schedule-interval +public enum StripePayoutInterval: String, Codable { + case manual + case daily + case weekly + case monthly } -public enum StripeWeeklyAnchor: String { - case sunday = "sunday" - case monday = "monday" - case tuesday = "tuesday" - case wednesday = "wednesday" - case thursday = "thursday" - case friday = "friday" - case saturday = "saturday" +// https://stripe.com/docs/api/curl#account_object-payout_schedule-weekly_anchor +public enum StripeWeeklyAnchor: String, Codable { + case sunday + case monday + case tuesday + case wednesday + case thursday + case friday + case saturday } diff --git a/Sources/Stripe/Helpers/StripeInterval.swift b/Sources/Stripe/Helpers/StripeInterval.swift deleted file mode 100644 index 05f00ce..0000000 --- a/Sources/Stripe/Helpers/StripeInterval.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// StripeInterval.swift -// Stripe -// -// Created by Andrew Edwards on 5/29/17. -// -// - -import Foundation - -public enum StripeInterval: String { - case day = "day" - case week = "week" - case month = "month" - case year = "year" - - case never = "never" - - var description: String { - return self.rawValue.uppercased() - } -} diff --git a/Sources/Stripe/Helpers/StripePlanInterval.swift b/Sources/Stripe/Helpers/StripePlanInterval.swift new file mode 100644 index 0000000..276fb71 --- /dev/null +++ b/Sources/Stripe/Helpers/StripePlanInterval.swift @@ -0,0 +1,16 @@ +// +// StripeInterval.swift +// Stripe +// +// Created by Andrew Edwards on 5/29/17. +// +// + +import Foundation + +public enum StripePlanInterval: String, Codable { + case day + case week + case month + case year +} diff --git a/Sources/Stripe/Helpers/StripeStatus.swift b/Sources/Stripe/Helpers/StripeStatus.swift index 11e9f04..5fb5a65 100644 --- a/Sources/Stripe/Helpers/StripeStatus.swift +++ b/Sources/Stripe/Helpers/StripeStatus.swift @@ -8,33 +8,33 @@ import Foundation -public enum StripeStatus: String { - case success = "success" - case succeeded = "succeeded" - case failed = "failed" - case pending = "pending" - case canceled = "canceled" - case chargeable = "chargeable" +public enum StripeStatus: String, Codable { + case success + case succeeded + case failed + case pending + case canceled + case chargeable } -public enum StripeSubscriptionStatus: String { - case trailing = "trailing" - case active = "active" - case pastdue = "past_due" - case canceled = "canceled" - case unpaid = "unpaid" +public enum StripeSubscriptionStatus: String, Codable { + case trailing + case active + case pastDue = "past_due" + case canceled + case unpaid } -public enum ConnectLegalEntityVerificationStatus: String { - case unverified = "unverified" - case pending = "pending" - case verified = "verified" +public enum LegalEntityVerificationStatus: String, Codable { + case unverified + case pending + case verified } -public enum ConnectLegalEntityVerificationState: String { +public enum LegalEntityVerificationState: String, Codable { case scanCorrupt = "scan_corrupt" case scanNotReadable = "scan_not_readable" - case scanFailedGreyScale = "scan_failed_greyscale" + case scanFailedGreyscale = "scan_failed_greyscale" case scanNotUploaded = "scan_not_uploaded" case scanIdTypeNotUploaded = "scan_id_type_not_supported" case scanIdCountryNotSupported = "scan_id_country_not_supported" diff --git a/Sources/Stripe/Helpers/TokenizedMethod.swift b/Sources/Stripe/Helpers/TokenizedMethod.swift deleted file mode 100644 index 6fc3eea..0000000 --- a/Sources/Stripe/Helpers/TokenizedMethod.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// TokenizedMethod.swift -// Stripe -// -// Created by Anthony Castelli on 4/15/17. -// -// - -import Foundation - -public enum TokenizedMethod: String { - case applePay = "apple_pay" - case androidPay = "android_pay" -} - -extension TokenizedMethod { - public init?(optionalRawValue: String?) { - if let rawValue = optionalRawValue { - if let value = TokenizedMethod(rawValue: rawValue) { - self = value - } - } - return nil - } -} diff --git a/Sources/Stripe/Helpers/ValidationCheck.swift b/Sources/Stripe/Helpers/ValidationCheck.swift deleted file mode 100644 index e7776bd..0000000 --- a/Sources/Stripe/Helpers/ValidationCheck.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// ValidationCheck.swift -// Stripe -// -// Created by Anthony Castelli on 4/15/17. -// -// - -import Foundation - -public enum ValidationCheck: String { - case pass = "pass" - case faile = "fail" - case unavailable = "unavailable" - case unchecked = "unchecked" -} - -extension ValidationCheck { - public init?(optionalRawValue: String?) { - if let rawValue = optionalRawValue { - if let value = ValidationCheck(rawValue: rawValue) { - self = value - } - } - return nil - } -} diff --git a/Sources/Stripe/Models/Balance/Balance.swift b/Sources/Stripe/Models/Balance/Balance.swift index f44bfe3..a47b726 100644 --- a/Sources/Stripe/Models/Balance/Balance.swift +++ b/Sources/Stripe/Models/Balance/Balance.swift @@ -6,29 +6,25 @@ // // -import Node +/** + Balance object + https://stripe.com/docs/api/curl#balance_object + */ -open class Balance: StripeModelProtocol { - - public private(set) var object: String? - public private(set) var isLiveMode: Bool? - public private(set) var available: [BalanceTransfer]? - public private(set) var pending: [BalanceTransfer]? - - public required init(node: Node) throws { - self.object = try node.get("object") - self.isLiveMode = try node.get("livemode") - self.available = try node.get("available") - self.pending = try node.get("pending") - } +public protocol Balance { + associatedtype BT: BalanceTransfer + + var object: String? { get } + var available: [BT]? { get } + var connectReserved: [BT]? { get } + var livemode: Bool? { get } + var pending: [BT]? { get } +} - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "object": self.object, - "livemode": self.isLiveMode, - "available": self.available, - "pending": self.pending - ] - return try Node(node: object) - } +public struct StripeBalance: Balance, StripeModel { + public var object: String? + public var available: [StripeBalanceTransfer]? + public var connectReserved: [StripeBalanceTransfer]? + public var livemode: Bool? + public var pending: [StripeBalanceTransfer]? } diff --git a/Sources/Stripe/Models/Balance/BalanceHistoryList.swift b/Sources/Stripe/Models/Balance/BalanceHistoryList.swift index 836740c..2e0f7d9 100644 --- a/Sources/Stripe/Models/Balance/BalanceHistoryList.swift +++ b/Sources/Stripe/Models/Balance/BalanceHistoryList.swift @@ -6,30 +6,15 @@ // // -import Foundation -import Vapor +/** + BalanceHistory list + https://stripe.com/docs/api#balance_history + */ -open class BalanceHistoryList: StripeModelProtocol { - - public private(set) var object: String? - public private(set) var url: String? - public private(set) var hasMore: Bool? - public private(set) var items: [BalanceTransactionItem]? - - public required 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) - } +public struct BalanceHistoryList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeBalance]? } diff --git a/Sources/Stripe/Models/Balance/BalanceTransactionItem.swift b/Sources/Stripe/Models/Balance/BalanceTransactionItem.swift index 251931d..cafece5 100644 --- a/Sources/Stripe/Models/Balance/BalanceTransactionItem.swift +++ b/Sources/Stripe/Models/Balance/BalanceTransactionItem.swift @@ -7,55 +7,44 @@ // import Foundation -import Vapor -open class BalanceTransactionItem: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var amount: Int? - public private(set) var availableOn: Date? - public private(set) var created: Date? - public private(set) var description: String? - // AETODO add fee - public private(set) var fees: [Fee]? - public private(set) var net: Int? - public private(set) var source: String? - public private(set) var status: StripeStatus? - public private(set) var type: ActionType? - - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.amount = try node.get("amount") - self.availableOn = try node.get("available_on") - self.created = try node.get("created") - self.description = try node.get("description") - self.fees = try node.get("fee_details") - self.net = try node.get("net") - self.source = try node.get("source") - if let status = node["status"]?.string { - self.status = StripeStatus(rawValue: status) - } - if let type = node["type"]?.string { - self.type = ActionType(rawValue: type) - } - } +/** + BalanceTransaction object + https://stripe.com/docs/api/curl#balance_transaction_object + */ + +public protocol BalanceTransactionItem { + associatedtype F: Fee - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "id": self.id, - "object": self.object, - "amount": self.amount, - "available_on": self.availableOn, - "created": self.created, - "description": self.description, - "fee_details": self.fees, - "net": self.net, - "source": self.source, - "status": self.status?.rawValue, - "type": self.type?.rawValue - ] - return try Node(node: object) - } + var id: String? { get } + var object: String? { get } + var amount: Int? { get } + var availableOn: Date? { get } + var created: Date? { get } + var currency: StripeCurrency? { get } + var description: String? { get } + var exchangeRate: Decimal? { get } + var fee: Int? { get } + var feeDetails: [F]? { get } + var net: Int? { get } + var source: String? { get } + var status: StripeStatus? { get } + var type: BalanceTransactionType? { get } +} + +public struct StripeBalanceTransactionItem: BalanceTransactionItem, StripeModel { + public var id: String? + public var object: String? + public var amount: Int? + public var availableOn: Date? + public var created: Date? + public var currency: StripeCurrency? + public var exchangeRate: Decimal? + public var description: String? + public var fee: Int? + public var feeDetails: [StripeFee]? + public var net: Int? + public var source: String? + public var status: StripeStatus? + public var type: BalanceTransactionType? } diff --git a/Sources/Stripe/Models/Balance/BalanceTransactionType.swift b/Sources/Stripe/Models/Balance/BalanceTransactionType.swift new file mode 100644 index 0000000..bc20c1d --- /dev/null +++ b/Sources/Stripe/Models/Balance/BalanceTransactionType.swift @@ -0,0 +1,32 @@ +// +// BalanceTransactionType.swift +// Stripe +// +// Created by Anthony Castelli on 4/16/17. +// +// + +import Foundation + +/** + BalanceTransactionType + https://stripe.com/docs/api/curl#balance_transaction_object-type + */ + +public enum BalanceTransactionType: String, Codable { + case adjustment + case applicationFee = "application_fee" + case applicationFeeRefund = "application_fee_refund" + case charge + case payment + case paymentFailureRefund = "payment_failure_refund" + case paymentRefund = "payment_refund" + case refund + case transfer + case transferRefund = "transfer_refund" + case payout + case payoutCancel = "payout_cancel" + case payoutFailure = "payout_failure" + case validation + case stripeFee = "stripe_fee" +} diff --git a/Sources/Stripe/Models/Balance/BalanceTransfer.swift b/Sources/Stripe/Models/Balance/BalanceTransfer.swift index d0f8f6c..bf89384 100644 --- a/Sources/Stripe/Models/Balance/BalanceTransfer.swift +++ b/Sources/Stripe/Models/Balance/BalanceTransfer.swift @@ -6,43 +6,19 @@ // // -import Node - /** Balance transfer is the body object of available array. https://stripe.com/docs/api/curl#balance_object */ -open class BalanceTransfer: StripeModelProtocol { - - public private(set) var currency: StripeCurrency? - public private(set) var amount: Int? - public private(set) var sourceTypes: [SourceType: Int] = [:] - - public required init(node: Node) throws { - if let currency = node["currency"]?.string { - self.currency = StripeCurrency(rawValue: currency) - } - self.amount = try node.get("amount") - - let items: [String : Int] = try node.get("source_types") - - for item in items { - sourceTypes[SourceType(rawValue: item.key) ?? .none] = item.value - } - } - - public func makeNode(in context: Context?) throws -> Node { - let types = self.sourceTypes.flatMap { $0 }.reduce([String : Int]()) { dictionary, item in - var dictionary = dictionary - dictionary.updateValue(item.value, forKey: item.key.rawValue) - return dictionary - } - - let object: [String : Any?] = [ - "currency": self.currency?.rawValue, - "amount": self.amount, - "source_types": types - ] - return try Node(node: object) - } +// TODO: - Use BalanceTransfer SourceTypes enum +public protocol BalanceTransfer { + var currency: StripeCurrency? { get } + var amount: Int? { get } + var sourceTypes: [String: Int]? { get } +} + +public struct StripeBalanceTransfer: BalanceTransfer, StripeModel { + public var currency: StripeCurrency? + public var amount: Int? + public var sourceTypes: [String: Int]? } diff --git a/Sources/Stripe/Models/Balance/Fee.swift b/Sources/Stripe/Models/Balance/Fee.swift new file mode 100644 index 0000000..1fb4353 --- /dev/null +++ b/Sources/Stripe/Models/Balance/Fee.swift @@ -0,0 +1,28 @@ +// +// Fee.swift +// Stripe +// +// Created by Anthony Castelli on 4/15/17. +// +// + +import Vapor + +/** + Fee + https://stripe.com/docs/api/curl#balance_transaction_object-fee_details + */ + +public protocol Fee { + var amount: Int? { get } + var currency: StripeCurrency? { get } + var description: String? { get } + var type: ActionType? { get } +} + +public struct StripeFee: Fee, StripeModel { + public var amount: Int? + public var currency: StripeCurrency? + public var description: String? + public var type: ActionType? +} diff --git a/Sources/Stripe/Models/Balance/SourceBalanceType.swift b/Sources/Stripe/Models/Balance/SourceBalanceType.swift new file mode 100644 index 0000000..545284a --- /dev/null +++ b/Sources/Stripe/Models/Balance/SourceBalanceType.swift @@ -0,0 +1,12 @@ +// +// SourceBalanceType.swift +// Stripe +// +// Created by Andrew Edwards on 2/11/18. +// + +public enum SourceBalanceType: String, Codable { + case card + case bankAccount = "bank_account" + case alipayAccount = "alipay_account" +} diff --git a/Sources/Stripe/Models/Charges/Charge.swift b/Sources/Stripe/Models/Charges/Charge.swift index 9dcfae0..d382248 100644 --- a/Sources/Stripe/Models/Charges/Charge.swift +++ b/Sources/Stripe/Models/Charges/Charge.swift @@ -7,155 +7,91 @@ // import Foundation -import Vapor - /** - Charge Model + Charge object https://stripe.com/docs/api/curl#charge_object */ -open class Charge: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var amount: Int? - public private(set) var amountRefunded: Int? - public private(set) var application: String? - public private(set) var applicationFee: String? - public private(set) var balanceTransactionId: String? - public private(set) var isCaptured: Bool? - public private(set) var created: Date? - public private(set) var currency: StripeCurrency? - public private(set) var customerId: String? - public private(set) var destination: String? - public private(set) var dispute: String? - public private(set) var failureCode: String? - public private(set) var failureMessage: String? - public private(set) var invoiceId: String? - public private(set) var isLiveMode: Bool? - public private(set) var onBehalfOf: String? - public private(set) var order: String? - public private(set) var outcome: Outcome? - public private(set) var isPaid: Bool? - public private(set) var recieptNumber: String? - public private(set) var isRefunded: Bool? - public private(set) var refunds: Refund? - public private(set) var review: String? - public private(set) var source: Source? - public private(set) var card: Card? - public private(set) var sourceTransfer: String? - public private(set) var statementDescriptor: String? - public private(set) var status: StripeStatus? - public private(set) var transfer: String? - - /** - Only these values are mutable/updatable. - https://stripe.com/docs/api/curl#update_charge - */ - - public private(set) var description: String? - public private(set) var fraud: FraudDetails? - public private(set) var metadata: Node? - public private(set) var receiptEmail: String? - public private(set) var shippingLabel: ShippingLabel? - public private(set) var transferGroup: String? - - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.amount = try node.get("amount") - self.amountRefunded = try node.get("amount_refunded") - self.application = try node.get("application") - self.applicationFee = try node.get("application_fee") - self.balanceTransactionId = try node.get("balance_transaction") - self.isCaptured = try node.get("captured") - self.created = try node.get("created") - self.customerId = try node.get("customer") - self.description = try node.get("description") - self.destination = try node.get("destination") - self.dispute = try node.get("dispute") - self.failureCode = try node.get("failure_code") - self.failureMessage = try node.get("failure_message") - self.invoiceId = try node.get("invoice") - self.isLiveMode = try node.get("livemode") - self.onBehalfOf = try node.get("on_behalf_of") - self.isPaid = try node.get("paid") - self.recieptNumber = try node.get("receipt_number") - self.isRefunded = try node.get("refunded") - self.review = try node.get("review") - self.sourceTransfer = try node.get("source_transfer") - self.statementDescriptor = try node.get("statement_descriptor") - self.transferGroup = try node.get("transfer_group") - if let currency = node["currency"]?.string { - self.currency = StripeCurrency(rawValue: currency) - } - self.fraud = try node.get("fraud_details") - self.outcome = try node.get("outcome") - self.refunds = try node.get("refunds") - if let status = node["status"]?.string { - self.status = StripeStatus(rawValue: status) - } - self.transfer = try node.get("transfer") - self.receiptEmail = try node.get("receipt_email") - if let _ = node["shipping"]?.object { - self.shippingLabel = try node.get("shipping") - } - - self.metadata = try node.get("metadata") - - // We have to determine if it's a card or a source item - if let sourceNode: Node = try node.get("source") { - if let object = sourceNode["object"]?.string, object == "card" { - self.card = try node.get("source") - } else if let object = sourceNode["object"]?.string, object == "source" { - self.source = try node.get("source") - } - } - } + +public protocol Charge { + associatedtype L: List + associatedtype S: Shipping + associatedtype F: FraudDetails + associatedtype SRC: Source - public func makeNode(in context: Context?) throws -> Node { - var object: [String : Any?] = [ - "id": self.id, - "object": self.object, - "amount": self.amount, - "amount_refunded": self.amountRefunded, - "application": self.application, - "application_fee": self.applicationFee, - "balance_transaction": self.balanceTransactionId, - "captured": self.isCaptured, - "created": self.created, - "currency": self.currency, - "customer": self.customerId, - "description": self.description, - "destination": self.destination, - "dispute": self.dispute, - "failure_code": self.failureCode, - "failure_message": self.failureMessage, - "fraud_details": self.fraud, - "invoice": self.invoiceId, - "livemode": self.isLiveMode, - "on_behalf_of": self.onBehalfOf, - "metadata": self.metadata, - "outcome": self.outcome, - "paid": self.isPaid, - "receipt_number": self.recieptNumber, - "refunded": self.isRefunded, - "refunds": self.refunds, - "review": self.review, - "shipping": self.shippingLabel, - "source_transfer": self.sourceTransfer, - "statement_descriptor": self.statementDescriptor, - "status": self.status?.rawValue, - "transfer": self.transfer, - "receipt_email": self.receiptEmail, - "transfer_group": self.transferGroup - ] - - if let source = self.source { - object["source"] = source - } else if let card = self.card { - object["source"] = card - } - return try Node(node: object) - } + var id: String? { get } + var object: String? { get } + var amount: Int? { get } + var amountRefunded: Int? { get } + var application: String? { get } + var applicationFee: String? { get } + var balanceTransaction: String? { get } + var captured: Bool? { get } + var created: Date? { get } + var currency: StripeCurrency? { get } + var customer: String? { get } + var description: String? { get } + var destination: String? { get } + var dispute: String? { get } + var failureCode: String? { get } + var failureMessage: String? { get } + var fraudDetails: F? { get } + var invoice: String? { get } + var livemode: Bool? { get } + var metadata: [String: String]? { get } + var onBehalfOf: String? { get } + var order: String? { get } + var outcome: StripeOutcome? { get } + var paid: Bool? { get } + var receiptEmail: String? { get } + var receiptNumber: String? { get } + var refunded: Bool? { get } + var refunds: L? { get } + var review: String? { get } + var shipping: S? { get } + var source: SRC? { get } + var sourceTransfer: String? { get } + var statementDescriptor: String? { get } + var status: StripeStatus? { get } + var transfer: String? { get } + var transferGroup: String? { get } +} + +public struct StripeCharge: Charge, StripeModel { + public var id: String? + public var object: String? + public var amount: Int? + public var amountRefunded: Int? + public var application: String? + public var applicationFee: String? + public var balanceTransaction: String? + public var captured: Bool? + public var created: Date? + public var currency: StripeCurrency? + public var customer: String? + public var description: String? + public var destination: String? + public var dispute: String? + public var failureCode: String? + public var failureMessage: String? + public var fraudDetails: StripeFraudDetails? + public var invoice: String? + public var livemode: Bool? + public var metadata: [String: String]? + public var onBehalfOf: String? + public var order: String? + public var outcome: StripeOutcome? + public var paid: Bool? + public var receiptEmail: String? + public var receiptNumber: String? + public var refunded: Bool? + public var refunds: RefundsList? + public var review: String? + public var shipping: ShippingLabel? + public var source: StripeSource? + public var sourceTransfer: String? + public var statementDescriptor: String? + public var status: StripeStatus? + public var transfer: String? + public var transferGroup: String? } diff --git a/Sources/Stripe/Models/Charges/ChargeList.swift b/Sources/Stripe/Models/Charges/ChargeList.swift index daaf59d..4971c68 100644 --- a/Sources/Stripe/Models/Charges/ChargeList.swift +++ b/Sources/Stripe/Models/Charges/ChargeList.swift @@ -6,28 +6,15 @@ // // -import Foundation -import Vapor +/** + Charges list + https://stripe.com/docs/api/curl#list_charges + */ -open class ChargeList: StripeModelProtocol { - - public private(set) var object: String? - public private(set) var hasMore: Bool? - public private(set) var items: [Charge]? - - public required init(node: Node) throws { - self.object = try node.get("object") - 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, - "has_more": self.hasMore, - "data": self.items - ] - return try Node(node: object) - } +public struct ChargesList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeCharge]? } - diff --git a/Sources/Stripe/Models/Charges/FruadDetails.swift b/Sources/Stripe/Models/Charges/FruadDetails.swift index e446ecc..c7aaf40 100644 --- a/Sources/Stripe/Models/Charges/FruadDetails.swift +++ b/Sources/Stripe/Models/Charges/FruadDetails.swift @@ -6,43 +6,22 @@ // // -import Foundation -import Vapor - - /** Fraud Details https://stripe.com/docs/api/curl#charge_object-fraud_details */ -public enum FraudReport: String { - case safe = "safe" - case fraudulent = "fraudulent" + +public enum FraudReport: String, Codable { + case safe + case fraudulent } -open class FraudDetails: StripeModelProtocol { +public protocol FraudDetails { + var userReport: FraudReport? { get } + var stripeReport: FraudReport? { get } +} - public private(set) var userReport: FraudReport? - public private(set) var stripeReport: FraudReport? - - public required init(node: Node) throws { - if let value: String? = try node.get("user_report") { - if let value = value { - self.userReport = FraudReport(rawValue: value) - } - } - - if let value: String? = try node.get("stripe_report") { - if let value = value { - self.stripeReport = FraudReport(rawValue: value) - } - } - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "user_report": self.userReport?.rawValue, - "stripe_report": self.stripeReport?.rawValue - ] - return try Node(node: object) - } +public struct StripeFraudDetails: FraudDetails, StripeModel { + public var userReport: FraudReport? + public var stripeReport: FraudReport? } diff --git a/Sources/Stripe/Models/Charges/Outcome.swift b/Sources/Stripe/Models/Charges/Outcome.swift index 7d55ea8..145d8ad 100644 --- a/Sources/Stripe/Models/Charges/Outcome.swift +++ b/Sources/Stripe/Models/Charges/Outcome.swift @@ -6,70 +6,48 @@ // // -import Foundation -import Vapor - - /** Outcome https://stripe.com/docs/api/curl#charge_object-outcome */ -public enum NetworkStatus: String { + +public enum NetworkStatus: String, Codable { case approvedByNetwork = "approved_by_network" case declinedByNetwork = "declined_by_network" case notSentToNetwork = "not_sent_to_network" case reversedAfterApproval = "reversed_after_approval" } -public enum RiskLevel: String { - case normal = "normal" - case elevated = "elevated" - case highest = "highest" +public enum RiskLevel: String, Codable { + case normal + case elevated + case highest case notAssessed = "not_assessed" - case unknown = "unknown" + case unknown } -public enum OutcomeType: String { - case authorized = "authorized" +public enum OutcomeType: String, Codable { + case authorized case manualReview = "manual_review" case issuerDeclined = "issuer_declined" - case blocked = "blocked" - case invalid = "invalid" + case blocked + case invalid +} + +public protocol Outcome { + var networkStatus: NetworkStatus? { get } + var reason: String? { get } + var riskLevel: RiskLevel? { get } + var rule: String? { get } + var sellerMessage: String? { get } + var type: OutcomeType? { get } } -open class Outcome: StripeModelProtocol { - - public private(set) var networkStatus: NetworkStatus? - public private(set) var reason: String? - public private(set) var riskLevel: RiskLevel? - public private(set) var rule: String? - public private(set) var sellerMessage: String? - public private(set) var type: OutcomeType? - - public required init(node: Node) throws { - if let networkStatus = node["network_status"]?.string { - self.networkStatus = NetworkStatus(rawValue: networkStatus) - } - self.reason = try node.get("reason") - if let riskLevel = node["risk_level"]?.string { - self.riskLevel = RiskLevel(rawValue: riskLevel) - } - self.rule = try node.get("rule") - self.sellerMessage = try node.get("seller_message") - if let outcome = node["type"]?.string { - self.type = OutcomeType(rawValue: outcome) - } - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "network_status": self.networkStatus?.rawValue, - "reason": self.reason ?? nil, - "risk_level": self.riskLevel?.rawValue, - "rule": self.rule ?? nil, - "seller_message": self.sellerMessage, - "type": self.type?.rawValue - ] - return try Node(node: object) - } +public struct StripeOutcome: Outcome, StripeModel { + public var networkStatus: NetworkStatus? + public var reason: String? + public var riskLevel: RiskLevel? + public var rule: String? + public var sellerMessage: String? + public var type: OutcomeType? } diff --git a/Sources/Stripe/Models/Charges/Refund.swift b/Sources/Stripe/Models/Charges/Refund.swift deleted file mode 100644 index 2957dd3..0000000 --- a/Sources/Stripe/Models/Charges/Refund.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// Refund.swift -// Stripe -// -// Created by Anthony Castelli on 4/15/17. -// -// - -import Foundation -import Vapor - - -/** - Refunds - https://stripe.com/docs/api/curl#charge_object-refunds - */ - -open class Refund: StripeModelProtocol { - - public private(set) var object: String? - public private(set) var hasMore: Bool - public private(set) var totalCount: Int? - public private(set) var url: String? - public private(set) var items: [RefundItem]? - - public required init(node: Node) throws { - self.object = try node.get("object") - self.items = try node.get("data") - self.hasMore = try node.get("has_more") - self.totalCount = try node.get("total_count") - self.url = try node.get("url") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "object": self.object, - "data": self.items, - "has_more": self.hasMore, - "total_count": self.totalCount, - "url": self.url - ] - return try Node(node: object) - } - -} diff --git a/Sources/Stripe/Models/Charges/RefundItem.swift b/Sources/Stripe/Models/Charges/RefundItem.swift deleted file mode 100644 index 3f97b48..0000000 --- a/Sources/Stripe/Models/Charges/RefundItem.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// RefundItem.swift -// Stripe -// -// Created by Anthony Castelli on 4/15/17. -// -// - -import Foundation -import Vapor - -open class RefundItem: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var amount: Int? - public private(set) var balanceTransactionId: String? - public private(set) var charge: String? - public private(set) var created: Date? - public private(set) var currency: StripeCurrency? - public private(set) var description: String? - public private(set) var metadata: Node? - public private(set) var reason: RefundReason? - public private(set) var receiptNumber: String? - public private(set) var status: StripeStatus? - - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.amount = try node.get("amount") - self.balanceTransactionId = try node.get("balance_transaction") - self.charge = try node.get("charge") - self.created = try node.get("created") - if let currency = node["currency"]?.string { - self.currency = StripeCurrency(rawValue: currency) - } - self.description = try node.get("description") - self.metadata = try node.get("metadata") - if let reason = node["reason"]?.string { - self.reason = RefundReason(rawValue: reason) - } - self.receiptNumber = try node.get("receipt_number") - if let status = node["status"]?.string { - self.status = StripeStatus(rawValue: status) - } - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "id": self.id, - "object": self.object, - "amount": self.amount, - "balance_transaction": self.balanceTransactionId, - "charge": self.charge , - "created": self.created, - "currency": self.currency?.rawValue, - "description": self.description, - "metadata": self.metadata, - "reason": self.reason?.rawValue, - "receipt_number": self.receiptNumber, - "status": self.status?.rawValue - ] - return try Node(node: object) - } -} diff --git a/Sources/Stripe/Models/Charges/Review.swift b/Sources/Stripe/Models/Charges/Review.swift new file mode 100644 index 0000000..5abaee3 --- /dev/null +++ b/Sources/Stripe/Models/Charges/Review.swift @@ -0,0 +1,33 @@ +// +// Review.swift +// Stripe +// +// Created by Andrew Edwards on 2/11/18. +// + +import Foundation + +/** + Review object + https://stripe.com/docs/api/curl#review_object + */ + +public protocol Review { + var id: String? { get } + var object: String? { get } + var charge: String? { get } + var created: Date? { get } + var livemode: Bool? { get } + var open: Bool? { get } + var reason: ReviewReason? { get } +} + +public struct StripeReview: Review, StripeModel { + public var id: String? + public var object: String? + public var charge: String? + public var created: Date? + public var livemode: Bool? + public var open: Bool? + public var reason: ReviewReason? +} diff --git a/Sources/Stripe/Models/Charges/ReviewReason.swift b/Sources/Stripe/Models/Charges/ReviewReason.swift new file mode 100644 index 0000000..1c69548 --- /dev/null +++ b/Sources/Stripe/Models/Charges/ReviewReason.swift @@ -0,0 +1,20 @@ +// +// ReviewReason.swift +// Stripe +// +// Created by Andrew Edwards on 2/11/18. +// + +/** + ReviewReason + https://stripe.com/docs/api/curl#review_object-reason + */ + +public enum ReviewReason: String, Codable { + case rule + case manual + case approved + case refunded + case refundedAsFraud = "refunded_as_fraud" + case disputed +} diff --git a/Sources/Stripe/Models/Connect/Account.swift b/Sources/Stripe/Models/Connect/Account.swift index 713bd46..3f2fb44 100644 --- a/Sources/Stripe/Models/Connect/Account.swift +++ b/Sources/Stripe/Models/Connect/Account.swift @@ -7,112 +7,77 @@ // import Foundation -import Vapor - /** - Account Model + Account object https://stripe.com/docs/api#account_object */ -open class ConnectAccount: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var businessName: String? - public private(set) var businessUrl: String? - public private(set) var chargesEnabled: Bool? - //public private(set) var transfersEnabled: Bool? - public private(set) var country: String? - public private(set) var debitNegativeBalances: Bool? - public private(set) var declineChargeOn: Node? - public private(set) var defaultCurrency: StripeCurrency? - public private(set) var detailsSubmitted: Bool? - public private(set) var displayName: String? - public private(set) var email: String? - public private(set) var externalAccounts: ExternalAccounts? - public private(set) var legalEntity: LegalEntity? - public private(set) var metadata: Node? - public private(set) var payoutSchedule: PayoutSchedule? - public private(set) var payoutStatementDescriptor: String? - public private(set) var payoutsEnabled: Bool? - public private(set) var productDescription: String? - public private(set) var statementDescriptor: String? - public private(set) var supportEmail: String? - public private(set) var supportPhone: String? - public private(set) var timezone: String? - public private(set) var tosAcceptance: TOSAcceptance? - public private(set) var type: String? - public private(set) var verification: Verification? - public private(set) var keys: Node? - - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.businessName = try node.get("business_name") - self.businessUrl = try node.get("business_url") - self.chargesEnabled = try node.get("charges_enabled") - // TODO - investigate, not getting value from response after rejecting account. - // https://stripe.com/docs/api/curl#reject_account - // self.transfersEnabled = try node.get("transfers_enabled") - self.country = try node.get("country") - self.debitNegativeBalances = try node.get("debit_negative_balances") - self.declineChargeOn = try node.get("decline_charge_on") - if let currency = node["default_currency"]?.string { - self.defaultCurrency = StripeCurrency(rawValue: currency) - } - self.detailsSubmitted = try node.get("details_submitted") - self.displayName = try node.get("display_name") - self.email = try node.get("email") - self.externalAccounts = try node.get("external_accounts") - self.legalEntity = try node.get("legal_entity") - self.metadata = try node.get("metadata") - self.payoutSchedule = try node.get("payout_schedule") - self.payoutStatementDescriptor = try node.get("payout_statement_descriptor") - self.payoutsEnabled = try node.get("payouts_enabled") - self.productDescription = try node.get("product_description") - self.statementDescriptor = try node.get("statement_descriptor") - self.supportEmail = try node.get("support_email") - self.supportPhone = try node.get("support_phone") - self.timezone = try node.get("timezone") - self.tosAcceptance = try node.get("tos_acceptance") - self.type = try node.get("type") - self.verification = try node.get("verification") - self.keys = try node.get("keys") - } +public protocol ConnectAccount { + associatedtype PS: PayoutSchedule + associatedtype TOSA: TOSAcceptance + associatedtype AV: AccountVerification + associatedtype LE: LegalEntity - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "id": self.id, - "object": self.object, - "business_name": self.businessName, - "business_url": self.businessUrl, - "charges_enabled": self.chargesEnabled, - //"transfers_enabled": self.transfersEnabled, - "country": self.country, - "debit_negative_balances": self.debitNegativeBalances, - "decline_charge_on": self.declineChargeOn, - "default_currency": self.defaultCurrency?.rawValue, - "details_submitted": self.detailsSubmitted, - "display_name": self.displayName, - "email": self.email, - "external_accounts": self.externalAccounts, - "legal_entity": self.legalEntity, - "metadata": self.metadata, - "payout_schedule": self.payoutSchedule, - "payout_statement_descriptor": self.payoutStatementDescriptor, - "payouts_enabled": self.payoutsEnabled, - "product_description": self.productDescription, - "statement_descriptor": self.statementDescriptor, - "support_email": self.supportEmail, - "support_phone": self.supportPhone, - "timezone": self.timezone, - "tos_acceptance": self.tosAcceptance, - "type": self.type, - "verification": self.verification, - "keys": self.keys - ] - - return try Node(node: object) - } + var id: String? { get } + var object: String? { get } + var businessLogo: String? { get } + var businessName: String? { get } + var businessUrl: String? { get } + var chargesEnabled: Bool? { get } + var country: String? { get } + var created: Date? { get } + var debitNegativeBalances: Bool? { get } + var declineChargeOn: [String: Bool]? { get } + var defaultCurrency: StripeCurrency? { get } + var detailsSubmitted: Bool? { get } + var displayName: String? { get } + var email: String? { get } + var externalAccounts: ExternalAccountsList? { get } + var legalEntity: LE? { get } + var metadata: [String: String]? { get } + var payoutSchedule: PS? { get } + var payoutStatementDescriptor: String? { get } + var payoutsEnabled: Bool? { get } + var productDescription: String? { get } + var statementDescriptor: String? { get } + var supportEmail: String? { get } + var supportPhone: String? { get } + var timezone: String? { get } + var tosAcceptance: TOSA? { get } + var type: ConnectedAccountType? { get } + var verification: AV? { get } + var transfersEnabled: Bool? { get } +} + +public struct StripeConnectAccount: ConnectAccount, StripeModel { + public var id: String? + public var object: String? + public var businessLogo: String? + public var businessName: String? + public var businessUrl: String? + public var chargesEnabled: Bool? + public var country: String? + public var created: Date? + public var debitNegativeBalances: Bool? + public var declineChargeOn: [String : Bool]? + public var defaultCurrency: StripeCurrency? + public var detailsSubmitted: Bool? + public var displayName: String? + public var email: String? + public var externalAccounts: ExternalAccountsList? + public var legalEntity: StripeConnectAccountLegalEntity? + public var metadata: [String: String]? + public var payoutSchedule: StripePayoutSchedule? + public var payoutStatementDescriptor: String? + public var payoutsEnabled: Bool? + public var productDescription: String? + public var statementDescriptor: String? + public var supportEmail: String? + public var supportPhone: String? + public var timezone: String? + public var tosAcceptance: StripeTOSAcceptance? + public var type: ConnectedAccountType? + public var verification: StripeAccountVerification? + public var transfersEnabled: Bool? } diff --git a/Sources/Stripe/Models/Connect/AccountList.swift b/Sources/Stripe/Models/Connect/AccountList.swift index 5b4dc4d..b46380a 100644 --- a/Sources/Stripe/Models/Connect/AccountList.swift +++ b/Sources/Stripe/Models/Connect/AccountList.swift @@ -6,29 +6,15 @@ // // -import Foundation -import Vapor +/** + Connected Account list + https://stripe.com/docs/api/curl#list_accounts + */ -open class AccountList: StripeModelProtocol { - public private(set) var object: String? - public private(set) var url: String? - public private(set) var hasMore: Bool? - public private(set) var items: [ConnectAccount]? - - public required 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) - } +public struct ConnectedAccountsList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeConnectAccount]? } diff --git a/Sources/Stripe/Models/Connect/AccountVerification.swift b/Sources/Stripe/Models/Connect/AccountVerification.swift new file mode 100644 index 0000000..45799d0 --- /dev/null +++ b/Sources/Stripe/Models/Connect/AccountVerification.swift @@ -0,0 +1,25 @@ +// +// AccountVerification.swift +// Stripe +// +// Created by Andrew Edwards on 12/8/17. +// + +import Foundation + +/** + Account verification + https://stripe.com/docs/api/curl#account_object-verification + */ + +public protocol AccountVerification { + var disabledReason: String? { get } + var dueBy: Date? { get } + var fieldsNeeded: [String]? { get } +} + +public struct StripeAccountVerification: AccountVerification, StripeModel { + public var disabledReason: String? + public var dueBy: Date? + public var fieldsNeeded: [String]? +} diff --git a/Sources/Stripe/Models/Connect/AdditionalOwner.swift b/Sources/Stripe/Models/Connect/AdditionalOwner.swift index 6f9339f..73ccdfa 100644 --- a/Sources/Stripe/Models/Connect/AdditionalOwner.swift +++ b/Sources/Stripe/Models/Connect/AdditionalOwner.swift @@ -6,34 +6,30 @@ // // -import Foundation -import Vapor +/** + Additional Owner object + https://stripe.com/docs/api/curl#account_object-legal_entity-additional_owners + */ -open class AdditionalOwner: StripeModelProtocol { +public protocol LegalEntityAdditionalOwner { + associatedtype SA: Address + associatedtype LEV: LegalEntityVerification - public private(set) var firstName: String? - public private(set) var lastName: String? - public private(set) var dateOfBirth: Node? - public private(set) var address: ShippingAddress? - public private(set) var verification: Verification? - - public required init(node: Node) throws { - self.firstName = try node.get("first_name") - self.lastName = try node.get("last_name") - self.dateOfBirth = try node.get("dob") - self.address = try node.get("address") - self.verification = try node.get("verification") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "first_name": self.firstName, - "last_name": self.lastName, - "dob": self.dateOfBirth, - "address": self.address, - "verification": self.verification - ] - - return try Node(node: object) - } + var firstName: String? { get } + var lastName: String? { get } + var dob: [String: Int]? { get } + var maidenName: String? { get } + var personalIdNumberProvided: Bool? { get } + var address: SA? { get } + var verification: LEV? { get } +} + +public struct StripeLegalEntityAdditionalOwner: LegalEntityAdditionalOwner, StripeModel { + public var firstName: String? + public var lastName: String? + public var dob: [String: Int]? + public var maidenName: String? + public var personalIdNumberProvided: Bool? + public var address: StripeAddress? + public var verification: StripeLegalEntityVerification? } diff --git a/Sources/Stripe/Models/Connect/ConnectLoginLink.swift b/Sources/Stripe/Models/Connect/ConnectLoginLink.swift index f5c00a3..44d6fd4 100644 --- a/Sources/Stripe/Models/Connect/ConnectLoginLink.swift +++ b/Sources/Stripe/Models/Connect/ConnectLoginLink.swift @@ -7,27 +7,20 @@ // import Foundation -import Vapor -open class ConnectLoginLink: StripeModelProtocol { - - public private(set) var object: String? - public private(set) var created: Date? - public private(set) var url: String? - - public required init(node: Node) throws { - self.object = try node.get("object") - self.created = try node.get("created") - self.url = try node.get("url") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "object": self.object, - "created": self.created, - "url": self.url - ] - - return try Node(node: object) - } +/** + Login link object + https://stripe.com/docs/api/curl#login_link_object + */ + +public protocol ConnectLoginLink { + var object: String? { get } + var created: Date? { get } + var url: String? { get } +} + +public struct StripeConnectLoginLink: ConnectLoginLink, StripeModel { + public var object: String? + public var created: Date? + public var url: String? } diff --git a/Sources/Stripe/Models/Connect/ConnectedAccountType.swift b/Sources/Stripe/Models/Connect/ConnectedAccountType.swift new file mode 100644 index 0000000..2826dea --- /dev/null +++ b/Sources/Stripe/Models/Connect/ConnectedAccountType.swift @@ -0,0 +1,13 @@ +// +// ConnectedAccountType.swift +// Stripe +// +// Created by Andrew Edwards on 7/9/17. +// +// + +// https://stripe.com/docs/api/curl#account_object-type +public enum ConnectedAccountType: String, Codable { + case custom + case standard +} diff --git a/Sources/Stripe/Models/Connect/ExternalAccounts.swift b/Sources/Stripe/Models/Connect/ExternalAccounts.swift index ed24758..a2a7bf7 100644 --- a/Sources/Stripe/Models/Connect/ExternalAccounts.swift +++ b/Sources/Stripe/Models/Connect/ExternalAccounts.swift @@ -6,49 +6,29 @@ // // -import Foundation -import Vapor +/** + External accounts list + https://stripe.com/docs/api/curl#account_object-external_accounts + */ -open class ExternalAccounts: StripeModelProtocol { +public struct ExternalAccountsList: StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + private var data: String? + public var cardAccounts: [StripeCard]? + public var bankAccounts: [StripeBankAccount]? - public private(set) var object: String? - public private(set) var url: String? - public private(set) var hasMore: Bool? - public private(set) var items: [Node]? - public private(set) var cardAccounts: [Card] = [] - public private(set) var bankAccounts: [BankAccount] = [] - - public required 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") - - // Seperate different type of accounts to make it easier to work with. - for item in items ?? [] { - - if let object = item["object"]?.string { - if object == "bank_account" { - let bank = try BankAccount(node: item) - bankAccounts.append(bank) - } - } - - if let object = item["object"]?.string { - if object == "card" { - cardAccounts.append(try Card(node: item)) - } - } - } - } - - 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) + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + object = try container.decodeIfPresent(String.self, forKey: .object) + hasMore = try container.decodeIfPresent(Bool.self, forKey: .hasMore) + totalCount = try container.decodeIfPresent(Int.self, forKey: .totalCount) + url = try container.decodeIfPresent(String.self, forKey: .url) + cardAccounts = try container.decodeIfPresent([StripeCard].self, forKey: .data)?.filter{ $0.object == "card" } + bankAccounts = try container.decodeIfPresent([StripeBankAccount].self, forKey: .data)?.filter{ $0.object == "bank_account" } } } + +public protocol ExternalAccount {} diff --git a/Sources/Stripe/Models/Connect/ExternalBankAccount.swift b/Sources/Stripe/Models/Connect/ExternalBankAccount.swift new file mode 100644 index 0000000..0901f25 --- /dev/null +++ b/Sources/Stripe/Models/Connect/ExternalBankAccount.swift @@ -0,0 +1,18 @@ +// +// ExternalBankAccount.swift +// Stripe +// +// Created by Andrew Edwards on 1/20/18. +// + +// Only used for creating/updating Account external sources. +// Not expected to be returned by the API at all. +public struct StripeExternalBankAccount: ExternalAccount, StripeModel { + public var object: String = "bank_account" + public var accountNumber: String? + public var country: String? + public var currency: StripeCurrency? + public var accountHolderName: String? + public var accountHolderType: String? + public var routingNumber: String? +} diff --git a/Sources/Stripe/Models/Connect/ExternalCardAccount.swift b/Sources/Stripe/Models/Connect/ExternalCardAccount.swift new file mode 100644 index 0000000..be81d47 --- /dev/null +++ b/Sources/Stripe/Models/Connect/ExternalCardAccount.swift @@ -0,0 +1,26 @@ +// +// ExternalCardAccount.swift +// Stripe +// +// Created by Andrew Edwards on 1/20/18. +// + +// Only used for creating/updating Account external sources. +// Not expected to be returned by the API at all. +public struct StripeExternalCardAccount: ExternalAccount, StripeModel { + public var object: String = "card" + public var currency: StripeCurrency? + public var defaultForCurrency: Bool? + public var expMonth: Int? + public var expYear: Int? + public var number: String? + public var addressCity: String? + public var addressCountry: String? + public var addressLine1: String? + public var addressLine2: String? + public var addressState: String? + public var addressZip: String? + public var cvc: String? + public var metadata: [String: String]? + public var name: String? +} diff --git a/Sources/Stripe/Models/Connect/LegalEntity.swift b/Sources/Stripe/Models/Connect/LegalEntity.swift index 815812a..27dbaba 100644 --- a/Sources/Stripe/Models/Connect/LegalEntity.swift +++ b/Sources/Stripe/Models/Connect/LegalEntity.swift @@ -7,70 +7,52 @@ // import Foundation -import Vapor /** - Legal Entity + Legal Entity object + https://stripe.com/docs/api/curl#account_object-legal_entity */ -open class LegalEntity: StripeModelProtocol { +public protocol LegalEntity { + associatedtype AO: LegalEntityAdditionalOwner + associatedtype A: Address + associatedtype LEA: LegalEntityVerification - public private(set) var additionalOwners: [AdditionalOwner]? - public private(set) var address: ShippingAddress? - public private(set) var businessName: String? - public private(set) var businessTaxIdProvided: Bool? - public private(set) var businessVATIdProvided: Bool? - public private(set) var dateOfBirth: Node? - public private(set) var firstName: String? - public private(set) var lastName: String? - public private(set) var gender: String? - public private(set) var maidenName: String? - public private(set) var personalAddress: ShippingAddress? - public private(set) var phoneNumber: String? - public private(set) var ssnLast4Provided: Bool? - public private(set) var taxIdRegistrar: String? - public private(set) var type: String? - public private(set) var verification: Verification? - - public required init(node: Node) throws { - self.additionalOwners = try node.get("additional_owners") - self.address = try node.get("address") - self.businessName = try node.get("business_name") - self.businessTaxIdProvided = try node.get("business_tax_id_provided") - self.businessVATIdProvided = try node.get("business_vat_id_provided") - self.dateOfBirth = try node.get("dob") - self.firstName = try node.get("first_name") - self.lastName = try node.get("last_name") - self.gender = try node.get("gender") - self.maidenName = try node.get("maiden_name") - self.personalAddress = try node.get("personal_address") - self.phoneNumber = try node.get("phone_number") - self.ssnLast4Provided = try node.get("ssn_last_4_provided") - self.taxIdRegistrar = try node.get("tax_id_registrar") - self.type = try node.get("type") - self.verification = try node.get("verification") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "additional_owners": additionalOwners, - "address": self.address, - "business_name": self.businessName, - "business_tax_id_provided": self.businessTaxIdProvided, - "business_vat_id_provided": self.businessVATIdProvided, - "dob": self.dateOfBirth, - "first_name": self.firstName, - "last_name": self.lastName, - "gender": self.gender, - "maiden_name": self.maidenName, - "personal_address": self.personalAddress, - "phone_number": self.phoneNumber, - "ssn_last_4_provided": self.ssnLast4Provided, - "tax_id_registrar": self.taxIdRegistrar, - "type": self.type, - "verification": self.verification - ] - - return try Node(node: object) - } + var additionalOwners: [AO]? { get } + var address: A? { get } + var businessName: String? { get } + var businessTaxIdProvided: Bool? { get } + var businessVATIdProvided: Bool? { get } + var dob: [String: Int]? { get } + var firstName: String? { get } + var lastName: String? { get } + var gender: String? { get } + var maidenName: String? { get } + var personalAddress: A? { get } + var phoneNumber: String? { get } + var personalIdNumberProvided: Bool? { get } + var ssnLast4Provided: Bool? { get } + var taxIdRegistrar: String? { get } + var type: String? { get } + var verification: LEA? { get } +} + +public struct StripeConnectAccountLegalEntity: LegalEntity, StripeModel { + public var additionalOwners: [StripeLegalEntityAdditionalOwner]? + public var address: StripeAddress? + public var businessName: String? + public var businessTaxIdProvided: Bool? + public var businessVATIdProvided: Bool? + public var dob: [String: Int]? + public var firstName: String? + public var lastName: String? + public var gender: String? + public var maidenName: String? + public var personalIdNumberProvided: Bool? + public var personalAddress: StripeAddress? + public var phoneNumber: String? + public var ssnLast4Provided: Bool? + public var taxIdRegistrar: String? + public var type: String? + public var verification: StripeLegalEntityVerification? } diff --git a/Sources/Stripe/Models/Connect/LegalEntityVerification.swift b/Sources/Stripe/Models/Connect/LegalEntityVerification.swift new file mode 100644 index 0000000..ddf02fc --- /dev/null +++ b/Sources/Stripe/Models/Connect/LegalEntityVerification.swift @@ -0,0 +1,26 @@ +// +// Verification.swift +// Stripe +// +// Created by Andrew Edwards on 7/8/17. +// +// + +/** + Legal entity verification object + https://stripe.com/docs/api/curl#account_object-legal_entity-verification + */ + +public protocol LegalEntityVerification { + var details: String? { get } + var detailsCode: LegalEntityVerificationState? { get } + var document: String? { get } + var status: LegalEntityVerificationStatus? { get } +} + +public struct StripeLegalEntityVerification: LegalEntityVerification, StripeModel { + public var details: String? + public var detailsCode: LegalEntityVerificationState? + public var document: String? + public var status: LegalEntityVerificationStatus? +} diff --git a/Sources/Stripe/Models/Connect/PayoutSchedule.swift b/Sources/Stripe/Models/Connect/PayoutSchedule.swift index 7612bfc..0faa50f 100644 --- a/Sources/Stripe/Models/Connect/PayoutSchedule.swift +++ b/Sources/Stripe/Models/Connect/PayoutSchedule.swift @@ -6,35 +6,21 @@ // // -import Foundation -import Vapor +/** + Payout Schedule object + https://stripe.com/docs/api/curl#account_object-payout_schedule + */ -open class PayoutSchedule: StripeModelProtocol { - - public private(set) var delayDays: Int? - public private(set) var interval: StripePayoutInterval? - public private(set) var monthlyAnchor: Int? - public private(set) var weeklyAnchor: StripeWeeklyAnchor? - - public required init(node: Node) throws { - - self.delayDays = try node.get("delay_days") - if let interval = node["interval"]?.string { - self.interval = StripePayoutInterval(rawValue: interval) - } - if let weeklyAnchor = node["weekly_anchor"]?.string { - self.weeklyAnchor = StripeWeeklyAnchor(rawValue: weeklyAnchor) - } - self.monthlyAnchor = try node.get("monthly_anchor") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "delay_days": self.delayDays, - "interval": self.interval, - "weekly_anchor": self.weeklyAnchor, - "monthly_anchor": self.monthlyAnchor - ] - return try Node(node: object) - } +public protocol PayoutSchedule { + var delayDays: Int? { get } + var interval: StripePayoutInterval? { get } + var monthlyAnchor: Int? { get } + var weeklyAnchor: StripeWeeklyAnchor? { get } +} + +public struct StripePayoutSchedule: PayoutSchedule, StripeModel { + public var delayDays: Int? + public var interval: StripePayoutInterval? + public var monthlyAnchor: Int? + public var weeklyAnchor: StripeWeeklyAnchor? } diff --git a/Sources/Stripe/Models/Connect/TOSAcceptance.swift b/Sources/Stripe/Models/Connect/TOSAcceptance.swift index 6d7f077..63caf4f 100644 --- a/Sources/Stripe/Models/Connect/TOSAcceptance.swift +++ b/Sources/Stripe/Models/Connect/TOSAcceptance.swift @@ -7,27 +7,20 @@ // import Foundation -import Vapor -open class TOSAcceptance: StripeModelProtocol { - - public private(set) var timestamp: Date? - public private(set) var ip: String? - public private(set) var userAgent: String? - - public required init(node: Node) throws { - self.timestamp = try node.get("timestamp") - self.ip = try node.get("ip") - self.userAgent = try node.get("user_agent") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "timestamp": self.timestamp, - "ip": self.ip, - "user_agent": self.userAgent - ] - - return try Node(node: object) - } +/** + Terms of acceptance object + https://stripe.com/docs/api/curl#account_object-tos_acceptance + */ + +public protocol TOSAcceptance { + var date: Date? { get } + var ip: String? { get } + var userAgent: String? { get } +} + +public struct StripeTOSAcceptance: TOSAcceptance, StripeModel { + public var date: Date? + public var ip: String? + public var userAgent: String? } diff --git a/Sources/Stripe/Models/Connect/Verification.swift b/Sources/Stripe/Models/Connect/Verification.swift deleted file mode 100644 index e19fe7e..0000000 --- a/Sources/Stripe/Models/Connect/Verification.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// Verification.swift -// Stripe -// -// Created by Andrew Edwards on 7/8/17. -// -// - -import Foundation -import Vapor - -open class Verification: StripeModelProtocol { - public private(set) var details: String? - public private(set) var detailsCode: ConnectLegalEntityVerificationState? - public private(set) var document: String? - public private(set) var status: ConnectLegalEntityVerificationStatus? - - public required init(node: Node) throws { - self.details = try node.get("details") - if let detailsCode = node["details_code"]?.string { - self.detailsCode = ConnectLegalEntityVerificationState(rawValue: detailsCode) - } - self.document = try node.get("document") - if let status = node["status"]?.string { - self.status = ConnectLegalEntityVerificationStatus(rawValue: status) - } - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "details": self.details, - "details_code": self.detailsCode?.rawValue, - "document": self.document, - "status": self.status?.rawValue - ] - - return try Node(node: object) - } -} diff --git a/Sources/Stripe/Models/Coupons/Coupon.swift b/Sources/Stripe/Models/Coupons/Coupon.swift index 6bc189b..250455f 100644 --- a/Sources/Stripe/Models/Coupons/Coupon.swift +++ b/Sources/Stripe/Models/Coupons/Coupon.swift @@ -7,74 +7,42 @@ // import Foundation -import Vapor /** Coupon Model https://stripe.com/docs/api/curl#coupon_object */ -open class Coupon: StripeModelProtocol { - public private(set) var id: String? - public private(set) var amountOff: Int? - public private(set) var currency: StripeCurrency? - public private(set) var duration: StripeDuration? - public private(set) var durationInMonths: Int? - public private(set) var maxRedemptions: Int? - public private(set) var percentOff: Int? - public private(set) 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 private(set) var metadata: Node? - - public required 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) - } - 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) - } + +public protocol Coupon { + var id: String? { get } + var object: String? { get } + var amountOff: Int? { get } + var created: Date? { get } + var currency: StripeCurrency? { get } + var duration: StripeDuration? { get } + var durationInMonths: Int? { get } + var livemode: Bool? { get } + var maxRedemptions: Int? { get } + var metadata: [String: String]? { get } + var percentOff: Int? { get } + var redeemBy: Date? { get } + var timesRedeemed: Int? { get } + var valid: Bool? { get } +} + +public struct StripeCoupon: Coupon, StripeModel { + public var id: String? + public var object: String? + public var amountOff: Int? + public var created: Date? + public var currency: StripeCurrency? + public var duration: StripeDuration? + public var durationInMonths: Int? + public var livemode: Bool? + public var maxRedemptions: Int? + public var metadata: [String: String]? + public var percentOff: Int? + public var redeemBy: Date? + public var timesRedeemed: Int? + public var valid: Bool? } diff --git a/Sources/Stripe/Models/Coupons/CouponList.swift b/Sources/Stripe/Models/Coupons/CouponList.swift index 47d807a..54b60f3 100644 --- a/Sources/Stripe/Models/Coupons/CouponList.swift +++ b/Sources/Stripe/Models/Coupons/CouponList.swift @@ -6,29 +6,15 @@ // // -import Foundation -import Vapor +/** + Coupons List + https://stripe.com/docs/api#list_coupons + */ -open class CouponList: StripeModelProtocol { - public private(set) var object: String? - public private(set) var url: String? - public private(set) var hasMore: Bool? - public private(set) var items: [Coupon]? - - public required 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) - } +public struct CouponsList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeCoupon]? } diff --git a/Sources/Stripe/Models/Coupons/Discount.swift b/Sources/Stripe/Models/Coupons/Discount.swift new file mode 100644 index 0000000..5150b6c --- /dev/null +++ b/Sources/Stripe/Models/Coupons/Discount.swift @@ -0,0 +1,35 @@ +// +// Discount.swift +// Stripe +// +// Created by Andrew Edwards on 6/7/17. +// +// + +import Foundation +import Vapor + +/** + Discount Model + https://stripe.com/docs/api/curl#discount_object + */ + +public protocol Discount { + associatedtype C: Coupon + + var object: String? { get } + var coupon: C? { get } + var customer: String? { get } + var end: Date? { get } + var start: Date? { get } + var subscription: String? { get } +} + +public struct StripeDiscount: Discount, StripeModel { + public var object: String? + public var coupon: StripeCoupon? + public var customer: String? + public var end: Date? + public var start: Date? + public var subscription: String? +} diff --git a/Sources/Stripe/Models/Customer/Customer.swift b/Sources/Stripe/Models/Customer/Customer.swift index b8fdffd..6f7696b 100644 --- a/Sources/Stripe/Models/Customer/Customer.swift +++ b/Sources/Stripe/Models/Customer/Customer.swift @@ -7,82 +7,51 @@ // import Foundation -import Vapor /** Customer Model https://stripe.com/docs/api/curl#customer_object */ -open class Customer: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var created: Date? - public private(set) var delinquent: Bool? - public private(set) var isLive: Bool? - public private(set) var currency: StripeCurrency? - public private(set) var sources: SourceList? - public private(set) var subscriptions: SubscriptionList? - public private(set) var discount: Discount? - /** - Only these values are mutable/updatable. - https://stripe.com/docs/api/curl#update_customer - */ - - public private(set) var accountBalance: Int? - public private(set) var bussinessVATId: String? - public private(set) var coupon: String? - public private(set) var defaultSourceId: String? - public private(set) var description: String? - public private(set) var email: String? - public private(set) var metadata: Node? - public private(set) var shipping: ShippingLabel? + +public protocol Customer { + associatedtype S: Shipping + associatedtype SRCL: List + associatedtype SL: List + associatedtype D: Discount - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.accountBalance = try node.get("account_balance") - self.created = try node.get("created") - self.email = try node.get("email") - self.bussinessVATId = try node.get("business_vat_id") - self.defaultSourceId = try node.get("default_source") - self.description = try node.get("description") - self.delinquent = try node.get("delinquent") - self.isLive = try node.get("livemode") - self.coupon = try node.get("coupon") - self.metadata = try node.get("metadata") - self.discount = try node.get("discount") - if let currency = node["currency"]?.string { - self.currency = StripeCurrency(rawValue: currency) - } - - if let _ = node["shipping"]?.object { - self.shipping = try node.get("shipping") - } - - self.sources = try node.get("sources") - self.subscriptions = try node.get("subscriptions") - } + var id: String? { get } + var object: String? { get } + var accountBalance: Int? { get } + var bussinessVATId: String? { get } + var created: Date? { get } + var currency: StripeCurrency? { get } + var defaultSource: String? { get } + var delinquent: Bool? { get } + var description: String? { get } + var discount: D? { get } + var email: String? { get } + var livemode: Bool? { get } + var metadata: [String: String]? { get } + var shipping: S? { get } + var sources: SRCL? { get } + var subscriptions: SL? { get } +} - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "id": self.id, - "object": self.object, - "account_balance": self.accountBalance, - "created": self.created, - "email": self.email, - "business_vat_id": self.bussinessVATId, - "description": self.description, - "delinquent": self.delinquent, - "livemode": self.isLive, - "coupon": self.coupon, - "metadata": self.metadata, - "currency": self.currency?.rawValue, - "shipping": self.shipping, - "sources": self.sources, - "subscriptions": self.subscriptions, - "discount": self.discount - ] - return try Node(node: object) - } +public struct StripeCustomer: Customer, StripeModel { + public var id: String? + public var object: String? + public var accountBalance: Int? + public var bussinessVATId: String? + public var created: Date? + public var currency: StripeCurrency? + public var defaultSource: String? + public var delinquent: Bool? + public var description: String? + public var discount: StripeDiscount? + public var email: String? + public var livemode: Bool? + public var metadata: [String : String]? + public var shipping: ShippingLabel? + public var sources: StripeSourcesList? + public var subscriptions: SubscriptionList? } diff --git a/Sources/Stripe/Models/Customer/CustomerList.swift b/Sources/Stripe/Models/Customer/CustomerList.swift index 4313636..f2df4e6 100644 --- a/Sources/Stripe/Models/Customer/CustomerList.swift +++ b/Sources/Stripe/Models/Customer/CustomerList.swift @@ -6,29 +6,15 @@ // // -import Foundation -import Vapor +/** + Customers List + https://stripe.com/docs/api/curl#list_customers + */ -open class CustomerList: StripeModelProtocol { - public private(set) var object: String? - public private(set) var url: String? - public private(set) var hasMore: Bool? - public let items: [Customer]? - - public required 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) - } +public struct CustomersList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeCustomer]? } diff --git a/Sources/Stripe/Models/Discount/Discount.swift b/Sources/Stripe/Models/Discount/Discount.swift deleted file mode 100644 index 83fcf44..0000000 --- a/Sources/Stripe/Models/Discount/Discount.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// File.swift -// Stripe -// -// Created by Andrew Edwards on 6/7/17. -// -// - -import Foundation -import Vapor - -/** - Discount Model - https://stripe.com/docs/api/curl#discount_object - */ -open class Discount: StripeModelProtocol { - - public private(set) var object: String? - public private(set) var coupon: Coupon? - public private(set) var customer: String? - public private(set) var end: Date? - public private(set) var start: Date? - public private(set) var subscription: String? - - - public required init(node: Node) throws { - - self.object = try node.get("object") - self.coupon = try node.get("coupon") - self.customer = try node.get("customer") - self.end = try node.get("end") - self.start = try node.get("start") - self.subscription = try node.get("subscription") - } - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "object": self.object, - "coupon": self.coupon, - "customer": self.customer, - "end": self.end, - "start": self.start, - "subscription": self.subscription - ] - - return try Node(node: object) - } -} diff --git a/Sources/Stripe/Models/Dispute/Dispute.swift b/Sources/Stripe/Models/Dispute/Dispute.swift index d75a792..013da30 100644 --- a/Sources/Stripe/Models/Dispute/Dispute.swift +++ b/Sources/Stripe/Models/Dispute/Dispute.swift @@ -7,188 +7,46 @@ // import Foundation -import Vapor -open class Dispute: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var amount: Int? - public private(set) var balanceTransactions: [BalanceTransactionItem]? - public private(set) var charge: String? - public private(set) var created: Date? - public private(set) var currency: StripeCurrency? - public private(set) var evidence: DisputeEvidence? - public private(set) var evidenceDetails: DisputeEvidenceDetails? - public private(set) var isChargeRefundable: Bool? - public private(set) var isLive: Bool? - public private(set) var metadata: Node? - public private(set) var disputeReason: DisputeReason? - public private(set) var disputeStatus: DisputeStatus? - - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.amount = try node.get("amount") - self.balanceTransactions = try node.get("balance_transactions") - self.charge = try node.get("charge") - self.created = try node.get("created") - if let currency = node["currency"]?.string { - self.currency = StripeCurrency(rawValue: currency) - } - self.evidence = try node.get("evidence") - self.evidenceDetails = try node.get("evidence_details") - self.isChargeRefundable = try node.get("is_charge_refundable") - self.isLive = try node.get("livemode") - self.metadata = try node.get("metadata") - if let disputereason = node["resaon"]?.string { - self.disputeReason = DisputeReason(rawValue: disputereason) - } - if let disputestatus = node["status"]?.string { - self.disputeStatus = DisputeStatus(rawValue: disputestatus) - } - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "id": self.id, - "object": self.object, - "amount": self.amount, - "balance_transactions": self.balanceTransactions, - "charge": self.charge, - "created": self.created, - "currency": self.currency, - "evidence": self.evidence, - "evidence_details": self.evidenceDetails, - "is_charge_refundable": self.isChargeRefundable, - "livemode": self.isLive, - "metadata": self.metadata, - "reason": self.disputeReason?.rawValue, - "status": self.disputeStatus?.rawValue - ] - - return try Node(node: object) - } -} +/** + Dispute object + https://stripe.com/docs/api#dispute_object + */ -public final class DisputeEvidence: StripeModelProtocol { - - public private(set) var accessActivityLog: String? - public private(set) var billingAddress: String? - public private(set) var cancellationPolicy: String? - public private(set) var cancellationPolicyDisclosure: String? - public private(set) var cancellationRebuttal: String? - public private(set) var customerCommunication: String? - public private(set) var customerEmailAddress: String? - public private(set) var customerName: String? - public private(set) var customerPurchaseIp: String? - public private(set) var customerSignature: String? - public private(set) var duplicateChargeDocumantation: String? - public private(set) var duplicateChargeExplination: String? - public private(set) var duplicateChargeId: String? - public private(set) var productDescription: String? - public private(set) var receipt: String? - public private(set) var refundPolicy: String? - public private(set) var refundPolicyDisclosure: String? - public private(set) var refundRefusalExplination: String? - public private(set) var serviceDate: String? - public private(set) var serviceDocumentation: String? - public private(set) var shippingAddress: String? - public private(set) var shippingCarrier: String? - public private(set) var shippingDate: String? - public private(set) var shippingDocumentation: String? - public private(set) var shippingTrackingNumber: String? - public private(set) var uncatagorizedFile: String? - public private(set) var uncatagorizedText: String? - - public init(node: Node) throws { - self.accessActivityLog = try node.get("access_activity_log") - self.billingAddress = try node.get("billing_address") - self.cancellationPolicy = try node.get("cancellation_policy") - self.cancellationPolicyDisclosure = try node.get("cancellation_policy_disclosure") - self.cancellationRebuttal = try node.get("cancellation_rebuttal") - self.customerCommunication = try node.get("customer_communication") - self.customerEmailAddress = try node.get("customer_email_address") - self.customerName = try node.get("customer_name") - self.customerPurchaseIp = try node.get("customer_purchase_ip") - self.customerSignature = try node.get("customer_signature") - self.duplicateChargeDocumantation = try node.get("duplicate_charge_documentation") - self.duplicateChargeExplination = try node.get("duplicate_charge_explanation") - self.duplicateChargeId = try node.get("duplicate_charge_id") - self.productDescription = try node.get("product_description") - self.receipt = try node.get("receipt") - self.refundPolicy = try node.get("refund_policy") - self.refundPolicyDisclosure = try node.get("refund_policy_disclosure") - self.refundRefusalExplination = try node.get("refund_refusal_explanation") - self.serviceDate = try node.get("service_date") - self.serviceDocumentation = try node.get("service_documentation") - self.shippingAddress = try node.get("shipping_address") - self.shippingCarrier = try node.get("shipping_carrier") - self.shippingDate = try node.get("shipping_date") - self.shippingDocumentation = try node.get("shipping_documentation") - self.shippingTrackingNumber = try node.get("shipping_tracking_number") - self.uncatagorizedFile = try node.get("uncategorized_file") - self.uncatagorizedText = try node.get("uncategorized_text") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "access_activity_log": self.accessActivityLog, - "billing_address": self.billingAddress, - "cancellation_policy": self.cancellationPolicy, - "cancellation_policy_disclosure": self.cancellationPolicyDisclosure, - "cancellation_rebuttal": self.cancellationRebuttal, - "customer_communication": self.customerCommunication, - "customer_email_address": self.customerEmailAddress, - "customer_name": self.customerName, - "customer_purchase_ip": self.customerPurchaseIp, - "customer_signature": self.customerSignature, - "duplicate_charge_documentation": self.duplicateChargeDocumantation, - "duplicate_charge_explanation": self.duplicateChargeExplination, - "duplicate_charge_id": self.duplicateChargeId, - "product_description": self.productDescription, - "receipt": self.receipt, - "refund_policy": self.refundPolicy, - "refund_policy_disclosure": self.refundPolicyDisclosure, - "refund_refusal_explanation": self.refundRefusalExplination, - "service_date": self.serviceDate, - "service_documentation": self.serviceDocumentation, - "shipping_address": self.shippingAddress, - "shipping_carrier": self.shippingCarrier, - "shipping_date": self.shippingDate, - "shipping_documentation": self.shippingDocumentation, - "shipping_tracking_number": self.shippingTrackingNumber, - "uncategorized_file": self.uncatagorizedFile, - "uncategorized_text": self.uncatagorizedText - ] - - return try Node(node: object) - } +public protocol Dispute { + associatedtype BTI: BalanceTransactionItem + associatedtype DE: DisputeEvidence + associatedtype DED: DisputeEvidenceDetails + + var id: String? { get } + var object: String? { get } + var amount: Int? { get } + var balanceTransactions: [BTI]? { get } + var charge: String? { get } + var created: Date? { get } + var currency: StripeCurrency? { get } + var evidence: DE? { get } + var evidenceDetails: DED? { get } + var isChargeRefundable: Bool? { get } + var livemode: Bool? { get } + var metadata: [String: String]? { get } + var reason: DisputeReason? { get } + var status: DisputeStatus? { get } } -public final class DisputeEvidenceDetails: StripeModelProtocol { - - public private(set) var dueBy: Date? - public private(set) var hasEvidence: Bool? - public private(set) var pastDue: Bool? - public private(set) var submissionCount: Int? - - public init(node: Node) throws { - - self.dueBy = try node.get("due_by") - self.hasEvidence = try node.get("has_evidence") - self.pastDue = try node.get("past_due") - self.submissionCount = try node.get("submission_count") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "due_by": self.dueBy, - "has_evidence": self.hasEvidence, - "past_due": self.pastDue, - "submission_count": self.submissionCount - ] - - return try Node(node: object) - } +public struct StripeDispute: Dispute, StripeModel { + public var id: String? + public var object: String? + public var amount: Int? + public var balanceTransactions: [StripeBalanceTransactionItem]? + public var charge: String? + public var created: Date? + public var currency: StripeCurrency? + public var evidence: StripeDisputeEvidence? + public var evidenceDetails: StripeDisputeEvidenceDetails? + public var isChargeRefundable: Bool? + public var livemode: Bool? + public var metadata: [String: String]? + public var reason: DisputeReason? + public var status: DisputeStatus? } diff --git a/Sources/Stripe/Models/Dispute/DisputeEvidence.swift b/Sources/Stripe/Models/Dispute/DisputeEvidence.swift new file mode 100644 index 0000000..9ba272b --- /dev/null +++ b/Sources/Stripe/Models/Dispute/DisputeEvidence.swift @@ -0,0 +1,71 @@ +// +// DisputeEvidence.swift +// Stripe +// +// Created by Andrew Edwards on 12/7/17. +// + +/** + DisputeEvidence object + https://stripe.com/docs/api#dispute_evidence_object + */ + +public protocol DisputeEvidence { + var accessActivityLog: String? { get } + var billingAddress: String? { get } + var cancellationPolicy: String? { get } + var cancellationPolicyDisclosure: String? { get } + var cancellationRebuttal: String? { get } + var customerCommunication: String? { get } + var customerEmailAddress: String? { get } + var customerName: String? { get } + var customerPurchaseIp: String? { get } + var customerSignature: String? { get } + var duplicateChargeDocumentation: String? { get } + var duplicateChargeExplanation: String? { get } + var duplicateChargeId: String? { get } + var productDescription: String? { get } + var receipt: String? { get } + var refundPolicy: String? { get } + var refundPolicyDisclosure: String? { get } + var refundRefusalExplanation: String? { get } + var serviceDate: String? { get } + var serviceDocumentation: String? { get } + var shippingAddress: String? { get } + var shippingCarrier: String? { get } + var shippingDate: String? { get } + var shippingDocumentation: String? { get } + var shippingTrackingNumber: String? { get } + var uncategorizedFile: String? { get } + var uncategorizedText: String? { get } +} + +public struct StripeDisputeEvidence: DisputeEvidence, StripeModel { + public var accessActivityLog: String? + public var billingAddress: String? + public var cancellationPolicy: String? + public var cancellationPolicyDisclosure: String? + public var cancellationRebuttal: String? + public var customerCommunication: String? + public var customerEmailAddress: String? + public var customerName: String? + public var customerPurchaseIp: String? + public var customerSignature: String? + public var duplicateChargeDocumentation: String? + public var duplicateChargeExplanation: String? + public var duplicateChargeId: String? + public var productDescription: String? + public var receipt: String? + public var refundPolicy: String? + public var refundPolicyDisclosure: String? + public var refundRefusalExplanation: String? + public var serviceDate: String? + public var serviceDocumentation: String? + public var shippingAddress: String? + public var shippingCarrier: String? + public var shippingDate: String? + public var shippingDocumentation: String? + public var shippingTrackingNumber: String? + public var uncategorizedFile: String? + public var uncategorizedText: String? +} diff --git a/Sources/Stripe/Models/Dispute/DisputeEvidenceDetails.swift b/Sources/Stripe/Models/Dispute/DisputeEvidenceDetails.swift new file mode 100644 index 0000000..b99b1f7 --- /dev/null +++ b/Sources/Stripe/Models/Dispute/DisputeEvidenceDetails.swift @@ -0,0 +1,27 @@ +// +// DisputeEvidenceDetails.swift +// Stripe +// +// Created by Andrew Edwards on 12/7/17. +// + +import Foundation + +/** + DisputeEvidenceDetails object + https://stripe.com/docs/api#dispute_object-evidence_details + */ + +public protocol DisputeEvidenceDetails { + var dueBy: Date? { get } + var hasEvidence: Bool? { get } + var pastDue: Bool? { get } + var submissionCount: Int? { get } +} + +public struct StripeDisputeEvidenceDetails: DisputeEvidenceDetails, StripeModel { + public var dueBy: Date? + public var hasEvidence: Bool? + public var pastDue: Bool? + public var submissionCount: Int? +} diff --git a/Sources/Stripe/Models/Dispute/DisputeList.swift b/Sources/Stripe/Models/Dispute/DisputeList.swift index 5a06c6e..526d0db 100644 --- a/Sources/Stripe/Models/Dispute/DisputeList.swift +++ b/Sources/Stripe/Models/Dispute/DisputeList.swift @@ -6,30 +6,15 @@ // // -import Foundation -import Vapor +/** + Disputes List + https://stripe.com/docs/api#list_disputes + */ -open class DisputeList: StripeModelProtocol { - - public private(set) var object: String? - public private(set) var url: String? - public private(set) var hasMore: Bool? - public private(set) var items: [Dispute]? - - public required 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) - } +public struct DisputesList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeDispute]? } diff --git a/Sources/Stripe/Helpers/DisputeReason.swift b/Sources/Stripe/Models/Dispute/DisputeReason.swift similarity index 73% rename from Sources/Stripe/Helpers/DisputeReason.swift rename to Sources/Stripe/Models/Dispute/DisputeReason.swift index 6da36d0..f07f4e3 100644 --- a/Sources/Stripe/Helpers/DisputeReason.swift +++ b/Sources/Stripe/Models/Dispute/DisputeReason.swift @@ -6,17 +6,16 @@ // // -import Foundation - -public enum DisputeReason: String { - case duplicate = "duplicate" - case fraudulent = "fraudulent" +// https://stripe.com/docs/api#dispute_object-reason +public enum DisputeReason: String, Codable { + case duplicate + case fraudulent case subscriptionCanceled = "subscription_canceled" case productUnacceptable = "product_unacceptable" case productNotReceived = "product_not_received" - case unrecognized = "unrecognized" + case unrecognized case creditNotProcessed = "credit_not_processed" - case general = "general" + case general case incorrectAccountDetails = "incorrect_account_details" case insufficientFunds = "insufficient_funds" case bankCannotProcess = "bank_cannot_process" @@ -24,13 +23,14 @@ public enum DisputeReason: String { case customerInitiated = "customer_initiated" } -public enum DisputeStatus: String { +// https://stripe.com/docs/api#dispute_object-status +public enum DisputeStatus: String, Codable { case warningNeedsResponse = "warning_needs_response" case warningUnderReview = "warning_under_review" case warningClosed = "warning_closed" case needsResponse = "needs_response" case underReview = "under_review" case chargeRefunded = "charge_refunded" - case won = "won" - case lost = "lost" + case won + case lost } diff --git a/Sources/Stripe/Models/EphemeralKey/EphemeralKey.swift b/Sources/Stripe/Models/EphemeralKey/EphemeralKey.swift index 7bec945..8944763 100644 --- a/Sources/Stripe/Models/EphemeralKey/EphemeralKey.swift +++ b/Sources/Stripe/Models/EphemeralKey/EphemeralKey.swift @@ -6,39 +6,23 @@ // import Foundation -import Vapor -open class EphemeralKey: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var associatedObjects: Node? - public private(set) var created: Date? - public private(set) var expires: Date? - public private(set) var isLive: Bool? - public private(set) var secret: String? - - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.associatedObjects = try node.get("associated_objects") - self.created = try node.get("created") - self.expires = try node.get("expires") - self.isLive = try node.get("livemode") - self.secret = try node.get("secret") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "id": self.id, - "object": self.object, - "associated_objects": self.associatedObjects, - "created": self.created, - "expires": self.expires, - "livemode": self.isLive, - "secret": self.secret - ] - - return try Node(node: object) - } +public protocol EphemeralKey { + var id: String? { get } + var object: String? { get } + var associatedObjects: [String: String]? { get } + var created: Date? { get } + var expires: Date? { get } + var livemode: Bool? { get } + var secret: String? { get } +} + +public struct StripeEphemeralKey: EphemeralKey, StripeModel { + public var id: String? + public var object: String? + public var associatedObjects: [String : String]? + public var created: Date? + public var expires: Date? + public var livemode: Bool? + public var secret: String? } diff --git a/Sources/Stripe/Models/Fee.swift b/Sources/Stripe/Models/Fee.swift deleted file mode 100644 index 51d4b9b..0000000 --- a/Sources/Stripe/Models/Fee.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// Fee.swift -// Stripe -// -// Created by Anthony Castelli on 4/15/17. -// -// - -import Foundation -import Vapor - -open class Fee: StripeModelProtocol { - - public private(set) var amount: Int? - public private(set) var currency: StripeCurrency? - public private(set) var description: String? - public private(set) var type: ActionType? - - public required init(node: Node) throws { - self.amount = try node.get("amount") - if let currency = node["currency"]?.string { - self.currency = StripeCurrency(rawValue: currency) - } - self.description = try node.get("description") - if let type = node["type"]?.string { - self.type = ActionType(rawValue: type) - } - } - - public func makeNode(in context: Context?) throws -> Node { - - let object: [String : Any?] = [ - "amount": self.amount, - "currency": self.currency, - "description": self.description, - "type": self.type?.rawValue - ] - return try Node(node: object) - } -} diff --git a/Sources/Stripe/Models/Inventory.swift b/Sources/Stripe/Models/Inventory.swift deleted file mode 100644 index 4c6f9dc..0000000 --- a/Sources/Stripe/Models/Inventory.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// Inventory.swift -// Stripe -// -// Created by Andrew Edwards on 8/22/17. -// -// - -import Foundation -import Vapor - -open class Inventory: StripeModelProtocol { - - public private(set) var quantity: Int? - public private(set) var type: InventoryType? - public private(set) var value: InventoryTypeValue? - - public required 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) - } -} diff --git a/Sources/Stripe/Models/Invoices/Invoice.swift b/Sources/Stripe/Models/Invoices/Invoice.swift index 150d492..00c1df4 100644 --- a/Sources/Stripe/Models/Invoices/Invoice.swift +++ b/Sources/Stripe/Models/Invoices/Invoice.swift @@ -7,154 +7,87 @@ // import Foundation -import Vapor /** - Invoice Model + Invoice object https://stripe.com/docs/api#invoice_object */ -open class Invoice: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var amountDue: Int - public private(set) var applicationFee: Int? - public private(set) var attemptCount: Int - public private(set) var hasAttemptedCharge: Bool - public private(set) var charge: String? - public private(set) var isClosed: Bool - public private(set) var customer: String? - public private(set) var date: Date? - public private(set) var description: String? - public private(set) var discount: String? - public private(set) var endingBalance: Int? - public private(set) var isForgiven: Bool - public private(set) var isLiveMode: Bool - public private(set) var isPaid: Bool - - public private(set) var nextPaymentAttempt: Date? - public private(set) var periodStart: Date? - public private(set) var periodEnd: Date? - - public private(set) var receiptNumber: String? - public private(set) var startingBalance: Int? - - public private(set) var statementDescriptor: String? - public private(set) var subscription: String? - - public private(set) var subtotal: Int - public private(set) var total: Int - - public private(set) var tax: Int? - public private(set) var taxPercent: Int? - - public private(set) var webhooksDeliveredAt: Date? - - public private(set) var metadata: Node? - - public private(set) var lines: [InvoiceLineItem]? - public private(set) var currency: StripeCurrency? - - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.amountDue = try node.get("amount_due") - self.applicationFee = try node.get("application_fee") - self.attemptCount = try node.get("attempt_count") - self.hasAttemptedCharge = try node.get("attempted") - self.charge = try node.get("charge") - self.isClosed = try node.get("closed") - self.customer = try node.get("customer") - self.date = try node.get("date") - self.description = try node.get("description") - self.discount = try node.get("discount") - self.endingBalance = try node.get("ending_balance") - self.isForgiven = try node.get("forgiven") - self.isLiveMode = try node.get("livemode") - self.isPaid = try node.get("paid") - self.nextPaymentAttempt = try node.get("next_payment_attempt") - self.periodStart = try node.get("period_start") - self.periodEnd = try node.get("period_end") - self.receiptNumber = try node.get("receipt_number") - self.startingBalance = try node.get("starting_balance") - self.statementDescriptor = try node.get("statement_descriptor") - self.subscription = try node.get("subscription") - self.subtotal = try node.get("subtotal") - self.total = try node.get("total") - self.tax = try node.get("tax") - self.taxPercent = try node.get("tax_percent") - - self.webhooksDeliveredAt = try node.get("webhooks_delivered_at") - - self.metadata = try node.get("metadata") - - if let currency = node["currency"]?.string { - self.currency = StripeCurrency(rawValue: currency) - } - - if let lines = node["lines"]?.object, let data = lines["data"]?.array { - self.lines = try data.map({ try InvoiceLineItem(node: $0) }) - } - } + +public protocol Invoice { + associatedtype D: Discount + associatedtype L: List - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "id": self.id, - "object": self.object, - "amount_due": self.amountDue, - "application_fee": self.applicationFee, - "attempt_count": self.attemptCount, - "attempted": self.hasAttemptedCharge, - "charge": self.charge, - "closed": self.isClosed, - "customer": self.customer, - "date": self.date, - "description": self.description, - "discount": self.discount, - "ending_balance": self.endingBalance, - "forgiven": self.isForgiven, - "livemode": self.isLiveMode, - "paid": self.isPaid, - "next_payment_attempt": self.nextPaymentAttempt, - "period_start": self.periodStart, - "period_end": self.periodEnd, - "receipt_number": self.receiptNumber, - "starting_balance": self.startingBalance, - "statement_descriptor": self.statementDescriptor, - "subscription": self.subscription, - "subtotal": self.subtotal, - "total": self.total, - "tax": self.tax, - "tax_percent": self.taxPercent, - "webhooks_delivered_at": self.webhooksDeliveredAt, - - "metadata": self.metadata, - - "currency": self.currency?.rawValue - ] - - return try Node(node: object) - } + var id: String? { get } + var object: String? { get } + var amountDue: Int? { get } + var applicationFee: Int? { get } + var attemptCount: Int? { get } + var attempted: Bool? { get } + var billing: String? { get } + var charge: String? { get } + var closed: Bool? { get } + var currency: StripeCurrency? { get } + var customer: String? { get } + var date: Date? { get } + var description: String? { get } + var discount: D? { get } + var dueDate: Date? { get } + var endingBalance: Int? { get } + var forgiven: Bool? { get } + var lines: L? { get } + var livemode: Bool? { get } + var metadata: [String: String]? { get } + var nextPaymentAttempt: Date? { get } + var number: String? { get } + var paid: Bool? { get } + var periodEnd: Date? { get } + var periodStart: Date? { get } + var receiptNumber: String? { get } + var startingBalance: Int? { get } + var statementDescriptor: String? { get } + var subscription: String? { get } + var subscriptionProrationDate: Int? { get } + var subtotal: Int? { get } + var total: Int? { get } + var tax: Int? { get } + var taxPercent: Decimal? { get } + var webhooksDeliveredAt: Date? { get } } -public final class InvoiceList: StripeModelProtocol { - - public private(set) var object: String? - public private(set) var hasMore: Bool? - public private(set) var items: [Invoice]? - - public init(node: Node) throws { - self.object = try node.get("object") - 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, - "has_more": self.hasMore, - "data": self.items - ] - return try Node(node: object) - } +public struct StripeInvoice: Invoice, StripeModel { + public var id: String? + public var object: String? + public var amountDue: Int? + public var applicationFee: Int? + public var attemptCount: Int? + public var attempted: Bool? + public var billing: String? + public var charge: String? + public var closed: Bool? + public var currency: StripeCurrency? + public var customer: String? + public var date: Date? + public var description: String? + public var discount: StripeDiscount? + public var dueDate: Date? + public var endingBalance: Int? + public var forgiven: Bool? + public var lines: InvoiceLineGroup? + public var livemode: Bool? + public var metadata: [String: String]? + public var nextPaymentAttempt: Date? + public var number: String? + public var paid: Bool? + public var periodEnd: Date? + public var periodStart: Date? + public var receiptNumber: String? + public var startingBalance: Int? + public var statementDescriptor: String? + public var subscription: String? + public var subscriptionProrationDate: Int? + public var subtotal: Int? + public var total: Int? + public var tax: Int? + public var taxPercent: Decimal? + public var webhooksDeliveredAt: Date? } diff --git a/Sources/Stripe/Models/Invoices/InvoiceItem.swift b/Sources/Stripe/Models/Invoices/InvoiceItem.swift index e2e650a..a231b0e 100644 --- a/Sources/Stripe/Models/Invoices/InvoiceItem.swift +++ b/Sources/Stripe/Models/Invoices/InvoiceItem.swift @@ -7,105 +7,50 @@ // import Foundation -import Vapor /** Invoice Items https://stripe.com/docs/api#invoiceitems */ -open class InvoiceItem: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var amount: Int - public private(set) var customer: String? - public private(set) var date: Date? - public private(set) var description: String? - public private(set) var isDiscountable: Bool - public private(set) var isLiveMode: Bool - public private(set) var isProration: Bool - public private(set) var periodStart: Date? - public private(set) var periodEnd: Date? - public private(set) var quantity: Int? - public private(set) var subscription: String? - - public private(set) var metadata: Node? - - public private(set) var plan: Plan? - public private(set) var currency: StripeCurrency? - - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.amount = try node.get("amount") - self.customer = try node.get("customer") - self.date = try node.get("date") - self.description = try node.get("description") - self.isDiscountable = try node.get("discountable") - self.isLiveMode = try node.get("livemode") - self.isProration = try node.get("proration") - self.quantity = try node.get("qantity") - self.subscription = try node.get("subscription") - - if let period = node["period"]?.object { - self.periodStart = period["start"]?.date - self.periodEnd = period["end"]?.date - } - - self.metadata = try node.get("metadata") - - self.plan = try? node.get("plan") - - if let currency = node["currency"]?.string { - self.currency = StripeCurrency(rawValue: currency) - } - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "id": self.id, - "object": self.object, - "amount": self.amount, - "customer": self.customer, - "date": self.date, - "description": self.description, - "discountable": self.isDiscountable, - "livemode": self.isLiveMode, - "proration": self.isProration, - "quantity": self.quantity, - "subscription": self.subscription, - "period": [ - "start": self.periodStart, - "end": self.periodEnd - ], - "metadata": self.metadata, - "plan": self.plan, - "currency": self.currency?.rawValue - ] - - return try Node(node: object) - } + +public protocol InvoiceItem { + associatedtype P: Plan + + var id: String? { get } + var object: String? { get } + var amount: Int? { get } + var currency: StripeCurrency? { get } + var customer: String? { get } + var date: Date? { get } + var description: String? { get } + var discountable: Bool? { get } + var invoice: String? { get } + var livemode: Bool? { get } + var metadata: [String: String]? { get } + var period: Period? { get } + var plan: P? { get } + var proration: Bool? { get } + var quantity: Int? { get } + var subscription: String? { get } + var subscriptionItem: String? { get } } -public final class InvoiceItemList: StripeModelProtocol { - - public private(set) var object: String? - public private(set) var hasMore: Bool? - public private(set) var items: [InvoiceItem]? - - public init(node: Node) throws { - self.object = try node.get("object") - 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, - "has_more": self.hasMore, - "data": self.items - ] - return try Node(node: object) - } - +public struct StripeInvoiceItem: InvoiceItem, StripeModel { + public var id: String? + public var object: String? + public var amount: Int? + public var currency: StripeCurrency? + public var customer: String? + public var date: Date? + public var description: String? + public var discountable: Bool? + public var invoice: String? + public var livemode: Bool? + public var metadata: [String : String]? + public var period: Period? + public var plan: StripePlan? + public var proration: Bool? + public var quantity: Int? + public var subscription: String? + public var subscriptionItem: String? } diff --git a/Sources/Stripe/Models/Invoices/InvoiceItemList.swift b/Sources/Stripe/Models/Invoices/InvoiceItemList.swift new file mode 100644 index 0000000..434fc38 --- /dev/null +++ b/Sources/Stripe/Models/Invoices/InvoiceItemList.swift @@ -0,0 +1,20 @@ +// +// InvoiceItemList.swift +// Stripe +// +// Created by Andrew Edwards on 12/7/17. +// + +/** + InvoiceItems List + https://stripe.com/docs/api#list_invoiceitems + */ + +public struct InvoiceItemsList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeInvoiceItem]? +} + diff --git a/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift b/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift index f6d4237..42652a6 100644 --- a/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift +++ b/Sources/Stripe/Models/Invoices/InvoiceLineGroup.swift @@ -6,27 +6,15 @@ // // -import Foundation -import Vapor +/** + InvoiceLines list + https://stripe.com/docs/api#invoice_lines + */ -open class InvoiceLineGroup: StripeModelProtocol { - - public private(set) var object: String? - public private(set) var hasMore: Bool? - public private(set) var items: [InvoiceLineItem]? - - public required init(node: Node) throws { - self.object = try node.get("object") - 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, - "has_more": self.hasMore, - "data": self.items - ] - return try Node(node: object) - } +public struct InvoiceLineGroup: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeInvoiceLineItem]? } diff --git a/Sources/Stripe/Models/Invoices/InvoiceLineItem.swift b/Sources/Stripe/Models/Invoices/InvoiceLineItem.swift index 44770ed..6ad33b2 100644 --- a/Sources/Stripe/Models/Invoices/InvoiceLineItem.swift +++ b/Sources/Stripe/Models/Invoices/InvoiceLineItem.swift @@ -7,76 +7,46 @@ // import Foundation -import Vapor -open class InvoiceLineItem: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var amount: Int - public private(set) var description: String? - public private(set) var isDiscountable: Bool - public private(set) var isLiveMode: Bool - public private(set) var periodStart: Date? - public private(set) var periodEnd: Date? - public private(set) var isProration: Bool - public private(set) var quantity: Int? - public private(set) var subscription: String? - public private(set) var subscriptionItem: String? - - public private(set) var plan: Plan? - - public private(set) var metadata: Node? - - public private(set) var currency: StripeCurrency? - - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.amount = try node.get("amount") - self.description = try node.get("description") - self.isDiscountable = try node.get("discountable") - self.isLiveMode = try node.get("livemode") - self.isProration = try node.get("proration") - self.quantity = try node.get("quantity") - self.subscription = try node.get("subscription") - self.subscriptionItem = try node.get("subscription_item") - - self.plan = try node.get("plan") - - self.metadata = try node.get("metadata") - - if let period = node["period"]?.object { - self.periodStart = period["start"]?.date - self.periodEnd = period["end"]?.date - } - - if let currency = node["currency"]?.string { - self.currency = StripeCurrency(rawValue: currency) - } - } +/** + InvoiceItem object + https://stripe.com/docs/api#invoice_line_item_object + */ + +public protocol InvoiceLineItem { + associatedtype P: Plan - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "id": self.id, - "object": self.object, - "amount": self.amount, - "description": self.description, - "discountable": self.isDiscountable, - "livemode": self.isLiveMode, - "proration": self.isProration, - "quantity": self.quantity, - "subscription": self.subscription, - "subscription_item": self.subscriptionItem, - "plan": self.plan, - "metadata": self.metadata, - "period": [ - "start": self.periodStart, - "end": self.periodEnd - ], - "currency": self.currency?.rawValue - ] - - return try Node(node: object) - } + var id: String? { get } + var object: String? { get } + var amount: Int? { get } + var currency: StripeCurrency? { get } + var description: String? { get } + var discountable: Bool? { get } + var livemode: Bool? { get } + var metadata: [String: String]? { get } + var period: Period? { get } + var plan: P? { get } + var proration: Bool? { get } + var quantity: Int? { get } + var subscription: String? { get } + var subscriptionItem: String? { get } + var type: String? { get } +} + +public struct StripeInvoiceLineItem: InvoiceLineItem, StripeModel { + public var id: String? + public var object: String? + public var amount: Int? + public var currency: StripeCurrency? + public var description: String? + public var discountable: Bool? + public var livemode: Bool? + public var metadata: [String : String]? + public var period: Period? + public var plan: StripePlan? + public var proration: Bool? + public var quantity: Int? + public var subscription: String? + public var subscriptionItem: String? + public var type: String? } diff --git a/Sources/Stripe/Models/Invoices/InvoiceList.swift b/Sources/Stripe/Models/Invoices/InvoiceList.swift new file mode 100644 index 0000000..1a3d9ce --- /dev/null +++ b/Sources/Stripe/Models/Invoices/InvoiceList.swift @@ -0,0 +1,19 @@ +// +// InvoiceList.swift +// Stripe +// +// Created by Andrew Edwards on 12/7/17. +// + +/** + Invoices list + https://stripe.com/docs/api#list_invoices + */ + +public struct InvoicesList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeInvoice]? +} diff --git a/Sources/Stripe/Models/Invoices/Period.swift b/Sources/Stripe/Models/Invoices/Period.swift new file mode 100644 index 0000000..31b942b --- /dev/null +++ b/Sources/Stripe/Models/Invoices/Period.swift @@ -0,0 +1,13 @@ +// +// Period.swift +// Stripe +// +// Created by Andrew Edwards on 12/7/17. +// + +import Foundation + +public struct Period: StripeModel { + public var start: Date? + public var end: Date? +} diff --git a/Sources/Stripe/Models/List.swift b/Sources/Stripe/Models/List.swift new file mode 100644 index 0000000..5ccf687 --- /dev/null +++ b/Sources/Stripe/Models/List.swift @@ -0,0 +1,15 @@ +// +// List.swift +// Stripe +// +// Created by Andrew Edwards on 12/3/17. +// + +public protocol List { + associatedtype T: StripeModel + var object: String? { get } + var hasMore: Bool? { get } + var totalCount: Int? { get } + var url: String? { get } + var data: [T]? { get } +} diff --git a/Sources/Stripe/Models/OrderList.swift b/Sources/Stripe/Models/OrderList.swift deleted file mode 100644 index fda2053..0000000 --- a/Sources/Stripe/Models/OrderList.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// OrderList.swift -// Stripe -// -// Created by Andrew Edwards on 8/23/17. -// -// - -import Foundation -import Vapor - -open 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 required 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/Stripe/Models/Orders/DeliveryEstimateType.swift b/Sources/Stripe/Models/Orders/DeliveryEstimateType.swift new file mode 100644 index 0000000..671b370 --- /dev/null +++ b/Sources/Stripe/Models/Orders/DeliveryEstimateType.swift @@ -0,0 +1,15 @@ +// +// DeliveryEstimateType.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation + +// https://stripe.com/docs/api/curl#order_object-shipping_methods-delivery_estimate-type +public enum DeliveryEstimateType: String, Codable { + case range + case exact +} diff --git a/Sources/Stripe/Models/Orders/Order.swift b/Sources/Stripe/Models/Orders/Order.swift new file mode 100644 index 0000000..8b5f44e --- /dev/null +++ b/Sources/Stripe/Models/Orders/Order.swift @@ -0,0 +1,72 @@ +// +// Order.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation + +/** + Order object + https://stripe.com/docs/api#order_object + */ + +public protocol Order { + associatedtype O: OrderItem + associatedtype L: List + associatedtype S: Shipping + associatedtype SM: ShippingMethod + associatedtype SS: OrderStatusTransitions + + var id: String? { get } + var object: String? { get } + var amount: Int? { get } + var amountReturned: Int? { get } + var application: String? { get } + var applicationFee: Int? { get } + var charge: String? { get } + var created: Date? { get } + var currency: StripeCurrency? { get } + var customer: String? { get } + var email: String? { get } + var externalCouponCode: String? { get } + var items: [O]? { get } + var livemode: Bool? { get } + var metadata: [String: String]? { get } + var returns: L? { get } + var selectedShippingMethod: String? { get } + var shipping: S? { get } + var shippingMethods: [SM]? { get } + var status: OrderStatus? { get } + var statusTransitions: SS? { get } + var updated: Date? { get } + var upstreamId: String? { get } +} + +public struct StripeOrder: Order, StripeModel { + public var id: String? + public var object: String? + public var amount: Int? + public var amountReturned: Int? + public var application: String? + public var applicationFee: Int? + public var charge: String? + public var created: Date? + public var currency: StripeCurrency? + public var customer: String? + public var email: String? + public var externalCouponCode: String? + public var items: [StripeOrderItem]? + public var livemode: Bool? + public var metadata: [String: String]? + public var returns: OrderReturnList? + public var selectedShippingMethod: String? + public var shipping: ShippingLabel? + public var shippingMethods: [StripeShippingMethod]? + public var status: OrderStatus? + public var statusTransitions: StripeOrderStatusTransitions? + public var updated: Date? + public var upstreamId: String? +} diff --git a/Sources/Stripe/Models/Orders/OrderItem.swift b/Sources/Stripe/Models/Orders/OrderItem.swift index b0237cf..f279237 100644 --- a/Sources/Stripe/Models/Orders/OrderItem.swift +++ b/Sources/Stripe/Models/Orders/OrderItem.swift @@ -6,50 +6,27 @@ // // -import Foundation -import Vapor - - /** OrderItem object https://stripe.com/docs/api#order_item_object-object */ -open 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 required 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) - } +public protocol OrderItem { + var object: String? { get } + var amount: Int? { get } + var currency: StripeCurrency? { get } + var description: String? { get } + var parent: String? { get } + var quantity: Int? { get } + var type: OrderItemType? { get } +} + +public struct StripeOrderItem: OrderItem, StripeModel { + public var object: String? + public var amount: Int? + public var currency: StripeCurrency? + public var description: String? + public var parent: String? + public var quantity: Int? + public var type: OrderItemType? } diff --git a/Sources/Stripe/Models/Orders/OrderItemType.swift b/Sources/Stripe/Models/Orders/OrderItemType.swift new file mode 100644 index 0000000..7df60e4 --- /dev/null +++ b/Sources/Stripe/Models/Orders/OrderItemType.swift @@ -0,0 +1,14 @@ +// +// OrderItemType.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +public enum OrderItemType: String, Codable { + case sku + case tax + case shipping + case discount +} diff --git a/Sources/Stripe/Models/Orders/OrderList.swift b/Sources/Stripe/Models/Orders/OrderList.swift new file mode 100644 index 0000000..ac446a9 --- /dev/null +++ b/Sources/Stripe/Models/Orders/OrderList.swift @@ -0,0 +1,20 @@ +// +// OrderList.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +/** + Orders List + https://stripe.com/docs/api/curl#list_orders + */ + +public struct OrdersList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeOrder]? +} diff --git a/Sources/Stripe/Models/Orders/OrderReturn.swift b/Sources/Stripe/Models/Orders/OrderReturn.swift index 18f3f2f..5c15d00 100644 --- a/Sources/Stripe/Models/Orders/OrderReturn.swift +++ b/Sources/Stripe/Models/Orders/OrderReturn.swift @@ -7,53 +7,34 @@ // import Foundation -import Vapor - /** OrderReturn object https://stripe.com/docs/api#order_return_object */ -open 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 required 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 protocol OrderReturn { + associatedtype OI: OrderItem - 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) - } + var id: String? { get } + var object: String? { get } + var amount: Int? { get } + var created: Date? { get } + var currency: StripeCurrency? { get } + var items: [OI]? { get } + var livemode: Bool? { get } + var order: String? { get } + var refund: String? { get } +} + +public struct StripeOrderReturn: OrderReturn, StripeModel { + public var id: String? + public var object: String? + public var amount: Int? + public var created: Date? + public var currency: StripeCurrency? + public var items: [StripeOrderItem]? + public var livemode: Bool? + public var order: String? + public var refund: String? } diff --git a/Sources/Stripe/Models/Orders/OrderReturnList.swift b/Sources/Stripe/Models/Orders/OrderReturnList.swift index 8bd30ac..bd163eb 100644 --- a/Sources/Stripe/Models/Orders/OrderReturnList.swift +++ b/Sources/Stripe/Models/Orders/OrderReturnList.swift @@ -6,29 +6,15 @@ // // -import Foundation -import Vapor +/** + Order return List + https://stripe.com/docs/api#list_order_returns + */ -open 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 required 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) - } +public struct OrderReturnList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeOrderReturn]? } diff --git a/Sources/Stripe/Models/Orders/OrderStatus.swift b/Sources/Stripe/Models/Orders/OrderStatus.swift new file mode 100644 index 0000000..fed68d0 --- /dev/null +++ b/Sources/Stripe/Models/Orders/OrderStatus.swift @@ -0,0 +1,15 @@ +// +// OrderStatus.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +public enum OrderStatus: String, Codable { + case created + case paid + case canceled + case fulfilled + case returned +} diff --git a/Sources/Stripe/Models/Orders/Orders.swift b/Sources/Stripe/Models/Orders/Orders.swift deleted file mode 100644 index 18bded6..0000000 --- a/Sources/Stripe/Models/Orders/Orders.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// Orders.swift -// Stripe -// -// Created by Andrew Edwards on 8/23/17. -// -// - -import Foundation -import Vapor - -/** - Order object - https://stripe.com/docs/api#order_object - */ - -open 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 required 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) - } -} diff --git a/Sources/Stripe/Models/Other/DeletedObject.swift b/Sources/Stripe/Models/Other/DeletedObject.swift index d2c1ff6..41f2c6f 100644 --- a/Sources/Stripe/Models/Other/DeletedObject.swift +++ b/Sources/Stripe/Models/Other/DeletedObject.swift @@ -6,24 +6,19 @@ // // -import Foundation import Vapor -open class DeletedObject: StripeModelProtocol { - public private(set) var deleted: Bool? - public private(set) var id: String? - - public required init(node: Node) throws { - self.deleted = try node.get("deleted") - self.id = try node.get("id") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "deleted": self.deleted, - "id": self.id - ] - return try Node(node: object) - } - +/** + Deleted object + https://stripe.com/docs/api/curl#delete_customer + */ + +public protocol DeletedObject { + var deleted: Bool? { get } + var id: String? { get } +} + +public struct StripeDeletedObject: DeletedObject, StripeModel { + public var deleted: Bool? + public var id: String? } diff --git a/Sources/Stripe/Models/PackageDimensions.swift b/Sources/Stripe/Models/PackageDimensions.swift deleted file mode 100644 index 001c2fe..0000000 --- a/Sources/Stripe/Models/PackageDimensions.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// PackageDimensions.swift -// Stripe -// -// Created by Andrew Edwards on 8/22/17. -// -// - -import Foundation -import Vapor - -open 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 required 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/Stripe/Models/Plans/Plans.swift b/Sources/Stripe/Models/Plans/Plans.swift index 054751a..e551c96 100644 --- a/Sources/Stripe/Models/Plans/Plans.swift +++ b/Sources/Stripe/Models/Plans/Plans.swift @@ -7,72 +7,38 @@ // import Foundation -import Vapor /** - Plan Model + Plan object https://stripe.com/docs/api/curl#plan_object */ -open class Plan: 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 interval: StripeInterval? - public private(set) var intervalCount: Int? - public private(set) var isLive: Bool? - - /** - Only these values are mutable/updatable. - https://stripe.com/docs/api/curl#update_plan - */ - - public private(set) var metadata: Node? - public private(set) var name: String? - public private(set) var statementDescriptor: String? - public private(set) var trialPeriodDays: Int? - - public init() {} - - public required 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) - } - - if let interval = node["interval"]?.string { - self.interval = StripeInterval(rawValue: interval) - } - - self.intervalCount = try node.get("interval_count") - self.isLive = try node.get("livemode") - self.metadata = try node.get("metadata") - self.name = try node.get("name") - self.statementDescriptor = try node.get("statement_descriptor") - self.trialPeriodDays = try node.get("trial_period_days") - } - - 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, - "interval": self.interval, - "interval_count": self.intervalCount, - "livemode": self.isLive, - "metadata": self.metadata, - "name": self.name, - "statement_descriptor": self.statementDescriptor, - "trial_period_days": self.trialPeriodDays - ] - - return try Node(node: object) - } + +public protocol Plan { + var id: String? { get } + var object: String? { get } + var amount: Int? { get } + var created: Date? { get } + var currency: StripeCurrency? { get } + var interval: StripePlanInterval? { get } + var intervalCount: Int? { get } + var livemode: Bool? { get } + var metadata: [String: String]? { get } + var nickname: String? { get } + var product: String? { get } + var trialPeriodDays: Int? { get } +} + +public struct StripePlan: Plan, StripeModel { + public var id: String? + public var object: String? + public var amount: Int? + public var created: Date? + public var currency: StripeCurrency? + public var interval: StripePlanInterval? + public var intervalCount: Int? + public var livemode: Bool? + public var metadata: [String: String]? + public var nickname: String? + public var product: String? + public var trialPeriodDays: Int? } diff --git a/Sources/Stripe/Models/Plans/PlansList.swift b/Sources/Stripe/Models/Plans/PlansList.swift index 1f18fd6..fd3d8a7 100644 --- a/Sources/Stripe/Models/Plans/PlansList.swift +++ b/Sources/Stripe/Models/Plans/PlansList.swift @@ -6,29 +6,15 @@ // // -import Foundation -import Vapor +/** + Plans List + https://stripe.com/docs/api/curl#list_plans + */ -open class PlansList: StripeModelProtocol { - public private(set) var object: String? - public private(set) var url: String? - public private(set) var hasMore: Bool? - public private(set) var items: [Plan]? - - public required 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) - } +public struct PlansList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripePlan]? } diff --git a/Sources/Stripe/Models/Products/PackageDimensions.swift b/Sources/Stripe/Models/Products/PackageDimensions.swift new file mode 100644 index 0000000..1b77163 --- /dev/null +++ b/Sources/Stripe/Models/Products/PackageDimensions.swift @@ -0,0 +1,28 @@ +// +// PackageDimensions.swift +// Stripe +// +// Created by Andrew Edwards on 8/22/17. +// +// + +import Foundation + +/** + Package Dimensions + https://stripe.com/docs/api/curl#product_object-package_dimensions + */ + +public protocol PackageDimensions { + var height: Decimal? { get } + var length: Decimal? { get } + var weight: Decimal? { get } + var width: Decimal? { get } +} + +public struct StripePackageDimensions: PackageDimensions, StripeModel { + public var height: Decimal? + public var length: Decimal? + public var weight: Decimal? + public var width: Decimal? +} diff --git a/Sources/Stripe/Models/Products/Products.swift b/Sources/Stripe/Models/Products/Products.swift index 777fde3..823f6b4 100644 --- a/Sources/Stripe/Models/Products/Products.swift +++ b/Sources/Stripe/Models/Products/Products.swift @@ -7,74 +7,55 @@ // import Foundation -import Vapor /** Product object https://stripe.com/docs/api#products */ -open class Product: StripeModelProtocol { +public protocol Product { + associatedtype L: List + associatedtype PD: PackageDimensions - 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 required 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) - } + var id: String? { get } + var object: String? { get } + var active: Bool? { get } + var attributes: [String]? { get } + var caption: String? { get } + var created: Date? { get } + var deactivateOn: [String]? { get } + var description: String? { get } + var images: [String]? { get } + var livemode: Bool? { get } + var metadata: [String: String]? { get } + var name: String? { get } + var packageDimensions: PD? { get } + var shippable: Bool? { get } + var skus: L? { get } + var statementDescriptor: String? { get } + var type: String? { get } + var updated: Date? { get } + var url: String? { get } +} + +public struct StripeProduct: Product, StripeModel { + public var id: String? + public var object: String? + public var active: Bool? + public var attributes: [String]? + public var caption: String? + public var created: Date? + public var deactivateOn: [String]? + public var description: String? + public var images: [String]? + public var livemode: Bool? + public var metadata: [String : String]? + public var name: String? + public var packageDimensions: StripePackageDimensions? + public var shippable: Bool? + public var skus: SKUList? + public var statementDescriptor: String? + public var type: String? + public var updated: Date? + public var url: String? } diff --git a/Sources/Stripe/Models/Products/ProductsList.swift b/Sources/Stripe/Models/Products/ProductsList.swift index 8813f7a..38ae4a5 100644 --- a/Sources/Stripe/Models/Products/ProductsList.swift +++ b/Sources/Stripe/Models/Products/ProductsList.swift @@ -6,29 +6,15 @@ // // -import Foundation -import Vapor +/** + Products List + https://stripe.com/docs/api/curl#list_products + */ -open 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 required 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) - } +public struct ProductsList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeProduct]? } diff --git a/Sources/Stripe/Models/Refunds/Refund.swift b/Sources/Stripe/Models/Refunds/Refund.swift new file mode 100644 index 0000000..a922707 --- /dev/null +++ b/Sources/Stripe/Models/Refunds/Refund.swift @@ -0,0 +1,44 @@ +// +// RefundItem.swift +// Stripe +// +// Created by Anthony Castelli on 4/15/17. +// +// + +import Foundation + +/** + Refund object + https://stripe.com/docs/api/curl#refunds + */ + +public protocol Refund { + var id: String? { get } + var object: String? { get } + var amount: Int? { get } + var balanceTransaction: String? { get } + var charge: String? { get } + var created: Date? { get } + var currency: StripeCurrency? { get } + var description: String? { get } + var metadata: [String: String]? { get } + var reason: RefundReason? { get } + var receiptNumber: String? { get } + var status: StripeStatus? { get } +} + +public struct StripeRefund: Refund, StripeModel { + public var id: String? + public var object: String? + public var amount: Int? + public var balanceTransaction: String? + public var charge: String? + public var created: Date? + public var currency: StripeCurrency? + public var description: String? + public var metadata: [String: String]? + public var reason: RefundReason? + public var receiptNumber: String? + public var status: StripeStatus? +} diff --git a/Sources/Stripe/Models/Refunds/RefundList.swift b/Sources/Stripe/Models/Refunds/RefundList.swift new file mode 100644 index 0000000..09e5cc0 --- /dev/null +++ b/Sources/Stripe/Models/Refunds/RefundList.swift @@ -0,0 +1,20 @@ +// +// RefundList.swift +// Stripe +// +// Created by Anthony Castelli on 4/15/17. +// +// + +/** + Refunds list + https://stripe.com/docs/api/curl#charge_object-refunds + */ + +public struct RefundsList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeRefund]? +} diff --git a/Sources/Stripe/Models/Refunds/RefundReason.swift b/Sources/Stripe/Models/Refunds/RefundReason.swift new file mode 100644 index 0000000..1cc3698 --- /dev/null +++ b/Sources/Stripe/Models/Refunds/RefundReason.swift @@ -0,0 +1,18 @@ +// +// RefundReason.swift +// Stripe +// +// Created by Anthony Castelli on 5/13/17. +// +// + +/** + Refund reason + https://stripe.com/docs/api/curl#refund_object-reason + */ + +public enum RefundReason: String, Codable { + case duplicate + case fraudulent + case requestedByCustomer = "requested_by_customer" +} diff --git a/Sources/Stripe/Models/SKU/Inventory.swift b/Sources/Stripe/Models/SKU/Inventory.swift new file mode 100644 index 0000000..b439480 --- /dev/null +++ b/Sources/Stripe/Models/SKU/Inventory.swift @@ -0,0 +1,24 @@ +// +// Inventory.swift +// Stripe +// +// Created by Andrew Edwards on 8/22/17. +// +// + +/** + Inventory object + https://stripe.com/docs/api/curl#sku_object-inventory + */ + +public protocol Inventory { + var quantity: Int? { get } + var type: InventoryType? { get } + var value: InventoryTypeValue? { get } +} + +public struct StripeInventory: Inventory, StripeModel { + public var quantity: Int? + public var type: InventoryType? + public var value: InventoryTypeValue? +} diff --git a/Sources/Stripe/Models/SKU/SKU.swift b/Sources/Stripe/Models/SKU/SKU.swift index 47ac3a9..d82bfd6 100644 --- a/Sources/Stripe/Models/SKU/SKU.swift +++ b/Sources/Stripe/Models/SKU/SKU.swift @@ -7,67 +7,45 @@ // import Foundation -import Vapor /** SKU object https://stripe.com/docs/api#skus */ -open class SKU: StripeModelProtocol { +public protocol SKU { + associatedtype PD: PackageDimensions + associatedtype I: Inventory - 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 required 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) - } + var id: String? { get } + var object: String? { get } + var active: Bool? { get } + var attributes: [String: String]? { get } + var created: Date? { get } + var currency: StripeCurrency? { get } + var image: String? { get } + var inventory: I? { get } + var livemode: Bool? { get } + var metadata: [String: String]? { get } + var packageDimensions: PD? { get } + var price: Int? { get } + var product: String? { get } + var updated: Date? { get } +} + +public struct StripeSKU: SKU, StripeModel { + public var id: String? + public var object: String? + public var active: Bool? + public var attributes: [String: String]? + public var created: Date? + public var currency: StripeCurrency? + public var image: String? + public var inventory: StripeInventory? + public var livemode: Bool? + public var metadata: [String: String]? + public var packageDimensions: StripePackageDimensions? + public var price: Int? + public var product: String? + public var updated: Date? } diff --git a/Sources/Stripe/Models/SKU/SKUList.swift b/Sources/Stripe/Models/SKU/SKUList.swift index bdbf393..e0a217a 100644 --- a/Sources/Stripe/Models/SKU/SKUList.swift +++ b/Sources/Stripe/Models/SKU/SKUList.swift @@ -6,29 +6,15 @@ // // -import Foundation -import Vapor +/** + SKU List + https://stripe.com/docs/api/curl#list_skus + */ -open 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 required 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) - } +public struct SKUList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeSKU]? } diff --git a/Sources/Stripe/Models/Shipping/DeliveryEstimate.swift b/Sources/Stripe/Models/Shipping/DeliveryEstimate.swift index 175f149..c34454f 100644 --- a/Sources/Stripe/Models/Shipping/DeliveryEstimate.swift +++ b/Sources/Stripe/Models/Shipping/DeliveryEstimate.swift @@ -6,33 +6,21 @@ // // -import Foundation -import Vapor +/** + Delivery Estimate + https://stripe.com/docs/api/curl#order_object-shipping_methods-delivery_estimate + */ -open 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 required 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) - } +public protocol DeliveryEstimate { + var date: String? { get } + var earliest: String? { get } + var latest: String? { get } + var type: DeliveryEstimateType? { get } +} + +public struct StripeDeliveryEstimate: DeliveryEstimate, StripeModel { + public var date: String? + public var earliest: String? + public var latest: String? + public var type: DeliveryEstimateType? } diff --git a/Sources/Stripe/Models/Shipping/OrderStatusTransitions.swift b/Sources/Stripe/Models/Shipping/OrderStatusTransitions.swift new file mode 100644 index 0000000..0a12b4b --- /dev/null +++ b/Sources/Stripe/Models/Shipping/OrderStatusTransitions.swift @@ -0,0 +1,28 @@ +// +// StatusTransitions.swift +// Stripe +// +// Created by Andrew Edwards on 8/23/17. +// +// + +import Foundation + +/** + StatusTransitions + https://stripe.com/docs/api/curl#order_object-status_transitions + */ + +public protocol OrderStatusTransitions { + var canceled: Date? { get } + var fulfiled: Date? { get } + var paid: Date? { get } + var returned: Date? { get } +} + +public struct StripeOrderStatusTransitions: OrderStatusTransitions, StripeModel { + public var canceled: Date? + public var fulfiled: Date? + public var paid: Date? + public var returned: Date? +} diff --git a/Sources/Stripe/Models/Shipping/ShippingAddress.swift b/Sources/Stripe/Models/Shipping/ShippingAddress.swift deleted file mode 100644 index abf6d0a..0000000 --- a/Sources/Stripe/Models/Shipping/ShippingAddress.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// ShippingAddress.swift -// Stripe -// -// Created by Anthony Castelli on 4/15/17. -// -// - -import Foundation -import Vapor - -/** - Shipping Address - https://stripe.com/docs/api/curl#charge_object-shipping-address - */ - -open class ShippingAddress: StripeModelProtocol { - - public var city: String? - public var country: String? - public var addressLine1: String? - public var addressLine2: String? - public var postalCode: String? - public var state: String? - - public init() { } - - public required init(node: Node) throws { - self.city = try node.get("city") - self.country = try node.get("country") - self.addressLine1 = try node.get("line1") - self.addressLine2 = try node.get("line2") - self.postalCode = try node.get("postal_code") - self.state = try node.get("state") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "city": self.city, - "country": self.country, - "line1": self.addressLine1, - "line2": self.addressLine2, - "postal_code": self.postalCode, - "state": self.state - ] - return try Node(node: object) - } -} diff --git a/Sources/Stripe/Models/Shipping/ShippingLabel.swift b/Sources/Stripe/Models/Shipping/ShippingLabel.swift index 2dd060e..43107f2 100644 --- a/Sources/Stripe/Models/Shipping/ShippingLabel.swift +++ b/Sources/Stripe/Models/Shipping/ShippingLabel.swift @@ -6,39 +6,24 @@ // // -import Foundation -import Vapor - /** Shipping https://stripe.com/docs/api/curl#charge_object-shipping */ -open class ShippingLabel: StripeModelProtocol { - - public var address: ShippingAddress? + +public protocol Shipping { + associatedtype T: Address + var address: T? { get } + var carrier: String? { get } + var name: String? { get } + var phone: String? { get } + var trackingNumber: String? { get } +} + +public struct ShippingLabel: Shipping, StripeModel { + public var address: StripeAddress? public var carrier: String? public var name: String? public var phone: String? public var trackingNumber: String? - - public init() { } - - public required init(node: Node) throws { - self.address = try node.get("address") - self.carrier = try node.get("carrier") - self.name = try node.get("name") - self.phone = try node.get("phone") - self.trackingNumber = try node.get("tracking_number") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "address": self.address, - "carrier": self.carrier, - "name": self.name, - "phone": self.phone, - "tracking_number": self.trackingNumber, - ] - return try Node(node: object) - } } diff --git a/Sources/Stripe/Models/Shipping/ShippingMethod.swift b/Sources/Stripe/Models/Shipping/ShippingMethod.swift index c7b0542..82f0b21 100644 --- a/Sources/Stripe/Models/Shipping/ShippingMethod.swift +++ b/Sources/Stripe/Models/Shipping/ShippingMethod.swift @@ -6,36 +6,25 @@ // // -import Foundation -import Vapor +/** + Shipping Method + https://stripe.com/docs/api/curl#order_object-shipping_methods + */ -open class ShippingMethod: StripeModelProtocol { +public protocol ShippingMethod { + associatedtype D: DeliveryEstimate - 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 required 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) - } + var id: String? { get } + var amount: Int? { get } + var currency: StripeCurrency? { get } + var deliveryEstimate: D? { get } + var description: String? { get } +} + +public struct StripeShippingMethod: ShippingMethod, StripeModel { + public var id: String? + public var amount: Int? + public var currency: StripeCurrency? + public var deliveryEstimate: StripeDeliveryEstimate? + public var description: String? } diff --git a/Sources/Stripe/Models/Shipping/StatusTransitions.swift b/Sources/Stripe/Models/Shipping/StatusTransitions.swift deleted file mode 100644 index 2ca87c4..0000000 --- a/Sources/Stripe/Models/Shipping/StatusTransitions.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// StatusTransitions.swift -// Stripe -// -// Created by Andrew Edwards on 8/23/17. -// -// - -import Foundation -import Vapor - -open 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 required 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) - } -} diff --git a/Sources/Stripe/Models/Shipping/StripeAddress.swift b/Sources/Stripe/Models/Shipping/StripeAddress.swift new file mode 100644 index 0000000..f41b91e --- /dev/null +++ b/Sources/Stripe/Models/Shipping/StripeAddress.swift @@ -0,0 +1,30 @@ +// +// StripeAddress.swift +// Stripe +// +// Created by Anthony Castelli on 4/15/17. +// +// + +/** + Shipping Address + https://stripe.com/docs/api/curl#charge_object-shipping-address + */ + +public protocol Address { + var city: String? { get } + var country: String? { get } + var line1: String? { get } + var line2: String? { get } + var postalCode: String? { get } + var state: String? { get } +} + +public struct StripeAddress: Address, StripeModel { + public var city: String? + public var country: String? + public var line1: String? + public var line2: String? + public var postalCode: String? + public var state: String? +} diff --git a/Sources/Stripe/Models/Sources/BankAccount.swift b/Sources/Stripe/Models/Sources/BankAccount.swift index 273feb9..d1591cb 100644 --- a/Sources/Stripe/Models/Sources/BankAccount.swift +++ b/Sources/Stripe/Models/Sources/BankAccount.swift @@ -6,75 +6,43 @@ // // -import Foundation -import Vapor - /** - Bank Account Model + Bank Account object https://stripe.com/docs/api/curl#customer_bank_account_object */ -open class BankAccount: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var account: String? - public private(set) var name: String? - public private(set) var country: String? - public private(set) var fingerprint: String? - public private(set) var last4: String? - public private(set) var routingNumber: String? - public private(set) var status: String? - public private(set) var customer: String? - public private(set) var defaultForCurrency: Bool? - public private(set) var currency: StripeCurrency? - - /** - Only these values are mutable/updatable. - https://stripe.com/docs/api/curl#customer_update_bank_account - */ - - public private(set) var accountHolderName: String? - public private(set) var accountHolderType: String? - public private(set) var metadata: Node? - - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.account = try node.get("account") - self.accountHolderName = try node.get("account_holder_name") - self.accountHolderType = try node.get("account_holder_type") - self.name = try node.get("bank_name") - self.country = try node.get("country") - self.fingerprint = try node.get("fingerprint") - self.last4 = try node.get("last4") - self.routingNumber = try node.get("routing_number") - self.status = try node.get("status") - self.customer = try node.get("customer") - self.defaultForCurrency = try node.get("default_for_currency") - if let currency = node["currency"]?.string { - self.currency = StripeCurrency(rawValue: currency) - } - self.metadata = try node.get("metadata") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "id": self.id, - "object": self.object, - "account": self.account, - "account_holder_name": self.accountHolderName, - "account_holder_type": self.accountHolderType, - "bank_name": self.name, - "country": self.country, - "fingerprint": self.fingerprint, - "last4": self.last4, - "routing_number": self.routingNumber, - "status": self.status, - "customer": self.customer, - "default_for_currency": self.defaultForCurrency, - "currency": self.currency?.rawValue, - "metadata": self.metadata - ] - return try Node(node: object) - } + +public protocol BankAccount { + var id: String? { get } + var object: String? { get } + var account: String? { get } + var accountHolderName: String? { get } + var accountHolderType: String? { get } + var bankName: String? { get } + var country: String? { get } + var currency: StripeCurrency? { get } + var customer: String? { get } + var defaultForCurrency: Bool? { get } + var fingerprint: String? { get } + var last4: String? { get } + var metadata: [String: String]? { get } + var routingNumber: String? { get } + var status: String? { get } +} + +public struct StripeBankAccount: BankAccount, StripeModel { + public var id: String? + public var object: String? + public var account: String? + public var accountHolderName: String? + public var accountHolderType: String? + public var bankName: String? + public var country: String? + public var currency: StripeCurrency? + public var customer: String? + public var defaultForCurrency: Bool? + public var fingerprint: String? + public var last4: String? + public var metadata: [String : String]? + public var routingNumber: String? + public var status: String? } diff --git a/Sources/Stripe/Models/Sources/Card.swift b/Sources/Stripe/Models/Sources/Card.swift index a778f7c..03279c3 100644 --- a/Sources/Stripe/Models/Sources/Card.swift +++ b/Sources/Stripe/Models/Sources/Card.swift @@ -6,122 +6,72 @@ // // -import Foundation -import Vapor - /** - Card Model + Card object https://stripe.com/docs/api/curl#card_object + https://stripe.com/docs/sources/cards */ -open class Card: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var account: String? - public private(set) var addressLine1Check: ValidationCheck? - public private(set) var addressZipCheck: ValidationCheck? - public private(set) var country: String? - public private(set) var brand: String? - public private(set) var customerId: String? - public private(set) var cvcCheck: ValidationCheck? - public private(set) var dynamicLastFour: String? - public private(set) var fingerprint: String? - public private(set) var fundingType: FundingType? - public private(set) var lastFour: String - public private(set) var tokenizedMethod: TokenizedMethod? - public private(set) var currency: StripeCurrency? - public private(set) var defaultForCurrency: Bool? - public private(set) var recipient: String? - - /** - Only these values are mutable/updatable. - https://stripe.com/docs/api/curl#update_card - */ - - public private(set) var addressCity: String? - public private(set) var addressCountry: String? - public private(set) var addressLine1: String? - public private(set) var addressLine2: String? - public private(set) var addressState: String? - public private(set) var addressZip: String? - public private(set) var expirationMonth: Int? - public private(set) var expirationYear: Int? - public private(set) var metadata: Node? - public private(set) var name: String? - - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.account = try node.get("account") - self.addressCity = try node.get("address_city") - self.addressCountry = try node.get("address_country") - self.addressLine1 = try node.get("address_line1") - if let addressLine1Check = node["address_line1_check"]?.string { - self.addressLine1Check = ValidationCheck(optionalRawValue: addressLine1Check) - } - self.addressLine2 = try node.get("address_line2") - self.addressState = try node.get("address_state") - self.addressZip = try node.get("address_zip") - if let addressZipCheck = node["address_zip_check"]?.string { - self.addressZipCheck = ValidationCheck(optionalRawValue: addressZipCheck) - } - self.country = try node.get("country") - self.brand = try node.get("brand") - if let currency = node["currency"]?.string { - self.currency = StripeCurrency(rawValue: currency) - } - self.defaultForCurrency = try node.get("default_for_currency") - self.customerId = try node.get("customer") - if let cvcCheck = node["cvc_check"]?.string { - self.cvcCheck = ValidationCheck(optionalRawValue: cvcCheck) - } - self.dynamicLastFour = try node.get("dynamic_last4") - self.expirationMonth = try node.get("exp_month") - self.expirationYear = try node.get("exp_year") - self.fingerprint = try node.get("fingerprint") - if let fundingType = node["funding"]?.string { - self.fundingType = FundingType(optionalRawValue: fundingType) - } - self.lastFour = try node.get("last4") - self.name = try node.get("name") - if let tokenizedMethod = node["tokenization_method"]?.string { - self.tokenizedMethod = TokenizedMethod(optionalRawValue: tokenizedMethod) - } - self.recipient = try node.get("recipient") - - self.metadata = try node.get("metadata") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "id": self.id, - "object": self.object, - "account": self.account, - "address_city": self.addressCity, - "address_country": self.addressCountry, - "address_line1": self.addressLine1, - "address_line1_check": self.addressLine1Check?.rawValue, - "address_line2": self.addressLine2, - "address_state": self.addressState, - "address_zip": self.addressZip, - "address_zip_check": self.addressZipCheck?.rawValue, - "country": self.country, - "brand": self.brand, - "customer": self.customerId, - "currency": self.currency?.rawValue, - "cvc_check": self.cvcCheck?.rawValue, - "default_for_currency": self.defaultForCurrency, - "dynamic_last4": self.dynamicLastFour, - "exp_month": self.expirationMonth, - "exp_year": self.expirationYear, - "fingerprint": self.fingerprint, - "funding": self.fundingType?.rawValue, - "last4": self.lastFour, - "name": self.name, - "recipient": self.recipient, - "tokenization_method": self.tokenizedMethod?.rawValue, - "metadata": self.metadata - ] - return try Node(node: object) - } + +public protocol Card { + var id: String? { get } + var object: String? { get } + var account: String? { get } + var addressCity: String? { get } + var addressCountry: String? { get } + var addressLine1: String? { get } + var addressLine1Check: CardValidationCheck? { get } + var addressLine2: String? { get } + var addressState: String? { get } + var addressZip: String? { get } + var addressZipCheck: CardValidationCheck? { get } + var availablePayoutMethods: [String]? { get } + var brand: String? { get } + var country: String? { get } + var currency: StripeCurrency? { get } + var customer: String? { get } + var cvcCheck: CardValidationCheck? { get } + var defaultForCurrency: Bool? { get } + var dynamicLast4: String? { get } + var expMonth: Int? { get} + var expYear: Int? { get} + var fingerprint: String? { get } + var funding: FundingType? { get } + var last4: String? { get } + var metadata: [String: String]? { get } + var name: String? { get } + var recipient: String? { get } + var tokenizationMethod: TokenizedMethod? { get } + var threeDSecure: String? { get } +} + +public struct StripeCard: Card, StripeModel { + public var id: String? + public var object: String? + public var account: String? + public var addressCity: String? + public var addressCountry: String? + public var addressLine1: String? + public var addressLine1Check: CardValidationCheck? + public var addressLine2: String? + public var addressState: String? + public var addressZip: String? + public var addressZipCheck: CardValidationCheck? + public var availablePayoutMethods: [String]? + public var brand: String? + public var country: String? + public var currency: StripeCurrency? + public var customer: String? + public var cvcCheck: CardValidationCheck? + public var defaultForCurrency: Bool? + public var dynamicLast4: String? + public var expMonth: Int? + public var expYear: Int? + public var fingerprint: String? + public var funding: FundingType? + public var last4: String? + public var metadata: [String : String]? + public var name: String? + public var recipient: String? + public var tokenizationMethod: TokenizedMethod? + public var threeDSecure: String? } diff --git a/Sources/Stripe/Models/Sources/CardValidationCheck.swift b/Sources/Stripe/Models/Sources/CardValidationCheck.swift new file mode 100644 index 0000000..4333721 --- /dev/null +++ b/Sources/Stripe/Models/Sources/CardValidationCheck.swift @@ -0,0 +1,14 @@ +// +// ValidationCheck.swift +// Stripe +// +// Created by Anthony Castelli on 4/15/17. +// +// + +public enum CardValidationCheck: String, Codable { + case pass + case failed + case unavailable + case unchecked +} diff --git a/Sources/Stripe/Models/Sources/Flow.swift b/Sources/Stripe/Models/Sources/Flow.swift new file mode 100644 index 0000000..2d1a74c --- /dev/null +++ b/Sources/Stripe/Models/Sources/Flow.swift @@ -0,0 +1,18 @@ +// +// Flow.swift +// Stripe +// +// Created by Andrew Edwards on 1/26/18. +// + +/** + Flow + https://stripe.com/docs/api/curl#source_object-flow + */ + +public enum Flow: String, Codable { + case redirect + case receiver + case codeVerification = "code_verification" + case none +} diff --git a/Sources/Stripe/Models/Sources/Mandate.swift b/Sources/Stripe/Models/Sources/Mandate.swift new file mode 100644 index 0000000..8b32146 --- /dev/null +++ b/Sources/Stripe/Models/Sources/Mandate.swift @@ -0,0 +1,23 @@ +// +// Mandate.swift +// Stripe +// +// Created by Andrew Edwards on 1/26/18. +// + +/** + Mandate + https://stripe.com/docs/api/curl#create_source-mandate + */ + +public protocol Mandate { + associatedtype TOS: TOSAcceptance + + var acceptance: TOS? { get } + var notificationMethod: String? { get} +} + +public struct StripeMandate: Mandate, StripeModel { + public var acceptance: StripeTOSAcceptance? + public var notificationMethod: String? +} diff --git a/Sources/Stripe/Models/Sources/OtherSources.swift b/Sources/Stripe/Models/Sources/OtherSources.swift new file mode 100644 index 0000000..a9d605a --- /dev/null +++ b/Sources/Stripe/Models/Sources/OtherSources.swift @@ -0,0 +1,84 @@ +// +// OtherSources.swift +// Stripe +// +// Created by Andrew Edwards on 12/6/17. +// + +/** + Payment Sources + https://stripe.com/docs/sources + */ + +// MARK: - ThreeDSecure +public struct ThreeDSecure: StripeModel { + public var card: String? + public var customer: String? + public var authenticated: Bool? +} + +// MARK: - Giropay +public struct Giropay: StripeModel { + public var bankCode: String? + public var bic: String? + public var bankName: String? + public var statementDescriptor: String? +} + +// MARK: - SepaDebit +public struct SepaDebit: StripeModel { + public var bankCode: String? + public var country: String? + public var fingerprint: String? + public var last4: String? + public var mandateReference: String? + public var mandateUrl: String? +} + +// MARK: - iDEAL +public struct iDEAL: StripeModel { + public var bank: String? + public var bic: String? + public var ibanLast4: String? + public var statementDescriptor: String? +} + +// MARK: - SOFORT +public struct SOFORT: StripeModel { + public var country: String? + public var bankCode: String? + public var bic: String? + public var bankName: String? + public var ibanLast4: String? + public var preferredLanguage: String? + public var statementDescriptor: String? +} + +// MARK: - Bancontact +public struct Bancontact: StripeModel { + public var bankCode: String? + public var bic: String? + public var bankName: String? + public var statementDescriptor: String? + public var preferredLanguage: String? +} + +// MARK: - Alipay +public struct Alipay: StripeModel { + public var nativeUrl: String? + public var statementDescriptor: String? +} + +// MARK: - P24 +public struct P24: StripeModel { + public var reference: String? +} + +// MARK: - ACH Credit Transfer +public struct ACHCreditTransfer: StripeModel { + public var accountNumber: String? + public var routingNumber: String? + public var fingerprint: String? + public var bankName: String? + public var swiftCode: String? +} diff --git a/Sources/Stripe/Models/Sources/Owner.swift b/Sources/Stripe/Models/Sources/Owner.swift index 46bf076..ede7ae6 100644 --- a/Sources/Stripe/Models/Sources/Owner.swift +++ b/Sources/Stripe/Models/Sources/Owner.swift @@ -6,34 +6,30 @@ // // -import Foundation -import Vapor +/** + Owner object + https://stripe.com/docs/api/curl#source_object-owner + */ -open class Owner: StripeModelProtocol { - public private(set) var address: ShippingAddress? - public private(set) var email: String? - public private(set) var name: String? - public private(set) var phone: String? - public private(set) var verifiedAddress: ShippingAddress? - public private(set) var verifiedEmail: String? - public private(set) var verifiedName: String? - public private(set) var verifiedPhone: String? - - public required init(node: Node) throws { - self.address = try node.get("address") - self.email = try node.get("email") - self.name = try node.get("name") - self.phone = try node.get("phone") - self.verifiedAddress = try node.get("verified_address") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "address": self.address, - "email": self.email, - "name": self.name, - "phone": self.phone - ] - return try Node(node: object) - } +public protocol Owner { + associatedtype A: Address + var address: A? { get } + var email: String? { get } + var name: String? { get } + var phone: String? { get } + var verifiedAddress: A? { get } + var verifiedEmail: String? { get } + var verifiedName: String? { get } + var verifiedPhone: String? { get } +} + +public struct StripeOwner: Owner, StripeModel { + public var address: StripeAddress? + public var email: String? + public var name: String? + public var phone: String? + public var verifiedAddress: StripeAddress? + public var verifiedEmail: String? + public var verifiedName: String? + public var verifiedPhone: String? } diff --git a/Sources/Stripe/Models/Sources/Receiver.swift b/Sources/Stripe/Models/Sources/Receiver.swift index 16b4a35..4c3a8b4 100644 --- a/Sources/Stripe/Models/Sources/Receiver.swift +++ b/Sources/Stripe/Models/Sources/Receiver.swift @@ -6,36 +6,25 @@ // // -import Foundation -import Vapor +/** + Receiver object + https://stripe.com/docs/api/curl#source_object-receiver + */ -open class Receiver: StripeModelProtocol { - - public private(set) var address: String? - public private(set) var amountCharged: Int? - public private(set) var amountReceived: Int? - public private(set) var amountReturned: Int? - public private(set) var refundMethod: String? - public private(set) var refundStatus: String? - - public required init(node: Node) throws { - self.address = try node.get("address") - self.amountCharged = try node.get("amount_charged") - self.amountReceived = try node.get("amount_received") - self.amountReturned = try node.get("amount_returned") - self.refundMethod = try node.get("refund_attributes_method") - self.refundStatus = try node.get("refund_attributes_status") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "address": self.address, - "amount_charged": self.amountCharged, - "amount_received": self.amountReceived, - "amount_returned": self.amountReturned, - "refund_attributes_method": self.refundMethod, - "refund_attributes_status": self.refundStatus - ] - return try Node(node: object) - } +public protocol Receiver { + var address: String? { get } + var amountCharged: Int? { get } + var amountReceived: Int? { get } + var amountReturned: Int? { get } + var refundAttributesMethod: String? { get } + var refundAttributesStatus: String? { get } +} + +public struct StripeReceiver: Receiver, StripeModel { + public var address: String? + public var amountCharged: Int? + public var amountReceived: Int? + public var amountReturned: Int? + public var refundAttributesMethod: String? + public var refundAttributesStatus: String? } diff --git a/Sources/Stripe/Models/Sources/Source.swift b/Sources/Stripe/Models/Sources/Source.swift index ee14896..89b1183 100644 --- a/Sources/Stripe/Models/Sources/Source.swift +++ b/Sources/Stripe/Models/Sources/Source.swift @@ -7,100 +7,128 @@ // import Foundation -import Vapor /** - Source Model + Source object https://stripe.com/docs/api/curl#source_object */ -open class Source: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var amount: Int? - public private(set) var clientSecret: String? - public private(set) var codeVerificationStatus: String? - public private(set) var attemptsRemaining: Int? - public private(set) var created: Date? - public private(set) var currency: StripeCurrency? - public private(set) var flow: String? - public private(set) var isLive: Bool? - public private(set) var redirectReturnUrl: String? - public private(set) var redirectStatus: String? - public private(set) var redirectUrl: String? - public private(set) var status: StripeStatus? - public private(set) var type: SourceType? - public private(set) var usage: String? - public private(set) var reciever: Receiver? - public private(set) var returnedSource: [String:[String: Node]]? - - /** - Only these values are mutable/updatable. - https://stripe.com/docs/api/curl#update_source - */ + +public protocol Source { + associatedtype R: Receiver + associatedtype O: Owner + associatedtype C: Card - public private(set) var metadata: Node? - public private(set) var owner: Owner? + var id: String? { get } + var object: String? { get } + var amount: Int? { get } + var clientSecret: String? { get } + var codeVerification: CodeVerification? { get } + var created: Date? { get } + var currency: StripeCurrency? { get } + var flow: Flow? { get } + var livemode: Bool? { get } + var metadata: [String: String]? { get } + var owner: O? { get } + var receiver: R? { get } + var redirect: SourceRedirect? { get } + var statementDescriptor: String? { get } + var status: StripeStatus? { get } + var type: SourceType? { get } + var usage: String? { get } + var card: C? { get } + var threeDSecure: ThreeDSecure? { get } + var giropay: Giropay? { get } + var sepaDebit: SepaDebit? { get } + var ideal: iDEAL? { get } + var sofort: SOFORT? { get } + var bancontact: Bancontact? { get } + var alipay: Alipay? { get } + var p24: P24? { get } + var achCreditTransfer: ACHCreditTransfer? { get } + // TODO: - Add multibanco, EPS + // https://stripe.com/docs/sources +} + +public struct StripeSource: Source, StripeModel { + public var id: String? + public var object: String? + public var amount: Int? + public var clientSecret: String? + public var codeVerification: CodeVerification? + public var created: Date? + public var currency: StripeCurrency? + public var flow: Flow? + public var livemode: Bool? + public var metadata: [String: String]? + public var owner: StripeOwner? + public var receiver: StripeReceiver? + public var redirect: SourceRedirect? + public var statementDescriptor: String? + public var status: StripeStatus? + public var type: SourceType? + public var usage: String? + public var card: StripeCard? + public var threeDSecure: ThreeDSecure? + public var giropay: Giropay? + public var sepaDebit: SepaDebit? + public var ideal: iDEAL? + public var sofort: SOFORT? + public var bancontact: Bancontact? + public var alipay: Alipay? + public var p24: P24? + public var achCreditTransfer: ACHCreditTransfer? - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.amount = try node.get("amount") - self.clientSecret = try node.get("client_secret") - /// We can check for code verification dictionary here - if let codeVerification = node["code_verification"]?.object { - self.attemptsRemaining = codeVerification["attempts_remaining"]?.int - self.codeVerificationStatus = codeVerification["status"]?.string - } - /// We can check for redirect dictionary here - if let redirect = node["redirect"]?.object { - self.redirectReturnUrl = redirect["return_url"]?.string - self.redirectStatus = redirect["status"]?.string - self.redirectUrl = redirect["url"]?.string + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decodeIfPresent(String.self, forKey: .id) + object = try container.decodeIfPresent(String.self, forKey: .object) + amount = try container.decodeIfPresent(Int.self, forKey: .amount) + clientSecret = try container.decodeIfPresent(String.self, forKey: .clientSecret) + codeVerification = try container.decodeIfPresent(CodeVerification.self, forKey: .codeVerification) + created = try container.decodeIfPresent(Date.self, forKey: .created) + currency = try container.decodeIfPresent(StripeCurrency.self, forKey: .currency) + flow = try container.decodeIfPresent(Flow.self, forKey: .flow) + livemode = try container.decodeIfPresent(Bool.self, forKey: .livemode) + metadata = try container.decodeIfPresent([String: String].self, forKey: .metadata) + owner = try container.decodeIfPresent(StripeOwner.self, forKey: .owner) + receiver = try container.decodeIfPresent(StripeReceiver.self, forKey: .receiver) + redirect = try container.decodeIfPresent(SourceRedirect.self, forKey: .redirect) + statementDescriptor = try container.decodeIfPresent(String.self, forKey: .statementDescriptor) + status = try container.decodeIfPresent(StripeStatus.self, forKey: .status) + usage = try container.decodeIfPresent(String.self, forKey: .usage) + + type = try container.decodeIfPresent(SourceType.self, forKey: .type) + + switch type! { + case .card: + card = try container.decodeIfPresent(StripeCard.self, forKey: .card) + + case .threeDSecure: + threeDSecure = try container.decodeIfPresent(ThreeDSecure.self, forKey: .threeDSecure) + + case .giropay: + giropay = try container.decodeIfPresent(Giropay.self, forKey: .giropay) + + case .sepaDebit: + sepaDebit = try container.decodeIfPresent(SepaDebit.self, forKey: .sepaDebit) + + case .ideal: + ideal = try container.decodeIfPresent(iDEAL.self, forKey: .ideal) + + case .sofort: + sofort = try container.decodeIfPresent(SOFORT.self, forKey: .sofort) + + case .bancontact: + bancontact = try container.decodeIfPresent(Bancontact.self, forKey: .bancontact) + + case .alipay: + alipay = try container.decodeIfPresent(Alipay.self, forKey: .alipay) + + case .p24: + p24 = try container.decodeIfPresent(P24.self, forKey: .p24) + + case .achCreditTransfer: + achCreditTransfer = try container.decodeIfPresent(ACHCreditTransfer.self, forKey: .achCreditTransfer) } - self.created = try node.get("created") - if let currency = node["currency"]?.string { - self.currency = StripeCurrency(rawValue: currency) - } - self.flow = try node.get("flow") - self.isLive = try node.get("livemode") - if let status = node["status"]?.string { - self.status = StripeStatus(rawValue: status) - } - /// if we have a type we should have a body to parse - if let type = node["type"]?.string { - self.type = SourceType(rawValue: type) - if let sourceTypeBody = node["\(type)"]?.object { - var sourceBody: [String: Node] = [:] - for (key,val) in sourceTypeBody { - sourceBody["\(key)"] = val - } - self.returnedSource = ["\(type)": sourceBody] - } - } - self.usage = try node.get("usage") - self.metadata = try node.get("metadata") - self.owner = try node.get("owner") - self.reciever = try node.get("receiver") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "id": self.id, - "object": self.object, - "amount": self.amount, - "client_secret": self.clientSecret, - "created": self.created, - "currency": self.currency?.rawValue, - "flow": self.flow, - "livemode": self.isLive, - "status": self.status?.rawValue, - "type": self.type?.rawValue, - "usage": self.usage, - "metadata": self.metadata, - "owner": self.owner, - "receiver": self.reciever - ] - return try Node(node: object) } } diff --git a/Sources/Stripe/Models/Sources/SourceList.swift b/Sources/Stripe/Models/Sources/SourceList.swift index b8cf829..e94d0c6 100644 --- a/Sources/Stripe/Models/Sources/SourceList.swift +++ b/Sources/Stripe/Models/Sources/SourceList.swift @@ -6,49 +6,15 @@ // // -import Foundation -import Vapor +/** + Sources list + https://stripe.com/docs/api#customer_object-sources + */ -open class SourceList: StripeModelProtocol { - - public private(set) var object: String? - public private(set) var url: String? - public private(set) var hasMore: Bool? - public private(set) var items: [Node]? - public private(set) var cardSources: [Card] = [] - public private(set) var bankSources: [BankAccount] = [] - - public required 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") - - // Seperate different type of accounts to make it easier to work with. - for item in items ?? [] { - - if let object = item["object"]?.string { - if object == "bank_account" { - let bank = try BankAccount(node: item) - bankSources.append(bank) - } - } - - if let object = item["object"]?.string { - if object == "card" { - cardSources.append(try Card(node: item)) - } - } - } - } - - 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) - } +public struct StripeSourcesList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeSource]? } diff --git a/Sources/Stripe/Models/Sources/SourceRedirect.swift b/Sources/Stripe/Models/Sources/SourceRedirect.swift new file mode 100644 index 0000000..e4e4c13 --- /dev/null +++ b/Sources/Stripe/Models/Sources/SourceRedirect.swift @@ -0,0 +1,13 @@ +// +// SourceRedirect.swift +// Stripe +// +// Created by Andrew Edwards on 12/4/17. +// + +public struct SourceRedirect: StripeModel { + public var failureReason: String? + public var returnUrl: String? + public var status: String? + public var url: String? +} diff --git a/Sources/Stripe/Models/Sources/SourceType.swift b/Sources/Stripe/Models/Sources/SourceType.swift new file mode 100644 index 0000000..f4dd175 --- /dev/null +++ b/Sources/Stripe/Models/Sources/SourceType.swift @@ -0,0 +1,25 @@ +// +// SourceType.swift +// Stripe +// +// Created by Anthony Castelli on 4/14/17. +// +// + +/** + Source type + https://stripe.com/docs/api/curl#source_object-type + */ + +public enum SourceType: String, Codable { + case card + case achCreditTransfer = "ach_credit_transfer" + case threeDSecure = "three_d_secure" + case giropay + case sepaDebit = "sepa_debit" + case ideal + case sofort + case bancontact + case alipay + case p24 +} diff --git a/Sources/Stripe/Models/Sources/TokenizedMethod.swift b/Sources/Stripe/Models/Sources/TokenizedMethod.swift new file mode 100644 index 0000000..b287489 --- /dev/null +++ b/Sources/Stripe/Models/Sources/TokenizedMethod.swift @@ -0,0 +1,14 @@ +// +// TokenizedMethod.swift +// Stripe +// +// Created by Anthony Castelli on 4/15/17. +// +// + +import Foundation + +public enum TokenizedMethod: String, Codable { + case applePay = "apple_pay" + case androidPay = "android_pay" +} diff --git a/Sources/Stripe/Models/StripeModel.swift b/Sources/Stripe/Models/StripeModel.swift new file mode 100644 index 0000000..98c74ea --- /dev/null +++ b/Sources/Stripe/Models/StripeModel.swift @@ -0,0 +1,111 @@ +// +// StripeModel.swift +// Stripe +// +// Created by Anthony Castelli on 4/14/17. +// +// + +import Vapor +import Foundation + +public protocol StripeModel: Content { + func toEncodedBody() throws -> String + func toEncodedDictionary() throws -> [String: Any] +} + +extension StripeModel { + public func toEncodedBody() throws -> String { + return try self.toEncodedDictionary().queryParameters + } + + public func toEncodedDictionary() throws -> [String: Any] { + let encoded = try JSONEncoder().encode(self) + + let jsonString = String(data: encoded, encoding: .utf8) ?? "" + + let final = try JSONDecoder().decode(AnyDecodable.self, from: jsonString.data(using: .utf8) ?? Data()).value as? [String: Any] ?? [:] + + return final + } +} + +public struct AnyDecodable: Decodable { + public var value: Any + + private struct CodingKeys: CodingKey { + var stringValue: String + var intValue: Int? + init?(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } + init?(stringValue: String) { self.stringValue = stringValue } + } + + public init(from decoder: Decoder) throws { + if let container = try? decoder.container(keyedBy: CodingKeys.self) { + var result = [String: Any]() + try container.allKeys.forEach { (key) throws in + result[key.stringValue] = try container.decode(AnyDecodable.self, forKey: key).value + } + value = result + } else if var container = try? decoder.unkeyedContainer() { + var result = [Any]() + while !container.isAtEnd { + result.append(try container.decode(AnyDecodable.self).value) + } + value = result + } else if let container = try? decoder.singleValueContainer() { + if let intVal = try? container.decode(Int.self) { + value = intVal + } else if let doubleVal = try? container.decode(Double.self) { + value = doubleVal + } else if let boolVal = try? container.decode(Bool.self) { + value = boolVal + } else if let stringVal = try? container.decode(String.self) { + value = stringVal + } else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable") + } + } else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise")) + } + } +} + +extension Dictionary { + var queryParameters: String { + guard let me = self as? [String: Any] else + { return "" } + return query(parameters: me) + } + + func query(parameters: [String: Any]) -> String { + var components: [(String, String)] = [] + + for key in parameters.keys { + let value = parameters[key]! + components += queryComponents(key: key, value) + } + return (components.map { "\($0)=\($1)" } as [String]).joined(separator: "&") + } + + public func queryComponents(key: String, _ value: Any) -> [(String, String)] { + var components: [(String, String)] = [] + + if let dictionary = value as? [String: Any] { + for (nestedKey, value) in dictionary { + components += queryComponents(key: "\(key)[\(nestedKey)]", value) + } + } else if let array = value as? [Any] { + for i in 0.. Node { - let object: [String: Any?] = [ - "id": self.id, - "object": self.object, - "application_fee_percent": self.applicationFeePercent, - "cancel_at_period_end": self.cancelAtPeriodEnd, - "canceled_at": self.canceledAt, - "created": self.created, - "current_period_end": self.currentPeriodEnd, - "current_period_start": self.currentPeriodStart, - "customer": self.customer, - "discount": self.discount, - "ended_at": self.endedAt, - "items": self.items, - "livemode": self.isLive, - "metadata": self.metadata, - "plan": self.plan, - "quantity": self.quantity, - "start": self.start, - "status": self.status?.rawValue, - "tax_percent": self.taxPercent, - "trial_end": self.trialEnd, - "trial_start": self.trialStart - ] - return try Node(node: object) - } + var id: String? { get } + var object: String? { get } + var applicationFeePercent: Decimal? { get } + var billing: String? { get } + var billingCycleAnchor: Date? { get } + var cancelAtPeriodEnd: Bool? { get } + var canceledAt: Date? { get } + var created: Date? { get } + var currentPeriodEnd: Date? { get } + var currentPeriodStart: Date? { get } + var customer: String? { get } + var daysUntilDue: Int? { get } + var discount: D? { get } + var endedAt: Date? { get } + var items: L? { get } + var livemode: Bool? { get } + var metadata: [String: String]? { get } + var plan: P? { get } + var quantity: Int? { get } + var start: Date? { get } + var status: StripeSubscriptionStatus? { get } + var taxPercent: Decimal? { get } + var trialEnd: Date? { get } + var trialStart: Date? { get } +} + +public struct StripeSubscription: Subscription, StripeModel { + public var id: String? + public var object: String? + public var applicationFeePercent: Decimal? + public var billing: String? + public var billingCycleAnchor: Date? + public var cancelAtPeriodEnd: Bool? + public var canceledAt: Date? + public var created: Date? + public var currentPeriodEnd: Date? + public var currentPeriodStart: Date? + public var customer: String? + public var daysUntilDue: Int? + public var discount: StripeDiscount? + public var endedAt: Date? + public var items: SubscriptionItemList? + public var livemode: Bool? + public var metadata: [String : String]? + public var plan: StripePlan? + public var quantity: Int? + public var start: Date? + public var status: StripeSubscriptionStatus? + public var taxPercent: Decimal? + public var trialEnd: Date? + public var trialStart: Date? } diff --git a/Sources/Stripe/Models/Subscriptions/SubscriptionItem.swift b/Sources/Stripe/Models/Subscriptions/SubscriptionItem.swift index 3105282..1c52808 100644 --- a/Sources/Stripe/Models/Subscriptions/SubscriptionItem.swift +++ b/Sources/Stripe/Models/Subscriptions/SubscriptionItem.swift @@ -7,43 +7,30 @@ // import Foundation -import Vapor + /** - SubscriptionItem Model + SubscriptionItem object https://stripe.com/docs/api/curl#subscription_items */ -open class SubscriptionItem: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var created: Date? - public private(set) var plan: Plan? - public private(set) var quantity: Int? - - /** - Deleted property - https://stripe.com/docs/api/curl#delete_subscription_item - */ - public private(set) var deleted: Bool? - - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.created = try node.get("created") - self.plan = try node.get("plan") - self.quantity = try node.get("quantity") - self.deleted = try node.get("deleted") - } + +public protocol SubscriptionItem { + associatedtype P: Plan - public func makeNode(in context: Context?) throws -> Node { - let object: [String: Any?] = [ - "id": self.id, - "object": self.object, - "created": self.created, - "plan": self.plan, - "quantity": self.quantity, - "deleted": self.deleted - ] - return try Node(node: object) - } + var id: String? { get } + var object: String? { get } + var created: Date? { get } + var metadata: [String: String]? { get } + var plan: P? { get } + var quantity: Int? { get } + var subscription: String? { get } +} + +public struct StripeSubscriptionItem: SubscriptionItem, StripeModel { + public var id: String? + public var object: String? + public var created: Date? + public var metadata: [String : String]? + public var plan: StripePlan? + public var quantity: Int? + public var subscription: String? } diff --git a/Sources/Stripe/Models/Subscriptions/SubscriptionItemList.swift b/Sources/Stripe/Models/Subscriptions/SubscriptionItemList.swift index a25dc9f..99f67aa 100644 --- a/Sources/Stripe/Models/Subscriptions/SubscriptionItemList.swift +++ b/Sources/Stripe/Models/Subscriptions/SubscriptionItemList.swift @@ -6,33 +6,15 @@ // // -import Foundation -import Vapor +/** + SubscriptionItem List + https://stripe.com/docs/api/curl#list_subscription_items + */ -open class SubscriptionItemList: StripeModelProtocol { - - public private(set) var object: String? - public private(set) var hasMore: Bool? - public private(set) var items: [SubscriptionItem]? - public private(set) var url: String? - public private(set) var totalCount: Int? - - public required init(node: Node) throws { - self.object = try node.get("object") - self.hasMore = try node.get("has_more") - self.items = try node.get("data") - self.url = try node.get("url") - self.totalCount = try node.get("total_count") - } - - public func makeNode(in context: Context?) throws -> Node { - let object: [String : Any?] = [ - "object": self.object, - "has_more": self.hasMore, - "data": self.items, - "url": self.url, - "total_count": self.totalCount - ] - return try Node(node: object) - } +public struct SubscriptionItemList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeSubscriptionItem]? } diff --git a/Sources/Stripe/Models/Subscriptions/SubscriptionList.swift b/Sources/Stripe/Models/Subscriptions/SubscriptionList.swift index 736bb77..4b53ec8 100644 --- a/Sources/Stripe/Models/Subscriptions/SubscriptionList.swift +++ b/Sources/Stripe/Models/Subscriptions/SubscriptionList.swift @@ -6,31 +6,15 @@ // // -import Foundation -import Vapor +/** + Subscriptions List + https://stripe.com/docs/api/curl#list_subscriptions + */ -open class SubscriptionList: StripeModelProtocol { - - public private(set) var object: String? - public private(set) var hasMore: Bool? - public private(set) var items: [Subscription]? - public private(set) var url: String? - - public required 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) - } - +public struct SubscriptionList: List, StripeModel { + public var object: String? + public var hasMore: Bool? + public var totalCount: Int? + public var url: String? + public var data: [StripeSubscription]? } diff --git a/Sources/Stripe/Models/Tokens/Token.swift b/Sources/Stripe/Models/Tokens/Token.swift index b511ce5..7e48097 100644 --- a/Sources/Stripe/Models/Tokens/Token.swift +++ b/Sources/Stripe/Models/Tokens/Token.swift @@ -6,50 +6,36 @@ // // -import Vapor +import Foundation /** - Token Model + Token object https://stripe.com/docs/api/curl#token_object */ -open class Token: StripeModelProtocol { - - public private(set) var id: String? - public private(set) var object: String? - public private(set) var type: String? - public private(set) var clientIp: String? - public private(set) var created: Date? - public private(set) var isLive: Bool? - public private(set) var isUsed: Bool? - - public private(set) var card: Card? - public private(set) var bankAccount: BankAccount? - - public required init(node: Node) throws { - self.id = try node.get("id") - self.object = try node.get("object") - self.type = try node.get("type") - self.clientIp = try node.get("client_ip") - self.created = try node.get("created") - self.isLive = try node.get("livemode") - self.isUsed = try node.get("used") - self.card = try node.get("card") - self.bankAccount = try node.get("bank_account") - } - - public func makeNode(in context: Context?) throws -> Node { - 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, - "card": self.card, - "banl_account": self.bankAccount - ] - return try Node(node: object) - } + +public protocol Token { + associatedtype C: Card + associatedtype B: BankAccount + var id: String? { get } + var object: String? { get } + var type: String? { get } + var clientIp: String? { get } + var created: Date? { get } + var livemode: Bool? { get } + var used: Bool? { get } + var card: C? { get } + var bankAccount: B? { get } +} + +public struct StripeToken: Token, StripeModel { + public var id: String? + public var object: String? + public var type: String? + public var clientIp: String? + public var created: Date? + public var livemode: Bool? + public var used: Bool? + public var card: StripeCard? + public var bankAccount: StripeBankAccount? } diff --git a/Sources/Stripe/Provider/Provider.swift b/Sources/Stripe/Provider/Provider.swift index affb57c..3954f37 100644 --- a/Sources/Stripe/Provider/Provider.swift +++ b/Sources/Stripe/Provider/Provider.swift @@ -8,59 +8,73 @@ import Vapor -private var _stripe: StripeClient? - -extension Droplet { - /* - Enables use of the `drop.stripe?` convenience methods. - */ - public var stripe: StripeClient? { - get { - return _stripe - } - set { - _stripe = newValue - } +public struct StripeConfig: Service { + let apiKey: String + public init(apiKey: String) { + self.apiKey = apiKey } } -public final class Provider: Vapor.Provider { - - public static let repositoryName = "vapor-stripe" +public final class StripeProvider: Provider { + public static let repositoryName = "stripe-provider" - public let apiKey: String - public let stripe: StripeClient - - public convenience init(config: Config) throws { - guard let stripeConfig = config["stripe"]?.object else { - throw StripeError.missingConfig - } - guard let apiKey = stripeConfig["apiKey"]?.string else { - throw StripeError.missingAPIKey - } - try self.init(apiKey: apiKey) - } - - public init(apiKey: String) throws { - self.apiKey = apiKey - self.stripe = try StripeClient(apiKey: apiKey) - } - - public func boot(_ drop: Droplet) { - self.stripe.initializeRoutes() - drop.stripe = self.stripe - } + public func boot(_ worker: Container) throws {} - public func boot(_ config: Configs.Config) throws { - + public func didBoot(_ worker: Container) throws -> EventLoopFuture { + return .done(on: worker) } - - public func afterInit(_ drop: Droplet) { - + + public func register(_ services: inout Services) throws { + services.register { (container) -> StripeClient in + let httpClient = try container.make(Client.self) + let config = try container.make(StripeConfig.self) + return StripeClient(config: config, client: httpClient) + } } +} - public func beforeRun(_ drop: Droplet) { +public struct StripeClient: Service { + public let balance: StripeBalanceRoutes + public let charge: StripeChargeRoutes + public let connectAccount: StripeConnectAccountRoutes + public let coupon: StripeCouponRoutes + public let customer: StripeCustomerRoutes + public let dispute: StripeDisputeRoutes + public let ephemeralKey: StripeEphemeralKeyRoutes + public let invoiceItem: StripeInvoiceItemRoutes + public let invoice: StripeInvoiceRoutes + public let orderReturn: StripeOrderReturnRoutes + public let order: StripeOrderRoutes + public let plan: StripePlanRoutes + public let product: StripeProductRoutes + public let refund: StripeRefundRoutes + public let sku: StripeSKURoutes + public let source: StripeSourceRoutes + public let subscriptionItem: StripeSubscriptionItemRoutes + public let subscription: StripeSubscriptionRoutes + public let token: StripeTokenRoutes + internal init(config: StripeConfig, client: Client) { + let apiRequest = StripeAPIRequest(httpClient: client, apiKey: config.apiKey) + + balance = StripeBalanceRoutes(request: apiRequest) + charge = StripeChargeRoutes(request: apiRequest) + connectAccount = StripeConnectAccountRoutes(request: apiRequest) + coupon = StripeCouponRoutes(request: apiRequest) + customer = StripeCustomerRoutes(request: apiRequest) + dispute = StripeDisputeRoutes(request: apiRequest) + ephemeralKey = StripeEphemeralKeyRoutes(request: apiRequest) + invoiceItem = StripeInvoiceItemRoutes(request: apiRequest) + invoice = StripeInvoiceRoutes(request: apiRequest) + orderReturn = StripeOrderReturnRoutes(request: apiRequest) + order = StripeOrderRoutes(request: apiRequest) + plan = StripePlanRoutes(request: apiRequest) + product = StripeProductRoutes(request: apiRequest) + refund = StripeRefundRoutes(request: apiRequest) + sku = StripeSKURoutes(request: apiRequest) + source = StripeSourceRoutes(request: apiRequest) + subscriptionItem = StripeSubscriptionItemRoutes(request: apiRequest) + subscription = StripeSubscriptionRoutes(request: apiRequest) + token = StripeTokenRoutes(request: apiRequest) } - } diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 112dd88..7e123f8 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -6,196 +6,103 @@ import XCTest extension AccountTests { static var allTests = [ - ("testCreateAccount", testCreateAccount), - ("testRetrieveAccount", testRetrieveAccount), - ("testUpdateAccount", testUpdateAccount), - ("testDeleteAccount", testDeleteAccount), - ("testRejectAccount", testRejectAccount), - ("testListAllAccounts", testListAllAccounts), - ("testFilterAccounts", testFilterAccounts), + ("testAccountParsedProperll", testAccountParsedProperll), ] } extension BalanceTests { static var allTests = [ - ("testBalance", testBalance), - ("testBalanceTransactionItem", testBalanceTransactionItem), - ("testBalanceHistory", testBalanceHistory), - ("testFilterBalanceHistory", testFilterBalanceHistory), + ("testBalanceParsedProperly", testBalanceParsedProperly), + ("testBalanceTransactionParsedProperly", testBalanceTransactionParsedProperly), ] } 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), + ("testChargeParsedProperly", testChargeParsedProperly), ] } extension CustomerTests { static var allTests = [ - ("testCreateCustomer", testCreateCustomer), - ("testRetrieveCustomer", testRetrieveCustomer), - ("testUpdateCustomer", testUpdateCustomer), - ("testAddNewSourceForCustomer", testAddNewSourceForCustomer), - ("testAddNewCardSourceForCustomer", testAddNewCardSourceForCustomer), - ("testAddNewBankAccountSourceForCustomer", testAddNewBankAccountSourceForCustomer), - ("testDeleteDiscount", testDeleteDiscount), - ("testDeleteCustomer", testDeleteCustomer), - ("testRetrieveAllCustomers", testRetrieveAllCustomers), - ("testFilterCustomers", testFilterCustomers), + ("testCustomerParsedProperly", testCustomerParsedProperly), ] } extension DisputeTests { static var allTests = [ - ("testRetrieveDispute", testRetrieveDispute), - ("testUpdateDispute", testUpdateDispute), - ("testCloseDispute", testCloseDispute), - ("testListAllDisputes", testListAllDisputes), - ("testFilterDisputes", testFilterDisputes), + ("testDisputeParsedProperly", testDisputeParsedProperly), ] } extension EphemeralKeyTests { static var allTests = [ - ("testCreateEphemeralKey", testCreateEphemeralKey), - ("testDeleteEphemeralKey", testDeleteEphemeralKey), + ("testEphemeralKeyParsedProperly", testEphemeralKeyParsedProperly), ] } -extension InvoiceItemTests { +extension ErrorTests { static var allTests = [ - ("testCreatingItem", testCreatingItem), - ("testFetchingItem", testFetchingItem), - ("testDeletingItem", testDeletingItem), - ("testUpdateItem", testUpdateItem), - ("testListAllItems", testListAllItems), + ("testErrorParsedProperly", testErrorParsedProperly), ] } extension InvoiceTests { static var allTests = [ - ("testCreatingInvoice", testCreatingInvoice), - ("testFetchingInvoice", testFetchingInvoice), - ("testFetchingInvoiceItems", testFetchingInvoiceItems), - ("testFetchUpcomingInvoice", testFetchUpcomingInvoice), - ("testUpdateInvoice", testUpdateInvoice), - ("testListAllInvoices", testListAllInvoices), -] -} - -extension OrderReturnTests { -static var allTests = [ - ("testRetrieveOrderReturn", testRetrieveOrderReturn), - ("testListAllOrderReturns", testListAllOrderReturns), - ("testFilterOrderReturns", testFilterOrderReturns), + ("testInvoiceParsedProperly", testInvoiceParsedProperly), + ("testInvoiceItemParsedProperly", testInvoiceItemParsedProperly), ] } extension OrderTests { static var allTests = [ - ("testRetrieveOrder", testRetrieveOrder), - ("testUpdateOrder", testUpdateOrder), - ("testPayOrder", testPayOrder), - ("testListAllOrders", testListAllOrders), - ("testFilterOrders", testFilterOrders), - ("testReturnOrder", testReturnOrder), -] -} - -extension PlanTests { -static var allTests = [ - ("testCreatePlan", testCreatePlan), - ("testRetrievePlan", testRetrievePlan), - ("testUpdatePlan", testUpdatePlan), - ("testDeletePlan", testDeletePlan), - ("testListAllPlans", testListAllPlans), - ("testFilterPlans", testFilterPlans), + ("testOrderIsProperlyParsed", testOrderIsProperlyParsed), ] } extension ProductTests { static var allTests = [ - ("testRetrieveProduct", testRetrieveProduct), - ("testUpdateProduct", testUpdateProduct), - ("testDeleteProduct", testDeleteProduct), - ("testListAllProducts", testListAllProducts), - ("testFilterProducts", testFilterProducts), -] -} - -extension ProviderTests { -static var allTests = [ - ("testProvider", testProvider), + ("testProductParsedProperly", testProductParsedProperly), ] } extension RefundTests { static var allTests = [ - ("testRefunding", testRefunding), - ("testUpdatingRefund", testUpdatingRefund), - ("testRetrievingRefund", testRetrievingRefund), - ("testListingAllRefunds", testListingAllRefunds), + ("testRefundParsedProperly", testRefundParsedProperly), ] } extension SKUTests { static var allTests = [ - ("testRetrieveSKU", testRetrieveSKU), - ("testUpdateSKU", testUpdateSKU), - ("testDeleteSKU", testDeleteSKU), - ("testListAllSKUs", testListAllSKUs), - ("testFilterSKUs", testFilterSKUs), + ("testSkuParsedProperly", testSkuParsedProperly), ] } extension SourceTests { static var allTests = [ - ("testRetrieveSource", testRetrieveSource), - ("testUpdateSource", testUpdateSource), -] -} - -extension SubscriptionItemTests { -static var allTests = [ - ("testRetrieveSubscriptionItem", testRetrieveSubscriptionItem), - ("testUpdateSubscriptionItem", testUpdateSubscriptionItem), - ("testDeleteSubscriptionItem", testDeleteSubscriptionItem), - ("testFilterSubscriptionItems", testFilterSubscriptionItems), + ("testCardSourceParsedProperly", testCardSourceParsedProperly), + ("testThreeDSecureSourceParsedProperly", testThreeDSecureSourceParsedProperly), + ("testSepaDebitSourceParsedProperly", testSepaDebitSourceParsedProperly), + ("testAlipaySourceParsedProperly", testAlipaySourceParsedProperly), + ("testGiropaySourceParsedProperly", testGiropaySourceParsedProperly), + ("testIdealSourceParsedProperly", testIdealSourceParsedProperly), + ("testP24SourceParsedProperly", testP24SourceParsedProperly), + ("testSofortSourceParsedProperly", testSofortSourceParsedProperly), + ("testBancontactSourceParsedProperly", testBancontactSourceParsedProperly), + ("testACHSourceParsedProperly", testACHSourceParsedProperly), ] } extension SubscriptionTests { static var allTests = [ - ("testRetrieveSubscription", testRetrieveSubscription), - ("testUpdateSubscription", testUpdateSubscription), - ("testDeleteDiscount", testDeleteDiscount), - ("testCancelSubscription", testCancelSubscription), - ("testFilterSubscriptionItems", testFilterSubscriptionItems), + ("testSubscriptionParsedProperly", testSubscriptionParsedProperly), ] } extension TokenTests { static var allTests = [ - ("testCardTokenCreation", testCardTokenCreation), - ("testTokenRetrieval", testTokenRetrieval), - ("testBankAccountTokenCreation", testBankAccountTokenCreation), + ("testCardTokenParsedProperly", testCardTokenParsedProperly), + ("testBankTokenParsedProperly", testBankTokenParsedProperly), ] } @@ -204,21 +111,16 @@ XCTMain([ testCase(AccountTests.allTests), testCase(BalanceTests.allTests), testCase(ChargeTests.allTests), - testCase(CouponTests.allTests), testCase(CustomerTests.allTests), testCase(DisputeTests.allTests), testCase(EphemeralKeyTests.allTests), - testCase(InvoiceItemTests.allTests), + testCase(ErrorTests.allTests), testCase(InvoiceTests.allTests), - testCase(OrderReturnTests.allTests), testCase(OrderTests.allTests), - testCase(PlanTests.allTests), testCase(ProductTests.allTests), - testCase(ProviderTests.allTests), testCase(RefundTests.allTests), testCase(SKUTests.allTests), testCase(SourceTests.allTests), - testCase(SubscriptionItemTests.allTests), testCase(SubscriptionTests.allTests), testCase(TokenTests.allTests), ]) diff --git a/Tests/StripeTests/AccountTests.swift b/Tests/StripeTests/AccountTests.swift index 7e35611..73fce87 100644 --- a/Tests/StripeTests/AccountTests.swift +++ b/Tests/StripeTests/AccountTests.swift @@ -7,430 +7,274 @@ // import XCTest - @testable import Stripe @testable import Vapor - - - - class AccountTests: XCTestCase { - - var drop: Droplet? - var accountId: String = "" - - override func setUp() { - do { - drop = try self.makeDroplet() - accountId = try drop?.stripe?.account.create(type: .custom, - email: "123@example.com", - country: "US").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 - accountId = "" - } - - func testCreateAccount() throws { - - // Only test with custom accounts because standard cannot reuse same email and test will fail. - do { - let account = try drop?.stripe?.account.create(type: .custom, - email: "123@example.com", - country: "US").serializedResponse() - - XCTAssertNotNil(account) - } - 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) + let accountString = """ +{ + "id": "acct_1032D82eZvKYlo2C", + "object": "account", + "business_logo": "logourl", + "business_name": "Stripe.com", + "business_url": "https://www.stripe.com", + "charges_enabled": false, + "country": "US", + "created": 1385798567, + "debit_negative_balances": true, + "decline_charge_on": { + "avs_failure": true, + "cvc_failure": false + }, + "default_currency": "usd", + "details_submitted": false, + "display_name": "Stripe.com", + "email": "site@stripe.com", + "external_accounts": { + "object": "list", + "data": [ + { + "id": "ba_1BnxhQ2eZvKYlo2C5cM6hYK1", + "object": "bank_account", + "account_holder_name": "Jane Austen", + "account_holder_type": "individual", + "bank_name": "STRIPE TEST BANK", + "country": "US", + "currency": "usd", + "fingerprint": "1JWtPxqbdX5Gamtc", + "last4": "6789", + "metadata": { + "hello": "world" + }, + "routing_number": "110000000", + "status": "new" + }, + { + "brand": "Visa", + "exp_month": 8, + "exp_year": 2019, + "fingerprint": "H5flqusa75kgwmml", + "funding": "unknown", + "id": "card_1BoJ2IKrZ43eBVAbSXsWRMXT", + "last4": "4242", + "metadata": {}, + "object": "card" } - } - catch { - XCTFail(error.localizedDescription) - } - } - - func testRetrieveAccount() throws { - do { - let retrievedAccount = try drop?.stripe?.account.retrieve(account: accountId).serializedResponse() - - XCTAssertNotNil(retrievedAccount) - } - 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) + ], + "has_more": false, + "total_count": 2, + "url": "/v1/accounts/acct_1032D82eZvKYlo2C/external_accounts" + }, + "legal_entity": { + "additional_owners": [ + { + "first_name": "Malcom", + "last_name": "X", + "maiden_name": "old", + "personal_id_number_provided": true, + "verification": { + "details": null, + "details_code": "failed_other", + "document": null, + "status": "verified" + }, + "dob": { + "day": 10, + "month": 10, + "year": 2016 } } - catch { - XCTFail(error.localizedDescription) - } + ], + "address": { + "city": null, + "country": "US", + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "business_name": "Vapor codes", + "business_tax_id_provided": false, + "dob": { + "day": 10, + "month": 10, + "year": 2016 + }, + "first_name": "Mike", + "last_name": "Jones", + "personal_address": { + "city": null, + "country": "US", + "line1": null, + "line2": null, + "postal_code": "12345", + "state": null + }, + "personal_id_number_provided": false, + "ssn_last_4_provided": false, + "type": "individual", + "verification": { + "details": null, + "details_code": "failed_other", + "document": null, + "status": "unverified" } + }, + "metadata": { + }, + "payout_schedule": { + "delay_days": 7, + "interval": "daily" + }, + "payout_statement_descriptor": "", + "payouts_enabled": false, + "product_description": "Vapor", + "statement_descriptor": "", + "support_email": null, + "support_phone": null, + "timezone": "US/Pacific", + "tos_acceptance": { + "date": 1385798567, + "ip": "0.0.0.0", + "user_agent": "ios-safari" + }, + "type": "standard", + "verification": { + "disabled_reason": "fields_needed", + "due_by": 1385798567, + "fields_needed": [ + "business_url", + "external_account", + "legal_entity.address.city", + "legal_entity.address.line1", + "legal_entity.address.postal_code", + "legal_entity.address.state", + "legal_entity.dob.day", + "legal_entity.dob.month", + "legal_entity.dob.year", + "legal_entity.first_name", + "legal_entity.last_name", + "legal_entity.type", + "product_description", + "support_phone", + "tos_acceptance.date", + "tos_acceptance.ip" + ] + } +} +""" - func testUpdateAccount() throws { - + func testAccountParsedProperll() throws { do { - let businessname = "Vapor Meals" - - let declineChargeOn = Node([ - "avs_failure": true, - "cvc_failure": false - ]) - - let externalAccount = Node([ - "object": "bank_account", - "account_number": "000123456789", - "country": "US", - "currency": "usd", - "account_holder_name": "Mr. Vapor", - "account_holder_type": "individual", - "routing_number": "110000000" - ]) - - let payoutSchedule = Node([ - "delay_days": 4, - "interval": "weekly", - "weekly_anchor": "friday" - ]) - - let tosAcceptance = Node([ - "date": 1499627823, - "ip": "0.0.0.0", - "user_agent": "Safari" - ]) - - let metadata = Node(["hello":"world"]) - - let updatedAccount = try drop?.stripe?.account.update(account: accountId, - businessName: businessname, - businessPrimaryColor: nil, - businessUrl: nil, - debitNegativeBalances: false, - declineChargeOn: declineChargeOn, - defaultCurrency: .usd, - email: nil, - externalAccount: externalAccount, - legalEntity: nil, - payoutSchedule: payoutSchedule, - payoutStatementDescriptor: nil, - productDescription: nil, - statementDescriptor: nil, - supportEmail: nil, - supportPhone: nil, - supportUrl: nil, - tosAcceptance: tosAcceptance, - metadata: metadata).serializedResponse() - // TODO - Add test for legal entity - - XCTAssertNotNil(updatedAccount) - - XCTAssertEqual(updatedAccount?.businessName, businessname) - - XCTAssertEqual(updatedAccount?.declineChargeOn?["avs_failure"]?.bool, true) - - XCTAssertEqual(updatedAccount?.declineChargeOn?["cvc_failure"]?.bool, false) - - XCTAssertEqual(updatedAccount?.externalAccounts?.bankAccounts[0].accountHolderName, "Mr. Vapor") - - XCTAssertEqual(updatedAccount?.externalAccounts?.bankAccounts[0].currency, StripeCurrency.usd) - - XCTAssertEqual(updatedAccount?.externalAccounts?.bankAccounts[0].accountHolderType, "individual") - - XCTAssertEqual(updatedAccount?.payoutSchedule?.interval, .weekly) - - XCTAssertEqual(updatedAccount?.payoutSchedule?.weeklyAnchor, .friday) - - XCTAssertEqual(updatedAccount?.payoutSchedule?.delayDays, 4) + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase + + let body = HTTPBody(string: accountString) + let futureAccount = try decoder.decode(StripeConnectAccount.self, from: body, on: EmbeddedEventLoop()) + + futureAccount.do { (account) in + XCTAssertEqual(account.id, "acct_1032D82eZvKYlo2C") + XCTAssertEqual(account.object, "account") + XCTAssertEqual(account.businessLogo, "logourl") + XCTAssertEqual(account.businessName, "Stripe.com") + XCTAssertEqual(account.businessUrl, "https://www.stripe.com") + XCTAssertEqual(account.chargesEnabled, false) + XCTAssertEqual(account.country, "US") + XCTAssertEqual(account.created, Date(timeIntervalSince1970: 1385798567)) + XCTAssertEqual(account.debitNegativeBalances, true) + XCTAssertEqual(account.declineChargeOn?["avsFailure"], true) + XCTAssertEqual(account.declineChargeOn?["cvcFailure"], false) + XCTAssertEqual(account.defaultCurrency, .usd) + XCTAssertEqual(account.detailsSubmitted, false) + XCTAssertEqual(account.displayName, "Stripe.com") + XCTAssertEqual(account.email, "site@stripe.com") + + // Payout Schedule + XCTAssertEqual(account.payoutSchedule?.delayDays, 7) + XCTAssertEqual(account.payoutSchedule?.interval, .daily) + + XCTAssertEqual(account.payoutStatementDescriptor, "") + XCTAssertEqual(account.payoutsEnabled, false) + XCTAssertEqual(account.productDescription, "Vapor") + XCTAssertEqual(account.statementDescriptor, "") + XCTAssertEqual(account.supportEmail, nil) + XCTAssertEqual(account.supportPhone, nil) + XCTAssertEqual(account.timezone, "US/Pacific") + + // TOS acceptance + XCTAssertEqual(account.tosAcceptance?.date, Date(timeIntervalSince1970: 1385798567)) + XCTAssertEqual(account.tosAcceptance?.ip, "0.0.0.0") + XCTAssertEqual(account.tosAcceptance?.userAgent, "ios-safari") + + XCTAssertEqual(account.type, .standard) + + // Verification + XCTAssertEqual(account.verification?.disabledReason, "fields_needed") + XCTAssertEqual(account.verification?.dueBy, Date(timeIntervalSince1970: 1385798567)) + XCTAssertEqual(account.verification?.fieldsNeeded, ["business_url", + "external_account", + "legal_entity.address.city", + "legal_entity.address.line1", + "legal_entity.address.postal_code", + "legal_entity.address.state", + "legal_entity.dob.day", + "legal_entity.dob.month", + "legal_entity.dob.year", + "legal_entity.first_name", + "legal_entity.last_name", + "legal_entity.type", + "product_description", + "support_phone", + "tos_acceptance.date", + "tos_acceptance.ip"]) + + // ExternalAccounts + XCTAssertEqual(account.externalAccounts?.object, "list") + XCTAssertEqual(account.externalAccounts?.hasMore, false) + XCTAssertEqual(account.externalAccounts?.totalCount, 2) + XCTAssertEqual(account.externalAccounts?.url, "/v1/accounts/acct_1032D82eZvKYlo2C/external_accounts") + XCTAssertEqual(account.externalAccounts?.cardAccounts?.count, 1) + XCTAssertEqual(account.externalAccounts?.bankAccounts?.count, 1) + XCTAssertEqual(account.externalAccounts?.cardAccounts?[0].id, "card_1BoJ2IKrZ43eBVAbSXsWRMXT") + XCTAssertEqual(account.externalAccounts?.bankAccounts?[0].id, "ba_1BnxhQ2eZvKYlo2C5cM6hYK1") + + // LegalEntity + XCTAssertEqual(account.legalEntity?.address?.country, "US") + XCTAssertEqual(account.legalEntity?.businessName, "Vapor codes") + XCTAssertEqual(account.legalEntity?.businessTaxIdProvided, false) + XCTAssertEqual(account.legalEntity?.dob?["day"], 10) + XCTAssertEqual(account.legalEntity?.dob?["month"], 10) + XCTAssertEqual(account.legalEntity?.dob?["year"], 2016) + XCTAssertEqual(account.legalEntity?.firstName, "Mike") + XCTAssertEqual(account.legalEntity?.lastName, "Jones") + XCTAssertEqual(account.legalEntity?.personalAddress?.country, "US") + XCTAssertEqual(account.legalEntity?.personalAddress?.postalCode, "12345") + XCTAssertEqual(account.legalEntity?.personalIdNumberProvided, false) + XCTAssertEqual(account.legalEntity?.ssnLast4Provided, false) + XCTAssertEqual(account.legalEntity?.type, "individual") + XCTAssertEqual(account.legalEntity?.verification?.detailsCode, .failedOther) + XCTAssertEqual(account.legalEntity?.verification?.status, .unverified) - XCTAssertEqual(updatedAccount?.tosAcceptance?.ip, "0.0.0.0") - - XCTAssertEqual(updatedAccount?.tosAcceptance?.userAgent, "Safari") - } - 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 testDeleteAccount() throws { - do { - let deletedAccount = try drop?.stripe?.account.delete(account: accountId).serializedResponse() - - XCTAssertNotNil(deletedAccount) - - XCTAssertEqual(deletedAccount?.deleted, true) - } - 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 testRejectAccount() throws { - - do { - let rejectedAccount = try drop?.stripe?.account.reject(account: accountId, for: .fraud).serializedResponse() - - XCTAssertNotNil(rejectedAccount) - - XCTAssertEqual(rejectedAccount?.chargesEnabled, 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 testCreateLoginLink() throws { - do { - // make account express account - let loginLink = try drop?.stripe?.account.createLoginLink(forAccount: accountId).serializedResponse() - - XCTAssertNotNil(loginLink) - - XCTAssertNotNil(loginLink?.url) - } - 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 testListAllAccounts() throws { - do { - let accounts = try drop?.stripe?.account.listAll().serializedResponse() - - XCTAssertNotNil(accounts) - - if let accountItems = accounts?.items { - XCTAssertGreaterThanOrEqual(accountItems.count, 1) - } else { - XCTFail("Accounts 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 testFilterAccounts() throws { - do { - let filter = StripeFilter() - - filter.limit = 1 - - let accounts = try drop?.stripe?.account.listAll(filter: filter).serializedResponse() - - XCTAssertNotNil(accounts) - - if let accountItems = accounts?.items { - XCTAssertEqual(accountItems.count, 1) - } else { - XCTFail("Accounts 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) + // Additional Owners + XCTAssertEqual(account.legalEntity?.additionalOwners?[0].firstName, "Malcom") + XCTAssertEqual(account.legalEntity?.additionalOwners?[0].lastName, "X") + XCTAssertEqual(account.legalEntity?.additionalOwners?[0].maidenName, "old") + XCTAssertEqual(account.legalEntity?.additionalOwners?[0].personalIdNumberProvided, true) + XCTAssertEqual(account.legalEntity?.additionalOwners?[0].verification?.details, nil) + XCTAssertEqual(account.legalEntity?.additionalOwners?[0].verification?.detailsCode, .failedOther) + XCTAssertEqual(account.legalEntity?.additionalOwners?[0].verification?.document, nil) + XCTAssertEqual(account.legalEntity?.additionalOwners?[0].verification?.status, .verified) + XCTAssertEqual(account.legalEntity?.additionalOwners?[0].dob?["day"], 10) + XCTAssertEqual(account.legalEntity?.additionalOwners?[0].dob?["month"], 10) + XCTAssertEqual(account.legalEntity?.additionalOwners?[0].dob?["year"], 2016) + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - XCTFail(error.localizedDescription) + XCTFail("\(error.localizedDescription)") } } } diff --git a/Tests/StripeTests/BalanceTests.swift b/Tests/StripeTests/BalanceTests.swift index 91e3f92..0b942e1 100644 --- a/Tests/StripeTests/BalanceTests.swift +++ b/Tests/StripeTests/BalanceTests.swift @@ -7,218 +7,172 @@ // import XCTest - @testable import Stripe @testable import Vapor - - class BalanceTests: XCTestCase { - - var drop: Droplet? - var transactionId: String = "" - - override func setUp() { - do { - drop = try self.makeDroplet() - - let paymentToken = try drop?.stripe?.tokens.createCardToken(withCardNumber: "4242 4242 4242 4242", - expirationMonth: 10, - expirationYear: 2018, - cvc: 123, - name: "Test Card", - customer: nil, - currency: nil) - .serializedResponse().id ?? "" - - transactionId = try drop?.stripe?.charge.create(amount: 10_00, - in: .usd, - withFee: nil, - toAccount: nil, - capture: nil, - description: "Vapor Stripe: Test Description", - destinationAccountId: nil, - destinationAmount: nil, - transferGroup: nil, - onBehalfOf: nil, - receiptEmail: nil, - shippingLabel: nil, - customer: nil, - statementDescriptor: nil, - source: paymentToken, - metadata: nil) - .serializedResponse().balanceTransactionId ?? "" - } - 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 - transactionId = "" + let balanceString = """ +{ + "object": "balance", + "available": [ + { + "currency": "usd", + "amount": 32147287853, + "source_types": { + "card": 32026441972, + "bank_account": 119300699 + } + }, + ], + "connect_reserved": [ + { + "currency": "eur", + "amount": 0 + }, + { + "currency": "nzd", + "amount": 0 + }, + ], + "livemode": false, + "pending": [ + { + "currency": "cad", + "amount": -21092, + "source_types": { + "card": -21092 + } + }, + { + "currency": "jpy", + "amount": 0, + "source_types": { + } + }, + { + "currency": "aud", + "amount": -33, + "source_types": { + } + }, + { + "currency": "gbp", + "amount": -81045, + "source_types": { + } } + ] +} +""" - func testBalance() throws { - + func testBalanceParsedProperly() throws { do { - let object = try drop?.stripe?.balance.retrieveBalance().serializedResponse() - XCTAssertNotNil(object) - } - catch let error as StripeError { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase - 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 testBalanceTransactionItem() throws { - do { - let object = try drop?.stripe?.balance.retrieveBalance(forTransaction: transactionId).serializedResponse() - XCTAssertNotNil(object) - } - catch let error as StripeError { + let body = HTTPBody(string: balanceString) + let futureBalance = try decoder.decode(StripeBalance.self, from: body, on: EmbeddedEventLoop()) - 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) + futureBalance.do { (balance) in + XCTAssertEqual(balance.object, "balance") + XCTAssertEqual(balance.livemode, false) + + // BalanceTransfer + XCTAssertEqual(balance.available?[0].currency, .usd) + XCTAssertEqual(balance.available?[0].amount, 32147287853) + XCTAssertEqual(balance.available?[0].sourceTypes?["card"], 32026441972) + // TODO: - Seeif this camel case is resolved in future versions of swift 4.1 snapshot + XCTAssertEqual(balance.available?[0].sourceTypes?["bankAccount"], 119300699) + + XCTAssertEqual(balance.connectReserved?[0].currency, .eur) + XCTAssertEqual(balance.connectReserved?[0].amount, 0) + XCTAssertEqual(balance.connectReserved?[1].currency, .nzd) + XCTAssertEqual(balance.connectReserved?[1].amount, 0) + + XCTAssertEqual(balance.pending?[0].currency, .cad) + XCTAssertEqual(balance.pending?[0].amount, -21092) + XCTAssertEqual(balance.pending?[0].sourceTypes?["card"], -21092) + XCTAssertEqual(balance.pending?[1].currency, .jpy) + XCTAssertEqual(balance.pending?[1].amount, 0) + XCTAssertEqual(balance.pending?[2].currency, .aud) + XCTAssertEqual(balance.pending?[2].amount, -33) + XCTAssertEqual(balance.pending?[3].currency, .gbp) + XCTAssertEqual(balance.pending?[3].amount, -81045) + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - XCTFail(error.localizedDescription) + XCTFail("\(error.localizedDescription)") } } - func testBalanceHistory() throws { - do { - let object = try drop?.stripe?.balance.history(forFilter: nil).serializedResponse() - XCTAssertNotNil(object) - } - 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) - } + let balanceTransactionString = """ +{ + "id": "txn_19XJJ02eZvKYlo2ClwuJ1rbA", + "object": "balance_transaction", + "amount": 999, + "available_on": 1483920000, + "created": 1483315442, + "currency": "usd", + "description": null, + "exchange_rate": 12.5, + "fee": 59, + "fee_details": [ + { + "amount": 59, + "application": null, + "currency": "usd", + "description": "Stripe processing fees", + "type": "stripe_fee" } + ], + "net": 940, + "source": "ch_19XJJ02eZvKYlo2CHfSUsSpl", + "status": "pending", + "type": "charge" +} +""" - func testFilterBalanceHistory() throws { - + func testBalanceTransactionParsedProperly() throws { do { - let filter = StripeFilter() - filter.limit = 1 - let balance = try drop?.stripe?.balance.history(forFilter: filter).serializedResponse() + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase - if let balances = balance?.items { - XCTAssertEqual(balances.count, 1) - } else { - XCTFail("Balances are not present") - } - } - catch let error as StripeError { + let body = HTTPBody(string: balanceTransactionString) + let futureBalanceTransaction = try decoder.decode(StripeBalanceTransactionItem.self, from: body, on: EmbeddedEventLoop()) - 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) + futureBalanceTransaction.do { (balancetransaction) in + XCTAssertEqual(balancetransaction.id, "txn_19XJJ02eZvKYlo2ClwuJ1rbA") + XCTAssertEqual(balancetransaction.object, "balance_transaction") + XCTAssertEqual(balancetransaction.amount, 999) + XCTAssertEqual(balancetransaction.availableOn, Date(timeIntervalSince1970: 1483920000)) + XCTAssertEqual(balancetransaction.created, Date(timeIntervalSince1970: 1483315442)) + XCTAssertEqual(balancetransaction.currency, .usd) + XCTAssertEqual(balancetransaction.exchangeRate, 12.5) + XCTAssertEqual(balancetransaction.fee, 59) + XCTAssertEqual(balancetransaction.net, 940) + XCTAssertEqual(balancetransaction.source, "ch_19XJJ02eZvKYlo2CHfSUsSpl") + XCTAssertEqual(balancetransaction.status, .pending) + XCTAssertEqual(balancetransaction.type, .charge) + + // Fee + XCTAssertEqual(balancetransaction.feeDetails?[0].amount, 59) + XCTAssertEqual(balancetransaction.feeDetails?[0].currency, .usd) + XCTAssertEqual(balancetransaction.feeDetails?[0].description, "Stripe processing fees") + XCTAssertEqual(balancetransaction.feeDetails?[0].type, .stripeFee) + + + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - XCTFail(error.localizedDescription) + XCTFail("\(error.localizedDescription)") } } } diff --git a/Tests/StripeTests/ChargeTests.swift b/Tests/StripeTests/ChargeTests.swift index a7c2763..d85f4fa 100644 --- a/Tests/StripeTests/ChargeTests.swift +++ b/Tests/StripeTests/ChargeTests.swift @@ -7,403 +7,109 @@ // import XCTest - @testable import Stripe @testable import Vapor - - - - +// TODO: - Implement Review tests class ChargeTests: XCTestCase { + let chargeString = """ +{ + "id": "ch_1BrbM42eZvKYlo2CIu7qiNPF", + "object": "charge", + "amount": 999, + "amount_refunded": 0, + "application": "oops", + "application_fee": "fee_something", + "balance_transaction": "txn_19XJJ02eZvKYlo2ClwuJ1rbA", + "captured": false, + "created": 1517704056, + "currency": "usd", + "customer": "cus_A2FVP45tySz01V", + "description": null, + "destination": null, + "dispute": null, + "failure_code": "expired_card", + "failure_message": "Your card has expired.", + "fraud_details": { + }, + "invoice": "in_1BqUzH2eZvKYlo2CV8BYZkYX", + "livemode": false, + "metadata": { + }, + "on_behalf_of": "some account", + "order": "order number", + "outcome": { + "network_status": "declined_by_network", + "reason": "expired_card", + "risk_level": "not_assessed", + "seller_message": "The bank returned the decline code `expired_card`.", + "type": "issuer_declined" + }, + "paid": false, + "receipt_email": "a@b.com", + "receipt_number": "some number", + "refunded": false, + "refunds": {}, + "review": "prv_123456", + "shipping": null, + "source": null, + "source_transfer": "sickness", + "statement_descriptor": "for a shirt", + "status": "failed", + "transfer_group": "group a" +} +""" - var drop: Droplet? - var chargeId: String = "" - - override func setUp() { - do { - drop = try self.makeDroplet() - - let tokenId = try drop?.stripe?.tokens.createCardToken(withCardNumber: "4242 4242 4242 4242", - expirationMonth: 10, - expirationYear: 2018, - cvc: 123, - name: "Test Card", - customer: nil, - currency: nil) - .serializedResponse().id ?? "" - - chargeId = try drop?.stripe?.charge.create(amount: 10_00, - in: .usd, - withFee: nil, - toAccount: nil, - capture: true, - description: "Vapor Stripe: Test Description", - destinationAccountId: nil, - destinationAmount: nil, - transferGroup: nil, - onBehalfOf: nil, - receiptEmail: nil, - shippingLabel: nil, - customer: nil, - statementDescriptor: nil, - source: tokenId, - metadata: 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 - chargeId = "" - } - - func testCharge() throws { - - do { - let paymentToken = try drop?.stripe?.tokens.createCardToken(withCardNumber: "4242 4242 4242 4242", - expirationMonth: 10, - expirationYear: 2018, - cvc: 123, - name: "Test Card", - customer: nil, - currency: nil) - .serializedResponse().id ?? "" - - let object = try drop?.stripe?.charge.create(amount: 10_00, - in: .usd, - withFee: nil, - toAccount: nil, - capture: true, - description: "Vapor Stripe: Test Description", - destinationAccountId: nil, - destinationAmount: nil, - transferGroup: nil, - onBehalfOf: nil, - receiptEmail: nil, - shippingLabel: nil, - customer: nil, - statementDescriptor: nil, - source: paymentToken, - metadata: nil) - .serializedResponse() - XCTAssertNotNil(object) - } - 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 testRetrieveCharge() throws { - do { - let object = try drop?.stripe?.charge.retrieve(charge: chargeId).serializedResponse() - XCTAssertNotNil(object) - } - 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 testListAllCharges() throws { - do { - let object = try drop?.stripe?.charge.listAll(filter: nil).serializedResponse() - XCTAssertNotNil(object) - } - 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 testFilterAllCharges() throws { - do { - let filter = StripeFilter() - filter.limit = 1 - let object = try drop?.stripe?.charge.listAll(filter: filter).serializedResponse() - XCTAssertEqual(object?.items?.count, 1) - } - 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 testChargeUpdate() throws { + func testChargeParsedProperly() throws { do { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase - let description = "Vapor description" - - let fraudReport = try Node(node: ["user_report":FraudReport.safe.rawValue]) - let fraudDetails = try FraudDetails(node: fraudReport) - - let metadata = try Node(node:["hello": "world"]) - let receiptEmail = "vapor@stripetest.com" - - let shippingAddress = ShippingAddress() - shippingAddress.addressLine1 = "123 Test St" - shippingAddress.addressLine2 = "456 Apt" - shippingAddress.city = "Test City" - shippingAddress.state = "CA" - shippingAddress.postalCode = "12345" - shippingAddress.country = "US" - - let shippingLabel = ShippingLabel() - shippingLabel.name = "Test User" - shippingLabel.phone = "555-111-2222" - shippingLabel.carrier = "FedEx" - shippingLabel.trackingNumber = "1234567890" - shippingLabel.address = shippingAddress - - let transferGroup = "Vapor group" - - let updatedCharge = try drop?.stripe?.charge.update(charge: chargeId, - description: description, - fraud: fraudDetails, - receiptEmail: receiptEmail, - shippingLabel: shippingLabel, - transferGroup: transferGroup, - metadata: metadata).serializedResponse() + let body = HTTPBody(string: chargeString) + let futureCharge = try decoder.decode(StripeCharge.self, from: body, on: EmbeddedEventLoop()) - XCTAssertNotNil(updatedCharge) - - XCTAssertEqual(updatedCharge?.description, description) - - XCTAssertEqual(updatedCharge?.fraud?.userReport?.rawValue, fraudDetails.userReport?.rawValue) - - XCTAssertEqual(updatedCharge?.metadata?["hello"], metadata["hello"]) - - XCTAssertEqual(updatedCharge?.receiptEmail, receiptEmail) - - XCTAssertEqual(updatedCharge?.shippingLabel?.name, shippingLabel.name) - - XCTAssertEqual(updatedCharge?.shippingLabel?.phone, shippingLabel.phone) - - XCTAssertEqual(updatedCharge?.shippingLabel?.carrier, shippingLabel.carrier) - - XCTAssertEqual(updatedCharge?.shippingLabel?.trackingNumber, shippingLabel.trackingNumber) - - // Verify address values - - XCTAssertEqual(updatedCharge?.shippingLabel?.address?.addressLine1, shippingAddress.addressLine1) - - XCTAssertEqual(updatedCharge?.shippingLabel?.address?.addressLine2, shippingAddress.addressLine2) - - XCTAssertEqual(updatedCharge?.shippingLabel?.address?.city, shippingAddress.city) - - XCTAssertEqual(updatedCharge?.shippingLabel?.address?.state, shippingAddress.state) - - XCTAssertEqual(updatedCharge?.shippingLabel?.address?.postalCode, shippingAddress.postalCode) - - XCTAssertEqual(updatedCharge?.shippingLabel?.address?.country, shippingAddress.country) - } - 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 testChargeCapture() throws { - do { - let paymentToken = try drop?.stripe?.tokens.createCardToken(withCardNumber: "4242 4242 4242 4242", - expirationMonth: 10, - expirationYear: 2018, - cvc: 123, - name: "Test Card", - customer: nil, - currency: nil) - .serializedResponse().id ?? "" - - let uncapturedCharge = try drop?.stripe?.charge.create(amount: 10_00, - in: .usd, - withFee: nil, - toAccount: nil, - capture: false, - description: "Vapor Stripe: Test Description", - destinationAccountId: nil, - destinationAmount: nil, - transferGroup: nil, - onBehalfOf: nil, - receiptEmail: nil, - shippingLabel: nil, - customer: nil, - statementDescriptor: nil, - source: paymentToken, - metadata: nil) - .serializedResponse().id ?? "" - - let object = try drop?.stripe?.charge.capture(charge: uncapturedCharge, - amount: nil, - applicationFee: nil, - destinationAmount: nil, - receiptEmail: nil, - statementDescriptor: "Hello govna").serializedResponse() - - XCTAssertNotNil(object) - - XCTAssert(object?.isCaptured ?? 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) + futureCharge.do { (charge) in + XCTAssertEqual(charge.id, "ch_1BrbM42eZvKYlo2CIu7qiNPF") + XCTAssertEqual(charge.object, "charge") + XCTAssertEqual(charge.amount, 999) + XCTAssertEqual(charge.amountRefunded, 0) + XCTAssertEqual(charge.application, "oops") + XCTAssertEqual(charge.applicationFee, "fee_something") + XCTAssertEqual(charge.balanceTransaction, "txn_19XJJ02eZvKYlo2ClwuJ1rbA") + XCTAssertEqual(charge.captured, false) + XCTAssertEqual(charge.created, Date(timeIntervalSince1970: 1517704056)) + XCTAssertEqual(charge.currency, .usd) + XCTAssertEqual(charge.customer, "cus_A2FVP45tySz01V") + XCTAssertEqual(charge.failureCode, "expired_card") + XCTAssertEqual(charge.failureMessage, "Your card has expired.") + XCTAssertEqual(charge.invoice, "in_1BqUzH2eZvKYlo2CV8BYZkYX") + XCTAssertEqual(charge.livemode, false) + XCTAssertEqual(charge.onBehalfOf, "some account") + XCTAssertEqual(charge.order, "order number") + + // Outcome + XCTAssertEqual(charge.outcome?.networkStatus, .declinedByNetwork) + XCTAssertEqual(charge.outcome?.reason, "expired_card") + XCTAssertEqual(charge.outcome?.riskLevel, .notAssessed) + XCTAssertEqual(charge.outcome?.sellerMessage, "The bank returned the decline code `expired_card`.") + XCTAssertEqual(charge.outcome?.type, .issuerDeclined) + + XCTAssertEqual(charge.paid, false) + XCTAssertEqual(charge.receiptEmail, "a@b.com") + XCTAssertEqual(charge.receiptNumber, "some number") + XCTAssertEqual(charge.refunded, false) + XCTAssertEqual(charge.review, "prv_123456") + XCTAssertEqual(charge.sourceTransfer, "sickness") + XCTAssertEqual(charge.statementDescriptor, "for a shirt") + XCTAssertEqual(charge.status, .failed) + XCTAssertEqual(charge.transferGroup, "group a") + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - XCTFail(error.localizedDescription) + XCTFail("\(error.localizedDescription)") } } } diff --git a/Tests/StripeTests/CouponTests.swift b/Tests/StripeTests/CouponTests.swift deleted file mode 100644 index 2086d65..0000000 --- a/Tests/StripeTests/CouponTests.swift +++ /dev/null @@ -1,300 +0,0 @@ -// -// CouponTests.swift -// Stripe -// -// Created by Andrew Edwards on 5/28/17. -// -// - -import XCTest - -@testable import Stripe -@testable import Vapor - - - - - -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 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 - couponId = "" - } - - func testCreateCoupon() throws { - do { - 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) - } - 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 testRetrieveCoupon() throws { - do { - let coupon = try drop?.stripe?.coupons.retrieve(coupon: couponId).serializedResponse() - - XCTAssertNotNil(coupon) - } - 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 testUpdateCoupon() throws { - - do { - let metadata = try Node(node:["hello":"world"]) - let updatedCoupon = try drop?.stripe?.coupons.update(metadata: metadata, forCouponId: couponId).serializedResponse() - - XCTAssertNotNil(updatedCoupon) - - XCTAssertEqual(updatedCoupon?.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 testDeleteCoupon() throws { - do { - let deletedCoupon = try drop?.stripe?.coupons.delete(coupon: couponId).serializedResponse() - - XCTAssertNotNil(deletedCoupon) - - XCTAssertTrue(deletedCoupon?.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 testListAllCoupons() throws { - do { - let coupons = try drop?.stripe?.coupons.listAll(filter: nil).serializedResponse() - - XCTAssertNotNil(coupons) - - if let couponItems = coupons?.items { - XCTAssertGreaterThanOrEqual(couponItems.count, 1) - } else { - XCTFail("Coupons 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 testFilterCoupons() throws { - do { - 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") - } - } - 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/CustomerTests.swift b/Tests/StripeTests/CustomerTests.swift index 302d4e4..018ab23 100644 --- a/Tests/StripeTests/CustomerTests.swift +++ b/Tests/StripeTests/CustomerTests.swift @@ -7,528 +7,58 @@ // import XCTest - @testable import Stripe @testable import Vapor - - - - class CustomerTests: XCTestCase { - - var drop: Droplet? - var customerId: String = "" - - override func setUp() { - do { - drop = try self.makeDroplet() - - customerId = try drop?.stripe?.customer.create(description: "Vapor test Account", - email: "vapor@stripetest.com").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 - customerId = "" - } - - func testCreateCustomer() throws { - do { - let object = try drop?.stripe?.customer.create(accountBalance: nil, - businessVATId: nil, - coupon: nil, - defaultSource: nil, - description: "Vapor test Account", - email: "vapor@stripetest.com", - shipping: nil, - source: nil, - metadata: nil).serializedResponse() - XCTAssertNotNil(object) - } - 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 testRetrieveCustomer() throws { - do { - let object = try drop?.stripe?.customer.retrieve(customer: customerId).serializedResponse() - XCTAssertNotNil(object) - } - 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 testUpdateCustomer() throws { - do { - let updatedDescription = "Updated Vapor test Account" - - let updatedAccountBalance = 20_00 - - let metadata = try Node(node:["hello":"world"]) - - let updatedCustomer = try drop?.stripe?.customer.update(accountBalance: updatedAccountBalance, - businessVATId: nil, - coupon: nil, - defaultSourceId: nil, - description: updatedDescription, - email: nil, - shipping: nil, - newSource: nil, - metadata: metadata, - forCustomerId: customerId).serializedResponse() - XCTAssertNotNil(updatedCustomer) - - XCTAssertEqual(updatedCustomer?.description, updatedDescription) - - XCTAssertEqual(updatedCustomer?.accountBalance, updatedAccountBalance) - - XCTAssertEqual(updatedCustomer?.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 testAddNewSourceForCustomer() throws { - do { - let card = Node(["number":"4242 4242 4242 4242", "cvc":123,"exp_month":10, "exp_year":2018]) - - let cardSourceToken = try drop?.stripe?.sources.createNewSource(sourceType: .card, - source: card, - amount: nil, - currency: .usd, - flow: nil, - owner: nil, - redirectReturnUrl: nil, - token: nil, - usage: nil).serializedResponse().id ?? "" - - let newSource = try drop?.stripe?.customer.addNewSource(forCustomer: customerId, - inConnectAccount: nil, - source: cardSourceToken).serializedResponse() - - XCTAssertNotNil(newSource) - - let updatedCustomer = try drop?.stripe?.customer.retrieve(customer: customerId).serializedResponse() - - XCTAssertNotNil(updatedCustomer) - - let customerCardSource = updatedCustomer?.sources?.items?.filter { $0["id"]?.string == newSource?.id}.first - - XCTAssertNotNil(customerCardSource) - - XCTAssertEqual(newSource?.id, customerCardSource?["id"]?.string) - } - 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 testAddNewCardSourceForCustomer() throws { - do { - let card = Node( [ - "object":"card", - "exp_month":10, - "exp_year": 2020, - "number": "4242 4242 4242 4242", - "address_city": "anytown", - "address_country": "US", - "address_line1": "123 main street", - "address_line2": "apt. 0", - "address_state": "NY", - "address_zip": "12345", - "currency": "usd", - "cvc": 123, - "default_for_currency": true, - "name": "Mr. Vapor Codes" - ]) - - let newCard = try drop?.stripe?.customer.addNewCardSource(forCustomer: customerId, - inConnectAccount: nil, - source: card) - .serializedResponse() - - XCTAssertNotNil(newCard) - - let updatedCustomer = try drop?.stripe?.customer.retrieve(customer: customerId).serializedResponse() - - XCTAssertNotNil(updatedCustomer) - - let customerCardSource = updatedCustomer?.sources?.cardSources.filter { $0.id == newCard?.id}.first - - XCTAssertNotNil(customerCardSource) - - XCTAssertEqual(newCard?.id, customerCardSource?.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 { - XCTFail(error.localizedDescription) - } - } - - func testAddNewBankAccountSourceForCustomer() throws { - do { - let bankDetails: Node = Node([ - "object":"bank_account", - "account_number": "000123456789", - "country": "US", - "currency": "usd", - "account_holder_name": "Mr. Vapor", - "account_holder_type": "individual", - "routing_number": "110000000" - ]) - - let newBankToken = try drop?.stripe?.customer.addNewBankAccountSource(forCustomer: customerId, - inConnectAccount: nil, - source: bankDetails).serializedResponse() - - XCTAssertNotNil(newBankToken) - - let updatedCustomer = try drop?.stripe?.customer.retrieve(customer: customerId).serializedResponse() - - XCTAssertNotNil(updatedCustomer) - - let customerBankAccountSource = updatedCustomer?.sources?.bankSources.filter { $0.id == newBankToken?.id}.first - - XCTAssertNotNil(customerBankAccountSource) - - XCTAssertEqual(newBankToken?.id, customerBankAccountSource?.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 { - XCTFail(error.localizedDescription) - } - } - - func testDeleteDiscount() throws { - do { - let couponId = try drop?.stripe?.coupons.create(id: nil, - duration: .once, - amountOff: nil, - currency: nil, - durationInMonths: nil, - maxRedemptions: nil, - percentOff: 5, - redeemBy: nil) - .serializedResponse().id ?? "" - - let updatedCustomer = try drop?.stripe?.customer.update(accountBalance: nil, - businessVATId: nil, - coupon: couponId, - defaultSourceId: nil, - description: nil, - email: nil, - shipping: nil, - newSource: nil, - forCustomerId: customerId).serializedResponse() - - XCTAssertNotNil(updatedCustomer?.discount) - - let deletedDiscount = try drop?.stripe?.customer.deleteDiscount(onCustomer: updatedCustomer?.id ?? "").serializedResponse() - - XCTAssertTrue(deletedDiscount?.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 testDeleteCustomer() throws { - do { - let object = try drop?.stripe?.customer.delete(customer: customerId).serializedResponse() - XCTAssertEqual(object?.deleted, true) - } - 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 testRetrieveAllCustomers() throws { - do { - let object = try drop?.stripe?.customer.listAll(filter: nil).serializedResponse() - XCTAssertGreaterThanOrEqual(object!.items!.count, 1) - } - 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) - } - } + let customerString = """ +{ + "id": "cus_CG7FIUH6U4JTkB", + "object": "customer", + "account_balance": 0, + "created": 1517702745, + "currency": "usd", + "default_source": "scr_123456", + "delinquent": false, + "description": "A customer", + "discount": null, + "email": "abc@xyz.com", + "livemode": false, + "metadata": { + }, + "shipping": null, + "sources": null, + "subscriptions": null +} +""" - func testFilterCustomers() throws { + func testCustomerParsedProperly() throws { do { - let filter = StripeFilter() - - filter.limit = 1 - - let customers = try drop?.stripe?.customer.listAll(filter: filter).serializedResponse() - - if let customerItems = customers?.items { - XCTAssertEqual(customerItems.count, 1) - XCTAssertNotNil(customerItems.first) - } - else { - XCTFail("Customers are not present") - } - } - catch let error as StripeError { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase - 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) + let body = HTTPBody(string: customerString) + let futureCustomer = try decoder.decode(StripeCustomer.self, from: body, on: EmbeddedEventLoop()) + + futureCustomer.do { (customer) in + XCTAssertEqual(customer.id, "cus_CG7FIUH6U4JTkB") + XCTAssertEqual(customer.object, "customer") + XCTAssertEqual(customer.accountBalance, 0) + XCTAssertEqual(customer.created, Date(timeIntervalSince1970: 1517702745)) + XCTAssertEqual(customer.currency, .usd) + XCTAssertEqual(customer.defaultSource, "scr_123456") + XCTAssertEqual(customer.delinquent, false) + XCTAssertEqual(customer.description, "A customer") + XCTAssertEqual(customer.email, "abc@xyz.com") + XCTAssertEqual(customer.livemode, false) + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - XCTFail(error.localizedDescription) + XCTFail("\(error.localizedDescription)") } } } diff --git a/Tests/StripeTests/DisputeTests.swift b/Tests/StripeTests/DisputeTests.swift index 82fe73b..2312183 100644 --- a/Tests/StripeTests/DisputeTests.swift +++ b/Tests/StripeTests/DisputeTests.swift @@ -7,297 +7,124 @@ // import XCTest - @testable import Stripe @testable import Vapor - - - - class DisputeTests: XCTestCase { - - var drop: Droplet? - var disputeId: String = "" - - override func setUp() { - do { - drop = try self.makeDroplet() - - let customer = try drop?.stripe?.customer.create(accountBalance: nil, - businessVATId: nil, - coupon: nil, - defaultSource: nil, - description: "Vapor test Account", - email: "vapor@stripetest.com", - shipping: nil, - source: nil, - metadata: nil).serializedResponse() - - let card = Node(["number":"4000 0000 0000 0259", "cvc":123,"exp_month":10, "exp_year":2020]) - - let sourceId = try drop?.stripe?.sources.createNewSource(sourceType: .card, - source: card, - amount: nil, - currency: .usd, - flow: nil, - owner: nil, - redirectReturnUrl: nil, - token: nil, - usage: nil, - metadata: nil).serializedResponse().id ?? "" - - _ = try drop?.stripe?.customer.addNewSource(forCustomer: customer?.id ?? "", - inConnectAccount: nil, - source: sourceId).serializedResponse() - - let chargeId = try drop?.stripe?.charge.create(amount: 10_00, - in: .usd, - withFee: nil, - toAccount: nil, - capture: true, - description: "Vapor Stripe: Test Description", - destinationAccountId: nil, - destinationAmount: nil, - transferGroup: nil, - onBehalfOf: nil, - receiptEmail: nil, - shippingLabel: nil, - customer: customer?.id ?? "", - statementDescriptor: nil, - source: nil, - metadata: nil).serializedResponse().id ?? "" - - disputeId = try drop?.stripe?.charge.retrieve(charge: chargeId).serializedResponse().dispute ?? "" - - } - 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 - disputeId = "" - } - - func testRetrieveDispute() throws { - do { - let dispute = try drop?.stripe?.disputes.retrieve(dispute: disputeId).serializedResponse() - XCTAssertNotNil(dispute) - } - 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 testUpdateDispute() throws { - do { - let evidence = Node([ - "customer_name": "Mr. Vapor", - "refund_refusal_explanation": "Because we need the money", - "cancellation_rebuttal": "I have no further comments", - ]) - - let updatedDispute = try drop?.stripe?.disputes.update(dispute: disputeId, evidence: evidence, submit: true).serializedResponse() - - XCTAssertNotNil(updatedDispute) - - XCTAssertEqual(updatedDispute?.evidence?.customerName, "Mr. Vapor") - XCTAssertEqual(updatedDispute?.evidence?.refundRefusalExplination, "Because we need the money") - XCTAssertEqual(updatedDispute?.evidence?.cancellationRebuttal, "I have no further comments") - } - 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 testCloseDispute() throws { - do { - let closedDispute = try drop?.stripe?.disputes.close(dispute: disputeId).serializedResponse() - - XCTAssertNotNil(closedDispute) - - XCTAssertEqual(closedDispute?.disputeStatus, .lost) - } - 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 testListAllDisputes() throws { - do { - let disputes = try drop?.stripe?.account.listAll().serializedResponse() - - XCTAssertNotNil(disputes) - - if let disputeItems = disputes?.items { - XCTAssertGreaterThanOrEqual(disputeItems.count, 1) - } else { - XCTFail("Disputes 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) - } - } + let disputeString = """ +{ + "amount": 1000, + "balance_transactions": [], + "charge": "ch_1BoJ2MKrZ43eBVAbDNoY8Anc", + "created": 1234567890, + "currency": "usd", + "evidence": { + "access_activity_log": "Rasengan", + "billing_address": "Rasengan", + "cancellation_policy": "Rasengan", + "cancellation_policy_disclosure": "Rasengan", + "cancellation_rebuttal": "Rasengan", + "customer_communication": "Rasengan", + "customer_email_address": "Rasengan", + "customer_name": "Rasengan", + "customer_purchase_ip": "Rasengan", + "customer_signature": "Rasengan", + "duplicate_charge_documentation": "Rasengan", + "duplicate_charge_explanation": "Rasengan", + "duplicate_charge_id": "Rasengan", + "product_description": "Rasengan", + "receipt": "Rasengan", + "refund_policy": "Rasengan", + "refund_policy_disclosure": "Rasengan", + "refund_refusal_explanation": "Rasengan", + "service_date": "Rasengan", + "service_documentation": "Rasengan", + "shipping_address": "Rasengan", + "shipping_carrier": "Rasengan", + "shipping_date": "Rasengan", + "shipping_documentation": "Rasengan", + "shipping_tracking_number": "Rasengan", + "uncategorized_file": "Rasengan", + "uncategorized_text": "Rasengan" + }, + "evidence_details": { + "due_by": 1518566399, + "has_evidence": false, + "past_due": false, + "submission_count": 0 + }, + "id": "dp_1BoJ2MKrZ43eBVAbjyK5qAWL", + "is_charge_refundable": false, + "livemode": false, + "metadata": {}, + "object": "dispute", + "reason": "general", + "status": "needs_response" +} +""" - func testFilterDisputes() throws { + func testDisputeParsedProperly() throws { do { - let filter = StripeFilter() - - filter.limit = 1 - - let disputes = try drop?.stripe?.account.listAll(filter: filter).serializedResponse() - - XCTAssertNotNil(disputes) - - if let disputeItems = disputes?.items { - XCTAssertEqual(disputeItems.count, 1) - } else { - XCTFail("Disputes 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) + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase + + let body = HTTPBody(string: disputeString) + let futureDispute = try decoder.decode(StripeDispute.self, from: body, on: EmbeddedEventLoop()) + + futureDispute.do { (dispute) in + XCTAssertEqual(dispute.amount, 1000) + XCTAssertEqual(dispute.charge, "ch_1BoJ2MKrZ43eBVAbDNoY8Anc") + XCTAssertEqual(dispute.created, Date(timeIntervalSince1970: 1234567890)) + XCTAssertEqual(dispute.currency, .usd) + XCTAssertEqual(dispute.id, "dp_1BoJ2MKrZ43eBVAbjyK5qAWL") + XCTAssertEqual(dispute.isChargeRefundable, false) + XCTAssertEqual(dispute.livemode, false) + XCTAssertEqual(dispute.object, "dispute") + XCTAssertEqual(dispute.reason, .general) + XCTAssertEqual(dispute.status, .needsResponse) + + // Evidence Datails + XCTAssertEqual(dispute.evidenceDetails?.dueBy, Date(timeIntervalSince1970: 1518566399)) + XCTAssertEqual(dispute.evidenceDetails?.hasEvidence, false) + XCTAssertEqual(dispute.evidenceDetails?.pastDue, false) + XCTAssertEqual(dispute.evidenceDetails?.submissionCount, 0) + + // Evidence + XCTAssertEqual(dispute.evidence?.accessActivityLog, "Rasengan") + XCTAssertEqual(dispute.evidence?.billingAddress, "Rasengan") + XCTAssertEqual(dispute.evidence?.cancellationPolicy, "Rasengan") + XCTAssertEqual(dispute.evidence?.cancellationPolicyDisclosure, "Rasengan") + XCTAssertEqual(dispute.evidence?.cancellationRebuttal, "Rasengan") + XCTAssertEqual(dispute.evidence?.customerCommunication, "Rasengan") + XCTAssertEqual(dispute.evidence?.customerEmailAddress, "Rasengan") + XCTAssertEqual(dispute.evidence?.customerName, "Rasengan") + XCTAssertEqual(dispute.evidence?.customerPurchaseIp, "Rasengan") + XCTAssertEqual(dispute.evidence?.customerSignature, "Rasengan") + XCTAssertEqual(dispute.evidence?.duplicateChargeDocumentation, "Rasengan") + XCTAssertEqual(dispute.evidence?.duplicateChargeExplanation, "Rasengan") + XCTAssertEqual(dispute.evidence?.duplicateChargeId, "Rasengan") + XCTAssertEqual(dispute.evidence?.productDescription, "Rasengan") + XCTAssertEqual(dispute.evidence?.receipt, "Rasengan") + XCTAssertEqual(dispute.evidence?.refundPolicy, "Rasengan") + XCTAssertEqual(dispute.evidence?.refundPolicyDisclosure, "Rasengan") + XCTAssertEqual(dispute.evidence?.refundRefusalExplanation, "Rasengan") + XCTAssertEqual(dispute.evidence?.serviceDate, "Rasengan") + XCTAssertEqual(dispute.evidence?.serviceDocumentation, "Rasengan") + XCTAssertEqual(dispute.evidence?.shippingAddress, "Rasengan") + XCTAssertEqual(dispute.evidence?.shippingCarrier, "Rasengan") + XCTAssertEqual(dispute.evidence?.shippingDate, "Rasengan") + XCTAssertEqual(dispute.evidence?.shippingDocumentation, "Rasengan") + XCTAssertEqual(dispute.evidence?.shippingTrackingNumber, "Rasengan") + XCTAssertEqual(dispute.evidence?.uncategorizedFile, "Rasengan") + XCTAssertEqual(dispute.evidence?.uncategorizedText, "Rasengan") + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - XCTFail(error.localizedDescription) + XCTFail("\(error.localizedDescription)") } } } diff --git a/Tests/StripeTests/EphemeralKeyTests.swift b/Tests/StripeTests/EphemeralKeyTests.swift index 9ca551e..317312d 100644 --- a/Tests/StripeTests/EphemeralKeyTests.swift +++ b/Tests/StripeTests/EphemeralKeyTests.swift @@ -6,148 +6,48 @@ // import XCTest - @testable import Stripe @testable import Vapor class EphemeralKeyTests: XCTestCase { - - var drop: Droplet? - var ephemeralKeyId: String = "" - - override func setUp() { - - do { - drop = try self.makeDroplet() - - let customerId = try drop?.stripe?.customer.create(description: "Vapor test Account", - email: "vapor@stripetest.com").serializedResponse().id ?? "" - - ephemeralKeyId = try drop?.stripe?.ephemeralKeys.create(customerId: customerId).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)") - } + let emphkeyString = """ + { + "id": "eph_123456", + "object": "ephemeral_key", + "associated_objects": { + "hello": "world" + }, + "created": 1234567890, + "expires": 1234567890, + "livemode": true, + "secret": "imasecret" } +""" - func testCreateEphemeralKey() throws { - + func testEphemeralKeyParsedProperly() throws { do { - let customerId = try drop?.stripe?.customer.create(description: "Vapor test Account", - email: "vapor@stripetest.com").serializedResponse().id ?? "" - - let ephemeralKey = try drop?.stripe?.ephemeralKeys.create(customerId: customerId).serializedResponse() - - XCTAssertNotNil(ephemeralKey) - - XCTAssertNotNil(ephemeralKey?.associatedObjects?.array) - - XCTAssertNotNil(ephemeralKey?.associatedObjects?.array?.first) - - XCTAssertNotNil(ephemeralKey?.associatedObjects?.array?.first?.object) - - XCTAssertNotNil(ephemeralKey?.associatedObjects?.array?.first?.object?["type"]?.string) - - XCTAssertNotNil(ephemeralKey?.associatedObjects?.array?.first?.object?["id"]?.string) - - XCTAssertNotNil(ephemeralKey?.secret) - } - 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 testDeleteEphemeralKey() throws { - - do { - - let ephemeralKey = try drop?.stripe?.ephemeralKeys.delete(ephemeralKeyId: ephemeralKeyId).serializedResponse() - - XCTAssertNotNil(ephemeralKey) - - XCTAssertNotNil(ephemeralKey?.associatedObjects?.array) - - XCTAssertNotNil(ephemeralKey?.associatedObjects?.array?.first) - - XCTAssertNotNil(ephemeralKey?.associatedObjects?.array?.first?.object) - - XCTAssertNotNil(ephemeralKey?.associatedObjects?.array?.first?.object?["type"]?.string) - - XCTAssertNotNil(ephemeralKey?.associatedObjects?.array?.first?.object?["id"]?.string) - - XCTAssertNil(ephemeralKey?.secret) - } - 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) + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase + + let body = HTTPBody(string: emphkeyString) + let futureKey = try decoder.decode(StripeEphemeralKey.self, from: body, on: EmbeddedEventLoop()) + + futureKey.do { (key) in + XCTAssertEqual(key.id, "eph_123456") + XCTAssertEqual(key.object, "ephemeral_key") + XCTAssertEqual(key.associatedObjects?["hello"], "world") + XCTAssertEqual(key.created, Date(timeIntervalSince1970: 1234567890)) + XCTAssertEqual(key.expires, Date(timeIntervalSince1970: 1234567890)) + XCTAssertEqual(key.livemode, true) + XCTAssertEqual(key.secret, "imasecret") + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - XCTFail(error.localizedDescription) + XCTFail("\(error.localizedDescription)") } } } diff --git a/Tests/StripeTests/ErrorTests.swift b/Tests/StripeTests/ErrorTests.swift new file mode 100644 index 0000000..27ca1ef --- /dev/null +++ b/Tests/StripeTests/ErrorTests.swift @@ -0,0 +1,49 @@ +// +// ErrorTests.swift +// StripeTests +// +// Created by Andrew Edwards on 2/27/18. +// + +import XCTest +@testable import Stripe +@testable import Vapor + +class ErrorTests: XCTestCase { + let errorString = """ +{ + "type": "card_error", + "charge": "ch_12345", + "message": "Sorry kiddo", + "code": "invalid_swipe_data", + "decline_code": "stolen_card", + "param": "card_number" +} +""" + + func testErrorParsedProperly() throws { + do { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase + + let body = HTTPBody(string: errorString) + let futureError = try decoder.decode(StripeAPIError.self, from: body, on: EmbeddedEventLoop()) + + futureError.do { (stripeError) in + XCTAssertEqual(stripeError.type, .cardError) + XCTAssertEqual(stripeError.charge, "ch_12345") + XCTAssertEqual(stripeError.message, "Sorry kiddo") + XCTAssertEqual(stripeError.code, .invalidSwipeData) + XCTAssertEqual(stripeError.declineCode, .stolenCard) + XCTAssertEqual(stripeError.param, "card_number") + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") + } + } + catch { + XCTFail("\(error.localizedDescription)") + } + } +} diff --git a/Tests/StripeTests/InvoiceItemTests.swift b/Tests/StripeTests/InvoiceItemTests.swift deleted file mode 100644 index b78edc0..0000000 --- a/Tests/StripeTests/InvoiceItemTests.swift +++ /dev/null @@ -1,243 +0,0 @@ -// -// InvoiceTests.swift -// Stripe -// -// Created by Anthony Castelli on 9/5/17. -// -// - -import XCTest - -@testable import Stripe -@testable import Vapor -@testable import Random - -class InvoiceItemTests: XCTestCase { - - var drop: Droplet? - var customerId = "" - var invoiceItemId = "" - - override func setUp() { - super.setUp() - do { - self.drop = try self.makeDroplet() - - let paymentTokenSource = try self.drop?.stripe?.tokens.createCardToken( - withCardNumber: "4242 4242 4242 4242", - expirationMonth: 10, - expirationYear: 2018, - cvc: 123, - name: "Test Card", - customer: nil, - currency: nil - ).serializedResponse().id ?? "" - - self.customerId = try self.drop?.stripe?.customer.create( - accountBalance: nil, - businessVATId: nil, - coupon: nil, - defaultSource: nil, - description: nil, - email: nil, - shipping: nil, - source: paymentTokenSource - ).serializedResponse().id ?? "" - - self.invoiceItemId = try self.drop?.stripe?.invoiceItems.createItem( - forCustomer: self.customerId, - amount: 10_000, inCurrency: .usd - ).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() { - self.drop = nil - self.customerId = "" - self.invoiceItemId = "" - super.tearDown() - } - - func testCreatingItem() throws { - do { - let object = try self.drop?.stripe?.invoiceItems.createItem( - forCustomer: self.customerId, - amount: 10_000, inCurrency: .usd - ).serializedResponse() - XCTAssertNotNil(object) - } 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 testFetchingItem() throws { - do { - let object = try self.drop?.stripe?.invoiceItems.fetch(invoiceItem: self.invoiceItemId).serializedResponse() - XCTAssertNotNil(object) - } 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 testDeletingItem() throws { - do { - let object = try self.drop?.stripe?.invoiceItems.delete(invoiceItem: self.invoiceItemId) - XCTAssertNotNil(object) - } 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 testUpdateItem() throws { - do { - let object = try self.drop?.stripe?.invoiceItems.update( - invoiceItem: self.invoiceItemId, - amount: 10_000, - description: "Update Invoice" - ).serializedResponse() - - XCTAssertEqual("Update Invoice", object?.description) - XCTAssertNotNil(object) - } 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 testListAllItems() throws { - do { - let object = try self.drop?.stripe?.invoiceItems.listAll().serializedResponse() - XCTAssertNotNil(object) - } 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/InvoiceTests.swift b/Tests/StripeTests/InvoiceTests.swift index 7b076fd..34d3882 100644 --- a/Tests/StripeTests/InvoiceTests.swift +++ b/Tests/StripeTests/InvoiceTests.swift @@ -7,306 +7,174 @@ // import XCTest - @testable import Stripe @testable import Vapor -@testable import Random class InvoiceTests: XCTestCase { - - var drop: Droplet? - var customerId = "" - var subscriptionId = "" - var invoiceId = "" - var invoiceItemId = "" - - override func setUp() { - super.setUp() - do { - self.drop = try self.makeDroplet() - - let planId = try self.drop?.stripe?.plans.create( - id: Data(bytes: URandom.bytes(count: 16)).base64String, - amount: 10_00, - currency: .usd, - interval: .week, - name: "Test Plan", - intervalCount: 5, - statementDescriptor: "Test Plan", - trialPeriodDays: nil, - metadata: nil - ).serializedResponse().id ?? "" - - let paymentTokenSource = try self.drop?.stripe?.tokens.createCardToken( - withCardNumber: "4242 4242 4242 4242", - expirationMonth: 10, - expirationYear: 2018, - cvc: 123, - name: "Test Card", - customer: nil, - currency: nil - ).serializedResponse().id ?? "" - - self.customerId = try self.drop?.stripe?.customer.create( - accountBalance: nil, - businessVATId: nil, - coupon: nil, - defaultSource: nil, - description: nil, - email: nil, - shipping: nil, - source: paymentTokenSource - ).serializedResponse().id ?? "" - - self.subscriptionId = try self.drop?.stripe?.subscriptions.create( - forCustomer: self.customerId, - plan: planId, - applicationFeePercent: nil, - couponId: nil, - items: nil, - quantity: nil, - source: nil, - taxPercent: nil, - trialEnd: nil, - trialPeriodDays: nil - ).serializedResponse().id ?? "" - - self.invoiceItemId = try self.drop?.stripe?.invoiceItems.createItem( - forCustomer: self.customerId, - amount: 10_000, inCurrency: .usd - ).serializedResponse().id ?? "" - - self.invoiceId = try drop?.stripe?.invoices.create( - forCustomer: self.customerId, - subscription: self.subscriptionId - ).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) + let invoiceString = """ +{ + "amount_due": 0, + "attempt_count": 0, + "attempted": false, + "billing": "charge_automatically", + "closed": false, + "currency": "usd", + "customer": "cus_CCiTI4Tpghl0nK", + "date": 1234567890, + "due_date": 1234567890, + "forgiven": false, + "id": "in_1BoJ2NKrZ43eBVAbQ8jb0Xfj", + "lines": { + "data": [ + { + "amount": 1000, + "currency": "usd", + "description": "My First Invoice Item (created for API docs)", + "discountable": true, + "id": "ii_1BoJ2NKrZ43eBVAby2HbDsY5", + "livemode": false, + "metadata": {}, + "object": "line_item", + "period": { + "end": 1516918659, + "start": 1516918659 + }, + "proration": false, + "type": "invoiceitem" } - } catch { - fatalError("Setup failed: \(error.localizedDescription)") - } - } - - override func tearDown() { - self.drop = nil - self.customerId = "" - self.subscriptionId = "" - self.invoiceId = "" - self.invoiceItemId = "" - super.tearDown() - } + ], + "has_more": false, + "object": "list", + "url": "/v1/invoices/in_1BoJ2NKrZ43eBVAbQ8jb0Xfj/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": 1234567890, + "number": "fe61cc956c-0001", + "object": "invoice", + "paid": false, + "period_end": 1234567890, + "period_start": 1234567890, + "starting_balance": 0, + "subtotal": 0, + "total": 0, + "webhooks_delivered_at": 1234567890 +} +""" - func testCreatingInvoice() throws { + func testInvoiceParsedProperly() throws { do { - let invoiceItem = try self.drop?.stripe?.invoiceItems.createItem( - forCustomer: self.customerId, - amount: 10_000, inCurrency: .usd - ).serializedResponse().id ?? "" - XCTAssertNotNil(invoiceItem) + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase - let object = try self.drop?.stripe?.invoices.create( - forCustomer: self.customerId, - subscription: self.subscriptionId - ).serializedResponse() - XCTAssertNotNil(object) - } 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 testFetchingInvoice() throws { - do { - let object = try self.drop?.stripe?.invoices.fetch(invoice: self.invoiceId).serializedResponse() - XCTAssertNotNil(object) - } 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) + let body = HTTPBody(string: invoiceString) + let futureInvoice = try decoder.decode(StripeInvoice.self, from: body, on: EmbeddedEventLoop()) + + futureInvoice.do { (invoice) in + XCTAssertEqual(invoice.id, "in_1BoJ2NKrZ43eBVAbQ8jb0Xfj") + XCTAssertEqual(invoice.amountDue, 0) + XCTAssertEqual(invoice.attemptCount, 0) + XCTAssertEqual(invoice.attempted, false) + XCTAssertEqual(invoice.billing, "charge_automatically") + XCTAssertEqual(invoice.closed, false) + XCTAssertEqual(invoice.currency, .usd) + XCTAssertEqual(invoice.customer, "cus_CCiTI4Tpghl0nK") + XCTAssertEqual(invoice.date, Date(timeIntervalSince1970: 1234567890)) + XCTAssertEqual(invoice.dueDate, Date(timeIntervalSince1970: 1234567890)) + XCTAssertEqual(invoice.forgiven, false) + XCTAssertEqual(invoice.livemode, false) + XCTAssertEqual(invoice.nextPaymentAttempt, Date(timeIntervalSince1970: 1234567890)) + XCTAssertEqual(invoice.number, "fe61cc956c-0001") + XCTAssertEqual(invoice.object, "invoice") + XCTAssertEqual(invoice.paid, false) + XCTAssertEqual(invoice.periodEnd, Date(timeIntervalSince1970: 1234567890)) + XCTAssertEqual(invoice.periodStart, Date(timeIntervalSince1970: 1234567890)) + XCTAssertEqual(invoice.startingBalance, 0) + XCTAssertEqual(invoice.subtotal, 0) + XCTAssertEqual(invoice.total, 0) + XCTAssertEqual(invoice.webhooksDeliveredAt, Date(timeIntervalSince1970: 1234567890)) + + // Test for invoice lineItem + XCTAssertEqual(invoice.lines?.hasMore, false) + XCTAssertEqual(invoice.lines?.object, "list") + XCTAssertEqual(invoice.lines?.url, "/v1/invoices/in_1BoJ2NKrZ43eBVAbQ8jb0Xfj/lines") + XCTAssertEqual(invoice.lines?.data?[0].amount, 1000) + XCTAssertEqual(invoice.lines?.data?[0].currency, .usd) + XCTAssertEqual(invoice.lines?.data?[0].description, "My First Invoice Item (created for API docs)") + XCTAssertEqual(invoice.lines?.data?[0].discountable, true) + XCTAssertEqual(invoice.lines?.data?[0].id, "ii_1BoJ2NKrZ43eBVAby2HbDsY5") + XCTAssertEqual(invoice.lines?.data?[0].livemode, false) + XCTAssertEqual(invoice.lines?.data?[0].object, "line_item") + XCTAssertEqual(invoice.lines?.data?[0].proration, false) + XCTAssertEqual(invoice.lines?.data?[0].type, "invoiceitem") + XCTAssertEqual(invoice.lines?.data?[0].period?.start, Date(timeIntervalSince1970: 1516918659)) + XCTAssertEqual(invoice.lines?.data?[0].period?.end, Date(timeIntervalSince1970: 1516918659)) + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } - } catch { - XCTFail(error.localizedDescription) } - } - - func testFetchingInvoiceItems() throws { - do { - let object = try self.drop?.stripe?.invoices.listItems(forInvoice: self.invoiceId) - XCTAssertNotNil(object) - } 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) + catch { + XCTFail("\(error.localizedDescription)") } } - func testFetchUpcomingInvoice() throws { - do { - let object = try self.drop?.stripe?.invoices.upcomingInvoice(forCustomer: self.customerId).serializedResponse() - XCTAssertNotNil(object) - } 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) - } - } + let invoiceItemString = """ +{ + "id": "ii_1BtydB2eZvKYlo2CzeKs27EC", + "object": "invoiceitem", + "amount": 2500, + "currency": "usd", + "customer": "cus_CIaBoYfVixDHWf", + "date": 1518270185, + "description": "One-time setup fee", + "discountable": true, + "invoice": "in_1BtydF2eZvKYlo2COZpiUqSi", + "livemode": false, + "metadata": { + }, + "period": { + "start": 1518270185, + "end": 1518270185 + }, + "plan": null, + "proration": false, + "quantity": null, + "subscription": null +} +""" - func testUpdateInvoice() throws { + func testInvoiceItemParsedProperly() throws { do { - let object = try self.drop?.stripe?.invoices.update( - invoice: self.invoiceId, - description: "Update Invoice" - ).serializedResponse() + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase + + let body = HTTPBody(string: invoiceItemString) + let futureInvoiceItem = try decoder.decode(StripeInvoiceItem.self, from: body, on: EmbeddedEventLoop()) - XCTAssertEqual("Update Invoice", object?.description) - XCTAssertNotNil(object) - } 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) + futureInvoiceItem.do { (invoiceItem) in + XCTAssertEqual(invoiceItem.id, "ii_1BtydB2eZvKYlo2CzeKs27EC") + XCTAssertEqual(invoiceItem.object, "invoiceitem") + XCTAssertEqual(invoiceItem.amount, 2500) + XCTAssertEqual(invoiceItem.currency, .usd) + XCTAssertEqual(invoiceItem.customer, "cus_CIaBoYfVixDHWf") + XCTAssertEqual(invoiceItem.date, Date(timeIntervalSince1970: 1518270185)) + XCTAssertEqual(invoiceItem.description, "One-time setup fee") + XCTAssertEqual(invoiceItem.discountable, true) + XCTAssertEqual(invoiceItem.invoice, "in_1BtydF2eZvKYlo2COZpiUqSi") + XCTAssertEqual(invoiceItem.livemode, false) + XCTAssertEqual(invoiceItem.proration, false) + XCTAssertEqual(invoiceItem.quantity, nil) + XCTAssertEqual(invoiceItem.subscription, nil) + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } - } catch { - XCTFail(error.localizedDescription) } - } - - func testListAllInvoices() throws { - do { - let object = try self.drop?.stripe?.invoices.listAll().serializedResponse() - XCTAssertNotNil(object) - } 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) + catch { + XCTFail("\(error.localizedDescription)") } } } diff --git a/Tests/StripeTests/OrderReturnTests.swift b/Tests/StripeTests/OrderReturnTests.swift deleted file mode 100644 index 939ee6c..0000000 --- a/Tests/StripeTests/OrderReturnTests.swift +++ /dev/null @@ -1,238 +0,0 @@ -// -// OrderReturnTests.swift -// Stripe -// -// Created by Andrew Edwards on 8/25/17. -// -// - -import XCTest - -@testable import Stripe -@testable import Vapor - - - - - -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 index a1048a9..320e0ac 100644 --- a/Tests/StripeTests/OrderTests.swift +++ b/Tests/StripeTests/OrderTests.swift @@ -7,361 +7,150 @@ // import XCTest - @testable import Stripe @testable import Vapor - - - - 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) + let orderString = """ +{ + "amount": 1500, + "created": 1234567890, + "currency": "usd", + "id": "or_1BoJ2NKrZ43eBVAbFf4SZyvD", + "items": [ + { + "amount": 1500, + "currency": "usd", + "description": "Gold Special", + "object": "order_item", + "parent": "sk_1BoJ2KKrZ43eBVAbu7ioKR0i", + "quantity": null, + "type": "sku" + } + ], + "livemode": false, + "metadata": { + "hello": "world" + }, + "object": "order", + "returns": { + "data": [ + { + "amount": 1500, + "created": 1234567890, + "currency": "usd", + "id": "orret_1BoJ2NKrZ43eBVAb8r8dx0GO", + "items": [ + { + "amount": 1500, + "currency": "usd", + "description": "Gold Special", + "object": "order_item", + "parent": "sk_1BoJ2NKrZ43eBVAb4RD4HHuH", + "quantity": null, + "type": "sku" + } + ], + "livemode": false, + "object": "order_return", + "order": "or_1BoJ2NKrZ43eBVAbSnN443id", + "refund": "re_1BoJ2NKrZ43eBVAbop3rb4h1" } - } - 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) - } - } + ], + "has_more": false, + "object": "list", + "url": "/v1/order_returns?order=or_1BoJ2NKrZ43eBVAbFf4SZyvD" + }, + "shipping": { + "address": { + "city": "Anytown", + "country": "US", + "line1": "1234 Main street", + "line2": null, + "postal_code": "123456", + "state": null + }, + "carrier": null, + "name": "Jenny Rosen", + "phone": null, + "tracking_number": null + }, + "status": "created", + "status_transitions": { + "canceled": 1515290550, + "fulfiled": 1507690550, + "paid": 1517688550, + "returned": 1927690550 + }, + "updated": 1234567890 +} +""" - func testReturnOrder() throws { + func testOrderIsProperlyParsed() 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) + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase + + let body = HTTPBody(string: orderString) + let futureOrder = try decoder.decode(StripeOrder.self, from: body, on: EmbeddedEventLoop()) + + futureOrder.do { (order) in + XCTAssertEqual(order.id, "or_1BoJ2NKrZ43eBVAbFf4SZyvD") + XCTAssertEqual(order.amount, 1500) + XCTAssertEqual(order.created, Date(timeIntervalSince1970: 1234567890)) + XCTAssertEqual(order.currency, .usd) + XCTAssertEqual(order.livemode, false) + XCTAssertEqual(order.metadata?["hello"], "world") + XCTAssertEqual(order.object, "order") + XCTAssertEqual(order.status, .created) + + // This test covers the Order Item object + XCTAssertEqual(order.statusTransitions?.canceled, Date(timeIntervalSince1970: 1515290550)) + XCTAssertEqual(order.statusTransitions?.fulfiled, Date(timeIntervalSince1970: 1507690550)) + XCTAssertEqual(order.statusTransitions?.paid, Date(timeIntervalSince1970: 1517688550)) + XCTAssertEqual(order.statusTransitions?.returned, Date(timeIntervalSince1970: 1927690550)) + XCTAssertEqual(order.updated, Date(timeIntervalSince1970: 1234567890)) + + XCTAssertEqual(order.items?[0].amount, 1500) + XCTAssertEqual(order.items?[0].currency, .usd) + XCTAssertEqual(order.items?[0].description, "Gold Special") + XCTAssertEqual(order.items?[0].object, "order_item") + XCTAssertEqual(order.items?[0].parent, "sk_1BoJ2KKrZ43eBVAbu7ioKR0i") + XCTAssertEqual(order.items?[0].quantity, nil) + XCTAssertEqual(order.items?[0].type, .sku) + + XCTAssertEqual(order.returns?.hasMore, false) + XCTAssertEqual(order.returns?.object, "list") + XCTAssertEqual(order.returns?.url, "/v1/order_returns?order=or_1BoJ2NKrZ43eBVAbFf4SZyvD") + + // This test covers the OrderItem Return object + XCTAssertEqual(order.returns?.data?[0].amount, 1500) + XCTAssertEqual(order.returns?.data?[0].created, Date(timeIntervalSince1970: 1234567890)) + XCTAssertEqual(order.returns?.data?[0].currency, .usd) + XCTAssertEqual(order.returns?.data?[0].id, "orret_1BoJ2NKrZ43eBVAb8r8dx0GO") + XCTAssertEqual(order.returns?.data?[0].livemode, false) + XCTAssertEqual(order.returns?.data?[0].object, "order_return") + XCTAssertEqual(order.returns?.data?[0].order, "or_1BoJ2NKrZ43eBVAbSnN443id") + XCTAssertEqual(order.returns?.data?[0].refund, "re_1BoJ2NKrZ43eBVAbop3rb4h1") + + XCTAssertEqual(order.shipping?.address?.city, "Anytown") + XCTAssertEqual(order.shipping?.address?.country, "US") + XCTAssertEqual(order.shipping?.address?.line1, "1234 Main street") + XCTAssertEqual(order.shipping?.address?.line2, nil) + XCTAssertEqual(order.shipping?.address?.postalCode, "123456") + XCTAssertEqual(order.shipping?.address?.state, nil) + XCTAssertEqual(order.shipping?.carrier, nil) + XCTAssertEqual(order.shipping?.name, "Jenny Rosen") + XCTAssertEqual(order.shipping?.phone, nil) + XCTAssertEqual(order.shipping?.trackingNumber, nil) + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } - catch { - XCTFail(error.localizedDescription) + catch { + XCTFail("\(error.localizedDescription)") } } } diff --git a/Tests/StripeTests/PlanTests.swift b/Tests/StripeTests/PlanTests.swift deleted file mode 100644 index 4aee280..0000000 --- a/Tests/StripeTests/PlanTests.swift +++ /dev/null @@ -1,319 +0,0 @@ -// -// PlanTests.swift -// Stripe -// -// Created by Andrew Edwards on 5/29/17. -// -// - -import XCTest -import Foundation - -@testable import Stripe -@testable import Vapor - - - - -@testable import Random - -class PlanTests: XCTestCase { - var drop: Droplet? - var planId: String = "" - - override func setUp() { - do { - drop = try self.makeDroplet() - - planId = try drop?.stripe?.plans.create(id: Data(bytes: URandom.bytes(count: 16)).base64String, - amount: 10_00, - currency: .usd, - interval: .week, - name: "Test Plan", - intervalCount: 5, - statementDescriptor: "Test Plan", - trialPeriodDays: nil, - metadata: 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 - planId = "" - } - - func testCreatePlan() throws { - do { - let plan = try drop?.stripe?.plans.create(id: Data(bytes: URandom.bytes(count: 16)).base64String, - amount: 10_00, - currency: .usd, - interval: .week, - name: "Test Plan", - intervalCount: 5, - statementDescriptor: "Test Plan", - trialPeriodDays: nil, - metadata: nil) - .serializedResponse().id ?? "" - XCTAssertNotNil(plan) - } - 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 testRetrievePlan() throws { - do { - let plan = try drop?.stripe?.plans.retrieve(plan: planId) - - XCTAssertNotNil(plan) - } - 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 testUpdatePlan() throws { - do { - let metadata = try Node(node:["hello":"world"]) - let newName = "Super plan" - let newStatementDescriptor = "Super descriptor" - let trialDays = 30 - - let updatedPlan = try drop?.stripe?.plans.update(name: newName, - statementDescriptor: newStatementDescriptor, - trialPeriodDays: trialDays, - metadata: metadata, - forPlanId: planId) - .serializedResponse() - - XCTAssertNotNil(updatedPlan) - - XCTAssertEqual(updatedPlan?.metadata?["hello"], metadata["hello"]) - - XCTAssertEqual(updatedPlan?.name, newName) - - XCTAssertEqual(updatedPlan?.statementDescriptor, newStatementDescriptor) - - XCTAssertEqual(updatedPlan?.trialPeriodDays, trialDays) - } - 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 testDeletePlan() throws { - do { - let deletedPlan = try drop?.stripe?.plans.delete(plan: planId).serializedResponse() - - XCTAssertNotNil(deletedPlan) - - XCTAssertTrue(deletedPlan?.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 testListAllPlans() throws { - do { - let plans = try drop?.stripe?.plans.listAll(filter: nil).serializedResponse() - - XCTAssertNotNil(plans) - - if let planItems = plans?.items { - XCTAssertGreaterThanOrEqual(planItems.count, 1) - } else { - XCTFail("Plans 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 testFilterPlans() throws { - do { - let filter = StripeFilter() - - filter.limit = 1 - - let plans = try drop?.stripe?.plans.listAll(filter: filter).serializedResponse() - - XCTAssertNotNil(plans) - - if let planItems = plans?.items { - XCTAssertEqual(planItems.count, 1) - } else { - XCTFail("Plans 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/ProductTests.swift b/Tests/StripeTests/ProductTests.swift index 8373d66..1849f25 100644 --- a/Tests/StripeTests/ProductTests.swift +++ b/Tests/StripeTests/ProductTests.swift @@ -7,292 +7,119 @@ // import XCTest - @testable import Stripe @testable import Vapor - - - - class ProductTests: XCTestCase { + let productString = """ + { + "id": "prod_BosWT9EsdzgjPn", + "object": "product", + "active": false, + "attributes": [ + "size", + "gender" + ], + "caption": "test", + "created": 1511420673, + "deactivate_on": [ - 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) - } - } + ], + "description": "Comfortable cotton t-shirt", + "images": [ - 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) - } + ], + "livemode": false, + "metadata": { + }, + "name": "T-shirt", + "package_dimensions": { + "height": 1.25, + "length": 2.44, + "weight": 3.0, + "width": 4.0 + }, + "shippable": false, + "skus": { + "object": "list", + "data": [ + { + "id": "sku_C3rCQ7Eq4wRfLw", + "object": "sku", + "active": true, + "attributes": { + "size": "Medium", + "gender": "Unisex" + }, + "created": 1514875358, + "currency": "usd", + "image": null, + "inventory": { + "quantity": 500, + "type": "finite", + "value": null + }, + "livemode": false, + "metadata": { + }, + "package_dimensions": null, + "price": 1500, + "product": "prod_BosWT9EsdzgjPn", + "updated": 1514875358 + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/skus?product=prod_BosWT9EsdzgjPn&active=true" + }, + "updated": 1511422435, + "type": "good", + "url": "https://api.stripe.com/" } +""" - func testFilterProducts() throws { + func testProductParsedProperly() 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) + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase + + let body = HTTPBody(string: productString) + let futureProduct = try decoder.decode(StripeProduct.self, from: body, on: EmbeddedEventLoop()) + + futureProduct.do { (product) in + XCTAssertEqual(product.id, "prod_BosWT9EsdzgjPn") + XCTAssertEqual(product.object, "product") + XCTAssertEqual(product.active, false) + XCTAssertEqual(product.attributes, ["size", "gender"]) + XCTAssertEqual(product.caption, "test") + XCTAssertEqual(product.created, Date(timeIntervalSince1970: 1511420673)) + XCTAssertEqual(product.deactivateOn, []) + XCTAssertEqual(product.description, "Comfortable cotton t-shirt") + XCTAssertEqual(product.images, []) + XCTAssertEqual(product.livemode, false) + XCTAssertEqual(product.metadata, [:]) + XCTAssertEqual(product.name, "T-shirt") + XCTAssertEqual(product.packageDimensions?.height, 1.25) + XCTAssertEqual(product.packageDimensions?.length, 2.44) + XCTAssertEqual(product.packageDimensions?.weight, 3.0) + XCTAssertEqual(product.packageDimensions?.width, 4.0) + XCTAssertEqual(product.shippable, false) + XCTAssertEqual(product.updated, Date(timeIntervalSince1970: 1511422435)) + XCTAssertEqual(product.url, "https://api.stripe.com/") + XCTAssertEqual(product.type, "good") + XCTAssertEqual(product.skus?.object, "list") + XCTAssertEqual(product.skus?.hasMore, false) + XCTAssertEqual(product.skus?.totalCount, 1) + XCTAssertEqual(product.skus?.url, "/v1/skus?product=prod_BosWT9EsdzgjPn&active=true") + XCTAssertNotNil(product.skus?.data?[0]) + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - XCTFail(error.localizedDescription) + XCTFail("\(error.localizedDescription)") } } } diff --git a/Tests/StripeTests/ProviderTests.swift b/Tests/StripeTests/ProviderTests.swift index 9132b5c..f5f7428 100644 --- a/Tests/StripeTests/ProviderTests.swift +++ b/Tests/StripeTests/ProviderTests.swift @@ -1,17 +1 @@ -import XCTest -@testable import Stripe -@testable import Vapor - -class ProviderTests: XCTestCase { - - func testProvider() throws { - let config = Config([ - "stripe": [ - "apiKey": "API_KEY" - ], - ]) - try config.addProvider(Stripe.Provider.self) - let drop = try Droplet(config: config) - XCTAssertEqual(drop.stripe?.apiKey, "API_KEY") - } -} +// TODO: - Implement diff --git a/Tests/StripeTests/RefundTests.swift b/Tests/StripeTests/RefundTests.swift index 8a66529..63e6be0 100644 --- a/Tests/StripeTests/RefundTests.swift +++ b/Tests/StripeTests/RefundTests.swift @@ -10,270 +10,53 @@ import XCTest @testable import Stripe @testable import Vapor - - - - class RefundTests: XCTestCase { - - var drop: Droplet? - var refundId: String = "" - - override func setUp() { - do { - drop = try self.makeDroplet() - - let cardToken = try drop?.stripe?.tokens.createCardToken(withCardNumber: "4242 4242 4242 4242", - expirationMonth: 10, - expirationYear: 2018, - cvc: 123, - name: "Test Card", - customer: nil, - currency: nil) - .serializedResponse().id ?? "" - - let chargeId = try drop?.stripe?.charge.create(amount: 10_00, - in: .usd, - withFee: nil, - toAccount: nil, - capture: true, - description: "Vapor Stripe: Test Description", - destinationAccountId: nil, - destinationAmount: nil, - transferGroup: nil, - onBehalfOf: nil, - receiptEmail: nil, - shippingLabel: nil, - customer: nil, - statementDescriptor: nil, - source: cardToken, - metadata: nil) - .serializedResponse().id ?? "" - - refundId = try drop?.stripe?.refunds.createRefund(charge: chargeId, - amount: 5_00, - reason: .requestedByCustomer, - refundApplicationFee: nil, - reverseTransfer: nil, - metadata: 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 - refundId = "" - } - - func testRefunding() throws { - do { - let cardToken = try drop?.stripe?.tokens.createCardToken(withCardNumber: "4242 4242 4242 4242", - expirationMonth: 10, - expirationYear: 2018, - cvc: 123, - name: "Test Card", - customer: nil, - currency: nil) - .serializedResponse().id ?? "" - - let charge = try drop?.stripe?.charge.create(amount: 10_00, - in: .usd, - withFee: nil, - toAccount: nil, - capture: true, - description: "Vapor Stripe: Test Description", - destinationAccountId: nil, - destinationAmount: nil, - transferGroup: nil, - onBehalfOf: nil, - receiptEmail: nil, - shippingLabel: nil, - customer: nil, - statementDescriptor: nil, - source: cardToken, - metadata: nil) - .serializedResponse().id ?? "" - - let metadata = Node(["hello":"world"]) - - let refund = try drop?.stripe?.refunds.createRefund(charge: charge, - amount: 5_00, - reason: .requestedByCustomer, - refundApplicationFee: false, - reverseTransfer: false, - metadata: metadata) - .serializedResponse() - XCTAssertNotNil(refund) - - XCTAssertEqual(refund?.metadata?.object?["hello"], metadata["hello"]) - - XCTAssertEqual(refund?.amount, 5_00) - - XCTAssertEqual(refund?.reason, .requestedByCustomer) - } - 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 testUpdatingRefund() throws { - do { - let metadata = Node(["hello":"world"]) - - let updatedRefund = try drop?.stripe?.refunds.update(metadata: metadata, - refund: refundId) - .serializedResponse() - XCTAssertNotNil(updatedRefund) - - XCTAssertEqual(updatedRefund?.metadata?.object?["hello"], metadata["hello"]) - - XCTAssertEqual(updatedRefund?.amount, 5_00) - } - 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 testRetrievingRefund() throws { - do { - let refund = try drop?.stripe?.refunds.retrieve(refund: refundId).serializedResponse() - XCTAssertNotNil(refund) - } - 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) - } - } + let refundString = """ +{ + "id": "re_1BrXqE2eZvKYlo2Cfa7NO6GF", + "object": "refund", + "amount": 1000, + "balance_transaction": "txn_1BrXqE2eZvKYlo2CyAuEMg17", + "charge": "ch_1BrXqD2eZvKYlo2Ce1sYPCDd", + "created": 1517690550, + "currency": "usd", + "metadata": { + "hello": "world" + }, + "reason": "requested_by_customer", + "receipt_number": "23760348", + "status": "succeeded" +} +""" - func testListingAllRefunds() throws { + func testRefundParsedProperly() throws { do { - let refunds = try drop?.stripe?.refunds.listAll(byChargeId: nil, filter: nil).serializedResponse() - - if let refundItems = refunds?.items { - XCTAssertGreaterThanOrEqual(refundItems.count, 1) - } else { - XCTFail("Refunds 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) + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase + + let body = HTTPBody(string: refundString) + let futureRefund = try decoder.decode(StripeRefund.self, from: body, on: EmbeddedEventLoop()) + + futureRefund.do { (refund) in + XCTAssertEqual(refund.id, "re_1BrXqE2eZvKYlo2Cfa7NO6GF") + XCTAssertEqual(refund.object, "refund") + XCTAssertEqual(refund.amount, 1000) + XCTAssertEqual(refund.balanceTransaction, "txn_1BrXqE2eZvKYlo2CyAuEMg17") + XCTAssertEqual(refund.charge, "ch_1BrXqD2eZvKYlo2Ce1sYPCDd") + XCTAssertEqual(refund.created, Date(timeIntervalSince1970: 1517690550)) + XCTAssertEqual(refund.currency, .usd) + XCTAssertEqual(refund.metadata?["hello"], "world") + XCTAssertEqual(refund.reason, .requestedByCustomer) + XCTAssertEqual(refund.receiptNumber, "23760348") + XCTAssertEqual(refund.status, .succeeded) + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - XCTFail(error.localizedDescription) + XCTFail("\(error.localizedDescription)") } } } diff --git a/Tests/StripeTests/SKUTests.swift b/Tests/StripeTests/SKUTests.swift index 86aa1d4..0532361 100644 --- a/Tests/StripeTests/SKUTests.swift +++ b/Tests/StripeTests/SKUTests.swift @@ -7,316 +7,81 @@ // import XCTest - @testable import Stripe @testable import Vapor - - - - class SKUTests: XCTestCase { + let skuString = """ +{ + "id": "sku_CG2zw7j7H8NEQq", + "object": "sku", + "active": true, + "attributes": { + "size": "Medium", + "gender": "Unisex" + }, + "created": 1517686889, + "currency": "usd", + "image": "https://www.example.com", + "inventory": { + "quantity": 499, + "type": "finite", + "value": "in_stock" }, + "metadata": { + "hello": "world" + }, + "package_dimensions": { + "height": 12.3, + "length": 13.3, + "weight": 14.3, + "width": 15.3 + }, + "price": 1500, + "livemode": false, + "product": "prod_BosWT9EsdzgjPn", + "updated": 1517686906 +} +""" - 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 { + func testSkuParsedProperly() 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) + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase + + let body = HTTPBody(string: skuString) + let futureSku = try decoder.decode(StripeSKU.self, from: body, on: EmbeddedEventLoop()) + + futureSku.do { (sku) in + XCTAssertEqual(sku.id, "sku_CG2zw7j7H8NEQq") + XCTAssertEqual(sku.object, "sku") + XCTAssertEqual(sku.active, true) + XCTAssertEqual(sku.attributes?["size"], "Medium") + XCTAssertEqual(sku.attributes?["gender"], "Unisex") + XCTAssertEqual(sku.created, Date(timeIntervalSince1970: 1517686889)) + XCTAssertEqual(sku.currency, .usd) + XCTAssertEqual(sku.image, "https://www.example.com") + XCTAssertEqual(sku.livemode, false) + XCTAssertEqual(sku.metadata?["hello"], "world") + XCTAssertEqual(sku.price, 1500) + XCTAssertEqual(sku.product, "prod_BosWT9EsdzgjPn") + XCTAssertEqual(sku.updated, Date(timeIntervalSince1970: 1517686906)) + + XCTAssertEqual(sku.packageDimensions?.height, 12.3) + XCTAssertEqual(sku.packageDimensions?.length, 13.3) + XCTAssertEqual(sku.packageDimensions?.weight, 14.3) + XCTAssertEqual(sku.packageDimensions?.width, 15.3) + + XCTAssertEqual(sku.inventory?.quantity, 499) + XCTAssertEqual(sku.inventory?.type, .finite) + XCTAssertEqual(sku.inventory?.value, .inStock) + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - XCTFail(error.localizedDescription) + XCTFail("\(error.localizedDescription)") } } } diff --git a/Tests/StripeTests/SourceTests.swift b/Tests/StripeTests/SourceTests.swift index 71954d4..a1ab648 100644 --- a/Tests/StripeTests/SourceTests.swift +++ b/Tests/StripeTests/SourceTests.swift @@ -7,146 +7,775 @@ // import XCTest - @testable import Stripe @testable import Vapor - - - - class SourceTests: XCTestCase { + let decoder = JSONDecoder() - var drop: Droplet? - var sourceId: String = "" override func setUp() { + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase + } + + let cardSourceString = """ + { + "id": "src_1AhIN74iJb0CbkEwmbRYPsd4", + "object": "source", + "amount": 20, + "client_secret": "src_client_secret_sSPHZ17iQG6j9uKFdAYqPErO", + "created": 1500471469, + "currency": "usd", + "flow": "redirect", + "livemode": false, + "metadata": { + "hello": "world" + }, + "owner": { + "address": { + "city": "Berlin", + "country": "DE", + "line1": "Nollendorfstraße 27", + "line2": null, + "postal_code": "10777", + "state": null + }, + "email": "jenny.rosen@example.com", + "name": "Jenny Rosen", + "phone": "1234567", + "verified_address": null, + "verified_email": null, + "verified_name": null, + "verified_phone": null + }, +"receiver": { + "address": "121042882-38381234567890123", + "amount_charged": 10, + "amount_received": 101, + "amount_returned": 110, + "refund_attributes_method": "email", + "refund_attributes_status": "missing" + }, +"redirect": { + "failure_reason": "declined", + "return_url": "https://www.google.com", + "status": "pending", + "url": "https://www.apple.com" + }, + "status": "chargeable", + "type": "card", + "usage": "reusable", + "card": { + "exp_month": 4, + "exp_year": 2024, + "address_line1_check": "unchecked", + "address_zip_check": "unchecked", + "brand": "Visa", + "country": "US", + "cvc_check": "unchecked", + "funding": "credit", + "last4": "4242", + "three_d_secure": "optional", + "tokenization_method": null, + "dynamic_last4": "9876" + } +} +""" + + func testCardSourceParsedProperly() throws { do { - drop = try self.makeDroplet() + let body = HTTPBody(string: cardSourceString) + let source = try decoder.decode(StripeSource.self, from: body, on: EmbeddedEventLoop()) - let card = Node(["number":"4242 4242 4242 4242", "cvc":123,"exp_month":10, "exp_year":2018]) - - sourceId = try drop?.stripe?.sources.createNewSource(sourceType: .card, - source: card, - amount: nil, - currency: nil, - flow: nil, - owner: nil, - redirectReturnUrl: nil, - token: nil, - usage: nil, - metadata: nil).serializedResponse().id ?? "" + source.do { (source) in + XCTAssertNil(source.threeDSecure) + XCTAssertNil(source.giropay) + XCTAssertNil(source.sepaDebit) + XCTAssertNil(source.ideal) + XCTAssertNil(source.sofort) + XCTAssertNil(source.bancontact) + XCTAssertNil(source.alipay) + XCTAssertNil(source.p24) + XCTAssertNil(source.achCreditTransfer) - } - 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) + // Cards have already been tested in the token tests. + XCTAssertNotNil(source.card) + + XCTAssertEqual(source.id, "src_1AhIN74iJb0CbkEwmbRYPsd4") + XCTAssertEqual(source.object, "source") + XCTAssertEqual(source.amount, 20) + XCTAssertEqual(source.clientSecret, "src_client_secret_sSPHZ17iQG6j9uKFdAYqPErO") + XCTAssertEqual(source.created, Date(timeIntervalSince1970: 1500471469)) + XCTAssertEqual(source.currency, .usd) + XCTAssertEqual(source.flow, Flow.redirect) + XCTAssertEqual(source.livemode, false) + XCTAssertEqual(source.metadata?["hello"], "world") + XCTAssertEqual(source.status, .chargeable) + XCTAssertEqual(source.type, .card) + XCTAssertEqual(source.usage, "reusable") + + // This tests covers the owner object + XCTAssertNotNil(source.owner) + XCTAssertEqual(source.owner?.address?.city, "Berlin") + XCTAssertEqual(source.owner?.address?.country, "DE") + XCTAssertEqual(source.owner?.address?.line1, "Nollendorfstraße 27") + XCTAssertEqual(source.owner?.address?.line2, nil) + XCTAssertEqual(source.owner?.address?.postalCode, "10777") + XCTAssertEqual(source.owner?.address?.state, nil) + XCTAssertEqual(source.owner?.email, "jenny.rosen@example.com") + XCTAssertEqual(source.owner?.name, "Jenny Rosen") + XCTAssertEqual(source.owner?.phone, "1234567") + XCTAssertEqual(source.owner?.verifiedEmail, nil) + XCTAssertEqual(source.owner?.verifiedName, nil) + XCTAssertEqual(source.owner?.verifiedPhone, nil) + + // This tests covers the receiver object + XCTAssertNotNil(source.receiver) + XCTAssertEqual(source.receiver?.address, "121042882-38381234567890123") + XCTAssertEqual(source.receiver?.amountCharged, 10) + XCTAssertEqual(source.receiver?.amountReceived, 101) + XCTAssertEqual(source.receiver?.amountReturned, 110) + XCTAssertEqual(source.receiver?.refundAttributesMethod, "email") + XCTAssertEqual(source.receiver?.refundAttributesStatus, "missing") + + // This tests covers the redirect object + XCTAssertNotNil(source.redirect) + XCTAssertEqual(source.redirect?.failureReason, "declined") + XCTAssertEqual(source.redirect?.returnUrl, "https://www.google.com") + XCTAssertEqual(source.redirect?.status, "pending") + XCTAssertEqual(source.redirect?.url, "https://www.apple.com") + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - fatalError("Setup failed: \(error.localizedDescription)") + XCTFail("\(error.localizedDescription)") } } - override func tearDown() { - drop = nil - sourceId = "" + let threeDSecureSourceString = """ + { + "id": "src_19YlvWAHEMiOZZp1QQlOD79v", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_kBwCSm6Xz5MQETiJ43hUH8qv", + "created": 1483663790, + "currency": "eur", + "flow": "redirect", + "livemode": false, + "metadata": {}, + "owner": { + "address": null, + "email": null, + "name": null, + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": null, + "verified_phone": null + }, + "redirect": { + "return_url": "https://shop.example.com/crtA6B28E1", + "status": "pending", + "url": "https://hooks.stripe.com/redirect/authenticate/src_19YlvWAHEMiOZZp1QQlOD79v?client_secret=src_client_secret_kBwCSm6Xz5MQETiJ43hUH8qv" + }, + "status": "pending", + "type": "three_d_secure", + "usage": "single_use", + "three_d_secure": { + "card": "src_19YP2AAHEMiOZZp1Di4rt1K6", + "customer": null, + "authenticated": false + } } +""" - func testRetrieveSource() throws { + func testThreeDSecureSourceParsedProperly() throws { do { - let retrievedSource = try drop?.stripe?.sources.retrieveSource(withId: sourceId).serializedResponse() + let body = HTTPBody(string: threeDSecureSourceString) + let source = try decoder.decode(StripeSource.self, from: body, on: EmbeddedEventLoop()) - XCTAssertNotNil(retrievedSource) + source.do { (source) in + XCTAssertNil(source.card) + XCTAssertNil(source.giropay) + XCTAssertNil(source.sepaDebit) + XCTAssertNil(source.ideal) + XCTAssertNil(source.sofort) + XCTAssertNil(source.bancontact) + XCTAssertNil(source.alipay) + XCTAssertNil(source.p24) + XCTAssertNil(source.achCreditTransfer) + + XCTAssertNotNil(source.threeDSecure) + XCTAssertEqual(source.threeDSecure?.card, "src_19YP2AAHEMiOZZp1Di4rt1K6") + XCTAssertEqual(source.threeDSecure?.customer, nil) + XCTAssertEqual(source.threeDSecure?.authenticated, false) + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") + } + } + catch { + XCTFail("\(error.localizedDescription)") + } + } + + let sepaDebitSourceString = """ +{ + "id": "src_18HgGjHNCLa1Vra6Y9TIP6tU", + "object": "source", + "amount": null, + "client_secret": "src_client_secret_XcBmS94nTg5o0xc9MSliSlDW", + "created": 1464803577, + "currency": "eur", + "flow": "none", + "livemode": false, + "owner": { + "address": null, + "email": null, + "name": "Jenny Rosen", + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": null, + "verified_phone": null + }, + "status": "chargeable", + "type": "sepa_debit", + "usage": "reusable", + "sepa_debit": { + "bank_code": "37040044", + "country": "DE", + "fingerprint": "NxdSyRegc9PsMkWy", + "last4": "3001", + "mandate_reference": "NXDSYREGC9PSMKWY", + "mandate_url": "https://hooks.stripe.com/adapter/sepa_debit/file/src_18HgGjHNCLa1Vra6Y9TIP6tU/src_client_secret_XcBmS94nTg5o0xc9MSliSlDW" + } +} +""" + + func testSepaDebitSourceParsedProperly() throws { + do { + let body = HTTPBody(string: sepaDebitSourceString) + let source = try decoder.decode(StripeSource.self, from: body, on: EmbeddedEventLoop()) - XCTAssertEqual(retrievedSource?.type, SourceType.card) + source.do { (source) in + XCTAssertNil(source.card) + XCTAssertNil(source.giropay) + XCTAssertNil(source.threeDSecure) + XCTAssertNil(source.ideal) + XCTAssertNil(source.sofort) + XCTAssertNil(source.bancontact) + XCTAssertNil(source.alipay) + XCTAssertNil(source.p24) + XCTAssertNil(source.achCreditTransfer) + + XCTAssertNotNil(source.sepaDebit) + XCTAssertEqual(source.sepaDebit?.bankCode, "37040044") + XCTAssertEqual(source.sepaDebit?.country, "DE") + XCTAssertEqual(source.sepaDebit?.fingerprint, "NxdSyRegc9PsMkWy") + XCTAssertEqual(source.sepaDebit?.last4, "3001") + XCTAssertEqual(source.sepaDebit?.mandateReference, "NXDSYREGC9PSMKWY") + XCTAssertEqual(source.sepaDebit?.mandateUrl, "https://hooks.stripe.com/adapter/sepa_debit/file/src_18HgGjHNCLa1Vra6Y9TIP6tU/src_client_secret_XcBmS94nTg5o0xc9MSliSlDW") + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") + } + } + catch { + XCTFail("\(error.localizedDescription)") + } + } + + let alipaySourceString = """ +{ + "id": "src_16xhynE8WzK49JbAs9M21jaR", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU", + "created": 1445277809, + "currency": "usd", + "flow": "redirect", + "livemode": true, + "owner": { + "address": null, + "email": null, + "name": "null", + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": "null", + "verified_phone": null + }, + "redirect": { + "return_url": "https://shop.example.com/crtA6B28E1", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_16xhynE8WzK49JbAs9M21jaR?client_secret=src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU" + }, + "statement_descriptor": null, + "status": "pending", + "type": "alipay", + "usage": "single_use", + "alipay": { + "statement_descriptor": "Veggies are healthy", + "native_url": "https://www.vapor.codes" + } +} +""" + + func testAlipaySourceParsedProperly() throws { + do { + let body = HTTPBody(string: alipaySourceString) + let source = try decoder.decode(StripeSource.self, from: body, on: EmbeddedEventLoop()) - XCTAssertNotNil(retrievedSource?.returnedSource) + source.do { (source) in + XCTAssertNil(source.card) + XCTAssertNil(source.giropay) + XCTAssertNil(source.threeDSecure) + XCTAssertNil(source.ideal) + XCTAssertNil(source.sofort) + XCTAssertNil(source.bancontact) + XCTAssertNil(source.sepaDebit) + XCTAssertNil(source.p24) + XCTAssertNil(source.achCreditTransfer) + + XCTAssertNotNil(source.alipay) + XCTAssertEqual(source.alipay?.statementDescriptor, "Veggies are healthy") + XCTAssertEqual(source.alipay?.nativeUrl, "https://www.vapor.codes") + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") + } } - catch let error as StripeError { + catch { + XCTFail("\(error.localizedDescription)") + } + } + + + let giroPaySourceString = """ +{ + "id": "src_16xhynE8WzK49JbAs9M21jaR", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU", + "created": 1445277809, + "currency": "eur", + "flow": "redirect", + "livemode": true, + "owner": { + "address": null, + "email": null, + "name": null, + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": "Jenny Rosen", + "verified_phone": null + }, + "redirect": { + "return_url": "https://shop.example.com/crtA6B28E1", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_16xhynE8WzK49JbAs9M21jaR?client_secret=src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU" + }, + "statement_descriptor": null, + "status": "pending", + "type": "giropay", + "usage": "single_use", + "giropay": { + "bank_code": "76547382", + "bic": "No idea what this is", + "bank_name": "Vapor Bank", + "statement_descriptor": "Buy more Vapor Cloud" + } +} +""" + + func testGiropaySourceParsedProperly() throws { + do { + let body = HTTPBody(string: giroPaySourceString) + let source = try decoder.decode(StripeSource.self, from: body, on: EmbeddedEventLoop()) - 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) + source.do { (source) in + XCTAssertNil(source.card) + XCTAssertNil(source.alipay) + XCTAssertNil(source.threeDSecure) + XCTAssertNil(source.ideal) + XCTAssertNil(source.sofort) + XCTAssertNil(source.bancontact) + XCTAssertNil(source.sepaDebit) + XCTAssertNil(source.p24) + XCTAssertNil(source.achCreditTransfer) + + XCTAssertNotNil(source.giropay) + XCTAssertEqual(source.giropay?.bankCode, "76547382") + XCTAssertEqual(source.giropay?.bic, "No idea what this is") + XCTAssertEqual(source.giropay?.bankName, "Vapor Bank") + XCTAssertEqual(source.giropay?.statementDescriptor, "Buy more Vapor Cloud") + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - XCTFail(error.localizedDescription) + XCTFail("\(error.localizedDescription)") } } + + + let idealSourceString = """ +{ + "id": "src_16xhynE8WzK49JbAs9M21jaR", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU", + "created": 1445277809, + "currency": "eur", + "flow": "redirect", + "livemode": true, + "owner": { + "address": null, + "email": null, + "name": "Jenny Rosen", + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": "Jenny Rosen", + "verified_phone": null + }, + "redirect": { + "return_url": "https://shop.example.com/crtA6B28E1", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_16xhynE8WzK49JbAs9M21jaR?client_secret=src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU" + }, + "statement_descriptor": null, + "status": "pending", + "type": "ideal", + "usage": "single_use", + "ideal": { + "bank": "Vapor Bank", + "bic": "who knows", + "iban_last4": "1234", + "statement_descriptor": "Buy more Vapor Cloud" + } +} +""" - func testUpdateSource() throws { + func testIdealSourceParsedProperly() throws { do { - let metadata = try Node(node:["hello": "world"]) + let body = HTTPBody(string: idealSourceString) + let source = try decoder.decode(StripeSource.self, from: body, on: EmbeddedEventLoop()) - let updatedSource = try drop?.stripe?.sources.update(owner: nil, - metadata: metadata, - forSourceId: sourceId).serializedResponse() + source.do { (source) in + XCTAssertNil(source.card) + XCTAssertNil(source.alipay) + XCTAssertNil(source.threeDSecure) + XCTAssertNil(source.giropay) + XCTAssertNil(source.sofort) + XCTAssertNil(source.bancontact) + XCTAssertNil(source.sepaDebit) + XCTAssertNil(source.p24) + XCTAssertNil(source.achCreditTransfer) + + XCTAssertNotNil(source.ideal) + XCTAssertEqual(source.ideal?.bank, "Vapor Bank") + XCTAssertEqual(source.ideal?.bic, "who knows") + XCTAssertEqual(source.ideal?.ibanLast4, "1234") + XCTAssertEqual(source.ideal?.statementDescriptor, "Buy more Vapor Cloud") + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") + } + } + catch { + XCTFail("\(error.localizedDescription)") + } + } + + + let p24SourceString = """ +{ + "id": "src_16xhynE8WzK49JbAs9M21jaR", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU", + "created": 1445277809, + "currency": "eur", + "flow": "redirect", + "livemode": true, + "owner": { + "address": null, + "email": "jenny.rosen@example.com", + "name": "Jenny Rosen", + "phone": null, + "verified_address": null, + "verified_email": "jenny.rosen@example.com", + "verified_name": "Jenny Rosen", + "verified_phone": null + }, + "redirect": { + "return_url": "https://shop.example.com/crtA6B28E1", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_16xhynE8WzK49JbAs9M21jaR?client_secret=src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU" + }, + "statement_descriptor": null, + "status": "pending", + "type": "p24", + "usage": "single_use", + "p24": { + "reference": "P24-000-111-222" + } +} +""" + + func testP24SourceParsedProperly() throws { + do { + let body = HTTPBody(string: p24SourceString) + let source = try decoder.decode(StripeSource.self, from: body, on: EmbeddedEventLoop()) - XCTAssertNotNil(updatedSource) + source.do { (source) in + XCTAssertNil(source.card) + XCTAssertNil(source.alipay) + XCTAssertNil(source.threeDSecure) + XCTAssertNil(source.giropay) + XCTAssertNil(source.sofort) + XCTAssertNil(source.bancontact) + XCTAssertNil(source.sepaDebit) + XCTAssertNil(source.ideal) + XCTAssertNil(source.achCreditTransfer) + + XCTAssertNotNil(source.p24) + XCTAssertEqual(source.p24?.reference, "P24-000-111-222") + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") + } + } + catch { + XCTFail("\(error.localizedDescription)") + } + } + + + let sofortSourceString = """ +{ + "id": "src_16xhynE8WzK49JbAs9M21jaR", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU", + "created": 1445277809, + "currency": "eur", + "flow": "redirect", + "livemode": true, + "owner": { + "address": null, + "email": null, + "name": "Jenny Rosen", + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": "Jenny Rosen", + "verified_phone": null + }, + "redirect": { + "return_url": "https://shop.example.com/crtA6B28E1", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_16xhynE8WzK49JbAs9M21jaR?client_secret=src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU" + }, + "statement_descriptor": null, + "status": "pending", + "type": "sofort", + "usage": "single_use", + "sofort": { + "country": "DE", + "bank_code": "34569", + "bic": "Meh", + "bank_name": "Vapor Bank", + "iban_last4": "4560", + "preferred_language": "English", + "statement_descriptor": "Henlo friend" + } +} +""" + + func testSofortSourceParsedProperly() throws { + do { + let body = HTTPBody(string: sofortSourceString) + let source = try decoder.decode(StripeSource.self, from: body, on: EmbeddedEventLoop()) - XCTAssertEqual(updatedSource?.metadata?["hello"], metadata["hello"]) + source.do { (source) in + XCTAssertNil(source.card) + XCTAssertNil(source.alipay) + XCTAssertNil(source.threeDSecure) + XCTAssertNil(source.giropay) + XCTAssertNil(source.p24) + XCTAssertNil(source.bancontact) + XCTAssertNil(source.sepaDebit) + XCTAssertNil(source.ideal) + XCTAssertNil(source.achCreditTransfer) + + XCTAssertNotNil(source.sofort) + XCTAssertEqual(source.sofort?.country, "DE") + XCTAssertEqual(source.sofort?.bankCode, "34569") + XCTAssertEqual(source.sofort?.bic, "Meh") + XCTAssertEqual(source.sofort?.bankName, "Vapor Bank") + XCTAssertEqual(source.sofort?.ibanLast4, "4560") + XCTAssertEqual(source.sofort?.preferredLanguage, "English") + XCTAssertEqual(source.sofort?.statementDescriptor, "Henlo friend") + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") + } + } + catch { + XCTFail("\(error.localizedDescription)") + } + } + + + let bancontactSourceString = """ +{ + "id": "src_16xhynE8WzK49JbAs9M21jaR", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU", + "created": 1445277809, + "currency": "eur", + "statement_descriptor": null, + "flow": "redirect", + "livemode": true, + "owner": { + "address": null, + "email": null, + "name": "Jenny Rosen", + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": "Jenny Rosen", + "verified_phone": null + }, + "redirect": { + "return_url": "https://shop.example.com/crtA6B28E1", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_16xhynE8WzK49JbAs9M21jaR?client_secret=src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU" + }, + "status": "pending", + "type": "bancontact", + "usage": "single_use", + "bancontact": { + "bank_code": "33333", + "bic": "Unknown", + "bank_name": "Vapor Bank", + "statement_descriptor": "Eat veggies", + "preferred_language": "English" + } +} +""" + + func testBancontactSourceParsedProperly() throws { + do { + let body = HTTPBody(string: bancontactSourceString) + let source = try decoder.decode(StripeSource.self, from: body, on: EmbeddedEventLoop()) - XCTAssertEqual(updatedSource?.type, SourceType.card) + source.do { (source) in + XCTAssertNil(source.card) + XCTAssertNil(source.alipay) + XCTAssertNil(source.threeDSecure) + XCTAssertNil(source.giropay) + XCTAssertNil(source.p24) + XCTAssertNil(source.sofort) + XCTAssertNil(source.sepaDebit) + XCTAssertNil(source.ideal) + XCTAssertNil(source.achCreditTransfer) + + XCTAssertNotNil(source.bancontact) + XCTAssertEqual(source.bancontact?.bankCode, "33333") + XCTAssertEqual(source.bancontact?.bic, "Unknown") + XCTAssertEqual(source.bancontact?.bankName, "Vapor Bank") + XCTAssertEqual(source.bancontact?.statementDescriptor, "Eat veggies") + XCTAssertEqual(source.bancontact?.preferredLanguage, "English") + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") + } + } + catch { + XCTFail("\(error.localizedDescription)") } - catch let error as StripeError { + } + + let achSourceString = """ +{ + "id": "src_16xhynE8WzK49JbAs9M21jaR", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU", + "created": 1445277809, + "currency": "eur", + "statement_descriptor": null, + "flow": "code_verification", + "livemode": true, + "owner": { + "address": null, + "email": null, + "name": "Jenny Rosen", + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": "Jenny Rosen", + "verified_phone": null + }, + "code_verification": { + "attempts_remaining": 30, + "status": "pending", + }, + "status": "pending", + "type": "ach_credit_transfer", + "usage": "single_use", + "ach_credit_transfer": { + "account_number": "test_52796e3294dc", + "routing_number": "110000000", + "fingerprint": "nfd2882gh38h", + "bank_name": "TEST BANK", + "swift_code": "TSTEZ122" + } +} +""" + + func testACHSourceParsedProperly() throws { + do { + let body = HTTPBody(string: achSourceString) + let source = try decoder.decode(StripeSource.self, from: body, on: EmbeddedEventLoop()) - 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) + source.do { (source) in + XCTAssertNil(source.card) + XCTAssertNil(source.alipay) + XCTAssertNil(source.threeDSecure) + XCTAssertNil(source.giropay) + XCTAssertNil(source.p24) + XCTAssertNil(source.sofort) + XCTAssertNil(source.sepaDebit) + XCTAssertNil(source.ideal) + XCTAssertNil(source.bancontact) + + XCTAssertNotNil(source.achCreditTransfer) + XCTAssertEqual(source.achCreditTransfer?.accountNumber, "test_52796e3294dc") + XCTAssertEqual(source.achCreditTransfer?.routingNumber, "110000000") + XCTAssertEqual(source.achCreditTransfer?.fingerprint, "nfd2882gh38h") + XCTAssertEqual(source.achCreditTransfer?.bankName, "TEST BANK") + XCTAssertEqual(source.achCreditTransfer?.swiftCode, "TSTEZ122") + + // CodeVerification + XCTAssertEqual(source.codeVerification?.attemptsRemaining, 30) + XCTAssertEqual(source.codeVerification?.status, .pending) + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - XCTFail(error.localizedDescription) + XCTFail("\(error.localizedDescription)") } } } diff --git a/Tests/StripeTests/SubscriptionItemTests.swift b/Tests/StripeTests/SubscriptionItemTests.swift deleted file mode 100644 index 38ea764..0000000 --- a/Tests/StripeTests/SubscriptionItemTests.swift +++ /dev/null @@ -1,280 +0,0 @@ -// -// SubscriptionItemTests.swift -// Stripe -// -// Created by Andrew Edwards on 6/9/17. -// -// - -import XCTest - -@testable import Stripe -@testable import Vapor - - - - -@testable import Random - -class SubscriptionItemTests: XCTestCase { - var drop: Droplet? - var subscriptionId: String = "" - var subscriptionItemId: String = "" - - override func setUp() { - do { - drop = try self.makeDroplet() - - let planId = try drop?.stripe?.plans.create(id: Data(bytes: URandom.bytes(count: 16)).base64String, - amount: 10_00, - currency: .usd, - interval: .week, - name: "Test Plan", - intervalCount: 5, - statementDescriptor: "Test Plan", - trialPeriodDays: nil, - metadata: nil) - .serializedResponse().id ?? "" - - let paymentTokenSource = try drop?.stripe?.tokens.createCardToken(withCardNumber: "4242 4242 4242 4242", - expirationMonth: 10, - expirationYear: 2018, - cvc: 123, - name: "Test Card", - customer: nil, - currency: nil) - .serializedResponse().id ?? "" - - let customerId = try drop?.stripe?.customer.create(accountBalance: nil, - businessVATId: nil, - coupon: nil, - defaultSource: nil, - description: nil, - email: nil, - shipping: nil, - source: paymentTokenSource).serializedResponse().id ?? "" - - subscriptionId = try drop?.stripe?.subscriptions.create(forCustomer: customerId, - plan: planId, - applicationFeePercent: nil, - couponId: nil, - items: nil, - quantity: nil, - source: nil, - taxPercent: nil, - trialEnd: nil, - trialPeriodDays: nil) - .serializedResponse().id ?? "" - - let planId2 = try drop?.stripe?.plans.create(id: Data(bytes: URandom.bytes(count: 16)).base64String, - amount: 10_00, - currency: .usd, - interval: .week, - name: "Test Plan", - intervalCount: 5, - statementDescriptor: "Test Plan", - trialPeriodDays: nil, - metadata: nil) - .serializedResponse().id ?? "" - - subscriptionItemId = try drop?.stripe?.subscriptionItems.create(planId: planId2, - prorate: true, - prorationDate: Date(), - quantity: 1, - subscriptionId: subscriptionId) - .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 - subscriptionId = "" - subscriptionItemId = "" - } - - func testRetrieveSubscriptionItem() throws { - do { - let subscriptionItem = try drop?.stripe?.subscriptionItems.retrieve(subscriptionItem: subscriptionItemId).serializedResponse() - - XCTAssertNotNil(subscriptionItem) - } - 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 testUpdateSubscriptionItem() throws { - do { - let newQuantity = 2 - - let updatedSubscriptionItem = try drop?.stripe?.subscriptionItems.update(plan: nil, - prorate: nil, - prorationDate: nil, - quantity: newQuantity, - subscriptionItemId: subscriptionItemId) - .serializedResponse() - - XCTAssertNotNil(updatedSubscriptionItem) - - XCTAssertEqual(updatedSubscriptionItem?.quantity, newQuantity) - } - 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 testDeleteSubscriptionItem() throws { - do { - let deletedSubscriptionItem = try drop?.stripe?.subscriptionItems.delete(subscriptionItem: subscriptionItemId, - prorate: nil, - proprationDate: nil).serializedResponse() - - XCTAssertNotNil(deletedSubscriptionItem) - - XCTAssertTrue(deletedSubscriptionItem?.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 testFilterSubscriptionItems() throws { - do { - let filter = StripeFilter() - - filter.limit = 1 - - let subscriptionItems = try drop?.stripe?.subscriptionItems.listAll(subscriptionId: subscriptionId, filter: filter).serializedResponse() - - XCTAssertNotNil(subscriptionItems) - - if let subscriptionItems = subscriptionItems?.items { - XCTAssertEqual(subscriptionItems.count, 1) - XCTAssertNotNil(subscriptionItems.first) - } else { - XCTFail("SubscriptionItems 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/SubscriptionTests.swift b/Tests/StripeTests/SubscriptionTests.swift index 7d92c5d..2c19fed 100644 --- a/Tests/StripeTests/SubscriptionTests.swift +++ b/Tests/StripeTests/SubscriptionTests.swift @@ -7,317 +7,223 @@ // import XCTest - @testable import Stripe @testable import Vapor - - - -@testable import Random - class SubscriptionTests: XCTestCase { - - var drop: Droplet? - var subscriptionId: String = "" - - override func setUp() { + func testSubscriptionParsedProperly() throws { do { - drop = try self.makeDroplet() - - let planId = try drop?.stripe?.plans.create(id: Data(bytes: URandom.bytes(count: 16)).base64String, - amount: 10_00, - currency: .usd, - interval: .week, - name: "Test Plan", - intervalCount: 5, - statementDescriptor: "Test Plan", - trialPeriodDays: nil, - metadata: nil) - .serializedResponse().id ?? "" - - let paymentTokenSource = try drop?.stripe?.tokens.createCardToken(withCardNumber: "4242 4242 4242 4242", - expirationMonth: 10, - expirationYear: 2018, - cvc: 123, - name: "Test Card", - customer: nil, - currency: nil) - .serializedResponse().id ?? "" + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase - let customerId = try drop?.stripe?.customer.create(accountBalance: nil, - businessVATId: nil, - coupon: nil, - defaultSource: nil, - description: nil, - email: nil, - shipping: nil, - source: paymentTokenSource) - .serializedResponse().id ?? "" - - subscriptionId = try drop?.stripe?.subscriptions.create(forCustomer: customerId, - plan: planId, - applicationFeePercent: nil, - couponId: nil, - items: nil, - quantity: nil, - source: nil, - taxPercent: nil, - trialEnd: nil, - trialPeriodDays: 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 - subscriptionId = "" - } - - func testRetrieveSubscription() throws { - do { - let subscription = try drop?.stripe?.subscriptions.retrieve(subscription: subscriptionId).serializedResponse() - - XCTAssertNotNil(subscription) - } - 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 testUpdateSubscription() throws { - do { - let newQuantity = 2 - - let updatedSubscription = try drop?.stripe?.subscriptions.update(subscription: subscriptionId, - applicationFeePercent: nil, - couponId: nil, - items: nil, - plan: nil, - prorate: nil, - quantity: newQuantity, - source: nil, - taxPercent: nil, - trialEnd: nil) - .serializedResponse() + let body = HTTPBody(string: subscriptionString) + let subscription = try decoder.decode(StripeSubscription.self, from: body, on: EmbeddedEventLoop()) - XCTAssertNotNil(updatedSubscription) - - XCTAssertEqual(updatedSubscription?.quantity, newQuantity) - } - 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) - } + subscription.do({ (sub) in + + // This test covers the Discount object + XCTAssertNotNil(sub.discount) + XCTAssertEqual(sub.discount?.object, "discount") + XCTAssertEqual(sub.discount?.customer, "cus_CCMIISleHrbPlY") + XCTAssertEqual(sub.discount?.end, Date(timeIntervalSince1970: 1399384361)) + XCTAssertEqual(sub.discount?.start, Date(timeIntervalSince1970: 1391694761)) + XCTAssertEqual(sub.discount?.subscription, "sub_12345") + + // This test covers the Coupon object + XCTAssertNotNil(sub.discount?.coupon) + XCTAssertEqual(sub.discount?.coupon?.id, "35OFF") + XCTAssertEqual(sub.discount?.coupon?.object, "coupon") + XCTAssertEqual(sub.discount?.coupon?.amountOff, 5) + XCTAssertEqual(sub.discount?.coupon?.created, Date(timeIntervalSince1970: 1391694467)) + XCTAssertEqual(sub.discount?.coupon?.currency, .usd) + XCTAssertEqual(sub.discount?.coupon?.duration, .repeating) + XCTAssertEqual(sub.discount?.coupon?.durationInMonths, 3) + XCTAssertEqual(sub.discount?.coupon?.livemode, false) + XCTAssertEqual(sub.discount?.coupon?.maxRedemptions, 22) + XCTAssertEqual(sub.discount?.coupon?.metadata?["hello"], "world") + XCTAssertEqual(sub.discount?.coupon?.percentOff, 25) + XCTAssertEqual(sub.discount?.coupon?.redeemBy, Date(timeIntervalSince1970: 1489793908)) + XCTAssertEqual(sub.discount?.coupon?.timesRedeemed, 1) + XCTAssertEqual(sub.discount?.coupon?.valid, true) + + // This test covers the Subscription item list object + XCTAssertNotNil(sub.items) + XCTAssertEqual(sub.items?.object, "list") + XCTAssertEqual(sub.items?.hasMore, false) + XCTAssertEqual(sub.items?.totalCount, 1) + XCTAssertEqual(sub.items?.url, "/v1/subscription_items?subscription=sub_AJ6s2Iy65K3RxN") + + // This test covers the SubscriptionItem object + XCTAssertNotNil(sub.items?.data) + XCTAssertNotNil(sub.items?.data?[0]) + XCTAssertEqual(sub.items?.data?[0].id, "si_19yUeQ2eZvKYlo2CnJwkz3pK") + XCTAssertEqual(sub.items?.data?[0].object, "subscription_item") + XCTAssertEqual(sub.items?.data?[0].created, Date(timeIntervalSince1970: 1489793911)) + XCTAssertEqual(sub.items?.data?[0].metadata?["hello"], "world") + XCTAssertEqual(sub.items?.data?[0].quantity, 1) + XCTAssertEqual(sub.items?.data?[0].subscription, "sub_AJ6s2Iy65K3RxN") + + // These cover the Plan object + XCTAssertNotNil(sub.items?.data?[0].plan) + XCTAssertEqual(sub.items?.data?[0].plan?.id, "30990foo1489793903") + XCTAssertEqual(sub.items?.data?[0].plan?.object, "plan") + XCTAssertEqual(sub.items?.data?[0].plan?.amount, 100) + XCTAssertEqual(sub.items?.data?[0].plan?.created, Date(timeIntervalSince1970: 1489793908)) + XCTAssertEqual(sub.items?.data?[0].plan?.currency, .usd) + XCTAssertEqual(sub.items?.data?[0].plan?.interval, .week) + XCTAssertEqual(sub.items?.data?[0].plan?.intervalCount, 1) + XCTAssertEqual(sub.items?.data?[0].plan?.livemode, false) + XCTAssertEqual(sub.items?.data?[0].plan?.metadata?["hello"], "world") + XCTAssertEqual(sub.items?.data?[0].plan?.nickname, "Foo") + XCTAssertEqual(sub.items?.data?[0].plan?.product, "prod_1234") + XCTAssertEqual(sub.items?.data?[0].plan?.trialPeriodDays, 3) + + // These cover the Plan object + XCTAssertNotNil(sub.plan) + XCTAssertEqual(sub.plan?.id, "30990foo1489793903") + XCTAssertEqual(sub.plan?.object, "plan") + XCTAssertEqual(sub.plan?.amount, 100) + XCTAssertEqual(sub.plan?.created, Date(timeIntervalSince1970: 1489793908)) + XCTAssertEqual(sub.plan?.currency, .usd) + XCTAssertEqual(sub.plan?.interval, .week) + XCTAssertEqual(sub.plan?.intervalCount, 1) + XCTAssertEqual(sub.plan?.livemode, false) + XCTAssertEqual(sub.plan?.metadata?["hello"], "world") + XCTAssertEqual(sub.plan?.nickname, "Foo") + XCTAssertEqual(sub.plan?.product, "prod_1234") + XCTAssertEqual(sub.plan?.trialPeriodDays, 14) + + XCTAssertEqual(sub.id, "sub_AJ6s2Iy65K3RxN") + XCTAssertEqual(sub.object, "subscription") + XCTAssertEqual(sub.applicationFeePercent, 12.7) + XCTAssertEqual(sub.billing, "charge_automatically") + XCTAssertEqual(sub.billingCycleAnchor, Date(timeIntervalSince1970: 1490398710)) + XCTAssertEqual(sub.cancelAtPeriodEnd, false) + XCTAssertEqual(sub.canceledAt, Date(timeIntervalSince1970: 1489793914)) + XCTAssertEqual(sub.created, Date(timeIntervalSince1970: 1489793910)) + XCTAssertEqual(sub.currentPeriodEnd, Date(timeIntervalSince1970: 1490398710)) + XCTAssertEqual(sub.currentPeriodStart, Date(timeIntervalSince1970: 1489793910)) + XCTAssertEqual(sub.customer, "cus_CCMIISleHrbPlY") + XCTAssertEqual(sub.daysUntilDue, 4) + XCTAssertEqual(sub.endedAt, Date(timeIntervalSince1970: 1489793914)) + XCTAssertEqual(sub.livemode, true) + XCTAssertEqual(sub.metadata?["foo"], "bar") + XCTAssertEqual(sub.quantity, 1) + XCTAssertEqual(sub.start, Date(timeIntervalSince1970: 1489793910)) + XCTAssertEqual(sub.status, StripeSubscriptionStatus.pastDue) + XCTAssertEqual(sub.taxPercent, 4.5) + XCTAssertEqual(sub.trialEnd, Date(timeIntervalSince1970: 1489793910)) + XCTAssertEqual(sub.trialStart, Date(timeIntervalSince1970: 1489793910)) + }).catch({ (error) in + XCTFail("\(error.localizedDescription)") + }) } catch { - XCTFail(error.localizedDescription) - } - } - - func testDeleteDiscount() throws { - do { - let couponId = try drop?.stripe?.coupons.create(id: nil, - duration: .once, - amountOff: nil, - currency: nil, - durationInMonths: nil, - maxRedemptions: nil, - percentOff: 5, - redeemBy: nil) - .serializedResponse().id ?? "" - - let updatedSubscription = try drop?.stripe?.subscriptions.update(subscription: subscriptionId, - applicationFeePercent: nil, - couponId: couponId, - items: nil, - plan: nil, - prorate: nil, - quantity: nil, - source: nil, - taxPercent: nil, - trialEnd: nil) - .serializedResponse() - - XCTAssertNotNil(updatedSubscription?.discount) - - let deletedDiscount = try drop?.stripe?.subscriptions.deleteDiscount(onSubscription: updatedSubscription?.id ?? "").serializedResponse() - - XCTAssertTrue(deletedDiscount?.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) + XCTFail("\(error.localizedDescription)") } } - func testCancelSubscription() throws { - do { - let canceledSubscription = try drop?.stripe?.subscriptions.cancel(subscription: subscriptionId, atPeriodEnd: false).serializedResponse() - - XCTAssertNotNil(canceledSubscription) - - XCTAssertTrue(canceledSubscription?.status == StripeSubscriptionStatus.canceled) - } - 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 testFilterSubscriptionItems() throws { - do { - let filter = StripeFilter() - - filter.limit = 1 - - let subscriptions = try drop?.stripe?.subscriptions.listAll(filter: filter).serializedResponse() - - XCTAssertNotNil(subscriptions) - - if let subscriptionItems = subscriptions?.items { - XCTAssertEqual(subscriptionItems.count, 1) - XCTAssertNotNil(subscriptionItems.first) - } else { - XCTFail("Subscriptions 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) - } - } + let subscriptionString = """ +{ + "id": "sub_AJ6s2Iy65K3RxN", + "object": "subscription", + "application_fee_percent": 12.7, + "billing": "charge_automatically", + "billing_cycle_anchor": 1490398710, + "cancel_at_period_end": false, + "canceled_at": 1489793914, + "created": 1489793910, + "current_period_end": 1490398710, + "current_period_start": 1489793910, + "customer": "cus_CCMIISleHrbPlY", + "days_until_due": 4, + "discount": { + "object": "discount", + "coupon": { + "id": "35OFF", + "object": "coupon", + "amount_off": 5, + "created": 1391694467, + "currency": "usd", + "duration": "repeating", + "duration_in_months": 3, + "livemode": false, + "max_redemptions": 22, + "metadata": { + "hello": "world" + }, + "percent_off": 25, + "redeem_by": 1489793908, + "times_redeemed": 1, + "valid": true + }, + "customer": "cus_CCMIISleHrbPlY", + "end": 1399384361, + "start": 1391694761, + "subscription": "sub_12345" + }, + "ended_at": 1489793914, + "items": { + "object": "list", + "data": [ + { + "id": "si_19yUeQ2eZvKYlo2CnJwkz3pK", + "object": "subscription_item", + "created": 1489793911, + "metadata": { + "hello": "world" + }, + "plan": { + "id": "30990foo1489793903", + "object": "plan", + "amount": 100, + "created": 1489793908, + "currency": "usd", + "interval": "week", + "interval_count": 1, + "livemode": false, + "metadata": { + "hello": "world" + }, + "nickname": "Foo", + "product": "prod_1234", + "trial_period_days": 3 + }, + "quantity": 1, + "subscription": "sub_AJ6s2Iy65K3RxN" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/subscription_items?subscription=sub_AJ6s2Iy65K3RxN" + }, + "livemode": true, + "metadata": { + "foo": "bar" + }, + "plan": { + "id": "30990foo1489793903", + "object": "plan", + "amount": 100, + "created": 1489793908, + "currency": "usd", + "interval": "week", + "interval_count": 1, + "livemode": false, + "metadata": { + "hello": "world" + }, + "nickname": "Foo", + "product": "prod_1234", + "trial_period_days": 14 + }, + "quantity": 1, + "start": 1489793910, + "status": "past_due", + "tax_percent": 4.5, + "trial_end": 1489793910, + "trial_start": 1489793910 +} +""" } diff --git a/Tests/StripeTests/Test+Droplet.swift b/Tests/StripeTests/Test+Droplet.swift deleted file mode 100644 index 38761ce..0000000 --- a/Tests/StripeTests/Test+Droplet.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// Test+Droplet.swift -// Stripe -// -// Created by Anthony Castelli on 4/15/17. -// -// - -import Foundation -import XCTest -import Vapor -import Stripe - -extension XCTestCase { - func makeDroplet() throws -> Droplet { - let config = Config([ - "stripe": [ - "apiKey": "sk_test_Wxn8UzIs9dkIR4qJYAtHhvY8" // Add your own API Key for tests - ], - ]) - try config.addProvider(Stripe.Provider.self) - return try Droplet(config: config) - } -} - -extension Data { - var base64UrlEncodedString: String { - return base64EncodedString() - .replacingOccurrences(of: "=", with: "") - .replacingOccurrences(of: "+", with: "") - .replacingOccurrences(of: "/", with: "") - } - - var base64String: String { - return self.makeBytes().base64Encoded.makeString() - .replacingOccurrences(of: "=", with: "") - .replacingOccurrences(of: "+", with: "") - .replacingOccurrences(of: "/", with: "") - } -} diff --git a/Tests/StripeTests/TokenTests.swift b/Tests/StripeTests/TokenTests.swift index b8c578b..e9b390a 100644 --- a/Tests/StripeTests/TokenTests.swift +++ b/Tests/StripeTests/TokenTests.swift @@ -10,169 +10,167 @@ import XCTest @testable import Stripe @testable import Vapor - - - class TokenTests: XCTestCase { - - var drop: Droplet? - var tokenId: String = "" - - override func setUp() { + func testCardTokenParsedProperly() throws { do { - drop = try self.makeDroplet() + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase - tokenId = try drop?.stripe?.tokens.createCardToken(withCardNumber: "4242 4242 4242 4242", - expirationMonth: 10, - expirationYear: 2018, - cvc: 123, - name: "Test Card", - customer: nil, - currency: nil) - .serializedResponse().id ?? "" - } - catch let error as StripeError { + let body = HTTPBody(string: cardTokenString) + let cardToken = try decoder.decode(StripeToken.self, from: body, on: EmbeddedEventLoop()) - 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) + cardToken.do { (token) in + XCTAssertNil(token.bankAccount) + XCTAssertNotNil(token.card) + + // This test covers the card object + XCTAssertEqual(token.card?.id, "card_1BnxhQ2eZvKYlo2CPNu4CkoA") + XCTAssertEqual(token.card?.object, "card") + XCTAssertEqual(token.card?.addressCity, "Miami") + XCTAssertEqual(token.card?.addressCountry, "US") + XCTAssertEqual(token.card?.addressLine1, "123 Main Street") + XCTAssertEqual(token.card?.addressLine1Check, .failed) + XCTAssertEqual(token.card?.addressLine2, "Apt 123") + XCTAssertEqual(token.card?.addressState, "Florida") + XCTAssertEqual(token.card?.addressZip, "12345") + XCTAssertEqual(token.card?.addressZipCheck, .pass) + XCTAssertEqual(token.card?.brand, "Visa") + XCTAssertEqual(token.card?.country, "US") + XCTAssertEqual(token.card?.cvcCheck, .pass) + XCTAssertEqual(token.card?.dynamicLast4, "1234") + XCTAssertEqual(token.card?.expMonth, 8) + XCTAssertEqual(token.card?.expYear, 2019) + XCTAssertEqual(token.card?.fingerprint, "Xt5EWLLDS7FJjR1c") + XCTAssertEqual(token.card?.funding, .credit) + XCTAssertEqual(token.card?.last4, "4242") + XCTAssertEqual(token.card?.metadata?["hello"], "world") + XCTAssertEqual(token.card?.name, "Vapor") + XCTAssertEqual(token.card?.tokenizationMethod, .applePay) + + XCTAssertEqual(token.id, "tok_1BnxhQ2eZvKYlo2CVEbDC7jK") + XCTAssertEqual(token.object, "token") + XCTAssertEqual(token.clientIp, "0.0.0.0") + XCTAssertEqual(token.created, Date(timeIntervalSince1970: 1516836636)) + XCTAssertEqual(token.livemode, false) + XCTAssertEqual(token.type, "card") + XCTAssertEqual(token.used, false) + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - fatalError("Setup failed: \(error.localizedDescription)") + XCTFail("\(error.localizedDescription)") } } - - override func tearDown() { - drop = nil - tokenId = "" - } - - func testCardTokenCreation() throws { + + func testBankTokenParsedProperly() throws { do { - let object = try drop?.stripe?.tokens.createCardToken(withCardNumber: "4242 4242 4242 4242", - expirationMonth: 10, - expirationYear: 2018, - cvc: 123, - name: "Test Card", - customer: nil, - currency: nil) - .serializedResponse() - XCTAssertNotNil(object?.card) - } - catch let error as StripeError { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.keyDecodingStrategy = .convertFromSnakeCase - 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 testTokenRetrieval() throws { - do { - let object = try drop?.stripe?.tokens.retrieve(tokenId).serializedResponse() - XCTAssertNotNil(object) - } - catch let error as StripeError { + let body = HTTPBody(string: bankAccountTokenString) + let bankToken = try decoder.decode(StripeToken.self, from: body, on: EmbeddedEventLoop()) - 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) + bankToken.do { (token) in + XCTAssertNil(token.card) + XCTAssertNotNil(token.bankAccount) + + // This test covers the bank account object + XCTAssertEqual(token.bankAccount?.id, "ba_1BnxhQ2eZvKYlo2C5cM6hYK1") + XCTAssertEqual(token.bankAccount?.object, "bank_account") + XCTAssertEqual(token.bankAccount?.accountHolderName, "Jane Austen") + XCTAssertEqual(token.bankAccount?.accountHolderType, "individual") + XCTAssertEqual(token.bankAccount?.bankName, "STRIPE TEST BANK") + XCTAssertEqual(token.bankAccount?.country, "US") + XCTAssertEqual(token.bankAccount?.currency, .usd) + XCTAssertEqual(token.bankAccount?.fingerprint, "1JWtPxqbdX5Gamtc") + XCTAssertEqual(token.bankAccount?.last4, "6789") + XCTAssertEqual(token.bankAccount?.metadata?["hello"], "world") + XCTAssertEqual(token.bankAccount?.routingNumber, "110000000") + XCTAssertEqual(token.bankAccount?.status, "new") + + XCTAssertEqual(token.id, "btok_1BnxhQ2eZvKYlo2CbYrQL91x") + XCTAssertEqual(token.object, "token") + XCTAssertEqual(token.clientIp, "0.0.0.0") + XCTAssertEqual(token.created, Date(timeIntervalSince1970: 1516836636)) + XCTAssertEqual(token.livemode, false) + XCTAssertEqual(token.type, "bank_account") + XCTAssertEqual(token.used, false) + + }.catch { (error) in + XCTFail("\(error.localizedDescription)") } } catch { - XCTFail(error.localizedDescription) + XCTFail("\(error.localizedDescription)") } } - func testBankAccountTokenCreation() throws { - do { - let object = try drop?.stripe?.tokens.createBankAccountToken(withAccountNumber: "000123456789", - country: "US", - currency: .usd, - routingNumber: "110000000", - accountHolderName: "Test Person", - accountHolderType: "Individual", - customer: nil).serializedResponse() - XCTAssertNotNil(object?.bankAccount) - } - 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) - } - } + let cardTokenString = """ +{ + "id": "tok_1BnxhQ2eZvKYlo2CVEbDC7jK", + "object": "token", + "card": { + "id": "card_1BnxhQ2eZvKYlo2CPNu4CkoA", + "object": "card", + "address_city":"Miami", + "address_country":"US", + "address_line1":"123 Main Street", + "address_line1_check":"failed", + "address_line2":"Apt 123", + "address_state":"Florida", + "address_zip":"12345", + "address_zip_check":"pass", + "brand": "Visa", + "country": "US", + "cvc_check": "pass", + "dynamic_last4": "1234", + "exp_month": 8, + "exp_year": 2019, + "fingerprint": "Xt5EWLLDS7FJjR1c", + "funding": "credit", + "last4": "4242", + "metadata": { + "hello": "world" + }, + "name": "Vapor", + "tokenization_method": "apple_pay" + }, + "client_ip": "0.0.0.0", + "created": 1516836636, + "livemode": false, + "type": "card", + "used": false +} +""" + + let bankAccountTokenString = """ +{ + "id": "btok_1BnxhQ2eZvKYlo2CbYrQL91x", + "object": "token", + "bank_account": { + "id": "ba_1BnxhQ2eZvKYlo2C5cM6hYK1", + "object": "bank_account", + "account_holder_name": "Jane Austen", + "account_holder_type": "individual", + "bank_name": "STRIPE TEST BANK", + "country": "US", + "currency": "usd", + "fingerprint": "1JWtPxqbdX5Gamtc", + "last4": "6789", + "metadata": { + "hello": "world" + }, + "routing_number": "110000000", + "status": "new" + }, + "client_ip": "0.0.0.0", + "created": 1516836636, + "livemode": false, + "type": "bank_account", + "used": false +} +""" } diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 48e37c1..0000000 --- a/circle.yml +++ /dev/null @@ -1,11 +0,0 @@ -dependencies: - override: - - eval "$(curl -sL https://apt.vapor.sh)" - - sudo apt-get install vapor - - sudo chmod -R a+rx /usr/ -test: - override: - - swift build - - swift build -c release - - swift test - \ No newline at end of file