иллюстрировать
Сделал WeChat. После платежей Alipay и Jingdong я обнаружил, что самым нелепым платежом должен быть платеж Jingdong.Чтобы полностью разработать платеж Jingdong, вы должны прочитать каждый уголок официального сайта документации разработчика платежей Jingdong.Вы не должны гадать, основываясь на каком-либо опыте.Процессы, такие как шифрование и дешифрование с открытым и закрытым ключом (не смотрите на официальный сайт, я гарантирую, вы пожалеете об этом), способ отправки запросов (отправка формы, вы найдете это странным после прочтения официального сайта), синхронизация платежей jump (post, fk), после успешной оплаты Возврат не оплатили за номер заказа (поддерживать его полностью самостоятельно, fk)
техническое описание
Сначала зайдите на официальный сайт:payapi.jd.com/. В проекте используется оплата веб-страницы ПК
1. Единый интерфейс заказа:wepay.jd.com/jdpay/sa...
Описание параметра:payapi.jd.com/docList....
Обязательно внимательно прочитайте описание этих параметров
Специальные параметры объясняются следующим образом:
1) В приведенных выше параметрах запроса номер продавца выделяется пользователю системой управления платежным продавцом JD, когда функция оплаты JD регистрируется и активируется.
2) Учетная запись пользователя — это учетная запись пользователя торговой системы.
3) Серийный номер транзакции — это номер, используемый для идентификации каждого платежного запроса.Продавец должен убедиться, что серийный номер транзакции уникален для каждого платежного запроса.Один и тот же серийный номер транзакции не может использоваться для нескольких запросов, в противном случае платежная служба Jingdong обработает последующий платеж. При запросе эта транзакция будет считаться дублирующим платежом.
4) Подробнее о правилах подписи см.: «Спецификация безопасности интерфейса — Алгоритм подписи»;
5) Для обеспечения информационной безопасности все поля в форме зашифрованы с помощью 3DES кроме торговца (номер мерчанта), номера версии (версия) и подписи (знак).
2. Создайте подпись
Процесс подписи делится на два шага: сначала исходные параметры склеиваются в строку S1 по правилам, а затем S1 используется для генерации знака строки подписи согласно алгоритму подписи. Правила сплайсинга исходной строки параметра:
1) Для параметров, отправленных формой POST: все параметры сортируются по ASCII-коду имен параметров от меньшего к большему (лексикографический порядок) и объединяются в строку S1 с использованием пар ключ-значение URL, ( например: k1=значение1&k2=значение2&k3=значение3 ...) 2) Для параметров взаимодействия XML-сообщения: удалить пробелы из каждой строки XML-сообщения и напрямую объединить его в строку строк как S1. Если сообщение состоит только из одной строки, она напрямую используется как S1, и сращивание не требуется.
Процесс создания подписи выглядит следующим образом:
1) Вычислить дайджест объединенной строки параметров S1 с помощью алгоритма SHA256 для получения строки S2; 2) Использовать сертификат закрытого ключа для шифрования строки S2 и выполнить транскодирование base64 для получения знака строки подписи; Получатель получает сообщение После текста сначала выполняется декодирование base64, затем для его расшифровки используется сертификат открытого ключа, после чего проверяется действительность подписи.
Меры предосторожности:
1) Пустые параметры в подписи не участвуют;
2) Поле знака в списке параметров не участвует в подписи;
3) Для упрощения обработки в подписи участвует и тег
4) Параметры чувствительны к регистру;
5) Правила шифрования RSA таковы: инициатор транзакции выполняет шифрование с закрытым ключом, а получатель выполняет расшифровку с открытым ключом; (инструмент проверки открытого и закрытого ключа RSA можно использовать для проверки совпадения открытого и закрытого ключей RSA продавца)
6) Система проверит действительность сертификата открытого ключа продавца.
Код подписи:
def get_sign_str(params, is_compatible=False):
"""
生成签名的字符串
Args:
params: 签名的字典数据
is_compatible: 是否是兼容模式(对字典中value值为空的也签名)
Returns:
返回签名
"""
raw = [(k, params[k]) for k in sorted(params.keys())]
if is_compatible:
order_str = "&".join("=".join(kv) for kv in raw)
else:
order_str = "&".join("=".join(kv) for kv in raw if kv[1])
return order_str
def sign(self, prestr):
"""
生成签名
Args:
prestr(str): 生成签名的原字符串
Returns:
返回生成好的签名
"""
key = MRSA.load_key(self.MERCHANT_RSA_PRI_KEY)
signature = key.private_encrypt(self.sha256(prestr), MRSA.pkcs1_padding)
sign = base64.b64encode(signature)
return sign
3. DES3 шифрует каждый параметр (кроме продавца (номер продавца), номера версии (версия) и подписи (знак))
Чтобы предотвратить раскрытие открытых текстовых данных при отправке почтовой формы, JD.com сделал DES3 для шифрования полей (вы не можете сделать это без отправки формы, и это настолько сложно, что вам действительно нужно изучить Alipay и WeChat).
Описание шифрования Jingdong DES выглядит следующим образом:
除特定说明外,商户和京东支付接口调用报文采用3DES加密,再通过base64转换为字符串。
3DES加密算法标为DESede,工作模式为电子密码本模式ECB,不填充(DESede/ECB/NoPadding)。
注:服务端NoPadding 为不填充,所以加密的原文字节必须是8的整数倍(如果调用我们提供的加密接口API则不必处理原文字节,加密接口内部已处理)。如果自己实现加密,原文字节不够8的整数倍,则按如下规则转为8的整数倍。
1. 把原文字符串转成字节数组。
2. 根据字节数组长度判断是否需要补位。
补位逻辑为:
int x = (i+ 4) % 8;
int y = (x == 0) ? 0 : (8 - x);
i为字节数组的长度,y为需要补位的长度。
补位值为0。
3. 将有效数据长度byte[]添加到原始byte数组的头部。
i为字节数组的长度。
result[0] = (byte) ((i >> 24) & 0xFF);
result[1] = (byte) ((i >> 16) & 0xFF);
result[2] = (byte) ((i >> 8) & 0xFF);
result[3] = (byte) (i & 0xFF);
4. 原文字节数组前面加上第三步的4个字节,再加上需补位的值。
例如:字符串”1”,转换成字节数组是[49],计算补位y=3, 计算有效数据长度为[0, 0, 0, 1],最后字节数组为[0, 0, 0, 1, 49, 0, 0, 0]。
Form表单接口的加密方式:
如果商户通过表单方式提交支付请求至收银台,为保证信息安全,表单中的各个字段除了merchant(商户号)、verion(版本号)、sign(签名)以外,其余字段全部采用3DES进行加密。
XML请求接口的加密方式:
通过XML接口方式和京东支付服务器交互的请求,应该对报文进行加密,加密方式为对整个报文整体进行3DES加密,再进行base64转码使其变为可读字符串,加密后的密文置于<encrypt></encrypt>标签中,同时再将报文中的<merchant>(商户号)、<version>(版本号)这两个字段单独置于<jdpay>标签下。
接收到京东支付加密报文后的处理方式:
接收到京东支付返回的加密报文后,先判断<jdpay>标签下的<result>标签的返回码,检查接口调用是否正常返回。然后再读取<encrypt>标签的密文内容进行base64解码,再进行3DES解密,解密后的报文即是原始报文。
Образец кода:
def des_pad(data):
e = len(data)
x = (e + 4) % 8
y = 0 if x == 0 else 8 - x
sizeByte = struct.pack('>I', e)
resultByte = range(len(sizeByte) + e + y)
resultByte[0:4] = sizeByte
resultByte[4:4 + e] = data
for i in range(0, y):
resultByte[e + 4 + i] = "\x00"
resultstr = ''.join(resultByte)
return resultstr
def encode_des(to_encode_str, des_key):
"""
DES3加密数据
Args:
to_encode_str(str): 要被加密的原字符串,这里的字符串需要被des_pad一下
des_key(str): 加密的key
Returns:
"""
key = base64.b64decode(des_key)
des3 = DES3.new(key, DES3.MODE_ECB)
return des3.encrypt(ToolsClass.des_pad(to_encode_str)).encode('hex_codec')
В этом случае подпись и шифрование завершены, и тогда оно будет прописано в форме на странице.
<form method="post" action="https://wepay.jd.com/jdpay/saveOrder" id="batchForm">
<input name="merchant" type="hidden" id="merchant" value="22294531" /><br/>
<input name="notifyUrl" type="hidden" id="notifyUrl" value="da652ac3b881c4ddc2ac26793b20c37fba91a994f108bf8a0a42b5ead05111997bfe2a97eaf4aa49562de1b6d1d32cd7" /><br/>
<input name="userId" type="hidden" id="userId" value="f23f2b73027cb0f8deb349af3086fdc50f6892f17c9f45b81b6d273d0cdb1cae8151f083427fc8f0" /><br/>
<input name="sign" type="hidden" id="sign" value="SJ6qfS+9CmXkt6ghJcf9nIdHJDReTFNkRyjFh5XZAsTAtfHT4SdmKeD88t+2dMnaszJ7vVjBnSu64aJyt6SODW2FHJk0WXEvZNixmo2h8F7vHO5lTE2jEG/9uN7sqg2c7kH2Fnu5cFLCeaMfb8uZqZ8CKi+g7Aw4b6rywvoH/8M="
/><br/>
<input name="currency" type="hidden" id="currency" value="ac7132c57f10d3ce" /><br/>
<input name="orderType" type="hidden" id="orderType" value="e00c693e6c5b8a60" /><br/>
<input name="tradeNum" type="hidden" id="tradeNum" value="05439876d54534c7604c42eca17c14cdf8eece390982627a0799194a74809ee6c9d07d3cff8a7c60"
/><br/>
<input name="amount" type="hidden" id="amount" value="e5a6c3761ab9ddaf" /><br/>
<input name="version" type="hidden" id="version" value="V2.0" /><br/>
<input name="tradeTime" type="hidden" id="tradeTime" value="d9668085c69c2ecb33367c0710f42c4bc7432967ba39f140"
/><br/> <input name="tradeName" type="hidden" id="tradeName" value="3e111657e2839e3a3ba10d54bb446817e5000daf14a2e3badbf9a93316ed6003" /><br/>
<input name="callbackUrl" type="hidden" id="callbackUrl" value="51c916293675ac44c2ac26793b20c37fba91a994f108bf8a0a42b5ead05111997bfe2a97eaf4aa49229a23b8c688e767"
/><br/><input type="submit" />
</form>
Вам решать, как вы это организуете
4. Асинхронный обратный вызов
После отправки запроса вы будете перенаправлены на страницу оплаты JD.com, вы можете войти в свою учетную запись для оплаты или использовать приложение JD.com или WeChat для сканирования и оплаты.
Когда пользователь сканирует код для оплаты, JD.com будет активно переходить по указанному вами URL-адресу (это поле есть при отправке платежного запроса) и будет асинхронно отправлять запрос на указанный адрес (такое есть при отправке платежа). request)), синхронный переход происходит после того, как пользователь сканирует код для оплаты, если страница оплаты Jingdong все еще существует, он будет выполнен. Асинхронно уведомление о результате платежа будет отправлено в любом случае. Для новичков необходимо знать негласные правила этой индустрии (WeChat, Alipay или другие). И он должен основываться на результате этого асинхронного уведомления.
Jingdong возвращает строку в формате xml
Формат возврата следующий (перевода строки нет, здесь я продемонстрирую перенос строки):
<?xml version=\"1.0\" encoding=\"UTF-8\" ?>
<jdpay>
<version>V2.0</version>
<merchant>22294531</merchant>
<result> <code>000000</code> <desc>success</desc> </result>
<encrypt>MWYxMjBjMzViZjgwOWM5ZDhjNjc0YmY1ZWJlY2QyODU0YTc5NmQ3ZWQxMWU1NzE3MWQ0OTUwOGI5NzllYmE4ZjM1YzRiZjlmYWE1M2ZiYjVmYzBmYTgyMDYyM2Q0YjM0NGM1ODFkZDhlYTA2Mjk0ZDE5ZDBlZDk5NTc3MmE4Nzk4OTFlYjIwZDgzMTc4MDU3NGVkZTFjNDY0MDMzNzNjZjc2OWZiMDQ0YjVhZGNhYmRhMGZmYTkyNzRhZDNhM2IxOGY5ZjZhYjBmYjhmZmI3Yzg0OTA3YzM0OGJmZTYwZTIzNzM3YjVmYzMzNmNkYTE0MjM2OWIwZDM5MjI2YWM5YmY3ZmZjZDBkNWJmM2ZkYWY4YTU3OWU4MDE3ZjQ5YmQ0ZWIyMDA0NTFmODZkNmViMDBiMDE2YTU3NTNjMzJjNDIzNWI5ZDkyYzQ3OTU4OTc2ZGIyZmNiMGUxNGRjNTM2OGZjYjQ0NmE0YWY1ZWVjZDYzNWI5ZDkyYzQ3OTU4OTc2NmIwM2QyZTU1ODJlNDNjM2M1NjA2YmQ5ZDc3MTRkMmNjN2ZiMDM3Yzg5ZDk1ODFkMWJhZmVjYjUwMzJlNTdkMTFmN2QxMDAxNjgyMzJjNTZhMmQzNTcyZGE4OTUzYWFjNTU5MDY4YWYyODE5ZDcyNmY5NmE1YTBmYWFiZTRiZTQ2OGZhMmM4M2JjMGM5NmNiMDE3ZWQ4MDkxY2FjZThiNzg4MjY5OWY1ZTJlYzBjOTIxODBhOGExNjExNGY4NWQwM2NkZjI2MTFmM2VmODcxYWM3MjUxZjMxMzZlYjFmNzI1NWE0OWM4MjMxZGY1MzBmY2Y1Mjg2NGUzMWRlMjc0M2I5ZDM5NjQzN2ZmZWQ1Y2M5NDY4ZDcwNWM1YzVhZmRlYzYwZWU3MDVhNjE0N2I1MGVlM2UyMGE2MzExNTE4YTUxOGRjMzBmMmUxZjE2NzYzNGRiNDJlODFmMDczOGYzZjMxN2NkMjkzNmU4ODc3NzJjMjkzM2ZlODlmMjUyNDVmNDI2MDA0M2VkYmUwOTlkNGEyNjU3YTM5YTE4ODU2OTBmNGQyNDcwZDE0ZWRjMmQxYjgxMzhhNjA5M2ZlNDkxYTQyMzE5YzBlNTA0MTdkYTg2ZGQ2NDQwODBmMjM4ZGI2YzIzMjNhOTE0M2VmMjZiZjczN2M5NWQwODYxMWY2OGE5MDQ0ZDZmNzE0NmIxZjQwZDdmZDMxOTQ2ZDM3YjIwNDJiODUzZGM0NTk0MzM5YzJkN2M2NDdiNGM4MzQ4MTRjZTIxZTlmYTYzNDYxNGMxMjlhZTE3NjE0ZDIzM2Q2MTQ4YzJiNWE3ZWVjMDU5MjFmNzJkNGNjNTU1NWZkNzVhN2U5Y2I1MDU1NjhlMWRlNjVhNzkyOGUxMThlODQyMGJkNzE2NjdmMDc3YmEyYTFkNmQyOTFiOGNjZTU2ZGMyYmE3ODY5ZGZiNmMyMWViYjc2ODc0Y2I3YTc4NGQ5NWY2NjY2Y2E5NjI0N2I1MGE4MTliMDBkNGIzNmViZTJlY2JmYTcwODUzYTM5ZTcwMDVmYWEzNWY2MDFhMWM2MGQ1MzEyYmQxNDU3Zjg4ZWVhNzY2YjZhOGE4ZGMxMGY3NjYwOWEzNWY2MDFhMWM2MGQ1MzFhNzA4NTNhMzllNzAwNWZhYTYxMmJmNjJiMmFlMGY5ODMxMzQ0MzQ0NjMxZDc3MTUyY2FiMjZlMjcyYmJjYmQzODVmNDY4OTA5YTdjMjlmNTI5NWFlZjE3NTI4ZmE4MzVhNzA4NTNhMzllNzAwNWZhNDk5OTQ2ZGU0OGU0NGQ2ZTE4YmRiYTBjZjNhM2ZkNjY5ODJjNGVhZjQzMjIyYWFhMWM0ZmU1ODRiNTg5OWEwYzAwNjI2NTllMDZkYzhiYTVmMjI3ZjUyYmQ3MjcyODllZmEwYzhiNDIwODc4ZjUzODY1MzAzZDkyNDM5OTRkNDczMTBjZDBhMTc4ZjAwOTIyZmM2ODk5YjkyYTJiODcwNjU4MzkzMzJkZWYzNDY1MzJlYTNiYTFhNjM0MWIwNjM4NjBjNjlmMzg1NWZjZWM5YWExMDdjZWY1MjkwZTZjMzgzOGYxNTRiNzFlN2E1YTczYWFkNzJlOTRiOWI3MmI2YWYyMTJjMjQ5Y2UzMmUxMGI4YWE0N2YzYzFmNjNiOGY4NjJlZmU1ZDM5NjcwODA3MGNjY2JjYWFkYjM3NzBmMGQzYjIyMGFmZTE3YWNjZWU1N2RmZTQxMzAxYjA2MDdlMg==</encrypt>
</jdpay>
Сначала используйте DES3 для расшифровки строки в узле шифрования.
def un_des_pad(data):
resultByte = data[0:4]
e = struct.unpack('>I', resultByte)[0]
x = (e + 4) % 8
y = 0 if x == 0 else 8 - x
return data[4:] if y == 0 else data[4:-y]
def decode_des(to_decode_str, des_key):
"""
解密数据
Args:
to_decode_str(str): 要解密的原字符串
des_key(str): 解密的key
Returns:
"""
key = base64.b64decode(des_key)
des3 = DES3.new(key, DES3.MODE_ECB)
param = to_decode_str.decode("hex_codec") if to_decode_str is bytes else base64.b64decode(to_decode_str).decode(
"hex_codec")
param = des3.decrypt(param)
return ToolsClass.un_des_pad(param)
sign_begin = xml_data.find('<encrypt>')
sign_end = xml_data.find('</encrypt>')
encrypt_str = xml_data[sign_begin + 9:sign_end]
xml_str = JdPay.decode_des(encrypt_str, deskey)
Расшифрованный открытый текст выглядит следующим образом:
<?xml version="1.0" encoding="UTF-8" >
<jdpay>
<version>V2.0</version>
<merchant>110290193003</merchant>
<result>
<code>000000</code>
<desc>success</desc>
</result>
<device>6220</device>
<sign>SJ6qfS+9CmXkt6ghJcf9nIdHJDReTFNkRyjFh5XZAsTAtfHT4SdmKeD88t+2dMnaszJ7vVjBnSu64aJyt6SODW2FHJk0WXEvZNixmo2h8F7vHO5lTE2jEG/9uN7sqg2c7kH2Fnu5cFLCeaMfb8uZqZ8CKi+g7Aw4b6rywvoH/8M=</sign>
<tradeNum>201704250935156041484635</tradeNum>
<tradeType>0</tradeType>
<amount>3140</amount>
<status>2</status>
<payList>
<pay>
<payType>3</payType>
<amount>1500</amount>
<currency>CNY</currency>
<tradeTime>20170425093516</tradeTime>
</pay>
<pay>
<payType>1</payType>
<amount>1640</amount>
<currency>CNY</currency>
<tradeTime>20170425093516</tradeTime>
<detail>
<cardHolderMobile>150****1596</cardHolderMobile>
</detail>
</pay>
</payList>
</jdpay>
После расшифровки необходимо проверить правильность подписи, получить подпись из приведенной выше строки и удалить строку после подписи.
def verify_mysign(cls, sign, xml_str, jd_public_key):
"""
验证签名
Args:
sign: 签名
xml_str: 去除签名后的xml字符串
jd_public_key: 用于验证的key
Returns:
"""
xml_sha_str = SHA256.new(xml_str).hexdigest()
key = MRSA.load_pub_key(jd_public_key)
signature = key.public_decrypt(base64.standard_b64decode(sign),
MRSA.pkcs1_padding)
return signature == xml_sha_str
После прохождения проверки верните строку xml с удаленным знаком и извлеките содержимое внутри (подробности см. в официальной документации о значении параметров)
5. Синхронный прыжок
Про синхронный прыжок и говорить нечего, просто дайте адрес перехода, но здесь надо отметить, что это post request (похоже, JD.com любит post за все подряд), а не get request от WeChat или Alipay или других . Так что не устанавливайте это неправильно
Ну вот и завершен полный онлайн платеж. Здесь также следует отметить, что когда речь идет о шифровании и дешифровании, должен быть ключ, симметричный ключ шифрования, используемый DES3, и асимметричные открытый и закрытый ключи, используемые для подписи. Поэтому не забудьте настроить его хорошо. Здесь я использую тестовый номер продавца, предоставленный JD.com в исходном коде.Также есть ключ, установленный JD.com.В частности, мне нужно загрузить JD.com [документ интерфейса JD.com для ПК и H5].Есть учетная запись информация в нижней части документа.
В демо так же есть интерфейсы подачи заявок на возврат и отмены заказов, по сути можно написать процесс завершения одного интерфейса, а можно напрямую применить другие процессы.