Create token for bank account information Stripe - ios

I am trying to create an ios app which safely collects a user's bank account information (with the intention of paying the user) using Stripe. Stripe recommends that I collect the bank information in an instance of STPBankAccountParams. This is not too bad:
var bankAccount = STPBankAccountParams()
bankAccount.routingNumber = routingNumber
bankAccount.accountNumber = accountNumber
...
Stripe then recommends that you tokenize the bankAccount for security purposes before sending to backend. They recommend you use this function:
func createToken(withBankAccount bankAccount: STPBankAccountParams, completion: STPTokenCompletionBlock? = nil)
The documentation on this function is a bit sparse: Docs
I am not sure how to run this function in my code. I want to use this function and get the token, but I lack understanding on how to do that in code. I want to run something like:
token = createToken(withBankAccount: bankAccount)
But of course that and other things I have tried have not worked yet. Does anyone have experience running the createTokenWithBankAccount() function in Stripe?

MadProgrammer's answer was very close, but did not actually work. I did talk with a representative from Stripe. For reference, he recommended the following code, which seems to work:
STPAPIClient.shared().createToken(withBankAccount: bankAccount) { (token, error) in
if let error = error {
print(error)
}
else {
print(token)
}
}

STPTokenCompletionBlock is closure (or callback), when the function completes (what ever task it was doing), it will call this block, passing you a STPToken or a Error. You would use something like
createToken(withBankAccount: bankAccount) { (token, error) in
// your code to check the token and error
}
This is a pretty common pattern and I suggest you take a look at something like Swift Programming Language: Closures

Related

How to use two stripe keys in app delegate in swift ios

I want to add two stripe keys to my App delegate field in Swift iOS. I have two stripe accounts and based on conditions I want to do credit/debit card payments. Thanks in advance.
For what I have done is instead of declaring the string keys in App delegate I am declaring the keys in the class itself where I am creating the token for payments like below. I have two classes in each class I am using each key.
#IBAction func submitCard(_ sender: AnyObject) {
let cardParams = paymentTextField.cardParams
Stripe.setDefaultPublishableKey("pk_test_1234567899875gh")// example key
// It takes the card details and creates the token here.
STPAPIClient.shared().createToken(withCard: cardParams) { token, error in
guard token != nil else {
print("Error creating token: %#", error!.localizedDescription);
return
}
print(token)
}
}
Is this write? or do I need to change the code?
Note: This is not about the environment, but two different stripe accounts.
You can simply create multiple STPAPIClients using different keys and then use them as needed, instead of using the shared instance.
https://stripe.dev/stripe-ios/docs/Classes/STPAPIClient.html#/c:objc(cs)STPAPIClient(im)initWithPublishableKey:
let clientA = STPAPIClient(publishableKey: "pk_123")
let clientB = STPAPIClient(publishableKey: "pk_456")
if usingClientA {
clientA.createToken(withCard: cardParams) { ... }
} else if usingClientB {
clientB.createToken(withCard: cardParams) { ... }
}
Also as a general point, you might want to have the public key get returned from an endpoint on your backend server rather than hardcoding into the app, as that makes it easier if you need to change the Stripe account used without going through app review.
Ideally you should use one Stripe account only. And the way you set your publishableKey in your AppDelegate depends on your current environment (i.e. development / production), like so:
let key = isProduction ? "pk_live_TYooMQauvdEDq54NiTphI7jx" : "pk_test_TYooMQauvdEDq54NiTphI7jx"
Stripe.setDefaultPublishableKey(key)
Should you need to set a new key based on "conditions" as what you've mentioned, later on, just set it with the function setDefaultPublishableKey.
You can use this condition in AppDelegate:
#if DEBUG
Stripe.setDefaultPublishableKey(testKey)
#else
Stripe.setDefaultPublishableKey(liveKey)
#endif
Result: if you'll use a debug environment, then Stripe will get the test key. In the case of archive and deploy to TestFlight, for example, Stripe will get live keys.

Possible to Integrate IOS app with ERC20 token

Is is possible at this point to integrate an IOS app with an ERC20 token on the ethereum network.
There seems to be a library called web3.swift that allows for integration with Ethereum. Maybe this is the way to go but don't know if it is ready for a production app.
In addition, there seem to be some online courses on Swift and blockchain such as this one from Lynda and this one from Udemy and some tutorials on blockchain integration such as this from AppCoda and this one which uses the Tierion blockchain as a service. In fact, AWS, Azure and so forth all seem to offer blockchain as a service. Amazon offers managed blockchain with Ethereum.
However, I haven't seen anything that specifically addresses how to integrate an IOS app with Ethereum. And if it's done on the back end by AWS does this defeat the purpose of using a blockchain to avoid centralization on a server?
The use case I am examining would be to view the number of tokens you have and enable users to spend tokens on services. The tokens would reside however on a blockchain.
Thanks for any advice on whether this is even possible at this stage and, if so, how to approach developing it.
New transaction
Infura service might help here. Once you create your account and setup a project - you will be provided with nodes (for main-net and some test-nets) that can perform write operations on a specific Ethereum network.
Here is the code written in Swift and Web3.swift that might help:
func send(sender: String,
receiver: String,
senderSecret: String,
tokenContractAddress: String,
amount: Int,
completion: #escaping Result<Transaction, Error>) {
let web3 = Web3(rpcURL: "YOUR_INFURA_NODE_ID_GOES_HERE")
do {
let privateKey = try EthereumPrivateKey(hexPrivateKey: senderSecret)
let senderWallet = try EthereumAddress(hex: sender, eip55: true)
let receiverWallet = try EthereumAddress(hex: receiver, eip55: true)
let contract = web3.eth.Contract(
type: GenericERC20Contract.self,
address: try EthereumAddress(hex: tokenContractAddress, eip55: true)
)
firstly {
return web3.eth.getTransactionCount(address: privateKey.address, block: .latest)
}.compactMap { nonce in
guard let tx = contract
.transfer(to: receiverWallet, value: BigUInt(amount))
.createTransaction(
nonce: nonce,
from: senderWallet,
value: 0,
gas: 100000,
gasPrice: EthereumQuantity(quantity: 21.gwei)
) else { return nil }
return try tx.sign(with: privateKey, chainId: 3)
}.then { tx in
return web3.eth.sendRawTransaction(transaction: tx!)
}.done { hash in
let tx = Transaction(hash: hash)
completion.set(.success(tx))
}.catch { error in
completion.set(.failure(error))
}
} catch {
completion.set(.failure(error))
}
}
Open information
If there is no need to initiate transactions and you just want to work with public information like token supply/holder balances/etc. - you can try some open API like blockscout to get the data you need.

iOS: How to detect if a user is subscribed to an auto-renewable subscription

Hopefully the title is self-explanatory. I'm trying to do something like this:
checkIfUserIsSubscribedToProduct(productID, transactionID: "some-unique-transaction-string", completion: { error, status in
if error == nil {
if status == .Subscribed {
// do something fun
}
}
}
does anything like the hypothetical code I've provided exist? I feel like I'm taking crazy pills
Edit
In similar questions I keep seeing a generic answer of "oh you gotta validate the receipt" but no explanation on how, or even what a receipt is. Could someone provide me with how to "validate the receipt"? I tried this tutorial but didn't seem to work.
Edit - For Bounty
Please address the following situation: A user subscribes to my auto-renewable subscription and gets more digital content because of it - cool, implemented. But how do I check whether that subscription is still valid (i.e. they did not cancel their subscription) each time they open the app? What is the simplest solution to check this? Is there something like the hypothetical code I provided in my question? Please walk me through this and provide any further details on the subject that may be helpful.
I know everyone was very concerned about me and how I was doing on this - fear not, solved my problem. Main problem was that I tried Apple's example code from the documentation, but it wasn't working so I gave up on it. Then I came back to it and implemented it with Alamofire and it works great. Here's the code solution:
Swift 3:
let receiptURL = Bundle.main.appStoreReceiptURL
let receipt = NSData(contentsOf: receiptURL!)
let requestContents: [String: Any] = [
"receipt-data": receipt!.base64EncodedString(options: []),
"password": "your iTunes Connect shared secret"
]
let appleServer = receiptURL?.lastPathComponent == "sandboxReceipt" ? "sandbox" : "buy"
let stringURL = "https://\(appleServer).itunes.apple.com/verifyReceipt"
print("Loading user receipt: \(stringURL)...")
Alamofire.request(stringURL, method: .post, parameters: requestContents, encoding: JSONEncoding.default)
.responseJSON { response in
if let value = response.result.value as? NSDictionary {
print(value)
} else {
print("Receiving receipt from App Store failed: \(response.result)")
}
}
As some comments pointed out there's a couple flaws with these answers.
Calling /verifyReceipt from the client isn't secure.
Comparing expiration dates against the device clock can be spoofed by changing the time (always a fun hack to try after cancelling a free trial :) )
There are some other tutorials of how to set up a server to handle the receipt verification, but this is only part of the problem. Making a network request to unpack and validate a receipt on every app launch can lead to issues, so there should be some caching too to keep things running smoothly.
The RevenueCat SDK provides a good out-of-the box solution for this.
A couple reasons why I like this approach:
Validates receipt server side (without requiring me to set up a server)
Checks for an "active" subscription with a server timestamp so can't be spoofed by changing the device clock
Caches the result so it's super fast and works offline
There's some more details in this question: https://stackoverflow.com/a/55404121/3166209
What it works down to is a simple function that you can call as often as needed and will return synchronously in most cases (since it's cached).
subscriptionStatus { (subscribed) in
if subscribed {
// Show that great pro content
}
}
What are you trying to achieve in particular? Do you want to check for a specific Apple ID?
I highly doubt that this is possible through the SDK. Referring to Is it possible to get the user's apple ID through the SDK? you can see that you can't even ask for the ID directly but rather services attached to it.
What would work is caching all transactions on your own server and search its database locally but that would require the app to ask for the user's Apple ID so the app could update the subscription state whenever it launches as it can check for IAP of the ID associated with the device.
However, the user could just type whatever he wanted - and it's unlikely to get this through Apple's app review process.
I am using MKSoreKit https://github.com/MugunthKumar/MKStoreKit for auto-renew subscriptions.but it is in objective c you can check the library code for solution.I am using it in my code and it is working fine.
using below method you can easily check subscription status..
if([MKStoreManager isProductPurchased:productIdentifier]) {
//unlock it
}
It gets the apple id from device and I think that is user specific

Stripe iOS integration - how do I know if Stripe is actually receiving my test inputs?

So I have been trying to integrate Stripe with my swift code and so far I have the following code for testing. I printed the credit card number because it is required to create a token on Stripe's end, and it correctly reflects the testing credit card number (e.g. 4242424242424242) used by Stripe (I also did input random numbers for cvc and exp date), however, the "validateCardReturningError" always skips the "createTokenWithCard" function to execute "print("Error")". In an attempt to debug, I also commented out "validateCardReturningError" to see if I get a token back, but instead I get nil as the value for token.
This behavior leads me to believe that the communication to Stripe isn't actually happening, therefore no token is being generated. If so, is there a way to test the communication? Alternatively, is there a way to check to see what value is being returned from "validateCardReturningError"?
if (userExpiration.isEmpty){ let expArr = userExpiration.componentsSeparatedByString("/")
if (expArr.count > 1) {
let expMonth: NSNumber = Int(expArr[0])!
let expYear: NSNumber = Int(expArr[1])!
creditCard.expMonth = expMonth.unsignedLongValue
creditCard.expYear = expYear.unsignedLongValue } }
print(creditCard.number)
do {
try creditCard.validateCardReturningError()
STPAPIClient.sharedClient().createTokenWithCard(
creditCard,
completion: { (token: STPToken?, stripeError: NSError?) -> Void in
print(token)
})
} catch {
print("Error")
}
The errors I get when I printed stripeError is:
Optional(Error Domain=com.stripe.lib Code=50 "Missing required param: exp_month." UserInfo={com.stripe.lib:ErrorMessageKey=Missing required param: exp_month., com.stripe.lib:ErrorParameterKey=card[expMonth], NSLocalizedDescription=Missing required param: exp_month.})
Change:
if (userExpiration.isEmpty)
to
if (!userExpiration.isEmpty)
so that you actually use the expiration date when there's something in there.
Also add an else clause to complain if it's empty, and prevent submission (I suppose this code is run when the user submits the form).
Stripe actually lets you browse all of the logs for your account in your dashboard. So if you head to https://dashboard.stripe.com/logs?method=not_get, you'll be able to see if you're successfully POSTing to the /v1/tokens endpoint.

Able to POST direct messages using Twitter's REST API, but trying to GET returns a 401 error

I am trying to get direct messages working in my app. I'm able to POST DMs just fine, but when I try to GET them from the endpoint https://api.twitter.com/1.1/direct_messages.json it returns a 401 - Unauthorized. I don't really understand how I can be authorized to send DMs but not get ones sent to me.
Here's how I'm authenticating initially:
if Twitter.sharedInstance().sessionStore.session() == nil {
Twitter.sharedInstance().logInWithCompletion { session, error in
if (session != nil) {
// successfully logged in, call loading functions
} else {
print("error: \(error!.localizedDescription)")
}
}
} else {
// already logged in, call loading functions
}
Every time I make a request using the REST API, it begins with
if let userID = Twitter.sharedInstance().sessionStore.session()?.userID {
let client = TWTRAPIClient(userID: userID)
The client is initialised the same way in both the POST and GET requests for DMs, yet the GET request fails.
As far as permissions go, I've checked that it has read/write/DM access according to Twitter, and successful requests return "x-access-level" = "read-write-directmessages";, so I think it's set properly.
I was concerned at one point that I might not be authenticating properly, since Twitter's documentation goes through the 3 step process for O-Auth and all I'm doing is telling the Twitter singleton to just log in... but I rationalised that away by assuming that those steps are all carried out in the logInWithCompletion function. And besides, if I wasn't authenticated properly I surely wouldn't be able to send DMs, right?
Any ideas on how I can fix this? I'm quite new so it may be something nice and simple! I've looked through some other questions, but they all seem to code the requests in full rather than using built-in methods like these - or have I got it all wrong?
Yeah, it was a stupid problem - I left the parameters blank since they are all marked as 'optional' - as in, a dictionary of ["" : ""]. I just set the paramaters in the request to nil, and now it works.

Resources