Skip to content

Commit

Permalink
Release 2.3.0
Browse files Browse the repository at this point in the history
- added subscription support
- added sha2/3 support on IPN and API
- added one step inline support
  • Loading branch information
Craig Christenson committed Feb 11, 2024
1 parent 52f2e1f commit fd793bf
Show file tree
Hide file tree
Showing 21 changed files with 1,640 additions and 986 deletions.
Binary file added twocheckout-2.3.0.ocmod.zip
Binary file not shown.
609 changes: 353 additions & 256 deletions upload/admin/controller/extension/payment/twocheckout_cplus.php

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions upload/admin/controller/extension/payment/twocheckout_inline.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ public function index()
$data['action'] = $this->url->link('extension/payment/twocheckout_inline', 'user_token=' . $this->session->data['user_token'], true);
$data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true);
$data['geo_zones'] = $this->model_localisation_geo_zone->getGeoZones();
$data['inline_types'] = [
[
'inline_type_id' => 'inline-one-step',
'name' => 'One step inline',
'description' => 'One step inline',
],
[
'inline_type_id' => 'inline',
'name' => 'Multi step inline',
'description' => 'Multi step inline',
],
];

$data[self::NAME . '_ipn'] = HTTPS_CATALOG . 'index.php?route=extension/payment/twocheckout_inline/ipn';
$data[self::NAME . '_account'] = isset($post_data[self::NAME . '_account']) ?
Expand Down Expand Up @@ -83,6 +95,9 @@ public function index()
$data[self::NAME . '_sort_order'] = isset($post_data[self::NAME . '_sort_order']) ?
$post_data[self::NAME . '_sort_order'] : $this->config->get(self::NAME . '_sort_order');

$data[self::NAME . '_inline_type_id'] = isset($post_data[self::NAME . '_inline_type_id']) ?
$post_data[self::NAME . '_inline_type_id'] : $this->config->get(self::NAME . '_inline_type_id');

$data['header'] = $this->load->controller('common/header');
$data['column_left'] = $this->load->controller('common/column_left');
$data['footer'] = $this->load->controller('common/footer');
Expand Down
54 changes: 30 additions & 24 deletions upload/admin/language/en-gb/extension/payment/twocheckout_cplus.php
Original file line number Diff line number Diff line change
@@ -1,43 +1,49 @@
<?php
// Heading
$_['heading_title'] = '2Checkout Convert Plus';
$_['heading_title'] = '2Checkout Convert Plus';

// Text
$_['text_extension'] = 'Extensions';
$_['text_success'] = 'Success: You have modified the 2Checkout account details.';
$_['text_edit'] = 'Edit 2Checkout Convert Plus';
$_['text_refund'] = 'Refund';
$_['text_total_amount_refund']= 'Total amount that can be refunded: ';
$_['text_refund_final'] = 'Refunded';
$_['text_twocheckout_cplus'] = '<a href="https://www.2checkout.com" target="_blank"><img src="view/image/payment/2checkout_verifone.png" alt="2Checkout Convert Plus logo" title="2Checkout Convert Plus" style="border: 1px solid #eeeeee;" /></a>';
$_['text_extension'] = 'Extensions';
$_['text_success'] = 'Success: You have modified the 2Checkout account details.';
$_['text_edit'] = 'Edit 2Checkout Convert Plus';
$_['text_refund'] = 'Refund';
$_['text_cancel'] = 'Canceled';
$_['text_total_amount_refund'] = 'Total amount that can be refunded: ';
$_['text_refund_final'] = 'Refunded';
$_['text_notify_recurring_fail'] = 'Recurring Transaction Failed:';
$_['text_notify_recurring_success'] = 'Recurring Transaction Successful:';
$_['text_ok'] = 'OK';
$_['text_confirm_cancel'] = 'Are you sure you want to cancel the recurring payments?';
$_['text_order_history_cancel'] = 'An administrator has canceled your recurring payments. Your card will no longer be charged.';
$_['text_canceled_success'] = 'Success: You have succesfully canceled this payment!';
$_['text_twocheckout_cplus'] = '<a href="https://www.2checkout.com" target="_blank"><img src="view/image/payment/2checkout_verifone.png" alt="2Checkout Convert Plus logo" title="2Checkout Convert Plus" style="border: 1px solid #eeeeee;" /></a>';
// Entry
$_['entry_account'] = '2Checkout Account ID';
$_['entry_secret_word'] = 'Secret Word';
$_['entry_secret_key'] = 'Secret Key';
$_['entry_test'] = 'Test Mode';
$_['entry_display'] = 'Direct Checkout';
$_['entry_total'] = 'Total';
$_['entry_order_status'] = 'Order Status';
$_['entry_pending_status'] = 'Pending Status ';
$_['entry_account'] = '2Checkout Account ID';
$_['entry_secret_word'] = 'Secret Word';
$_['entry_secret_key'] = 'Secret Key';
$_['entry_test'] = 'Test Mode';
$_['entry_display'] = 'Direct Checkout';
$_['entry_total'] = 'Total';
$_['entry_order_status'] = 'Order Status';
$_['entry_pending_status'] = 'Pending Status ';
$_['entry_processing_status'] = 'Processing Status ';
$_['entry_canceled_status'] = 'Canceled Status';
$_['entry_failed_status'] = 'Failed Status';
$_['entry_canceled_status'] = 'Canceled Status';
$_['entry_failed_status'] = 'Failed Status';
$_['entry_chargeback_status'] = 'Chargeback Status';
$_['entry_refunded_status'] = 'Refunded Status';
$_['entry_geo_zone'] = 'Geo Zone';
$_['entry_status'] = 'Status';
$_['entry_sort_order'] = 'Sort Order';
$_['entry_refunded_status'] = 'Refunded Status';
$_['entry_geo_zone'] = 'Geo Zone';
$_['entry_status'] = 'Status';
$_['entry_sort_order'] = 'Sort Order';
$_['entry_ipn'] = 'IPN url';

// Help
$_['help_ipn'] = 'Add this url to your 2Checkout admin area (under IPN section) in order to get the order status updated each time';
$_['help_secret_word'] = 'The secret word to confirm transactions with (must be the same as defined on the merchant account configuration page).';
$_['help_secret_key'] = 'The secret key (from your 2Checkout account) is required to authenticate into 2Checkout.';
$_['help_total'] = 'The checkout total the order must reach before this payment method becomes active.';
$_['help_entry_processing_status'] = 'This status is used for setting order status after payment callback.';

// Error
$_['error_permission'] = 'Warning: You do not have permission to modify 2Checkout!';
$_['error_permission'] = 'Warning: You do not have permission to modify 2Checkout!';
$_['error_account'] = 'Account No. Required!';
$_['error_secret_word'] = 'Secret Word Required!';
$_['error_secret_key'] = 'Secret Key Required!';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
$_['entry_status'] = 'Status';
$_['entry_sort_order'] = 'Sort Order';
$_['entry_ipn'] = 'IPN url';
$_['entry_inline_type'] = 'Template';

// Help
$_['help_ipn'] = 'Add this url to your 2Checkout admin area (under IPN section) in order to get the order status updated each time';
Expand Down
4 changes: 2 additions & 2 deletions upload/admin/model/extension/payment/twocheckout_api.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ private function getHeaders()
}
$gmtDate = gmdate('Y-m-d H:i:s');
$string = strlen($sellerId) . $sellerId . strlen($gmtDate) . $gmtDate;
$hash = hash_hmac('md5', $string, $secretKey);
$hash = hash_hmac('sha3-256', $string, $secretKey);

$headers[] = 'Content-Type: application/json';
$headers[] = 'Accept: application/json';
$headers[] = 'X-Avangate-Authentication: code="' . $sellerId . '" date="' . $gmtDate . '" hash="' . $hash . '"';
$headers[] = 'X-Avangate-Authentication: code="' . $sellerId . '" date="' . $gmtDate . '" hash="' . $hash . '" algo="sha3-256"';

return $headers;
}
Expand Down
45 changes: 43 additions & 2 deletions upload/admin/model/extension/payment/twocheckout_cplus.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ class ModelExtensionPaymentTwocheckoutCplus extends Model {
const API_URL = 'https://api.2checkout.com/rest/';
const API_VERSION = '6.0';

const RECURRING_ACTIVE = 1;
const RECURRING_INACTIVE = 2;
const RECURRING_CANCELLED = 3;
const RECURRING_SUSPENDED = 4;
const RECURRING_EXPIRED = 5;
const RECURRING_PENDING = 6;

/**
* install the module & creates the table
*/
Expand Down Expand Up @@ -57,11 +64,11 @@ private function getHeaders() {
}
$gmtDate = gmdate( 'Y-m-d H:i:s' );
$string = strlen( $sellerId ) . $sellerId . strlen( $gmtDate ) . $gmtDate;
$hash = hash_hmac( 'md5', $string, $secretKey );
$hash = hash_hmac( 'sha3-256', $string, $secretKey );

$headers[] = 'Content-Type: application/json';
$headers[] = 'Accept: application/json';
$headers[] = 'X-Avangate-Authentication: code="' . $sellerId . '" date="' . $gmtDate . '" hash="' . $hash . '"';
$headers[] = 'X-Avangate-Authentication: code="' . $sellerId . '" date="' . $gmtDate . '" hash="' . $hash . '" algo="sha3-256"';

return $headers;
}
Expand Down Expand Up @@ -106,6 +113,36 @@ private function call( $endpoint, $params = [], $method = 'GET' ) {
}
}

/**
* @param $reference
* @return mixed
* @throws Exception
*/
public function removeSubscription($reference){
{
try {
$url = self::API_URL . self::API_VERSION . '/subscriptions/'.$reference.'/';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeaders());
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');

$response = curl_exec($ch);
if ($response === false) {
exit(curl_error($ch));
}
curl_close($ch);

return json_decode($response, true);
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}
}

/**
* @param $order_id
* @param string $comment
Expand Down Expand Up @@ -189,4 +226,8 @@ private function updateRefundTransaction($order_id, $comment)
`date_added` = NOW()
");
}

public function editOrderRecurringStatus($order_recurring_id, $status) {
$this->db->query("UPDATE `" . DB_PREFIX . "order_recurring` SET `status` = '" . (int)$status . "' WHERE `order_recurring_id` = '" . (int)$order_recurring_id . "'");
}
}
4 changes: 2 additions & 2 deletions upload/admin/model/extension/payment/twocheckout_inline.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ private function getHeaders()
}
$gmtDate = gmdate('Y-m-d H:i:s');
$string = strlen($sellerId) . $sellerId . strlen($gmtDate) . $gmtDate;
$hash = hash_hmac('md5', $string, $secretKey);
$hash = hash_hmac('sha3-256', $string, $secretKey);

$headers[] = 'Content-Type: application/json';
$headers[] = 'Accept: application/json';
$headers[] = 'X-Avangate-Authentication: code="' . $sellerId . '" date="' . $gmtDate . '" hash="' . $hash . '"';
$headers[] = 'X-Avangate-Authentication: code="' . $sellerId . '" date="' . $gmtDate . '" hash="' . $hash . '" algo="sha3-256"';

return $headers;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<div id="twocheckout_cplus_buttons" class="form-control-static buttons clearfix">
{% if order_recurring_id and false %}
<div class="pull-right">
<button type="button" id="button-cancel" data-loading-text="{{ text_loading }}" class="btn btn-danger">{{ button_text }}</button>
</div>
{% endif %}
</div>
<script type="text/javascript">
$(document).ready(function() {
let addOrderHistory = function(success_callback) {
$.ajax({
url: '{{ catalog }}index.php?route=api/order/history&api_token={{ api_token }}&order_id={{ order_id }}',
type: 'post',
dataType: 'json',
data: 'order_status_id={{ order_status_id }}&notify={{ notify }}&override=0&append=0&comment=' + encodeURIComponent("{{ comment }}"),
success: function(json) {
if (json['error']) {
$('#twocheckout_cplus_buttons').before('<div class="alert alert-danger"><i class="fa fa-exclamation-circle"></i> ' + json['error'] + ' <button type="button" class="close" data-dismiss="alert">&times;</button></div>');
}
if (json['success']) {
success_callback();
}
},
error: function(xhr, ajaxOptions, thrownError) {
$('#twocheckout_cplus_buttons').before('<div class="alert alert-danger"><i class="fa fa-exclamation-circle"></i> ' + thrownError + "<br />" + xhr.statusText + "<br />" + xhr.responseText + ' <button type="button" class="close" data-dismiss="alert">&times;</button></div>');
}
});
}
$(document).delegate('#button-cancel', 'click', function() {
if (!confirm("{{ text_confirm_cancel }}")) {
return false;
}
$.ajax({
url: '{{ cancel }}',
dataType: 'json',
beforeSend: function() {
$('#button-cancel').button('loading');
},
success: function(json) {
$('.alert').remove();
if (json['success']) {
addOrderHistory(function() {
$('#twocheckout_cplus_buttons').before('<div class="alert alert-success">' + json['success'] + '<button type="button" class="close" data-dismiss="alert">&times;</button></div>');
$('#button-cancel').hide();
});
}
if (json['error']) {
$('#twocheckout_cplus_buttons').before('<div class="alert alert-danger">' + json['error'] + '<button type="button" class="close" data-dismiss="alert">&times;</button></div>');
$('#button-cancel').button('reset');
}
},
error: function(xhr, ajaxOptions, thrownError) {
alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
}
});
});
});
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,19 @@
placeholder="{{ entry_sort_order }}" id="payment_twocheckout_inline_sort_order" class="form-control"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label" for="payment_twocheckout_inline_order_status_id">{{ entry_inline_type }}</label>
<div class="col-sm-9">
<select name="payment_twocheckout_inline_inline_type_id" id="payment_twocheckout_inline_inline_type_id" class="form-control">
{% for inline_type in inline_types %}
<option value="{{ inline_type.inline_type_id }}"
{{ inline_type.inline_type_id == payment_twocheckout_inline_inline_type_id ? 'selected':'' }}>
{{ inline_type.name }}
</option>
{% endfor %}
</select>
</div>
</div>
</form>
</div>
</div>
Expand Down
29 changes: 26 additions & 3 deletions upload/catalog/controller/extension/payment/twocheckout_api.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ public function ipn()
}
$secret_key = $this->config->get('payment_twocheckout_api_secret_key');
$params = $this->request->post;
$hash = $this->extractHashFromParams($params);
$order = $this->model_checkout_order->getOrder($params['REFNOEXT']);

// ignore all other payment methods
Expand All @@ -176,15 +177,37 @@ public function ipn()
throw new Exception(sprintf('Cannot identify order: "%s".', $params['REFNOEXT']));
}

if (!$model->isIpnResponseValid($params, $secret_key)) {
throw new Exception(sprintf('MD5 hash mismatch for 2Checkout IPN with date: "%s".', $params['IPN_DATE']));
if (!$model->isIpnResponseValid($params, $secret_key, $hash)) {
throw new Exception(sprintf('Hash mismatch for 2Checkout IPN with date: "%s".', $params['IPN_DATE']));
}

$model->processOrderStatus($params);

echo $model->calculateIpnResponse($params, $secret_key);
echo $model->calculateIpnResponse($params, $secret_key, $hash['algo']);
}
exit();
}

/**
* @params array $params
* @return array [hash, algo]
*/
protected function extractHashFromParams($params): array {
if (!empty($params['SIGNATURE_SHA3_256'])) {
$receivedAlgo = 'sha3-256';
$receivedHash = $params['SIGNATURE_SHA3_256'];
}

if (empty($receivedHash) && !empty($params['SIGNATURE_SHA2_256'])) {
$receivedAlgo = 'sha256';
$receivedHash = $params['SIGNATURE_SHA2_256'];
}

if (empty($receivedHash)) {
$receivedAlgo = 'md5';
$receivedHash = $params['HASH'];
}

return ['hash' => $receivedHash, 'algo' => $receivedAlgo];
}
}
Loading

0 comments on commit fd793bf

Please sign in to comment.