iOS支付
iOS支付分為兩類,第三方支付和應(yīng)用內(nèi)支付(內(nèi)購)。
第三方支付包括:支付寶支付、微信支付、銀聯(lián)支付、百度錢包、京東支付等等。
應(yīng)用內(nèi)支付(In-App Purchase):在應(yīng)用程序內(nèi)購買虛擬商品。如果你在App Store上銷售的應(yīng)用程序,將收到支付金額的70%。
第三方支付
彈出方式
網(wǎng)頁
有些第三方支付沒有安裝客戶端,可以直接彈出網(wǎng)頁進行支付。(比如支付寶)
調(diào)用APP
手機中安裝了客戶端可以跳轉(zhuǎn)到APP中進行支付。微信支付只能調(diào)用App進行支付。
支付寶支付
相關(guān)資料
支付寶開放平臺(SDK&開發(fā)文檔):https://open.alipay.com/platform/home.htm
移動支付集成:https://doc.open.alipay.com/doc2/detail?treeId=59&articleId=103563&docType=1
商戶服務(wù)平臺(與支付寶簽約需要填寫的公司資料):https://b.alipay.com/newIndex.htm
支付流程
在商戶服務(wù)平臺先與支付寶簽約,獲得商戶ID(partner)和賬號ID(seller),需要提供公司資質(zhì)或者營業(yè)執(zhí)照,個人無法申請。
文檔地址:https://doc.open.alipay.com/doc2/detail?treeId=58&articleId=103542&docType=1
生成并下載相應(yīng)的公鑰私鑰文件(加密簽名用)
文檔地址:https://doc.open.alipay.com/doc2/detail.htm?spm=0.0.0.0.POMYKl&treeId=58&articleId=103543&docType=1
下載支付寶SDK:https://doc.open.alipay.com/doc2/detail?treeId=54&articleId=103419&docType=1
生成訂單信息
調(diào)用支付寶客戶端,由支付寶客戶端跟支付寶安全服務(wù)器打交道
支付完畢后返回支付結(jié)果給商戶客戶端和服務(wù)器
SDK里有集成支付寶功能的一個Demo,集成支付功能的具體操作方式,可以參考Demo。
代碼集成流程
參考文檔地址:https://doc.open.alipay.com/doc2/detail.htm?spm=0.0.0.0.efmKDS&treeId=59&articleId=103676&docType=1
下載官方SDK
下載地址:https://doc.open.alipay.com/doc2/detail?treeId=54&articleId=103419&docType=1
本Demo使用的SDK是從官方Demo整理出來的,整理的SDK版本:201501022。
下載地址:http://7xooko.com1.z0.glb.clouddn.com/AlipaySDK.zip
目錄結(jié)構(gòu)如下:
├──AlipaySDK.bundle├──AlipaySDK.framework├──Order.h├──Order.m├──Util├──libcrypto.a├──libssl.a└──openssl
其中:
AlipaySDK.bundle和AlipaySDK.framework是支付寶SDK
Order類:定義訂單信息
Util、libcrypto.a、libssl.a、openssl:數(shù)據(jù)簽名,對訂單信息進行加密
添加依賴庫
其中,需要注意的是:
如果是Xcode 7.0之后的版本,需要添加libc++.tbd、libz.tbd;
如果是Xcode 7.0之前的版本,需要添加libc++.dylib、libz.dylib。
創(chuàng)建prefix header filePCH文件,添加#import
在Build Settings中的prefix header設(shè)置pch文件路徑
在Build Settings中Header Search Paths添加頭文件引用路徑,[文件路徑]/AlipaySDK/
在需要調(diào)用AlipaySDK的文件中,增加頭文件引用。
#import#import"Order.h"#import"DataSigner.h"
生成訂單信息及簽名
//將商品信息賦予AlixPayOrder的成員變量Order *order = [[Order alloc] init];order.partner= PartnerID;// 商戶IDorder.seller= SellerID;// 賬號IDorder.tradeNO=@"20150923";//訂單ID(由商家自行制定)order.productName=@"iPhone6s";//商品標題order.productDescription=@"新年打折";//商品描述order.amount=@"0.01";//商品價格(單位:元)order.notifyURL=@"http://www.chaosky.me";//回調(diào)URL,支付成功或者失敗回調(diào)通知自己的服務(wù)器進行訂單狀態(tài)變更order.service=@"mobile.securitypay.pay";order.paymentType=@"1";order.inputCharset=@"utf-8";order.itBPay=@"30m";order.showUrl=@"m.alipay.com";// 應(yīng)用注冊scheme,在AlixPayDemo-Info.plist定義URL typesNSString*appScheme =@"AliPayDemo";//將商品信息拼接成字符串NSString*orderSpec = [order description];NSLog(@"orderSpec = %@",orderSpec);//獲取私鑰并將商戶信息簽名,外部商戶可以根據(jù)情況存放私鑰和簽名,只需要遵循RSA簽名規(guī)范,并將簽名字符串base64編碼和UrlEncodeid signer = CreateRSADataSigner(PartnerPrivKey);NSString*signedString = [signer signString:orderSpec];//將簽名成功字符串格式化為訂單字符串,請嚴格按照該格式NSString*orderString =nil;if(signedString !=nil) {? ? orderString = [NSStringstringWithFormat:@"%@&sign=\"%@\"&sign_type=\"%@\"",? ? ? ? ? ? ? ? ?? orderSpec, signedString,@"RSA"];? ? [[AlipaySDK defaultService] payOrder:orderString fromScheme:appScheme callback:^(NSDictionary*resultDic) {NSLog(@"reslut = %@",resultDic);? ? }];}
Xcode設(shè)置URL scheme
iPhone SDK可以把你的App和一個自定義的URL Scheme綁定。該URL Scheme可用來從瀏覽器或別的App啟動你的App。
配置方法:打開info.plist文件,找到或者添加如圖所示的鍵值對:
URL Scheme值為代碼中對應(yīng)的值,必須一致。
配置支付寶客戶端返回url處理方法
AppDelegate.m文件中,增加引用代碼:
#import
在@implementation AppDelegate中增加如下代碼:
- (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation{//如果極簡開發(fā)包不可用,會跳轉(zhuǎn)支付寶錢包進行支付,需要將支付寶錢包的支付結(jié)果回傳給開發(fā)包if([url.hostisEqualToString:@"safepay"]) {? ? ? ? [[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary*resultDic) {//【由于在跳轉(zhuǎn)支付寶客戶端支付的過程中,商戶app在后臺很可能被系統(tǒng)kill了,所以pay接口的callback就會失效,請商戶對standbyCallback返回的回調(diào)結(jié)果進行處理,就是在這個方法里面處理跟callback一樣的邏輯】NSLog(@"result = %@",resultDic);? ? ? ? }];? ? }if([url.hostisEqualToString:@"platformapi"]){//支付寶錢包快登授權(quán)返回authCode[[AlipaySDK defaultService] processAuthResult:url standbyCallback:^(NSDictionary*resultDic) {//【由于在跳轉(zhuǎn)支付寶客戶端支付的過程中,商戶app在后臺很可能被系統(tǒng)kill了,所以pay接口的callback就會失效,請商戶對standbyCallback返回的回調(diào)結(jié)果進行處理,就是在這個方法里面處理跟callback一樣的邏輯】NSLog(@"result = %@",resultDic);? ? ? ? }];? ? }returnYES;}
微信支付
需要提供公司資質(zhì)或者營業(yè)執(zhí)照,個人無法申請。
相關(guān)文檔
微信開放平臺:https://open.weixin.qq.com
微信支付商戶平臺:https://pay.weixin.qq.com/index.php
微信公眾平臺:https://mp.weixin.qq.com
支付流程
向微信注冊你的應(yīng)用程序id
開發(fā)者應(yīng)用登記頁面進行登記,登記并選擇移動應(yīng)用進行設(shè)置后,將獲得AppID,可立即用于開發(fā)。但應(yīng)用登記完成后還需要提交審核,只有審核通過的應(yīng)用才能正式發(fā)布使用。
微信APP支付接入商戶服務(wù)中心
下載微信SDK文件,如果在項目中應(yīng)使用SDK的最新版。
本Demo使用的SDK是從官方Demo整理出來的,整理的SDK版本:1.6.1。
下載地址:http://7xooko.com1.z0.glb.clouddn.com/AlipaySDK.zip
目錄結(jié)構(gòu)如下:
├──SDKExport│?? ├──WXApi.h│?? ├──WXApiObject.h│?? ├──libWeChatSDK.a│?? └──read_me.txt└──lib├──ApiXml.h├──ApiXml.mm├──WXUtil.h├──WXUtil.mm├──payRequsestHandler.h└──payRequsestHandler.mm
其中:
SDKExport文件夾:SDK文件
lib文件夾:工具類
添加依賴庫
SystemConfiguration.frameworklibz.dyliblibsqlite3.dyliblibc++.dylibCoreTelephony.frameworkCoreGraphics.framework
Xcode設(shè)置URL scheme
在Xcode中,選擇你的工程設(shè)置項,選中“TARGETS”一欄,在“info”標簽欄的“URL type“添加“URL scheme”為你所注冊的應(yīng)用程序id(如下圖所示)。
在你需要使用微信終端API的文件中import WXApi.h 頭文件,并增加 WXApiDelegate 協(xié)議。
// 微信所有的API接口#import"WXApi.h"http:// APP端簽名相關(guān)頭文件#import"payRequsestHandler.h"@interfaceAppDelegate()@end
要使你的程序啟動后微信終端能響應(yīng)你的程序,必須在代碼中向微信終端注冊你的id。(如下圖所示,在 AppDelegate 的 didFinishLaunchingWithOptions 函數(shù)中向微信注冊id)。
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {// Override point for customization after application launch.//向微信注冊[WXApi registerApp:APP_ID withDescription:@"demo 2.0"];returnYES;}
重寫AppDelegate的handleOpenURL和openURL方法:
- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url{return[WXApi handleOpenURL:url delegate:self];}- (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation{return[WXApi handleOpenURL:url delegate:self];}
現(xiàn)在,你的程序要實現(xiàn)和微信終端交互的具體請求與回應(yīng),因此需要實現(xiàn)WXApiDelegate協(xié)議的兩個方法:
-(void) onReq:(BaseReq*)req{if([req isKindOfClass:[GetMessageFromWXReq class]])? ? {// 微信請求App提供內(nèi)容, 需要app提供內(nèi)容后使用sendRsp返回NSString*strTitle = [NSStringstringWithFormat:@"微信請求App提供內(nèi)容"];NSString*strMsg =@"微信請求App提供內(nèi)容,App要調(diào)用sendResp:GetMessageFromWXResp返回給微信";UIAlertView*alert = [[UIAlertViewalloc] initWithTitle:strTitle message:strMsg delegate:selfcancelButtonTitle:@"OK"otherButtonTitles:nil,nil];? ? ? ? alert.tag=1000;? ? ? ? [alert show];? ? }elseif([req isKindOfClass:[ShowMessageFromWXReq class]])? ? {? ? ? ? ShowMessageFromWXReq* temp = (ShowMessageFromWXReq*)req;? ? ? ? WXMediaMessage *msg = temp.message;//顯示微信傳過來的內(nèi)容WXAppExtendObject *obj = msg.mediaObject;NSString*strTitle = [NSStringstringWithFormat:@"微信請求App顯示內(nèi)容"];NSString*strMsg = [NSStringstringWithFormat:@"標題:%@ \n內(nèi)容:%@ \n附帶信息:%@ \n縮略圖:%lu bytes\n\n", msg.title, msg.description, obj.extInfo, msg.thumbData.length];UIAlertView*alert = [[UIAlertViewalloc] initWithTitle:strTitle message:strMsg delegate:selfcancelButtonTitle:@"OK"otherButtonTitles:nil,nil];? ? ? ? [alert show];? ? }elseif([req isKindOfClass:[LaunchFromWXReq class]])? ? {//從微信啟動AppNSString*strTitle = [NSStringstringWithFormat:@"從微信啟動"];NSString*strMsg =@"這是從微信啟動的消息";UIAlertView*alert = [[UIAlertViewalloc] initWithTitle:strTitle message:strMsg delegate:selfcancelButtonTitle:@"OK"otherButtonTitles:nil,nil];? ? ? ? [alert show];? ? }}
onReq是微信終端向第三方程序發(fā)起請求,要求第三方程序響應(yīng)。第三方程序響應(yīng)完后必須調(diào)用sendRsp返回。在調(diào)用sendRsp返回時,會切回到微信終端程序界面。
-(void) onResp:(BaseResp*)resp {NSString*strMsg = [NSStringstringWithFormat:@"errcode:%d", resp.errCode];NSString*strTitle;if([resp isKindOfClass:[SendMessageToWXResp class]])? ?? {? ? ? ?? strTitle = [NSStringstringWithFormat:@"發(fā)送媒體消息結(jié)果"];? ?? }if([resp isKindOfClass:[PayResp class]]){//支付返回結(jié)果,實際支付結(jié)果需要去微信服務(wù)器端查詢strTitle = [NSStringstringWithFormat:@"支付結(jié)果"];switch(resp.errCode) {caseWXSuccess:? ? ? ? ? ? ? ?? strMsg =@"支付結(jié)果:成功!";NSLog(@"支付成功-PaySuccess,retcode = %d", resp.errCode);break;default:? ? ? ? ? ? ? ?? strMsg = [NSStringstringWithFormat:@"支付結(jié)果:失敗!retcode = %d, retstr = %@", resp.errCode,resp.errStr];NSLog(@"錯誤,retcode = %d, retstr = %@", resp.errCode,resp.errStr);break;? ? ? ?? }? ?? }UIAlertView*alert = [[UIAlertViewalloc] initWithTitle:strTitle message:strMsg delegate:selfcancelButtonTitle:@"OK"otherButtonTitles:nil,nil];? ?? [alert show]; }
如果第三方程序向微信發(fā)送了sendReq的請求,那么onResp會被回調(diào)。sendReq請求調(diào)用后,會切到微信終端程序界面
應(yīng)用內(nèi)支付(In-App Purchase)
在應(yīng)用程序內(nèi)購買虛擬商品。如果你在App Store上銷售的應(yīng)用程序,將收到支付金額的70%。
相關(guān)資料
沙盒測試賬號:352135598@qq.com 密碼:Test1234phone
支付流程
配置App ID
為應(yīng)用建立建立一個不帶通配符的App ID
用該App ID生成和安裝相應(yīng)的Provisioning Profile文件。
配置iTunes Connect
填寫相關(guān)的稅務(wù),銀行,聯(lián)系人信息
參考鏈接:iOS App提交指南(二)-協(xié)議、稅務(wù)和銀行業(yè)務(wù)
添加一個用于在sandbox付費的測試用戶
用該App ID創(chuàng)建一個新的應(yīng)用。
創(chuàng)建應(yīng)用內(nèi)付費項目,選擇付費類型。
App 內(nèi)購買項目摘要填寫
主要代碼實現(xiàn)
在工程中引入StoreKit.framework和#import
獲得所有的付費Product ID列表。這個可以用常量存儲在本地,也可以由自己的服務(wù)器返回。
//在內(nèi)購項目中創(chuàng)建的商品單號#defineProductID_IAP_FTHJ @"com.1000phone.IAPDemo.fthj_purple"http:// 方天畫戟 488元#defineProductID_IAP_XYJ @"com.1000phone.IAPDemo.xyj"http:// 軒轅劍 6,498元#defineProductID_IAP_JB @"com.1000phone.IAPDemo.jb"http:// 金幣 6元=6金幣
?
制作界面,展示所有的應(yīng)用內(nèi)付費項目。這些應(yīng)用內(nèi)付費項目的價格和介紹信息可以從App Store服務(wù)器請求,也可以是自己的服務(wù)器返回。向App Store查詢速度非常慢,通常需要2-3秒鐘,最好從服務(wù)器請求。
- (void)createViews{NSArray* buttonNames = @[@"軒轅劍 6498元",@"方天畫戟 488元",@"金幣6元=6金幣"];? ? __weaktypeof(self) weakSelf =self;? ? [buttonNames enumerateObjectsUsingBlock:^(NSString* buttonName,NSUIntegeridx,BOOL* stop) {UIButton* button = [UIButtonbuttonWithType:UIButtonTypeSystem];? ? ? ? [weakSelf.viewaddSubview:button];? ? ? ? button.frame=CGRectMake(100,100+ idx?? *60,150,50);? ? ? ? button.titleLabel.font= [UIFontsystemFontOfSize:18];? ? ? ? [button setTitle:buttonName forState:UIControlStateNormal];// 設(shè)置tag值button.tag= PAY_BUTTON_BEGIN_TAG + idx;? ? ? ? [button addTarget:selfaction:@selector(buyProduct:) forControlEvents:UIControlEventTouchUpInside];? ? }];}- (void)buyProduct:(UIButton*) sender{}
?
當用戶點擊了一個IAP項目,我們先查詢用戶是否允許應(yīng)用內(nèi)付費。
- (void)buyProduct:(UIButton*) sender{self.buyType= sender.tag- PAY_BUTTON_BEGIN_TAG;if([SKPaymentQueue canMakePayments]) {// 執(zhí)行下面提到的第5步:[selfrequestProductData];NSLog(@"允許程序內(nèi)付費購買");? ? }else{NSLog(@"不允許程序內(nèi)付費購買");UIAlertView*alerView =? [[UIAlertViewalloc] initWithTitle:@"提示"message:@"您的手機沒有打開程序內(nèi)付費購買"delegate:nilcancelButtonTitle:NSLocalizedString(@"關(guān)閉",nil) otherButtonTitles:nil];? ? ? ? [alerView show];? ? }}
我們先通過該IAP的ProductID向AppStore查詢,獲得SKPayment實例,然后通過SKPaymentQueue的 addPayment方法發(fā)起一個購買的操作。
// 下面的ProductId應(yīng)該是事先在itunesConnect中添加好的,已存在的付費項目。否則查詢會失敗。- (void)requestProductData {NSLog(@"---------請求對應(yīng)的產(chǎn)品信息------------");NSArray*product =nil;switch(self.buyType) {case0:? ? ? ? ?? product = [NSArrayarrayWithObject:ProductID_IAP_XYJ];break;case1:? ? ? ? ?? product = [NSArrayarrayWithObject:ProductID_IAP_FTHJ];break;case2:? ? ? ? ?? product = [NSArrayarrayWithObject:ProductID_IAP_JB];break;?? }NSSet*nsset = [NSSetsetWithArray:product];?? SKProductsRequest *request=[[SKProductsRequest alloc] initWithProductIdentifiers: nsset];?? request.delegate=self;?? [request start];}#pragma mark - SKProductsRequestDelegate// 收到的產(chǎn)品信息回調(diào)- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{NSLog(@"-----------收到產(chǎn)品反饋信息--------------");NSArray*myProduct = response.products;if(myProduct.count==0) {NSLog(@"無法獲取產(chǎn)品信息,購買失敗。");return;?? }NSLog(@"產(chǎn)品Product ID:%@",response.invalidProductIdentifiers);NSLog(@"產(chǎn)品付費數(shù)量: %d", (int)[myProduct count]);// populate UIfor(SKProduct *productinmyProduct){NSLog(@"product info");NSLog(@"SKProduct 描述信息%@", [product description]);NSLog(@"產(chǎn)品標題 %@", product.localizedTitle);NSLog(@"產(chǎn)品描述信息: %@", product.localizedDescription);NSLog(@"價格: %@", product.price);NSLog(@"Product id: %@", product.productIdentifier);?? }?? SKPayment * payment = [SKPayment paymentWithProduct:myProduct[0]];NSLog(@"---------發(fā)送購買請求------------");?? [[SKPaymentQueue defaultQueue] addPayment:payment];}//彈出錯誤信息- (void)request:(SKRequest *)request didFailWithError:(NSError*)error{NSLog(@"-------彈出錯誤信息----------");UIAlertView*alerView =? [[UIAlertViewalloc] initWithTitle:NSLocalizedString(@"Alert",NULL) message:[error localizedDescription]? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? delegate:nilcancelButtonTitle:NSLocalizedString(@"Close",nil) otherButtonTitles:nil];?? [alerView show];}-(void) requestDidFinish:(SKRequest *)request{NSLog(@"----------反饋信息結(jié)束--------------");}
在viewDidLoad方法中,將購買頁面設(shè)置成購買的Observer。
- (void)viewDidLoad {? ? [superviewDidLoad];? ? [selfcreateViews];// 監(jiān)聽購買結(jié)果[[SKPaymentQueue defaultQueue] addTransactionObserver:self];}- (void)dealloc{? ? [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];}
當用戶購買的操作有結(jié)果時,就會觸發(fā)下面的回調(diào)函數(shù),相應(yīng)進行處理即可。
#pragma mark - SKPaymentTransactionObserver// 處理交易結(jié)果- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray*)transactions {for(SKPaymentTransaction *transactionintransactions)? ? {switch(transaction.transactionState)? ? ? ? {caseSKPaymentTransactionStatePurchased://交易完成NSLog(@"transactionIdentifier = %@", transaction.transactionIdentifier);? ? ? ? ? ? ? ? [selfcompleteTransaction:transaction];break;caseSKPaymentTransactionStateFailed://交易失敗[selffailedTransaction:transaction];break;caseSKPaymentTransactionStateRestored://已經(jīng)購買過該商品[selfrestoreTransaction:transaction];break;caseSKPaymentTransactionStatePurchasing://商品添加進列表NSLog(@"商品添加進列表");break;default:break;? ? ? ? }? ? }}// 交易完成- (void)completeTransaction:(SKPaymentTransaction *)transaction {NSString* productIdentifier = transaction.payment.productIdentifier;//? ? NSString * receipt = [transaction.transactionReceipt base64EncodedString];if([productIdentifier length] >0) {// 向自己的服務(wù)器驗證購買憑證}// Remove the transaction from the payment queue.[[SKPaymentQueue defaultQueue] finishTransaction: transaction];}// 交易失敗- (void)failedTransaction:(SKPaymentTransaction *)transaction {if(transaction.error.code!= SKErrorPaymentCancelled) {NSLog(@"購買失敗");? ? }else{NSLog(@"用戶取消交易");? ? }? ? [[SKPaymentQueue defaultQueue] finishTransaction: transaction];}// 已購商品- (void)restoreTransaction:(SKPaymentTransaction *)transaction {// 對于已購商品,處理恢復(fù)購買的邏輯[[SKPaymentQueue defaultQueue] finishTransaction: transaction];}
服務(wù)器驗證憑證(Optional)。如果購買成功,我們需要將憑證發(fā)送到服務(wù)器上進行驗證。考慮到網(wǎng)絡(luò)異常情況,iOS端的發(fā)送憑證操作應(yīng)該進行持久化,如果程序退出,崩潰或網(wǎng)絡(luò)異常,可以恢復(fù)重試。