iOS 支付宝、微信支付接入

要实现支付宝和微信支付功能的前提,首先需要一个商户账号。申请开通请往官方指南。

在获取有商户账号,就可以拿到重要也是必须的关键参数。

支付宝关键参数:

商户PID
商户收款账号
商户私钥,pkcs8格式
支付宝公钥

============

微信支付关键参数

微信支付appid
微信支付密钥AppSecret
API密钥(在商户平台设置)

注意的是,密钥的生成保存之类的,最好如官方所说在服务端处理,出于安全问题。
支付宝与微信支付,两个的区别粗略的说,更像是客户端发起支付与服务端发起支付两种方式。

支付宝支付主要是本身APP客户端发起支付,然后通过SDK调起支付宝客户端,发起支付请求,支付完成后支付宝服务端异步通知服务端,与返回支付结果给客户端。
微信支付更多的是服务端处理,APP客户端发起支付前,先往服务端递交支付信息,服务端使用支付信息向微信统一支付发起请求,并返回预订单信息给客户端,客户端再利用预订单信息生成带签名的支付信息,然后再通过SDK调起微信客户端进行支付,支付完成后微信服务端返回客户端支付结果,同时通知客户服务端。
不管使用哪种支付,在发起支付前,都要自己的服务器生成订单并获取到订单号和钱。

集成SDK,有两种集成方法,最为方便的就是cocopods,不过之前并没有cocopods的集成方法,都是手动集成,手动集成相对麻烦,需要先下载SDK包,拖入工程,添加依赖库。官方有较为详细的介绍手动集入
支付宝:https://docs.open.alipay.com/204/105295/

微信:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=1417694084&token=&lang=zh_CN

也可以通过cocopods,

pod 'AlipaySDK-iOS'
pod 'WechatOpenSDK'

集成支付宝支付

首先在AppDelegate中处理回调问题

- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation {
    
    if ([url.host isEqualToString:@"safepay"]) {
        //跳转支付宝钱包进行支付,处理支付结果
        [[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
            NSLog(@"result = %@",resultDic);
        }];
    }
    return YES;
}
// NOTE: 9.0以后使用新API接口
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options
{
    if ([url.host isEqualToString:@"safepay"]) {
        //跳转支付宝钱包进行支付,处理支付结果
        [[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
            NSLog(@"result = %@",resultDic);
        }];
    }
    return YES;
}

注意这里的safepay。微信是不提供这个判断的。

然后点击项目名称,点击 Info 选项卡,在“URL Types”选项中,点击“+”,在“URL Schemes”中输入“xxxxx”(注意这里填入的要和待会调起支付宝的appScheme参数值一样。否则会无法返回应用。
然后根据官方文档要求,将商品信息拼接成字符串作为待签名字符串,官方提供
app_id=2015052600090779&biz_content={"timeout_express":"30m","seller_id":"","product_code":"QUICK_MSECURITY_PAY","total_amount":"0.02","subject":"1","body":"我是测试数据","out_trade_no":"ZQLM3O56MJD4SK3"}&charset=utf-8&method=alipay.trade.app.pay&sign_type=RSA2&timestamp=2016-07-28 20:36:11&version=1.0

这里的做法是新建一个model,并创建响应的属性,比如这里创建了一个叫Order的model,并定义属性,然后使用MJExtension转为json字符串。参数可以看参数说明https://docs.open.alipay.com/204/105465/
最后拼接上biz_content与app_id。当然也可以用官方的方法,也是很推荐的。

Order* order = [Order new];

// NOTE: app_id设置
order.app_id = appID;

// NOTE: 支付接口名称
order.method = @"alipay.trade.app.pay";

// NOTE: 参数编码格式
order.charset = @"utf-8";

// NOTE: 当前时间点
NSDateFormatter* formatter = [NSDateFormatter new];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
order.timestamp = [formatter stringFromDate:[NSDate date]];

// NOTE: 支付版本
order.version = @"1.0";

// NOTE: sign_type设置
order.sign_type = @"RSA";

// NOTE: 商品数据
order.biz_content = [BizContent new];
order.biz_content.body = @"我是测试数据";
order.biz_content.subject = @"1";
order.biz_content.out_trade_no = [self generateTradeNO]; //订单ID(由商家自行制定)
order.biz_content.timeout_express = @"30m"; //超时时间设置
order.biz_content.total_amount = [NSString stringWithFormat:@"%.2f", 0.01]; //商品价格
//将商品信息拼接成字符串
NSString *orderInfo = [order orderInfoEncoded:NO];
NSString *orderInfoEncoded = [order orderInfoEncoded:YES];

拼接完字符串,要记得使用UTF-8编码一下,官方要求value都应编码,如果这里不清楚orderInfoEncoded函数由来或者发现不存在,你可以往Order的.m文件写入以下代码。


- (NSString *)orderInfoEncoded:(BOOL)bEncoded {
    
    if (_app_id.length <= 0) {
        return nil;
    }
    
    // NOTE: 增加不变部分数据
    NSMutableDictionary *tmpDict = [NSMutableDictionary new];
    [tmpDict addEntriesFromDictionary:@{@"app_id":_app_id,
                                        @"method":_method?:@"alipay.trade.app.pay",
                                        @"charset":_charset?:@"utf-8",
                                        @"timestamp":_timestamp?:@"",
                                        @"version":_version?:@"1.0",
                                        @"biz_content":_biz_content.description?:@"",
                                        @"sign_type":_sign_type?:@"RSA"}];
    
    
    // NOTE: 增加可变部分数据
    if (_format.length > 0) {
        [tmpDict setObject:_format forKey:@"format"];
    }
    
    if (_return_url.length > 0) {
        [tmpDict setObject:_return_url forKey:@"return_url"];
    }
    
    if (_notify_url.length > 0) {
        [tmpDict setObject:_notify_url forKey:@"notify_url"];
    }
    
    if (_app_auth_token.length > 0) {
        [tmpDict setObject:_app_auth_token forKey:@"app_auth_token"];
    }
    
    // NOTE: 排序,得出最终请求字串
    NSArray* sortedKeyArray = [[tmpDict allKeys] sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        return [obj1 compare:obj2];
    }];
    
    NSMutableArray *tmpArray = [NSMutableArray new];
    for (NSString* key in sortedKeyArray) {
        NSString* orderItem = [self orderItemWithKey:key andValue:[tmpDict objectForKey:key] encoded:bEncoded];
        if (orderItem.length > 0) {
            [tmpArray addObject:orderItem];
        }
    }
    return [tmpArray componentsJoinedByString:@"&"];
}

- (NSString*)orderItemWithKey:(NSString*)key andValue:(NSString*)value encoded:(BOOL)bEncoded
{
    if (key.length > 0 && value.length > 0) {
        if (bEncoded) {
            value = [self encodeValue:value];
        }
        return [NSString stringWithFormat:@"%@=%@", key, value];
    }
    return nil;
}

- (NSString*)encodeValue:(NSString*)value
{
    NSString* encodedValue = value;
    if (value.length > 0) {
        encodedValue = (__bridge_transfer  NSString*)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)value, NULL, (__bridge CFStringRef)@"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8 );
    }
    return encodedValue;
}

然后是签名
// NOTE: 获取私钥并将商户信息签名,外部商户的加签过程请务必放在服务端,防止公私钥数据泄露;
//       需要遵循RSA签名规范,并将签名字符串base64编码和UrlEncode
id<DataSigner> signer = CreateRSADataSigner(privateKey);
NSString *signedString = [signer signString:orderInfo];

最好再将,上面编码过的字符串和和签名再拼接,官方提供

NSString *orderString = [NSString stringWithFormat:@"%@&sign=%@",
                             orderInfoEncoded, signedString];
 

最后开始调用支付
// NOTE: 调用支付结果开始支付

NSString *appScheme = @"xxxxx";
    [[AlipaySDK defaultService] payOrder:orderString fromScheme:appScheme callback:^(NSDictionary *resultDic) {
        NSLog(@"reslut = %@",resultDic);
NSDictionary * dict;
            if ([resultDic[@"resultStatus"] integerValue] == 9000) {
                
                dict = @{@"code":@"0",@"result":@"支付成功"};
            }
            else if([resultDic[@"resultStatus"] integerValue] == 6001)
            {
                dict = @{@"code":@"-2",@"result":@"已取消支付"};
            }
            else
            {
                dict = @{@"code":@"-1",@"result":@"支付失败"};
            }
            //这个地方你可以做一个回调提示
    }];
集成微信支付

微信支付和支付宝不同,微信需要注册,因为微信SDK还包含了其他功能,如登录。
在AppDelegate中注册微信SDK

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [WXApi registerApp:WxAppid enableMTA:YES];
     return YES;
}

处理回调

//iOS9.0前
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    [WXApi handleOpenURL:url delegate:[WXApiManager sharedManager]];
return YES;
}
//iOS9.0后
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options{
  [WXApi handleOpenURL:url delegate:[WXApiManager sharedManager]];
    return YES;
}

然后像添加支付宝的URL Scheme一样,添加appScheme,identifier写入weixin(也可以随意),URL scheme写成微信开放平台申请的appid。之后还需要在info.plist文件中加入白名单LSApplicationQueriesSchemes--》weixin与wechat。在这里说一下,如果在支付宝调起出现问题,请在此白名单中加入alipay与alipayshare。
接下来是调起支付

PayReq *request = [[[PayReq alloc] init] autorelease];
request.partnerId = @"10000100";
request.prepayId= @"1101000000140415649af9fc314aa427";
request.package = @"Sign=WXPay";
request.nonceStr= @"a462b76e7436e98e0ed6e13c64b4fd1c";
request.timeStamp= @"1397527777";
request.sign= @"582282D72DD2B03AD892830965F428CB16E7A256";
[WXApi sendReq:request];

这里看似简单,实际不是的,微信文档曾被不少人说成了一坨屎。先看看参数要求https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12,可以看到签名算法问题还是很容易出现问题的。
参数的意义

/** 商家向财付通申请的商家id */
@property (nonatomic, retain) NSString *partnerId;
/** 预支付订单 */
@property (nonatomic, retain) NSString *prepayId;
/** 随机串,防重发 */
@property (nonatomic, retain) NSString *nonceStr;
/** 时间戳,防重发 */
@property (nonatomic, assign) UInt32 timeStamp;
/** 商家根据财付通文档填写的数据和签名 */
@property (nonatomic, retain) NSString *package;
/** 商家根据微信开放平台文档对数据做的签名 */
@property (nonatomic, retain) NSString *sign;

这里提供一个随机串生成方法

/**
 ------------------------------
 产生随机字符串
 ------------------------------
 1.生成随机数算法 ,随机字符串,不长于32位
 2.微信支付API接口协议中包含字段nonce_str,主要保证签名不可预测。
 3.我们推荐生成随机数算法如下:调用随机数函数生成,将得到的值转换为字符串。
 */
+ (NSString *)generateTradeNO
{
    static int kNumber = 15;
    
    NSString *sourceStr = @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
    NSMutableString *resultStr = [[NSMutableString alloc] init];
    
    //  srand函数是初始化随机数的种子,为接下来的rand函数调用做准备。
    //  time(0)函数返回某一特定时间的小数值。
    //  这条语句的意思就是初始化随机数种子,time函数是为了提高随机的质量(也就是减少重复)而使用的。
    
    // srand(time(0)) 就是给这个算法一个启动种子,也就是算法的随机种子数,有这个数以后才可以产生随机数,用1970.1.1至今的秒数,初始化随机数种子。
    // Srand是种下随机种子数,你每回种下的种子不一样,用Rand得到的随机数就不一样。为了每回种下一个不一样的种子,所以就选用Time(0),Time(0)是得到当前时时间值(因为每时每刻时间是不一样的了)。
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
    srand(time(0)); // 此行代码有警告:
#pragma clang diagnostic pop
    for (int i = 0; i < kNumber; i++) {
        
        unsigned index = rand() % [sourceStr length];
        
        NSString *oneStr = [sourceStr substringWithRange:NSMakeRange(index, 1)];
        
        [resultStr appendString:oneStr];
    }
    return resultStr;
}

这里有个值得注意的是,官方所说的

image.png

如果服务器不做这个预订单处理,那么我们就得自己发起请求做预订单处理。详细看https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
这里说一下大概的做法,先定义主要参数,然后转义为XML格式。然后使用AFNetworking,发起统一支付请求,成功会返回一串XML,然后再把XML的主要信息,放入PayReq的对象中,调起客户端微信支付。
参考以下主要做法

 // 客户端操作     (实际操作应由服务端操作)
    //============================================================
    NSString *tradeType = @"APP";                                       //交易类型
    NSString *totalFee  = money;                                         //交易价格1表示0.01元,10表示0.1元
    NSString *tradeNO   = [self generateTradeNO];                       //随机字符串变量 这里最好使用和安卓端一致的生成逻辑
    NSString *addressIP = [Cyhkeychain getIPAddress:YES];               //设备IP地址
    NSString *orderNo   = orderSn;//[NSString stringWithFormat:@"%ld",time(0)];   //随机产生订单号用于测试,正式使用请换成你从自己服务器获取的订单号
    NSString *notifyUrl = @"http://www.xxxxxxx/xxx";// 交易结果通知网站此处用于测试,随意填写,正式使用时填写正确网站
//获取SIGN签名
    MXWechatSignAdaptor *adaptor = [[MXWechatSignAdaptor alloc] initWithWechatAppId:MXWechatAPPID
                                                                        wechatMCHId:MXWechatMCHID
                                                                            tradeNo:tradeNO
                                                                   wechatPartnerKey:MXWechatPartnerKey
                                                                           payTitle:@"订单"
                                                                            orderNo:orderNo
                                                                           totalFee:totalFee
                                                                           deviceIp:addressIP
                                                                          notifyUrl:notifyUrl
                                                                          tradeType:tradeType];

 //转换成XML字符串,实际并不是正确的XML格式,需要使用AF方法进行转义
    NSString *string = [[adaptor dic] XMLString];
    AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
    session.responseSerializer = [[AFHTTPResponseSerializer alloc] init];
    [session.requestSerializer setValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
    [session.requestSerializer setValue:kUrlWechatPay forHTTPHeaderField:@"SOAPAction"];
    [session.requestSerializer setQueryStringSerializationWithBlock:^NSString *(NSURLRequest *request, NSDictionary *parameters, NSError *__autoreleasing *error) {
        return string;
    }];
    
    [session POST:kUrlWechatPay
       parameters:string
         progress:nil
          success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject)
     {
         
         //  输出XML数据
         NSString *responseString = [[NSString alloc] initWithData:responseObject
                                                          encoding:NSUTF8StringEncoding] ;
         //  将微信返回的xml数据解析转义成字典
         NSDictionary *dic = [NSDictionary dictionaryWithXMLString:responseString];
         
//         NSLog(@"下单返回状态码:%@",[dic objectForKey:@"result_code"]);
         // 判断返回的许可
         if ([[dic objectForKey:@"result_code"] isEqualToString:@"SUCCESS"]
             &&[[dic objectForKey:@"return_code"] isEqualToString:@"SUCCESS"] ) {
             // 发起微信支付,设置参数
             PayReq *request = [[PayReq alloc] init];
             request.openID = [dic objectForKey:WXAPPID];
             request.partnerId = [dic objectForKey:WXMCHID];
             request.prepayId= [dic objectForKey:WXPREPAYID];
             request.package = @"Sign=WXPay";
             request.nonceStr= [dic objectForKey:WXNONCESTR];
             
             // 将当前时间转化成时间戳
             NSDate *datenow = [NSDate date];
             NSString *timeSp = [NSString stringWithFormat:@"%ld", (long)[datenow timeIntervalSince1970]];
             UInt32 timeStamp =[timeSp intValue];
             request.timeStamp= timeStamp;
             
             // 签名加密
             MXWechatSignAdaptor *md5 = [[MXWechatSignAdaptor alloc] init];
             
             request.sign=[md5 createMD5SingForPay:request.openID
                                         partnerid:request.partnerId
                                          prepayid:request.prepayId
                                           package:request.package
                                          noncestr:request.nonceStr
                                         timestamp:request.timeStamp];
             
             // 调用微信
             [WXApi sendReq:request];
         }
         
     } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
         
     }];

这里只是作为参考,因为有些方法是需要自己去写的,粘贴复制必错。

=============这上面是服务端没做预订单处理的====如果做了,那么客户端是相对简单的,要注意的就是签名问题,sign的拼接加密。官方要求的sign的拼接模板
stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
stringSignTemp=stringA+"&key=192006250b4c09247ec02edce69f6a2d" //注:key为商户平台设置的密钥key

sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7" //注:MD5签名方式

sign=hash_hmac("sha256",stringSignTemp,key).toUpperCase()="6A9AE1657590FD6257D693A078E1C3E4BB6BA4DC30B23E0EE2496E54170DACD6" //注:HMAC-SHA256签名方式

用MD5就不需要HMAC-SHA256方式,否则反之。操作参考如下

 // 发起微信支付,设置参数
             PayReq *request = [[PayReq alloc] init];
             request.openID = [dic objectForKey:WXAPPID];
             request.partnerId = [dic objectForKey:WXMCHID];
             request.prepayId= [dic objectForKey:WXPREPAYID];
             request.package = @"Sign=WXPay";
             request.nonceStr= [dic objectForKey:WXNONCESTR];
 // 将当前时间转化成时间戳
             NSDate *datenow = [NSDate date];
             NSString *timeSp = [NSString stringWithFormat:@"%ld", (long)[datenow timeIntervalSince1970]];
             UInt32 timeStamp =[timeSp intValue];
             request.timeStamp= timeStamp;

request.sign=[md5 createMD5SingForPay:request.openID
                                         partnerid:request.partnerId
                                          prepayid:request.prepayId
                                           package:request.package
                                          noncestr:request.nonceStr
                                         timestamp:request.timeStamp];//字段拼接与MD5加密
             
             // 调用微信
             [WXApi sendReq:request];

当然,如果服务端在处理预订单返回sign的MD5字符串的话,就更简单,直接拼接上去就OK了。
最后处理好支付结果的返回就行了。

#pragma mark - WXApiDelegate
- (void)onResp:(BaseResp *)resp
{
    if([resp isKindOfClass:[PayResp class]]){
        
        //支付返回结果,实际支付结果需要去微信服务器端查询
        NSString *strMsg;
    
        switch (resp.errCode) {
            case WXSuccess:
                strMsg = @"支付结果:成功!";
//                NSLog(@"支付成功-PaySuccess,retcode = %d", resp.errCode);
                break;
                
            default:
                strMsg = [NSString stringWithFormat:@"支付结果:失败!retcode = %d, retstr = %@", resp.errCode,resp.errStr];
//                NSLog(@"错误,retcode = %d, retstr = %@", resp.errCode,resp.errStr);
                break;
        }
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容