-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Payment sources are not limited to Sources but may also include Cards and Bank Accounts. The `StripePaymentSource` type is an enum that wraps a `StripeBankAccount`, a `StripeCard`, or a `StripeSource`.
- Loading branch information
Showing
4 changed files
with
267 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// | ||
// PaymentSource.swift | ||
// Stripe | ||
// | ||
// Created by Nicolas Bachschmidt on 2018-08-09. | ||
// | ||
|
||
/** | ||
Payment Source objects | ||
https://stripe.com/docs/api#customer_bank_account_object | ||
https://stripe.com/docs/api#card_object | ||
https://stripe.com/docs/api#source_object | ||
*/ | ||
|
||
public enum StripePaymentSource: StripeModel { | ||
case bankAccount(StripeBankAccount) | ||
case card(StripeCard) | ||
case source(StripeSource) | ||
|
||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
let object = try container.decode(String.self, forKey: .object) | ||
switch object { | ||
case "bank_account": | ||
self = try .bankAccount(StripeBankAccount(from: decoder)) | ||
case "card": | ||
self = try .card(StripeCard(from: decoder)) | ||
case "source": | ||
self = try .source(StripeSource(from: decoder)) | ||
default: | ||
throw DecodingError.dataCorruptedError( | ||
forKey: CodingKeys.object, | ||
in: container, | ||
debugDescription: "Unknown payment source \"\(object)\"" | ||
) | ||
} | ||
} | ||
|
||
public func encode(to encoder: Encoder) throws { | ||
switch self { | ||
case let .bankAccount(bankAccount): | ||
try bankAccount.encode(to: encoder) | ||
case let .card(card): | ||
try card.encode(to: encoder) | ||
case let .source(source): | ||
try source.encode(to: encoder) | ||
} | ||
} | ||
|
||
public enum CodingKeys: String, CodingKey { | ||
case object | ||
} | ||
} | ||
|
||
extension StripePaymentSource { | ||
public var bankAccount: StripeBankAccount? { | ||
guard case let .bankAccount(bankAccount) = self else { | ||
return nil | ||
} | ||
return bankAccount | ||
} | ||
|
||
public var card: StripeCard? { | ||
guard case let .card(card) = self else { | ||
return nil | ||
} | ||
return card | ||
} | ||
|
||
public var source: StripeSource? { | ||
guard case let .source(source) = self else { | ||
return nil | ||
} | ||
return source | ||
} | ||
|
||
public var id: String { | ||
switch self { | ||
case let .bankAccount(bankAccount): | ||
return bankAccount.id | ||
case let .card(card): | ||
return card.id | ||
case let .source(source): | ||
return source.id | ||
} | ||
} | ||
|
||
public var object: String { | ||
switch self { | ||
case let .bankAccount(bankAccount): | ||
return bankAccount.object | ||
case let .card(card): | ||
return card.object | ||
case let .source(source): | ||
return source.object | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
// | ||
// PaymentSourceTests.swift | ||
// Stripe | ||
// | ||
// Created by Nicolas Bachschmidt on 2018-08-09. | ||
// | ||
|
||
import XCTest | ||
@testable import Stripe | ||
@testable import Vapor | ||
|
||
class PaymentSourceTests: XCTestCase { | ||
let sourceListString = """ | ||
{ | ||
"object": "list", | ||
"total_count": 3, | ||
"data": [ | ||
{ | ||
"id": "card_1Cs8z3GaLcnLeFWiU9SDCez8", | ||
"object": "card", | ||
"address_city": null, | ||
"address_country": null, | ||
"address_line1": null, | ||
"address_line1_check": null, | ||
"address_line2": null, | ||
"address_state": null, | ||
"address_zip": null, | ||
"address_zip_check": null, | ||
"brand": "Visa", | ||
"country": "FR", | ||
"customer": "cus_D3t6eeIn7f2nYi", | ||
"cvc_check": "pass", | ||
"dynamic_last4": null, | ||
"exp_month": 12, | ||
"exp_year": 2029, | ||
"fingerprint": "GhnGuMVuycvktkHE", | ||
"funding": "credit", | ||
"last4": "0003", | ||
"metadata": { | ||
}, | ||
"name": null, | ||
"tokenization_method": null, | ||
"type": "Visa" | ||
}, | ||
{ | ||
"id": "src_1CxF9xGaLcnLeFWif1vfiSkS", | ||
"object": "source", | ||
"ach_credit_transfer": { | ||
"account_number": "test_49b8d100bde6", | ||
"bank_name": "TEST BANK", | ||
"fingerprint": "0W7zV79lnzu4L4Ie", | ||
"routing_number": "110000000", | ||
"swift_code": "TSTEZ122" | ||
}, | ||
"amount": null, | ||
"client_secret": "src_client_secret_DO1CKYXtHAFzKMNPY4Fsi7d9", | ||
"created": 1533825041, | ||
"currency": "usd", | ||
"flow": "receiver", | ||
"livemode": false, | ||
"metadata": { | ||
}, | ||
"owner": { | ||
"address": null, | ||
"email": "[email protected]", | ||
"name": null, | ||
"phone": null, | ||
"verified_address": null, | ||
"verified_email": null, | ||
"verified_name": null, | ||
"verified_phone": null | ||
}, | ||
"receiver": { | ||
"address": "110000000-test_49b8d100bde6", | ||
"amount_charged": 0, | ||
"amount_received": 1000, | ||
"amount_returned": 0, | ||
"refund_attributes_method": "email", | ||
"refund_attributes_status": "missing" | ||
}, | ||
"statement_descriptor": null, | ||
"status": "chargeable", | ||
"type": "ach_credit_transfer", | ||
"usage": "reusable" | ||
}, | ||
{ | ||
"id": "ba_1CxEzUGaLcnLeFWiz0fJrOVm", | ||
"object": "bank_account", | ||
"account_holder_name": "", | ||
"account_holder_type": "individual", | ||
"bank_name": "STRIPE TEST BANK", | ||
"country": "US", | ||
"currency": "usd", | ||
"customer": "cus_D3t6eeIn7f2nYi", | ||
"disabled": false, | ||
"fingerprint": "xrXW6SzxS6Gjr4d7", | ||
"last4": "6789", | ||
"metadata": { | ||
}, | ||
"name": "", | ||
"routing_number": "110000000", | ||
"status": "new", | ||
"validated": false, | ||
"verified": false | ||
} | ||
], | ||
"has_more": false, | ||
"url": "/v1/customers/cus_D3t6eeIn7f2nYi/sources" | ||
} | ||
""" | ||
|
||
func testSourceListIsProperlyParsed() throws { | ||
do { | ||
let decoder = JSONDecoder() | ||
decoder.dateDecodingStrategy = .secondsSince1970 | ||
|
||
let body = HTTPBody(string: sourceListString) | ||
var headers: HTTPHeaders = [:] | ||
headers.replaceOrAdd(name: .contentType, value: MediaType.json.description) | ||
let request = HTTPRequest(headers: headers, body: body) | ||
let futureOrder = try decoder.decode(StripeSourcesList.self, from: request, maxSize: 65_536, on: EmbeddedEventLoop()) | ||
|
||
futureOrder.do { list in | ||
XCTAssertEqual(list.object, "list") | ||
XCTAssertEqual(list.hasMore, false) | ||
XCTAssertEqual(list.data.count, 3) | ||
XCTAssertEqual(list.bankAccounts.count, 1) | ||
XCTAssertEqual(list.cards.count, 1) | ||
XCTAssertEqual(list.sources.count, 1) | ||
XCTAssertEqual(list.data.map { $0.id }, [ | ||
"card_1Cs8z3GaLcnLeFWiU9SDCez8", | ||
"src_1CxF9xGaLcnLeFWif1vfiSkS", | ||
"ba_1CxEzUGaLcnLeFWiz0fJrOVm", | ||
]) | ||
XCTAssertEqual(list.data.map { $0.object }, ["card", "source", "bank_account"]) | ||
|
||
}.catch { (error) in | ||
XCTFail("\(error)") | ||
} | ||
} | ||
catch { | ||
XCTFail("\(error)") | ||
} | ||
} | ||
} |