Skip to content

Instantly share code, notes, and snippets.

@kayoslab
Created September 18, 2018 09:14
Show Gist options
  • Save kayoslab/0998523f0d2e6ceecb525d9e3bdb9efe to your computer and use it in GitHub Desktop.
Save kayoslab/0998523f0d2e6ceecb525d9e3bdb9efe to your computer and use it in GitHub Desktop.
Swift port of the Legacy Alipay Order, which is usually only available in Objective-C. I rely on the RSA implementation of SwiftyRSA here, so that I don't have to work with their own (obj-c) implementations.
import Foundation
import SwiftyRSA
/// Swift port of the always included _Order_ class from Alipay, because we suffered from the terrible code.
/// The code comments went through google translate to give us some hints on what is happening under the hood.
/// This struct is used to hold the data for an order within the alipay framework and to get the url parameter
/// containing the order object including a RSA signature.
private struct LegacyAlipayOrder {
/********************************** Pay four elements **********************************/
/// When the merchant signs an agreement with Alipay, Alipay assigns a unique
/// identification number to the merchant (16-digit pure number starting with 2088).
private var partner: String?
/// The Alipay unique user number corresponding to the Alipay account (16-bit pure
/// number starting with 2088), the order payment amount will be entered into the
/// account, and a partner can correspond to multiple seller_ids.
private var sellerID: String?
/// The unique order number for the merchant's website item.
private var outTradeNO: String?
/// The total amount of funds for this order, in RMB (Yuan).
/// The value range is [0.01, 100000000.00], which is accurate to two decimal places.
private var totalFee: String?
/********************************** Commodity related **********************************/
/// Product title/transaction title/order title/order keyword, etc.
private var subject: String?
/// A specific description of a transaction. If it is a variety of products,
/// please add the product description string to the body.
private var body: String?
/****************************** Other mandatory parameters *****************************/
/// The interface name is fixed to mobile.securitypay.pay.
private var service: String?
/// The encoding format used by the merchant website is fixed to utf-8.
private var inputCharset: String?
/// The Alipay server proactively informs the http path of the page specified in the merchant's website.
private var notifyURL: String?
private var returnURL: String?
private var currency: String?
private var forexbiz: String?
/********************************* Optional parameters *********************************/
/// Payment type, 1: Purchase of goods. (the default value is not passed)
private var paymentType: String?
/// Specifically distinguish the type of goods in the local transaction, 1: physical
/// transaction; (default value in the case of no transmission), 0: virtual transaction;
/// (restriction of rules such as credit card is not allowed).
private var goodsType: String?
/// Whether to initiate real-name verification when paying,
/// F: Do not initiate real-name verification; (default value in case of no transmission),
/// T: Initiate real-name verification; (Business business requires buyer real-name authentication)
private var rnCheck: String?
/// Identifies the client.
private var appID: String?
/// Identifies the client source. The content of the parameter value is as follows:
/// appenv=“system=client platform name^version=business system version”
private var appenv: String?
/// Set the timeout for unpaid transactions. Once the timeout expires,
/// the transaction will be automatically closed. When the user enters the payment password
/// and clicks to confirm the payment (that is, after creating the Alipay transaction),
/// the timing starts. Value range: 1m to 15d, or use absolute time (example format: 2014-06-13 16:00:00).
/// 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.
private var itBPay: String?
/// Product address
private var showURL: String?
/// Business extension parameters, Alipay specific business needs to add this field, json format.
/// When the merchant accesses, it is determined by negotiation with Alipay.
private var outContext: Dictionary<String, Any>?
private var description: String {
var description: String = ""
/** Pay elements **/
if let partner = partner {
description = description.appendingFormat("partner=\"%@\"", partner)
}
if let sellerID = sellerID {
description = description.appendingFormat("&seller_id=\"%@\"", sellerID)
}
if let outTradeNO = outTradeNO {
description = description.appendingFormat("&out_trade_no=\"%@\"", outTradeNO)
}
if let totalFee = totalFee {
description = description.appendingFormat("&total_fee=\"%@\"", totalFee)
}
/** Commodity related **/
if let subject = subject {
description = description.appendingFormat("&subject=\"%@\"", subject)
}
if let body = body {
description = description.appendingFormat("&body=\"%@\"", body)
}
/** Other mandatory parameters **/
if let service = service {
description = description.appendingFormat("&service=\"%@\"", service)
}
if let inputCharset = inputCharset {
description = description.appendingFormat("&_input_charset=\"%@\"", inputCharset)
}
if let notifyURL = notifyURL {
description = description.appendingFormat("&notify_url=\"%@\"", notifyURL)
}
if let returnURL = returnURL {
description = description.appendingFormat("&return_url=\"%@\"", returnURL)
}
if let currency = currency {
description = description.appendingFormat("&currency=\"%@\"", currency)
}
if let forexbiz = forexbiz {
description = description.appendingFormat("&forex_biz=\"%@\"", forexbiz)
}
/** Optional parameters **/
if let paymentType = paymentType {
description = description.appendingFormat("&payment_type=\"%@\"", paymentType)
}
if let itBPay = itBPay {
description = description.appendingFormat("&it_b_pay=\"%@\"", itBPay)
}
if let showURL = showURL {
description = description.appendingFormat("&show_url=\"%@\"", showURL)
}
if let appID = appID {
description = description.appendingFormat("&app_id=\"%@\"", appID)
}
if let outContext = outContext {
for (key, value) in outContext.enumerated() {
description = description.appendingFormat("&%@=\"%@\"", key, value as? CVarArg ?? "")
}
}
return description
}
func description(with key: PrivateKey) throws -> String? {
guard let signed = try signedString(with: key) else { return nil }
return description + "&sign=\"" + signed + "\"&sign_type=\"RSA\""
}
private func signedString(with key: PrivateKey) throws -> String? {
/// Alipay allows all characters, but "!*'();:@&=+$,/?%#[]".
let characterset = CharacterSet(charactersIn: "!*'();:@&=+$,/?%#[]").inverted
/// Contains the UTF8 encoded Message object with the order's description.
let message = try ClearMessage(string: description, using: .utf8)
/// The message's signature using SHA1.
let signature = try message.signed(with: key, digestType: Signature.DigestType.sha1)
/// Percent encoded signature as base64 string.
let wrappedSignedString = signature.base64String.addingPercentEncoding(withAllowedCharacters: characterset)
// Let's be sure everything worked out as expected.
guard let signedString = wrappedSignedString, !signedString.isEmpty else { return nil }
return signedString
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment