iOS開發-微信/支付寶/內購封裝

  • 一句話集成微信支付,支付寶支付以及內購
    可以在github上下載的工具類

源碼

  • 如何使用

1.將工具類拖入工程
2.AppDelegate代碼如下:

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
        
    return [[XNPayManager sharedPayManager] handleOpenURL:url];
}


// NOTE: 9.0以后使用新API接口
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options
{
    return [[XNPayManager sharedPayManager] handleOpenURL:url];
}

3.在使用的地方調起微信或者支付寶

  • 支付寶

TIPS:拼裝參數應在商戶服務端完成,商戶服務端直接將組裝和簽名后的請求串orderString傳給客戶端,客戶端直接傳給SDK發起請求。文檔和Demo是為了示例效果在客戶端實現。所以我們項目中應該按照次操作

  // params應該是由后臺返回的
 [[XNPayManager sharedPayManager] ALIPayWithPayParams: params success:^(PayCode code) {
                    // 成功業務邏輯
                    DLog(@"成功");
  } failure:^(PayCode code) {
                    // 失敗業務邏輯
                    DLog(@"失敗");
 }]

  • 微信支付
  // dict應該是由后臺返回的
  [[XNPayManager sharedPayManager] WXPayWithPayDict: dict success:^(PayCode code) {
                    // 成功業務邏輯
                    DLog(@"成功");
  } failure:^(PayCode code) {
                    // 失敗業務邏輯
                    DLog(@"成功");
 }];

  • 內購
//productId 產品唯一標示
        [[XNPayManager sharedPayManager] requestProductData: productId success:^(PayCode code) {
           // 成功業務邏輯
        } failure:^(PayCode code) {
            // 失敗業務邏輯
        }];

  • 詳細代碼

在封裝的代碼中沒有使用通知,因為通知是全局的,然后在項目中出現了一下問題,比如:待支付訂單->訂單詳情 這兩個頁面都有支付的操作.如果使用通知:這兩個控制器支付成功\失敗的業務邏輯就會沖突

1.聲明支付單例

+ (instancetype)sharedPayManager {
    static dispatch_once_t onceToken;
    static XNPayManager *instance;
    dispatch_once(&onceToken, ^{
        instance = [[XNPayManager alloc] init];
    });
    return instance;
}

2.自定義支付狀態碼,其實也可以不用定義

/**
 支付狀態碼
 */
typedef NS_ENUM(NSInteger, PayCode) {
    WXSUCESS            = 1001,   /**成功*/
    WXERROR             = 1002,   /**失敗*/
    WXSCANCEL           = 1003,   /**取消*/
    
    ALIPAYSUCESS        = 1101,   /**支付寶支付成功*/
    ALIPAYERROR         = 1102,   /**支付寶支付錯誤*/
    ALIPAYCANCEL        = 1103,   /**支付寶支付取消*/
    
    APPSTOREPAYSUCESS   = 1201,   /**內購支付成功*/
    APPSTOREPAYERROR    = 1201,   /**內購支付成功*/
    APPSTOREPAYCANCEL   = 1201,   /**內購支付成功*/
};

3.微信支付

/**
 微信支付
 
 @param dict 微信訂單字典(全部由后臺拼接好給iOS端)
 @param successBlock 成功的回調
 @param failBolck 失敗的回調
 */
- (void)WXPayWithPayDict:(NSDictionary *)dict
                 success:(void(^)(PayCode code)) successBlock
                 failure:(void(^)(PayCode code)) failBolck {
    self.successBlock = successBlock;
    self.failBolck = failBolck;
    NSString *strMsg = nil;
    //1. 判斷是否安裝微信
    if (![WXApi isWXAppInstalled]) {
        NSLog(@"您尚未安裝\"微信App\",請先安裝后再返回支付");
        strMsg = @"您尚未安裝\"微信App\",請先安裝后再返回支付";
        [self tipMessageAlert:nil message:strMsg];
        return;
    }
    
    //2.判斷微信的版本是否支持最新Api
    if (![WXApi isWXAppInstalled]) {
        NSLog(@"您微信當前版本不支持此功能,請先升級微信應用");
        strMsg = @"您微信當前版本不支持此功能,請先升級微信應用";
        [self tipMessageAlert:nil message:strMsg];
        return;
    }
    
    if (!kDictIsEmpty(dict)) {
        
        //調起微信支付
        PayReq *req = [[PayReq alloc]init];
        req.openID = dict[@"appid"];
        req.partnerId = dict[@"partnerid"];
        req.prepayId = dict[@"prepayid"];
        req.nonceStr = dict[@"noncestr"];
        req.timeStamp = [dict[@"timestamp"] intValue];
        req.package = @"Sign=WXPay";
        req.sign = dict[@"sign"];
        [WXApi sendReq:req];
    }

}

#pragma mark - WXApiDelegate
//支付結果回調
/// - see [支付結果回調](https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_5)
- (void)onResp:(BaseResp *)resp {
    
    NSString *strMsg = [NSString stringWithFormat:@"errcode:%d",resp.errCode];
    
    //回調中errCode值列表:
    // 0 成功 展示成功頁面
    //-1 錯誤 可能的原因:簽名錯誤、未注冊APPID、項目設置APPID不正確、注冊的APPID與設置的不匹配、其他異常等
    //-2 用戶取消 無需處理。發生場景:用戶不支付了,點擊取消,返回APP
    
    if ([resp isKindOfClass:[PayResp class]]) {
        // 支付返回結果,實際支付結果需要去微信服務器端查詢
        switch (resp.errCode) {
            case WXSuccess:{
                strMsg = @"支付結果:成功!";
                self.successBlock(WXSUCESS);
                DLog(@"支付成功-PaySuccess,retcode = %d", resp.errCode);
                break;
            }
            case WXErrCodeUserCancel:{
                strMsg = @"支付結果:取消";
                self.failBolck(WXSCANCEL);
                DLog(@"支付取消-PayCancel,retcode = %d", resp.errCode);
            }
            default:{
                strMsg = @"支付結果:失敗";
                self.failBolck(WXERROR);
                DLog(@"錯誤,retcode = %d, retstr = %@", resp.errCode,resp.errStr);
                break;
            }
        }
        [self tipMessageAlert:@"支付結果" message:strMsg];
    }
    
}

4.支付寶

/**
 支付寶支付
 
 @param params 支付寶支付參數(全部由后臺拼接好給iOS端)
 @param successBlock 成功的回調
 @param failBolck 失敗的回調
 */
- (void)ALIPayWithPayParams:(NSString *)params
                    success:(void(^)(PayCode code)) successBlock
                    failure:(void(^)(PayCode code)) failBolck {
    
    self.successBlock = successBlock;
    self.failBolck = failBolck;
    NSString *appScheme = @"appScheme";
    [[AlipaySDK defaultService] payOrder:params fromScheme:appScheme callback:^(NSDictionary *resultDic) {
        DLog(@"我這里是payVC%@",resultDic);
        DLog(@"%@",resultDic[@"memo"]);
        [self aliPayResult:resultDic];
    }];


}

#pragma mark - 支付寶支付結果處理

- (void)aliPayResult:(NSDictionary *)resultDic {
    
    // 返回結果需要通過 resultStatus 以及 result 字段的值來綜合判斷并確定支付結果。 在 resultStatus=9000,并且 success="true"以及 sign="xxx"校驗通過的情況下,證明支付成功。其它情況歸為失敗。較低安全級別的場合,也可以只通過檢查 resultStatus 以及 success="true"來判定支付結果
    NSInteger resultStatus = [resultDic[@"resultStatus"] integerValue];
    
    if (resultDic && [resultDic objectForKey:@"resultStatus"]) {
        switch (resultStatus) {
            case 9000:
                [self tipMessageAlert:@"支付結果" message:@"訂單支付成功"];
                self.successBlock(ALIPAYSUCESS);
                break;
            case 8000:
                [self tipMessageAlert:@"支付結果" message:@"正在處理中"];
                self.failBolck(ALIPAYERROR);
                break;
            case 4000:
                [self tipMessageAlert:@"支付結果" message:@"訂單支付失敗,請稍后再試"];
                self.failBolck(ALIPAYERROR);
                break;
            case 6001:
                [self tipMessageAlert:@"支付結果" message:@"已取消支付"];
                self.failBolck(ALIPAYCANCEL);
                break;
            case 6002:
                [self tipMessageAlert:@"支付結果" message:@"網絡連接錯誤,請稍后再試"];
                self.failBolck(ALIPAYERROR);
                break;
            default:
                break;
        }
    }
    
}

5.內購

/**
 內購
 
 @param productId productId
 @param successBlock 成功的回調
 @param failBolck 失敗的回調
 */
- (void)requestProductData:(NSString *)productId
                   success:(void(^)(PayCode code)) successBlock
                   failure:(void(^)(PayCode code)) failBolck {

    if ([SKPaymentQueue canMakePayments]) {
        self.successBlock = successBlock;
        self.failBolck = failBolck;
        // 最好設置上
        MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:kKeyWindow animated:YES];
        hud.labelText = @"正在購買,請勿離開...";
        hud.dimBackground = YES;
        self.productId = productId;
        NSArray *productArr = [[NSArray alloc]initWithObjects:productId, nil];
        
        NSSet *productSet = [NSSet setWithArray:productArr];
        
        SKProductsRequest *request = [[SKProductsRequest alloc]initWithProductIdentifiers:productSet];
        
        request.delegate = self;
        
        [request start];
        
    }else{
        DLog(@"不允許程序內付費");
    }

}


#pragma mark - SKProductsRequestDelegate
// 收到產品返回信息
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    
    
    NSArray *productArr = response.products;
    
    if ([productArr count] == 0) {
        DLog(@"沒有該商品");
        [MBProgressHUD hideAllHUDsForView:kKeyWindow animated:YES];
        return;
    }
    
    DLog(@"productId = %@",response.invalidProductIdentifiers);
    DLog(@"產品付費數量 = %zd",productArr.count);
    
    SKProduct *p = nil;
    
    for (SKProduct *pro in productArr) {
        DLog(@"description:%@",[pro description]);
        DLog(@"localizedTitle:%@",[pro localizedTitle]);
        DLog(@"localizedDescription:%@",[pro localizedDescription]);
        DLog(@"price:%@",[pro price]);
        DLog(@"productIdentifier:%@",[pro productIdentifier]);
        if ([pro.productIdentifier isEqualToString:self.productId]) {
            p = pro;
        }
    }
    
    SKPayment *payment = [SKPayment paymentWithProduct:p];
    
    //發送內購請求
    [[SKPaymentQueue defaultQueue] addPayment:payment];
    
}

#pragma mark - SKRequestDelegate

- (void)requestDidFinish:(SKRequest *)request {
    
    DLog(@"獲取產品成功");
//    [MBProgressHUD hideHUDForView:kKeyWindow animated:YES];
    
    
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
    
    [MBProgressHUD hideHUDForView:kKeyWindow animated:YES];
    
    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:kKeyWindow animated:YES];
    hud.labelText = @"購買失敗";
    hud.mode = MBProgressHUDModeText;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [MBProgressHUD hideHUDForView:kKeyWindow animated:YES];
    });
    
}



// 監聽購買結果
//SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
    
    
    for (SKPaymentTransaction *tran in transactions) {
        switch (tran.transactionState) {
            case SKPaymentTransactionStatePurchased: //交易完成
                [self completeTransaction:tran];
                // Remove the transaction from the payment queue.
                [[SKPaymentQueue defaultQueue] finishTransaction: tran];
                break;
            case SKPaymentTransactionStatePurchasing: //商品添加進列表
                break;
            case SKPaymentTransactionStateRestored: //購買過
                [self restoreTransaction:tran];
                break;
            case SKPaymentTransactionStateFailed: //交易失敗
                [self failedTransaction:tran];
                // Remove the transaction from the payment queue.
                [[SKPaymentQueue defaultQueue] finishTransaction: tran];
                break;
                
            default:
                break;
        }
    }
}

- (void)completeTransaction:(SKPaymentTransaction *)transaction {
    NSString * productIdentifier = transaction.payment.productIdentifier;
    DLog(@"productIdentifier Product id:%@", productIdentifier);
    NSString *transactionReceiptString= nil;
    
    //系統IOS7.0以上獲取支付驗證憑證的方式應該改變,切驗證返回的數據結構也不一樣了。
    
    // 驗證憑據,獲取到蘋果返回的交易憑據
    // appStoreReceiptURL iOS7.0增加的,購買交易完成后,會將憑據存放在該地址
    NSURL *receiptUrl=[[NSBundle mainBundle] appStoreReceiptURL];
    NSData * receiptData = [NSData dataWithContentsOfURL:receiptUrl];
    
    transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    
    DLog(@"transactionReceiptString == %@",transactionReceiptString);
    
    if ([productIdentifier length] > 0) {

        self.hud.labelText = @"正在驗證,請勿離開...";

         // 請求自己的服務器去驗證用戶購買結果
        // 往后臺驗證,只有服務器有反應就hide
        [MBProgressHUD hideAllHUDsForView:kKeyWindow animated:YES];
    }


}
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
    if(transaction.error.code != SKErrorPaymentCancelled) {
        DLog(@"購買失敗");
        self.failBolck(APPSTOREPAYCANCEL);
    } else {
        DLog(@"用戶取消交易");
        self.failBolck(APPSTOREPAYCANCEL);
    }
    [MBProgressHUD hideHUDForView:kKeyWindow animated:YES];
    [SMGlobalMethod showViewCenter:kKeyWindow.center longMessage:@"購買失敗"];
}

- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
    // 對于已購商品,處理恢復購買的邏輯
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}


6.單例回調

#pragma mark - 單例類回調
- (BOOL)handleOpenURL:(NSURL *)url {
    
    if ([url.host isEqualToString:@"safepay"])
    {
        //跳轉支付寶錢包進行支付,處理支付結果
        [[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
            DLog(@"result = %@",resultDic);
            DLog(@"openURL : 支付寶回調 : result = %@",resultDic);
            [self aliPayResult:resultDic];
        }];
        // 授權跳轉支付寶錢包進行支付,處理支付結果
        [[AlipaySDK defaultService] processAuth_V2Result:url standbyCallback:^(NSDictionary *resultDic) {
            NSLog(@"result = %@",resultDic);
            // 解析 auth code
            NSString *result = resultDic[@"result"];
            NSString *authCode = nil;
            if (result.length>0) {
                NSArray *resultArr = [result componentsSeparatedByString:@"&"];
                for (NSString *subResult in resultArr) {
                    if (subResult.length > 10 && [subResult hasPrefix:@"auth_code="]) {
                        authCode = [subResult substringFromIndex:10];
                        break;
                    }
                }
            }
            DLog(@"授權結果 authCode = %@", authCode?:@"");
        }];
        
        return [url.host isEqualToString:@"safepay"];
    }
    else
    {
        return [WXApi handleOpenURL:url delegate:self];
    }
    
}

參考博客:

iOS支付封裝(微信/支付寶)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,414評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,169評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,722評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,465評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,823評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,000評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,554評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,295評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,513評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,722評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,125評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,430評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,237評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,482評論 2 379

推薦閱讀更多精彩內容