Skip to content

Instantly share code, notes, and snippets.

@kayoslab
Last active September 19, 2018 12:37
Show Gist options
  • Save kayoslab/cfda98eceebf283c92e0a42d79f14cc8 to your computer and use it in GitHub Desktop.
Save kayoslab/cfda98eceebf283c92e0a42d79f14cc8 to your computer and use it in GitHub Desktop.
Swift port according to the documentation on the Alipay website. I should create an example project for this in the future. As already mentioned in the legacy version of this class I'm relying on SwiftyRSA for the signing here.
import Foundation
import SwiftyRSA
/// Automatically generated by the Chinese Website.
struct AlipayPublicParameter {
enum CodingKeys: String, CodingKey {
case appID = "app_id"
case method
case format
case charset
case signType = "sign_type"
case sign
case timestamp
case version
case notifyURL = "notify_url"
case businessContent = "biz_content"
case signedDescriptionString
}
enum SignType: String, Codable {
case rsa = "RSA"
case rsa2 = "RSA2"
}
/// Alipay's app ID assigned to the developer
let appID: String
/// Interface name
let method: String
/// Only JSON is supported and therefore the default value.
let format: String? = "JSON"
/// The encoding format requested, such as utf-8, gbk, gb2312, etc.
let charset: String
/// The type of signature algorithm used by the merchant to generate the signature string.
/// Currently supports RSA2 and RSA. RSA2 is recommended and therefore the default value.
let signType: SignType = .rsa2
/// The signature string of the merchant request parameter.
/// See https://docs.open.alipay.com/291/105974 for more details.
let sign: String
/// The time the request was sent, the format "yyyy-MM-dd HH:mm:ss"
let timestamp: String
/// The version of the interface being called is fixed to: 1.0 and therefore the default value.
let version: String = "1.0"
/// The Alipay server actively informs the http/https path of the page specified in the merchant server.
/// Merchants are recommended to use https
let notifyURL: String
/// The set of service request parameters, the maximum length is not limited,
/// all the request parameters except the public parameters must be passed in this parameter,
/// specifically refer to the quick access document of each product.
let businessContent: AlipayBusinessContent
/// The string that can be used to contact the ALIPAY backend.
let signedDescriptionString: String
/// Initialize
///
/// - Parameters:
/// - info: Order info object
init(for info: [String: String], with key: PrivateKey) throws {
// do something with your info data and initialise this object.
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
self.timestamp = formatter.string(from: Date())
var signableDescriptionObjects: [String: String] = [CodingKeys.appID.rawValue: self.appID,
CodingKeys.method.rawValue: self.method,
CodingKeys.charset.rawValue: self.charset,
CodingKeys.signType.rawValue: self.signType.rawValue,
CodingKeys.timestamp.rawValue: self.timestamp,
CodingKeys.version.rawValue: self.version,
CodingKeys.notifyURL.rawValue: self.notifyURL]
if let format = self.format {
signableDescriptionObjects[CodingKeys.format.rawValue] = format
}
/// The business contetn is appended as JSON string, luckily.
let jsonEncoder = JSONEncoder()
let jsonBusinessData = try jsonEncoder.encode(businessContent)
if let jsonBusinessContent = String(data: jsonBusinessData, encoding: .utf8) {
signableDescriptionObjects[CodingKeys.businessContent.rawValue] = jsonBusinessContent
}
/// Alipay allows all characters, but "!*'();:@&=+$,/?%#[]".
let characterset = CharacterSet(charactersIn: "!*'();:@&=+$,/?%#[]").inverted
/// creates a string that looks like this: key1="value1"&key2="value2" containing all entries in signableDescriptionObjects
/// and percentencode the values with the given alipay character scheme
let signableDescription = signableDescriptionObjects.sorted(by: <).compactMap({ "\($0.key)=\"\($0.value.addingPercentEncoding(withAllowedCharacters: characterset) ?? "")\"" }).joined(separator: "&")
/// Contains the UTF8 encoded Message object with the order's description.
let message = try ClearMessage(string: signableDescription, using: .utf8)
/// The message's signature based on the chosen signtype.
let signature = signType == .rsa ? try message.signed(with: key, digestType: Signature.DigestType.sha1) : try message.signed(with: key, digestType: Signature.DigestType.sha256)
let signatureString = signature.base64String
// Let's be sure everything worked out as expected.
guard !signatureString.isEmpty else { throw AlipayTransactionError.signingError }
self.sign = signatureString
self.signedDescriptionString = signableDescription.appendingFormat("&\(CodingKeys.sign.rawValue)=\"%@\"", signatureString)
}
}
/// Not all parameters of this BusinessContent are relevant for a succesful transaction.
struct AlipayBusinessContent: Codable {
enum CodingKeys: String, CodingKey {
case body
case subject
case outTradeNumber = "out_trade_no"
case sellerID = "seller_id"
case timeoutExpress = "timeout_express"
case totalAmount = "total_amount"
case productCode = "product_code"
case goodsType = "goods_type"
case passbackParams = "passback_params"
case promoParams = "promo_params"
case extendParam = "extend_param"
case enablePayChannels = "enable_pay_channels"
case disablePayChannels = "disable_pay_channels"
case storeID = "store_id"
case extUserInfo = "ext_user_info"
}
/// A specific description of a transaction. If it is a variety of products,
/// please add the product description string to the body.
let body: String?
/// Product title / transaction title / order title / order keyword, etc.
let subject: String
/// Merchant website unique order number
let outTradeNumber: String
/// Receive Alipay User ID. If the value is blank, the default is the Alipay user ID corresponding to
/// the merchant subscription account (for example, 2088102147948060).
let sellerID: String?
/// The latest payment time allowed for this order, the transaction will be closed after the deadline.
/// Value range: 1m ~ 15d. M-minute, h-hour, d-day, 1c-day (1c-day case, no matter when the transaction is created,
/// it is closed at 0). The value of this parameter does not accept decimal points, such as 1.5h, which can be converted to 90m.
/// - Note: If it is empty, the default is 15d.
let timeoutExpress: String?
/// The total amount of the order, the unit is yuan, accurate to two decimal places, the value range [0.01,100000000].
let totalAmount: String
/// Sales product code, product code signed by merchant and Alipay, fixed value QUICK_MSECURITY_PAY
let productCode: String = "QUICK_MSECURITY_PAY"
/// Main product type: 0: virtual goods, 1: physical goods
/// - Note: Virtual class goods do not support the use of flower channels
let goodsType: String? = "1"
/// The public return parameter, if the parameter is passed when requested, will be returned when returned to the merchant.
/// Alipay returns this parameter as it is when it is notified asynchronously. This parameter must be sent to Alipay after UrlEncode
let passbackParams: String?
/// Preferential parameters
/// - Note: Available only after negotiation with Alipay
let promoParams: String?
/// Business extension parameters.
/// See https://docs.open.alipay.com/#kzcs for more details.
let extendParam: AlipayBusinessExtensionParameters?
/// users can only pay within the specified channel range when there are multiple channels separated by ","
/// - Note: mutually exclusive with disable_pay_channels
/// See https://docs.open.alipay.com/#qdsm for more details.
let enablePayChannels: String?
/// users can't use the specified channel to pay when there are multiple channels separated by ","
/// - Note: mutually exclusive with enable_pay_channels
/// See https://docs.open.alipay.com/#qdsm for more details.
let disablePayChannels: String?
/// Merchant store number. This parameter is used in the request parameter to distinguish
/// between stores and non-transfer items.
let storeID: String?
/// Externally designated buyers, see the ExtUserInfo parameter description.
let extUserInfo: AlipayExtUserInfo?
}
struct AlipayBusinessExtensionParameters: Codable {
enum CodingKeys: String, CodingKey {
case sysServiceProviderID = "sys_service_provider_id"
case needBuyerRealNamed
case transMemo = "trans_memo"
case hbFqNumber = "hb_fq_num"
case hbFqSellerPercent = "hb_fq_seller_percent"
}
/// System vendor number, which is used as the basis for system vendor rebate data extraction.
/// Please fill in the PID of the system vendor contract agreement.
let sysServiceProviderID: String?
/// Whether to initiate real name verification.
/// T: initiated
/// F: Do not initiate
let needBuyerRealNamed: String?
/// Accounting Note: This field is displayed in the accounting notes for offline billing.
let transMemo: String?
/// Number of stages of flower buds (currently only supports 3, 6, 12)
/// Note: To use this parameter, you need to read the “Flower Staging Access Document” carefully.
/// See https://docs.open.alipay.com/277/106748 for more details.
let hbFqNumber: String?
/// The seller bears the proportion of charges, the merchant bears the handling fee of 100,
/// the user accepts the handling fee to pass 0, only supports the incoming 100, 0,
/// other ratios are not supported.
/// - Note: Please carefully read the related documentation.
/// See https://docs.open.alipay.com/277/106748 for more details.
let hbFqSellerPercent: String?
}
struct AlipayExtUserInfo: Codable {
enum CodingKeys: String, CodingKey {
case name
case mobile
case certType = "cert_type"
case certNo = "cert_no"
case minimumAge = "min_age"
case fixBuyer = "fix_buyer"
case needCheckInfo = "need_check_info"
}
/// The customers name
/// - Note: This parameter is valid only when need_check_info=T
let name: String?
/// The customers telephone number.
/// - Note: This parameter is not verified at this time.
let mobile: String?
/// ID card: IDENTITY_CARD, passport: PASSPORT, military officer card: OFFICER_CARD,
/// soldier card: SOLDIER_CARD, account book: HOKOU.
/// If there is any other type of support, please contact the Ant Financial staff.
/// - Note: This parameter is valid only when need_check_info=T
let certType: String?
/// The customers license number.
/// - Note: This parameter is valid only when need_check_info=T
let certNo: String?
/// Minimum buyer age allowed, buyer age must be greater than or equal to the value passed
/// - Note: 1. This parameter is valid only when need_check_info=T.
/// 2. min_age is an integer and must be greater than or equal to 0.
let minimumAge: String?
/// Is it mandatory to verify the payer identity information?
/// T: forced check, F: not mandatory
let fixBuyer: String?
/// Whether to force verification of identity information
/// T: forced check, F: not mandatory
let needCheckInfo: String?
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment