iOS实现微信支付流程详解

2020-01-21 00:50:28王振洲

背景

自微信支付、支付宝支付入世以来,移动端的支付日渐火热。虚拟货币有取代实体货币的趋向(这句纯属扯淡,不用管),支付在app开发中是一项基本的功能,有必要去掌握。从难易程度上讲,不管是微信支付还是支付宝支付都是非常简单的,因为第三方的支付文档非常详细,而且他们内部的安全性也非常高。作为使用这些支付策略的我们,只需要掌握流程,能够实现正常支付的功能即可。为什么要写下这篇博文,原因有二。其一,微信支付流程中有坑,其二,以后忘记了可以拿出来看看。

配置

1.微信支付需要两个账号,财付通和微信开发者,注册完成后需要开通支付功能,这些流程就不用多说了。在所有申请成功后,我们要取出我们支付功能需要的key:

appID、app密钥(微信发给你的邮件中有如何生成密钥的链接)、商户号。

2.在Xcode中配置app ID,需要设置下白名单,在url中配置app ID,不清楚的童鞋可以百度一下。

支付

从此处开始,进入本次的主题,开始支付。支付的过程且分为四个步骤:

第一步  获取订单号

获取订单号的途径可以是客户端自己生成,也可以去服务器生成,不过一般都是服务器生成。假设拿到了订单号设为order_no。

第二步  统一下单

这个过程是非常关键的一步,也是坑常驻的一步。统一下单有的人做法是在服务器操作,有的在客户端,不管在哪下单都是有必要搞懂的。接下来看看统一下单必须要的参数列表:


/*应用ID 微信开放平台审核通过的应用app ID*/
@property (nonatomic, copy) NSString *appid;
/*商户号 微信支付分配的商户号*/
@property (nonatomic, copy) NSString *mch_id;
/*随机字符串 随机字符串,不长于32位*/
@property (nonatomic, copy) NSString *nonce_str;
/*签名*/
@property (nonatomic, copy) NSString *sign;
/*商品描述 天天爱消除-游戏充值。*/
@property (nonatomic, copy) NSString *body;
/*商户订单号*/
@property (nonatomic, copy) NSString *out_trade_no;
/*总金额 订单总金额,单位为分*/
@property (nonatomic, copy) NSString *total_fee;
/*终端IP*/
@property (nonatomic, copy) NSString *spbill_create_ip;
/*通知地址*/
@property (nonatomic, copy) NSString *notify_url;
/*交易类型*/
@property (nonatomic, copy) NSString *trade_type;

其中,sign是其他所有参数按照key1=value1&key2=value2...的方式拼接,然后进行加密得到。参数拼接按照字母排序,举个例子,参数为appid=@"id",mch_id=@"mch"得到的字符串应该是:@"appid=id&mch_id=mch",然后对该字符串进行加密,如下述代码


- (instancetype)initWithDicInfo:(NSDictionary *)infoDic{

  if (self = [super init]) {

    self.appid = WECHAT_SHARE_APPID;

    self.mch_id = WECHAT_MCH_ID;

    self.nonce_str = [AppMethod getRandomString];

    self.body = @"test";

    self.out_trade_no = [infoDic formateObjectForKey:@"order_no"];

    self.total_fee = [NSString stringWithFormat:@"%@", [infoDic formateObjectForKey:@"amount"]];

    self.spbill_create_ip = [AppMethod deviceIPAdress];

    self.notify_url = [NSString stringWithFormat:@"%@%@", BASE_URL, WECHAT_NOTI_URL];

    self.trade_type = @"APP";

    self.payDic = [NSMutableDictionary dictionary];

    [self.payDic setValue:self.appid forKey:@"appid"];

    [self.payDic setValue:self.mch_id forKey:@"mch_id"];

    [self.payDic setValue:self.nonce_str forKey:@"nonce_str"];

    [self.payDic setValue:self.body forKey:@"body"];

    [self.payDic setValue:self.out_trade_no forKey:@"out_trade_no"];

    [self.payDic setValue:self.total_fee forKey:@"total_fee"];

    [self.payDic setValue:self.spbill_create_ip forKey:@"spbill_create_ip"];

    [self.payDic setValue:self.notify_url forKey:@"notify_url"];

    [self.payDic setValue:self.trade_type forKey:@"trade_type"];

    self.sign = [self partnerSignOrder:self.payDic];

    [self.payDic setValue:self.sign forKey:@"sign"];

  }

  return self;

}
- (NSString *)partnerSignOrder:(NSDictionary*)paramDic{
  NSArray *keyArray = [paramDic allKeys];
  NSMutableArray *sortedKeyArray = [NSMutableArray arrayWithArray:keyArray];
  [sortedKeyArray sortUsingComparator:^NSComparisonResult(NSString* key1, NSString* key2) {
    return [key1 compare:key2];
  }];
  NSMutableString *paramString = [NSMutableString stringWithString:@""];
  // 拼接成 A=B&X=Y
  for (NSString *key in sortedKeyArray){
    if ([paramDic[key] length] != 0){
      [paramString appendFormat:@"&%@=%@", key, paramDic[key]];
    }
  }
  if ([paramString length] > 1){
    [paramString deleteCharactersInRange:NSMakeRange(0, 1)];  // remove first '&'
  }
  [paramString appendFormat:@"&key=%@", WeChatPARTNER_ID];//app密钥
  return [[AppMethod signString:paramString] uppercaseString];
}

AppMethod.m
+ (NSString *)signString:(NSString*)origString{
  const char *original_str = [origString UTF8String];
  unsigned char result[32];
  CC_MD5(original_str, (CC_LONG)strlen(original_str), result);//调用md5
  NSMutableString *hash = [NSMutableString string];
  for (int i = 0; i < 16; i++){
    [hash appendFormat:@"%02X", result[i]];
  }
  return hash;
}