【IOS】 In App Purchase 内置购买 总结
发布日期:2021-06-28 22:16:19 浏览次数:2 分类:技术文章

本文共 34108 字,大约阅读时间需要 113 分钟。

上一个项目用到了In App Purchase,发现现在大家对这个挺关注的,把上次写的总结贴出来给大家看一下,希望对大家有点帮助!

两种方案

一, 我的程序具体步骤
1 添加Storekit.Framework,编写自己的storeObsever,用于处理交易,代码如下,其中completeTransaction和failedTransaction两个函数是自定义的用来处理交易成功与失败其它的就都是SKPaymentTransactionObserver这个代理要求的。

代码
 #import <Foundation/Foundation.h> 
#import <StoreKit/StoreKit.h> 
 #import <StoreKit/SKPaymentTransaction.h> 
@interface MyStoreObserver : NSObject < SKPaymentTransactionObserver > {
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions; 
-(void) PurchasedTransaction: (SKPaymentTransaction *)transaction; 
- (void) completeTransaction: (SKPaymentTransaction *)transaction; 
- (void) failedTransaction: (SKPaymentTransaction *)transaction;
 -(void) paymentQueueRestoreCompletedTransactionsFinished: (SKPaymentTransaction *)transaction;
 -(void) paymentQueue:(SKPaymentQueue *) paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error; 
@end 

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions函数用来更新transactions的状态
 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { 
 for (SKPaymentTransaction* transaction in transactions)  {  
switch (transaction.transactionState)  {  
case SKPaymentTransactionStatePurchased:  
[self completeTransaction:transaction];  
break;  
case SKPaymentTransactionStateFailed: 
 [self failedTransaction:transaction];  
break;  
case SKPaymentTransactionStateRestored: 
 break;  
default:  
break;  
}  
2 在程序中添加storeObsever,最好在applicationDidFinishLaunching中添加

MyStoreObserver *tempObserver = [[MyStoreObserver alloc] init];  
self.observer = tempObserver;  
[tempObserver release];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self.observer]; 

3 发送付费请求,在相应的类中实现SKProductsRequestDelegate,别忘记定义自己产品的identifier

//请求产品信息 
 #define kMyFeatureIdentifier 
yourProductIdentifiers
  (你自己的产品identifiers)
SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers: 
 [NSSet setWithObject: kMyFeatureIdentifier]]; 
 request.delegate = self; 
[request start];  
代理方法相关代码,如果请求成功的话就可以发送付费请求  
#pragma mark request delegate
 //!收到产品的消息 
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{  
SKPayment *payment = [SKPayment paymentWithProductIdentifier:kMyFeatureIdentifier]; 
 [[SKPaymentQueue defaultQueue] addPayment:payment]; 
[request autorelease];
  }
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{  
UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:@"Alert" message:[error localizedDescription]  delegate:nil c
ancelButtonTitle:NSLocalizedString(@"Close",nil)  otherButtonTitles:nil];  [alerView show]; [alerView release]; 
}

4  接下来就是Apple自己的付费相关处理了,需要做是就是点击按钮来确定付费

5 交易完成后需要向Apple验证这次交易是否成功,要不然没成功就把产品给别人的话,那就亏了,在1中可以看到当交易成功时会调用自定义的completeTransaction函数,在该函数中我们需要验证transactionReceipt 关于验证SKPaymentTransaction的transactionReceipt transactionReceipt是只有当SKPaymentTransaction完成时,即transactionState 被设置为SKPaymentTransactionStatePurchased 或 SKPaymentTransactionStateRestored时才被创建,因此只有这两种状态下能去验证transactionReceipt


原始思路及具体步骤如下:  
a 从SKPaymentTranscation的实例中将transactionReceipt转化为NSString   
 NSString *temptransactionReceipt  = [[NSString alloc] initWithData:[mytransaction transactionReceipt] encoding:NSUTF8StringEncoding];

如果用NSLog的方法将其写出来显示如下,里边有很多的‘+’ { "signature" = 
"AZNZdoggtjbU/wMqZ4SSd3lgkxbWr+/zcV7Oez4io7f5oPMliKlQzWW4vj+FLsVyhjyyuPyTSugJ6m4Hrp+CjdAptGZg4iWExoyE6stltg0EfD8Ezggjg5q04ws74pMZ/0aRgjedua8dCMMqR7C8ZjojfOYU6LrFiK7qbUUiV+inMIIDUzCCAjugAwIBAgIIZRSRTdlYBLUwDQYJKoZIhvcNAQEFBQAwfzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkFwcGxlIEluYy4xJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MTMwMQYDVQQDDCpBcHBsZSBpVHVuZXMgU3RvcmUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDkwNjE1MjIwNTU2WhcNMTQwNjE0MjIwNTU2WjBkMSMwIQYDVQQDDBpQdXJjaGFzZVJlY2VpcHRDZXJ0aWZpY2F0ZTEbMBkGA1UECwwSQXBwbGUgaVR1bmVzIFN0b3JlMRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAytGMXZy3gitJ2JMKFojSDynC/9yYezyn9HBX+u3/3VcpWE2XhcgGKYqNBA1+AewOzrKO774OsokTu4qymEx10ph8UTmsZewB0ESMHBEjF7FN6/HccsQUYC3WagrHnT12HG2Ih0OAm/ZhpWzj0HS4m813LpIyo00sewMvMNL2hkcCAwEAAaNyMHAwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBQ2HejinYLSARi1MmsO10MLkVhDOjAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFKmDg/IZSMU+ElcIFMzNo36ZXyT1MBAGCiqGSIb3Y2QGBQEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQARpJs+O2Y3gL8gHdASkrfZHFpwINd1VcB5VF5LkVpnFz63zylA/3cGIDG91b/d5NIwZjkVt4Bgvd62o/mCbzCsWiNfSKTJVFK1D78BDQoSO2oHTuQuz1BR7xzNHxQZ90zUS6ZX9SC8N3g3A1jEtAyDhZNB+CRBBXLwZdnBUeBsT9QLpjvTnekZcGTnU08zfCjGF3eBJEu9eP6WgexK1xMSp72kEOmYbn6yTi3D4YrcYx4Q3n/57VBP2en8qXWeP5oHDsLTGzLRsWdoB3VxJLrF2ivL8JS8zqC0qyac452pN6xunRuzyyfpaqzQL12BzFEe44xna2byektSbtquA5LNAAAAAA=="; "purchase-info" = "ewoJIml0ZW0taWQiID0gIjMzMDU5OTg4MCI7Cgkib3JpZ2luYWwtdHJhbnNhY3Rpb24taWQiID0gIjEwMDAwMDAwMDAwOTEyNTgiOwoJInB1cmNoYXNlLWRhdGUiID0gIjIwMDktMTAtMTQgMDY6MDY6NTQgRXRjL0dNVCI7CgkicHJvZHVjdC1pZCIgPSAiY29tLnNlbnNreS5jbmFtZXNpZ3B1cmNoYXNlY29uc3VtYWJsZSI7CgkidHJhbnNhY3Rpb24taWQiID0gIjEwMDAwMDAwMDAwOTEyNTgiOwoJInF1YW50aXR5IiA9ICIxIjsKCSJvcmlnaW5hbC1wdXJjaGFzZS1kYXRlIiA9ICIyMDA5LTEwLTE0IDA2OjA2OjU0IEV0Yy9HTVQiOwoJImJpZCIgPSAiY29tLnNlbnNreS5jc2lnbmF0dXJlYXBwIjsKCSJidnJzIiA9ICIxLjAiOwp9"; "pod" = "100"; "signing-status" = "0";}    
b然后通过Post的方法将其提交给服务器,在这里只是将transactionReceipt传给服务器,验证由服务器完成。代码如下,    
 NSString *requestStirng =[NSString stringWithFormat: @"receipt_data=%@",temptransactioReceipt];  

requestStirng = [requestStirng stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 

 NSData *postData = [NSData dataWithBytes:[requestStirng UTF8String] length:[requestStirng length]]; 
NSMutableURLRequest *connectionRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[kURL          stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]; 
 [connectionRequest setHTTPMethod:@"POST"];  
[connectionRequest setTimeoutInterval:120.0]; 
 [connectionRequest setCachePolicy:NSURLRequestUseProtocolCachePolicy]; 
 [connectionRequest setHTTPBody:postData];  
出现的错误,服务器用传过去的receipt_data向Apple验证,通不过,但将用NSLog的方法在后台写出的temptransactionreceipt向Apple验证能通过  错误原因  对比发现通过URL传给服务器的字符串不能很好的完成urlEncode,在里边出现的+不能自动进行编码转化,因为在Objective-C 中不能将NSString 真正的实现URL encode , 
解决方法可参考下面的地址  
http://simonwoodside.com/weblog/2009/4/22/how_to_really_url_encode/ 
 但当我用上面地址给出的方法进行编码后仍不能解决该问题,原因不明 
 解决办法:
在将transactionReceipt转化成的NSString传给服务器之前先进行转化,将里边的+转换为%2B , 将步骤a中的代码改为 
 NSString *temptransactionReceipt  = [[NSString alloc] initWithData:[mytransaction transactionReceipt] encoding:NSUTF8StringEncoding]; 
 temptransactionReceipt =  [temptransactionReceipt stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"];  


6 服务器端验证相关代码 其中XML为自定义的一个类,getHeader所做的工作就是把receipt数据Post给Apple然后得到返回的结果返回  
$url = "https://buy.itunes.apple.com/verifyReceipt";/ $receipt = json_encode(array("receipt-data" => base64_encode($receipt_data)));     
$response_json = $Xml->getHeader($url, $receipt);    
 $response = json_decode($response_json['content'], true);

getHeader的代码如下  public function getHeader($url, $data)         { 
                   $ch = curl_init(); 
                    $timeout = 300; // set to zero for no timeout  
                 curl_setopt($ch, CURLOPT_URL, $url); 
         //       curl_setopt($ch, CURLOPT_ENCODING, 'gzip'); 
                  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1); //post到https 
                  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
                   curl_setopt($ch, CURLOPT_POST, true); 
                  curl_setopt($ch, CURLOPT_POSTFIELDS, $data);  
                 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  
                 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);//跟随页面的跳转
         //       curl_setopt($ch, CURLOPT_HEADER, true); 
                  curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); 
                   $handles = curl_exec($ch); 
                  $header = curl_getinfo($ch);
                   curl_close($ch); 
                  $header['content'] = $handles; 
                  return $header; 
        } 


二 。项目源码

//

//  IAPSecond.h

//  SpritePetText026Test

//

//  Created by luonan on 13-12-28.

//

//

#import "cocos2d.h"

USING_NS_CC;

#import <StoreKit/StoreKit.h>

@interface APPPay : NSObject<SKProductsRequestDelegate,SKPaymentTransactionObserver>

{

 @public

    int buyType;

}

+ (APPPay *) sharedHelper;

//+(CCScene *) scene;

- (void) requestProUpgradeProductData;

-(void)RequestProductData;

-(bool)CanMakePay;

-(void)buy:(int)type;

-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;//重载

-(void) PurchasedTransaction: (SKPaymentTransaction *)transaction;

- (void) completeTransaction: (SKPaymentTransaction *)transaction;

- (void) failedTransaction: (SKPaymentTransaction *)transaction;

-(void) paymentQueueRestoreCompletedTransactionsFinished: (SKPaymentTransaction *)transaction;

-(void) paymentQueue:(SKPaymentQueue *) paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error;

- (void) restoreTransaction: (SKPaymentTransaction *)transaction;

-(void)provideContent:(NSString *)product;

-(void)recordTransaction:(NSString *)product;

-(BOOL)putStringToItunes:(NSData*)iapData;

@end

//

//  IAPSecond.m

//  SpritePetText026Test

//

//  Created by luonan on 13-12-28.

//

//

//

#import "IAPSecond.h"

#include "../../MainView.h"

#include "../../ResManager.h"

#include "../../constants/StringConstant.h"

#include "../../socket/message/NetWaitManager.h"

#if defined(NXPetforTw)

#include "../../channel/appstore/IAPSecond.h"

#else//除了Tw其他都接入TD

#include "TDCCAccount.h"

#include "TalkingDataGA.h"

#endif

enum{

    gpmjewel45=0,

    gpmjewel450,

    gpmjewel750,

    gpmjewel1450,

    gpmjewel2550,

    gpmjewel3600,

    gpmjewel5400,

    

}buyCoinsTag;

#if defined (SpritePetText026Test)

//#define ProductID_gpmjewel1450 @"gpm_jewel_1450"//900

//#define ProductID_gpmjewel2550 @"gpm_jewel_2550" //1500

//#define ProductID_gpmjewel3600 @"gpm_jewel_3600" //2100

//#define ProductID_gpmjewel450     @"gpm_jewel_450" //$300

//#define ProductID_gpmjewel45     @"gpm_jewel_45" //$30

//#define ProductID_gpmjewel5400 @"gpm_jewel_5400" //3000

//#define ProductID_gpmjewel1750 @"gpm_jewel_750"//450

#define ProductID_gpmjewel1450 @"gpmjewel1450"//900

#define ProductID_gpmjewel2550 @"gpmjewel2550" //1500

#define ProductID_gpmjewel3600 @"gpmjewel3600" //2100

#define ProductID_gpmjewel450     @"gpmjewel450" //$300

#define ProductID_gpmjewel45     @"gpmjewel45" //$30

#define ProductID_gpmjewel5400 @"gpmjewel5400" //3000

#define ProductID_gpmjewel1750 @"gpmjewel750"//450

#else

#define ProductID_gpmjewel1450 @"gpmjewel1450"//900

#define ProductID_gpmjewel2550 @"gpmjewel2550" //1500

#define ProductID_gpmjewel3600 @"gpmjewel3600" //2100

#define ProductID_gpmjewel450     @"gpmjewel450" //$300

#define ProductID_gpmjewel45     @"gpmjewel45" //$30

#define ProductID_gpmjewel5400 @"gpmjewel5400" //3000

#define ProductID_gpmjewel1750 @"gpmjewel750"//450

#endif

USING_NS_CC;

static APPPay * _sharedHelper;

@implementation APPPay

+ (APPPay *) sharedHelper {

    

    if (_sharedHelper != nil) {

       //  [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

        return _sharedHelper;

    }

    _sharedHelper = [APPPay alloc];

    return _sharedHelper;

    

}

//+(CCScene *) scene

//{

//    CCScene *scene = [CCScene node];

//    HelloWorldLayer *layer = [HelloWorldLayer node];

//    [scene addChild: layer];

//    return scene;

//}

-(id)init

{

        return self;

}

-(void)buy:(int)type

{

    buyType = type;

    if ([SKPaymentQueue canMakePayments]) {

        //[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

        [self RequestProductData];

        CCLOG("允许程序内付费购买");

    }

    else

    {

        CCLOG("不允许程序内付费购买");

        string buyerror =  "buyerror";

        

       // MainView::showToast(buyerror);

        UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:@"Alert"

                                                            message:@"You can‘t purchase in app store(没允许应用程序内购买)"

                                                           delegate:nilcancelButtonTitle:NSLocalizedString(@"Close(关闭)",nil) otherButtonTitles:nil];

        

        [alerView show];

        [alerView release];

        

    }

}

-(bool)CanMakePay

{

    return [SKPaymentQueue canMakePayments];

}

-(void)RequestProductData

{

    CCLOG("---------请求对应的产品信息------------");

    NSArray *product = nil;

    switch (buyType) {

        case gpmjewel750:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel1750,nil];

            break;

        case gpmjewel5400:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel5400,nil];

            break;

        case gpmjewel45:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel45,nil];

            break;

        case gpmjewel450:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel450,nil];

            break;

        case gpmjewel3600:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel3600,nil];

            break;

        case gpmjewel2550:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel2550,nil];

            break;

        case gpmjewel1450:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel1450,nil];

            break;

        default:

            break;

    }

    NSSet *nsset = [NSSet setWithArray:product];

    SKProductsRequest *request=[[SKProductsRequest alloc] initWithProductIdentifiers: nsset];

    request.delegate=self;

    [request start];

    [product release];

}

//<SKProductsRequestDelegate> 请求协议

//收到的产品信息

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{

    

    NSLog(@"-----------收到产品反馈信息--------------");

    NSArray *myProduct = response.products;

    NSLog(@"产品Product ID:%@",response.invalidProductIdentifiers);

    NSLog(@"产品付费数量: %d", [myProduct count]);

    // populate UI

    for(SKProduct *product in myProduct){

        NSLog(@"product info");

        NSLog(@"SKProduct 描述信息%@", [product description]);

        NSLog(@"产品标题 %@" , product.localizedTitle);

        NSLog(@"产品描述信息: %@" , product.localizedDescription);

        NSLog(@"价格: %@" , product.price);

        NSLog(@"Product id: %@" , product.productIdentifier);

    }

    

    

//     [TDGAVirtualCurrency onChargeRequst:"@sss" iapId:"@aaa" currencyAmount:1000 currencyType:"@dddd" virtualCurrencyAmount:100 paymentType:"@qqqq" ];

    

    SKPayment *payment = nil;

    switch (buyType) {

        case gpmjewel1450:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel1450];    //支付$0.99

            break;

        case gpmjewel2550:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel2550];    //支付$1.99

            break;

        case gpmjewel3600:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel3600];    //支付$9.99

            break;

        case gpmjewel45:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel45];    //支付$19.99

            break;

            

        case gpmjewel450:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel450];    //支付$19.99

            break;

        case gpmjewel5400:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel5400];    //支付$29.99

            break;

        case gpmjewel750:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel1750];    //支付$29.99

            break;

            

        default:

            break;

    }

    CCLOG("---------发送购买请求------------");

    [[SKPaymentQueue defaultQueue] addPayment:payment];

    [request autorelease];

    

}

- (void)requestProUpgradeProductData

{

    CCLOG("------请求升级数据---------");

    NSSet *productIdentifiers = [NSSet setWithObject:ProductID_gpmjewel1450];

    SKProductsRequest* productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];

    productsRequest.delegate = self;

    [productsRequest start];

    

}

//弹出错误信息

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{

    CCLOG("-------弹出错误信息----------");

NetWaitManager::getInstance()->endNormalWaitting();

    UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Alert",NULL) message:[error localizedDescription]

                                                       delegate:nilcancelButtonTitle:NSLocalizedString(@"Close",nil) otherButtonTitles:nil];

    [alerView show];

    [alerView release];

}

-(void) requestDidFinish:(SKRequest *)request

{

    NSLog(@"----------反馈信息结束--------------");

    

}

-(void) PurchasedTransaction: (SKPaymentTransaction *)transaction{

    CCLOG("-----PurchasedTransaction----");

    NSArray *transactions =[[NSArray alloc] initWithObjects:transaction, nil];

    [self paymentQueue:[SKPaymentQueue defaultQueue] updatedTransactions:transactions];

    [transactions release];

}

//<SKPaymentTransactionObserver> 千万不要忘记绑定,代码如下:

//----监听购买结果

//[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions//交易结果

{

NetWaitManager::getInstance()->endNormalWaitting();

    CCLOG("-----paymentQueue--------");

    for (SKPaymentTransaction *transaction in transactions)

    {

        switch (transaction.transactionState)

        {

//                NSData* d = transaction.transactionReceipt;

                

            case SKPaymentTransactionStatePurchased://交易完成

            {

                [self completeTransaction:transaction];

                CCLOG("-----交易完成 --------");

                CCLOG("不允许程序内付费购买");

               

               

                if([self putStringToItunes:transaction.transactionReceipt]){

                    //这里给用户添加金币,装备

                   

                    

                }

//                string chargeOk =ResManager::getInstance()->getString(StringConstant::chargeOk);

//                MainView::showToast(chargeOk);

                UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:@"Alert"

                                                                    message:@"購買成功啦"

                                                                   delegate:nilcancelButtonTitle:NSLocalizedString(@"Close(關閉)",nil) otherButtonTitles:nil];

                

                [alerView show];

                

                

                CCLOG("order haved call on ");

                [alerView release];

            }

                

                break;

            case SKPaymentTransactionStateFailed://交易失败

            {

                [self failedTransaction:transaction];

                CCLOG("-----交易失败 --------");

             

                

                string chargeError = ResManager::getInstance()->getString(StringConstant::chargeError);

                MainView::showToast(chargeError);

//                UIAlertView *alerView2 =  [[UIAlertView alloc] initWithTitle:@"Alert"

//                                                                     message:@"購買失敗,請重新嘗試購買~"

//                                                                    delegate:nil cancelButtonTitle:NSLocalizedString(@"Close(關閉)",nil) otherButtonTitles:nil];

//                

//                [alerView2 show];

//                [alerView2 release];

            }

                

                break;

            case SKPaymentTransactionStateRestored://已经购买过该商品

            {

                [self restoreTransaction:transaction];

                CCLOG("-----已经购买过该商品 --------");

                MainView::showToast("buyed ");

            }

               

            case SKPaymentTransactionStatePurchasing:      //商品添加进列表

                CCLOG("-----商品添加进列表 --------");

                break;

            default:

                break;

        }

    }

}

- (void) completeTransaction: (SKPaymentTransaction *)transaction

{

    CCLOG("-----completeTransaction--------");

    // Your application should implement these two methods.

    NSString *product = transaction.payment.productIdentifier;

    if ([product length] > 0) {

        

        NSArray *tt = [product componentsSeparatedByString:@"."];

        NSString *bookid = [tt lastObject];

        if ([bookid length] > 0) {

            [self recordTransaction:bookid];

            [self provideContent:bookid];

        }

    }

    

    // Remove the transaction from the payment queue.

    

    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

    

}

//记录交易

-(void)recordTransaction:(NSString *)product{

    CCLOG("-----记录交易--------");

}

//处理下载内容

-(void)provideContent:(NSString *)product{

    CCLOG("-----下载--------");

}

- (void) failedTransaction: (SKPaymentTransaction *)transaction{

    NSLog(@"失败");

    if (transaction.error.code != SKErrorPaymentCancelled)

    {

    }

    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

    

}

-(void) paymentQueueRestoreCompletedTransactionsFinished: (SKPaymentTransaction *)transaction{

    

}

- (void) restoreTransaction: (SKPaymentTransaction *)transaction

{

    NSLog(@" 交易恢复处理");

    

}

-(void) paymentQueue:(SKPaymentQueue *) paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error{

    CCLOG("-------paymentQueue----");

}

#pragma mark connection delegate

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data

{

    NSLog(@"%@",  [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);

}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{

    

}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{

    switch([(NSHTTPURLResponse *)response statusCode]) {

        case 200:

        case 206:

            break;

        case 304:

            break;

        case 400:

            break;

        case 404:

            break;

        case 416:

            break;

        case 403:

            break;

        case 401:

        case 500:

            break;

        default:

            break;

    }

}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {

    NSLog(@"test");

}

-(void)dealloc

{

    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];//解除监听

    [super dealloc];

}

-(BOOL)putStringToItunes:(NSData*)iapData{//用户购成功的transactionReceipt

    

  

    

    

    

    NSString* adStr= [[NSString alloc] initWithData:iapData  encoding://NSASCIIStringEncoding

                      NSUTF8StringEncoding];

    NSLog(@"adStr: %@",adStr);

//    NSString*encodingStr = [iapData base64EncodedString];

//  

//    NSLog(@"encodingStr: %@",encodingStr);

    const char* destDir = [adStr UTF8String];

    

    //CCUserDefault::sharedUserDefault()->setStringForKey("charge", destDir);

    NetDataSend::getInstance()->sendIOSCharge(destDir);

    

return true;

/**

    NSString *URL=@"";

    // 测试

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];// autorelease];

    [request setURL:[NSURL URLWithString:URL]];

    [request setHTTPMethod:@"POST"];

    //设置contentType

    [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    //设置Content-Length

    [request setValue:[NSString stringWithFormat:@"%d", [encodingStr length]] forHTTPHeaderField:@"Content-Length"];

    

    NSDictionary* body = [NSDictionary dictionaryWithObjectsAndKeys:encodingStr, @"receipt-data", nil];

    SBJsonWriter *writer = [SBJsonWriter new];

    [request setHTTPBody:[[writer stringWithObject:body] dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]];

    NSHTTPURLResponse *urlResponse=nil;

    NSError *errorr=nil;

    NSData *receivedData = [NSURLConnection sendSynchronousRequest:request

                                                 returningResponse:&urlResponse

                                                             error:&errorr];

    

    //解析

    NSString *results=[[NSString alloc]initWithBytes:[receivedData bytes] length:[receivedData length] encoding:NSUTF8StringEncoding];

    

    NSLog(@"yhh; %@",results);

    NSDictionary*dic = [results JSONValue];

    if([[dic objectForKey:@"status"] intValue]==0){//注意,status=@"0" 是验证收据成功

        return true;

    }

    return false;**/

}

@end

参考

http://www.cocoachina.com/bbs/read.php?tid-11357.html

//

//  IAPSecond.h

//  SpritePetText026Test

//

//  Created by luonan on 13-12-28.

//

//

#import "cocos2d.h"

USING_NS_CC;

#import <StoreKit/StoreKit.h>

@interface APPPay : NSObject<SKProductsRequestDelegate,SKPaymentTransactionObserver>

{

 @public

    int buyType;

}

+ (APPPay *) sharedHelper;

//+(CCScene *) scene;

- (void) requestProUpgradeProductData;

-(void)RequestProductData;

-(bool)CanMakePay;

-(void)buy:(int)type;

-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;//重载

-(void) PurchasedTransaction: (SKPaymentTransaction *)transaction;

- (void) completeTransaction: (SKPaymentTransaction *)transaction;

- (void) failedTransaction: (SKPaymentTransaction *)transaction;

-(void) paymentQueueRestoreCompletedTransactionsFinished: (SKPaymentTransaction *)transaction;

-(void) paymentQueue:(SKPaymentQueue *) paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error;

- (void) restoreTransaction: (SKPaymentTransaction *)transaction;

-(void)provideContent:(NSString *)product;

-(void)recordTransaction:(NSString *)product;

-(BOOL)putStringToItunes:(NSData*)iapData;

@end

//

//  IAPSecond.m

//  SpritePetText026Test

//

//  Created by luonan on 13-12-28.

//

//

//

#import "IAPSecond.h"

#include "../../MainView.h"

#include "../../ResManager.h"

#include "../../constants/StringConstant.h"

#include "../../socket/message/NetWaitManager.h"

#if defined(NXPetforTw)

#include "../../channel/appstore/IAPSecond.h"

#else//除了Tw其他都接入TD

#include "TDCCAccount.h"

#include "TalkingDataGA.h"

#endif

enum{

    gpmjewel45=0,

    gpmjewel450,

    gpmjewel750,

    gpmjewel1450,

    gpmjewel2550,

    gpmjewel3600,

    gpmjewel5400,

    

}buyCoinsTag;

#if defined (SpritePetText026Test)

//#define ProductID_gpmjewel1450 @"gpm_jewel_1450"//900

//#define ProductID_gpmjewel2550 @"gpm_jewel_2550" //1500

//#define ProductID_gpmjewel3600 @"gpm_jewel_3600" //2100

//#define ProductID_gpmjewel450     @"gpm_jewel_450" //$300

//#define ProductID_gpmjewel45     @"gpm_jewel_45" //$30

//#define ProductID_gpmjewel5400 @"gpm_jewel_5400" //3000

//#define ProductID_gpmjewel1750 @"gpm_jewel_750"//450

#define ProductID_gpmjewel1450 @"gpmjewel1450"//900

#define ProductID_gpmjewel2550 @"gpmjewel2550" //1500

#define ProductID_gpmjewel3600 @"gpmjewel3600" //2100

#define ProductID_gpmjewel450     @"gpmjewel450" //$300

#define ProductID_gpmjewel45     @"gpmjewel45" //$30

#define ProductID_gpmjewel5400 @"gpmjewel5400" //3000

#define ProductID_gpmjewel1750 @"gpmjewel750"//450

#else

#define ProductID_gpmjewel1450 @"gpmjewel1450"//900

#define ProductID_gpmjewel2550 @"gpmjewel2550" //1500

#define ProductID_gpmjewel3600 @"gpmjewel3600" //2100

#define ProductID_gpmjewel450     @"gpmjewel450" //$300

#define ProductID_gpmjewel45     @"gpmjewel45" //$30

#define ProductID_gpmjewel5400 @"gpmjewel5400" //3000

#define ProductID_gpmjewel1750 @"gpmjewel750"//450

#endif

USING_NS_CC;

static APPPay * _sharedHelper;

@implementation APPPay

+ (APPPay *) sharedHelper {

    

    if (_sharedHelper != nil) {

       //  [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

        return _sharedHelper;

    }

    _sharedHelper = [APPPay alloc];

    return _sharedHelper;

    

}

//+(CCScene *) scene

//{

//    CCScene *scene = [CCScene node];

//    HelloWorldLayer *layer = [HelloWorldLayer node];

//    [scene addChild: layer];

//    return scene;

//}

-(id)init

{

        return self;

}

-(void)buy:(int)type

{

    buyType = type;

    if ([SKPaymentQueue canMakePayments]) {

        //[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

        [self RequestProductData];

        CCLOG("允许程序内付费购买");

    }

    else

    {

        CCLOG("不允许程序内付费购买");

        string buyerror =  "buyerror";

        

       // MainView::showToast(buyerror);

        UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:@"Alert"

                                                            message:@"You can‘t purchase in app store(没允许应用程序内购买)"

                                                           delegate:nilcancelButtonTitle:NSLocalizedString(@"Close(关闭)",nil) otherButtonTitles:nil];

        

        [alerView show];

        [alerView release];

        

    }

}

-(bool)CanMakePay

{

    return [SKPaymentQueue canMakePayments];

}

-(void)RequestProductData

{

    CCLOG("---------请求对应的产品信息------------");

    NSArray *product = nil;

    switch (buyType) {

        case gpmjewel750:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel1750,nil];

            break;

        case gpmjewel5400:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel5400,nil];

            break;

        case gpmjewel45:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel45,nil];

            break;

        case gpmjewel450:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel450,nil];

            break;

        case gpmjewel3600:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel3600,nil];

            break;

        case gpmjewel2550:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel2550,nil];

            break;

        case gpmjewel1450:

            product=[[NSArray alloc] initWithObjects:ProductID_gpmjewel1450,nil];

            break;

        default:

            break;

    }

    NSSet *nsset = [NSSet setWithArray:product];

    SKProductsRequest *request=[[SKProductsRequest alloc] initWithProductIdentifiers: nsset];

    request.delegate=self;

    [request start];

    [product release];

}

//<SKProductsRequestDelegate> 请求协议

//收到的产品信息

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{

    

    NSLog(@"-----------收到产品反馈信息--------------");

    NSArray *myProduct = response.products;

    NSLog(@"产品Product ID:%@",response.invalidProductIdentifiers);

    NSLog(@"产品付费数量: %d", [myProduct count]);

    // populate UI

    for(SKProduct *product in myProduct){

        NSLog(@"product info");

        NSLog(@"SKProduct 描述信息%@", [product description]);

        NSLog(@"产品标题 %@" , product.localizedTitle);

        NSLog(@"产品描述信息: %@" , product.localizedDescription);

        NSLog(@"价格: %@" , product.price);

        NSLog(@"Product id: %@" , product.productIdentifier);

    }

    

    

//     [TDGAVirtualCurrency onChargeRequst:"@sss" iapId:"@aaa" currencyAmount:1000 currencyType:"@dddd" virtualCurrencyAmount:100 paymentType:"@qqqq" ];

    

    SKPayment *payment = nil;

    switch (buyType) {

        case gpmjewel1450:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel1450];    //支付$0.99

            break;

        case gpmjewel2550:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel2550];    //支付$1.99

            break;

        case gpmjewel3600:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel3600];    //支付$9.99

            break;

        case gpmjewel45:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel45];    //支付$19.99

            break;

            

        case gpmjewel450:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel450];    //支付$19.99

            break;

        case gpmjewel5400:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel5400];    //支付$29.99

            break;

        case gpmjewel750:

            payment  = [SKPayment paymentWithProductIdentifier:ProductID_gpmjewel1750];    //支付$29.99

            break;

            

        default:

            break;

    }

    CCLOG("---------发送购买请求------------");

    [[SKPaymentQueue defaultQueue] addPayment:payment];

    [request autorelease];

    

}

- (void)requestProUpgradeProductData

{

    CCLOG("------请求升级数据---------");

    NSSet *productIdentifiers = [NSSet setWithObject:ProductID_gpmjewel1450];

    SKProductsRequest* productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];

    productsRequest.delegate = self;

    [productsRequest start];

    

}

//弹出错误信息

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{

    CCLOG("-------弹出错误信息----------");

NetWaitManager::getInstance()->endNormalWaitting();

    UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Alert",NULL) message:[error localizedDescription]

                                                       delegate:nilcancelButtonTitle:NSLocalizedString(@"Close",nil) otherButtonTitles:nil];

    [alerView show];

    [alerView release];

}

-(void) requestDidFinish:(SKRequest *)request

{

    NSLog(@"----------反馈信息结束--------------");

    

}

-(void) PurchasedTransaction: (SKPaymentTransaction *)transaction{

    CCLOG("-----PurchasedTransaction----");

    NSArray *transactions =[[NSArray alloc] initWithObjects:transaction, nil];

    [self paymentQueue:[SKPaymentQueue defaultQueue] updatedTransactions:transactions];

    [transactions release];

}

//<SKPaymentTransactionObserver> 千万不要忘记绑定,代码如下:

//----监听购买结果

//[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions//交易结果

{

NetWaitManager::getInstance()->endNormalWaitting();

    CCLOG("-----paymentQueue--------");

    for (SKPaymentTransaction *transaction in transactions)

    {

        switch (transaction.transactionState)

        {

//                NSData* d = transaction.transactionReceipt;

                

            case SKPaymentTransactionStatePurchased://交易完成

            {

                [self completeTransaction:transaction];

                CCLOG("-----交易完成 --------");

                CCLOG("不允许程序内付费购买");

               

               

                if([self putStringToItunes:transaction.transactionReceipt]){

                    //这里给用户添加金币,装备

                   

                    

                }

//                string chargeOk =ResManager::getInstance()->getString(StringConstant::chargeOk);

//                MainView::showToast(chargeOk);

                UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:@"Alert"

                                                                    message:@"購買成功啦"

                                                                   delegate:nilcancelButtonTitle:NSLocalizedString(@"Close(關閉)",nil) otherButtonTitles:nil];

                

                [alerView show];

                

                

                CCLOG("order haved call on ");

                [alerView release];

            }

                

                break;

            case SKPaymentTransactionStateFailed://交易失败

            {

                [self failedTransaction:transaction];

                CCLOG("-----交易失败 --------");

             

                

                string chargeError = ResManager::getInstance()->getString(StringConstant::chargeError);

                MainView::showToast(chargeError);

//                UIAlertView *alerView2 =  [[UIAlertView alloc] initWithTitle:@"Alert"

//                                                                     message:@"購買失敗,請重新嘗試購買~"

//                                                                    delegate:nil cancelButtonTitle:NSLocalizedString(@"Close(關閉)",nil) otherButtonTitles:nil];

//                

//                [alerView2 show];

//                [alerView2 release];

            }

                

                break;

            case SKPaymentTransactionStateRestored://已经购买过该商品

            {

                [self restoreTransaction:transaction];

                CCLOG("-----已经购买过该商品 --------");

                MainView::showToast("buyed ");

            }

               

            case SKPaymentTransactionStatePurchasing:      //商品添加进列表

                CCLOG("-----商品添加进列表 --------");

                break;

            default:

                break;

        }

    }

}

- (void) completeTransaction: (SKPaymentTransaction *)transaction

{

    CCLOG("-----completeTransaction--------");

    // Your application should implement these two methods.

    NSString *product = transaction.payment.productIdentifier;

    if ([product length] > 0) {

        

        NSArray *tt = [product componentsSeparatedByString:@"."];

        NSString *bookid = [tt lastObject];

        if ([bookid length] > 0) {

            [self recordTransaction:bookid];

            [self provideContent:bookid];

        }

    }

    

    // Remove the transaction from the payment queue.

    

    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

    

}

//记录交易

-(void)recordTransaction:(NSString *)product{

    CCLOG("-----记录交易--------");

}

//处理下载内容

-(void)provideContent:(NSString *)product{

    CCLOG("-----下载--------");

}

- (void) failedTransaction: (SKPaymentTransaction *)transaction{

    NSLog(@"失败");

    if (transaction.error.code != SKErrorPaymentCancelled)

    {

    }

    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

    

}

-(void) paymentQueueRestoreCompletedTransactionsFinished: (SKPaymentTransaction *)transaction{

    

}

- (void) restoreTransaction: (SKPaymentTransaction *)transaction

{

    NSLog(@" 交易恢复处理");

    

}

-(void) paymentQueue:(SKPaymentQueue *) paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error{

    CCLOG("-------paymentQueue----");

}

#pragma mark connection delegate

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data

{

    NSLog(@"%@",  [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);

}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{

    

}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{

    switch([(NSHTTPURLResponse *)response statusCode]) {

        case 200:

        case 206:

            break;

        case 304:

            break;

        case 400:

            break;

        case 404:

            break;

        case 416:

            break;

        case 403:

            break;

        case 401:

        case 500:

            break;

        default:

            break;

    }

}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {

    NSLog(@"test");

}

-(void)dealloc

{

    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];//解除监听

    [super dealloc];

}

-(BOOL)putStringToItunes:(NSData*)iapData{//用户购成功的transactionReceipt

    

  

    

    

    

    NSString* adStr= [[NSString alloc] initWithData:iapData  encoding://NSASCIIStringEncoding

                      NSUTF8StringEncoding];

    NSLog(@"adStr: %@",adStr);

//    NSString*encodingStr = [iapData base64EncodedString];

//  

//    NSLog(@"encodingStr: %@",encodingStr);

    const char* destDir = [adStr UTF8String];

    

    //CCUserDefault::sharedUserDefault()->setStringForKey("charge", destDir);

    NetDataSend::getInstance()->sendIOSCharge(destDir);

    

return true;

/**

    NSString *URL=@"";

    // 测试

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];// autorelease];

    [request setURL:[NSURL URLWithString:URL]];

    [request setHTTPMethod:@"POST"];

    //设置contentType

    [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    //设置Content-Length

    [request setValue:[NSString stringWithFormat:@"%d", [encodingStr length]] forHTTPHeaderField:@"Content-Length"];

    

    NSDictionary* body = [NSDictionary dictionaryWithObjectsAndKeys:encodingStr, @"receipt-data", nil];

    SBJsonWriter *writer = [SBJsonWriter new];

    [request setHTTPBody:[[writer stringWithObject:body] dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]];

    NSHTTPURLResponse *urlResponse=nil;

    NSError *errorr=nil;

    NSData *receivedData = [NSURLConnection sendSynchronousRequest:request

                                                 returningResponse:&urlResponse

                                                             error:&errorr];

    

    //解析

    NSString *results=[[NSString alloc]initWithBytes:[receivedData bytes] length:[receivedData length] encoding:NSUTF8StringEncoding];

    

    NSLog(@"yhh; %@",results);

    NSDictionary*dic = [results JSONValue];

    if([[dic objectForKey:@"status"] intValue]==0){//注意,status=@"0" 是验证收据成功

        return true;

    }

    return false;**/

}

@end

转载地址:https://blog.csdn.net/yhhwatl/article/details/21794359 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:【IOS】审核被拒 Advertising
下一篇:mac下使用终端

发表评论

最新留言

很好
[***.229.124.182]2024年04月23日 11时36分48秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

从零开始学Android!这篇文章可以满足你80%日常工作!文末领取面试资料 2019-04-29
作为字节跳动面试官,看懂这些帮你轻松解决就业问题!先收藏了 2019-04-29
保洁阿姨看完都会了!腾讯,字节,阿里,小米,京东大厂Offer拿到手软!再不刷题就晚了! 2019-04-29
做了5年Android,3年内被辞退5次,35岁程序员该何去何从?论程序员成长的正确姿势 2019-04-29
做了三年Android,2021年Android工作或许更难找,灵魂拷问 2019-04-29
涨知识!从草根到百万年薪程序员的十年风雨之路,搞懂这些直接来阿里入职 2019-04-29
深入浅出Android开发!46道面试题带你了解中高级Android面试,吐血整理 2019-04-29
深入讲解Android!Android开发热门前沿知识,完整版开放下载 2019-04-29
看完不会的来打我!Android性能优化之启动优化实战篇!薪资翻倍 2019-04-29
看完我工资从12K变成了20K!Github标星25K+超火的Android实战项目,已开源 2019-04-29
经典Android开发教程!靠着这份面试题跟答案,分享一点面试小经验 2019-04-29
经验分享:Android事件分发机制及设计思路,学习路线+知识点梳理 2019-04-29
腾讯T2亲自教你!五步搞定Android开发环境部署,年薪超过80万! 2019-04-29
腾讯T2亲自教你!五步搞定Android开发环境部署,聪明人已经收藏了! 2019-04-29
字节跳动面试必问:这个回答让我错失offer!分享一点面试小经验 2019-04-29
字节跳动面试真题:2021Android最新大厂面试真题总结,成功收获美团,小米安卓offer 2019-04-29
字节跳动面试:从草根到百万年薪程序员的十年风雨之路,成功收获美团,小米安卓offer 2019-04-29
学海无涯!Android性能优化最佳实践,详细的Android学习指南 2019-04-29
安卓app开发!Android面试必刷的200道真题,大厂面试题汇总 2019-04-29
安卓ndk开发!Android技术功底不够如何去面试,学习路线+知识点梳理 2019-04-29